[gradle-1.12] 57/211: Upstream import 1.0

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:17:59 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 b0c81457ef0bef44b3bb62e3c34a0ee34a32c5ec
Author: Miguel Landaeta <miguel at miguel.cc>
Date:   Mon Jul 2 20:13:08 2012 -0430

    Upstream import 1.0
---
 build.gradle                                       |  635 +++----
 buildSrc/build.gradle                              |   27 +-
 .../org/gradle/build/GenerateReleasesXml.groovy    |   42 +
 .../main/groovy/org/gradle/build/Releases.groovy   |   90 +
 .../main/groovy/org/gradle/build/Version.groovy    |  102 +-
 .../build/docs/AssembleSampleDocsTask.groovy       |    4 +-
 .../gradle/build/docs/DocGenerationException.java  |   25 +
 .../org/gradle/build/docs/Docbook2XHtml.groovy     |   87 +
 .../gradle/build/docs/ExtractSnippetsTask.groovy   |    2 +-
 .../build/docs/UserGuideTransformTask.groovy       |   60 +-
 .../build/docs/XIncludeAwareXmlProvider.groovy     |   22 +-
 .../org/gradle/build/docs/XslTransformer.java      |   60 +
 .../gradle/build/docs/dsl/ClassLinkMetaData.java   |   81 +-
 .../build/docs/dsl/ExtractDslMetaDataTask.groovy   |   11 +-
 .../org/gradle/build/docs/dsl/LinkMetaData.java    |    2 +-
 .../gradle/build/docs/dsl/TypeNameResolver.java    |    4 +-
 .../docs/dsl/docbook/AssembleDslDocTask.groovy     |  102 +-
 .../gradle/build/docs/dsl/docbook/ClassDoc.groovy  |   34 +-
 .../dsl/docbook/ClassDocGenerationException.java   |   24 +
 .../docs/dsl/docbook/ClassExtensionDoc.groovy      |   64 +-
 .../build/docs/dsl/docbook/DslDocModel.groovy      |   22 +-
 .../docs/dsl/docbook/ExtensionMetaData.groovy      |   31 -
 .../build/docs/dsl/docbook/JavadocConverter.java   |   67 +-
 .../build/docs/dsl/docbook/JavadocScanner.java     |    4 +-
 .../build/docs/dsl/docbook/LinkRenderer.java       |    5 +-
 .../build/docs/dsl/docbook/PropertyDoc.groovy      |    3 +
 .../docs/dsl/model/ClassExtensionMetaData.groovy   |   34 +
 .../build/docs/dsl/model/ExtensionMetaData.groovy  |   28 +
 .../build/docs/dsl/model/MethodMetaData.java       |    3 +
 .../build/docs/dsl/model/MixinMetaData.groovy      |   26 +
 .../build/samples/WrapperProjectCreator.groovy     |   52 -
 .../build/startscripts/StartScriptGenerator.groovy |   59 -
 .../groovy/org/gradle/plugins/jsoup/Jsoup.groovy   |   59 +
 .../gradle/plugins/jsoup/JsoupFilterReader.groovy  |   58 +
 .../org/gradle/plugins/jsoup/JsoupPlugin.groovy    |   33 +
 .../org/gradle/plugins/pegdown/PegDown.groovy      |   76 +
 .../gradle/plugins/pegdown/PegDownPlugin.groovy    |   33 +
 .../META-INF/gradle-plugins/jsoup.properties       |    1 +
 .../META-INF/gradle-plugins/pegdown.properties     |    1 +
 .../build/startscripts/unixStartScriptHead.txt     |   85 -
 .../build/startscripts/unixStartScriptTail.txt     |   99 -
 .../build/startscripts/windowsStartScriptHead.txt  |   59 -
 .../build/startscripts/windowsStartScriptTail.txt  |   23 -
 .../groovy/org/gradle/build/ReleasesTest.groovy    |  116 ++
 .../docs/dsl/ExtractDslMetaDataTaskTest.groovy     |    2 +-
 .../build/docs/dsl/docbook/ClassDocTest.groovy     |   61 +-
 .../docs/dsl/docbook/JavadocConverterTest.groovy   |   24 +-
 config/checkstyle/checkstyle.xml                   |   15 +-
 config/checkstyle/required-header.txt              |    8 +-
 config/checkstyle/suppressions.xml                 |    5 +
 config/codenarc.xml                                |    7 +-
 gradle.properties                                  |    2 -
 gradle/classycle.gradle                            |   32 +
 gradle/codeQuality.gradle                          |   25 +-
 gradle/conventions-dsl.gradle                      |   18 +
 gradle/eclipse.gradle                              |   16 +
 gradle/groovyProject.gradle                        |   71 +
 gradle/idea.gradle                                 |  263 +++
 gradle/integTest.gradle                            |   77 +-
 gradle/providedConfiguration.gradle                |   19 +
 gradle/publish.gradle                              |   24 +-
 gradle/resumeBuild.gradle                          |   10 +
 gradle/ssh.gradle                                  |   54 -
 gradle/testFixtures.gradle                         |   54 +
 gradle/testWithUnknownOS.gradle                    |    7 +
 gradle/wrapper/gradle-wrapper.properties           |    4 +-
 gradlew                                            |  108 +-
 gradlew.bat                                        |   52 +-
 settings.gradle                                    |   10 +
 src/toplevel/LICENSE                               |  508 +++++
 src/toplevel/NOTICE                                |    1 +
 src/toplevel/changelog.txt                         |    3 +-
 src/toplevel/init.d/readme.txt                     |    1 +
 src/toplevel/media/gradle-icon-128x128.png         |  Bin 0 -> 12804 bytes
 src/toplevel/media/gradle-icon-16x16.png           |  Bin 0 -> 3387 bytes
 src/toplevel/media/gradle-icon-24x24.png           |  Bin 0 -> 3887 bytes
 src/toplevel/media/gradle-icon-256x256.png         |  Bin 0 -> 31871 bytes
 src/toplevel/media/gradle-icon-32x32.png           |  Bin 0 -> 4348 bytes
 src/toplevel/media/gradle-icon-48x48.png           |  Bin 0 -> 5934 bytes
 src/toplevel/media/gradle-icon-512x512.png         |  Bin 0 -> 80027 bytes
 src/toplevel/media/gradle-icon-64x64.png           |  Bin 0 -> 6455 bytes
 src/toplevel/media/gradle.icns                     |  Bin 0 -> 241431 bytes
 subprojects/announce/announce.gradle               |    9 +-
 .../BuildAnnouncementsPluginIntegrationTest.groovy |   92 +
 .../api/plugins/announce/AnnouncePlugin.groovy     |   51 +-
 .../announce/AnnouncePluginExtension.groovy        |   79 +
 .../announce/BuildAnnouncementsPlugin.groovy       |   34 +
 .../internal/AnnouncerUnavailableException.groovy  |   29 +
 .../internal/AnnouncingBuildListener.groovy        |   87 +
 .../internal/DefaultAnnouncerFactory.groovy        |   43 +-
 .../announce/internal/DefaultIconProvider.groovy   |   35 +
 .../api/plugins/announce/internal/Growl.groovy     |   17 +-
 .../internal/GrowlNotifyBackedAnnouncer.groovy     |   49 +
 .../plugins/announce/internal/IconProvider.java    |   25 +
 .../internal/IgnoreUnavailableAnnouncer.groovy     |   38 +
 .../plugins/announce/internal/NotifySend.groovy    |   28 +-
 .../api/plugins/announce/internal/Snarl.groovy     |   24 +-
 .../jdk6/AppleScriptBackedGrowlAnnouncer.groovy    |   62 +
 .../gradle-plugins/build-announcements.properties  |    1 +
 .../announce/AnnouncePluginConventionTest.groovy   |   55 -
 .../announce/AnnouncePluginExtensionTest.groovy    |   73 +
 .../api/plugins/announce/AnnouncePluginTest.groovy |    4 +-
 .../announce/BuildAnnouncementsPluginTest.groovy   |   33 +
 .../internal/AnnouncingBuildListenerTest.groovy    |  127 ++
 .../internal/DefaultAnnouncerFactoryTest.groovy    |   61 +-
 .../internal/DefaultIconProviderTest.groovy        |   54 +
 .../internal/IgnoreUnavailableAnnouncerTest.groovy |   49 +
 .../announce/internal/NotifySendTest.groovy        |   40 -
 .../api/plugins/announce/internal/SnarlTest.groovy |   41 -
 subprojects/antlr/antlr.gradle                     |   17 +-
 .../org/gradle/api/plugins/antlr/AntlrPlugin.java  |   36 +-
 subprojects/base-services/base-services.gradle     |   15 +
 .../main/java/org/gradle/api/internal/Factory.java |   23 +
 .../api/internal/project/ServiceRegistry.java      |   23 +
 .../org/gradle/internal/CompositeStoppable.java    |  139 ++
 .../src/main/java/org/gradle/internal/Factory.java |   29 +
 .../main/java/org/gradle/internal/Stoppable.java   |   28 +
 .../java/org/gradle/internal/SystemProperties.java |   44 +
 .../org/gradle/internal/UncheckedException.java    |   53 +
 .../org/gradle/internal/concurrent/ThreadSafe.java |   23 +
 .../org/gradle/internal/jvm/JavaHomeException.java |   26 +
 .../java/org/gradle/internal/jvm/JavaInfo.java     |   66 +
 .../src/main/java/org/gradle/internal/jvm/Jvm.java |  323 ++++
 .../org/gradle/internal/os/OperatingSystem.java    |  278 +++
 .../internal/service/DefaultServiceRegistry.java   |  459 +++++
 .../gradle/internal/service/ServiceRegistry.java   |   53 +
 .../internal/service/UnknownServiceException.java  |   29 +
 .../gradle/internal/CompositeStoppableTest.groovy  |  145 ++
 .../org/gradle/internal/jvm/AppleJvmTest.groovy    |   94 +
 .../groovy/org/gradle/internal/jvm/JvmTest.groovy  |  286 +++
 .../gradle/internal/os/OperatingSystemTest.groovy  |  248 +++
 .../service/DefaultServiceRegistryTest.java        |  492 +++++
 subprojects/cli/cli.gradle                         |   27 +
 .../gradle/cli/AbstractCommandLineConverter.java   |   36 +
 .../AbstractPropertiesCommandLineConverter.java    |   48 +
 .../gradle/cli/CommandLineArgumentException.java   |   31 +
 .../java/org/gradle/cli/CommandLineConverter.java  |   31 +
 .../java/org/gradle/cli/CommandLineOption.java     |  107 ++
 .../java/org/gradle/cli/CommandLineParser.java     |  519 ++++++
 .../java/org/gradle/cli/ParsedCommandLine.java     |  107 ++
 .../org/gradle/cli/ParsedCommandLineOption.java    |   41 +
 .../cli/ProjectPropertiesCommandLineConverter.java |   35 +
 .../cli/SystemPropertiesCommandLineConverter.java  |   34 +
 ...stractPropertiesCommandLineConverterTest.groovy |   73 +
 .../org/gradle/cli/CommandLineParserTest.groovy    |  621 +++++++
 .../org/gradle/cli/ParsedCommandLineTest.groovy    |   42 +
 ...rojectPropertiesCommandLineConverterTest.groovy |   32 +
 ...SystemPropertiesCommandLineConverterTest.groovy |   32 +
 subprojects/code-quality/code-quality.gradle       |   25 +-
 .../quality/CheckstylePluginIntegrationTest.groovy |  127 ++
 .../quality/CodeNarcPluginIntegrationTest.groovy   |  122 ++
 .../CodeQualityPluginIntegrationTest.groovy        |  206 +++
 .../quality/FindBugsPluginIntegrationTest.groovy   |  143 ++
 .../quality/JDependPluginIntegrationTest.groovy    |  128 ++
 .../quality/PmdPluginIntegrationTest.groovy        |   99 +
 .../internal/FindBugsSpecBuilderTest.groovy        |  117 ++
 .../gradle/api/plugins/quality/Checkstyle.groovy   |  169 ++
 .../org/gradle/api/plugins/quality/Checkstyle.java |  133 --
 .../api/plugins/quality/CheckstyleExtension.groovy |   29 +
 .../api/plugins/quality/CheckstylePlugin.groovy    |   77 +
 .../api/plugins/quality/CheckstyleReports.java     |   34 +
 .../org/gradle/api/plugins/quality/CodeNarc.groovy |  145 ++
 .../org/gradle/api/plugins/quality/CodeNarc.java   |  107 --
 .../api/plugins/quality/CodeNarcExtension.groovy   |   38 +
 .../api/plugins/quality/CodeNarcPlugin.groovy      |   85 +
 .../api/plugins/quality/CodeNarcReports.java       |   48 +
 .../plugins/quality/CodeQualityExtension.groovy    |   42 +
 .../api/plugins/quality/CodeQualityPlugin.groovy   |   86 +-
 .../org/gradle/api/plugins/quality/FindBugs.groovy |  128 ++
 .../api/plugins/quality/FindBugsExtension.groovy   |   24 +
 .../api/plugins/quality/FindBugsPlugin.groovy      |  113 ++
 .../api/plugins/quality/FindBugsReports.java       |   44 +
 .../GroovyCodeQualityPluginConvention.groovy       |    3 +-
 .../org/gradle/api/plugins/quality/JDepend.groovy  |  108 ++
 .../api/plugins/quality/JDependExtension.groovy    |   24 +
 .../api/plugins/quality/JDependPlugin.groovy       |   94 +
 .../gradle/api/plugins/quality/JDependReports.java |   44 +
 .../org/gradle/api/plugins/quality/Pmd.groovy      |  103 ++
 .../gradle/api/plugins/quality/PmdExtension.groovy |   71 +
 .../gradle/api/plugins/quality/PmdPlugin.groovy    |   90 +
 .../org/gradle/api/plugins/quality/PmdReports.java |   40 +
 .../internal/AbstractCodeQualityPlugin.groovy      |  126 ++
 .../plugins/quality/internal/AntCheckstyle.groovy  |   42 -
 .../plugins/quality/internal/AntCodeNarc.groovy    |   45 -
 .../quality/internal/CheckstyleReportsImpl.java    |   35 +
 .../quality/internal/CodeNarcReportsImpl.java      |   46 +
 .../quality/internal/ConsoleReportWriter.java      |   53 -
 .../quality/internal/FindBugsReportsImpl.java      |   41 +
 .../quality/internal/JDependReportsImpl.java       |   42 +
 .../plugins/quality/internal/PmdReportsImpl.java   |   41 +
 .../quality/internal/findbugs/FindBugsDaemon.java  |   23 +
 .../internal/findbugs/FindBugsDaemonClient.java    |   43 +
 .../findbugs/FindBugsDaemonClientProtocol.java     |   21 +
 .../internal/findbugs/FindBugsDaemonManager.groovy |   61 +
 .../internal/findbugs/FindBugsDaemonServer.java    |   50 +
 .../internal/findbugs/FindBugsExecuter.java        |   74 +
 .../quality/internal/findbugs/FindBugsResult.java  |   44 +
 .../quality/internal/findbugs/FindBugsSpec.java    |   54 +
 .../internal/findbugs/FindBugsSpecBuilder.java     |  115 ++
 .../gradle/api/plugins/quality/package-info.java   |    2 +-
 .../META-INF/gradle-plugins/checkstyle.properties  |    1 +
 .../gradle-plugins/code-quality.properties         |    0
 .../META-INF/gradle-plugins/codenarc.properties    |    1 +
 .../META-INF/gradle-plugins/findbugs.properties    |    1 +
 .../META-INF/gradle-plugins/jdepend.properties     |    1 +
 .../META-INF/gradle-plugins/pmd.properties         |    1 +
 .../plugins/quality/CheckstylePluginTest.groovy    |  169 ++
 .../api/plugins/quality/CodeNarcPluginTest.groovy  |  203 ++
 .../plugins/quality/CodeQualityPluginTest.groovy   |   62 +-
 .../api/plugins/quality/FindBugsPluginTest.groovy  |  180 ++
 .../gradle/api/plugins/quality/FindBugsTest.groovy |  106 ++
 .../api/plugins/quality/JDependPluginTest.groovy   |  168 ++
 .../api/plugins/quality/PmdPluginTest.groovy       |  174 ++
 .../internal/findbugs/FindBugsExecuterTest.groovy  |   55 +
 subprojects/core-impl/core-impl.gradle             |   17 +-
 .../artifacts/DefaultDependencyFactory.java        |   65 +
 .../DefaultDependencyManagementServices.java       |  375 +++-
 .../artifacts/DefaultResolvedArtifact.java         |  112 ++
 .../artifacts/DefaultResolvedDependency.java       |  196 ++
 .../internal/artifacts/PlexusLoggerAdapter.java    |  101 +
 .../artifacts/dsl/DefaultArtifactHandler.groovy    |   70 +
 .../dsl/DefaultPublishArtifactFactory.java         |  140 ++
 .../ivyservice/ArtifactCacheMetaData.java          |   22 +
 .../ivyservice/ArtifactResolveResult.java          |   34 +
 .../artifacts/ivyservice/ArtifactResolver.java     |   25 +
 .../ivyservice/BrokenArtifactResolveResult.java    |   36 +
 .../BrokenModuleVersionResolveResult.java          |   43 +
 .../CacheLockingArtifactDependencyResolver.java    |   40 +
 .../artifacts/ivyservice/CacheLockingManager.java  |   38 +
 .../ivyservice/DefaultCacheLockingManager.java     |   63 +
 .../ivyservice/DefaultIvyDependencyPublisher.java  |    0
 .../artifacts/ivyservice/DefaultIvyFactory.java    |   38 +
 .../ivyservice/DefaultLenientConfiguration.java    |  150 ++
 .../ivyservice/DefaultPublishOptionsFactory.java   |    0
 .../ivyservice/DefaultResolvedConfiguration.java   |   62 +
 .../ivyservice/DefaultSettingsConverter.java       |   73 +
 .../ivyservice/DefaultUnresolvedDependency.java    |   40 +
 .../ivyservice/DependencyToModuleResolver.java     |   30 +
 .../DependencyToModuleVersionIdResolver.java       |   28 +
 .../ErrorHandlingArtifactDependencyResolver.java   |  146 ++
 .../ivyservice/ErrorHandlingArtifactPublisher.java |   38 +
 .../FileBackedArtifactResolveResult.java           |   36 +
 .../ivyservice/IvyBackedArtifactPublisher.java     |   94 +
 .../ivyservice/IvyDependencyPublisher.java         |    0
 .../internal/artifacts/ivyservice/IvyFactory.java  |    0
 .../artifacts/ivyservice/IvyLoggingAdaper.java     |    0
 .../artifacts/ivyservice/IvySettingsFactory.java   |   40 +
 .../api/internal/artifacts/ivyservice/IvyUtil.java |   54 +
 .../ivyservice/ModuleDescriptorConverter.java      |   29 +
 .../ivyservice/ModuleVersionIdResolveResult.java   |   39 +
 .../ivyservice/ModuleVersionNotFoundException.java |   28 +
 .../ivyservice/ModuleVersionResolveException.java  |   91 +
 .../ivyservice/ModuleVersionResolveResult.java     |   49 +
 .../ivyservice/PublishOptionsFactory.java          |    0
 .../ivyservice/ResolvedArtifactFactory.java        |   45 +
 .../ivyservice/ResolvedConfigurationBuilder.java   |   31 +
 .../SelfResolvingDependencyResolver.java           |   85 +
 .../artifacts/ivyservice/SettingsConverter.java    |   34 +
 ...cuitEmptyConfigsArtifactDependencyResolver.java |   80 +
 .../VersionForcingDependencyToModuleResolver.java  |   45 +
 .../clientmodule/ClientModuleResolver.java         |   76 +
 .../DefaultCachedModuleResolution.java             |   54 +
 .../DefaultResolvedModuleVersion.java              |   33 +
 .../ForceChangeDependencyDescriptor.java           |   40 +
 .../dynamicversions/ModuleResolutionCache.java     |   37 +
 .../ModuleResolutionCacheEntry.java                |   31 +
 .../SingleFileBackedModuleResolutionCache.java     |  105 ++
 .../ivyresolve/ArtifactNotFoundException.java      |   32 +
 .../ivyresolve/ArtifactOriginWithMetaData.java     |   49 +
 .../ivyresolve/ArtifactResolveException.java       |   61 +
 .../CacheLockingModuleVersionRepository.java       |   63 +
 .../ivyresolve/CachingModuleVersionRepository.java |  245 +++
 .../ivyresolve/ChangingModuleDetector.java         |   67 +
 .../ivyservice/ivyresolve/DefaultIvyAdapter.java   |   37 +
 .../ivyresolve/DefaultModuleVersionDescriptor.java |   41 +
 .../ivyresolve/DelegatingDependencyResolver.java   |  149 ++
 .../ivyresolve/DependencyResolverAdapter.java      |  117 ++
 .../ivyresolve/DependencyResolverIdentifier.java   |   74 +
 .../ivyservice/ivyresolve/DownloadedArtifact.java  |   43 +
 .../ivyservice/ivyresolve/IvyAdapter.java          |   25 +
 .../ivyservice/ivyresolve/IvyContextualiser.java   |   62 +
 .../ivyresolve/LazyDependencyToModuleResolver.java |  192 ++
 .../ivyresolve/LoopbackDependencyResolver.java     |   94 +
 .../ivyresolve/ModuleVersionDescriptor.java        |   27 +
 .../ivyresolve/ModuleVersionRepository.java        |   45 +
 .../ivyresolve/NoOpRepositoryCacheManager.java     |   76 +
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |  105 ++
 .../ivyresolve/RestrictedDependencyResolver.java   |   36 +
 .../StartParameterResolutionOverride.java          |  107 ++
 .../ivyservice/ivyresolve/UserResolverChain.java   |  173 ++
 .../parser/GradlePomModuleDescriptorBuilder.java   |  563 ++++++
 .../parser/GradlePomModuleDescriptorParser.java    |  299 +++
 .../parser/ModuleScopedParserSettings.java         |   98 +
 .../ivyresolve/parser/ParserRegistry.java          |   41 +
 .../modulecache/DefaultCachedModuleDescriptor.java |   57 +
 .../modulecache/DefaultModuleDescriptorCache.java  |  125 ++
 .../modulecache/ModuleDescriptorCache.java         |   39 +
 .../modulecache/ModuleDescriptorCacheEntry.java    |   32 +
 .../modulecache/ModuleDescriptorFileStore.java     |   48 +
 .../modulecache/ModuleDescriptorStore.java         |   65 +
 .../ArtifactsExtraAttributesStrategy.java          |    0
 .../ArtifactsToModuleDescriptorConverter.java      |   26 +
 .../ConfigurationsToModuleDescriptorConverter.java |   26 +
 ...efaultArtifactsToModuleDescriptorConverter.java |   76 +
 ...tConfigurationsToModuleDescriptorConverter.java |   45 +
 .../DefaultExcludeRuleConverter.java               |   41 +
 .../DefaultModuleDescriptorFactory.java            |   48 +
 .../moduleconverter/ExcludeRuleConverter.java      |    0
 .../moduleconverter/ModuleDescriptorFactory.java   |    0
 .../PublishModuleDescriptorConverter.java          |   50 +
 .../ResolveModuleDescriptorConverter.java          |   51 +
 ...bstractDependencyDescriptorFactoryInternal.java |   94 +
 .../ClientModuleDependencyDescriptor.java          |   33 +
 .../ClientModuleDependencyDescriptorFactory.java   |   65 +
 ...ultDependenciesToModuleDescriptorConverter.java |   62 +
 ...aultModuleDescriptorFactoryForClientModule.java |    0
 .../DependenciesToModuleDescriptorConverter.java   |   28 +
 .../dependencies/DependencyDescriptorFactory.java  |   35 +
 .../DependencyDescriptorFactoryDelegate.java       |   63 +
 .../DependencyDescriptorFactoryInternal.java       |    0
 .../dependencies/EnhancedDependencyDescriptor.java |   34 +
 .../ExternalModuleDependencyDescriptorFactory.java |   57 +
 .../ModuleDescriptorFactoryForClientModule.java    |    0
 .../dependencies/ProjectDependencyDescriptor.java  |   31 +
 .../ProjectDependencyDescriptorFactory.java        |   52 +
 .../DefaultProjectModuleRegistry.java              |   58 +
 .../projectmodule/ProjectDependencyResolver.java   |   80 +
 .../projectmodule/ProjectModuleRegistry.java       |   26 +
 .../resolveengine/DefaultDependencyResolver.java   |   70 +
 .../resolveengine/DependencyGraphBuilder.java      |  900 +++++++++
 .../LatestModuleConflictResolver.java              |   50 +
 .../resolveengine/ModuleConflictResolver.java      |   22 +
 .../resolveengine/ModuleRevisionResolveState.java  |   22 +
 .../resolveengine/ModuleVersionSpec.java           |  552 ++++++
 .../resolveengine/StrictConflictResolver.java      |   30 +
 .../CannotLocateLocalMavenRepositoryException.java |   27 +
 .../DefaultLocalMavenRepositoryLocator.java        |   93 +
 .../mvnsettings/DefaultMavenFileLocations.java     |   53 +
 .../mvnsettings/LocalMavenRepositoryLocator.java   |   22 +
 .../artifacts/mvnsettings/MavenFileLocations.java  |   26 +
 .../artifacts/publish/maven/ArtifactPom.java       |   41 -
 .../publish/maven/ArtifactPomContainer.java        |   31 -
 .../publish/maven/ArtifactPomFactory.java          |   25 -
 .../publish/maven/DefaultArtifactPomFactory.java   |   28 -
 .../maven/DefaultLocalMavenCacheLocator.java       |   95 -
 .../publish/maven/DefaultMavenFactory.java         |   46 -
 .../artifacts/publish/maven/DefaultMavenPom.java   |  252 ---
 .../publish/maven/DefaultMavenPomFactory.java      |   47 -
 .../publish/maven/ExcludeRuleConverter.java        |   26 -
 .../publish/maven/LocalMavenCacheLocator.java      |   22 -
 .../publish/maven/PomDependenciesConverter.java    |   29 -
 .../DefaultConf2ScopeMappingContainer.java         |  115 --
 .../dependencies/DefaultExcludeRuleConverter.java  |   40 -
 .../DefaultPomDependenciesConverter.java           |  149 --
 .../maven/deploy/AbstractMavenResolver.java        |  295 ---
 .../publish/maven/deploy/BaseMavenDeployer.java    |  129 --
 .../publish/maven/deploy/BaseMavenInstaller.java   |   51 -
 .../maven/deploy/BasePomFilterContainer.java       |  137 --
 .../publish/maven/deploy/ClassifierArtifact.java   |   71 -
 .../publish/maven/deploy/CustomDeployTask.java     |   44 -
 .../deploy/CustomInstallDeployTaskSupport.java     |   29 -
 .../publish/maven/deploy/CustomInstallTask.java    |   37 -
 .../publish/maven/deploy/DefaultArtifactPom.java   |  213 ---
 .../maven/deploy/DefaultArtifactPomContainer.java  |   78 -
 .../maven/deploy/DefaultDeployTaskFactory.java     |   27 -
 .../maven/deploy/DefaultInstallTaskFactory.java    |   27 -
 .../maven/deploy/DefaultMavenDeployment.java       |   64 -
 .../publish/maven/deploy/DefaultPomFilter.java     |   56 -
 .../publish/maven/deploy/LoggingHelper.java        |   46 -
 .../groovy/DefaultGroovyMavenDeployer.groovy       |   51 -
 .../maven/deploy/groovy/RepositoryBuilder.java     |   35 -
 .../maven/deploy/groovy/RepositoryFactory.java     |   54 -
 .../mvnsettings/EmptyMavenSettingsSupplier.java    |   49 -
 .../deploy/mvnsettings/MavenSettingsProvider.java  |   39 -
 .../deploy/mvnsettings/MavenSettingsSupplier.java  |   27 -
 .../MaybeUserMavenSettingsSupplier.java            |   45 -
 .../maven/pombuilder/CustomModelBuilder.java       |   81 -
 .../publish/maven/pombuilder/ModelFactory.java     |   43 -
 .../maven/pombuilder/PlexusLoggerAdapter.java      |  101 -
 .../AbstractAuthenticationSupportedRepository.java |   37 +
 .../CommonsHttpClientBackedRepository.java         |  255 ---
 .../CustomResolverArtifactRepository.java          |   62 +
 .../DefaultExternalResourceRepository.java         |  112 ++
 .../DefaultFlatDirArtifactRepository.java          |   84 +
 .../repositories/DefaultIvyArtifactRepository.java |  175 +-
 .../DefaultMavenArtifactRepository.java            |  108 ++
 .../repositories/DefaultPasswordCredentials.java   |   52 +
 .../repositories/DefaultResolverFactory.java       |  181 +-
 .../EnhancedArtifactDownloadReport.java            |   38 +
 .../repositories/ExternalResourceRepository.java   |   46 +
 .../repositories/ExternalResourceResolver.java     |  475 +++++
 .../artifacts/repositories/IvyResolver.java        |   47 +
 .../artifacts/repositories/MavenResolver.java      |  316 ++++
 .../repositories/PatternBasedResolver.java         |   33 +
 .../ProgressLoggingTransferListener.java           |   81 +
 .../AbstractRepositoryCacheManager.java            |   85 +
 .../DownloadingRepositoryCacheManager.java         |  148 ++
 .../LocalFileRepositoryCacheManager.java           |   88 +
 .../layout/GradleRepositoryLayout.java             |   42 +
 .../repositories/layout/MavenRepositoryLayout.java |   45 +
 .../layout/PatternRepositoryLayout.java            |   63 +
 .../repositories/layout/RepositoryLayout.java      |   47 +
 .../repositories/layout/ResolvedPattern.java       |   50 +
 .../transport/RepositoryTransport.java             |   29 +
 .../transport/RepositoryTransportFactory.java      |   99 +
 .../file/FileExternalResourceRepository.java       |   48 +
 .../externalresource/AbstractExternalResource.java |   49 +
 .../externalresource/ExternalResource.java         |   33 +
 .../ExternalResourceIvyResourceAdapter.java        |   79 +
 .../LocalFileStandInExternalResource.java          |   96 +
 .../LocallyAvailableExternalResource.java          |   45 +
 .../MetaDataOnlyExternalResource.java              |   79 +
 .../externalresource/MissingExternalResource.java  |   68 +
 .../cached/ByUrlCachedExternalResourceIndex.java   |   30 +
 .../cached/CachedExternalResource.java             |   89 +
 .../cached/CachedExternalResourceAdapter.java      |   76 +
 .../cached/CachedExternalResourceIndex.java        |   71 +
 .../cached/DefaultCachedExternalResource.java      |   87 +
 .../cached/DefaultCachedExternalResourceIndex.java |  122 ++
 ...actAtRepositoryCachedExternalResourceIndex.java |   30 +
 .../ivy/ArtifactAtRepositoryKey.java               |   64 +
 .../AbstractLocallyAvailableResourceFinder.java    |   37 +
 .../CompositeLocallyAvailableResourceFinder.java   |   69 +
 .../local/DefaultLocallyAvailableResource.java     |   65 +
 .../LazyLocallyAvailableResourceCandidates.java    |   58 +
 .../local/LocallyAvailableResource.java            |   31 +
 .../local/LocallyAvailableResourceCandidates.java  |   30 +
 .../local/LocallyAvailableResourceFinder.java      |   32 +
 ...leResourceFinderSearchableFileStoreAdapter.java |   54 +
 .../ivy/LocallyAvailableResourceFinderFactory.java |   80 +
 ...PatternBasedLocallyAvailableResourceFinder.java |   81 +
 .../metadata/DefaultExternalResourceMetaData.java  |   70 +
 .../metadata/ExternalResourceMetaData.java         |   54 +
 .../metadata/ExternalResourceMetaDataCompare.java  |   71 +
 .../CacheAwareExternalResourceAccessor.java        |   30 +
 .../DefaultCacheAwareExternalResourceAccessor.java |  102 +
 .../transfer/ExternalResourceAccessor.java         |   75 +
 .../transfer/ExternalResourceLister.java           |   26 +
 .../transfer/ExternalResourceUploader.java         |   25 +
 .../transport/file/FileTransport.java              |   56 +
 .../transport/http/DefaultHttpSettings.java        |   35 +
 .../transport/http/HttpClientConfigurer.java       |  115 ++
 .../transport/http/HttpClientHelper.java           |  138 ++
 .../transport/http/HttpProxySettings.java          |   42 +
 .../transport/http/HttpResourceAccessor.java       |  110 ++
 .../transport/http/HttpResourceLister.java         |   43 +
 .../transport/http/HttpResourceUploader.java       |   47 +
 .../transport/http/HttpResponseResource.java       |  131 ++
 .../transport/http/HttpSettings.java               |   24 +
 .../transport/http/HttpTransport.java              |   59 +
 .../JavaSystemPropertiesHttpProxySettings.java     |  103 ++
 .../transport/http/ntlm/NTLMCredentials.java       |   92 +
 .../transport/http/ntlm/NTLMSchemeFactory.java     |   64 +
 .../internal/filestore/DefaultFileStoreEntry.java  |   39 +
 .../gradle/api/internal/filestore/FileStore.java   |   24 +
 .../api/internal/filestore/FileStoreEntry.java     |   29 +
 .../api/internal/filestore/FileStoreSearcher.java  |   25 +
 .../filestore/GroupedAndNamedUniqueFileStore.java  |   58 +
 .../internal/filestore/UniquePathFileStore.java    |   98 +
 .../filestore/ivy/ArtifactRevisionIdFileStore.java |   45 +
 .../notations/ClientModuleNotationParser.java      |   48 +
 .../DependencyClassPathNotationParser.java         |   58 +
 .../notations/DependencyFilesNotationParser.java   |   46 +
 .../notations/DependencyMapNotationParser.java     |   56 +
 .../notations/DependencyNotationParser.java        |   58 +
 .../notations/DependencyProjectNotationParser.java |   50 +
 .../notations/DependencyStringNotationParser.java  |   77 +
 .../notations/ProjectDependencyFactory.java        |   55 +
 ...internal.artifacts.DependencyManagementServices |    1 +
 .../gradle/api/artifacts/ArtifactsTestUtils.java   |   60 +
 .../DefaultDependencyManagementServicesTest.groovy |  127 ++
 .../artifacts/DefaultResolvedArtifactTest.groovy   |   83 +
 .../artifacts/DefaultResolvedDependencySpec.groovy |   92 +
 .../artifacts/DefaultResolvedDependencyTest.java   |  185 ++
 .../DependencyManagementServicesTest.groovy        |   40 -
 .../dsl/DefaultArtifactHandlerTest.groovy          |  127 ++
 .../dsl/DefaultPublishArtifactFactoryTest.groovy   |  251 +++
 ...cheLockingArtifactDependencyResolverTest.groovy |   44 +
 .../DefaultIvyDependencyPublisherTest.java         |    0
 .../ivyservice/DefaultIvyFactoryTest.groovy        |   40 +
 .../DefaultPublishOptionsFactoryTest.java          |    0
 .../ivyservice/DefaultSettingsConverterTest.groovy |  136 ++
 ...orHandlingArtifactDependencyResolverTest.groovy |  133 ++
 .../ErrorHandlingArtifactPublisherTest.groovy      |   76 +
 .../ivyservice/IvyBackedArtifactPublisherTest.java |  164 ++
 .../ivyservice/IvySettingsFactoryTest.groovy       |   37 +
 .../artifacts/ivyservice/IvyUtilTest.groovy        |    0
 .../ModuleVersionResolveExceptionTest.groovy       |   47 +
 .../ivyservice/ResolvedArtifactFactoryTest.groovy  |   53 +
 .../SelfResolvingDependencyResolverTest.java       |  177 ++
 ...EmptyConfigsArtifactDependencyResolverTest.java |   79 +
 ...ionForcingDependencyToModuleResolverTest.groovy |   64 +
 .../clientmodule/ClientModuleResolverTest.groovy   |   85 +
 .../CachingModuleVersionRepositoryTest.groovy      |   87 +
 .../DependencyResolverIdentifierTest.groovy        |  135 ++
 .../LazyDependencyToModuleResolverTest.groovy      |  277 +++
 .../ivyresolve/parser/PomParserTest.groovy         |  183 ++
 ...ltArtifactsToModuleDescriptorConverterTest.java |  129 ++
 ...figurationsToModuleDescriptorConverterTest.java |    0
 .../DefaultExcludeRuleConverterTest.java           |   52 +
 .../DefaultModuleDescriptorFactoryTest.groovy      |   64 +
 .../moduleconverter/IvyConverterTestUtil.java      |    0
 .../PublishModuleDescriptorConverterTest.groovy    |   54 +
 .../ResolveModuleDescriptorConverterTest.groovy    |   56 +
 ...actDependencyDescriptorFactoryInternalTest.java |  125 ++
 ...lientModuleDependencyDescriptorFactoryTest.java |   95 +
 ...ependenciesToModuleDescriptorConverterTest.java |  127 ++
 ...ModuleDescriptorFactoryForClientModuleTest.java |  103 ++
 .../DependencyDescriptorFactoryDelegateTest.java   |    0
 ...ernalModuleDependencyDescriptorFactoryTest.java |   72 +
 .../ProjectDependencyDescriptorFactoryTest.java    |   66 +
 .../ProjectDependencyResolverTest.groovy           |   56 +
 .../DependencyGraphBuilderTest.groovy              |  893 +++++++++
 .../resolveengine/ModuleVersionSpecTest.groovy     |  367 ++++
 .../DefaultLocalMavenRepositoryLocatorTest.groovy  |  106 ++
 .../maven/DefaultDeployTaskFactoryTest.java        |   31 -
 .../maven/DefaultLocalMavenCacheLocatorTest.groovy |   58 -
 .../maven/DefaultMavenPomFactoryTest.groovy        |   46 -
 .../publish/maven/DefaultMavenPomTest.groovy       |  191 --
 .../DefaultConf2ScopeMappingContainerTest.java     |  123 --
 .../DefaultExcludeRuleConverterTest.java           |   60 -
 .../DefaultPomDependenciesConverterTest.java       |  254 ---
 .../maven/deploy/AbstractMavenResolverTest.java    |  239 ---
 .../maven/deploy/BaseMavenDeployerTest.java        |  106 --
 .../maven/deploy/BaseMavenInstallerTest.java       |   71 -
 .../maven/deploy/BasePomFilterContainerTest.java   |  187 --
 .../deploy/DefaultArtifactPomContainerTest.groovy  |  100 -
 .../maven/deploy/DefaultArtifactPomTest.java       |  272 ---
 .../publish/maven/deploy/DefaultPomFilterTest.java |   55 -
 .../groovy/DefaultGroovyMavenDeployerTest.groovy   |  117 --
 .../DefaultGroovyPomFilterContainerTest.groovy     |  122 --
 .../EmptyMavenSettingsSupplierTest.groovy          |   55 -
 .../MaybeUserMavenSettingsSupplierTest.groovy      |   63 -
 .../DefaultFlatDirArtifactRepositoryTest.groovy    |   68 +
 .../DefaultIvyArtifactRepositoryTest.groovy        |  222 ++-
 .../DefaultMavenArtifactRepositoryTest.groovy      |  119 ++
 .../repositories/DefaultResolverFactoryTest.groovy |  161 +-
 .../DefaultArtifactResolutionCacheTest.groovy      |   80 +
 .../CachedExternalResourceAdapterTest.groovy       |   85 +
 ...positeLocallyAvailableResourceFinderTest.groovy |   74 +
 .../DefaultLocallyAvailableResourceTest.groovy     |   66 +
 ...zyLocallyAvailableResourceCandidatesTest.groovy |   57 +
 .../DefaultExternalResourceMetaDataTest.groovy     |   33 +
 .../ExternalResourceMetaDataCompareTest.groovy     |  172 ++
 ...ltCacheAwareExternalResourceAccessorTest.groovy |   61 +
 .../transport/http/HttpClientConfigurerTest.groovy |   92 +
 .../transport/http/HttpResponseResourceTest.groovy |   57 +
 ...avaSystemPropertiesHttpProxySettingsTest.groovy |   85 +
 .../transport/http/ntlm/NTLMCredentialsTest.groovy |   92 +
 .../DependencyClassPathNotationParserTest.groovy   |   55 +
 .../DependencyMapNotationParserTest.groovy         |  141 ++
 .../notations/DependencyNotationParserTest.groovy  |   57 +
 .../DependencyStringNotationParserTest.groovy      |  175 ++
 .../notations/ProjectDependencyFactoryTest.groovy  |   52 +
 subprojects/core/core.gradle                       |  132 +-
 ...ependencyResolutionEventsIntegrationTest.groovy |   52 +
 .../FileCollectionSymlinkIntegrationTest.groovy    |   58 +
 .../ArchiveTaskPermissionsIntegrationTest.groovy   |  152 ++
 .../api/tasks/CopyErrorIntegrationTest.groovy      |   79 +
 .../tasks/CopyPermissionsIntegrationTest.groovy    |  116 ++
 .../api/tasks/CopyTaskIntegrationTest.groovy       |  436 +++++
 .../api/tasks/FileTreeCopyIntegrationTest.groovy   |  101 +
 .../fixtures/AbstractExecutionResult.java          |   35 -
 .../fixtures/AbstractGradleExecuter.java           |  262 ---
 .../fixtures/BasicGradleDistribution.java          |   46 -
 .../integtests/fixtures/DaemonGradleExecuter.java  |   65 -
 .../integtests/fixtures/ExecutionResult.java       |   51 -
 .../integtests/fixtures/ForkingGradleExecuter.java |  299 ---
 .../integtests/fixtures/GradleDistribution.java    |  179 --
 .../fixtures/GradleDistributionExecuter.java       |  175 --
 .../gradle/integtests/fixtures/GradleExecuter.java |  105 --
 .../gradle/integtests/fixtures/HttpServer.groovy   |  243 ---
 .../fixtures/InProcessGradleExecuter.java          |  378 ----
 .../integtests/fixtures/IvyRepository.groovy       |   81 -
 .../integtests/fixtures/MavenRepository.groovy     |   91 -
 .../fixtures/PreviousGradleVersionExecuter.groovy  |   94 -
 .../integtests/fixtures/ScriptExecuter.groovy      |   38 -
 .../internal/AbstractAutoTestedSamplesTest.groovy  |   33 -
 .../fixtures/internal/AbstractIntegrationTest.java |   77 -
 .../fixtures/internal/AutoTestedSamplesUtil.groovy |   70 -
 .../fixtures/internal/IntegrationTestHint.java     |   31 -
 .../copyTestResources/src/one/ignore/bad.file      |    0
 .../api/tasks}/copyTestResources/src/one/one.a     |    0
 .../api/tasks}/copyTestResources/src/one/one.b     |    0
 .../copyTestResources/src/one/sub/ignore/bad.file  |    0
 .../tasks}/copyTestResources/src/one/sub/onesub.a  |    0
 .../tasks}/copyTestResources/src/one/sub/onesub.b  |    0
 .../gradle/api/tasks}/copyTestResources/src/root.a |    0
 .../gradle/api/tasks}/copyTestResources/src/root.b |    0
 .../copyTestResources/src/two/ignore/bad.file      |    0
 .../api/tasks}/copyTestResources/src/two/two.a     |    0
 .../api/tasks}/copyTestResources/src/two/two.b     |    0
 .../tasks}/copyTestResources/src2/three/three.a    |    0
 .../tasks}/copyTestResources/src2/three/three.b    |    0
 .../groovy/org/gradle/BuildExceptionReporter.java  |  148 +-
 .../src/main/groovy/org/gradle/BuildLogger.java    |    3 +-
 .../src/main/groovy/org/gradle/CacheUsage.java     |    4 +-
 .../org/gradle/CommandLineArgumentException.java   |   33 -
 .../src/main/groovy/org/gradle/GradleLauncher.java |    8 +-
 .../src/main/groovy/org/gradle/RefreshOptions.java |   85 +
 .../src/main/groovy/org/gradle/StartParameter.java |  472 ++---
 .../src/main/groovy/org/gradle/api/AntBuilder.java |    8 +
 .../main/groovy/org/gradle/api/DefaultTask.java    |    2 +-
 .../org/gradle/api/DomainObjectCollection.java     |   65 +-
 .../groovy/org/gradle/api/DomainObjectSet.java     |   49 +
 .../org/gradle/api/IllegalDependencyNotation.java  |    2 +-
 .../main/groovy/org/gradle/api/JavaVersion.java    |   11 +
 .../org/gradle/api/LocationAwareException.java     |  139 --
 .../core/src/main/groovy/org/gradle/api/Named.java |   53 +
 .../gradle/api/NamedDomainObjectCollection.java    |  296 +--
 .../org/gradle/api/NamedDomainObjectContainer.java |   49 +-
 .../org/gradle/api/NamedDomainObjectList.java      |   52 +
 .../org/gradle/api/NamedDomainObjectSet.java       |   52 +
 .../core/src/main/groovy/org/gradle/api/Namer.java |   57 +
 .../src/main/groovy/org/gradle/api/Nullable.java   |   27 +
 .../src/main/groovy/org/gradle/api/Project.java    |  498 +++--
 .../src/main/groovy/org/gradle/api/Script.java     |  699 +++----
 .../org/gradle/api/ScriptCompilationException.java |   46 -
 .../core/src/main/groovy/org/gradle/api/Task.java  |   63 +-
 .../main/groovy/org/gradle/api/Transformer.java    |   11 +-
 .../org/gradle/api/UncheckedIOException.java       |    2 +-
 .../main/groovy/org/gradle/api/XmlProvider.java    |   50 +
 .../gradle/api/artifacts/ArtifactIdentifier.java   |   55 +
 .../api/artifacts/ArtifactRepositoryContainer.java |  230 +++
 .../org/gradle/api/artifacts/ClientModule.java     |    2 -
 .../api/artifacts/ConfigurablePublishArtifact.java |   57 +
 .../org/gradle/api/artifacts/Configuration.java    |  152 +-
 .../api/artifacts/ConfigurationContainer.java      |   45 +-
 .../gradle/api/artifacts/ConflictResolution.java   |   21 +
 .../artifacts/DependencyResolutionListener.java    |   35 +
 .../org/gradle/api/artifacts/DependencySet.java    |   25 +
 .../org/gradle/api/artifacts/ExcludeRule.java      |   15 +-
 .../gradle/api/artifacts/ExternalDependency.java   |    2 +-
 .../gradle/api/artifacts/LenientConfiguration.java |   53 +
 .../org/gradle/api/artifacts/ModuleDependency.java |   38 +-
 .../api/artifacts/ModuleVersionIdentifier.java     |   42 +
 .../api/artifacts/ModuleVersionSelector.java       |   44 +
 .../org/gradle/api/artifacts/PublishArtifact.java  |   22 +-
 .../gradle/api/artifacts/PublishArtifactSet.java   |   27 +
 .../gradle/api/artifacts/PublishInstruction.java   |   99 -
 .../gradle/api/artifacts/ResolutionStrategy.java   |  164 ++
 .../api/artifacts/ResolvableDependencies.java      |   82 +
 .../org/gradle/api/artifacts/ResolveException.java |   21 +-
 .../org/gradle/api/artifacts/ResolvedArtifact.java |   16 +
 .../api/artifacts/ResolvedConfiguration.java       |    9 +-
 .../gradle/api/artifacts/ResolvedDependency.java   |   11 +-
 .../api/artifacts/ResolvedModuleVersion.java       |   27 +
 .../gradle/api/artifacts/ResolverContainer.java    |  197 --
 .../gradle/api/artifacts/UnresolvedDependency.java |   32 +
 .../artifacts/cache/ArtifactResolutionControl.java |   26 +
 .../cache/DependencyResolutionControl.java         |   25 +
 .../artifacts/cache/ModuleResolutionControl.java   |   31 +
 .../api/artifacts/cache/ResolutionControl.java     |   55 +
 .../api/artifacts/cache/ResolutionRules.java       |   42 +
 .../gradle/api/artifacts/cache/package-info.java   |   20 +
 .../gradle/api/artifacts/dsl/ArtifactHandler.java  |   69 +-
 .../api/artifacts/dsl/ArtifactRepository.java      |   35 -
 .../api/artifacts/dsl/DependencyHandler.java       |  209 ++-
 .../api/artifacts/dsl/IvyArtifactRepository.java   |   56 -
 .../api/artifacts/dsl/RepositoryHandler.java       |  163 +-
 .../gradle/api/artifacts/maven/MavenFactory.java   |   34 -
 .../org/gradle/api/artifacts/maven/MavenPom.java   |  224 ---
 .../gradle/api/artifacts/maven/MavenResolver.java  |   51 -
 .../api/artifacts/maven/PomFilterContainer.java    |  136 --
 .../gradle/api/artifacts/maven/XmlProvider.java    |   50 -
 .../artifacts/repositories/ArtifactRepository.java |   38 +
 .../repositories/AuthenticationSupported.java      |   46 +
 .../FlatDirectoryArtifactRepository.java           |   73 +
 .../repositories/IvyArtifactRepository.java        |  108 ++
 .../repositories/MavenArtifactRepository.java      |   71 +
 .../repositories/PasswordCredentials.java          |   49 +
 .../api/artifacts/repositories/WebdavResolver.java |   46 -
 .../api/artifacts/specs/DependencySpecs.java       |   75 -
 .../org/gradle/api/artifacts/specs/Type.java       |   51 -
 .../gradle/api/artifacts/specs/package-info.java   |   20 -
 .../org/gradle/api/dsl/ConvenienceProperty.java    |   73 -
 .../org/gradle/api/dsl/ConventionProperty.java     |   76 +
 .../groovy/org/gradle/api/dsl/package-info.java    |    2 +-
 .../org/gradle/api/file/CopyProcessingSpec.java    |  254 +--
 .../main/groovy/org/gradle/api/file/CopySpec.java  |   11 +-
 .../org/gradle/api/file/FileCopyDetails.java       |    7 +
 .../org/gradle/api/file/FileTreeElement.java       |    7 +
 .../org/gradle/api/file/SourceDirectorySet.java    |   14 +-
 .../api/internal/AbstractClassGenerator.java       |  104 +-
 .../api/internal/AbstractClassPathProvider.java    |  165 --
 .../internal/AbstractDomainObjectCollection.java   |  163 --
 .../api/internal/AbstractMultiCauseException.java  |   30 +-
 .../AbstractNamedDomainObjectContainer.java        |   64 +
 .../org/gradle/api/internal/AbstractTask.java      |  114 +-
 .../api/internal/AsmBackedClassGenerator.java      |  272 ++-
 .../internal/AutoCreateDomainObjectContainer.java  |   51 -
 .../AutoCreateDomainObjectContainerDelegate.groovy |   75 -
 .../org/gradle/api/internal/BeanDynamicObject.java |  222 ++-
 .../gradle/api/internal/ChainingTransformer.java   |   10 +-
 .../org/gradle/api/internal/ClassGenerator.java    |    5 -
 .../internal/ClassGeneratorBackedInstantiator.java |   40 +
 .../org/gradle/api/internal/ClassPathProvider.java |    5 +-
 .../org/gradle/api/internal/ClassPathRegistry.java |   52 +-
 .../api/internal/CompositeDomainObjectSet.java     |  111 ++
 .../api/internal/CompositeDynamicObject.java       |  222 +--
 .../org/gradle/api/internal/ConfigureDelegate.java |   94 +
 .../gradle/api/internal/ConventionAwareHelper.java |  111 +-
 .../org/gradle/api/internal/ConventionMapping.java |   17 +-
 .../org/gradle/api/internal/ConventionTask.java    |   14 +-
 .../DefaultAutoCreateDomainObjectContainer.java    |   68 -
 .../api/internal/DefaultClassPathProvider.java     |   50 +-
 .../api/internal/DefaultClassPathRegistry.java     |  116 +-
 .../internal/DefaultDomainObjectCollection.java    |  291 +++
 .../api/internal/DefaultDomainObjectContainer.java |  110 --
 .../api/internal/DefaultDomainObjectSet.java       |   77 +
 .../DefaultNamedDomainObjectCollection.java        |  325 ++++
 .../DefaultNamedDomainObjectContainer.java         |  317 ----
 .../api/internal/DefaultNamedDomainObjectList.java |  192 ++
 .../api/internal/DefaultNamedDomainObjectSet.java  |   87 +
 .../api/internal/DelegatingDomainObjectSet.java    |  134 ++
 .../api/internal/DependencyClassPathProvider.java  |   62 +
 .../gradle/api/internal/DirectInstantiator.java    |   67 +
 .../gradle/api/internal/DocumentationRegistry.java |   50 +
 .../groovy/org/gradle/api/internal/DomNode.java    |   51 +
 .../internal/DynamicModulesClassPathProvider.java  |   47 +
 .../org/gradle/api/internal/DynamicObject.java     |    7 +
 .../gradle/api/internal/DynamicObjectAware.java    |   28 +-
 .../gradle/api/internal/DynamicObjectHelper.java   |  160 +-
 .../org/gradle/api/internal/DynamicObjectUtil.java |   30 +
 .../api/internal/DynamicPropertyNamer.groovy       |   60 +
 .../org/gradle/api/internal/ExceptionAnalyser.java |    0
 .../api/internal/ExtensibleDynamicObject.java      |  196 ++
 .../groovy/org/gradle/api/internal/Factory.java    |   29 -
 .../FactoryNamedDomainObjectContainer.java         |  115 ++
 .../org/gradle/api/internal/FilteredAction.java    |   38 +
 .../api/internal/GradleDistributionLocator.java    |    3 +
 ...GroovySourceGenerationBackedClassGenerator.java |  134 --
 .../org/gradle/api/internal/HasConvention.java     |   30 +
 .../org/gradle/api/internal/IConventionAware.java  |    6 -
 .../org/gradle/api/internal/Instantiator.java      |   28 +
 .../api/internal/LocationAwareException.java       |  161 ++
 .../api/internal/MapBackedDynamicObject.java       |   58 -
 ...amedDomainObjectContainerConfigureDelegate.java |   32 +
 .../groovy/org/gradle/api/internal/Operation.java  |   26 +
 .../org/gradle/api/internal/ProcessOperations.java |   25 +
 .../gradle/api/internal/PropertiesTransformer.java |   84 +
 .../ReflectiveNamedDomainObjectFactory.java        |   55 +
 .../org/gradle/api/internal/TaskInternal.java      |   14 +-
 .../gradle/api/internal/TaskOutputsInternal.java   |    0
 .../api/internal/ThreadGlobalInstantiator.java     |   60 +
 .../org/gradle/api/internal/XmlTransformer.java    |   87 +-
 .../api/internal/artifacts/ArtifactContainer.java  |   48 -
 .../artifacts/ArtifactDependencyResolver.java      |   27 +
 .../artifacts/ArtifactPublicationServices.java     |   24 +
 .../api/internal/artifacts/ArtifactPublisher.java  |   28 +
 .../artifacts/CachingDependencyResolveContext.java |    6 +-
 .../artifacts/ConfigurationContainerFactory.java   |   31 -
 .../artifacts/DefaultArtifactIdentifier.java       |   55 +
 .../DefaultArtifactRepositoryContainer.java        |  236 +++
 .../DefaultConfigurationContainerFactory.java      |   79 -
 .../internal/artifacts/DefaultDependencySet.java   |   57 +
 .../api/internal/artifacts/DefaultExcludeRule.java |   45 +-
 .../artifacts/DefaultExcludeRuleContainer.java     |    5 +-
 .../artifacts/DefaultModuleVersionIdentifier.java  |   77 +
 .../artifacts/DefaultModuleVersionSelector.java    |   99 +
 .../artifacts/DefaultPublishArtifactSet.java       |   83 +
 .../artifacts/DefaultResolvedArtifact.java         |   79 -
 .../artifacts/DefaultResolvedDependency.java       |  160 --
 .../artifacts/DefaultResolverContainer.java        |  231 ---
 .../api/internal/artifacts/DependencyInternal.java |   23 -
 .../artifacts/DependencyManagementServices.java    |   10 +-
 .../artifacts/DependencyResolutionServices.java    |   34 +
 .../artifacts/ExcludeRuleNotationParser.java       |   47 +
 .../gradle/api/internal/artifacts/IvyService.java  |   36 -
 .../internal/artifacts/ResolvableDependency.java   |   21 +
 .../artifacts/ResolvedConfigurationIdentifier.java |    7 -
 .../api/internal/artifacts/ResolverFactory.java    |   38 +
 .../ConfigurationContainerInternal.java            |   23 +
 .../configurations/ConfigurationInternal.java      |   25 +
 .../artifacts/configurations/Configurations.java   |   31 +-
 .../configurations/DefaultConfiguration.java       |  361 ++--
 .../DefaultConfigurationContainer.java             |   89 +-
 .../configurations/DefaultResolutionStrategy.java  |   90 +
 .../configurations/DependencyMetaDataProvider.java |    7 -
 .../configurations/ForcedModuleNotationParser.java |   98 +
 .../configurations/ResolutionStrategyInternal.java |   43 +
 .../conflicts/LatestConflictResolution.java        |   27 +
 .../conflicts/StrictConflictResolution.java        |   27 +
 .../configurations/dynamicversion/CachePolicy.java |   33 +
 .../dynamicversion/DefaultCachePolicy.java         |  217 +++
 .../artifacts/dependencies/AbstractDependency.java |    5 +-
 .../dependencies/DefaultClientModule.java          |    3 +-
 .../DefaultExternalModuleDependency.java           |    3 +-
 .../dependencies/DefaultProjectDependency.java     |    9 +-
 .../artifacts/dsl/ClasspathScriptTransformer.java  |    4 +-
 .../artifacts/dsl/DefaultArtifactHandler.groovy    |   66 -
 .../dsl/DefaultPublishArtifactFactory.java         |   34 -
 .../artifacts/dsl/DefaultRepositoryHandler.java    |  213 +--
 .../dsl/DefaultRepositoryHandlerFactory.java       |   38 -
 .../artifacts/dsl/FixMainScriptTransformer.java    |    0
 .../artifacts/dsl/PublishArtifactFactory.java      |   25 -
 .../SharedConventionRepositoryHandlerFactory.java  |   45 -
 .../dependencies/ClassPathDependencyFactory.java   |   49 -
 .../dependencies/DefaultClientModuleFactory.java   |   55 -
 .../dsl/dependencies/DefaultDependencyFactory.java |   83 -
 .../dependencies/DefaultDependencyHandler.groovy   |   43 +-
 .../DefaultProjectDependencyFactory.java           |   64 -
 .../dsl/dependencies/DependencyFactory.java        |    1 +
 .../IDependencyImplementationFactory.java          |   26 -
 .../dsl/dependencies/MapModuleNotationParser.java  |   56 -
 .../dsl/dependencies/ModuleDependencyFactory.java  |   74 -
 .../dependencies/ModuleDescriptorDelegate.groovy   |    1 +
 .../dependencies/ParsedModuleStringNotation.java   |    4 +-
 .../dsl/dependencies/ProjectDependencyFactory.java |   28 -
 .../artifacts/dsl/dependencies/ProjectFinder.java  |    4 +-
 .../SelfResolvingDependencyFactory.java            |   40 -
 .../artifacts/ivyservice/ClientModuleResolver.java |   94 -
 .../ivyservice/DefaultIvyConversionResult.java     |   50 -
 .../ivyservice/DefaultIvyDependencyResolver.java   |  162 --
 .../artifacts/ivyservice/DefaultIvyFactory.java    |   28 -
 .../ivyservice/DefaultIvyReportConverter.java      |  329 ----
 .../artifacts/ivyservice/DefaultIvyService.java    |  191 --
 .../ivyservice/DefaultSettingsConverter.java       |  218 ---
 .../ivyservice/ErrorHandlingIvyService.java        |  152 --
 .../ivyservice/GradleIBiblioResolver.java          |  211 ---
 .../artifacts/ivyservice/IvyConversionResult.java  |   34 -
 .../ivyservice/IvyDependencyResolver.java          |   28 -
 .../artifacts/ivyservice/IvyReportConverter.java   |   26 -
 .../api/internal/artifacts/ivyservice/IvyUtil.java |   54 -
 .../LocalFileRepositoryCacheManager.java           |   93 -
 .../ivyservice/ModuleDescriptorConverter.java      |   30 -
 .../ivyservice/NoOpRepositoryCacheManager.java     |   76 -
 .../artifacts/ivyservice/ResolverFactory.java      |   50 -
 .../SelfResolvingDependencyResolver.java           |   82 -
 .../artifacts/ivyservice/SettingsConverter.java    |   38 -
 .../ShortcircuitEmptyConfigsIvyService.java        |   74 -
 .../ivyservice/SnapshotVersionMatcher.java         |   36 -
 .../AbstractModuleDescriptorConverter.java         |   51 -
 .../ArtifactsToModuleDescriptorConverter.java      |   28 -
 .../ConfigurationsToModuleDescriptorConverter.java |   28 -
 ...efaultArtifactsToModuleDescriptorConverter.java |   77 -
 ...tConfigurationsToModuleDescriptorConverter.java |   46 -
 .../DefaultExcludeRuleConverter.java               |   41 -
 .../DefaultModuleDescriptorFactory.java            |   30 -
 .../PublishModuleDescriptorConverter.java          |   64 -
 .../ResolveModuleDescriptorConverter.java          |   51 -
 ...bstractDependencyDescriptorFactoryInternal.java |  123 --
 .../ClientModuleDependencyDescriptorFactory.java   |   69 -
 ...ultDependenciesToModuleDescriptorConverter.java |   93 -
 .../DependenciesToModuleDescriptorConverter.java   |   29 -
 .../dependencies/DependencyDescriptorFactory.java  |   32 -
 .../DependencyDescriptorFactoryDelegate.java       |   56 -
 .../ExternalModuleDependencyDescriptorFactory.java |   55 -
 .../ProjectDependencyDescriptorFactory.java        |   79 -
 .../ProjectDependencyDescriptorStrategy.java       |   27 -
 .../artifacts/publish/AbstractPublishArtifact.java |    9 +-
 .../publish/DefaultArtifactContainer.java          |   47 -
 .../artifacts/publish/DefaultPublishArtifact.java  |   10 +-
 .../publish/maven/MavenPomMetaInfoProvider.java    |   22 -
 .../artifacts/publish/maven/deploy/PomFilter.java  |   34 -
 .../repositories/ArtifactRepositoryInternal.java   |   11 +-
 .../repositories/DefaultInternalRepository.java    |  163 --
 .../FixedResolverArtifactRepository.java           |   39 +
 .../artifacts/repositories/InternalRepository.java |   24 -
 .../artifacts/repositories/WebdavRepository.java   |   80 -
 .../org/gradle/api/internal/cache/Cache.java       |   22 +
 .../api/internal/cache/CacheAccessSerializer.java  |   38 +
 .../gradle/api/internal/cache/CacheSupport.java    |   35 +
 .../gradle/api/internal/cache/MapBackedCache.java  |   36 +
 .../CacheBackedFileSnapshotRepository.java         |   17 +-
 .../CacheBackedTaskHistoryRepository.java          |   26 +-
 .../CacheLockHandlingTaskExecuter.java             |   38 +
 .../internal/changedetection/CachingHasher.java    |    5 +-
 .../changedetection/DefaultFileSnapshotter.java    |    0
 .../internal/changedetection/DefaultHasher.java    |    4 +-
 .../DefaultTaskArtifactStateCacheAccess.java       |   85 +
 .../DefaultTaskArtifactStateRepository.java        |   33 +-
 .../changedetection/FileCollectionSnapshot.java    |    0
 .../internal/changedetection/FileSnapshotter.java  |    0
 .../changedetection/InMemoryIndexedCache.java      |    4 +-
 .../changedetection/OutputFilesSnapshotter.java    |    5 +-
 .../ShortCircuitTaskArtifactStateRepository.java   |   87 +-
 .../TaskArtifactStateCacheAccess.java              |   50 +
 .../TaskCacheLockHandlingBuildExecuter.java        |   35 +
 .../internal/classpath/DefaultModuleRegistry.java  |  307 +++
 .../classpath/DefaultPluginModuleRegistry.java     |   51 +
 .../api/internal/classpath/EffectiveClassPath.java |   74 +
 .../api/internal/classpath/ManifestUtil.java       |  104 ++
 .../org/gradle/api/internal/classpath/Module.java  |   50 +
 .../api/internal/classpath/ModuleRegistry.java     |   35 +
 .../internal/classpath/PluginModuleRegistry.java   |   25 +
 .../internal/classpath/UnknownModuleException.java |   22 +
 .../collections/CollectionEventRegister.java       |   81 +
 .../api/internal/collections/CollectionFilter.java |   58 +
 .../internal/collections/FilteredCollection.java   |  170 ++
 .../api/internal/collections/FilteredList.java     |  148 ++
 .../api/internal/collections/FilteredSet.java      |   26 +
 .../concurrent/SynchronizedServiceRegistry.java    |   57 +
 .../api/internal/concurrent/Synchronizer.java      |   46 +
 .../api/internal/file/AbstractFileCollection.java  |    3 +-
 .../api/internal/file/AbstractFileResolver.java    |  172 +-
 .../api/internal/file/AbstractFileResource.java    |   47 +
 .../api/internal/file/AbstractFileTreeElement.java |   20 +-
 .../gradle/api/internal/file/BaseDirConverter.java |   75 -
 .../api/internal/file/BaseDirFileResolver.java     |   78 +
 .../api/internal/file/DefaultFileOperations.java   |   21 +-
 .../api/internal/file/DefaultFileTreeElement.java  |   11 +
 .../internal/file/DefaultSourceDirectorySet.java   |   14 +-
 .../file/DefaultTemporaryFileProvider.java         |   96 +-
 .../gradle/api/internal/file/FileOperations.java   |  135 +-
 .../api/internal/file/FileOrUriNotationParser.java |  110 ++
 .../org/gradle/api/internal/file/FileResolver.java |    3 +
 .../org/gradle/api/internal/file/FileResource.java |   44 +
 .../api/internal/file/IdentityFileResolver.java    |   84 +-
 .../internal/file/MaybeCompressedFileResource.java |   67 +
 .../api/internal/file/TemporaryFileProvider.java   |   68 +-
 .../internal/file/TmpDirTemporaryFileProvider.java |   56 +
 .../api/internal/file/archive/TarCopyAction.java   |    4 +-
 .../internal/file/archive/TarCopySpecVisitor.java  |   34 +-
 .../api/internal/file/archive/TarFileTree.java     |   71 +-
 .../internal/file/archive/ZipCopySpecVisitor.java  |   11 +-
 .../api/internal/file/archive/ZipFileTree.java     |   21 +-
 .../file/archive/compression/Bzip2Archiver.java    |   87 +
 .../file/archive/compression/Compressor.java       |   33 +
 .../file/archive/compression/GzipArchiver.java     |   82 +
 .../file/archive/compression/SimpleCompressor.java |   36 +
 .../DefaultFileCollectionResolveContext.java       |   12 +-
 .../file/collections/FileCollectionAdapter.java    |    3 +-
 .../collections/FileCollectionResolveContext.java  |  109 +-
 .../file/collections/ListBackedFileSet.java        |    3 +-
 .../api/internal/file/collections/MapFileTree.java |   19 +-
 .../file/collections/SimpleFileCollection.java     |    3 +-
 .../api/internal/file/copy/CopyActionImpl.java     |    8 +-
 .../api/internal/file/copy/CopySpecImpl.java       |   36 +-
 .../gradle/api/internal/file/copy/FilterChain.java |  206 +--
 .../gradle/api/internal/file/copy/LineFilter.java  |    2 +-
 .../internal/file/copy/MappingCopySpecVisitor.java |   44 +-
 .../api/internal/file/copy/PathNotationParser.java |   63 +
 .../api/internal/file/copy/ReadableCopySpec.java   |    4 +-
 .../api/internal/file/copy/RegExpNameMapper.java   |    2 +-
 .../api/internal/file/copy/RenamingCopyAction.java |    4 +-
 .../initialization/AbstractScriptHandler.java      |    8 +-
 .../initialization/DefaultScriptHandler.java       |    4 +-
 .../DefaultScriptHandlerFactory.java               |   53 +-
 .../NoClassLoaderUpdateScriptHandler.java          |    4 +-
 .../initialization/ScriptHandlerFactory.java       |    0
 .../initialization/ScriptHandlerInternal.java      |    0
 .../internal/notations/NotationParserBuilder.java  |   80 +
 .../api/internal/notations/api/NotationParser.java |   28 +
 .../notations/api/TopLevelNotationParser.java      |   24 +
 .../api/UnsupportedNotationException.java          |   28 +
 .../notations/parsers/CompositeNotationParser.java |   58 +
 .../parsers/ErrorHandlingNotationParser.java       |   73 +
 .../parsers/FlatteningNotationParser.java          |   51 +
 .../notations/parsers/JustReturningParser.java     |   44 +
 .../api/internal/notations/parsers/MapKey.java     |   28 +
 .../notations/parsers/MapNotationParser.java       |  131 ++
 .../notations/parsers/TypedNotationParser.java     |   48 +
 .../api/internal/plugins/AbstractConvention.java   |  127 --
 .../api/internal/plugins/DefaultConvention.groovy  |   34 -
 .../api/internal/plugins/DefaultConvention.java    |  235 +++
 .../plugins/DefaultExtraPropertiesExtension.java   |   81 +
 .../plugins/DefaultObjectConfigurationAction.java  |    4 +-
 .../internal/plugins/DefaultPluginCollection.java  |   38 +-
 .../internal/plugins/DefaultPluginRegistry.java    |    9 +-
 .../plugins/DefaultProjectsPluginContainer.java    |   10 +-
 .../org/gradle/api/internal/plugins/DslObject.java |   85 +
 .../api/internal/plugins/ExtensionsStorage.java    |   94 +
 .../ExtraPropertiesDynamicObjectAdapter.java       |   56 +
 .../api/internal/plugins/PluginRegistry.java       |    3 +-
 .../api/internal/project/AbstractProject.java      |  226 ++-
 .../internal/project/DefaultAntBuilderFactory.java |    2 +-
 .../project/DefaultIsolatedAntBuilder.groovy       |   87 +-
 .../internal/project/DefaultServiceRegistry.java   |  340 ----
 .../internal/project/GlobalServicesRegistry.java   |  182 +-
 .../project/GradleInternalServiceRegistry.java     |   30 +-
 .../api/internal/project/ProjectFactory.java       |   16 +-
 .../api/internal/project/ProjectInternal.java      |   15 +-
 .../project/ProjectInternalServiceRegistry.java    |   76 +-
 .../api/internal/project/ProjectScript.groovy      |   11 +-
 .../api/internal/project/ProjectStateInternal.java |   22 +-
 .../api/internal/project/ServiceRegistry.java      |   53 -
 .../internal/project/ServiceRegistryFactory.java   |    4 +-
 .../internal/project/TaskExecutionServices.java    |   78 +
 .../project/TaskInternalServiceRegistry.java       |    2 +
 .../project/TopLevelBuildServiceRegistry.java      |  281 +--
 .../AnnotationProcessingTaskFactory.java           |   57 +-
 .../InputPropertyAnnotationHandler.java            |    0
 .../NestedBeanPropertyAnnotationHandler.java       |    6 +-
 .../OutputDirectoryPropertyAnnotationHandler.java  |   61 +-
 .../OutputFilePropertyAnnotationHandler.java       |   61 +-
 .../project/taskfactory/PropertyActionContext.java |    8 +
 .../org/gradle/api/internal/resource/Resource.java |    3 +-
 .../gradle/api/internal/resource/UriResource.java  |    2 +-
 .../internal/resources/DefaultResourceHandler.java |   52 +
 .../resources/ResourceIsAFolderException.java      |   28 +
 .../gradle/api/internal/resources/URIBuilder.java  |   48 +
 .../api/internal/tasks/DefaultTaskCollection.java  |   54 +-
 .../api/internal/tasks/DefaultTaskContainer.java   |   49 +-
 .../tasks/DefaultTaskContainerFactory.java         |   12 +-
 .../api/internal/tasks/DefaultTaskDependency.java  |    4 +-
 .../api/internal/tasks/DefaultTaskInputs.java      |    4 +-
 .../api/internal/tasks/TaskContainerInternal.java  |    2 +-
 .../execution/ExecuteActionsTaskExecuter.java      |    3 -
 .../groovy/org/gradle/api/invocation/Gradle.java   |   39 +-
 .../groovy/org/gradle/api/logging/LogLevel.java    |    4 +-
 .../main/groovy/org/gradle/api/logging/Logger.java |   16 +
 .../org/gradle/api/logging/LoggingManager.java     |   16 -
 .../groovy/org/gradle/api/plugins/Convention.java  |   10 +-
 .../org/gradle/api/plugins/ExtensionAware.java     |   72 +
 .../org/gradle/api/plugins/ExtensionContainer.java |  103 ++
 .../api/plugins/ExtraPropertiesExtension.java      |  186 ++
 .../api/plugins/ObjectConfigurationAction.java     |    0
 .../org/gradle/api/plugins/PluginCollection.java   |   25 +-
 .../api/resources/MissingResourceException.java    |   26 +
 .../org/gradle/api/resources/ReadableResource.java |   32 +
 .../groovy/org/gradle/api/resources/Resource.java  |   47 +
 .../gradle/api/resources/ResourceException.java    |   35 +
 .../org/gradle/api/resources/ResourceHandler.java  |   39 +
 .../org/gradle/api/resources/package-info.java     |   20 +
 .../main/groovy/org/gradle/api/specs/AndSpec.java  |    4 +-
 .../groovy/org/gradle/api/specs/CompositeSpec.java |    7 +-
 .../main/groovy/org/gradle/api/specs/Specs.java    |   25 +-
 .../org/gradle/api/tasks/AbstractCopyTask.java     |   28 +-
 .../org/gradle/api/tasks/ConventionValue.java      |   15 +-
 .../src/main/groovy/org/gradle/api/tasks/Copy.java |  203 +-
 .../groovy/org/gradle/api/tasks/Directory.groovy   |    6 +-
 .../src/main/groovy/org/gradle/api/tasks/Exec.java |    6 +-
 .../groovy/org/gradle/api/tasks/GradleBuild.java   |    0
 .../main/groovy/org/gradle/api/tasks/Input.java    |    0
 .../main/groovy/org/gradle/api/tasks/JavaExec.java |   30 +-
 .../main/groovy/org/gradle/api/tasks/Optional.java |    2 +-
 .../org/gradle/api/tasks/OutputDirectories.java    |   32 +
 .../groovy/org/gradle/api/tasks/OutputFiles.java   |   32 +
 .../groovy/org/gradle/api/tasks/SourceTask.java    |   30 +-
 .../org/gradle/api/tasks/TaskCollection.java       |   29 +-
 .../gradle/api/tasks/TaskExecutionException.java   |    0
 .../groovy/org/gradle/api/tasks/TaskInputs.java    |    2 +-
 .../api/tasks/TaskInstantiationException.java      |   58 +-
 .../main/groovy/org/gradle/api/tasks/Upload.java   |   27 +-
 .../org/gradle/api/tasks/VerificationTask.java     |   71 +-
 .../api/tasks/bundling/AbstractArchiveTask.java    |   28 +
 .../org/gradle/api/tasks/bundling/Compression.java |   34 +-
 .../groovy/org/gradle/api/tasks/bundling/Tar.java  |   27 +-
 .../tasks/diagnostics/DependencyReportTask.java    |    2 +-
 .../api/tasks/diagnostics/TaskReportTask.java      |    6 +-
 .../diagnostics/internal/AsciiReportRenderer.java  |   35 +-
 .../internal/SingleProjectTaskReportModel.java     |   13 +-
 .../diagnostics/internal/TaskReportRenderer.java   |    8 +-
 .../diagnostics/internal/TextReportRenderer.java   |    9 +-
 .../org/gradle/cache/AutoCloseCacheFactory.java    |   91 -
 .../main/groovy/org/gradle/cache/CacheAccess.java  |   55 +
 .../main/groovy/org/gradle/cache/CacheBuilder.java |   48 +-
 .../main/groovy/org/gradle/cache/CacheFactory.java |   31 -
 .../org/gradle/cache/CacheOpenException.java       |   30 +
 .../groovy/org/gradle/cache/CacheRepository.java   |   64 +-
 .../groovy/org/gradle/cache/CacheValidator.java    |   25 +
 .../org/gradle/cache/DefaultCacheFactory.java      |   34 -
 .../org/gradle/cache/DefaultCacheRepository.java   |   92 -
 .../cache/DefaultPersistentDirectoryCache.java     |  117 --
 .../groovy/org/gradle/cache/DefaultSerializer.java |    2 +-
 .../org/gradle/cache/DirectoryCacheBuilder.java    |   49 +
 .../org/gradle/cache/ObjectCacheBuilder.java       |   37 +
 .../groovy/org/gradle/cache/PersistentCache.java   |   52 +-
 .../org/gradle/cache/PersistentIndexedCache.java   |   11 +
 .../org/gradle/cache/PersistentStateCache.java     |   21 +
 .../groovy/org/gradle/cache/SimpleStateCache.java  |   63 -
 .../cache/btree/BTreePersistentIndexedCache.java   |  684 -------
 .../main/groovy/org/gradle/cache/btree/Block.java  |   59 -
 .../org/gradle/cache/btree/BlockPayload.java       |   51 -
 .../org/gradle/cache/btree/BlockPointer.java       |   68 -
 .../groovy/org/gradle/cache/btree/BlockStore.java  |   68 -
 .../org/gradle/cache/btree/CachingBlockStore.java  |  100 -
 .../cache/btree/CorruptedCacheException.java       |   22 -
 .../gradle/cache/btree/FileBackedBlockStore.java   |  352 ----
 .../org/gradle/cache/btree/FreeListBlockStore.java |  271 ---
 .../gradle/cache/btree/StateCheckBlockStore.java   |   78 -
 .../gradle/cache/internal/AbstractFileAccess.java  |   35 +
 .../org/gradle/cache/internal/CacheFactory.java    |   34 +
 .../gradle/cache/internal/DefaultCacheAccess.java  |  289 +++
 .../gradle/cache/internal/DefaultCacheFactory.java |  239 +++
 .../cache/internal/DefaultCacheRepository.java     |  252 +++
 .../cache/internal/DefaultFileLockManager.java     |  296 +++
 .../internal/DefaultPersistentDirectoryCache.java  |  112 ++
 .../internal/DefaultPersistentDirectoryStore.java  |  113 ++
 .../internal/DefaultProcessMetaDataProvider.java   |   35 +
 .../org/gradle/cache/internal/FileAccess.java      |   59 +
 .../groovy/org/gradle/cache/internal/FileLock.java |   37 +
 .../org/gradle/cache/internal/FileLockManager.java |   56 +
 .../cache/internal/LockTimeoutException.java       |   25 +
 .../MultiProcessSafePersistentIndexedCache.java    |   92 +
 .../gradle/cache/internal/OnDemandFileAccess.java  |   50 +
 .../cache/internal/ProcessMetaDataProvider.java    |   31 +
 .../gradle/cache/internal/SimpleStateCache.java    |   91 +
 .../cache/internal/UnitOfWorkParticipant.java      |   22 +
 .../btree/BTreePersistentIndexedCache.java         |  720 ++++++++
 .../org/gradle/cache/internal/btree/Block.java     |   59 +
 .../gradle/cache/internal/btree/BlockPayload.java  |   51 +
 .../gradle/cache/internal/btree/BlockPointer.java  |   68 +
 .../gradle/cache/internal/btree/BlockStore.java    |   68 +
 .../cache/internal/btree/CachingBlockStore.java    |  100 +
 .../internal/btree/CorruptedCacheException.java    |   22 +
 .../cache/internal/btree/FileBackedBlockStore.java |  352 ++++
 .../cache/internal/btree/FreeListBlockStore.java   |  271 +++
 .../cache/internal/btree/LockingBlockStore.java    |   94 +
 .../cache/internal/btree/StateCheckBlockStore.java |   78 +
 .../configuration/DefaultProjectEvaluator.java     |   45 -
 .../configuration/DefaultScriptPluginFactory.java  |    4 +-
 .../configuration/GradleLauncherMetaData.java      |    4 +-
 .../configuration/ImplicitTasksConfigurer.java     |    2 +-
 .../org/gradle/configuration/ImportsReader.groovy  |   32 -
 .../org/gradle/configuration/ImportsReader.java    |   61 +
 .../gradle/configuration/ImportsScriptSource.java  |    9 +-
 .../configuration/LifecycleProjectEvaluator.java   |   50 +
 .../org/gradle/configuration/ScriptPlugin.java     |    0
 .../gradle/configuration/ScriptPluginFactory.java  |    0
 .../gradle/execution/BuildConfigurationAction.java |   20 +
 .../groovy/org/gradle/execution/BuildExecuter.java |    6 -
 .../org/gradle/execution/BuildExecutionAction.java |   20 +
 .../gradle/execution/BuildExecutionContext.java    |   24 +
 .../org/gradle/execution/DefaultBuildExecuter.java |   74 +-
 .../gradle/execution/DefaultTaskGraphExecuter.java |   61 +-
 .../DefaultTasksBuildExecutionAction.java          |   55 +
 .../gradle/execution/DelegatingBuildExecuter.java  |   56 -
 .../org/gradle/execution/DryRunBuildExecuter.java  |   35 -
 .../execution/DryRunBuildExecutionAction.java      |   34 +
 ...ludedTaskFilteringBuildConfigurationAction.java |   57 +
 .../execution/ProjectDefaultsBuildExecuter.java    |   53 -
 .../execution/SelectedTaskExecutionAction.java     |   58 +
 .../org/gradle/execution/TaskFailureHandler.java   |   25 +
 .../org/gradle/execution/TaskGraphExecuter.java    |    5 +-
 .../TaskNameResolvingBuildConfigurationAction.java |  110 ++
 .../execution/TaskNameResolvingBuildExecuter.java  |  115 --
 .../scripts/AsmBackedEmptyScriptGenerator.java     |   84 -
 .../org/gradle/groovy/scripts/BasicScript.groovy   |  149 +-
 .../scripts/CachingScriptCompilationHandler.java   |   46 -
 .../gradle/groovy/scripts/CachingScriptSource.java |    0
 .../org/gradle/groovy/scripts/DefaultScript.groovy |  347 ++--
 .../scripts/DefaultScriptCompilationHandler.java   |  225 ---
 .../scripts/DefaultScriptCompilerFactory.java      |   51 +-
 .../groovy/scripts/DefaultScriptRunnerFactory.java |   61 -
 .../groovy/scripts/DelegatingScriptSource.java     |    0
 .../groovy/org/gradle/groovy/scripts/Script.java   |    2 +-
 .../org/gradle/groovy/scripts/ScriptAware.java     |    0
 .../groovy/scripts/ScriptCompilationException.java |   40 +
 .../groovy/scripts/ScriptCompilationHandler.java   |   31 -
 .../org/gradle/groovy/scripts/ScriptCompiler.java  |    2 -
 .../groovy/scripts/ScriptExecutionListener.java    |    0
 .../org/gradle/groovy/scripts/ScriptMetaData.java  |   25 -
 .../gradle/groovy/scripts/ScriptRunnerFactory.java |   20 -
 .../org/gradle/groovy/scripts/ScriptSource.java    |    4 +-
 .../gradle/groovy/scripts/StringScriptSource.java  |    4 +-
 .../org/gradle/groovy/scripts/UriScriptSource.java |    6 +-
 .../internal/AsmBackedEmptyScriptGenerator.java    |   84 +
 .../internal/CachingScriptClassCompiler.java       |   41 +
 .../internal/DefaultScriptCompilationHandler.java  |  226 +++
 .../internal/DefaultScriptRunnerFactory.java       |   64 +
 .../scripts/internal/EmptyScriptGenerator.java     |   20 +
 .../FileCacheBackedScriptClassCompiler.java        |   83 +
 .../scripts/internal/ScriptClassCompiler.java      |   23 +
 .../scripts/internal/ScriptCompilationHandler.java |   33 +
 .../scripts/internal/ScriptRunnerFactory.java      |   23 +
 .../internal/ShortCircuitEmptyScriptCompiler.java  |   37 +
 .../AbstractCommandLineConverter.java              |   38 -
 ...AbstractSettingsFileSearchStrategyTemplate.java |   37 -
 .../org/gradle/initialization/BaseSettings.java    |   15 +-
 .../initialization/BuildFileProjectSpec.java       |    3 +-
 .../org/gradle/initialization/BuildLoader.java     |   87 +-
 .../gradle/initialization/BuildSourceBuilder.java  |   40 +-
 .../gradle/initialization/ClassLoaderFactory.java  |   35 -
 .../gradle/initialization/ClassLoaderRegistry.java |   40 +
 .../initialization/CommandLineConverter.java       |   33 -
 .../gradle/initialization/CommandLineOption.java   |   72 -
 .../gradle/initialization/CommandLineParser.java   |  460 -----
 .../initialization/CompositeInitScriptFinder.java  |   37 +
 .../initialization/DefaultClassLoaderFactory.java  |   63 -
 .../initialization/DefaultClassLoaderRegistry.java |   79 +
 .../DefaultCommandLineConverter.java               |  193 +-
 .../initialization/DefaultExceptionAnalyser.java   |  218 +--
 .../initialization/DefaultGradleLauncher.java      |   27 +-
 .../DefaultGradleLauncherFactory.java              |   37 +-
 .../DefaultGradlePropertiesLoader.java             |   62 +-
 .../initialization/DefaultInitScriptFinder.java    |   40 -
 .../gradle/initialization/DefaultSettings.groovy   |    6 +-
 .../initialization/DefaultSettingsFinder.java      |   31 +-
 .../initialization/DependencyResolutionLogger.java |   42 +
 .../initialization/DirectoryInitScriptFinder.java  |   43 +
 .../DistributionInitScriptFinder.java              |   43 +
 .../EmbeddedScriptSettingsFinder.java              |   36 -
 .../initialization/GradleLauncherAction.java       |   18 +
 .../initialization/GradleLauncherFactory.java      |    2 +-
 .../initialization/IGradlePropertiesLoader.java    |    8 +-
 .../ISettingsFileSearchStrategy.java               |   27 -
 .../gradle/initialization/InitScriptFinder.java    |    4 +-
 .../gradle/initialization/InitScriptHandler.java   |    4 +-
 .../initialization/InstantiatingBuildLoader.java   |   73 +
 .../MasterDirSettingsFinderStrategy.java           |   43 -
 .../initialization/ModelConfigurationListener.java |   25 +
 .../ParentDirSettingsFinderStrategy.java           |   39 -
 .../gradle/initialization/ParsedCommandLine.java   |   80 -
 .../initialization/ParsedCommandLineOption.java    |   42 -
 .../ProjectDirectoryProjectSpec.java               |    3 +-
 .../ProjectPropertySettingBuildLoader.java         |   81 +
 .../PropertiesLoadingSettingsProcessor.java        |   11 +-
 .../initialization/ProvidedInitScriptFinder.java   |   37 +
 .../SameLevelDirSettingsFinderStrategy.java        |   42 -
 .../ScriptEvaluatingSettingsProcessor.java         |   18 +-
 .../org/gradle/initialization/SettingsFactory.java |    3 +-
 .../org/gradle/initialization/SettingsHandler.java |   37 +-
 .../gradle/initialization/SettingsLocation.java    |   14 +-
 .../gradle/initialization/SettingsProcessor.java   |    3 +-
 .../initialization/UserHomeInitScriptFinder.java   |   21 +-
 .../gradle/initialization/layout/BuildLayout.java  |   36 +
 .../layout/BuildLayoutConfiguration.java           |   53 +
 .../initialization/layout/BuildLayoutFactory.java  |   69 +
 .../org/gradle/invocation/DefaultGradle.java       |   55 +-
 .../gradle/listener/AsyncListenerBroadcast.java    |    2 +-
 .../gradle/listener/ContextClassLoaderProxy.java   |   82 +-
 .../org/gradle/listener/LazyCreationProxy.java     |   55 +
 .../org/gradle/listener/ListenerBroadcast.java     |    6 +-
 .../listener/ListenerNotificationException.java    |    0
 .../org/gradle/logging/LoggingConfiguration.java   |   24 +-
 .../org/gradle/logging/LoggingServiceRegistry.java |   63 +-
 .../groovy/org/gradle/logging/ShowStacktrace.java  |   23 +
 .../AbstractLineChoppingStyledTextOutput.java      |   77 +
 .../logging/internal/AbstractStyledTextOutput.java |    5 +-
 .../org/gradle/logging/internal/AnsiConsole.java   |   86 +-
 .../internal/BufferingStyledTextOutput.java        |   77 +
 .../gradle/logging/internal/DefaultColorMap.java   |    5 -
 .../logging/internal/DefaultLoggingConfigurer.java |    8 +
 .../logging/internal/DefaultLoggingManager.java    |   14 +-
 .../internal/DefaultLoggingManagerFactory.java     |    2 +-
 .../internal/DefaultStdErrLoggingSystem.java       |   37 +
 .../internal/DefaultStdOutLoggingSystem.java       |   37 +
 .../internal/EmbeddedLoggingManagerFactory.java    |   36 +
 .../logging/internal/EmbeddedLoggingServices.java  |   30 +
 .../internal/LinePrefixingStyledTextOutput.java    |   46 +
 .../internal/LoggingCommandLineConverter.java      |   71 +-
 .../gradle/logging/internal/NoOpLoggingSystem.java |   42 +
 .../logging/internal/OutputEventRenderer.java      |   10 +-
 .../internal/ProgressLogEventGenerator.java        |    2 +-
 .../logging/internal/Slf4jLoggingConfigurer.java   |  183 --
 .../logging/internal/StdErrLoggingSystem.java      |   25 +-
 .../logging/internal/StdOutLoggingSystem.java      |   25 +-
 .../internal/StyledTextOutputBackedRenderer.java   |    8 +-
 .../gradle/logging/internal/TerminalDetector.java  |   88 -
 .../logging/internal/TerminalDetectorFactory.java  |   44 +
 .../slf4j/SimpleSlf4jLoggingConfigurer.java        |   38 +
 .../internal/slf4j/Slf4jLoggingConfigurer.java     |  188 ++
 .../groovy/org/gradle/messaging/actor/Actor.java   |   23 +-
 .../org/gradle/messaging/actor/ActorFactory.java   |    8 +
 .../actor/internal/DefaultActorFactory.java        |  100 +-
 .../messaging/concurrent/AsyncStoppable.java       |   67 +-
 .../messaging/concurrent/CompositeStoppable.java   |  108 --
 .../concurrent/DefaultExecutorFactory.java         |   19 +-
 .../org/gradle/messaging/concurrent/Stoppable.java |   28 -
 .../org/gradle/messaging/dispatch/Addressable.java |   24 -
 .../gradle/messaging/dispatch/AsyncDispatch.java   |  380 ++--
 .../gradle/messaging/dispatch/AsyncReceive.java    |  345 ++--
 .../messaging/dispatch/BroadcastDispatch.java      |  278 +--
 .../gradle/messaging/dispatch/DelayedReceive.java  |  161 ++
 .../dispatch/DiscardOnFailureDispatch.java         |   36 -
 .../dispatch/DiscardingFailureHandler.java         |   30 +
 .../org/gradle/messaging/dispatch/Dispatch.java    |    0
 .../messaging/dispatch/DispatchFailureHandler.java |   23 +
 .../dispatch/ExceptionTrackingDispatch.java        |   37 -
 .../dispatch/ExceptionTrackingFailureHandler.java  |   47 +
 .../dispatch/ExceptionTrackingListener.java        |   49 -
 .../dispatch/FailureHandlingDispatch.java          |   34 +
 .../messaging/dispatch/MethodInvocation.java       |    5 +
 .../messaging/dispatch/ProxyDispatchAdapter.java   |  176 +-
 .../gradle/messaging/dispatch/QueuingDispatch.java |   80 +
 .../org/gradle/messaging/dispatch/Receive.java     |   56 +-
 .../messaging/dispatch/ReflectionDispatch.java     |   84 +-
 .../messaging/dispatch/StoppableDispatch.java      |   50 +-
 .../messaging/dispatch/ThreadSafeDispatch.java     |   31 -
 .../org/gradle/messaging/remote/Address.java       |   30 +
 .../org/gradle/messaging/remote/Addressable.java   |   22 +
 .../org/gradle/messaging/remote/ConnectEvent.java  |   14 +-
 .../gradle/messaging/remote/MessagingClient.java   |   61 +-
 .../gradle/messaging/remote/MessagingServer.java   |   72 +-
 .../gradle/messaging/remote/ObjectConnection.java  |  127 +-
 .../messaging/remote/internal/AsyncConnection.java |   39 +
 .../remote/internal/AsyncConnectionAdapter.java    |   84 +
 .../remote/internal/BroadcastSendProtocol.java     |   96 +
 .../remote/internal/BufferingProtocol.java         |   90 +
 .../remote/internal/ChannelLookupProtocol.java     |  105 ++
 .../messaging/remote/internal/ChannelMessage.java  |   52 -
 .../ChannelMessageMarshallingDispatch.java         |   47 -
 .../ChannelMessageUnmarshallingDispatch.java       |   47 -
 .../messaging/remote/internal/ChannelMetaInfo.java |   53 -
 .../internal/ChannelRegistrationProtocol.java      |   90 +
 .../remote/internal/CompositeAddress.java          |   62 +
 .../remote/internal/ConnectException.java          |    0
 .../messaging/remote/internal/ConnectRequest.java  |   31 -
 .../messaging/remote/internal/Connection.java      |   61 +-
 .../remote/internal/DefaultIncomingBroadcast.java  |   88 +
 .../remote/internal/DefaultMessageSerializer.java  |   37 +
 .../remote/internal/DefaultMessagingClient.java    |   89 +-
 .../remote/internal/DefaultMessagingServer.java    |  193 +-
 .../internal/DefaultMultiChannelConnection.java    |  238 +--
 .../internal/DefaultMultiChannelConnector.java     |  141 +-
 .../remote/internal/DefaultObjectConnection.java   |  133 +-
 .../remote/internal/DefaultOutgoingBroadcast.java  |  116 ++
 .../remote/internal/DelegatingConnection.java      |   45 +
 .../remote/internal/DisconnectAwareConnection.java |   46 +
 .../DisconnectAwareConnectionDecorator.java        |  124 ++
 .../remote/internal/EagerReceiveBuffer.java        |  271 +++
 .../remote/internal/EndOfStreamDispatch.java       |   59 -
 .../remote/internal/EndOfStreamEvent.java          |   29 -
 .../remote/internal/EndOfStreamFilter.java         |   72 -
 .../remote/internal/EndOfStreamReceive.java        |   43 -
 .../remote/internal/GroupMessageFilter.java        |   48 +
 .../internal/HandshakeIncomingConnector.java       |   45 +-
 .../internal/HandshakeOutgoingConnector.java       |   37 +-
 .../remote/internal/IncomingBroadcast.java         |   20 +
 .../remote/internal/IncomingConnector.java         |   64 +-
 .../internal/IncomingMethodInvocationHandler.java  |  110 +-
 .../messaging/remote/internal/InputForwarder.java  |  135 ++
 .../gradle/messaging/remote/internal/Message.java  |  326 ++--
 .../messaging/remote/internal/MessageHub.java      |  224 +++
 .../remote/internal/MessageIOException.java        |   24 +
 .../remote/internal/MessageOriginator.java         |   57 +
 .../remote/internal/MessageSerializer.java         |   27 +
 .../remote/internal/MessagingServices.java         |  197 ++
 .../MethodInvocationMarshallingDispatch.java       |  104 +-
 .../MethodInvocationUnmarshallingDispatch.java     |  115 +-
 .../messaging/remote/internal/MethodMetaInfo.java  |  114 --
 .../remote/internal/MultiChannelConnection.java    |   92 +-
 .../remote/internal/MultiChannelConnector.java     |   55 +-
 .../remote/internal/OutgoingBroadcast.java         |   20 +
 .../remote/internal/OutgoingConnector.java         |   53 +-
 .../internal/OutgoingMethodInvocationHandler.java  |   89 +-
 .../remote/internal/OutgoingMultiplex.java         |   33 +
 .../remote/internal/PlaceholderException.java      |   57 +-
 .../gradle/messaging/remote/internal/Protocol.java |   53 +
 .../messaging/remote/internal/ProtocolContext.java |   39 +
 .../messaging/remote/internal/ProtocolStack.java   |  323 ++++
 .../messaging/remote/internal/ReceiveProtocol.java |  112 ++
 .../remote/internal/RemoteDisconnectProtocol.java  |   66 +
 .../remote/internal/RemoteMethodInvocation.java    |   54 -
 .../gradle/messaging/remote/internal/Router.java   |  238 +++
 .../messaging/remote/internal/SendProtocol.java    |  116 ++
 .../remote/internal/SocketConnection.java          |  208 ---
 .../remote/internal/SynchronizedDispatch.java      |   61 +
 .../remote/internal/TcpIncomingConnector.java      |  115 --
 .../remote/internal/TcpMessagingClient.java        |   55 -
 .../remote/internal/TcpMessagingServer.java        |   58 -
 .../remote/internal/TcpOutgoingConnector.java      |  100 -
 .../remote/internal/TypeCastDispatch.java          |   32 +
 .../remote/internal/UnicastSendProtocol.java       |   86 +
 .../messaging/remote/internal/WorkerProtocol.java  |   70 +
 .../remote/internal/inet/InetAddressFactory.java   |   78 +
 .../remote/internal/inet/InetEndpoint.java         |   31 +
 .../remote/internal/inet/MultiChoiceAddress.java   |   77 +
 .../remote/internal/inet/MulticastConnection.java  |   88 +
 .../remote/internal/inet/SocketConnection.java     |  233 +++
 .../remote/internal/inet/SocketInetAddress.java    |   68 +
 .../remote/internal/inet/TcpIncomingConnector.java |  131 ++
 .../remote/internal/inet/TcpOutgoingConnector.java |   78 +
 .../internal/protocol/AbstractPayloadMessage.java  |   42 +
 .../remote/internal/protocol/ChannelAvailable.java |   57 +
 .../internal/protocol/ChannelUnavailable.java      |   58 +
 .../remote/internal/protocol/ConnectRequest.java   |   32 +
 .../internal/protocol/ConsumerAvailable.java       |   34 +
 .../remote/internal/protocol/ConsumerMessage.java  |   62 +
 .../remote/internal/protocol/ConsumerReady.java    |   22 +
 .../remote/internal/protocol/ConsumerStopped.java  |   22 +
 .../remote/internal/protocol/ConsumerStopping.java |   22 +
 .../internal/protocol/ConsumerUnavailable.java     |   22 +
 .../remote/internal/protocol/DiscoveryMessage.java |   56 +
 .../protocol/DiscoveryProtocolSerializer.java      |  163 ++
 .../remote/internal/protocol/EndOfStreamEvent.java |   20 +
 .../remote/internal/protocol/LookupRequest.java    |   51 +
 .../remote/internal/protocol/MessageCredits.java   |   53 +
 .../remote/internal/protocol/MethodMetaInfo.java   |  115 ++
 .../internal/protocol/ParticipantAvailable.java    |   68 +
 .../internal/protocol/ParticipantUnavailable.java  |   56 +
 .../remote/internal/protocol/PayloadMessage.java   |   24 +
 .../internal/protocol/ProducerAvailable.java       |   34 +
 .../remote/internal/protocol/ProducerMessage.java  |   62 +
 .../remote/internal/protocol/ProducerReady.java    |   22 +
 .../remote/internal/protocol/ProducerStopped.java  |   22 +
 .../internal/protocol/ProducerUnavailable.java     |   22 +
 .../internal/protocol/RemoteMethodInvocation.java  |   56 +
 .../remote/internal/protocol/Request.java          |   63 +
 .../remote/internal/protocol/RoutableMessage.java  |   23 +
 .../internal/protocol/RouteAvailableMessage.java   |   33 +
 .../internal/protocol/RouteUnavailableMessage.java |   20 +
 .../remote/internal/protocol/StatelessMessage.java |   42 +
 .../remote/internal/protocol/UnknownMessage.java   |   30 +
 .../remote/internal/protocol/WorkerStopped.java    |   42 +
 .../remote/internal/protocol/WorkerStopping.java   |   19 +
 .../groovy/org/gradle/process/JavaForkOptions.java |  396 ++--
 .../org/gradle/process/ProcessForkOptions.java     |    0
 .../internal/AbstractExecHandleBuilder.java        |   12 +-
 .../gradle/process/internal/DefaultExecHandle.java |   10 +-
 .../process/internal/DefaultJavaForkOptions.java   |  167 +-
 .../internal/DefaultProcessForkOptions.java        |    2 +-
 .../process/internal/DefaultWorkerProcess.java     |    6 +-
 .../internal/DefaultWorkerProcessFactory.java      |   20 +-
 .../org/gradle/process/internal/ExecHandle.java    |    2 +
 .../gradle/process/internal/ExecHandleBuilder.java |    3 +-
 .../process/internal/ExecOutputHandleRunner.java   |    7 +-
 .../process/internal/JavaExecHandleBuilder.java    |   16 +
 .../org/gradle/process/internal/JvmOptions.java    |  308 +++
 .../internal/ProcessParentingInitializer.java      |   58 +
 .../internal/child/ActionExecutionWorker.java      |   20 +-
 ...nClassesInIsolatedClassLoaderWorkerFactory.java |   39 +-
 ...ionClassesInSystemClassLoaderWorkerFactory.java |   50 +-
 .../internal/child/BootstrapSecurityManager.java   |   93 +
 .../child/ImplementationClassLoaderWorker.java     |    9 +-
 .../IsolatedApplicationClassLoaderWorker.java      |   14 +-
 .../child/SystemApplicationClassLoaderWorker.java  |   20 +-
 .../process/internal/child/WorkerFactory.java      |   12 +-
 .../child/WorkerProcessClassPathProvider.java      |   69 +-
 .../launcher/BootstrapClassLoaderWorker.java       |   12 +-
 .../groovy/org/gradle/profile/BuildProfile.java    |   63 +-
 .../org/gradle/profile/CompositeOperation.java     |   50 +
 .../org/gradle/profile/ContinuousOperation.java    |   40 +
 .../gradle/profile/DependencyResolveProfile.java   |   30 +
 .../org/gradle/profile/ElapsedTimeFormatter.java   |   54 -
 .../org/gradle/profile/HTMLProfileReport.groovy    |   41 +-
 .../main/groovy/org/gradle/profile/Operation.java  |   26 +
 .../org/gradle/profile/ProfileEventAdapter.java    |  114 ++
 .../groovy/org/gradle/profile/ProfileListener.java |   85 +-
 .../org/gradle/profile/ProfileReportRenderer.java  |   63 +
 .../groovy/org/gradle/profile/ProjectProfile.java  |   66 +-
 .../profile/ReportGeneratingProfileListener.java   |   40 +
 .../groovy/org/gradle/profile/TaskExecution.java   |   48 +
 .../groovy/org/gradle/profile/TaskProfile.java     |   74 -
 .../org/gradle/reporting/CodePanelRenderer.java    |   28 +
 .../org/gradle/reporting/DomReportRenderer.java    |   55 +
 .../org/gradle/reporting/DurationFormatter.java    |   56 +
 .../org/gradle/reporting/HtmlReportRenderer.java   |   93 +
 .../org/gradle/reporting/TabbedPageRenderer.java   |   76 +
 .../groovy/org/gradle/reporting/TabsRenderer.java  |   61 +
 .../gradle/reporting/TextDomReportRenderer.java    |   50 +
 .../org/gradle/reporting/TextReportRenderer.java   |   44 +
 .../org/gradle/testfixtures/ProjectBuilder.java    |   74 +-
 .../testfixtures/internal/GlobalTestServices.java  |   62 +-
 .../internal/InMemoryCacheFactory.java             |   87 +-
 .../testfixtures/internal/NoOpLoggingManager.java  |   10 -
 .../testfixtures/internal/ProjectBuilderImpl.java  |   93 +
 .../internal/TestTopLevelBuildServiceRegistry.java |    9 +-
 .../org/gradle/util/AvailablePortFinder.java       |  116 ++
 .../gradle/util/BuildCommencedTimeProvider.java    |   24 +
 .../util/ClassLoaderBackedClasspathSource.java     |   43 +
 .../groovy/org/gradle/util/ClassLoaderFactory.java |   39 +
 .../src/main/groovy/org/gradle/util/ClassPath.java |   41 +
 .../groovy/org/gradle/util/ClasspathSource.java    |   23 +
 .../main/groovy/org/gradle/util/ClasspathUtil.java |   58 +-
 .../groovy/org/gradle/util/CollectionUtils.java    |   75 +
 .../main/groovy/org/gradle/util/ConfigureUtil.java |  122 +-
 .../org/gradle/util/DefaultClassLoaderFactory.java |   82 +
 .../groovy/org/gradle/util/DefaultClassPath.java   |   85 +
 .../groovy/org/gradle/util/DeprecationLogger.java  |  218 ++-
 .../org/gradle/util/DisconnectableInputStream.java |    5 +-
 .../org/gradle/util/DistributionLocator.java       |   37 +-
 .../org/gradle/util/FilteringClassLoader.java      |   46 +-
 .../main/groovy/org/gradle/util/GFileUtils.java    |   67 +
 .../src/main/groovy/org/gradle/util/GUtil.java     |   70 +-
 .../main/groovy/org/gradle/util/GradleVersion.java |   68 +-
 .../src/main/groovy/org/gradle/util/HashUtil.java  |   69 -
 .../main/groovy/org/gradle/util/JavaMethod.java    |   12 +-
 .../groovy/org/gradle/util/JavaReflectionUtil.java |   40 +
 .../core/src/main/groovy/org/gradle/util/Jvm.java  |  111 +-
 .../org/gradle/util/LineBufferingOutputStream.java |    1 +
 .../org/gradle/util/MultiParentClassLoader.java    |   14 +-
 .../org/gradle/util/MutableURLClassLoader.java     |   46 +
 .../org/gradle/util/ObservableUrlClassLoader.java  |   52 -
 .../groovy/org/gradle/util/OperatingSystem.java    |  120 --
 .../src/main/groovy/org/gradle/util/PosixUtil.java |   75 -
 .../groovy/org/gradle/util/ReflectionUtil.groovy   |   44 +-
 .../groovy/org/gradle/util/ServiceLocator.java     |  147 ++
 .../main/groovy/org/gradle/util/StdinSwapper.java  |   39 +
 .../main/groovy/org/gradle/util/StdoutSwapper.java |   39 +
 .../src/main/groovy/org/gradle/util/Swapper.java   |   43 +
 .../groovy/org/gradle/util/SystemProperties.java   |   29 -
 .../src/main/groovy/org/gradle/util/TextUtil.java  |   28 +-
 .../main/groovy/org/gradle/util/TreeVisitor.java   |   39 +
 .../main/groovy/org/gradle/util/UUIDGenerator.java |   24 +
 .../groovy/org/gradle/util/UncheckedException.java |   33 -
 .../src/main/groovy/org/gradle/util/WrapUtil.java  |   14 +
 .../main/groovy/org/gradle/util/hash/HashUtil.java |   84 +
 .../groovy/org/gradle/util/hash/HashValue.java     |   86 +
 .../gradle/util/internal/ArgumentsSplitter.java    |   63 +
 .../gradle/util/internal/LimitedDescription.java   |   60 +
 .../org/gradle/configuration/default-imports.txt   |    3 +
 .../org/gradle/profile/ProfileTemplate.html        |  237 ++-
 .../main/resources/org/gradle/profile/style.css    |    4 +
 .../resources/org/gradle/reporting/base-style.css  |  162 ++
 .../main/resources/org/gradle/reporting}/report.js |    0
 subprojects/core/src/releases.xml                  |   25 +
 .../org/gradle/BuildExceptionReporterTest.groovy   |  105 +-
 .../groovy/org/gradle/StartParameterTest.groovy    |  135 +-
 .../groovy/org/gradle/api/JavaVersionTest.java     |   12 +-
 .../api/artifacts/PublishInstructionTest.java      |   71 -
 .../api/artifacts/maven/Conf2ScopeMappingTest.java |   55 -
 .../org/gradle/api/artifacts/specs/TypeTest.groovy |   38 -
 .../api/file/FileCollectionSymlinkTest.groovy      |   53 +
 .../org/gradle/api/file/FileVisitorUtil.groovy     |   29 +-
 .../api/internal/AbstractClassGeneratorTest.java   |  416 ++++-
 .../AbstractClassGeneratorTestGroovy.groovy        |   26 +
 .../AbstractMultiCauseExceptionTest.groovy         |   23 +-
 .../AbstractNamedDomainObjectContainerTest.groovy  |  196 ++
 .../api/internal/AsmBackedClassGeneratorTest.java  |    2 +-
 .../AutoCreateDomainObjectContainerTest.groovy     |  189 --
 .../api/internal/ChainingTransformerTest.java      |    6 +-
 .../ClassGeneratorBackedInstantiatorTest.groovy    |   35 +
 .../internal/CompositeDomainObjectSetTest.groovy   |  403 ++++
 .../api/internal/ConventionAwareHelperTest.java    |   97 +-
 ...faultAutoCreateDomainObjectContainerSpec.groovy |   59 -
 .../internal/DefaultClassPathRegistryTest.groovy   |   50 +
 .../DefaultDomainObjectCollectionTest.java         |  585 ++++++
 .../internal/DefaultDomainObjectContainerTest.java |  370 ----
 .../api/internal/DefaultDomainObjectSetTest.groovy |   31 +
 .../DefaultNamedDomainObjectContainerTest.java     |  664 -------
 .../DefaultNamedDomainObjectListTest.groovy        |  456 +++++
 .../internal/DefaultNamedDomainObjectSetTest.java  |  747 ++++++++
 .../org/gradle/api/internal/DefaultTaskTest.groovy |  156 +-
 .../DependencyClassPathProviderTest.groovy         |   50 +
 .../api/internal/DirectInstantiatorTest.groovy     |  197 ++
 .../api/internal/DocumentationRegistryTest.groovy  |   73 +
 .../api/internal/DynamicObjectHelperTest.java      |  837 ---------
 .../internal/DynamicObjectHelperTestHelper.groovy  |   93 -
 .../api/internal/ExtensibleDynamicObjectTest.java  |  865 +++++++++
 .../ExtensibleDynamicObjectTestHelper.groovy       |  126 ++
 .../FactoryNamedDomainObjectContainerSpec.groovy   |  144 ++
 .../gradle/api/internal/FilteredActionSpec.groovy  |   69 +
 ...vySourceGenerationBackedClassGeneratorTest.java |   23 -
 .../api/internal/LocationAwareExceptionTest.groovy |  155 ++
 .../api/internal/MapBackedDynamicObjectTest.java   |   51 -
 ...AutoCreateNamedDomainObjectContainerSpec.groovy |  110 ++
 .../api/internal/PropertiesTransformerTest.groovy  |  101 +
 .../org/gradle/api/internal/TestContainer.java     |   57 +-
 .../gradle/api/internal/XmlTransformerTest.groovy  |   75 +-
 .../CachingDependencyResolveContextTest.groovy     |   12 +-
 .../DefaultArtifactRepositoryContainerTest.groovy  |  246 +++
 .../DefaultConfigurationContainerFactoryTest.java  |   83 -
 .../artifacts/DefaultDependencySetTest.groovy      |   53 +
 .../artifacts/DefaultExcludeRuleContainerTest.java |   83 +
 .../DefaultModuleVersionIdentifierTest.groovy      |   42 +
 .../artifacts/DefaultPublishArtifactSetTest.groovy |   83 +
 .../artifacts/DefaultResolvedArtifactTest.java     |  102 -
 .../artifacts/DefaultResolvedDependencyTest.java   |  203 --
 .../artifacts/DefaultResolverContainerTest.groovy  |  217 ---
 .../artifacts/ExcludeRuleNotationParserTest.groovy |   82 +
 .../DefaultConfigurationContainerSpec.groovy       |  101 +
 .../DefaultConfigurationContainerTest.groovy       |  115 ++
 .../DefaultConfigurationContainerTest.java         |  165 --
 .../configurations/DefaultConfigurationSpec.groovy |  289 +++
 .../configurations/DefaultConfigurationTest.java   |  356 ++--
 .../DefaultResolutionStrategyTest.groovy           |   62 +
 .../ForcedModuleNotationParserTest.groovy          |  126 ++
 .../dynamicversion/DefaultCachePolicySpec.groovy   |  248 +++
 .../dependencies/AbstractModuleDependencyTest.java |    8 +-
 .../DefaultExcludeRuleContainerTest.java           |   72 -
 .../DefaultExternalModuleDependencyTest.java       |    9 +-
 .../dependencies/DefaultProjectDependencyTest.java |   41 +-
 .../dsl/DefaultArtifactHandlerTest.groovy          |   97 -
 .../dsl/DefaultConfigurationHandlerTest.groovy     |  103 --
 .../dsl/DefaultPublishArtifactFactoryTest.groovy   |   59 -
 .../dsl/DefaultRepositoryHandlerFactoryTest.java   |   57 -
 .../dsl/DefaultRepositoryHandlerTest.groovy        |  422 +++--
 .../dependencies/AbstractModuleFactoryTest.java    |  170 --
 .../ClassPathDependencyFactoryTest.groovy          |   63 -
 .../DefaultClientModuleFactoryTest.java            |   30 -
 .../dependencies/DefaultDependencyFactoryTest.java |  168 --
 .../DefaultDependencyHandlerTest.groovy            |  383 ++--
 .../DefaultProjectDependencyFactoryTest.java       |   75 -
 .../dependencies/ModuleDependencyFactoryTest.java  |   90 -
 .../SelfResolvingDependencyFactoryTest.java        |   49 -
 .../ivyservice/ClientModuleResolverTest.groovy     |   37 -
 .../DefaultIvyDependencyResolverTest.java          |  299 ---
 .../ivyservice/DefaultIvyFactoryTest.java          |   36 -
 .../ivyservice/DefaultIvyServicePublishTest.java   |  175 --
 .../ivyservice/DefaultIvyServiceResolveTest.java   |  129 --
 .../ivyservice/DefaultIvyServiceTest.java          |   68 -
 .../ivyservice/DefaultSettingsConverterTest.groovy |  123 --
 .../ivyservice/ErrorHandlingIvyServiceTest.groovy  |  156 --
 .../ivyservice/GradleIBiblioResolverTest.groovy    |   88 -
 .../ivyservice/Report2ClasspathTest.groovy         |   28 -
 .../SelfResolvingDependencyResolverTest.java       |  169 --
 .../ShortcircuitEmptyConfigsIvyServiceTest.java    |   92 -
 ...ltArtifactsToModuleDescriptorConverterTest.java |  124 --
 .../DefaultExcludeRuleConverterTest.java           |   54 -
 .../DefaultModuleDescriptorFactoryTest.java        |   39 -
 .../PublishModuleDescriptorConverterTest.java      |   72 -
 .../ResolveModuleDescriptorConverterTest.java      |   74 -
 ...actDependencyDescriptorFactoryInternalTest.java |  137 --
 ...lientModuleDependencyDescriptorFactoryTest.java |   98 -
 ...ependenciesToModuleDescriptorConverterTest.java |  134 --
 ...ModuleDescriptorFactoryForClientModuleTest.java |   92 -
 ...ernalModuleDependencyDescriptorFactoryTest.java |  127 --
 .../ProjectDependencyDescriptorFactoryTest.java    |  127 --
 .../publish/DefaultArtifactContainerTest.java      |   70 -
 .../publish/DefaultPublishArtifactTest.java        |   30 +-
 .../api/internal/cache/MapBackedCacheTest.groovy   |   47 +
 .../CacheBackedFileSnapshotRepositoryTest.groovy   |   31 +-
 .../changedetection/CachingHasherTest.java         |   28 +-
 .../DefaultFileSnapshotterTest.groovy              |    0
 .../DefaultTaskArtifactStateCacheAccessTest.groovy |   53 +
 .../DefaultTaskArtifactStateRepositoryTest.java    |  107 +-
 ...hortCircuitTaskArtifactStateRepositoryTest.java |   77 +-
 .../classpath/DefaultModuleRegistryTest.groovy     |  237 +++
 .../api/internal/classpath/ManifestUtilTest.groovy |  144 ++
 .../collections/CollectionEventRegisterSpec.groovy |  114 ++
 .../SynchronizedServiceRegistryTest.groovy         |   45 +
 .../internal/file/AbstractFileTreeElementTest.java |   58 +-
 .../api/internal/file/AbstractFileTreeTest.groovy  |    0
 .../api/internal/file/BaseDirConverterTest.groovy  |  335 ----
 .../internal/file/BaseDirFileResolverSpec.groovy   |  194 ++
 .../internal/file/BaseDirFileResolverTest.groovy   |  333 ++++
 .../internal/file/CompositeFileCollectionTest.java |    3 +-
 .../internal/file/DefaultFileOperationsTest.groovy |   25 +-
 .../file/DefaultFileTreeElementTest.groovy         |   41 +
 .../file/DefaultTemporaryFileProviderTest.groovy   |  104 +-
 .../file/FileOrUriNotationParserTest.groovy        |  108 ++
 .../file/MaybeCompressedFileResourceTest.groovy    |   38 +
 .../file/archive/TarCopySpecVisitorTest.java       |  184 +-
 .../api/internal/file/archive/TarFileTreeTest.java |   76 +-
 .../file/archive/ZipCopySpecVisitorTest.java       |   91 +-
 .../api/internal/file/archive/ZipFileTreeTest.java |   41 +-
 .../file/archive/compression/ArchiversTest.groovy  |   41 +
 .../DefaultFileCollectionResolveContextTest.groovy |  834 +++++----
 .../api/internal/file/copy/CopySpecImplTest.groovy |   29 +-
 .../internal/file/copy/DeleteActionImplTest.groovy |    5 +-
 .../api/internal/file/copy/LineFilterTest.groovy   |    2 +-
 .../file/copy/MappingCopySpecVisitorTest.java      |   67 +-
 .../file/copy/PathNotationParserTest.groovy        |   90 +
 .../internal/file/copy/RenamingCopyActionTest.java |    3 +-
 .../file/copy/SyncCopySpecVisitorTest.java         |    2 +-
 .../DefaultScriptHandlerFactoryTest.groovy         |   28 +-
 .../initialization/DefaultScriptHandlerTest.groovy |   17 +-
 .../parsers/ErrorHandlingNotationParserTest.groovy |   62 +
 .../notations/parsers/MapNotationParserTest.groovy |  110 ++
 .../parsers/TypedNotationParserTest.groovy         |   53 +
 .../internal/plugins/DefaultConventionTest.groovy  |  106 +-
 .../DefaultExtraPropertiesExtensionTest.groovy     |   27 +
 .../DefaultObjectConfigurationActionTest.groovy    |    0
 .../plugins/DefaultPluginRegistryTest.java         |   45 +-
 .../DefaultProjectsPluginContainerTest.java        |   62 +-
 .../api/internal/plugins/DslObjectTest.groovy      |   47 +
 .../internal/plugins/ExtensionContainerTest.groovy |  195 ++
 .../ExtraPropertiesDynamicObjectAdapterTest.groovy |   98 +
 .../project/DefaultIsolatedAntBuilderTest.groovy   |   77 +-
 .../api/internal/project/DefaultProjectTest.groovy |  226 +--
 .../project/DefaultServiceRegistryTest.java        |  297 ---
 .../project/GlobalServicesRegistryTest.java        |  224 ++-
 .../project/GradleInternalServiceRegistryTest.java |   29 +-
 .../api/internal/project/ProjectFactoryTest.java   |   35 +-
 .../ProjectInternalServiceRegistryTest.java        |  125 +-
 .../project/ProjectStateInternalSpec.groovy        |   38 +
 .../project/TaskExecutionServicesTest.groovy       |   54 +
 .../project/TaskInternalServiceRegistryTest.java   |    3 +-
 .../TopLevelBuildServiceRegistryTest.groovy        |  264 +++
 .../project/TopLevelBuildServiceRegistryTest.java  |  257 ---
 .../AnnotationProcessingTaskFactoryTest.java       |  234 ++-
 .../project/taskfactory/TaskFactoryTest.java       |   19 +-
 .../api/internal/resource/UriResourceTest.groovy   |    6 +-
 .../api/internal/resources/URIBuilderTest.groovy   |   31 +
 .../internal/tasks/DefaultTaskContainerTest.java   |   41 +-
 .../execution/ExecuteActionsTaskExecuterTest.java  |   11 +-
 .../tasks/util/DefaultJavaForkOptionsTest.groovy   |  563 +++---
 .../util/DefaultProcessForkOptionsTest.groovy      |    0
 .../plugins/ExtraPropertiesExtensionTest.groovy    |  218 +++
 .../groovy/org/gradle/api/specs/SpecsTest.groovy   |   35 -
 .../api/tasks/AbstractConventionTaskTest.java      |   45 -
 .../org/gradle/api/tasks/AbstractCopyTaskTest.java |    6 +-
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |  375 ----
 .../org/gradle/api/tasks/AbstractTaskTest.java     |  341 ----
 .../org/gradle/api/tasks/GradleBuildTest.groovy    |    0
 .../gradle/api/tasks/GroovyTaskTestHelper.groovy   |   45 -
 .../groovy/org/gradle/api/tasks/UploadTest.java    |   46 +-
 .../diagnostics/DependencyReportTaskTest.java      |   13 +-
 .../api/tasks/diagnostics/TaskReportTaskTest.java  |   14 +-
 .../internal/AsciiReportRendererTest.groovy        |   42 +-
 .../internal/PropertyReportRendererTest.java       |    3 +-
 .../internal/TaskDetailsFactoryTest.groovy         |   24 +-
 .../internal/TextReportRendererTest.groovy         |  115 ++
 .../internal/TextReportRendererTest.java           |  118 --
 .../gradle/cache/AutoCloseCacheFactoryTest.groovy  |  102 -
 .../gradle/cache/DefaultCacheFactoryTest.groovy    |   41 -
 .../gradle/cache/DefaultCacheRepositoryTest.java   |  133 --
 .../cache/DefaultPersistentDirectoryCacheTest.java |  171 --
 .../org/gradle/cache/SimpleStateCacheTest.groovy   |   61 -
 .../btree/BTreePersistentIndexedCacheTest.java     |  321 ----
 .../cache/internal/DefaultCacheAccessTest.groovy   |  365 ++++
 .../cache/internal/DefaultCacheFactoryTest.groovy  |  399 ++++
 .../internal/DefaultCacheRepositoryTest.groovy     |  176 ++
 .../internal/DefaultFileLockManagerTest.groovy     |  346 ++++
 .../DefaultPersistentDirectoryCacheTest.java       |  194 ++
 .../DefaultPersistentDirectoryStoreTest.groovy     |   93 +
 .../DefaultProcessMetaDataProviderTest.groovy      |   40 +
 ...ltiProcessSafePersistentIndexedCacheTest.groovy |  145 ++
 .../cache/internal/OnDemandFileAccessTest.groovy   |   53 +
 .../cache/internal/SimpleStateCacheTest.groovy     |   97 +
 .../btree/BTreePersistentIndexedCacheTest.java     |  316 ++++
 .../configuration/DefaultProjectEvaluatorTest.java |  127 --
 .../DefaultScriptPluginFactoryTest.java            |    4 +-
 .../configuration/ImportsScriptSourceTest.java     |   22 +-
 .../LifecycleProjectEvaluatorTest.java             |  127 ++
 .../execution/DefaultBuildExecuterTest.groovy      |  138 ++
 .../gradle/execution/DefaultBuildExecuterTest.java |  109 --
 .../execution/DefaultTaskGraphExecuterTest.java    |  335 +++-
 .../DefaultTasksBuildExecutionActionTest.groovy    |   72 +
 .../gradle/execution/DryRunBuildExecuterTest.java  |   61 -
 .../execution/DryRunBuildExecutionActionTest.java  |   84 +
 ...askFilteringBuildConfigurationActionTest.groovy |   60 +
 .../ProjectDefaultsBuildExecuterTest.java          |  105 --
 .../SelectedTaskExecutionActionTest.groovy         |  107 ++
 ...kNameResolvingBuildConfigurationActionTest.java |  409 ++++
 .../TaskNameResolvingBuildExecuterTest.java        |  392 ----
 .../AsmBackedEmptyScriptGeneratorTest.groovy       |   37 -
 .../CachingScriptCompilationHandlerTest.groovy     |   91 -
 .../groovy/scripts/CachingScriptSourceTest.java    |    0
 .../DefaultScriptCompilationHandlerTest.java       |  289 ---
 .../DefaultScriptCompilerFactoryTest.groovy        |   60 +
 .../scripts/DefaultScriptCompilerFactoryTest.java  |  272 ---
 .../scripts/DefaultScriptRunnerFactoryTest.java    |  146 --
 .../gradle/groovy/scripts/DefaultScriptTest.groovy |    3 +-
 .../gradle/groovy/scripts/UriScriptSourceTest.java |   18 +-
 .../AsmBackedEmptyScriptGeneratorTest.groovy       |   37 +
 .../internal/CachingScriptClassCompilerTest.groovy |  117 ++
 .../DefaultScriptCompilationHandlerTest.java       |  292 +++
 .../internal/DefaultScriptRunnerFactoryTest.java   |  150 ++
 .../FileCacheBackedScriptClassCompilerTest.groovy  |  107 ++
 .../ShortCircuitEmptyScriptCompilerTest.groovy     |   65 +
 .../AbstractSettingsFinderStrategyTest.java        |   70 -
 .../gradle/initialization/BuildLoaderTest.groovy   |  206 ---
 .../initialization/BuildSourceBuilderTest.groovy   |   51 +-
 .../initialization/CommandLineParserTest.groovy    |  549 ------
 .../CompositeInitScriptFinderTest.groovy           |   37 +
 .../DefaultCommandLineConverterTest.java           |  166 +-
 .../DefaultExceptionAnalyserTest.java              |  574 +++---
 .../DefaultGradleLauncherFactoryTest.groovy        |    1 +
 .../initialization/DefaultGradleLauncherTest.java  |   80 +-
 .../DefaultGradlePropertiesLoaderTest.java         |  181 +-
 .../DefaultInitScriptFinderTest.java               |   67 -
 .../initialization/DefaultSettingsFinderTest.java  |   90 -
 .../DependencyResolutionLoggerTest.groovy          |   47 +
 .../DistributionInitScriptFinderTest.groovy        |   85 +
 .../EmbeddedScriptSettingsFinderTest.java          |   76 -
 .../initialization/InitScriptHandlerTest.groovy    |   45 +
 .../initialization/InitScriptHandlerTest.java      |   53 -
 .../InstantiatingBuildLoaderTest.groovy            |  140 ++
 .../MasterDirSettingsFinderStrategyTest.java       |   55 -
 .../ParentDirSettingsFinderStrategyTest.java       |   55 -
 .../ProjectPropertySettingBuildLoaderTest.groovy   |   95 +
 .../PropertiesLoadingSettingsProcessorTest.java    |   27 +-
 .../ProvidedInitScriptFinderTest.java              |   73 +
 .../SameLevelDirSettingsFinderStrategyTest.java    |   55 -
 .../ScriptEvaluatingSettingsProcessorTest.groovy   |   32 +-
 .../gradle/initialization/SettingsFactoryTest.java |   13 +-
 .../gradle/initialization/SettingsHandlerTest.java |   30 +-
 .../UserHomeInitScriptFinderTest.java              |   44 +-
 .../layout/BuildLayoutConfigurationTest.groovy     |   39 +
 .../layout/BuildLayoutFactoryTest.groovy           |  180 ++
 .../org/gradle/invocation/DefaultGradleTest.java   |  132 +-
 .../listener/AsyncListenerBroadcastTest.groovy     |    0
 .../gradle/listener/LazyCreationProxyTest.groovy   |   90 +
 .../logging/LoggingServiceRegistryTest.groovy      |   19 +-
 ...AbstractLineChoppingStyledTextOutputTest.groovy |  153 ++
 .../internal/AbstractStyledTextOutputTest.groovy   |    2 +-
 .../gradle/logging/internal/AnsiConsoleTest.groovy |   14 +-
 .../internal/DefaultLoggingManagerTest.java        |   97 -
 .../DefaultStandardOutputRedirectorTest.groovy     |    2 +-
 .../LoggingCommandLineConverterTest.groovy         |   24 +
 .../internal/OutputEventRendererTest.groovy        |    4 +-
 .../internal/Slf4jLoggingConfigurerTest.groovy     |  180 --
 .../internal/TerminalDetectorFactoryTest.groovy    |   65 +
 .../slf4j/Slf4jLoggingConfigurerTest.groovy        |  180 ++
 .../actor/internal/DefaultActorFactoryTest.groovy  |  140 +-
 .../concurrent/CompositeStoppableTest.groovy       |   87 -
 .../messaging/dispatch/AsyncDispatchTest.groovy    |  422 +++--
 .../messaging/dispatch/AsyncReceiveTest.groovy     |  264 +--
 .../dispatch/ExceptionTrackingDispatchTest.groovy  |   40 -
 .../ExceptionTrackingFailureHandlerTest.groovy     |   61 +
 .../dispatch/ExceptionTrackingListenerTest.groovy  |   61 -
 .../dispatch/FailureHandlingDispatchTest.groovy    |   57 +
 .../messaging/dispatch/MethodInvocationTest.java   |    0
 .../dispatch/ProxyDispatchAdapterTest.groovy       |   20 +-
 .../messaging/dispatch/QueuingDispatchTest.groovy  |   78 +
 .../internal/AsyncConnectionAdapterTest.groovy     |  103 ++
 .../internal/BroadcastSendProtocolTest.groovy      |  135 ++
 .../remote/internal/BufferingProtocolTest.groovy   |  190 ++
 .../internal/ChannelLookupProtocolTest.groovy      |  140 ++
 .../ChannelMessageMarshallingDispatchTest.java     |   76 -
 .../ChannelMessageUnmarshallingDispatchTest.java   |   76 -
 .../ChannelRegistrationProtocolTest.groovy         |   97 +
 .../remote/internal/CompositeAddressTest.groovy    |   46 +
 .../internal/DefaultMessagingClientTest.groovy     |   42 +
 .../internal/DefaultMessagingClientTest.java       |   51 -
 .../internal/DefaultMessagingServerTest.groovy     |   18 +-
 .../DefaultMultiChannelConnectionTest.groovy       |  276 ---
 .../internal/DefaultObjectConnectionTest.java      |  471 ++---
 .../DisconnectAwareConnectionDecoratorTest.groovy  |  168 ++
 .../remote/internal/EagerReceiveBufferTest.groovy  |  135 ++
 .../remote/internal/EndOfStreamDispatchTest.groovy |   99 -
 .../remote/internal/EndOfStreamFilterTest.groovy   |   68 -
 .../remote/internal/EndOfStreamReceiveTest.groovy  |   50 -
 .../remote/internal/GroupMessageFilterTest.groovy  |   57 +
 .../internal/HandshakeIncomingConnectorTest.groovy |   30 +-
 .../internal/HandshakeOutgoingConnectorTest.groovy |   23 +-
 .../remote/internal/InputForwarderTest.groovy      |  195 ++
 .../messaging/remote/internal/MessageTest.groovy   |  180 +-
 .../remote/internal/MessagingServicesTest.groovy   |   48 +
 .../MethodInvocationMarshallingDispatchTest.groovy |   85 +
 .../MethodInvocationMarshallingDispatchTest.java   |   55 -
 ...ethodInvocationUnmarshallingDispatchTest.groovy |   97 +
 .../MethodInvocationUnmarshallingDispatchTest.java |   76 -
 .../internal/PlaceholderExceptionTest.groovy       |   54 +
 .../remote/internal/ProtocolStackTest.groovy       |  383 ++++
 .../remote/internal/ReceiveProtocolTest.groovy     |  142 ++
 .../internal/RemoteDisconnectProtocolTest.groovy   |  131 ++
 .../internal/RemoteMethodInvocationTest.java       |   37 -
 .../messaging/remote/internal/RouterTest.groovy    |  303 +++
 .../remote/internal/SendProtocolTest.groovy        |  191 ++
 .../remote/internal/TcpConnectorTest.groovy        |   49 -
 .../remote/internal/UnicastSendProtocolTest.groovy |  133 ++
 .../remote/internal/WorkerProtocolTest.groovy      |   96 +
 .../internal/inet/MultiChoiceAddressTest.groovy    |   49 +
 .../internal/inet/SocketInetAddressTest.groovy     |   43 +
 .../inet/TcpConnectorConcurrencyTest.groovy        |   85 +
 .../remote/internal/inet/TcpConnectorTest.groovy   |   86 +
 .../protocol/AbstractPayloadMessageTest.groovy     |   69 +
 .../DiscoveryProcotolSerializerTest.groovy         |   96 +
 .../protocol/RemoteMethodInvocationTest.java       |   37 +
 .../process/internal/DefaultExecHandleTest.java    |   11 +-
 .../internal/DefaultWorkerProcessFactoryTest.java  |   14 +-
 .../internal/JavaExecHandleBuilderTest.groovy      |   27 +-
 .../gradle/process/internal/JvmOptionsTest.groovy  |   90 +
 .../internal/child/ActionExecutionWorkerTest.java  |   34 +-
 .../child/BootstrapSecurityManagerTest.groovy      |  104 ++
 .../child/ImplementationClassLoaderWorkerTest.java |    6 +-
 .../WorkerProcessClassPathProviderTest.groovy      |   27 +-
 .../org/gradle/profile/BuildProfileTest.groovy     |   66 +
 .../gradle/profile/CompositeOperationTest.groovy   |   37 +
 .../gradle/reporting/DurationFormatterTest.groovy  |   54 +
 .../gradle/reporting/HtmlReportRendererTest.groovy |   60 +
 .../org/gradle/reporting/TabsRendererTest.groovy   |   48 +
 .../reporting/TextDomReportRendererTest.groovy     |   47 +
 .../gradle/reporting/TextReportRendererTest.groovy |   41 +
 .../gradle/testfixtures/ProjectBuilderTest.groovy  |   11 +-
 .../org/gradle/util/AvailablePortFinderTest.groovy |   37 +
 .../org/gradle/util/CollectionUtilsTest.groovy     |   66 +
 .../org/gradle/util/ConcurrentSpecification.groovy |  551 ------
 .../gradle/util/ConcurrentSpecificationTest.groovy |  354 +++-
 .../org/gradle/util/ConfigureUtilTest.groovy       |  211 ++-
 .../util/DefaultClassLoaderFactoryTest.groovy      |   91 +
 .../util/DefaultClassLoaderFactoryTestHelper.java  |   35 +
 .../org/gradle/util/DefaultClassPathTest.groovy    |   58 +
 .../gradle/util/FilteringClassLoaderTest.groovy    |   22 +
 .../groovy/org/gradle/util/GFileUtilsTest.groovy   |   55 +
 .../test/groovy/org/gradle/util/GUtilTest.groovy   |  137 ++
 .../src/test/groovy/org/gradle/util/GUtilTest.java |   92 -
 .../org/gradle/util/GradleVersionTest.groovy       |   55 +-
 .../test/groovy/org/gradle/util/HelperUtil.groovy  |  182 --
 .../org/gradle/util/JUnit4GroovyMockery.java       |  118 --
 .../groovy/org/gradle/util/JavaMethodTest.java     |    6 +-
 .../src/test/groovy/org/gradle/util/JvmTest.groovy |  114 --
 .../gradle/util/LineBufferingOutputStreamTest.java |    1 +
 .../src/test/groovy/org/gradle/util/Matchers.java  |  375 ----
 .../gradle/util/MultiParentClassLoaderTest.groovy  |    5 +
 .../org/gradle/util/MultithreadedTestCase.java     |  665 -------
 .../groovy/org/gradle/util/NameMatcherTest.java    |   21 +-
 .../gradle/util/ObservableUrlClassLoaderTest.java  |   44 -
 .../src/test/groovy/org/gradle/util/Resources.java |   68 -
 .../org/gradle/util/ServiceLocatorTest.groovy      |  183 ++
 .../groovy/org/gradle/util/StdinSwapperTest.groovy |   32 +
 .../org/gradle/util/StdoutSwapperTest.groovy       |   44 +
 .../test/groovy/org/gradle/util/SwapperTest.groovy |   62 +
 .../groovy/org/gradle/util/TemporaryFolder.java    |  107 --
 .../src/test/groovy/org/gradle/util/TestFile.java  |  467 -----
 .../groovy/org/gradle/util/TestFileHelper.groovy   |  116 --
 .../groovy/org/gradle/util/TextUtilTest.groovy     |   17 +-
 .../org/gradle/util/hash/HashValueTest.groovy      |   64 +
 .../util/internal/ArgumentsSplitterTest.groovy     |   85 +
 .../util/internal/LimitedDescriptionTest.groovy    |   42 +
 .../gradle-plugins/custom-plugin.properties        |    1 +
 .../resources/org/gradle/api/file/symlinks/file}   |    0
 .../org/gradle/api/file/symlinks/symlink}          |    0
 .../org/gradle/api/file/symlinks/symlinked}        |    0
 .../api/internal/file/archive/permissions.tar      |  Bin 0 -> 10240 bytes
 .../gradle/api/internal/xml-transformer-test.dtd}  |    0
 .../api/tasks/AbstractConventionTaskTest.java      |   43 +
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |  312 ++++
 .../org/gradle/api/tasks/AbstractTaskTest.java     |  302 +++
 .../gradle/api/tasks/AntBuilderAwareUtil.groovy    |    0
 .../tasks/bundling/AbstractArchiveTaskTest.groovy  |    0
 .../gradle/messaging/actor/TestActorFactory.java   |   44 +
 .../tests/fixtures/ConcurrentTestUtil.groovy       |  771 ++++++++
 .../org/gradle/util/ConcurrentSpecification.groovy |   27 +
 .../groovy/org/gradle/util/HelperUtil.groovy       |  172 ++
 .../groovy/org/gradle/util/Matchers.java           |  405 ++++
 .../org/gradle/util/MultithreadedTestCase.java     |  666 +++++++
 .../groovy/org/gradle/util/TestTask.groovy         |    0
 subprojects/cpp/cpp.gradle                         |   25 +
 .../cpp/AbstractBinariesIntegrationSpec.groovy     |   33 +
 .../org/gradle/plugins/cpp/AvailableCompilers.java |  164 ++
 .../cpp/CppExePluginGoodBehaviourTest.groovy       |   25 +
 .../plugins/cpp/CppIntegrationTestRunner.java      |   91 +
 .../cpp/CppLibPluginGoodBehaviourTest.groovy       |   25 +
 .../plugins/cpp/CppPluginIntegrationTest.groovy    |  220 +++
 .../plugins/cpp/CppSamplesIntegrationTest.groovy   |   79 +
 .../gradle/plugins/binaries/BinariesPlugin.java    |   59 +
 .../org/gradle/plugins/binaries/model/Binary.java  |   39 +
 .../gradle/plugins/binaries/model/CompileSpec.java |   51 +
 .../gradle/plugins/binaries/model/Compiler.java    |   24 +
 .../plugins/binaries/model/CompilerRegistry.java   |   31 +
 .../gradle/plugins/binaries/model/Executable.java  |   23 +
 .../binaries/model/HeaderExportingSourceSet.java   |   27 +
 .../org/gradle/plugins/binaries/model/Library.java |   27 +
 .../plugins/binaries/model/LibraryCompileSpec.java |   32 +
 .../model/NativeDependencyCapableSourceSet.java    |   25 +
 .../binaries/model/NativeDependencySet.java        |   28 +
 .../gradle/plugins/binaries/model/SourceSet.java   |   25 +
 .../binaries/model/internal/BinaryCompileSpec.java |   23 +
 .../model/internal/BinaryCompileSpecFactory.java   |   27 +
 .../model/internal/CompileSpecFactory.java         |   29 +
 .../binaries/model/internal/CompileTaskAware.java  |   23 +
 .../binaries/model/internal/CompilerAdapter.java   |   32 +
 .../ConfigurationBasedNativeDependencySet.groovy   |   86 +
 .../binaries/model/internal/DefaultBinary.java     |   66 +
 .../model/internal/DefaultCompilerRegistry.java    |   90 +
 .../binaries/model/internal/DefaultExecutable.java |   31 +
 .../binaries/model/internal/DefaultLibrary.java    |   76 +
 .../binaries/model/internal/package-info.java      |   20 +
 .../plugins/binaries/model/package-info.java       |   20 +
 .../org/gradle/plugins/binaries/package-info.java  |   20 +
 .../gradle/plugins/binaries/tasks/Compile.groovy   |   32 +
 .../plugins/binaries/tasks/package-info.java       |   20 +
 .../plugins/cpp/CppExeConventionPlugin.groovy      |   58 +
 .../org/gradle/plugins/cpp/CppExtension.java       |   50 +
 .../plugins/cpp/CppLibConventionPlugin.groovy      |   68 +
 .../groovy/org/gradle/plugins/cpp/CppPlugin.groovy |  100 +
 .../org/gradle/plugins/cpp/CppSourceSet.java       |   65 +
 .../org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy |   77 +
 .../cpp/cdt/model/CprojectDescriptor.groovy        |  115 ++
 .../plugins/cpp/cdt/model/CprojectSettings.groovy  |  107 ++
 .../plugins/cpp/cdt/model/ProjectDescriptor.groovy |   46 +
 .../plugins/cpp/cdt/model/ProjectSettings.groovy   |   40 +
 .../cpp/cdt/tasks/GenerateMetadataFileTask.groovy  |   50 +
 .../cpp/compiler/capability/AgainstLibrary.java    |   31 +
 .../cpp/compiler/capability/CompilesCpp.java       |   30 +
 .../compiler/capability/StandardCppCompiler.java   |   23 +
 .../cpp/compiler/capability/package-info.java      |   20 +
 .../cpp/compiler/internal/ArgCollector.java        |   23 +
 .../plugins/cpp/compiler/internal/ArgWriter.java   |   82 +
 .../CommandLinCppCompilerArgumentsApplicator.java  |   36 +
 .../compiler/internal/CommandLineCppCompiler.java  |   66 +
 .../internal/CommandLineCppCompilerAdapter.java    |   59 +
 ...ommandLineCppCompilerArgumentsToOptionFile.java |   54 +
 .../compiler/internal/CompileSpecToArguments.java  |   25 +
 .../plugins/cpp/compiler/internal/CppCompiler.java |   24 +
 .../cpp/compiler/internal/ListArgCollector.java    |   52 +
 .../gradle/plugins/cpp/gpp/GppCompileSpec.groovy   |  234 +++
 .../plugins/cpp/gpp/GppCompilerPlugin.groovy       |   44 +
 .../plugins/cpp/gpp/GppLibraryCompileSpec.groovy   |   40 +
 .../cpp/gpp/internal/GppCompileSpecFactory.java    |   42 +
 .../gpp/internal/GppCompileSpecToArguments.java    |   55 +
 .../plugins/cpp/gpp/internal/GppCompiler.java      |   45 +
 .../cpp/gpp/internal/GppCompilerAdapter.java       |  106 ++
 .../gpp/internal/version/GppVersionDeterminer.java |  105 ++
 .../org/gradle/plugins/cpp/gpp/package-info.java   |   20 +
 .../plugins/cpp/internal/CppCompileSpec.java       |   33 +
 .../plugins/cpp/internal/DefaultCppSourceSet.java  |   92 +
 .../cpp/msvcpp/MicrosoftVisualCppPlugin.groovy     |   46 +
 .../internal/VisualCppCompileSpecToArguments.java  |   47 +
 .../cpp/msvcpp/internal/VisualCppCompiler.java     |   36 +
 .../msvcpp/internal/VisualCppCompilerAdapter.java  |   51 +
 .../org/gradle/plugins/cpp/package-info.java       |   20 +
 .../META-INF/gradle-plugins/binaries.properties    |    1 +
 .../META-INF/gradle-plugins/cpp-exe.properties     |    1 +
 .../META-INF/gradle-plugins/cpp-lib.properties     |    1 +
 .../META-INF/gradle-plugins/cpp.properties         |    1 +
 .../META-INF/gradle-plugins/eclipse-cdt.properties |    1 +
 .../gradle-plugins/gpp-compiler.properties         |    1 +
 .../cpp/cdt/model/defaultCproject-linux.xml        |  114 ++
 .../cpp/cdt/model/defaultCproject-macos.xml        |  115 ++
 .../plugins/cpp/cdt/model/defaultProject.xml       |   83 +
 .../internal/DefaultCompilerRegistryTest.groovy    |  130 ++
 .../plugins/cpp/CppExeConventionPluginTest.groovy  |   37 +
 .../plugins/cpp/CppLibConventionPluginTest.groovy  |   39 +
 .../org/gradle/plugins/cpp/CppPluginTest.groovy    |  225 +++
 .../cpp/cdt/model/CprojectSettingsSpec.groovy      |   58 +
 .../cpp/cdt/model/ProjectDescriptorSpec.groovy     |   48 +
 .../cpp/compiler/internal/ArgWriterSpec.groovy     |   76 +
 .../plugins/cpp/gpp/GppCompileSpecTest.groovy      |   40 +
 .../cpp/gpp/GppLibraryCompileSpecTest.groovy       |   37 +
 .../version/GppVersionDeterminerTest.groovy        |  142 ++
 subprojects/docs/docs.gradle                       |  374 ++--
 subprojects/docs/release-notes-transform.gradle    |  157 ++
 subprojects/docs/src/docs/css/base.css             |    7 +
 subprojects/docs/src/docs/css/javadoc.css          |  592 ++++++
 subprojects/docs/src/docs/dsl/dsl.xml              |  165 +-
 .../docs/src/docs/dsl/org.gradle.api.Project.xml   |    6 +
 .../docs/src/docs/dsl/org.gradle.api.Script.xml    |    3 +
 .../dsl/org.gradle.api.artifacts.Configuration.xml |   25 +-
 ...gradle.api.artifacts.ConfigurationContainer.xml |   34 +
 ...org.gradle.api.artifacts.ResolutionStrategy.xml |   37 +
 ...rg.gradle.api.artifacts.dsl.ArtifactHandler.xml |   25 +
 ....gradle.api.artifacts.dsl.DependencyHandler.xml |   40 +
 .../dsl/org.gradle.api.dsl.ConventionProperty.xml  |   22 +
 .../docs/dsl/org.gradle.api.invocation.Gradle.xml  |    9 +
 ...dle.api.plugins.ApplicationPluginConvention.xml |    6 +-
 .../dsl/org.gradle.api.plugins.ExtensionAware.xml  |   25 +
 ...gradle.api.plugins.ExtraPropertiesExtension.xml |   49 +
 ...org.gradle.api.plugins.JavaPluginConvention.xml |    4 +-
 ...rg.gradle.api.plugins.MavenPluginConvention.xml |    8 +-
 ....api.plugins.ProjectReportsPluginConvention.xml |    2 +-
 ...i.plugins.announce.AnnouncePluginConvention.xml |   34 -
 ...pi.plugins.announce.AnnouncePluginExtension.xml |   38 +
 .../org.gradle.api.plugins.quality.Checkstyle.xml  |   43 +-
 ...dle.api.plugins.quality.CheckstyleExtension.xml |   44 +
 .../org.gradle.api.plugins.quality.CodeNarc.xml    |   31 +-
 ...radle.api.plugins.quality.CodeNarcExtension.xml |   44 +
 ...le.api.plugins.quality.CodeQualityExtension.xml |   27 +
 .../org.gradle.api.plugins.quality.FindBugs.xml    |   54 +
 ...radle.api.plugins.quality.FindBugsExtension.xml |   31 +
 ...s.quality.GroovyCodeQualityPluginConvention.xml |    2 +-
 .../dsl/org.gradle.api.plugins.quality.JDepend.xml |   38 +
 ...gradle.api.plugins.quality.JDependExtension.xml |   31 +
 .../dsl/org.gradle.api.plugins.quality.Pmd.xml     |   50 +
 ...org.gradle.api.plugins.quality.PmdExtension.xml |   45 +
 .../dsl/org.gradle.api.plugins.sonar.Sonar.xml     |   88 -
 .../org.gradle.api.plugins.sonar.SonarAnalyze.xml  |   23 +
 .../dsl/org.gradle.api.tasks.AbstractCopyTask.xml  |   10 +-
 .../docs/dsl/org.gradle.api.tasks.SourceSet.xml    |   11 +-
 .../dsl/org.gradle.api.tasks.SourceSetOutput.xml   |   54 +
 ...adle.api.tasks.bundling.AbstractArchiveTask.xml |    3 +
 ....api.tasks.diagnostics.DependencyReportTask.xml |    2 +-
 .../dsl/org.gradle.api.tasks.javadoc.Groovydoc.xml |    4 +
 .../dsl/org.gradle.api.tasks.scala.ScalaDoc.xml    |    2 +-
 .../docs/dsl/org.gradle.api.tasks.testing.Test.xml |   22 +
 .../dsl/org.gradle.api.tasks.wrapper.Wrapper.xml   |    4 +-
 .../src/docs/dsl/org.gradle.plugins.ear.Ear.xml    |   34 +
 .../org.gradle.plugins.ear.EarPluginConvention.xml |   44 +
 ...rg.gradle.plugins.ide.api.FileContentMerger.xml |   44 +
 .../org.gradle.plugins.ide.api.GeneratorTask.xml   |    6 -
 ...plugins.ide.api.PropertiesFileContentMerger.xml |   41 +
 ...dle.plugins.ide.api.PropertiesGeneratorTask.xml |   38 +
 ...gradle.plugins.ide.api.XmlFileContentMerger.xml |   41 +
 ...org.gradle.plugins.ide.api.XmlGeneratorTask.xml |    3 -
 ...adle.plugins.ide.eclipse.GenerateEclipseJdt.xml |    8 -
 ....plugins.ide.eclipse.GenerateEclipseProject.xml |   36 -
 ...ins.ide.eclipse.GenerateEclipseWtpComponent.xml |   14 +-
 ...plugins.ide.eclipse.GenerateEclipseWtpFacet.xml |    7 -
 ....plugins.ide.eclipse.model.EclipseClasspath.xml |   57 +
 ...gradle.plugins.ide.eclipse.model.EclipseJdt.xml |   38 +
 ...adle.plugins.ide.eclipse.model.EclipseModel.xml |   65 +
 ...le.plugins.ide.eclipse.model.EclipseProject.xml |   66 +
 ...gradle.plugins.ide.eclipse.model.EclipseWtp.xml |   42 +
 ...ugins.ide.eclipse.model.EclipseWtpComponent.xml |   88 +
 ...e.plugins.ide.eclipse.model.EclipseWtpFacet.xml |   37 +
 ....gradle.plugins.ide.idea.GenerateIdeaModule.xml |   71 -
 ...gradle.plugins.ide.idea.GenerateIdeaProject.xml |   15 -
 ...org.gradle.plugins.ide.idea.model.IdeaModel.xml |   58 +
 ...rg.gradle.plugins.ide.idea.model.IdeaModule.xml |  104 ++
 ...g.gradle.plugins.ide.idea.model.IdeaProject.xml |   57 +
 ...gradle.plugins.ide.idea.model.IdeaWorkspace.xml |   44 +
 .../docs/dsl/org.gradle.plugins.signing.Sign.xml   |   31 +
 ...org.gradle.plugins.signing.SigningExtension.xml |   37 +
 subprojects/docs/src/docs/dsl/plugins.xml          |   58 +-
 .../docs/src/docs/release/content/Lato-bold.woff   |  Bin 0 -> 37284 bytes
 .../src/docs/release/content/Lato-regular.woff     |  Bin 0 -> 35884 bytes
 .../src/docs/release/content/jquery-1.7.2-min.js   |    4 +
 subprojects/docs/src/docs/release/content/logo.gif |  Bin 0 -> 7488 bytes
 .../docs/src/docs/release/content/script.js        |   44 +
 .../docs/src/docs/release/content/style.css        |  135 ++
 subprojects/docs/src/docs/release/notes.md         |  308 +++
 .../src/docs/stylesheets/userGuideHtmlCommon.xsl   |    4 +
 .../docs/src/docs/stylesheets/userGuidePdf.xsl     |    4 +
 .../docs/src/docs/stylesheets/websiteHtml.xsl      |   44 -
 .../docs/src/docs/userguide/announcePlugin.xml     |   15 -
 subprojects/docs/src/docs/userguide/ant.xml        |    5 +-
 .../docs/src/docs/userguide/applicationPlugin.xml  |   31 +-
 .../userguide/artifactDependenciesTutorial.xml     |  258 ++-
 .../docs/src/docs/userguide/artifactMngmt.xml      |   84 +-
 .../docs/userguide/buildAnnouncementsPlugin.xml    |   24 +
 .../docs/src/docs/userguide/buildEnvironment.xml   |  128 ++
 .../docs/src/docs/userguide/buildLifecycle.xml     |   25 +-
 .../src/docs/userguide/buildScriptsTutorial.xml    |   26 +-
 .../docs/src/docs/userguide/checkstylePlugin.xml   |  113 ++
 .../docs/src/docs/userguide/codeNarcPlugin.xml     |  114 ++
 .../docs/src/docs/userguide/codeQualityPlugin.xml  |  322 ----
 .../docs/src/docs/userguide/commandLine.xml        |  437 +++--
 .../src/docs/userguide/commandLineTutorial.xml     |    2 +-
 subprojects/docs/src/docs/userguide/cpp.xml        |  188 ++
 .../docs/src/docs/userguide/customPlugins.xml      |   88 +-
 .../docs/src/docs/userguide/customTasks.xml        |   12 +-
 subprojects/docs/src/docs/userguide/depMngmt.xml   |  553 +++++-
 subprojects/docs/src/docs/userguide/earPlugin.xml  |  191 ++
 .../docs/src/docs/userguide/eclipsePlugin.xml      |  343 +---
 subprojects/docs/src/docs/userguide/embedding.xml  |   95 +-
 .../docs/src/docs/userguide/findBugsPlugin.xml     |   95 +
 subprojects/docs/src/docs/userguide/glossary.xml   |  139 ++
 .../docs/src/docs/userguide/gradleDaemon.xml       |  138 ++
 .../docs/src/docs/userguide/gradleWrapper.xml      |   43 +-
 .../docs/src/docs/userguide/groovyPlugin.xml       |    4 +-
 .../docs/src/docs/userguide/groovyTutorial.xml     |    2 +-
 .../docs/src/docs/userguide/guiTutorial.xml        |  250 +--
 subprojects/docs/src/docs/userguide/ideaPlugin.xml |  335 +---
 .../userguide/img/javaPluginConfigurations.graphml |  181 +-
 .../userguide/img/javaPluginConfigurations.png     |  Bin 26682 -> 20942 bytes
 .../docs/src/docs/userguide/initscripts.xml        |   70 +-
 .../docs/src/docs/userguide/installation.xml       |   20 +-
 .../docs/src/docs/userguide/introduction.xml       |    9 +-
 subprojects/docs/src/docs/userguide/javaPlugin.xml |  174 +-
 .../docs/src/docs/userguide/javaTutorial.xml       |   20 +-
 .../docs/src/docs/userguide/jdependPlugin.xml      |   95 +
 .../docs/src/docs/userguide/multiproject.xml       |   74 +-
 .../docs/src/docs/userguide/organizeBuildLogic.xml |   39 +-
 subprojects/docs/src/docs/userguide/overview.xml   |    2 +-
 subprojects/docs/src/docs/userguide/plugins.xml    |  210 ++-
 subprojects/docs/src/docs/userguide/pmdPlugin.xml  |   94 +
 .../docs/src/docs/userguide/potentialTraps.xml     |    2 +-
 .../docs/src/docs/userguide/signingPlugin.xml      |  181 ++
 .../docs/src/docs/userguide/sonarPlugin.xml        |  134 +-
 .../docs/src/docs/userguide/standardPlugins.xml    |  175 +-
 subprojects/docs/src/docs/userguide/tasks.xml      |    4 +-
 .../docs/src/docs/userguide/thisAndThat.xml        |   51 +-
 .../docs/src/docs/userguide/troubleshooting.xml    |   58 +
 subprojects/docs/src/docs/userguide/tutorials.xml  |   10 +-
 subprojects/docs/src/docs/userguide/userguide.xml  |   17 +-
 subprojects/docs/src/docs/userguide/warPlugin.xml  |    7 +-
 .../docs/src/docs/userguide/webTutorial.xml        |    2 +-
 .../docs/src/docs/userguide/workingWithFiles.xml   |   31 +-
 .../src/docs/userguide/writingBuildScripts.xml     |   38 +
 subprojects/docs/src/samples/announce/build.gradle |    3 +
 subprojects/docs/src/samples/announce/init.gradle  |    3 +
 .../docs/src/samples/application/build.gradle      |   49 +-
 .../docs/src/samples/application/src/dist/LICENSE  |   15 +
 .../docs/src/samples/codeQuality/build.gradle      |   23 +-
 .../codeQuality/config/checkstyle/checkstyle.xml   |    0
 .../codeQuality/config/codenarc/codenarc.xml       |    0
 .../docs/src/samples/codeQuality/readme.xml        |   34 +-
 .../groovy/org/gradle/sample/GroovyPerson.groovy   |    0
 .../src/main/java/org/gradle/sample/Person.java    |    0
 .../groovy/org/gradle/sample/PersonTest.groovy     |    0
 .../docs/src/samples/cpp/dependencies/build.gradle |   63 +
 .../cpp/dependencies/exe/src/main/cpp/main.cpp     |    6 +
 .../cpp/dependencies/lib/src/main/cpp/hello.cpp    |    5 +
 .../cpp/dependencies/lib/src/main/headers/hello.h  |    1 +
 .../src/samples/cpp/dependencies/settings.gradle   |    1 +
 subprojects/docs/src/samples/cpp/exe/build.gradle  |   11 +
 .../src/samples/cpp/exe/src/main/cpp/hello.cpp     |    6 +
 .../docs/src/samples/cpp/exewithlib/build.gradle   |   18 +
 .../cpp/exewithlib/exe/src/main/cpp/main.cpp       |    6 +
 .../cpp/exewithlib/lib/src/main/cpp/hello.cpp      |   10 +
 .../cpp/exewithlib/lib/src/main/headers/hello.h    |    7 +
 .../src/samples/cpp/exewithlib/settings.gradle     |    1 +
 .../customBuildLanguage/billing/build.gradle       |    2 +-
 .../org/gradle/samples/ProductDefinition.groovy    |    1 +
 .../groovy/org/gradle/samples/ProductPlugin.groovy |   13 +-
 .../gradle/samples/ProductPluginConvention.groovy  |   12 -
 .../gradle-plugins/product-module.properties       |    1 +
 .../identityManagement/build.gradle                |    2 +-
 .../customBuildLanguage/reporting/build.gradle     |    2 +-
 .../customDistribution/consumer/build.gradle       |    6 +
 .../samples/customDistribution/plugin/build.gradle |   31 +
 .../customDistribution/plugin/settings.gradle      |    1 +
 .../plugin/src/initScripts/custom-plugins.gradle   |   10 +
 .../main/groovy/org/gradle/GreetingPlugin.groovy   |    0
 .../src/main/groovy/org/gradle/GreetingTask.groovy |    0
 .../META-INF/gradle-plugins/greeting.properties    |    0
 .../docs/src/samples/customDistribution/readme.xml |   13 +
 .../docs/src/samples/customPlugin/build.gradle     |   29 -
 .../src/samples/customPlugin/consumer/build.gradle |   24 +
 .../src/samples/customPlugin/plugin/build.gradle   |   37 +
 .../samples/customPlugin/plugin/settings.gradle    |    1 +
 .../main/groovy/org/gradle/GreetingPlugin.groovy   |    0
 .../src/main/groovy/org/gradle/GreetingTask.groovy |    0
 .../META-INF/gradle-plugins/greeting.properties    |    0
 .../groovy/org/gradle/GreetingPluginTest.groovy    |    0
 .../test/groovy/org/gradle/GreetingTaskTest.groovy |    0
 .../docs/src/samples/customPlugin/readme.xml       |   11 +-
 .../samples/customPlugin/usesCustomPlugin.gradle   |   12 -
 .../src/samples/customPlugin/usesCustomTask.gradle |   14 -
 .../docs/src/samples/dependencies/build.gradle     |   15 +-
 .../src/samples/ear/earCustomized/ear/build.gradle |   34 +
 .../src/samples/ear/earCustomized/ear/readme.xml   |    3 +
 .../src/main/app/META-INF/weblogic-application.xml |    8 +
 .../src/samples/ear/earCustomized/settings.gradle  |   16 +
 .../src/samples/ear/earCustomized/war/build.gradle |   25 +
 .../main/java/org/gradle/sample/SimpleGreeter.java |   26 +
 .../earCustomized/war/src/main/webapp/index.jsp    |    4 +
 .../docs/src/samples/ear/earWithWar/build.gradle   |   13 +
 .../docs/src/samples/ear/earWithWar/readme.xml     |    3 +
 .../src/samples/ear/earWithWar/settings.gradle     |   16 +
 .../src/samples/ear/earWithWar/war/build.gradle    |   25 +
 .../main/java/org/gradle/sample/SimpleGreeter.java |   26 +
 .../ear/earWithWar/war/src/main/webapp/index.jsp   |    4 +
 subprojects/docs/src/samples/eclipse/build.gradle  |   42 +-
 .../src/samples/groovy/groovy-1.5.6/build.gradle   |   10 -
 .../src/samples/groovy/groovy-1.5.6/readme.xml     |   18 -
 .../src/main/groovy/org/gradle/Person.groovy       |    5 -
 .../src/test/groovy/org/gradle/PersonTest.groovy   |   16 -
 .../src/samples/groovy/groovy-1.6.7/build.gradle   |   10 -
 .../src/samples/groovy/groovy-1.6.7/readme.xml     |   18 -
 .../src/main/groovy/org/gradle/Person.groovy       |    5 -
 .../src/test/groovy/org/gradle/PersonTest.groovy   |   31 -
 .../groovy/multiproject/buildSrc/build.gradle      |    7 +
 .../org/gradle/buildsrc/BuildSrcClass.groovy       |    7 +
 .../groovy/org/gradle/buildsrc/BuildSrcClass.java  |    7 -
 .../multiproject/groovycDetector/build.gradle      |    2 +-
 .../groovy/multiproject/testproject/build.gradle   |    2 +-
 .../src/test/groovy/org/gradle/VersionTest.groovy  |    4 +-
 subprojects/docs/src/samples/idea/build.gradle     |   34 +-
 .../docs/src/samples/ivypublish/build.gradle       |   15 +-
 .../docs/src/samples/java/apiAndImpl/build.gradle  |   65 +
 .../apiAndImpl/src/api/java/doubler/Doubler.java   |    5 +
 .../src/impl/java/doubler/impl/DoublerImpl.java    |    9 +
 .../test/java/doubler/impl/DoublerImplTest.java    |   13 +
 .../docs/src/samples/java/base/build.gradle        |    6 -
 .../docs/src/samples/java/base/prod/build.gradle   |   11 +-
 .../docs/src/samples/java/base/test/build.gradle   |    2 +-
 .../src/samples/java/customizedLayout/readme.xml   |    0
 .../src/samples/java/multiproject/api/build.gradle |    6 +-
 .../src/samples/java/multiproject/build.gradle     |    2 +-
 .../java/multiproject/buildSrc/build.gradle        |    7 -
 .../docs/src/samples/java/multiproject/readme.xml  |    0
 .../docs/src/samples/java/quickstart/build.gradle  |    4 +-
 .../docs/src/samples/java/quickstart/readme.xml    |    0
 .../samples/java/withIntegrationTests/build.gradle |   27 +-
 .../samples/java/withIntegrationTests/readme.xml   |    0
 .../src/samples/maven/pomGeneration/build.gradle   |   32 +-
 .../src/samples/maven/pomGeneration/readme.xml     |    0
 .../docs/src/samples/maven/quickstart/build.gradle |   11 -
 .../docs/src/samples/maven/quickstart/readme.xml   |    0
 .../quickstart/src/main/java/org/MyClass.java      |    0
 .../docs/src/samples/mavenRepo/build.gradle        |  103 --
 .../src/samples/multiProjectBuildSrc/build.gradle  |    8 +
 .../multiProjectBuildSrc/buildSrc/build.gradle     |   14 +
 .../plugina/src/main/groovy/plugina/PluginA.groovy |   10 +
 .../META-INF/gradle-plugins/plugina.properties     |    1 +
 .../pluginb/src/main/groovy/pluginb/PluginB.groovy |   10 +
 .../META-INF/gradle-plugins/pluginb.properties     |    1 +
 .../multiProjectBuildSrc/buildSrc/settings.gradle  |    1 +
 subprojects/docs/src/samples/osgi/build.gradle     |    6 +-
 subprojects/docs/src/samples/osgi/readme.xml       |    0
 .../src/samples/scala/customizedLayout/readme.xml  |    0
 subprojects/docs/src/samples/scala/fsc/readme.xml  |    2 +-
 .../src/samples/scala/mixedJavaAndScala/readme.xml |    0
 .../docs/src/samples/scala/quickstart/readme.xml   |    0
 .../src/samples/signing/conditional/build.gradle   |   26 +
 .../signing/conditional/src/main/java/Sample.java  |    2 +
 .../conditional/src/main/resources/sample.txt      |    1 +
 .../docs/src/samples/signing/maven/build.gradle    |   33 +
 .../src/samples/signing/maven/gradle.properties    |    7 +
 .../src/samples/signing/maven/secKeyRingFile.gpg   |  Bin 0 -> 2619 bytes
 .../signing/maven/src/main/java/Sample.java        |    2 +
 .../signing/maven/src/main/resources/sample.txt    |    1 +
 .../docs/src/samples/signing/tasks/build.gradle    |   17 +
 .../src/samples/signing/tasks/secKeyRingFile.gpg   |  Bin 0 -> 2619 bytes
 .../tasks/src/stuff/hello.txt}                     |    0
 .../docs/src/samples/sonar/advanced/build.gradle   |   26 +
 subprojects/docs/src/samples/sonar/build.gradle    |   25 -
 .../src/samples/sonar/multiProject/build.gradle    |   49 +
 .../src/samples/sonar/multiProject/settings.gradle |    1 +
 .../docs/src/samples/sonar/quickstart/build.gradle |   36 +
 .../src/main/java/org/gradle/Person.java           |    0
 .../src/test/java/org/gradle/PersonTest.java       |    0
 .../samples/testng/java-jdk15-passing/build.gradle |    2 +-
 .../samples/testng/suitexmlbuilder/build.gradle    |    2 +-
 .../docs/src/samples/toolingApi/build/build.gradle |   18 +-
 .../src/samples/toolingApi/eclipse/build.gradle    |   38 +
 .../docs/src/samples/toolingApi/eclipse/readme.xml |    3 +
 .../src/main/java/org/gradle/sample/Main.java      |   39 +
 .../docs/src/samples/toolingApi/idea/build.gradle  |   45 +
 .../docs/src/samples/toolingApi/idea/readme.xml    |   19 +
 .../idea/src/main/java/org/gradle/sample/Main.java |   64 +
 .../docs/src/samples/toolingApi/model/build.gradle |   28 -
 .../docs/src/samples/toolingApi/model/readme.xml   |    3 -
 .../src/main/java/org/gradle/sample/Main.java      |   39 -
 .../userguide/ant/useExternalAntTask/build.gradle  |    2 +-
 .../ant/useExternalAntTaskWithConfig/pmd-rules.xml |    0
 .../artifacts/configurationHandling/build.gradle   |   13 +-
 .../artifacts/defineRepository/build.gradle        |   99 +-
 .../artifacts/dependencyBasics/build.gradle        |   10 +
 .../artifacts/excludesAndClassifiers/build.gradle  |    9 +-
 .../artifacts/externalDependencies/build.gradle    |   19 +-
 .../samples/userguide/artifacts/maven/build.gradle |   38 +-
 .../artifacts/resolutionStrategy/build.gradle      |   11 +
 .../userguide/artifacts/uploading/build.gradle     |   73 +-
 .../src/samples/userguide/files/copy/build.gradle  |    6 +-
 .../samples/userguide/files/fileTrees/build.gradle |    8 +-
 .../configurationInjection/build.gradle            |    8 +
 .../initScripts/configurationInjection/init.gradle |    5 +
 .../samples/userguide/java/sourceSets/build.gradle |   37 +-
 .../java/sourceSets/src/intTest/java/SomeTest.java |    7 +
 .../sourceSets/src/intTest/resources/resource.txt  |    1 +
 .../multiproject/dependencies/java/build.gradle    |    3 -
 .../dependencies/javaWithCustomConf/build.gradle   |    2 +-
 .../messages/consumer/build.gradle                 |    6 -
 .../messages/producer/build.gradle                 |    4 -
 .../messages/settings.gradle                       |    1 -
 .../messages/consumer/build.gradle                 |    4 +-
 .../multiproject/dependencies/webDist/build.gradle |    4 +-
 .../partialTasks/water/bluewhale/build.gradle      |    2 +-
 .../partialTasks/water/krill/build.gradle          |    2 +-
 .../partialTasks/water/tropicalFish/build.gradle   |    2 +-
 .../water/bluewhale/build.gradle                   |    2 +-
 .../water/krill/build.gradle                       |    2 +-
 .../water/tropicalFish/build.gradle                |    2 +-
 .../organizeBuildLogic/customPlugin/build.gradle   |    2 +-
 .../build.gradle                                   |   21 +-
 .../customPluginWithConvention/build.gradle        |   14 +-
 .../build.gradle                                   |   20 +-
 .../organizeBuildLogic/inherited/build.gradle      |    0
 .../inherited/child/build.gradle                   |    0
 .../organizeBuildLogic/inherited/settings.gradle   |    0
 .../organizeBuildLogic/injected/build.gradle       |    0
 .../injected/child1/build.gradle                   |    0
 .../injected/child2/build.gradle                   |    0
 .../organizeBuildLogic/injected/settings.gradle    |    0
 .../organizeBuildLogic/nestedBuild/build.gradle    |    0
 .../organizeBuildLogic/nestedBuild/other.gradle    |    0
 .../tasks/customTaskUsingConvention/build.gradle   |   34 +
 .../tasks/customTaskWithFileProperty/build.gradle  |   29 +
 .../customTaskWithFileProperty/build/hello.txt     |    1 +
 .../incrementalBuild/inputsAndOutputs/build.gradle |    4 +-
 .../noInputsAndOutputs/build.gradle                |    4 +-
 .../userguide/tutorial/configByDag/build.gradle    |   17 +-
 .../configureObjectUsingScript/build.gradle        |    0
 .../configureObjectUsingScript/other.gradle        |    0
 .../configureProjectUsingScript/build.gradle       |    0
 .../configureProjectUsingScript/other.gradle       |    0
 .../userguide/tutorial/directoryTask/build.gradle  |   10 -
 .../tutorial/dynamicProperties/build.gradle        |    6 -
 .../tutorial/extraProperties/build.gradle          |   30 +
 .../tutorial/groovyWithFlatDir/build.gradle        |    2 +-
 .../userguide/tutorial/localVariables/build.gradle |    6 +
 .../userguide/tutorial/manifest/build.gradle       |    2 +-
 .../tutorial/pluginAccessConvention/build.gradle   |    7 +-
 .../userguide/tutorial/pluginConfig/build.gradle   |    4 +-
 .../tutorial/pluginConvention/build.gradle         |    4 +-
 .../projectReports/api}/build.gradle               |    0
 .../userguide/tutorial/projectReports/build.gradle |    2 +-
 .../projectReports/webapp}/build.gradle            |    0
 .../samples/userguideOutput/compileSourceSet.out   |    7 +
 .../configurationHandlingAllFiles.out              |    2 +-
 .../userguideOutput/configureObjectUsingScript.out |    0
 .../configureProjectUsingScript.out                |    0
 .../customTaskWithConventionOutput.out             |    2 +
 .../userguideOutput/dependencyListReport.out       |    6 +-
 ...pendentTaskForApplicationDistributionOutput.out |   11 +
 .../userguideOutput/externalDependencies.out       |    9 +-
 ...namicProperties.out => extraTaskProperties.out} |    0
 .../userguideOutput/inheritedBuildLogic.out        |    0
 .../userguideOutput/initScriptConfiguration.out    |    2 +
 .../samples/userguideOutput/injectedBuildLogic.out |    0
 .../samples/userguideOutput/lazyFileProperties.out |    1 +
 .../multitestingBuildDependents.out                |   63 +-
 .../userguideOutput/multitestingBuildNeeded.out    |   55 +-
 .../src/samples/userguideOutput/nestedBuild.out    |    0
 .../userguideOutput/pluginAccessConvention.out     |    1 -
 .../src/samples/userguideOutput/pluginConfig.out   |    3 +-
 .../samples/userguideOutput/pluginConvention.out   |    3 +-
 .../src/samples/userguideOutput/pluginIntro.out    |    2 +-
 .../samples/userguideOutput/projectListReport.out  |    2 +-
 .../samples/userguideOutput/propertyListReport.out |    6 +-
 .../userguideOutput/signingArchivesOutput.out      |    9 +
 .../samples/userguideOutput/signingTaskOutput.out  |    6 +
 .../samples/userguideOutput/taskListAllReport.out  |    4 +-
 .../src/samples/userguideOutput/taskListReport.out |    4 +-
 .../samples/webApplication/customised/build.gradle |   38 +-
 .../samples/webApplication/customised/readme.xml   |    0
 .../samples/webApplication/quickstart/build.gradle |   23 +-
 .../samples/webApplication/quickstart/readme.xml   |    0
 subprojects/ear/ear.gradle                         |   23 +
 .../plugins/ear/EarPluginGoodBehaviourTest.groovy  |   22 +
 .../plugins/ear/EarPluginIntegrationTest.groovy    |  143 ++
 .../main/groovy/org/gradle/plugins/ear/Ear.groovy  |  130 ++
 .../groovy/org/gradle/plugins/ear/EarPlugin.java   |  174 ++
 .../gradle/plugins/ear/EarPluginConvention.groovy  |   87 +
 .../ear/descriptor/DeploymentDescriptor.java       |  219 +++
 .../gradle/plugins/ear/descriptor/EarModule.java   |   54 +
 .../plugins/ear/descriptor/EarSecurityRole.java    |   39 +
 .../plugins/ear/descriptor/EarWebModule.java       |   31 +
 .../internal/DefaultDeploymentDescriptor.groovy    |  284 +++
 .../descriptor/internal/DefaultEarModule.groovy    |   72 +
 .../internal/DefaultEarSecurityRole.groovy         |   60 +
 .../descriptor/internal/DefaultEarWebModule.groovy |   45 +
 .../plugins/ear/descriptor/package-info.java       |   20 +
 .../org/gradle/plugins/ear/package-info.java       |   20 +
 .../META-INF/gradle-plugins/ear.properties         |    1 +
 .../org/gradle/plugins/ear/EarPluginTest.groovy    |  273 +++
 .../groovy/org/gradle/plugins/ear/EarTest.groovy   |  100 +
 .../DefaultDeploymentDescriptorTest.groovy         |  103 ++
 subprojects/ide/ide.gradle                         |   20 +-
 .../plugins/ide/AbstractIdeIntegrationTest.groovy  |   19 +-
 .../ide/AutoTestedSamplesIntegrationTest.groovy    |   31 +
 .../plugins/ide/AutoTestedSamplesTest.groovy       |   31 -
 .../eclipse/AbstractEclipseIntegrationTest.groovy  |  148 +-
 .../ide/eclipse/EclipseClasspathFixture.groovy     |  136 ++
 .../eclipse/EclipseClasspathIntegrationTest.groovy |  580 +++++-
 ...ClasspathRemoteResolutionIntegrationTest.groovy |   76 +
 .../ide/eclipse/EclipseEarIntegrationTest.groovy   |   36 +
 .../ide/eclipse/EclipseIntegrationTest.groovy      |  198 +-
 .../EclipseMultiModuleIntegrationTest.groovy       |   15 +-
 .../eclipse/EclipsePluginGoodBehaviourTest.groovy  |   25 +
 .../eclipse/EclipseProjectIntegrationTest.groovy   |  107 +-
 .../ide/eclipse/EclipseWtpIntegrationTest.groovy   |   44 +-
 .../eclipse/EclipseWtpModelIntegrationTest.groovy  |  470 ++++-
 .../idea/ConfigurationHooksIntegrationTest.groovy  |  101 +
 .../plugins/ide/idea/ConfigurationHooksTest.groovy |   85 -
 .../plugins/ide/idea/IdeaIntegrationTest.groovy    |  155 +-
 .../ide/idea/IdeaModuleIntegrationTest.groovy      |  133 +-
 .../ide/idea/IdeaMultiModuleIntegrationTest.groovy |   25 +-
 .../ide/idea/IdeaPluginGoodBehaviourTest.groovy    |   25 +
 .../ide/idea/IdeaProjectIntegrationTest.groovy     |  100 +-
 .../ide/idea/IdeaWorkspaceIntegrationTest.groovy   |   52 +
 .../expectedFiles/apiClasspath.xml                 |    6 +-
 .../expectedFiles/groovyprojectClasspath.xml       |    6 +-
 .../expectedFiles/webAppJava6Classpath.xml         |    1 +
 .../expectedFiles/webAppWithVarsClasspath.xml      |    9 +-
 .../expectedFiles/webAppWithVarsWtpComponent.xml   |    3 +-
 .../expectedFiles/webserviceClasspath.xml          |   13 +-
 .../expectedFiles/webserviceWtpComponent.xml       |    3 +-
 .../test/java/org/gradle/shared/PersonTest.java    |    3 -
 .../canCreateAndDeleteMetaData/master/build.gradle |   69 +-
 .../webAppWithVars/build.gradle                    |    5 +-
 .../canCreateAndDeleteMetaData/build.gradle        |    8 +-
 .../expectedFiles/api/api.iml.xml                  |    8 +-
 .../expectedFiles/webservice/webservice.iml.xml    |   20 +-
 .../overwritesExistingDependencies/build.gradle    |    6 +-
 .../expectedFiles/root.iml.xml                     |    8 +-
 .../worksWithNonStandardLayout/root/build.gradle   |    2 +
 .../plugins/ide/api/FileContentMerger.groovy       |   62 +
 .../org/gradle/plugins/ide/api/GeneratorTask.java  |   64 +-
 .../ide/api/PropertiesFileContentMerger.groovy     |   43 +
 .../plugins/ide/api/PropertiesGeneratorTask.java   |   49 +
 .../plugins/ide/api/XmlFileContentMerger.groovy    |   48 +
 .../gradle/plugins/ide/api/XmlGeneratorTask.java   |   24 -
 .../plugins/ide/eclipse/EclipsePlugin.groovy       |  233 +--
 .../plugins/ide/eclipse/EclipseWtpPlugin.groovy    |  262 +++
 .../ide/eclipse/GenerateEclipseClasspath.groovy    |   51 +-
 .../plugins/ide/eclipse/GenerateEclipseJdt.groovy  |   55 +-
 .../ide/eclipse/GenerateEclipseProject.groovy      |  145 +-
 .../ide/eclipse/GenerateEclipseWtpComponent.groovy |   50 +-
 .../ide/eclipse/GenerateEclipseWtpFacet.groovy     |   28 +-
 .../ide/eclipse/internal/EclipseNameDeduper.groovy |    4 +-
 .../eclipse/internal/LinkedResourcesCreator.groovy |   34 +
 .../eclipse/model/AbstractClasspathEntry.groovy    |   26 +-
 .../ide/eclipse/model/AbstractLibrary.groovy       |   33 +-
 .../plugins/ide/eclipse/model/Classpath.groovy     |   11 +-
 .../plugins/ide/eclipse/model/Container.groovy     |    8 +-
 .../ide/eclipse/model/EclipseClasspath.groovy      |  129 +-
 .../plugins/ide/eclipse/model/EclipseJdt.groovy    |   66 +-
 .../plugins/ide/eclipse/model/EclipseModel.groovy  |   31 +-
 .../ide/eclipse/model/EclipseProject.groovy        |  101 +-
 .../plugins/ide/eclipse/model/EclipseWtp.groovy    |   68 +-
 .../ide/eclipse/model/EclipseWtpComponent.groovy   |  181 +-
 .../ide/eclipse/model/EclipseWtpFacet.groovy       |   78 +-
 .../gradle/plugins/ide/eclipse/model/Facet.groovy  |   35 +-
 .../plugins/ide/eclipse/model/FileReference.java   |   42 +
 .../org/gradle/plugins/ide/eclipse/model/Jdt.java  |    9 +-
 .../plugins/ide/eclipse/model/Library.groovy       |   11 +-
 .../plugins/ide/eclipse/model/Project.groovy       |   18 -
 .../ide/eclipse/model/ProjectDependency.groovy     |    5 +-
 .../plugins/ide/eclipse/model/SourceFolder.groovy  |   30 +-
 .../plugins/ide/eclipse/model/Variable.groovy      |   11 +-
 .../plugins/ide/eclipse/model/WtpFacet.groovy      |    3 +-
 .../model/internal/ClassFoldersCreator.groovy      |   41 +
 .../eclipse/model/internal/ClasspathFactory.groovy |  234 +--
 .../model/internal/ExportedEntriesUpdater.groovy   |   36 +
 .../model/internal/FileReferenceFactory.groovy     |  139 ++
 .../model/internal/ProjectDependencyBuilder.groovy |   15 +-
 .../model/internal/SourceFoldersCreator.groovy     |  102 +
 .../model/internal/WtpComponentFactory.groovy      |   63 +-
 .../plugins/ide/idea/GenerateIdeaModule.groovy     |  217 +--
 .../plugins/ide/idea/GenerateIdeaProject.groovy    |   38 +-
 .../plugins/ide/idea/GenerateIdeaWorkspace.groovy  |    9 +-
 .../org/gradle/plugins/ide/idea/IdeaPlugin.groovy  |   63 +-
 .../ide/idea/internal/IdeaNameDeduper.groovy       |    4 +-
 .../gradle/plugins/ide/idea/model/FilePath.groovy  |   33 +
 .../ide/idea/model/IdeaLanguageLevel.groovy        |   37 +
 .../gradle/plugins/ide/idea/model/IdeaModel.groovy |   64 +-
 .../plugins/ide/idea/model/IdeaModule.groovy       |  168 +-
 .../plugins/ide/idea/model/IdeaModuleIml.groovy    |   64 +-
 .../plugins/ide/idea/model/IdeaProject.groovy      |  111 +-
 .../plugins/ide/idea/model/IdeaProjectIpr.groovy   |   43 -
 .../plugins/ide/idea/model/IdeaWorkspace.groovy    |   67 +
 .../org/gradle/plugins/ide/idea/model/Jdk.groovy   |   18 +-
 .../gradle/plugins/ide/idea/model/Module.groovy    |   30 +-
 .../plugins/ide/idea/model/ModuleLibrary.groovy    |   29 +-
 .../plugins/ide/idea/model/ModulePath.groovy       |   65 -
 .../org/gradle/plugins/ide/idea/model/Path.groovy  |  102 +-
 .../plugins/ide/idea/model/PathFactory.groovy      |   93 +-
 .../gradle/plugins/ide/idea/model/Project.groovy   |   16 +-
 .../ide/idea/model/SingleEntryModuleLibrary.groovy |   78 +
 .../model/internal/IdeaDependenciesProvider.groovy |  187 +-
 .../model/internal/ModuleDependencyBuilder.groovy  |   10 +-
 .../ide/internal/IdeDependenciesExtractor.groovy   |  206 +++
 .../gradle/plugins/ide/internal/IdePlugin.groovy   |   14 +-
 .../AbstractPersistableConfigurationObject.groovy  |   14 +-
 ...PropertiesPersistableConfigurationObject.groovy |   40 +-
 .../XmlPersistableConfigurationObject.groovy       |   12 +-
 .../PersistableConfigurationObjectGenerator.java   |    2 +-
 .../internal/provider/BasicIdeaModelBuilder.java   |   36 +
 .../internal/provider/BuildModelAction.java        |   29 +-
 .../tooling/internal/provider/BuildsModel.java     |   28 +
 .../internal/provider/EclipseModelBuilder.java     |  160 ++
 .../internal/provider/EclipsePluginApplier.java    |   38 -
 .../internal/provider/GradleProjectBuilder.java    |   80 +
 .../internal/provider/IdeaModelBuilder.java        |  140 ++
 .../tooling/internal/provider/ModelBuilder.java    |  112 --
 .../internal/provider/ModelBuildingAdapter.java    |   25 +-
 .../tooling/internal/provider/TasksFactory.java    |   32 +-
 .../EclipseProjectDependenciesFactory.java         |   46 -
 .../dependencies/ExternalDependenciesFactory.java  |   47 -
 .../dependencies/SourceDirectoriesFactory.java     |   46 -
 .../META-INF/gradle-plugins/eclipse-wtp.properties |    1 +
 .../plugins/ide/eclipse/model/defaultWtpFacet.xml  |    5 +-
 .../plugins/ide/eclipse/EclipsePluginTest.groovy   |   89 +-
 .../ide/eclipse/EclipseWtpPluginTest.groovy        |  163 ++
 .../ide/eclipse/GenerateEclipseProjectTest.groovy  |   55 -
 .../eclipse/GenerateEclipseWtpComponentTest.groovy |    2 +-
 .../ide/eclipse/GenerateEclipseWtpFacetTest.groovy |    9 -
 .../plugins/ide/eclipse/model/ClasspathTest.groovy |   41 +-
 .../plugins/ide/eclipse/model/ContainerTest.groovy |    6 +-
 .../ide/eclipse/model/EclipseModelTest.groovy      |   28 +-
 .../ide/eclipse/model/EclipseProjectTest.groovy    |    2 +-
 .../plugins/ide/eclipse/model/FacetTest.groovy     |   35 +-
 .../plugins/ide/eclipse/model/JdtTest.groovy       |    4 +-
 .../plugins/ide/eclipse/model/LibraryTest.groovy   |   34 +-
 .../ide/eclipse/model/ProjectDependencyTest.groovy |    8 +-
 .../ide/eclipse/model/SourceFolderTest.groovy      |   36 +-
 .../plugins/ide/eclipse/model/VariableTest.groovy  |   36 +-
 .../plugins/ide/eclipse/model/WtpFacetTest.groovy  |    6 +-
 .../model/internal/FileReferenceFactoryTest.groovy |  130 ++
 .../internal/ProjectDependencyBuilderTest.groovy   |   29 +-
 .../ide/idea/ GenerateIdeaModuleTest.groovy        |    6 +-
 .../gradle/plugins/ide/idea/IdeaPluginTest.groovy  |   61 +-
 .../ide/idea/model/IdeaLanguageLevelTest.groovy    |   47 +
 .../plugins/ide/idea/model/ModulePathTest.groovy   |   31 -
 .../plugins/ide/idea/model/ModuleTest.groovy       |   18 +-
 .../plugins/ide/idea/model/PathFactoryTest.groovy  |   69 +-
 .../gradle/plugins/ide/idea/model/PathTest.groovy  |   89 +-
 .../plugins/ide/idea/model/ProjectTest.groovy      |   15 +-
 .../internal/ModuleDependencyBuilderTest.groovy    |   27 +-
 .../plugins/ide/internal/GeneratorTaskTest.groovy  |   28 -
 .../internal/IdeDependenciesExtractorTest.groovy   |   48 +
 ...ertiesPersistableConfigurationObjectTest.groovy |    4 +-
 .../internal/provider/TasksFactoryTest.groovy      |   44 +-
 .../EclipseProjectDependenciesFactoryTest.groovy   |   50 -
 .../ExternalDependenciesFactoryTest.groovy         |   47 -
 .../SourceDirectoriesFactoryTest.groovy            |   48 -
 ...ustomOrgEclipseWstCommonProjectFacetCoreXml.xml |    2 +-
 .../plugins/ide/eclipse/model/customProject.xml    |    2 +-
 subprojects/integ-test/integ-test.gradle           |   24 +-
 .../DependencyReportTaskIntegrationTest.groovy     |   60 +
 .../org/gradle/debug/GradleBuildRunner.groovy      |   34 +
 .../org/gradle/debug/GradleRunConfiguration.groovy |   31 +
 .../integtests/AntProjectIntegrationTest.groovy    |    2 +-
 .../gradle/integtests/AntlrIntegrationTest.java    |   27 -
 .../integtests/AntlrPluginGoodBehaviourTest.groovy |   25 +
 .../integtests/ApplicationIntegrationSpec.groovy   |  104 ++
 .../integtests/ApplicationIntegrationTest.groovy   |   80 +-
 .../integtests/ArchiveIntegrationTest.groovy       |  161 +-
 .../ArtifactDependenciesIntegrationTest.groovy     |  274 ---
 .../BroadcastMessagingIntegrationTest.groovy       |  338 ++++
 .../BuildAggregationIntegrationTest.groovy         |    4 +-
 .../BuildScriptClasspathIntegrationTest.java       |   47 +-
 .../BuildScriptErrorIntegrationTest.java           |    4 +-
 .../BuildScriptExecutionIntegrationTest.groovy     |   28 +-
 .../integtests/CacheProjectIntegrationTest.groovy  |  114 +-
 .../integtests/CharacterEncodingIntegTest.groovy   |  105 ++
 ...ntModuleDependenciesResolveIntegrationTest.java |   41 -
 .../integtests/CodeQualityIntegrationTest.groovy   |  188 --
 .../integtests/CommandLineIntegrationTest.groovy   |  179 +-
 .../integtests/CopyErrorIntegrationTest.groovy     |   76 -
 .../integtests/CopyTaskIntegrationTest.groovy      |  368 ----
 ...CrossVersionCompatibilityIntegrationTest.groovy |   83 -
 .../integtests/CustomPluginIntegrationTest.groovy  |  207 +++
 .../DependenciesResolveIntegrationTest.java        |   42 -
 .../integtests/DistributionIntegrationTest.groovy  |   34 +-
 .../DistributionLocatorIntegrationTest.groovy      |   48 +
 .../integtests/DynamicObjectIntegrationTest.groovy |  324 +++-
 .../gradle/integtests/ExecIntegrationTest.groovy   |    2 +-
 .../ExternalPluginIntegrationTest.groovy           |   69 -
 .../ExternalScriptErrorIntegrationTest.groovy      |    2 +-
 .../ExternalScriptExecutionIntegrationTest.groovy  |   58 +-
 .../integtests/FileTreeCopyIntegrationTest.groovy  |   83 -
 .../GroovyPluginGoodBehaviourTest.groovy           |   25 +
 .../integtests/GroovyProjectIntegrationTest.groovy |   36 +
 .../integtests/GroovyProjectIntegrationTest.java   |   38 -
 .../IncrementalBuildIntegrationTest.groovy         |   10 +-
 .../IncrementalGroovyCompileIntegrationTest.groovy |   53 -
 .../IncrementalJavaCompileIntegrationTest.groovy   |  121 --
 ...crementalJavaProjectBuildIntegrationTest.groovy |    9 +-
 .../IncrementalScalaCompileIntegrationTest.groovy  |   53 -
 .../IncrementalTestIntegrationTest.groovy          |    7 +-
 .../integtests/InitScriptErrorIntegrationTest.java |    4 +-
 .../InitScriptExecutionIntegrationTest.groovy      |  123 +-
 .../integtests/IvyPublishIntegrationTest.groovy    |  123 --
 .../gradle/integtests/JUnitIntegrationTest.groovy  |  404 ----
 .../integtests/JUnitTestExecutionResult.groovy     |  167 --
 .../JavaProjectCrossVersionIntegrationTest.groovy  |   53 +
 .../integtests/JavaProjectIntegrationTest.groovy   |  303 +++
 .../integtests/JavaProjectIntegrationTest.java     |  102 -
 .../integtests/LoggingIntegrationTest.groovy       |  190 +-
 .../integtests/MavenPluginGoodBehaviourTest.groovy |   25 +
 .../integtests/MavenProjectIntegrationTest.groovy  |   37 +
 .../MixedJavaAndWebProjectIntegrationTest.groovy   |   56 +
 .../MixedWarAndEjbProjectIntegrationTest.groovy    |   77 +
 .../integtests/MultiprojectIntegrationTest.groovy  |    4 +-
 .../integtests/OsgiPluginGoodBehaviourTest.groovy  |   25 +
 .../OsgiProjectSampleIntegrationTest.groovy        |    2 +-
 .../PluginCrossVersionIntegrationTest.groovy       |   72 +
 .../integtests/ProfilingIntegrationTest.groovy     |   48 +
 .../integtests/ProjectLayoutIntegrationTest.groovy |   16 +-
 .../integtests/ProjectLoadingIntegrationTest.java  |   13 +-
 .../ProjectReportsPluginIntegrationTest.java       |   33 +
 .../integtests/SamplesAntlrIntegrationTest.groovy  |   41 -
 .../SamplesApplicationIntegrationTest.groovy       |   80 -
 .../SamplesCodeQualityIntegrationTest.groovy       |   44 -
 ...amplesCustomBuildLanguageIntegrationTest.groovy |   59 -
 .../SamplesCustomPluginIntegrationTest.groovy      |   52 -
 ...lesExcludesAndClassifiersIntegrationTest.groovy |   51 -
 ...lesGroovyCustomizedLayoutIntegrationTest.groovy |   48 -
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |   79 -
 .../SamplesGroovyOldVersionsIntegrationTest.groovy |   55 -
 .../SamplesGroovyQuickstartIntegrationTest.groovy  |   52 -
 .../SamplesIvyPublishIntegrationTest.groovy        |   41 -
 .../SamplesJavaBaseIntegrationTest.groovy          |   57 -
 ...mplesJavaCustomizedLayoutIntegrationTest.groovy |   57 -
 .../SamplesJavaMultiProjectIntegrationTest.groovy  |  219 ---
 .../SamplesJavaOnlyIfIntegrationTest.groovy        |   92 -
 ...esJavaProjectWithIntTestsIntegrationTest.groovy |   45 -
 .../SamplesJavaQuickstartIntegrationTest.groovy    |   71 -
 ...SamplesMixedJavaAndGroovyIntegrationTest.groovy |   71 -
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   74 -
 .../SamplesRepositoriesIntegrationTest.groovy      |   43 -
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   51 -
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   66 -
 .../SamplesWebProjectIntegrationTest.groovy        |   71 -
 .../SamplesWebQuickstartIntegrationTest.groovy     |   65 -
 .../integtests/ScalaPluginGoodBehaviourTest.groovy |   25 +
 .../integtests/ScalaProjectIntegrationTest.java    |   10 +-
 .../SettingsScriptErrorIntegrationTest.java        |    6 +-
 .../SettingsScriptExecutionIntegrationTest.groovy  |   18 +-
 .../gradle/integtests/StdioIntegrationTest.groovy  |   76 +
 .../integtests/SyncTaskIntegrationTest.groovy      |    2 +-
 .../TaskAutoDependencyIntegrationTest.groovy       |    2 +-
 .../integtests/TaskDefinitionIntegrationTest.java  |   23 +-
 .../TaskErrorExecutionIntegrationTest.groovy       |    6 +-
 .../integtests/TaskExecutionIntegrationTest.java   |   34 +-
 .../UnicastMessagingIntegrationTest.groovy         |  268 +++
 .../UserGuideSamplesIntegrationTest.groovy         |   32 -
 .../integtests/UserGuideSamplesRunner.groovy       |  274 ---
 .../integtests/WarPluginGoodBehaviourTest.groovy   |   25 +
 .../integtests/WaterProjectIntegrationTest.groovy  |    2 +-
 .../integtests/WebProjectIntegrationTest.java      |   18 +-
 .../integtests/WorkerProcessIntegrationTest.java   |   49 +-
 .../WrapperCrossVersionIntegrationTest.groovy      |   67 +
 .../WrapperProjectIntegrationTest.groovy           |  162 +-
 .../BuildEnvironmentIntegrationTest.groovy         |  188 ++
 .../SingleUseDaemonIntegrationTest.groovy          |  124 ++
 .../DistroTempDirIsUniquePerTestSpec.groovy        |   55 +
 .../fixture/TempDirIsUniquePerTestSpec.groovy      |   53 +
 .../maven/MavenProjectIntegrationTest.groovy       |   64 -
 .../maven/MavenRepoIntegrationTest.groovy          |   43 -
 .../maven/MavenSnapshotIntegrationTest.groovy      |   95 -
 ...SamplesMavenPomGenerationIntegrationTest.groovy |  152 --
 .../SamplesMavenQuickstartIntegrationTest.groovy   |   96 -
 .../ivy/IvyEarProjectPublishIntegrationTest.groovy |   60 +
 .../IvyJavaProjectPublishIntegrationTest.groovy    |   60 +
 .../publish/ivy/IvyPublishIntegrationTest.groovy   |  214 +++
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |   86 +
 .../ivy/IvyWarProjectPublishIntegrationTest.groovy |   59 +
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   41 +
 .../MavenEarProjectPublishIntegrationTest.groovy   |   60 +
 .../MavenJavaProjectPublishIntegrationTest.groovy  |   61 +
 .../MavenNewPublicationIntegrationTest.groovy      |  158 ++
 .../maven/MavenPublicationIntegrationTest.groovy   |   80 +
 ...MavenPublishRespectsPomConfigurationTest.groovy |   84 +
 .../MavenWarProjectPublishIntegrationTest.groovy   |   59 +
 ...SamplesMavenPomGenerationIntegrationTest.groovy |  114 ++
 .../SamplesMavenQuickstartIntegrationTest.groovy   |   86 +
 .../AbstractDependencyResolutionTest.groovy        |   43 +
 .../ArtifactDependenciesIntegrationTest.groovy     |  655 +++++++
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |   99 +
 ...CacheDependencyResolutionIntegrationTest.groovy |  116 ++
 ...ModuleDependenciesResolveIntegrationTest.groovy |   83 +
 .../DependenciesResolveIntegrationTest.java        |   42 +
 .../DependencyNotationIntegrationSpec.groovy       |  153 ++
 .../resolve/FlatDirResolveIntegrationTest.groovy   |   88 +
 ...odingDependencyResolutionIntegrationTest.groovy |   47 +
 ...ProxyDependencyResolutionIntegrationTest.groovy |  146 ++
 ...irectDependencyResolutionIntegrationTest.groovy |   82 +
 .../ProjectDependencyResolveIntegrationTest.groovy |  303 +++
 .../ResolveCrossVersionIntegrationTest.groovy      |   48 +
 .../ResolvedConfigurationIntegrationTest.groovy    |  113 ++
 .../VersionConflictResolutionIntegTest.groovy      |  691 +++++++
 ...AliasedArtifactResolutionIntegrationTest.groovy |  202 ++
 .../CacheReuseCrossVersionIntegrationTest.groovy   |  125 ++
 .../M3CacheReuseCrossVersionIntegrationTest.groovy |   79 +
 .../MavenLocalCacheReuseIntegrationTest.groovy     |   87 +
 .../ResolutionOverrideIntegrationTest.groovy       |  216 +++
 ...achedDependencyResolutionIntegrationTest.groovy |  224 +++
 .../FilerSystemResolverIntegrationTest.groovy      |   76 +
 .../custom/IvySFtpResolverIntegrationTest.groovy   |   78 +
 .../custom/IvyUrlResolverIntegrationTest.groovy    |  118 ++
 ...emoteDependencyResolutionIntegrationTest.groovy |  194 ++
 ...ingModuleRemoteResolutionIntegrationTest.groovy |  425 +++++
 .../ivy/IvyDependencyResolveIntegrationTest.groovy |  144 ++
 ...cRevisionRemoteResolutionIntegrationTest.groovy |  455 +++++
 ...LocalDependencyResolutionIntegrationTest.groovy |  125 ++
 ...emoteDependencyResolutionIntegrationTest.groovy |  256 +++
 .../BadPomFileDependenciesIntegrationTest.groovy   |   46 +
 .../MavenDependencyResolveIntegrationTest.groovy   |  175 ++
 ...LocalDependencyResolutionIntegrationTest.groovy |  137 ++
 ...emoteDependencyResolutionIntegrationTest.groovy |  319 ++++
 .../MavenRemotePomResolutionIntegrationTest.groovy |  179 ++
 ...emoteDependencyResolutionIntegrationTest.groovy |  589 ++++++
 .../AutoTestedSamplesCoreIntegrationTest.groovy    |   33 +
 .../AutoTestedSamplesPluginsIntegrationTest.groovy |   33 +
 .../samples/CoreAutoTestedSamplesTest.groovy       |   31 -
 .../samples/PluginsAutoTestedSamplesTest.groovy    |   31 -
 .../samples/SamplesAntlrIntegrationTest.groovy     |   42 +
 .../SamplesApplicationIntegrationTest.groovy       |   83 +
 .../SamplesCodeQualityIntegrationTest.groovy       |   44 +
 ...amplesCustomBuildLanguageIntegrationTest.groovy |   59 +
 .../SamplesCustomPluginIntegrationTest.groovy      |   59 +
 ...lesExcludesAndClassifiersIntegrationTest.groovy |   52 +
 ...lesGroovyCustomizedLayoutIntegrationTest.groovy |   49 +
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |   79 +
 .../SamplesGroovyQuickstartIntegrationTest.groovy  |   53 +
 .../SamplesJavaApiAndImplIntegrationTest.groovy    |   89 +
 .../samples/SamplesJavaBaseIntegrationTest.groovy  |   58 +
 ...mplesJavaCustomizedLayoutIntegrationTest.groovy |   58 +
 .../SamplesJavaMultiProjectIntegrationTest.groovy  |  224 +++
 .../SamplesJavaOnlyIfIntegrationTest.groovy        |   92 +
 ...esJavaProjectWithIntTestsIntegrationTest.groovy |   46 +
 .../SamplesJavaQuickstartIntegrationTest.groovy    |   72 +
 ...SamplesMixedJavaAndGroovyIntegrationTest.groovy |   72 +
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   75 +
 ...mplesMultiProjectBuildSrcIntegrationTest.groovy |   39 +
 .../SamplesRepositoriesIntegrationTest.groovy      |   43 +
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   52 +
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   67 +
 .../SamplesWebProjectIntegrationTest.groovy        |   98 +
 .../SamplesWebQuickstartIntegrationTest.groovy     |  119 ++
 .../samples/UserGuideSamplesIntegrationTest.groovy |   33 +
 .../testng/SampleTestNGIntegrationTest.groovy      |   71 -
 .../integtests/testng/TestNGExecutionResult.groovy |  161 --
 .../testng/TestNGIntegrationProject.groovy         |   67 -
 .../integtests/testng/TestNGIntegrationTest.groovy |  112 --
 .../SamplesToolingApiIntegrationTest.groovy        |   77 -
 .../gradle/integtests/tooling/ToolingApi.groovy    |  101 -
 .../ToolingApiBuildExecutionIntegrationTest.groovy |  152 --
 .../ToolingApiEclipseModelIntegrationTest.groovy   |  336 ----
 ...norsProjectCustomizationsIntegrationTest.groovy |  127 --
 .../tooling/ToolingApiIntegrationTest.groovy       |   99 -
 .../tooling/ToolingApiModelIntegrationTest.groovy  |   63 -
 .../tooling/ToolingApiSpecification.groovy         |   35 -
 .../compressedTarWithWrongExtension.tar            |  Bin 0 -> 472 bytes
 .../projectWithConfigurationHierarchy.gradle       |   59 -
 .../projectWithCyclesInDependencyGraph.gradle      |   38 -
 .../canHaveCycleInProjectDependencies/build.gradle |   42 -
 .../settings.gradle                                |    1 -
 .../canNestModules/projectWithNestedModules.gradle |   31 -
 .../projectWithFlatDir.gradle                      |   28 -
 .../projectWithDynamicVersions.gradle              |   33 -
 .../projectWithConflicts.gradle                    |   57 -
 .../projectWithUnknownDependency.gradle            |   16 -
 .../CommandLineIntegrationTest/shared/build.gradle |    4 +-
 .../shared/settings.gradle                         |    0
 .../canBuildJavaProject/build.gradle               |    3 -
 .../src/main/groovy/org/gradle/CustomTask.groovy   |   10 -
 .../src/main/java/org/gradle/Person.java           |    5 -
 .../shared/build.gradle                            |   11 -
 .../canExecuteCommands/canExecuteCommands.gradle   |    6 +-
 .../canExecuteJava/canExecuteJava.gradle           |    8 +-
 .../src/main/groovy/Person.java                    |    4 -
 .../src/main/groovy/PersonImpl.Groovy              |    4 -
 .../build.gradle                                   |    4 -
 .../build.gradle                                   |    2 +-
 .../executesTestsInCorrectEnvironment/build.gradle |    8 -
 .../src/test/java/org/gradle/OkTest.java           |   72 -
 .../LoggingIntegrationTest/deprecated/build.gradle |    7 +
 .../logging/buildSrc/build.gradle                  |   20 -
 .../LoggingIntegrationTest/logging/external.gradle |    0
 .../logging/nestedBuild/buildSrc/build.gradle      |   20 -
 .../logging/project2/build.gradle                  |    2 +-
 .../canUseANonStandardBuildDir/build.gradle        |    6 +-
 .../groovy/expectedClasspathFile.txt               |    0
 .../eclipseproject/groovy/expectedProjectFile.txt  |    0
 .../eclipseproject/scala/expectedClasspathFile.txt |    0
 .../eclipseproject/scala/expectedProjectFile.txt   |    4 +-
 .../build.gradle                                   |   38 -
 .../shared/projectWithMavenSnapshots.gradle        |   20 -
 .../build.gradle                                   |    0
 .../settings.gradle                                |    0
 .../build.gradle                                   |   38 +
 .../settings.gradle                                |    0
 .../build.gradle                                   |    0
 .../settings.gradle                                |    0
 .../maven/pomGeneration/expectedNewPom.txt         |    0
 .../maven/pomGeneration/expectedPom.txt            |    0
 .../maven/pomGeneration/expectedQuickstartPom.txt  |    0
 .../projectA-1.2-ivy.xml                           |    0
 .../projectB-1.5-ivy.xml                           |    0
 .../projectWithConfigurationHierarchy.gradle       |   58 +
 .../projectA-1.2-ivy.xml                           |    0
 .../projectB-1.5-ivy.xml                           |    0
 .../projectWithCyclesInDependencyGraph.gradle      |   35 +
 .../canNestModules/projectWithNestedModules.gradle |   26 +
 .../canUseDynamicVersions/projectA-1.2-ivy.xml     |    0
 .../canUseDynamicVersions/projectB-1.5-ivy.xml     |    0
 .../projectWithDynamicVersions.gradle              |   33 +
 .../projectA-1.2-ivy.xml                           |    0
 .../projectA-2.0-ivy.xml                           |    0
 .../projectB-1.5-ivy.xml                           |    0
 .../projectB-2.1.5-ivy.xml                         |    0
 .../projectWithConflicts.gradle                    |   54 +
 .../dependencyReportWithConflicts/settings.gradle  |    0
 .../projectWithUnknownDependency.gradle            |   14 +
 .../build.gradle                                   |   80 +
 .../shared/producer.gradle                         |    0
 .../shared/projectWithMavenSnapshots.gradle        |   20 +
 .../shared/src/main/java/org/gradle/Test.java      |    0
 .../canListenForTestResults/build.gradle           |   20 -
 .../executesTestsInCorrectEnvironment/build.gradle |    9 -
 .../groovyJdk15Failing/build.gradle                |   16 -
 .../groovyJdk15Passing/build.gradle                |   16 -
 .../javaJdk15Failing/build.gradle                  |   15 -
 .../internal-integ-testing.gradle                  |   50 +
 .../fixtures/AbstractAutoTestedSamplesTest.groovy  |   44 +
 .../fixtures/AbstractCompatibilityTestRunner.java  |   99 +
 .../fixtures/AbstractDelegatingGradleExecuter.java |   45 +
 .../fixtures/AbstractGradleExecuter.java           |  340 ++++
 .../fixtures/AbstractIntegrationSpec.groovy        |  137 ++
 .../fixtures/AbstractIntegrationTest.java          |   65 +
 .../fixtures/AbstractMultiTestRunner.java          |  205 ++
 .../integtests/fixtures/ArtifactBuilder.java       |    0
 .../fixtures/AutoTestedSamplesUtil.groovy          |   72 +
 .../integtests/fixtures/AvailableJavaHomes.java    |   97 +
 .../fixtures/BasicGradleDistribution.java          |   78 +
 .../fixtures/CrossVersionIntegrationSpec.groovy    |   52 +
 .../fixtures/CrossVersionTestRunner.groovy         |   76 +
 .../integtests/fixtures/DaemonGradleExecuter.java  |  102 +
 .../fixtures/EmbeddedDaemonGradleExecuter.java     |  105 ++
 .../integtests/fixtures/ExecutionFailure.java      |    0
 .../integtests/fixtures/ExecutionResult.java       |   60 +
 .../integtests/fixtures/ForkingGradleExecuter.java |  218 +++
 .../integtests/fixtures/ForkingGradleHandle.java   |  117 ++
 .../fixtures/GradleBackedArtifactBuilder.java      |    0
 .../integtests/fixtures/GradleDistribution.java    |  244 +++
 .../fixtures/GradleDistributionExecuter.java       |  263 +++
 .../gradle/integtests/fixtures/GradleExecuter.java |  151 ++
 .../gradle/integtests/fixtures/GradleHandle.java   |   32 +
 .../gradle/integtests/fixtures/HttpServer.groovy   |  437 +++++
 .../fixtures/InProcessGradleExecuter.java          |  381 ++++
 .../integtests/fixtures/IntegrationTestHint.java   |   32 +
 .../integtests/fixtures/IvyRepository.groovy       |  256 +++
 .../fixtures/JUnitTestExecutionResult.groovy       |  165 ++
 .../integtests/fixtures/MavenRepository.groovy     |  353 ++++
 .../fixtures/MultiVersionIntegrationSpec.groovy    |   28 +
 .../fixtures/MultiVersionSpecRunner.groovy         |   53 +
 .../fixtures/OutputScrapingExecutionFailure.java   |   91 +
 .../fixtures/OutputScrapingExecutionResult.java    |  132 ++
 .../fixtures/OutputScrapingGradleHandle.java       |   36 +
 .../fixtures/PreviousGradleVersionExecuter.groovy  |  180 ++
 .../integtests/fixtures/ReleasedVersions.java      |   73 +
 .../org/gradle/integtests/fixtures/RuleHelper.java |    0
 .../gradle/integtests/fixtures/SFTPServer.groovy   |  153 ++
 .../org/gradle/integtests/fixtures/Sample.java     |    0
 .../integtests/fixtures/ScriptExecuter.groovy      |   38 +
 .../gradle/integtests/fixtures/TargetVersions.java |   25 +
 .../fixtures/TestClassExecutionResult.java         |    0
 .../integtests/fixtures/TestExecutionResult.java   |    0
 .../fixtures/TestNGExecutionResult.groovy          |  159 ++
 .../fixtures/TestNativeFileSystem.groovy           |  111 ++
 .../integtests/fixtures/TestProxyServer.groovy     |   70 +
 .../gradle/integtests/fixtures/TestResources.java  |    0
 .../fixtures/UserGuideSamplesRunner.groovy         |  289 +++
 .../org/gradle/integtests/fixtures/UsesSample.java |    0
 .../fixtures/WellBehavedPluginTest.groovy          |   53 +
 .../fixtures/versions/VersionsInfo.groovy          |   78 +
 .../src/main/resources/sshd-config/test-dsa.key    |   12 +
 .../fixtures/versions/VersionsInfoTest.groovy      |   99 +
 .../internal-testing/internal-testing.gradle       |   33 +
 .../testing/internal/util/ExceptionAssert.groovy   |   61 +
 .../testing/internal/util/IdeQuickCheckRunner.java |   68 +
 .../gradle/testing/internal/util/Network.groovy    |   29 +
 .../groovy/org/gradle/util/EmptyStatement.groovy   |   25 +
 .../groovy/org/gradle/util/FailsWithMessage.java   |   32 +
 .../org/gradle/util/FailsWithMessageExtension.java |   54 +
 .../org/gradle/util/JUnit4GroovyMockery.java       |  118 ++
 .../org/gradle/util/PreconditionVerifier.groovy    |   27 +
 .../main/groovy/org/gradle/util/RedirectStdIn.java |   45 +
 .../org/gradle/util/RedirectStdOutAndErr.java      |    0
 .../org/gradle/util/ReflectionEqualsMatcher.java   |    0
 .../main/groovy/org/gradle/util/Requires.groovy    |   31 +
 .../src/main/groovy/org/gradle/util/Resources.java |   68 +
 .../org/gradle/util/SetSystemProperties.java       |    0
 .../groovy/org/gradle/util/TemporaryFolder.java    |  114 ++
 .../groovy/org/gradle/util/TestDirHelper.groovy    |    0
 .../src/main/groovy/org/gradle/util/TestFile.java  |  553 ++++++
 .../groovy/org/gradle/util/TestFileContext.java    |    0
 .../groovy/org/gradle/util/TestFileHelper.groovy   |  156 ++
 .../groovy/org/gradle/util/TestPrecondition.groovy |  107 ++
 .../gradle/util/TestPreconditionExtension.groovy   |   32 +
 subprojects/jetty/jetty.gradle                     |   33 +-
 .../api/plugins/jetty/AbstractJettyRunTask.java    |   78 +-
 .../org/gradle/api/plugins/jetty/JettyPlugin.java  |   43 +-
 .../org/gradle/api/plugins/jetty/JettyRun.java     |   15 +-
 .../org/gradle/api/plugins/jetty/JettyRunWar.java  |    2 +-
 .../org/gradle/api/plugins/jetty/JettyStop.java    |    8 +
 .../api/plugins/jetty/ScanTargetPattern.java       |   12 +-
 .../plugins/jetty/internal/JettyConfiguration.java |   11 +-
 .../jetty/internal/JettyPluginWebAppContext.java   |    6 +-
 .../gradle/api/plugins/jetty/internal/Monitor.java |   36 +-
 .../META-INF/gradle-plugins/jetty.properties       |    0
 subprojects/launcher/launcher.gradle               |   79 +-
 .../gradle/launcher/SystemClassLoaderTest.groovy   |   86 +
 .../DaemonConfigurabilityIntegrationSpec.groovy    |   75 +
 .../daemon/DaemonFeedbackIntegrationSpec.groovy    |  257 +++
 .../launcher/daemon/DaemonIntegrationSpec.groovy   |   48 +
 .../launcher/daemon/DaemonLifecycleSpec.groovy     |  502 +++++
 .../daemon/DispachingFailureIntegrationSpec.groovy |   37 +
 .../StoppingDaemonSmokeIntegrationSpec.groovy      |   76 +
 .../daemon/testing/DaemonContextParser.java        |   50 +
 .../testing/DaemonEventSequenceBuilder.groovy      |   90 +
 .../daemon/testing/DaemonsEventSequence.groovy     |  154 ++
 .../launcher/daemon/testing/DaemonsState.groovy    |   50 +
 .../daemon/testing/DaemonsStateCheckpoint.groovy   |   44 +
 .../org/gradle/launcher/BuildActionParameters.java |   30 -
 .../gradle/launcher/CommandLineActionFactory.java  |  222 ---
 .../org/gradle/launcher/DaemonBuildAction.java     |   47 -
 .../java/org/gradle/launcher/DaemonClient.java     |  102 -
 .../java/org/gradle/launcher/DaemonConnector.java  |  283 ---
 .../main/java/org/gradle/launcher/DaemonMain.java  |  149 --
 .../launcher/DefaultBuildActionParameters.java     |   50 -
 .../DefaultGradleLauncherActionExecuter.java       |   65 -
 .../gradle/launcher/ExceptionReportingAction.java  |   40 -
 .../org/gradle/launcher/ExecuteBuildAction.java    |   50 -
 .../org/gradle/launcher/ExecutionListener.java     |   32 -
 .../java/org/gradle/launcher/GradleDaemon.java     |   22 -
 .../launcher/GradleLauncherActionExecuter.java     |   29 -
 .../gradle/launcher/IncomingConnectionHandler.java |   23 -
 .../org/gradle/launcher/InitializationAware.java   |   22 -
 .../src/main/java/org/gradle/launcher/Main.java    |   51 +-
 .../java/org/gradle/launcher/ProcessBootstrap.java |   24 +-
 .../org/gradle/launcher/ReportedException.java     |   25 -
 .../java/org/gradle/launcher/RunBuildAction.java   |   51 -
 .../java/org/gradle/launcher/StopDaemonAction.java |   28 -
 .../org/gradle/launcher/cli/ActionAdapter.java     |   36 +
 .../gradle/launcher/cli/BuildActionsFactory.java   |  160 ++
 .../org/gradle/launcher/cli/CommandLineAction.java |   35 +
 .../launcher/cli/CommandLineActionFactory.java     |  214 +++
 .../org/gradle/launcher/cli/DaemonBuildAction.java |   52 +
 .../gradle/launcher/cli/ExecuteBuildAction.java    |   44 +
 .../org/gradle/launcher/cli/GuiActionsFactory.java |   44 +
 .../org/gradle/launcher/cli/RunBuildAction.java    |   52 +
 .../org/gradle/launcher/cli/StopDaemonAction.java  |   30 +
 .../launcher/daemon/bootstrap/DaemonMain.java      |  174 ++
 .../daemon/bootstrap/ForegroundDaemonMain.java     |   45 +
 .../launcher/daemon/bootstrap/GradleDaemon.java    |   24 +
 .../launcher/daemon/client/DaemonClient.java       |  199 ++
 .../daemon/client/DaemonClientInputForwarder.java  |  117 ++
 .../daemon/client/DaemonClientServices.java        |   56 +
 .../daemon/client/DaemonClientServicesSupport.java |  102 +
 .../launcher/daemon/client/DaemonConnection.java   |   46 +
 .../launcher/daemon/client/DaemonConnector.java    |   42 +
 .../daemon/client/DaemonDisappearedException.java  |   30 +
 .../client/DaemonInitialConnectException.java      |   28 +
 .../launcher/daemon/client/DaemonStarter.java      |   20 +
 .../daemon/client/DefaultDaemonConnector.java      |  139 ++
 .../daemon/client/DefaultDaemonStarter.java        |  105 ++
 .../client/EmbeddedDaemonClientServices.java       |  108 ++
 .../daemon/client/EmbeddedDaemonStarter.java       |   37 +
 .../client/NoUsableDaemonFoundException.java       |   28 +
 .../daemon/client/SingleUseDaemonClient.java       |   51 +
 .../client/SingleUseDaemonClientServices.java      |   39 +
 .../daemon/client/StopDaemonClientServices.java    |   39 +
 .../launcher/daemon/client/StopDispatcher.java     |   53 +
 .../daemon/configuration/CurrentProcess.java       |   76 +
 .../daemon/configuration/DaemonParameters.java     |  209 +++
 .../configuration/DaemonServerConfiguration.java   |   34 +
 .../DefaultDaemonServerConfiguration.java          |   54 +
 .../ForegroundDaemonConfiguration.java             |   30 +
 .../daemon/context/DaemonCompatibilitySpec.java    |   38 +
 .../launcher/daemon/context/DaemonContext.java     |   68 +
 .../daemon/context/DaemonContextBuilder.java       |  111 ++
 .../daemon/context/DefaultDaemonContext.java       |   74 +
 .../daemon/diagnostics/DaemonDiagnostics.java      |   44 +
 .../launcher/daemon/logging/DaemonGreeter.java     |   76 +
 .../launcher/daemon/logging/DaemonMessages.java    |   34 +
 .../org/gradle/launcher/daemon/protocol/Build.java |   80 +
 .../launcher/daemon/protocol/BuildAndStop.java     |   26 +
 .../launcher/daemon/protocol/BuildStarted.java     |   37 +
 .../launcher/daemon/protocol/CloseInput.java       |   26 +
 .../gradle/launcher/daemon/protocol/Command.java   |   51 +
 .../launcher/daemon/protocol/CommandFailure.java   |   29 +
 .../launcher/daemon/protocol/DaemonBusy.java       |   28 +
 .../launcher/daemon/protocol/DaemonFailure.java    |   29 +
 .../gradle/launcher/daemon/protocol/Failure.java   |   36 +
 .../launcher/daemon/protocol/ForwardInput.java     |   33 +
 .../gradle/launcher/daemon/protocol/IoCommand.java |   26 +
 .../gradle/launcher/daemon/protocol/Result.java    |   44 +
 .../org/gradle/launcher/daemon/protocol/Stop.java  |   24 +
 .../gradle/launcher/daemon/protocol/Success.java   |   32 +
 .../gradle/launcher/daemon/registry/DaemonDir.java |   46 +
 .../launcher/daemon/registry/DaemonInfo.java       |   66 +
 .../launcher/daemon/registry/DaemonRegistry.java   |   43 +
 .../daemon/registry/DaemonRegistryContent.java     |   61 +
 .../daemon/registry/DaemonRegistryServices.java    |   76 +
 .../daemon/registry/EmbeddedDaemonRegistry.java    |  150 ++
 .../daemon/registry/PersistentDaemonRegistry.java  |  174 ++
 .../org/gradle/launcher/daemon/server/Daemon.java  |  239 +++
 .../daemon/server/DaemonServerConnector.java       |   44 +
 .../launcher/daemon/server/DaemonServices.java     |   97 +
 .../daemon/server/DaemonStateCoordinator.java      |  346 ++++
 .../daemon/server/DaemonStoppedException.java      |   36 +
 .../daemon/server/DaemonTcpServerConnector.java    |   97 +
 .../daemon/server/DomainRegistryUpdater.java       |   78 +
 .../daemon/server/IncomingConnectionHandler.java   |   22 +
 .../daemon/server/exec/BuildCommandOnly.java       |   41 +
 .../server/exec/CatchAndForwardDaemonFailure.java  |   38 +
 .../daemon/server/exec/DaemonCommandAction.java    |   35 +
 .../daemon/server/exec/DaemonCommandExecuter.java  |   41 +
 .../daemon/server/exec/DaemonCommandExecution.java |  142 ++
 .../server/exec/DefaultDaemonCommandExecuter.java  |   82 +
 .../server/exec/EstablishBuildEnvironment.java     |   69 +
 .../launcher/daemon/server/exec/ExecuteBuild.java  |   62 +
 .../daemon/server/exec/ForwardClientInput.java     |  125 ++
 ...HandleClientDisconnectBeforeSendingCommand.java |   24 +
 .../launcher/daemon/server/exec/HandleStop.java    |   37 +
 .../launcher/daemon/server/exec/LogToClient.java   |   66 +
 .../daemon/server/exec/ResetDeprecationLogger.java |   26 +
 .../launcher/daemon/server/exec/ReturnResult.java  |   48 +
 .../server/exec/StartBuildOrRespondWithBusy.java   |   58 +
 .../server/exec/StartStopIfBuildAndStop.java       |   38 +
 .../server/exec/StopConnectionAfterExecution.java  |   40 +
 .../daemon/server/exec/WatchForDisconnection.java  |   53 +
 .../launcher/exec/BuildActionParameters.java       |   35 +
 .../exec/DefaultBuildActionParameters.java         |   76 +
 .../java/org/gradle/launcher/exec/EntryPoint.java  |   82 +
 .../launcher/exec/ExceptionReportingAction.java    |   39 +
 .../gradle/launcher/exec/ExecutionCompleter.java   |   21 +
 .../gradle/launcher/exec/ExecutionListener.java    |   32 +
 .../exec/GradleLauncherActionExecuter.java         |   29 +
 .../gradle/launcher/exec/InitializationAware.java  |   22 +
 .../org/gradle/launcher/exec/ProcessCompleter.java |   26 +
 .../gradle/launcher/exec/ReportedException.java    |   25 +
 .../java/org/gradle/launcher/protocol/Build.java   |   38 -
 .../java/org/gradle/launcher/protocol/Command.java |   37 -
 .../gradle/launcher/protocol/CommandComplete.java  |   30 -
 .../java/org/gradle/launcher/protocol/Result.java  |   29 -
 .../java/org/gradle/launcher/protocol/Stop.java    |   24 -
 .../internal/provider/ConfiguringBuildAction.java  |   68 +-
 .../DaemonGradleLauncherActionExecuter.java        |   22 +-
 .../internal/provider/DefaultConnection.java       |  128 +-
 .../provider/DelegatingBuildModelAction.java       |   26 +-
 .../internal/provider/EmbeddedExecuterSupport.java |   42 +
 .../EmbeddedGradleLauncherActionExecuter.java      |   16 +-
 .../internal/provider/ExecuteBuildAction.java      |   16 +-
 ...oggingBridgingGradleLauncherActionExecuter.java |   21 +-
 .../provider/input/AdaptedOperationParameters.java |  150 ++
 .../input/ProviderOperationParameters.java         |   66 +
 .../launcher/CommandLineActionFactoryTest.groovy   |  227 ---
 .../gradle/launcher/DaemonBuildActionTest.groovy   |   47 -
 .../org/gradle/launcher/DaemonClientTest.groovy    |  103 --
 .../launcher/ExceptionReportingActionTest.groovy   |   62 -
 .../groovy/org/gradle/launcher/MainTest.groovy     |   76 +-
 .../org/gradle/launcher/RunBuildActionTest.groovy  |   66 -
 .../gradle/launcher/StopDaemonActionTest.groovy    |   32 -
 .../launcher/cli/BuildActionsFactoryTest.groovy    |  199 ++
 .../cli/CommandLineActionFactoryTest.groovy        |  230 +++
 .../launcher/cli/DaemonBuildActionTest.groovy      |   49 +
 .../launcher/cli/GuiActionsFactoryTest.groovy      |   37 +
 .../gradle/launcher/cli/RunBuildActionTest.groovy  |   67 +
 .../launcher/cli/StopDaemonActionTest.groovy       |   33 +
 .../launcher/daemon/EmbeddedDaemonSmokeTest.groovy |   70 +
 .../client/DaemonClientInputForwarderTest.groovy   |  136 ++
 .../daemon/client/DaemonClientServicesTest.groovy  |   45 +
 .../launcher/daemon/client/DaemonClientTest.groovy |  142 ++
 .../client/DefaultDaemonConnectorTest.groovy       |  171 ++
 .../daemon/configuration/CurrentProcessTest.groovy |   81 +
 .../configuration/DaemonParametersTest.groovy      |  249 +++
 .../context/DaemonCompatibilitySpecSpec.groovy     |  101 +
 .../registry/DaemonRegistryServicesTest.groovy     |   35 +
 .../registry/DomainRegistryUpdaterTest.groovy      |   83 +
 .../registry/EmbeddedDaemonRegistrySpec.groovy     |   83 +
 .../DaemonServerExceptionHandlingTest.groovy       |  121 ++
 .../daemon/server/DaemonServicesTest.groovy        |   48 +
 .../server/DaemonStateCoordinatorTest.groovy       |  125 ++
 .../daemon/server/StopDispatcherTest.groovy        |   56 +
 .../exec/DefaultBuildActionParametersTest.groovy   |   39 +
 .../org/gradle/launcher/exec/EntryPointTest.groovy |   70 +
 .../exec/ExceptionReportingActionTest.groovy       |   61 +
 .../provider/ConfiguringBuildActionTest.groovy     |   76 +
 .../DaemonGradleLauncherActionExecuterTest.groovy  |   17 +-
 ...EmbeddedGradleLauncherActionExecuterTest.groovy |   12 +-
 .../provider/ExecuteBuildActionTest.groovy         |   18 +-
 ...BridgingGradleLauncherActionExecuterTest.groovy |   31 +-
 .../input/AdaptedOperationParametersTest.groovy    |   71 +
 subprojects/maven/maven.gradle                     |   12 +-
 .../api/artifacts/maven/Conf2ScopeMapping.java     |    0
 .../maven/Conf2ScopeMappingContainer.java          |    0
 .../api/artifacts/maven/GroovyMavenDeployer.java   |    0
 .../gradle/api/artifacts/maven/MavenDeployer.java  |    0
 .../api/artifacts/maven/MavenDeployment.java       |    0
 .../org/gradle/api/artifacts/maven/MavenPom.java   |  225 +++
 .../gradle/api/artifacts/maven/MavenResolver.java  |   51 +
 .../api/artifacts/maven/PomFilterContainer.java    |  136 ++
 .../gradle/api/artifacts/maven/PublishFilter.java  |    0
 .../gradle/api/artifacts/maven/package-info.java   |    0
 .../groovy/org/gradle/api/plugins/MavenPlugin.java |   94 +-
 .../gradle/api/plugins/MavenPluginConvention.java  |   45 +-
 .../plugins/MavenRepositoryHandlerConvention.java  |  100 +
 .../api/publication/InstallPublications.groovy     |   36 +
 .../api/publication/PublicationPlugin.groovy       |   44 +
 .../org/gradle/api/publication/Publications.groovy |   31 +
 .../api/publication/PublishPublications.groovy     |   36 +
 .../api/publication/maven/MavenArtifact.groovy     |   22 +
 .../api/publication/maven/MavenDependency.groovy   |   25 +
 .../publication/maven/MavenPomCustomizer.groovy    |   22 +
 .../api/publication/maven/MavenPublication.groovy  |   32 +
 .../api/publication/maven/MavenPublisher.groovy    |   23 +
 .../gradle/api/publication/maven/MavenScope.groovy |   24 +
 .../publication/maven/internal/ArtifactPom.java    |   41 +
 .../maven/internal/ArtifactPomContainer.java       |   31 +
 .../maven/internal/ArtifactPomFactory.java         |   25 +
 .../maven/internal/BasePomFilterContainer.java     |  137 ++
 .../maven/internal/CustomModelBuilder.java         |   82 +
 .../maven/internal/DefaultArtifactPom.java         |  212 +++
 .../internal/DefaultArtifactPomContainer.java      |   74 +
 .../maven/internal/DefaultArtifactPomFactory.java  |   27 +
 .../DefaultConf2ScopeMappingContainer.java         |  115 ++
 .../maven/internal/DefaultDeployerFactory.java     |   70 +
 .../maven/internal/DefaultMavenDeployment.java     |   64 +
 .../maven/internal/DefaultMavenFactory.java        |   45 +
 .../maven/internal/DefaultMavenPom.java            |  251 +++
 .../maven/internal/DefaultMavenPomFactory.java     |   46 +
 .../DefaultMavenRepositoryHandlerConvention.java   |   74 +
 .../maven/internal/DefaultPomFilter.java           |   56 +
 .../maven/internal/DeployerFactory.java            |   25 +
 .../maven/internal/ExcludeRuleConverter.java       |   26 +
 .../publication/maven/internal/MavenFactory.java   |   37 +
 .../maven/internal/MavenPomMetaInfoProvider.java   |   22 +
 .../internal/MavenPublicationPomGenerator.groovy   |   58 +
 .../publication/maven/internal/ModelFactory.java   |   43 +
 .../maven/internal/PomDependenciesConverter.java   |   29 +
 .../api/publication/maven/internal/PomFilter.java  |   34 +
 .../maven/internal/ant/AbstractMavenResolver.java  |  299 +++
 .../maven/internal/ant/BaseMavenDeployer.java      |  129 ++
 .../maven/internal/ant/BaseMavenInstaller.java     |   50 +
 .../maven/internal/ant/CustomDeployTask.java       |   44 +
 .../ant/CustomInstallDeployTaskSupport.java        |   29 +
 .../maven/internal/ant/CustomInstallTask.java      |   37 +
 .../internal/ant/DefaultDeployTaskFactory.java     |   27 +
 .../internal/ant/DefaultExcludeRuleConverter.java  |   40 +
 .../internal/ant/DefaultGroovyMavenDeployer.groovy |   51 +
 .../internal/ant/DefaultInstallTaskFactory.java    |   27 +
 .../internal/ant/DefaultMavenPublisher.groovy      |   93 +
 .../ant/DefaultPomDependenciesConverter.java       |  149 ++
 .../internal/ant/EmptyMavenSettingsSupplier.java   |   52 +
 .../maven/internal/ant/LoggingHelper.java          |   46 +
 .../maven/internal/ant/MavenSettingsSupplier.java  |   27 +
 .../ant/MaybeUserMavenSettingsSupplier.java        |   47 +
 .../maven/internal/ant/RepositoryBuilder.java      |   35 +
 .../maven/internal/ant/RepositoryFactory.java      |   54 +
 .../internal/model/DefaultMavenArtifact.groovy     |   24 +
 .../internal/model/DefaultMavenDependency.groovy   |   52 +
 .../internal/model/DefaultMavenPublication.groovy  |   44 +
 .../modelbuilder/DependenciesConverter.groovy      |   59 +
 .../modelbuilder/MavenPublicationBuilder.groovy    |   80 +
 .../internal/pombuilder/CustomModelBuilder.java    |   82 +
 .../maven/internal/pombuilder/ModelFactory.java    |   44 +
 .../internal/pombuilder/PlexusLoggerAdapter.java   |  105 ++
 .../api/artifacts/maven/Conf2ScopeMappingTest.java |   61 +
 .../api/plugins/MavenPluginConventionTest.groovy   |    5 +-
 .../org/gradle/api/plugins/MavenPluginTest.java    |   54 +-
 .../maven/internal/BasePomFilterContainerTest.java |  178 ++
 .../DefaultArtifactPomContainerTest.groovy         |   98 +
 .../maven/internal/DefaultArtifactPomTest.java     |  271 +++
 .../DefaultConf2ScopeMappingContainerTest.java     |  128 ++
 .../internal/DefaultMavenPomFactoryTest.groovy     |   45 +
 .../maven/internal/DefaultMavenPomTest.groovy      |  184 ++
 ...aultMavenRepositoryHandlerConventionTest.groovy |  140 ++
 .../maven/internal/DefaultPomFilterTest.java       |   55 +
 .../internal/ant/AbstractMavenResolverTest.java    |  240 +++
 .../maven/internal/ant/BaseMavenDeployerTest.java  |  108 ++
 .../maven/internal/ant/BaseMavenInstallerTest.java |   72 +
 .../internal/ant/DefaultDeployTaskFactoryTest.java |   30 +
 .../ant/DefaultExcludeRuleConverterTest.java       |   57 +
 .../ant/DefaultGroovyMavenDeployerTest.groovy      |  116 ++
 .../ant/DefaultGroovyPomFilterContainerTest.groovy |  121 ++
 .../internal/ant/DefaultMavenPublisherTest.groovy  |  152 ++
 .../ant/DefaultPomDependenciesConverterTest.java   |  253 +++
 .../ant/EmptyMavenSettingsSupplierTest.groovy      |   55 +
 .../ant/MaybeUserMavenSettingsSupplierTest.groovy  |   65 +
 .../MavenPublicationBuilderTest.groovy             |  209 +++
 subprojects/native/native.gradle                   |   29 +
 .../nativeplatform/NativeIntegrationException.java |   26 +
 .../NativeIntegrationUnavailableException.java     |   25 +
 .../nativeplatform/NoOpTerminalDetector.java       |   25 +
 .../nativeplatform/ProcessEnvironment.java         |  103 ++
 .../nativeplatform/ReflectiveEnvironment.java      |   78 +
 .../internal/nativeplatform/TerminalDetector.java  |   23 +
 .../nativeplatform/WindowsTerminalDetector.java    |   36 +
 .../ComposableFilePermissionHandler.java           |   45 +
 .../filesystem/FallbackFileStat.java               |  190 ++
 .../nativeplatform/filesystem/FallbackPOSIX.java   |  226 +++
 .../filesystem/FilePermissionHandler.java          |   25 +
 .../filesystem/FilePermissionHandlerFactory.java   |   78 +
 .../nativeplatform/filesystem/FileSystem.java      |   94 +
 .../nativeplatform/filesystem/FileSystems.java     |   26 +
 .../filesystem/GenericFileSystem.java              |  150 ++
 .../nativeplatform/filesystem/PosixUtil.java       |   83 +
 .../jdk7/PosixFilePermissionConverter.java         |   96 +
 .../jdk7/PosixJdk7FilePermissionHandler.java       |   47 +
 .../jna/AbstractProcessEnvironment.java            |   90 +
 .../nativeplatform/jna/JnaBootPathConfigurer.java  |   75 +
 .../internal/nativeplatform/jna/Kernel32.java      |   66 +
 .../gradle/internal/nativeplatform/jna/LibC.java   |   31 +
 .../jna/LibCBackedProcessEnvironment.java          |   72 +
 .../jna/LibCBackedTerminalDetector.java            |   56 +
 .../nativeplatform/jna/UnsupportedEnvironment.java |   70 +
 .../jna/WindowsHandlesManipulator.java             |   86 +
 .../jna/WindowsProcessEnvironment.java             |   60 +
 .../nativeplatform/services/NativeServices.java    |   71 +
 .../filesystem/CommonFileSystemTest.groovy         |  112 ++
 .../ComposableFilePermissionHandlerTest.groovy     |   51 +
 .../filesystem/FallbackFileStatTest.groovy         |   42 +
 .../filesystem/FallbackPOSIXTest.groovy            |   52 +
 .../FilePermissionHandlerFactoryOnJdk7Test.groovy  |   72 +
 ...ilePermissionHandlerFactoryOnNonJdk7Test.groovy |   94 +
 .../filesystem/LinuxFileSystemTest.groovy          |   35 +
 .../filesystem/MacOsFileSystemTest.groovy          |   35 +
 .../nativeplatform/filesystem/PosixUtilTest.groovy |   42 +
 .../filesystem/WindowsFileSystemTest.groovy        |   36 +
 .../jdk7/PosixFilePermissionConverterTest.groovy   |   59 +
 .../jdk7/PosixJdk7FilePermissionHandlerTest.groovy |   44 +
 .../jna/LibCBackedProcessEnvironmentTest.groovy    |   39 +
 .../jna/ProcessEnvironmentTest.groovy              |   79 +
 .../services/NativeServicesTest.groovy             |   46 +
 subprojects/open-api/open-api.gradle               |   17 +-
 .../openapi/BlockingRequestObserver.java           |    4 +-
 ...CrossVersionCompatibilityIntegrationTest.groovy |   72 +-
 .../ExtraTestCommandLineOptionsListener.java       |   32 +
 .../integtests/openapi/GradleRunnerTest.groovy     |   39 +-
 .../gradle/integtests/openapi/OpenApiFixture.java  |   21 +-
 .../gradle/integtests/openapi/OpenApiUiTest.groovy | 1234 ++++++-------
 .../integtests/openapi/OutputUILordTest.groovy     |  163 +-
 .../shared/build.gradle                            |    2 +-
 .../external/runner/GradleRunnerFactory.java       |    2 +
 .../org/gradle/openapi/external/ui/UIFactory.java  |    6 +
 subprojects/osgi/osgi.gradle                       |    8 +-
 .../plugins/osgi/DefaultAnalyzerFactory.java       |    2 +-
 .../internal/plugins/osgi/DefaultOsgiManifest.java |    6 +-
 .../org/gradle/api/plugins/osgi/OsgiPlugin.groovy  |    2 +-
 .../api/plugins/osgi/OsgiPluginConvention.java     |    9 +-
 .../plugins/osgi/DefaultOsgiManifestTest.java      |   12 +-
 .../gradle/api/plugins/osgi/OsgiPluginTest.groovy  |    2 +-
 subprojects/performance/performance.gradle         |   62 +
 subprojects/performance/src/generator.groovy       |  167 ++
 .../org/gradle/peformance/PerformanceTest.groovy   |   56 +
 .../peformance/fixture/MeasuredOperation.groovy    |   47 +
 .../peformance/fixture/PerformanceResults.groovy   |   69 +
 .../fixture/PerformanceTestRunner.groovy           |   79 +
 .../peformance/fixture/TestProjectLocator.groovy   |   38 +
 .../performance/src/templates/Production.groovy    |   13 +
 .../performance/src/templates/Production.java      |   24 +
 subprojects/performance/src/templates/Test.groovy  |   13 +
 subprojects/performance/src/templates/Test.java    |   12 +
 subprojects/performance/src/templates/build.gradle |   53 +
 subprojects/performance/src/templates/build.xml    |   70 +
 subprojects/performance/src/templates/pom.xml      |   58 +
 .../performance/src/templates/settings.gradle      |    1 +
 subprojects/plugins/plugins.gradle                 |   64 +-
 .../gradle/api/plugins/BuildSrcPluginTest.groovy   |   96 +
 .../internal/TaskReportContainerIntegTest.groovy   |  118 ++
 .../api/tasks/JavaExecIntegrationTest.groovy       |   99 +
 .../AntForkingGroovyCompilerIntegrationTest.groovy |   45 +
 ...ntInProcessGroovyCompilerIntegrationTest.groovy |   40 +
 .../BasicGroovyCompilerIntegrationSpec.groovy      |   90 +
 .../DaemonGroovyCompilerIntegrationTest.groovy     |   30 +
 .../compile/GroovyCompilerIntegrationSpec.groovy   |   55 +
 .../InProcessGroovyCompilerIntegrationTest.groovy  |   29 +
 .../IncrementalGroovyCompileIntegrationTest.groovy |   53 +
 .../gradle/java/JavaPluginGoodBehaviourTest.groovy |   25 +
 .../AntForkingJavaCompilerIntegrationTest.groovy   |   35 +
 .../AntInProcessJavaCompilerIntegrationTest.groovy |   35 +
 .../BasicJavaCompilerIntegrationSpec.groovy        |  210 +++
 .../org/gradle/java/compile/ClassFile.groovy       |   68 +
 .../CommandLineJavaCompilerIntegrationTest.groovy  |   42 +
 .../InProcessJavaCompilerIntegrationTest.groovy    |   31 +
 .../IncrementalJavaCompileIntegrationTest.groovy   |  121 ++
 .../compile/JavaCompilerIntegrationSpec.groovy     |   87 +
 .../DaemonJavaCompilerIntegrationTest.groovy       |   33 +
 .../gradle/javadoc/JavadocIntegrationTest.groovy   |   37 +
 .../InterruptedTestThreadIntegrationTest.groovy    |   52 +
 .../testing/TestEnvironmentIntegrationTest.groovy  |   66 +
 .../TestOutputListenerIntegrationTest.groovy       |  202 ++
 .../testing/junit/JUnitIntegrationTest.groovy      |  422 +++++
 .../testng/SampleTestNGIntegrationTest.groovy      |   66 +
 .../testing/testng/TestNGIntegrationProject.groovy |   67 +
 .../testing/testng/TestNGIntegrationTest.groovy    |  157 ++
 .../badCodeBreaksBuild/build.gradle                |    9 +
 .../src/main/groovy/BrokenClass.groovy             |    5 +
 .../badJavaCodeBreaksBuild/build.gradle            |    9 +
 .../src/main/groovy/BrokenClass.java               |    2 +
 .../src/main/groovy/OkClass.groovy                 |    4 +
 .../build.gradle                                   |    9 +
 .../src/test/groovy/MyGroovyTestCase.groovy        |    6 +
 .../canUseAstTransformWrittenInGroovy/build.gradle |    9 +
 .../src/main/groovy/GroovyMagicField.groovy        |    4 +
 .../main/groovy/GroovyMagicFieldTransform.groovy   |   17 +
 .../groovy/GroovyMagicFieldTransformTest.groovy    |   10 +
 .../canUseBuiltInAstTransform/build.gradle         |    9 +
 .../src/test/groovy/TestDelegate.groovy            |    6 +
 .../src/test/groovy/UseBuiltInTransformTest.groovy |   11 +
 .../canUseThirdPartyAstTransform/build.gradle      |    9 +
 .../src/main/java/MagicField.java                  |    4 +
 .../src/main/java/MagicFieldTransform.java         |   17 +
 .../src/main/java/MagicInterface.java              |    4 +
 .../src/main/java/MagicInterfaceTransform.java     |   17 +
 .../src/main/java/Marker.java                      |    2 +
 .../src/test/groovy/MagicFieldTransformTest.groovy |   11 +
 .../recompilesDependentClasses/NewIPerson.groovy   |    0
 .../recompilesDependentClasses/build.gradle        |    0
 .../src/main/groovy/IPerson.groovy                 |    0
 .../src/main/groovy/Person.groovy                  |    0
 .../build.gradle                                   |    0
 .../src/main/groovy}/Person.java                   |    0
 .../src/main/groovy/PersonImpl.groovy              |    3 +
 .../recompilesDependentClasses/NewIPerson.java     |    0
 .../recompilesDependentClasses/build.gradle        |    0
 .../src/main/java/IPerson.java                     |    0
 .../src/main/java/Person.java                      |    0
 .../NewIPerson.java                                |    0
 .../app/src/main/java/Person.java                  |    0
 .../build.gradle                                   |    0
 .../lib/src/main/java/IPerson.java                 |    0
 .../settings.gradle                                |    0
 .../build.gradle                                   |    4 +
 .../src/main/java/Test.java                        |    0
 .../handlesTagsAndTaglets/build.gradle             |   23 +
 .../src/main/java/Person.java                      |   11 +
 .../src/taglet/java/CustomTaglet.java              |   51 +
 .../build.gradle                                   |   13 +
 .../src/test/java/org/gradle/JUnitTest.java        |   14 +
 .../test/java/org/gradle/MySecurityManager.java    |   15 +
 .../build.gradle                                   |   13 +
 .../src/test/java/org/gradle/JUnitTest.java        |   13 +
 .../test/java/org/gradle/MySystemClassLoader.java  |   13 +
 .../build.gradle                                   |   21 +
 .../src/main/java/org/gradle/MyAgent.java          |   15 +
 .../main/java/org/gradle/MySystemClassLoader.java  |   38 +
 .../src/test/java/org/gradle/JUnitTest.java        |   17 +
 .../build.gradle                                   |   19 +
 .../src/test/java/org/gradle/JMockitTest.java      |   39 +
 .../canHaveMultipleTestTaskInstances/build.gradle  |    0
 .../src/test/java/org/gradle/Test1.java            |    0
 .../src/test/java/org/gradle/Test2.java            |    0
 .../canRunSingleTests/build.gradle                 |    0
 .../canRunSingleTests/src/test/java/NotATest.java  |    0
 .../canRunSingleTests/src/test/java/Ok.java        |    0
 .../canRunSingleTests/src/test/java/Ok2.java       |    0
 .../detectsTestClasses/build.gradle                |    0
 .../test/java/org/gradle/AbstractHasRunWith.java   |    0
 .../src/test/java/org/gradle/CustomRunner.java     |    0
 .../test/java/org/gradle/EmptyRunWithSubclass.java |    0
 .../src/test/java/org/gradle/TestsOnInner.java     |    0
 .../executesTestsInCorrectEnvironment/build.gradle |    9 +
 .../src/test/java/org/gradle/OkTest.java           |  121 ++
 .../src/test/java/org/gradle/OtherTest.java        |    0
 .../JUnitIntegrationTest/junit3Tests/build.gradle  |    0
 .../src/test/java/org/gradle/Junit3Test.java       |    0
 .../JUnitIntegrationTest/junit4Tests/build.gradle  |    0
 .../src/test/java/org/gradle/IgnoredTest.java      |    0
 .../src/test/java/org/gradle/Junit4Test.java       |    0
 .../junit4_4Tests/build.gradle                     |    0
 .../build.gradle                                   |    0
 .../src/test/java/org/gradle/BrokenAfter.java      |    0
 .../src/test/java/org/gradle/BrokenAfterClass.java |    0
 .../src/test/java/org/gradle/BrokenBefore.java     |    0
 .../test/java/org/gradle/BrokenBeforeAndAfter.java |    0
 .../test/java/org/gradle/BrokenBeforeClass.java    |    0
 .../test/java/org/gradle/BrokenConstructor.java    |    0
 .../src/test/java/org/gradle/BrokenException.java  |    0
 .../src/test/java/org/gradle/BrokenRunner.java     |   23 +
 .../src/test/java/org/gradle/BrokenTest.java       |    0
 .../java/org/gradle/ClassWithBrokenRunner.java     |    7 +
 .../src/test/java/org/gradle/Unloadable.java       |    0
 .../suitesOutputIsVisible/build.gradle             |    7 +
 .../src/test/java/org/gradle/ASuite.java           |   38 +
 .../src/test/java/org/gradle/OkTest.java           |   31 +
 .../src/test/java/org/gradle/OtherTest.java        |   25 +
 .../canListenForTestResults/build.gradle           |   20 +
 .../src/test/java/AppException.java                |    0
 .../src/test/java/SomeTest.java                    |    0
 .../executesTestsInCorrectEnvironment/build.gradle |    9 +
 .../src/test/java/org/gradle/OkTest.java           |    0
 .../groovyJdk15Failing/build.gradle                |   16 +
 .../src/main/groovy/org/gradle/Ok.groovy           |    0
 .../src/test/groovy/org/gradle/BadTest.groovy      |    0
 .../groovyJdk15Passing/build.gradle                |   16 +
 .../src/main/groovy/org/gradle/Ok.groovy           |    0
 .../src/test/groovy/org/gradle/OkTest.groovy       |    0
 .../javaJdk14Failing/build.gradle                  |    0
 .../src/main/java/org/gradle/Ok.java               |    0
 .../src/test/java/org/gradle/BadTest.java          |    0
 .../javaJdk15Failing/build.gradle                  |   15 +
 .../src/main/java/org/gradle/Ok.java               |    0
 .../src/test/java/org/gradle/BadTest.java          |    0
 .../src/test/java/org/gradle/BrokenAfterSuite.java |    0
 .../org/gradle/TestWithBrokenMethodDependency.java |    0
 .../test/java/org/gradle/TestWithBrokenSetup.java  |    0
 .../supportsTestGroups/build.gradle                |   16 +
 .../src/test/java/org/gradle/groups/SomeTest.java  |   30 +
 .../plugins/DefaultArtifactPublicationSet.java     |   57 +
 .../internal/plugins/StartScriptGenerator.groovy   |  124 ++
 .../api/internal/tasks/DefaultSourceSet.java       |   38 +-
 .../internal/tasks/DefaultSourceSetContainer.java  |   92 +-
 .../api/internal/tasks/DefaultSourceSetOutput.java |  118 ++
 .../compile/AntDependsStaleClassCleaner.groovy     |   15 +-
 .../tasks/compile/AntGroovyCompiler.groovy         |   32 +-
 .../internal/tasks/compile/AntJavaCompiler.groovy  |   47 +-
 .../internal/tasks/compile/ApiGroovyCompiler.java  |  118 ++
 .../tasks/compile/CommandLineJavaCompiler.java     |   70 +
 .../CommandLineJavaCompilerArgumentsGenerator.java |   76 +
 .../tasks/compile/CompilationFailedException.java  |   26 +
 .../api/internal/tasks/compile/CompileSpec.java    |   20 +
 .../api/internal/tasks/compile/Compiler.java       |   13 +-
 .../compile/DefaultGroovyJavaJointCompileSpec.java |   38 +
 .../tasks/compile/DefaultJavaCompileSpec.java      |   56 +
 .../tasks/compile/DefaultJavaCompilerFactory.java  |   87 +
 .../compile/DefaultJvmLanguageCompileSpec.java     |   53 +
 .../tasks/compile/DelegatingGroovyCompiler.java    |   31 +
 .../tasks/compile/DelegatingJavaCompiler.java      |   31 +
 .../internal/tasks/compile/GroovyCompileSpec.java  |   29 +
 .../api/internal/tasks/compile/GroovyCompiler.java |   26 -
 .../tasks/compile/GroovyCompilerFactory.java       |   71 +
 .../tasks/compile/GroovyJavaJointCompileSpec.java  |   20 +
 .../tasks/compile/GroovyJavaJointCompiler.java     |   19 -
 .../compile/InProcessJavaCompilerFactory.java      |   52 +
 .../tasks/compile/IncrementalGroovyCompiler.java   |   21 +-
 .../tasks/compile/IncrementalJavaCompiler.java     |   28 +-
 .../compile/IncrementalJavaCompilerSupport.java    |   39 +
 .../compile/IncrementalJavaSourceCompiler.java     |   77 -
 .../internal/tasks/compile/JavaCompileSpec.java    |   37 +
 .../api/internal/tasks/compile/JavaCompiler.java   |   22 -
 .../compile/JavaCompilerArgumentsBuilder.java      |  156 ++
 .../tasks/compile/JavaCompilerFactory.java         |   25 +
 .../internal/tasks/compile/JavaSourceCompiler.java |   26 -
 .../tasks/compile/JvmLanguageCompileSpec.java      |   35 +
 .../tasks/compile/NormalizingGroovyCompiler.java   |  103 ++
 .../tasks/compile/NormalizingJavaCompiler.java     |  104 ++
 .../internal/tasks/compile/SimpleWorkResult.java   |   30 +
 .../internal/tasks/compile/SunJavaCompiler.java    |   45 +
 .../tasks/compile/TransformingClassLoader.java     |  188 ++
 .../tasks/compile/daemon/CompileResult.java        |   44 +
 .../tasks/compile/daemon/CompilerDaemon.java       |   26 +
 .../tasks/compile/daemon/CompilerDaemonClient.java |   60 +
 .../daemon/CompilerDaemonClientProtocol.java       |   23 +
 .../compile/daemon/CompilerDaemonFactory.java      |   23 +
 .../compile/daemon/CompilerDaemonManager.java      |  109 ++
 .../tasks/compile/daemon/CompilerDaemonServer.java |   64 +
 .../daemon/CompilerDaemonServerProtocol.java       |   27 +
 .../tasks/compile/daemon/DaemonForkOptions.java    |  132 ++
 .../tasks/compile/daemon/DaemonGroovyCompiler.java |   75 +
 .../tasks/compile/daemon/DaemonJavaCompiler.java   |   49 +
 .../daemon/InProcessCompilerDaemonFactory.java     |   76 +
 .../tasks/compile/jdk6/Jdk6JavaCompiler.java       |   59 +
 .../tasks/testing/DefaultTestOutputEvent.java      |   40 +
 .../internal/tasks/testing/TestClassProcessor.java |    2 +-
 .../api/internal/tasks/testing/TestFramework.java  |    2 +-
 .../internal/tasks/testing/TestOutputEvent.java    |   41 -
 .../tasks/testing/TestResultProcessor.java         |    2 +
 .../testing/WorkerTestClassProcessorFactory.java   |    2 +-
 .../detection/AbstractTestFrameworkDetector.java   |   15 +-
 .../detection/ClassFileExtractionManager.java      |   14 +-
 .../testing/detection/DefaultTestExecuter.java     |    2 +-
 .../tasks/testing/junit/JUnitDetector.java         |    5 +-
 .../testing/junit/JUnitTestClassExecuter.java      |   64 +-
 .../testing/junit/JUnitTestClassProcessor.java     |   25 +-
 .../tasks/testing/junit/JUnitTestEventAdapter.java |  140 ++
 .../tasks/testing/junit/JUnitTestFramework.java    |    8 +-
 .../junit/JUnitTestResultProcessorAdapter.java     |  138 --
 .../testing/junit/JUnitXmlReportGenerator.java     |   11 +-
 .../junit/TestClassExecutionEventGenerator.java    |   89 +
 .../testing/junit/TestClassExecutionListener.java  |   23 +
 .../testing/junit/report/ClassPageRenderer.java    |    8 +-
 .../testing/junit/report/DefaultTestReport.java    |   69 +-
 .../tasks/testing/junit/report/PageRenderer.java   |  212 +--
 .../testing/junit/report/TestResultModel.java      |   38 +-
 .../tasks/testing/logging/DefaultTestLogging.java  |   36 +
 .../testing/logging/StandardStreamsLogger.java     |   53 +
 .../CaptureTestOutputTestResultProcessor.java      |   61 +-
 .../processors/MaxNParallelTestClassProcessor.java |    8 +-
 .../RestartEveryNTestClassProcessor.java           |    2 +-
 .../results/AttachParentTestResultProcessor.java   |    1 +
 .../tasks/testing/results/DefaultTestResult.java   |   16 +-
 .../testing/results/LoggingResultProcessor.java    |    1 +
 .../results/StateTrackingTestResultProcessor.java  |  117 +-
 .../tasks/testing/results/TestListenerAdapter.java |   27 +-
 .../internal/tasks/testing/results/TestState.java  |   96 +
 .../tasks/testing/results/TestSummaryListener.java |    0
 .../testing/results/UnknownTestDescriptor.java     |   40 +
 .../testng/TestNGConfigurationListener.java        |   50 +
 .../tasks/testing/testng/TestNGDetector.java       |    5 +-
 .../testng/TestNGListenerAdapterFactory.java       |   61 +
 .../testing/testng/TestNGTestClassProcessor.java   |  207 ++-
 .../tasks/testing/testng/TestNGTestFramework.java  |    5 +-
 .../testng/TestNGTestResultProcessorAdapter.java   |  276 +--
 .../testing/worker/ForkingTestClassProcessor.java  |    2 +-
 .../internal/tasks/testing/worker/TestWorker.java  |  231 ++-
 .../java/archives/internal/DefaultManifest.java    |    2 +-
 .../internal/DefaultManifestMergeSpec.java         |    3 +-
 .../gradle/api/plugins/ApplicationPlugin.groovy    |  257 +--
 .../api/plugins/ApplicationPluginConvention.groovy |   31 +-
 .../org/gradle/api/plugins/BasePlugin.groovy       |   32 +-
 .../org/gradle/api/plugins/GroovyBasePlugin.java   |   49 +-
 .../org/gradle/api/plugins/GroovyPlugin.java       |    6 +-
 .../org/gradle/api/plugins/JavaBasePlugin.java     |  118 +-
 .../groovy/org/gradle/api/plugins/JavaPlugin.java  |   56 +-
 .../gradle/api/plugins/JavaPluginConvention.groovy |   36 +-
 .../gradle/api/plugins/ProjectReportsPlugin.java   |   43 +-
 .../plugins/ProjectReportsPluginConvention.groovy  |    3 +-
 .../gradle/api/plugins/ReportingBasePlugin.java    |   13 +-
 .../api/plugins/ReportingBasePluginConvention.java |   53 +-
 .../groovy/org/gradle/api/plugins/WarPlugin.java   |   26 +-
 .../groovy/org/gradle/api/reporting/Report.java    |  110 ++
 .../org/gradle/api/reporting/ReportContainer.java  |   60 +
 .../groovy/org/gradle/api/reporting/Reporting.java |   52 +
 .../gradle/api/reporting/ReportingExtension.java   |  106 ++
 .../org/gradle/api/reporting/SingleFileReport.java |   40 +
 .../reporting/internal/DefaultReportContainer.java |   80 +
 .../api/reporting/internal/SimpleReport.java       |   77 +
 .../reporting/internal/TaskGeneratedReport.java    |   28 +
 .../internal/TaskGeneratedSingleFileReport.java    |   32 +
 .../reporting/internal/TaskReportContainer.java    |   82 +
 .../org/gradle/api/reporting/package-info.java     |   20 +
 .../groovy/org/gradle/api/tasks/SourceSet.java     |   56 +-
 .../org/gradle/api/tasks/SourceSetContainer.java   |    4 +-
 .../org/gradle/api/tasks/SourceSetOutput.java      |  147 ++
 .../tasks/application/CreateStartScripts.groovy    |   51 +-
 .../org/gradle/api/tasks/bundling/Jar.groovy       |    2 +-
 .../api/tasks/compile/AbstractOptions.groovy       |   79 -
 .../gradle/api/tasks/compile/AbstractOptions.java  |   98 +
 .../org/gradle/api/tasks/compile/Compile.java      |   41 +-
 .../gradle/api/tasks/compile/CompileOptions.groovy |  160 --
 .../gradle/api/tasks/compile/CompileOptions.java   |  372 ++++
 .../gradle/api/tasks/compile/DebugOptions.groovy   |   32 -
 .../org/gradle/api/tasks/compile/DebugOptions.java |   64 +
 .../gradle/api/tasks/compile/DependOptions.groovy  |   51 -
 .../gradle/api/tasks/compile/DependOptions.java    |  102 +
 .../gradle/api/tasks/compile/ForkOptions.groovy    |   51 -
 .../org/gradle/api/tasks/compile/ForkOptions.java  |  112 ++
 .../gradle/api/tasks/compile/GroovyCompile.java    |   57 +-
 .../api/tasks/compile/GroovyCompileOptions.groovy  |   56 -
 .../api/tasks/compile/GroovyCompileOptions.java    |  196 ++
 .../api/tasks/compile/GroovyForkOptions.groovy     |   24 -
 .../api/tasks/compile/GroovyForkOptions.java       |   67 +
 .../org/gradle/api/tasks/javadoc/Groovydoc.java    |   18 +-
 .../org/gradle/api/tasks/javadoc/Javadoc.java      |   33 +-
 .../groovy/org/gradle/api/tasks/testing/Test.java  |  184 +-
 .../gradle/api/tasks/testing/TestDescriptor.java   |    0
 .../org/gradle/api/tasks/testing/TestLogging.java  |   37 +
 .../gradle/api/tasks/testing/TestOutputEvent.java  |   40 +
 .../api/tasks/testing/TestOutputListener.java      |   31 +
 .../org/gradle/api/tasks/testing/TestResult.java   |    0
 .../api/tasks/testing/testng/TestNGOptions.groovy  |   53 +-
 .../org/gradle/api/tasks/wrapper/Wrapper.java      |  280 +++
 .../org/gradle/api/tasks/wrapper/package-info.java |   20 +
 .../external/javadoc/CoreJavadocOptions.java       |    0
 .../external/javadoc/JavadocMemberLevel.java       |    0
 .../external/javadoc/JavadocOfflineLink.java       |    0
 .../external/javadoc/JavadocOutputLevel.java       |    0
 .../javadoc/StandardJavadocDocletOptions.java      | 1952 ++++++++++----------
 .../javadoc/internal/JavadocExecHandleBuilder.java |    2 +-
 .../api/internal/plugins/unixStartScript.txt       |  164 ++
 .../api/internal/plugins/windowsStartScript.txt    |   91 +
 .../internal/tasks/testing/junit/report/style.css  |  131 --
 .../api/tasks/application/unixStartScript.txt      |  179 --
 .../api/tasks/application/windowsStartScript.txt   |   82 -
 .../DefaultArtifactPublicationSetTest.groovy       |  140 ++
 .../plugins/StartScriptGeneratorTest.groovy        |   73 +
 .../tasks/DefaultSourceSetContainerTest.java       |   12 +-
 .../api/internal/tasks/DefaultSourceSetTest.groovy |   59 +-
 ...ndLineJavaCompilerArgumentsGeneratorTest.groovy |   75 +
 .../compile/DefaultJavaCompilerFactoryTest.groovy  |   85 +
 .../compile/DelegatingJavaCompilerTest.groovy      |   33 +
 .../InProcessJavaCompilerFactoryTest.groovy        |   39 +
 .../compile/IncrementalJavaCompilerTest.groovy     |   60 +
 .../IncrementalJavaSourceCompilerTest.groovy       |   48 -
 .../JavaCompilerArgumentsBuilderTest.groovy        |  244 +++
 .../compile/NormalizingJavaCompilerTest.groovy     |  124 ++
 .../compile/TransformingClassLoaderTest.groovy     |   96 +
 .../daemon/DaemonForkOptionsMergeTest.groovy       |   52 +
 .../compile/daemon/DaemonForkOptionsTest.groovy    |  176 ++
 .../detection/DefaultTestClassScannerTest.groovy   |    0
 .../junit/JUnitTestClassProcessorTest.groovy       |  113 +-
 .../testing/junit/JUnitTestFrameworkTest.java      |    6 +-
 .../TestClassExecutionEventGeneratorTest.groovy    |  131 ++
 .../junit/report/CompositeTestResultsTest.groovy   |    2 +-
 .../junit/report/DefaultTestReportTest.groovy      |    4 +-
 .../logging/StandardStreamsLoggerTest.groovy       |  113 ++
 ...CaptureTestOutputTestResultProcessorTest.groovy |   53 +-
 .../MaxNParallelTestClassProcessorTest.groovy      |    2 +-
 .../RestartEveryNTestClassProcessorTest.java       |  311 ++--
 .../testing/results/DefaultTestResultTest.groovy   |   48 +
 .../testing/results/TestListenerAdapterTest.groovy |  559 +++---
 .../testing/results/TestSummaryListenerTest.groovy |  254 ++-
 .../testng/TestNGListenerAdapterFactorySpec.groovy |   78 +
 .../testing/testng/TestNGTestFrameworkTest.java    |    2 +-
 .../worker/ForkingTestClassProcessorTest.java      |  282 +--
 .../tasks/testing/worker/TestWorkerTest.groovy     |    0
 .../api/plugins/ApplicationPluginTest.groovy       |   18 +-
 .../api/plugins/BasePluginConventionTest.groovy    |    4 +-
 .../org/gradle/api/plugins/BasePluginTest.groovy   |  228 ++-
 .../gradle/api/plugins/GroovyBasePluginTest.groovy |    8 +-
 .../org/gradle/api/plugins/GroovyPluginTest.groovy |    7 +-
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |   81 +-
 .../api/plugins/JavaPluginConventionTest.groovy    |   16 +-
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |  114 +-
 .../ReportingBasePluginConventionTest.groovy       |   64 +
 .../plugins/ReportingBasePluginConventionTest.java |   86 -
 .../api/plugins/ReportingBasePluginTest.groovy     |   47 +
 .../api/plugins/ReportingBasePluginTest.java       |   32 -
 .../org/gradle/api/plugins/WarPluginTest.groovy    |   14 +-
 .../api/reporting/ReportingExtensionTest.groovy    |   56 +
 .../internal/DefaultReportContainerTest.groovy     |  132 ++
 .../internal/TaskGeneratedReportTest.groovy        |   43 +
 .../internal/TaskReportContainerTest.groovy        |  108 ++
 .../api/tasks/compile/AbstractOptionsTest.groovy   |   80 +-
 .../api/tasks/compile/CompileOptionsTest.groovy    |    5 +-
 .../org/gradle/api/tasks/compile/CompileTest.java  |   23 +-
 .../api/tasks/compile/ForkOptionsTest.groovy       |   13 +-
 .../tasks/compile/GroovyCompileOptionsTest.groovy  |   18 +-
 .../api/tasks/compile/GroovyCompileTest.java       |   31 +-
 .../api/tasks/compile/GroovyForkOptionsTest.groovy |    3 +-
 .../org/gradle/api/tasks/testing/TestTest.java     |   10 +-
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |  161 ++
 .../javadoc/StandardJavadocDocletOptionsTest.java  |   21 +-
 .../internal/JavadocExecHandleBuilderTest.groovy   |    2 +-
 .../javadoc/internal/JavadocOptionFileTest.java    |   27 +-
 .../api/tasks/compile/AbstractCompileTest.java     |    0
 subprojects/scala/scala.gradle                     |    8 +-
 .../IncrementalScalaCompileIntegrationTest.groovy  |   54 +
 .../recompilesDependentClasses/NewIPerson.scala    |    0
 .../recompilesDependentClasses/build.gradle        |    0
 .../src/main/scala/IPerson.scala                   |    0
 .../src/main/scala/Person.scala                    |    0
 .../build.gradle                                   |    0
 .../src/main/scala/Person.java                     |    0
 .../src/main/scala/PersonImpl.scala                |    0
 .../internal/tasks/scala/AntScalaCompiler.groovy   |   21 +-
 .../tasks/scala/DefaultScalaCompileSpec.java       |   39 +
 .../scala/DefaultScalaJavaJointCompileSpec.java    |   39 +
 .../tasks/scala/DefaultScalaJavaJointCompiler.java |   63 +-
 .../tasks/scala/IncrementalScalaCompiler.java      |   26 +-
 .../api/internal/tasks/scala/ScalaCompileSpec.java |   30 +
 .../api/internal/tasks/scala/ScalaCompiler.java    |   27 -
 .../tasks/scala/ScalaJavaJointCompileSpec.java     |   22 +
 .../tasks/scala/ScalaJavaJointCompiler.java        |   21 -
 .../api/plugins/scala/ScalaBasePlugin.groovy       |    5 +-
 .../gradle/api/plugins/scala/ScalaPlugin.groovy    |    4 +-
 .../org/gradle/api/tasks/scala/ScalaCompile.java   |   33 +-
 .../scala/DefaultScalaJavaJointCompilerTest.groovy |   29 +-
 .../api/plugins/scala/ScalaBasePluginTest.groovy   |   20 +-
 .../api/plugins/scala/ScalaPluginTest.groovy       |   17 +-
 .../gradle/api/tasks/scala/ScalaCompileTest.java   |   18 +-
 subprojects/signing/signing.gradle                 |   29 +
 .../NoSigningCredentialsIntegrationSpec.groovy     |   76 +
 .../SigningConfigurationsIntegrationSpec.groovy    |   46 +
 .../plugins/signing/SigningIntegrationSpec.groovy  |  178 ++
 .../plugins/signing/SigningSamplesSpec.groovy      |   56 +
 .../signing/SigningTasksIntegrationSpec.groovy     |  116 ++
 .../gradle/plugins/signing/keys/default/keyId.txt  |    1 +
 .../plugins/signing/keys/default/password.txt      |    1 +
 .../plugins/signing/keys/default/secring.gpg       |  Bin 0 -> 2619 bytes
 .../groovy/org/gradle/plugins/signing/Sign.groovy  |  205 ++
 .../gradle/plugins/signing/SignOperation.groovy    |  179 ++
 .../org/gradle/plugins/signing/Signature.groovy    |  330 ++++
 .../gradle/plugins/signing/SignatureSpec.groovy    |   72 +
 .../gradle/plugins/signing/SigningExtension.groovy |  379 ++++
 .../gradle/plugins/signing/SigningPlugin.groovy    |   46 +
 .../plugins/signing/SigningPluginConvention.groovy |   74 +
 .../plugins/signing/signatory/Signatory.groovy     |   48 +
 .../signing/signatory/SignatoryProvider.groovy     |   52 +
 .../signing/signatory/SignatorySupport.groovy      |   26 +
 .../plugins/signing/signatory/pgp/PgpKeyId.groovy  |  100 +
 .../signing/signatory/pgp/PgpSignatory.groovy      |   80 +
 .../signatory/pgp/PgpSignatoryFactory.groovy       |  109 ++
 .../signatory/pgp/PgpSignatoryProvider.groovy      |   76 +
 .../signing/type/AbstractSignatureType.groovy      |   50 +
 .../type/AbstractSignatureTypeProvider.groovy      |   48 +
 .../signing/type/BinarySignatureType.groovy        |   24 +
 .../type/DefaultSignatureTypeProvider.groovy       |   29 +
 .../plugins/signing/type/SignatureType.groovy      |   32 +
 .../signing/type/SignatureTypeProvider.groovy      |   27 +
 .../signing/type/pgp/ArmoredSignatureType.groovy   |   34 +
 .../META-INF/gradle-plugins/signing.properties     |    1 +
 .../plugins/signing/ConventionSmokeSpec.groovy     |   95 +
 .../plugins/signing/SignOperationSpec.groovy       |   88 +
 .../signing/SignatoriesConfigurationSpec.groovy    |   97 +
 .../signing/SigningConfigurationsSpec.groovy       |   64 +
 .../plugins/signing/SigningProjectSpec.groovy      |  119 ++
 .../gradle/plugins/signing/SigningTasksSpec.groovy |   66 +
 .../signing/signatory/pgp/PgpKeyIdSpec.groovy      |   54 +
 .../type/AbstractSignatureTypeProviderSpec.groovy  |   60 +
 .../signing/type/AbstractSignatureTypeSpec.groovy  |   50 +
 subprojects/signing/src/test/resources/1.txt       |    1 +
 subprojects/signing/src/test/resources/2.txt       |    1 +
 .../src/test/resources/keys/gradle/keyId.txt       |    1 +
 .../src/test/resources/keys/gradle/password.txt    |    1 +
 .../src/test/resources/keys/gradle/secring.gpg     |  Bin 0 -> 2619 bytes
 .../test/resources/keys/invalid-key-ring/keyId.txt |    1 +
 .../resources/keys/invalid-key-ring/password.txt   |    1 +
 .../resources/keys/invalid-key-ring/secring.gpg    |    1 +
 subprojects/sonar/sonar.gradle                     |   37 +-
 .../org/gradle/api/plugins/sonar/Sonar.groovy      |  258 ---
 .../gradle/api/plugins/sonar/SonarAnalyze.groovy   |   56 +
 .../gradle/api/plugins/sonar/SonarPlugin.groovy    |  150 +-
 .../sonar/internal/ClassesOnlyClassLoader.java     |   39 -
 .../sonar/internal/SonarCodeAnalyzer.groovy        |  111 +-
 .../plugins/sonar/model/IncludeProperties.groovy   |   26 +
 .../sonar/model/ModelToPropertiesConverter.groovy  |   86 +
 .../api/plugins/sonar/model/SonarProperty.groovy   |   28 +
 .../api/plugins/sonar/model/SonarRootModel.groovy  |  452 +++++
 .../api/plugins/sonar/SonarAnalyzeTest.groovy      |   43 +
 .../api/plugins/sonar/SonarPluginTest.groovy       |  134 +-
 .../model/ModelToPropertiesConverterTest.groovy    |  113 ++
 .../tooling/AutoTestedSamplesToolingApiTest.groovy |  109 ++
 .../ConcurrentToolingApiIntegrationSpec.groovy     |  355 ++++
 ...GlobalLoggingManipulationIntegrationTest.groovy |   85 +
 .../SamplesToolingApiIntegrationTest.groovy        |   91 +
 .../tooling/ToolingApiIntegrationTest.groovy       |  140 ++
 .../tooling/fixture/ConfigurableOperation.groovy   |   63 +
 .../fixture/ExternalToolingApiDistribution.groovy  |   47 +
 .../tooling/fixture/IncludeAllPermutations.java    |   23 +
 .../tooling/fixture/MaxTargetGradleVersion.java    |   25 +
 .../tooling/fixture/MinTargetGradleVersion.java    |   25 +
 .../tooling/fixture/MinToolingApiVersion.java      |   25 +
 .../TestClasspathToolingApiDistribution.groovy     |   34 +
 .../integtests/tooling/fixture/TextUtil.java       |   27 +
 .../integtests/tooling/fixture/ToolingApi.groovy   |  111 ++
 .../ToolingApiCompatibilitySuiteRunner.groovy      |  168 ++
 .../tooling/fixture/ToolingApiDistribution.groovy  |   26 +
 .../fixture/ToolingApiDistributionResolver.groovy  |   70 +
 .../tooling/fixture/ToolingApiSpecification.groovy |   86 +
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |  306 +++
 ...piEclipseLinkedResourcesCrossVersionSpec.groovy |   57 +
 ...ngApiEclipseMinimalModelCrossVersionSpec.groovy |   70 +
 ...EclipseModelWithFlatRepoCrossVersionSpec.groovy |   56 +
 ...ToolingApiBuildExecutionCrossVersionSpec.groovy |  150 ++
 ...ildableEclipseModelFixesCrossVersionSpec.groovy |   74 +
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |   53 +
 .../ToolingApiGradleProjectCrossVersionSpec.groovy |  103 ++
 ...orsProjectCustomizationsCrossVersionSpec.groovy |  134 ++
 .../m5/ToolingApiIdeaModelCrossVersionSpec.groovy  |  312 ++++
 .../m5/ToolingApiModelCrossVersionSpec.groovy      |   62 +
 ...ReceivingStandardStreamsCrossVersionSpec.groovy |   75 +
 .../BuildEnvironmentModelCrossVersionSpec.groovy   |   80 +
 .../ConsumingStandardInputCrossVersionSpec.groovy  |  104 ++
 .../m8/GradlePropertiesCrossVersionSpec.groovy     |   72 +
 .../m8/JavaConfigurabilityCrossVersionSpec.groovy  |  113 ++
 ...rictLongRunningOperationCrossVersionSpec.groovy |  104 ++
 .../ToolingApiEclipseModelCrossVersionSpec.groovy  |   45 +
 .../m8/ToolingApiLoggingCrossVersionSpec.groovy    |  132 ++
 .../m8/UnknownModelFeedbackCrossVersionSpec.groovy |   43 +
 ...sionOnlyBuildEnvironmentCrossVersionSpec.groovy |   55 +
 .../m9/DaemonErrorFeedbackCrossVersionSpec.groovy  |   49 +
 .../M9JavaConfigurabilityCrossVersionSpec.groovy   |  131 ++
 ...singCommandLineArgumentsCrossVersionSpec.groovy |  187 ++
 .../java/org/gradle/tooling/BuildLauncher.java     |   95 +-
 .../java/org/gradle/tooling/GradleConnector.java   |   31 +-
 .../org/gradle/tooling/LongRunningOperation.java   |  148 ++
 .../main/java/org/gradle/tooling/ModelBuilder.java |   87 +-
 .../java/org/gradle/tooling/ProjectConnection.java |   44 +-
 .../java/org/gradle/tooling/ResultHandler.java     |   13 +
 .../org/gradle/tooling/UnknownModelException.java  |   31 +
 .../tooling/UnsupportedVersionException.java       |    4 +
 .../UnsupportedBuildArgumentException.java         |   34 +
 ...UnsupportedOperationConfigurationException.java |   36 +
 .../gradle/tooling/exceptions/package-info.java    |   20 +
 .../tooling/internal/DefaultEclipseProject.java    |  117 --
 .../internal/DefaultEclipseProjectDependency.java  |   44 -
 .../internal/DefaultEclipseSourceDirectory.java    |   44 -
 .../internal/DefaultExternalDependency.java        |   45 -
 .../org/gradle/tooling/internal/DefaultTask.java   |   56 -
 .../internal/build/DefaultBuildEnvironment.java    |   62 +
 .../build/VersionOnlyBuildEnvironment.java         |   34 +
 .../consumer/AbstractLongRunningOperation.java     |   99 -
 .../tooling/internal/consumer/AsyncConnection.java |   28 -
 .../internal/consumer/BlockingResultHandler.java   |    6 +-
 .../CachingToolingImplementationLoader.java        |   44 -
 .../internal/consumer/ConnectionFactory.java       |   33 +-
 .../internal/consumer/ConnectionParameters.java    |    5 +
 .../consumer/ConnectorServiceRegistry.java         |   47 -
 .../internal/consumer/ConnectorServices.java       |   52 +
 .../internal/consumer/DefaultAsyncConnection.java  |   90 -
 .../internal/consumer/DefaultBuildLauncher.java    |   40 +-
 .../consumer/DefaultConnectionParameters.java      |   54 +-
 .../internal/consumer/DefaultGradleConnector.java  |   51 +-
 .../internal/consumer/DefaultModelBuilder.java     |   74 +-
 .../consumer/DefaultProjectConnection.java         |   43 +-
 .../DefaultToolingImplementationLoader.java        |   99 -
 .../tooling/internal/consumer/Distribution.java    |    4 +-
 .../internal/consumer/DistributionFactory.java     |   67 +-
 .../tooling/internal/consumer/LazyConnection.java  |  135 --
 .../tooling/internal/consumer/LoggingProvider.java |   28 +
 .../tooling/internal/consumer/ModelProvider.java   |   59 +
 .../internal/consumer/ProgressListenerAdapter.java |   51 -
 .../consumer/ProgressLoggingConnection.java        |  105 --
 .../internal/consumer/ProtocolToModelAdapter.java  |  165 --
 .../internal/consumer/ResultHandlerAdapter.java    |   11 +-
 .../internal/consumer/SynchronizedLogging.java     |   75 +
 .../consumer/ToolingImplementationLoader.java      |   22 -
 .../internal/consumer/async/AsyncConnection.java   |   33 +
 .../consumer/async/DefaultAsyncConnection.java     |   98 +
 .../consumer/connection/AdaptedConnection.java     |   70 +
 .../consumer/connection/ConsumerConnection.java    |   37 +
 .../connection/ConsumerConnectionMetadata.java     |   44 +
 .../consumer/connection/LazyConnection.java        |  156 ++
 .../connection/LoggingInitializerConnection.java   |   60 +
 .../connection/ProgressLoggingConnection.java      |  112 ++
 .../converters/GradleProjectConverter.java         |   64 +
 .../loader/CachingToolingImplementationLoader.java |   47 +
 .../loader/DefaultToolingImplementationLoader.java |   80 +
 .../SynchronizedToolingImplementationLoader.java   |   58 +
 .../loader/ToolingImplementationLoader.java        |   24 +
 .../parameters/ConsumerOperationParameters.java    |  153 ++
 .../parameters/ProgressListenerAdapter.java        |   51 +
 .../protocoladapter/ModelPropertyHandler.java      |   51 +
 .../protocoladapter/ProtocolToModelAdapter.java    |  186 ++
 .../protocoladapter/TargetTypeProvider.java        |   53 +
 .../consumer/versioning/FeatureValidator.java      |   46 +
 .../internal/consumer/versioning/ModelMapping.java |   65 +
 .../consumer/versioning/VersionDetails.java        |   66 +
 .../eclipse/DefaultEclipseExternalDependency.java  |   45 +
 .../eclipse/DefaultEclipseLinkedResource.java      |   55 +
 .../internal/eclipse/DefaultEclipseProject.java    |  134 ++
 .../eclipse/DefaultEclipseProjectDependency.java   |   44 +
 .../eclipse/DefaultEclipseSourceDirectory.java     |   44 +
 .../internal/eclipse/DefaultEclipseTask.java       |   56 +
 .../internal/gradle/DefaultGradleProject.java      |  126 ++
 .../tooling/internal/gradle/DefaultGradleTask.java |   76 +
 .../internal/idea/DefaultIdeaCompilerOutput.java   |   68 +
 .../internal/idea/DefaultIdeaContentRoot.java      |   83 +
 .../internal/idea/DefaultIdeaDependencyScope.java  |   67 +
 .../internal/idea/DefaultIdeaLanguageLevel.java    |   85 +
 .../tooling/internal/idea/DefaultIdeaModule.java   |  137 ++
 .../internal/idea/DefaultIdeaModuleDependency.java |   69 +
 .../tooling/internal/idea/DefaultIdeaProject.java  |  118 ++
 .../DefaultIdeaSingleEntryLibraryDependency.java   |   91 +
 .../internal/idea/DefaultIdeaSourceDirectory.java  |   46 +
 .../protocol/BuildOperationParametersVersion1.java |    2 +
 .../internal/protocol/ConnectionVersion4.java      |    7 +-
 .../protocol/InternalBasicIdeaProject.java         |   22 +
 .../protocol/InternalBuildEnvironment.java         |   25 +
 .../internal/protocol/InternalConnection.java      |   34 +
 .../internal/protocol/InternalGradleProject.java   |   22 +
 .../internal/protocol/InternalIdeaProject.java     |   23 +
 .../protocol/InternalProtocolInterface.java        |   32 +
 .../internal/protocol/InternalTestModel.java       |   24 +
 .../LongRunningOperationParametersVersion1.java    |    8 +
 .../eclipse/EclipseLinkedResourceVersion1.java     |   33 +
 .../InternalUnsupportedBuildArgumentException.java |   31 +
 .../internal/reflect/CompatibleIntrospector.java   |   71 +
 .../org/gradle/tooling/model/BuildableElement.java |   33 +
 .../org/gradle/tooling/model/BuildableProject.java |   28 -
 .../java/org/gradle/tooling/model/Element.java     |   39 +
 .../org/gradle/tooling/model/GradleProject.java    |   56 +
 .../java/org/gradle/tooling/model/GradleTask.java  |   32 +
 .../org/gradle/tooling/model/HasGradleProject.java |   32 +
 .../gradle/tooling/model/HierarchicalElement.java  |   40 +
 .../gradle/tooling/model/HierarchicalProject.java  |   35 -
 .../main/java/org/gradle/tooling/model/Model.java  |   26 +
 .../java/org/gradle/tooling/model/Project.java     |   51 -
 .../gradle/tooling/model/ProjectDependency.java    |    7 +-
 .../main/java/org/gradle/tooling/model/Task.java   |    7 +-
 .../tooling/model/UnsupportedMethodException.java  |   37 +
 .../tooling/model/build/BuildEnvironment.java      |   56 +
 .../tooling/model/build/GradleEnvironment.java     |   32 +
 .../tooling/model/build/JavaEnvironment.java       |   45 +
 .../gradle/tooling/model/build/package-info.java   |   20 +
 .../model/eclipse/EclipseLinkedResource.java       |   76 +
 .../tooling/model/eclipse/EclipseProject.java      |   25 +-
 .../model/eclipse/EclipseProjectDependency.java    |    6 +-
 .../gradle/tooling/model/eclipse/EclipseTask.java  |    3 +-
 .../model/eclipse/HierarchicalEclipseProject.java  |   26 +-
 .../tooling/model/idea/BasicIdeaProject.java       |   26 +
 .../tooling/model/idea/IdeaCompilerOutput.java     |   50 +
 .../gradle/tooling/model/idea/IdeaContentRoot.java |   48 +
 .../gradle/tooling/model/idea/IdeaDependency.java  |   41 +
 .../tooling/model/idea/IdeaDependencyScope.java    |   25 +
 .../tooling/model/idea/IdeaLanguageLevel.java      |   31 +
 .../org/gradle/tooling/model/idea/IdeaModule.java  |   75 +
 .../tooling/model/idea/IdeaModuleDependency.java   |   32 +
 .../org/gradle/tooling/model/idea/IdeaProject.java |   59 +
 .../idea/IdeaSingleEntryLibraryDependency.java     |   49 +
 .../tooling/model/idea/IdeaSourceDirectory.java    |   26 +
 .../gradle/tooling/model/idea/package-info.java    |   20 +
 .../gradle/tooling/model/internal/Exceptions.java  |   57 +
 .../gradle/tooling/model/internal/TestModel.java   |   26 +
 .../internal/DefaultEclipseProjectTest.groovy      |   27 -
 .../CachingToolingImplementationLoaderTest.groovy  |   74 -
 .../internal/consumer/ConnectionFactoryTest.groovy |   18 +-
 .../internal/consumer/ConnectorServicesTest.groovy |   41 +
 .../consumer/DefaultBuildLauncherTest.groovy       |   26 +-
 .../consumer/DefaultGradleConnectorTest.groovy     |   14 +-
 .../consumer/DefaultModelBuilderTest.groovy        |   75 +-
 .../consumer/DefaultProjectConnectionTest.groovy   |   17 +-
 .../DefaultToolingImplementationLoaderTest.groovy  |   82 -
 .../consumer/DistributionFactoryTest.groovy        |   56 +-
 .../internal/consumer/LazyConnectionTest.groovy    |  101 -
 .../consumer/ProgressListenerAdapterTest.groovy    |   63 -
 .../consumer/ProgressLoggingConnectionTest.groovy  |   70 -
 .../consumer/ProtocolToModelAdapterTest.groovy     |  108 --
 .../consumer/SynchronizedLoggingTest.groovy        |   67 +
 .../tooling/internal/consumer/TestConnection.java  |   36 -
 .../consumer/connection/LazyConnectionTest.groovy  |  135 ++
 .../ProgressLoggingConnectionTest.groovy           |   76 +
 .../CachingToolingImplementationLoaderTest.groovy  |   77 +
 .../DefaultToolingImplementationLoaderTest.groovy  |   76 +
 ...chronizedToolingImplementationLoaderTest.groovy |   93 +
 .../internal/consumer/loader/TestConnection.java   |   36 +
 .../ConsumerOperationParametersTest.groovy         |   67 +
 .../parameters/ProgressListenerAdapterTest.groovy  |   63 +
 .../ProtocolToModelAdapterTest.groovy              |  150 ++
 .../eclipse/DefaultEclipseProjectTest.groovy       |   27 +
 .../gradle/DefaultGradleProjectTest.groovy         |   47 +
 .../reflect/CompatibleIntrospectorTest.groovy      |   59 +
 subprojects/tooling-api/tooling-api.gradle         |   31 +-
 .../ExtraTestCommandLineOptionsListener.java       |   32 +
 .../integtests/FavoritesIntegrationTest.java       |  122 +-
 .../integtests/LiveOutputIntegrationTest.groovy    |  105 +-
 ...projectProjectAndTaskListIntegrationTest.groovy |   28 +-
 .../gradle/foundation/CommandLineAssistant.java    |   44 +-
 .../gradle/foundation/common/ListReorderer.java    |  688 +++----
 .../org/gradle/foundation/common/ObserverLord.java |  334 ++--
 .../gradle/foundation/common/ReorderableList.java  |   24 +-
 .../ipc/gradle/AbstractGradleServerProtocol.java   |   24 +-
 .../gradle/ExecuteGradleCommandClientProtocol.java |    2 +-
 .../gradle/ExecuteGradleCommandServerProtocol.java |    4 +-
 .../ipc/gradle/TaskListServerProtocol.java         |    8 +-
 .../gradle/foundation/output/LiveOutputParser.java |   38 +
 .../visitors/TaskTreePopulationVisitor.java        |  345 ++--
 .../gradleplugin/foundation/DOM4JSerializer.java   |    4 +-
 .../gradleplugin/foundation/GradlePluginLord.java  |   22 +-
 .../foundation/favorites/FavoritesEditor.java      |   29 +-
 .../foundation/request/ExecutionRequest.java       |    4 +-
 .../foundation/request/RefreshTaskListRequest.java |    4 +-
 .../gradleplugin/foundation/request/Request.java   |    4 +-
 .../foundation/runner/GradleRunner.java            |    4 +-
 .../foundation/search/BasicTextSearchCriteria.java |  122 ++
 .../foundation/search/TextBlockSearchEditor.java   |  192 ++
 .../userinterface/swing/common/SearchPanel.java    |  402 ++++
 .../swing/common/TextPaneSearchInteraction.java    |  162 ++
 .../userinterface/swing/generic/OutputPanel.java   |  315 +++-
 .../swing/generic/OutputPanelLord.java             |    9 +-
 .../userinterface/swing/generic/OutputTab.java     |   47 +-
 .../swing/generic/OutputTextPane.java              |  147 +-
 .../generic/SwingEditFavoriteInteraction.java      |   74 +-
 .../swing/generic/SwingExportInteraction.java      |   11 +-
 .../swing/generic/TaskTreeComponent.java           |    2 +-
 .../userinterface/swing/generic/Utility.java       |   36 +-
 .../swing/generic/tabs/FavoriteTasksTab.java       |    7 +-
 .../userinterface/swing/generic/tabs/SetupTab.java |   32 +-
 .../swing/standalone/Application.java              |   14 +-
 .../swing/standalone/BlockingApplication.java      |    6 +-
 .../favorites/FavoritesEditorWrapper.java          |    4 +-
 .../runner/GradleRunnerInteractionWrapper.java     |   12 +-
 .../userinterface/swing/generic/add-favorite.png   |  Bin 0 -> 788 bytes
 .../userinterface/swing/generic/close.png          |  Bin
 .../userinterface/swing/generic/find.png           |  Bin 0 -> 826 bytes
 .../userinterface/swing/generic/next-link.png      |  Bin 0 -> 679 bytes
 .../userinterface/swing/generic/pin.png            |  Bin 0 -> 750 bytes
 .../userinterface/swing/generic/previous-link.png  |  Bin 0 -> 696 bytes
 .../userinterface/swing/generic/stop.png           |  Bin 0 -> 876 bytes
 .../foundation/CommandLineAssistantTest.groovy     |   79 -
 .../gradle/foundation/CommandLineParsingTest.java  |   32 +-
 .../groovy/org/gradle/foundation/TestUtility.java  |    8 +-
 .../foundation/TextBlockSearchEditorTests.java     |  182 ++
 subprojects/ui/ui.gradle                           |   17 +-
 subprojects/website/website.gradle                 |  335 ++++
 .../java/org/gradle/api/tasks/wrapper/Wrapper.java |  392 ----
 .../wrapper/internal/WrapperScriptGenerator.java   |   96 -
 .../org/gradle/api/tasks/wrapper/package-info.java |   20 -
 .../org/gradle/wrapper/BootstrapMainStarter.java   |    2 +-
 .../src/main/java/org/gradle/wrapper/Download.java |   20 +
 .../java/org/gradle/wrapper/GradleWrapperMain.java |   58 +-
 .../src/main/java/org/gradle/wrapper/Install.java  |   76 +-
 .../java/org/gradle/wrapper/PathAssembler.java     |   68 +-
 .../gradle/wrapper/SystemPropertiesHandler.java    |   15 -
 .../src/main/java/org/gradle/wrapper/Wrapper.java  |   98 -
 .../org/gradle/wrapper/WrapperConfiguration.java   |   87 +
 .../java/org/gradle/wrapper/WrapperExecutor.java   |  152 ++
 .../wrapper/internal/unixWrapperScriptHead.txt     |   67 -
 .../wrapper/internal/unixWrapperScriptTail.txt     |   97 -
 .../wrapper/internal/windowsWrapperScriptHead.txt  |   57 -
 .../wrapper/internal/windowsWrapperScriptTail.txt  |   21 -
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |  197 --
 .../groovy/org/gradle/wrapper/InstallTest.groovy   |  191 +-
 .../org/gradle/wrapper/PathAssemblerTest.java      |   77 +-
 .../wrapper/SystemPropertiesHandlerTest.groovy     |    5 -
 .../org/gradle/wrapper/WrapperExecutorTest.groovy  |  201 ++
 .../groovy/org/gradle/wrapper/WrapperTest.groovy   |  106 --
 subprojects/wrapper/wrapper.gradle                 |   26 +-
 4004 files changed, 201628 insertions(+), 80014 deletions(-)

diff --git a/build.gradle b/build.gradle
index a2913e3..f63f4fa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-import org.gradle.build.samples.WrapperProjectCreator
 import org.gradle.build.Version
 import org.gradle.build.Install
 import org.gradle.build.Git
-import java.util.jar.Attributes
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
+import org.gradle.build.Releases
 
 /**
  * For building Gradle you usually don't need to specify any properties. Only certain functionality of the Gradle requires
@@ -27,325 +25,179 @@ import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDepend
  * following properties can be set:
  *
  * Uploading distributions to Gradle's release and snapshot repository at codehaus: artifactoryUserName, artifactoryUserPassword
- * Uploading the userguide and the javadoc to Gradle's website: websiteFtpUserName, websiteFtpUserPassword
+ * Uploading the userguide and the javadoc to Gradle's website: websiteScpUserName, websiteScpUserPassword
  * Using the build to create a new distribution and install it on the local machine: gradle_installPath
  */
 
-version = new Version(project)
+ext.releases = new Releases(project(':core').file('src/releases.xml'), project)
+version = Version.forProject(project)
 
 defaultTasks 'assemble'
 
 apply plugin: 'java-base'
 
-startScriptsDir = new File("$buildDir/startScripts")
-
 archivesBaseName = 'gradle'
 
-libraries = [
-        ant: 'org.apache.ant:ant:1.8.2 at jar',
+ext {
+    versions = [
+        commons_io: 'commons-io:commons-io:1.4'
+    ]
+    libraries = [
+        ant: dependencies.module('org.apache.ant:ant:1.8.2') {
+            dependency 'org.apache.ant:ant-launcher:1.8.2 at jar'
+        },
+        asm: 'asm:asm-all:3.3.1 at jar',
+        commons_cli: 'commons-cli:commons-cli:1.2 at jar',
+        commons_io: dependencies.module(versions.commons_io),
+        commons_lang: 'commons-lang:commons-lang:2.6 at jar',
+        commons_collections: 'commons-collections:commons-collections:3.2.1 at jar',
+        ivy: dependencies.module('org.apache.ivy:ivy:2.2.0'){
+            dependency "com.jcraft:jsch:0.1.46"
+        },
+        jcip: "net.jcip:jcip-annotations:1.0 at jar",
+    ]
+    
+}
+
+// Logging
+libraries.slf4j_api = 'org.slf4j:slf4j-api:1.6.4 at jar'
+libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.6.4') {
+    dependency libraries.slf4j_api
+}
+libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.6.4') {
+    dependency libraries.slf4j_api
+}
+libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.6.4') {
+    dependency libraries.slf4j_api
+}
+libraries.logback_core = 'ch.qos.logback:logback-core:1.0.0 at jar'
+libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.0') {
+    dependency libraries.logback_core
+    dependency libraries.slf4j_api
+}
+
+// Jetty
+libraries.servlet_api = "org.mortbay.jetty:servlet-api:2.5-20081211 at jar"
+libraries.jetty_util = dependencies.module("org.mortbay.jetty:jetty-util:6.1.25") {
+    dependency libraries.slf4j_api
+    dependency libraries.servlet_api
+}
+libraries.jetty = dependencies.module("org.mortbay.jetty:jetty:6.1.25") {
+    dependency libraries.jetty_util
+    dependency libraries.servlet_api
+}
+
+libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:httpclient:4.1.2') {
+    dependency "org.apache.httpcomponents:httpcore:4.1.2 at jar"
+    dependency libraries.jcl_to_slf4j
+    dependency "commons-codec:commons-codec:1.4 at jar"
+    dependency "org.samba.jcifs:jcifs:1.3.17"
+}
+
+libraries.maven_ant_tasks = dependencies.module("org.apache.maven:maven-ant-tasks:2.1.3") {
+    libraries.ant
+}
+
+libraries += [
         ant_junit: 'org.apache.ant:ant-junit:1.8.2 at jar',
-        ant_launcher: 'org.apache.ant:ant-launcher:1.8.2 at jar',
         ant_antlr: 'org.apache.ant:ant-antlr:1.8.2 at jar',
         antlr: 'antlr:antlr:2.7.7 at jar',
-        asm_all: 'asm:asm-all:3.3.1 at jar',
-        commons_cli: 'commons-cli:commons-cli:1.2 at jar',
-        commons_io: 'commons-io:commons-io:1.4 at jar',
-        commons_lang: 'commons-lang:commons-lang:2.6 at jar',
         dom4j: 'dom4j:dom4j:1.6.1 at jar',
-        guava: 'com.google.guava:guava:r08 at jar',
-        groovy: 'org.codehaus.groovy:groovy-all:1.7.10 at jar',
-        ivy: 'org.apache.ivy:ivy:2.2.0 at jar',
+        guava: 'com.google.guava:guava:11.0.1 at jar',
+        groovy: 'org.codehaus.groovy:groovy-all:1.8.6 at jar',
         jaxen: 'jaxen:jaxen:1.1 at jar',
-        slf4j_api: 'org.slf4j:slf4j-api:1.6.1 at jar',
-        jcl_to_slf4j: 'org.slf4j:jcl-over-slf4j:1.6.1 at jar',
-        jul_to_slf4j: 'org.slf4j:jul-to-slf4j:1.6.1 at jar',
-        log4j_to_slf4j: 'org.slf4j:log4j-over-slf4j:1.6.1 at jar',
-        logback_classic: 'ch.qos.logback:logback-classic:0.9.28 at jar',
-        logback_core: 'ch.qos.logback:logback-core:0.9.28 at jar',
-        junit: 'junit:junit:4.8.2',
+        jcip: "net.jcip:jcip-annotations:1.0",
+        jna: 'net.java.dev.jna:jna:3.2.7 at jar',
+        junit: 'junit:junit:4.10',
         xmlunit: 'xmlunit:xmlunit:1.3',
+        nekohtml: 'net.sourceforge.nekohtml:nekohtml:1.9.14'
 ]
 
-libraries.spock = ['org.spockframework:spock-core:0.5-groovy-1.7 at jar',
+libraries.spock = ['org.spockframework:spock-core:0.6-groovy-1.8 at jar',
         libraries.groovy,
-        'org.hamcrest:hamcrest-core:1.1 at jar',
-        'cglib:cglib-nodep:2.2',
-        'org.objenesis:objenesis:1.2']
-libraries.jmock = ['org.jmock:jmock:2.5.1 at jar',
-        'org.hamcrest:hamcrest-core:1.1 at jar',
-        'org.hamcrest:hamcrest-library:1.1 at jar',
-        'org.jmock:jmock-junit4:2.5.1 at jar',
-        'org.jmock:jmock-legacy:2.5.1 at jar',
         'org.objenesis:objenesis:1.2',
         'cglib:cglib-nodep:2.2']
-libraries.groovy_depends = [libraries.groovy, libraries.commons_cli]
-libraries.jetty_depends = ["org.mortbay.jetty:jetty:6.1.25 at jar",
-        "org.mortbay.jetty:jetty-util:6.1.25 at jar",
-        "org.mortbay.jetty:servlet-api:2.5-20081211 at jar"]
+libraries.jmock = ['org.jmock:jmock:2.5.1',
+        'org.hamcrest:hamcrest-core:1.1',
+        'org.hamcrest:hamcrest-library:1.1',
+        'org.jmock:jmock-junit4:2.5.1',
+        'org.jmock:jmock-legacy:2.5.1',
+        'org.objenesis:objenesis:1.2',
+        'cglib:cglib-nodep:2.2']
 
 allprojects {
     group = 'org.gradle'
 
-    plugins.withType(JavaPlugin).whenPluginAdded {
+    plugins.withType(JavaPlugin) {
         sourceCompatibility = 1.5
         targetCompatibility = 1.5
     }
 
     repositories {
-        mavenRepo(urls: 'http://repo.gradle.org/gradle/libs')
+        maven { url 'http://repo.gradle.org/gradle/libs' }
+        maven { url 'http://repository.codehaus.org/' }
+    }
+
+    configurations {
+        all {
+            resolutionStrategy {
+                //we cannot use 'hours' for now due to java 1.5 problem
+                cacheDynamicVersionsFor 24*60*60, 'seconds'
+                cacheChangingModulesFor 24*60*60, 'seconds'
+            }
+        }
     }
 
     version = this.version
-}
 
-configure(groovyProjects()) {
-    apply plugin: 'groovy'
+    apply from: "$rootDir/gradle/conventions-dsl.gradle"
+    
+    ext {
+        isDevBuild = {
+            gradle.taskGraph.hasTask(developerBuild)
+        }
 
-    archivesBaseName = "gradle-${name.replaceAll("\\p{Upper}") { "-${it.toLowerCase()}" } }"
-    dependencies {
-        testCompile libraries.junit, libraries.jmock, libraries.spock
-    }
+        isCIBuild = {
+            gradle.taskGraph.hasTask(ciBuild)
+        }
 
-    apply from: "$rootDir/gradle/compile.gradle"
+        isCommitBuild = {
+            gradle.taskGraph.hasTask(commitBuild)
+        }
 
-    test {
-        maxParallelForks = guessMaxForks()
-    }
+        isFinalReleaseBuild = {
+            gradle.taskGraph.hasTask(releaseVersion)
+        }
 
-    tasks.withType(Jar).each { jar ->
-        jar.manifest.mainAttributes([
-                (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
-                (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
-        ])
+        isRcBuild = {
+            gradle.taskGraph.hasTask(rcVersion)
+        }
+
+        isNightlyBuild = {
+            gradle.taskGraph.hasTask(nightlyVersion)
+        }
+
+        isReleaseBuild = { // Are we doing any kind of “release”? i.e. final, nightly or rc
+            isFinalReleaseBuild() || isNightlyBuild() || isRcBuild()
+        }
     }
 }
 
+configure(groovyProjects()) {
+    apply from: "$rootDir/gradle/groovyProject.gradle"
+}
+
 configure(publishedProjects()) {
     apply from: "$rootDir/gradle/publish.gradle"
 }
 
 allprojects {
     apply from: "$rootDir/gradle/codeQuality.gradle"
+    apply from: "$rootDir/gradle/testWithUnknownOS.gradle"
 }
 
-allprojects {
-    apply plugin: 'idea'
-    apply plugin: 'eclipse'
-    def config = configurations.findByName('testRuntime')
-    if (!config) {
-        return
-    }
-    ideDir = file('ide')
-    task ide(type: Sync) {
-        into "$ideDir/lib"
-        from { config.copyRecursive {dep -> !(dep instanceof ProjectDependency)}.files }
-    }
-}
-
-ideaModule {
-    excludeDirs += file('intTestHomeDir')
-    excludeDirs += file('buildSrc/build')
-    excludeDirs += file('buildSrc/.gradle')
-    excludeDirs += file('performanceTest/.gradle')
-    excludeDirs += file('performanceTest/build')
-    excludeDirs += file('website/.gradle')
-    excludeDirs += file('website/build')
-}
-
-ideaProject {
-    wildcards += ['?*.gradle']
-    javaVersion = '1.6'
-    withXml { provider ->
-		def node = provider.asNode()
-
-		// Use git
-        def vcsConfig = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
-        vcsConfig.mapping[0].'@vcs' = 'Git'
-
-        // Set gradle home
-        def gradleSettings = node.appendNode('component', [name: 'GradleSettings'])
-        gradleSettings.appendNode('option', [name: 'SDK_HOME', value: gradle.gradleHomeDir.absolutePath])
-
-        // Code formatting options
-        def codeFormatSettings = new XmlParser().parseText('''
-  <component name="CodeStyleSettingsManager">
-    <option name="PER_PROJECT_SETTINGS">
-      <value>
-        <option name="USE_SAME_INDENTS" value="true" />
-        <option name="RIGHT_MARGIN" value="200" />
-        <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
-        <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
-        <option name="JD_P_AT_EMPTY_LINES" value="false" />
-        <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
-        <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
-        <option name="JD_KEEP_EMPTY_RETURN" value="false" />
-        <option name="WRAP_COMMENTS" value="true" />
-        <option name="IF_BRACE_FORCE" value="3" />
-        <option name="DOWHILE_BRACE_FORCE" value="3" />
-        <option name="WHILE_BRACE_FORCE" value="3" />
-        <option name="FOR_BRACE_FORCE" value="3" />
-        <ADDITIONAL_INDENT_OPTIONS fileType="groovy">
-          <option name="INDENT_SIZE" value="2" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="java">
-          <option name="INDENT_SIZE" value="4" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="js">
-          <option name="INDENT_SIZE" value="4" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="jsp">
-          <option name="INDENT_SIZE" value="4" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="php">
-          <option name="INDENT_SIZE" value="4" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="scala">
-          <option name="INDENT_SIZE" value="2" />
-          <option name="CONTINUATION_INDENT_SIZE" value="2" />
-          <option name="TAB_SIZE" value="2" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="sql">
-          <option name="INDENT_SIZE" value="2" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-        <ADDITIONAL_INDENT_OPTIONS fileType="xml">
-          <option name="INDENT_SIZE" value="4" />
-          <option name="CONTINUATION_INDENT_SIZE" value="8" />
-          <option name="TAB_SIZE" value="4" />
-          <option name="USE_TAB_CHARACTER" value="false" />
-          <option name="SMART_TABS" value="false" />
-          <option name="LABEL_INDENT_SIZE" value="0" />
-          <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-          <option name="USE_RELATIVE_INDENTS" value="false" />
-        </ADDITIONAL_INDENT_OPTIONS>
-      </value>
-    </option>
-    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
-  </component>
-''')
-        node.append(codeFormatSettings)
-    }
-
-    whenConfigured { project ->
-        project.jdk.languageLevel = 'JDK_1_5'
-    }
-}
-
-ideaWorkspace {
-    withXml { provider ->
-        Node node = provider.asNode()
-
-        Node runManagerConfig = node.component.find { it.'@name' == 'RunManager' }
-
-        // Add int test configuration to JUnit defaults
-        Node runConfig = runManagerConfig.configuration.find { it.'@type' == 'JUnit'}
-
-        Node vmParameters = runConfig.option.find { it.'@name' == 'VM_PARAMETERS' }
-        vmParameters.'@value' = "\"-DintegTest.gradleHomeDir=${intTestImage.destinationDir}\" -ea -Dorg.gradle.integtest.executer=embedded"
-
-        // Add an application configuration
-        runManagerConfig.'@selected' = 'Application.Gradle'
-        def appConfig = new XmlParser().parseText('''
-    <configuration default="false" name="Gradle" type="Application" factoryName="Application">
-      <extension name="coverage" enabled="false" merge="false" />
-      <option name="MAIN_CLASS_NAME" value="org.gradle.launcher.Main" />
-      <option name="VM_PARAMETERS" value="" />
-      <option name="PROGRAM_PARAMETERS" value="" />
-      <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
-      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
-      <option name="ALTERNATIVE_JRE_PATH" value="" />
-      <option name="ENABLE_SWING_INSPECTOR" value="false" />
-      <option name="ENV_VARIABLES" />
-      <option name="PASS_PARENT_ENVS" value="true" />
-      <module name="launcher" />
-      <envs />
-      <RunnerSettings RunnerId="Debug">
-        <option name="DEBUG_PORT" value="63810" />
-        <option name="TRANSPORT" value="0" />
-        <option name="LOCAL" value="true" />
-      </RunnerSettings>
-      <RunnerSettings RunnerId="Run" />
-      <ConfigurationWrapper RunnerId="Debug" />
-      <ConfigurationWrapper RunnerId="Run" />
-      <method />
-    </configuration>
-''')
-        runManagerConfig.append(appConfig)
-    }
-}
-
-// Exclude resource directories from compilation and add them back in as classpath resources
-
-ideaProject {
-    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()
-        resourceDirs.each {
-            exclude.appendNode('directory', [url: "file://\$PROJECT_DIR\$/${rootProject.relativePath(it)}", includeSubdirectories: true])
-        }
-    }
-}
-
-configure(groovyProjects()) {
-    ideaModule {
-        scopes.RUNTIME.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files { sourceSets.main.resources.srcDirs })))
-        scopes.TEST.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files { sourceSets*.resources*.srcDirs })))
-    }
-    eclipseClasspath {
-        plusConfigurations.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files { sourceSets*.resources*.srcDirs*.findAll { it.isDirectory() }} )))
-        whenConfigured { classpath ->
-            classpath.entries.removeAll { it instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder && it.path.endsWith('/resources') }
-        }
-    }
-}
+apply from: "gradle/idea.gradle"
+apply from: "gradle/eclipse.gradle"
 
 configurations {
     dists
@@ -355,9 +207,6 @@ configurations {
     plugins {
         visible = false
     }
-    coreImpl {
-        visible = false
-    }
     testRuntime {
         extendsFrom runtime
         extendsFrom plugins
@@ -365,9 +214,9 @@ configurations {
 }
 
 dependencies {
-    runtime runtimeProjects()
+    runtime project(':launcher')
     plugins pluginProjects()
-    coreImpl project(':coreImpl')
+    plugins project(':coreImpl')
 }
 
 evaluationDependsOn(':docs')
@@ -377,19 +226,33 @@ clean.dependsOn subprojects.collect { "$it.path:clean" }
 
 task check(overwrite: true, dependsOn: groovyProjects()*.check)
 check.dependsOn ':docs:checkstyleApi'
+configure(groovyProjects()) {
+    check.dependsOn ":docs:checkstyleApi"
+}
+
 task test(overwrite: true, dependsOn: groovyProjects()*.test)
 task uploadArchives(dependsOn: publishedProjects()*.uploadArchives)
 task publishLocalArchives(dependsOn: publishedProjects()*.publishLocalArchives)
 
-zipRootFolder = "$archivesBaseName-${-> version}"
+task aggregateTestReports(type: TestReportAggregator) {
+    testReportDir = reporting.file("tests")
+    testResultsDir = file("${buildDir}/test-results")
+    projects = subprojects
+}
+
+ext.zipRootFolder = "$archivesBaseName-${-> version}"
 
-binDistImage = copySpec {
+ext.binDistImage = copySpec {
     from('src/toplevel') {
+        exclude 'media/**'
         expand(version: version)
     }
-    from project(':docs').distDocs.destFile
+    from('src/toplevel') {
+        include 'media/**'
+    }
+    from project(':docs').outputs.distDocs
     into('bin') {
-        from startScriptsDir
+        from { project(':launcher').startScripts.outputs.files }
         fileMode = 0755
     }
     into('lib') {
@@ -397,33 +260,30 @@ binDistImage = copySpec {
         into('plugins') {
             from configurations.plugins - configurations.runtime
         }
-        into('core-impl') {
-            from configurations.coreImpl - configurations.runtime
-        }
     }
 }
 
-allDistImage = copySpec {
+ext.allDistImage = copySpec {
     with binDistImage
     into('src') {
         from groovyProjects().collect {project -> project.sourceSets.main.allSource }
     }
     into('docs') {
-        from project(':docs').docsDir
+        from project(':docs').outputs.docs
     }
     into('samples') {
-        from project(':docs').samplesDir
+        from project(':docs').outputs.samples
     }
 }
 
-task allZip(type: Zip, dependsOn: ['launcher:startScripts', 'docs:docs', 'docs:samples']) {
+task allZip(type: Zip) {
     classifier = 'all'
     into(zipRootFolder) {
         with allDistImage
     }
 }
 
-task binZip(type: Zip, dependsOn: ['launcher:startScripts', 'docs:distDocs']) {
+task binZip(type: Zip) {
     classifier = 'bin'
     into(zipRootFolder) {
         with binDistImage
@@ -446,7 +306,6 @@ task srcZip(type: Zip) {
             include 'gradle/'
             include 'src/'
             include '*.gradle'
-            include 'gradle.properties'
             include 'wrapper/'
             include 'gradlew.bat'
         }
@@ -460,37 +319,29 @@ artifacts {
 }
 
 task intTestImage(type: Sync) {
-    dependsOn allZip.taskDependencies
-    with allDistImage
-    integTestGradleHome = file("$buildDir/integ test")
+    dependsOn "launcher:startScripts"
+    with binDistImage
+    ext.integTestGradleHome = file("$buildDir/integ test")
     into integTestGradleHome
     doLast { task ->
         ant.chmod(dir: "$integTestGradleHome/bin", perm: "ugo+rx", includes: "**/*")
-        WrapperProjectCreator.createProject(new File(integTestGradleHome, 'samples'), binZip.archivePath.parentFile, version.toString())
     }
 }
 
-def isDevBuild() {
-    gradle.taskGraph.hasTask(developerBuild)
-}
-
-def isCIBuild() {
-    gradle.taskGraph.hasTask(ciBuild)
-}
-
-def isCommitBuild() {
-    gradle.taskGraph.hasTask(commitBuild)
+gradle.taskGraph.whenReady {
+    if (([isFinalReleaseBuild(), isNightlyBuild(), isRcBuild()].findAll { it }).size() > 1) {
+        throw new GradleException("This appears to be more than one type of release: final - ${isFinalReleaseBuild()}, rc - ${isRcBuild()}, nightly - ${isNightlyBuild() }")
+    }
 }
 
-def guessMaxForks() {
+def guessMaxForks(project) {
+    if (project.hasProperty("maxParallelForks")) {
+        return Integer.valueOf(project.getProperty("maxParallelForks"))
+    }
     int processors = Runtime.runtime.availableProcessors()
     return Math.max(2, (int) (processors / 2))
 }
 
-task testedDists(dependsOn: [assemble, check, integTests])
-
-task nightlyBuild(dependsOn: [clean, testedDists, ':docs:uploadDocs'])
-
 task install(type: Install) {
     description = 'Installs the minimal distribution into directory $gradle_installPath'
     group = 'build'
@@ -507,29 +358,30 @@ task installAll(type: Install) {
     installDirProperyName = 'gradle_installPath'
 }
 
-uploadDists {
-    dependsOn testedDists
-    uploadDescriptor = false
-    doFirst {
-        repositories {
-            ivy {
-                name = 'gradleReleases'
-                artifactPattern "${version.distributionUrl}/[artifact]-[revision](-[classifier]).[ext]"
-                userName = artifactoryUserName
-                password = artifactoryUserPassword
-            }
-        }
-    }
+task testedDists(dependsOn: [check]) {
+    outputs.files configurations.dists.allArtifacts.files
 }
 
 gradle.taskGraph.whenReady {graph ->
-    if (graph.hasTask(uploadDists) || graph.hasTask(uploadArchives)) {
+    if (graph.hasTask(uploadArchives)) {
         // check properties defined and fail early
         artifactoryUserName
         artifactoryUserPassword
     }
 }
 
+task quickCheck {
+    def i = gradle.startParameter.taskNames.findIndexOf { it ==~ /(?i):?(quickCheck|qC)/ }
+    if (i >= 0) {
+        gradle.startParameter.taskNames.addAll(i, ["doc:checkstyleApi", "codeQuality", "classes", "test"])
+    }
+    doFirst {
+        if (i < 0) {
+            throw new GradleException("Due to the way it is implemented, the 'quickCheck' task has to be invoked directly, and its name can only be abbreviated to 'qC'.")
+        }
+    }
+}
+
 task developerBuild {
     description = 'Builds distributions and runs pre-checkin checks'
     group = 'build'
@@ -546,68 +398,143 @@ task commitBuild {
     dependsOn testedDists
 }
 
+task nightlyVersion
+
+task checkJavaVersion << {
+    assert Jvm.current().isJava7() : "Must use a Java 7 compatible JVM to perform this build. Current JVM is ${Jvm.current()}"
+}
+
+task nightlyBuild {
+    description = 'Nightly build performed by the CI server'
+    dependsOn checkJavaVersion, nightlyVersion, testedDists, "uploadAll"
+}
+
 gradle.taskGraph.whenReady {graph ->
     if (graph.hasTask(ciBuild)) {
-        subprojects { reportsDirName = "$rootProject.reportsDir/${path.replaceFirst(':', '').replaceAll(':', '.')}" }
+        subprojects { reporting.baseDir "$rootProject.reporting.baseDir/${path.replaceFirst(':', '').replaceAll(':', '.')}" }
     }
 }
 
 // A marker task which causes the release version to be used when it is present in the task graph
 task releaseVersion
+//TODO SF - this task name is inconsistent because other releaseXxx tasks actually upload some content somewhere. Should be called something like 'markReleaseVersion'
 
-task tag {
-    doLast {
-        def git = new Git(project)
-        git.checkNoModifications()
-        git.tag("REL_$version", "Release $version")
-        git.branch("RB_$version")
-    }
+// A marker task which indicates that we are building an rc of some sort
+task rcVersion
+
+//TODO RG - we depend here on the alphabetical order of dependent task execution. We Should change that to fail early if javaversion is inappropriate
+task rc {
+    description "Builds a release candidate for the next release"
+    dependsOn checkJavaVersion, rcVersion, testedDists, "uploadAll"
 }
 
+task tag(type: Tag)
+
+task testedTag(type: Tag, dependsOn: testedDists)
+
 task releaseArtifacts {
     description = 'Builds the release artifacts'
+    //TODO SF - this task name is inconsistent because other releaseXxx tasks actually upload some content somewhere. Should be called something like 'buildReleaseArtifacts'
     group = 'release'
-    dependsOn releaseVersion, assemble, ':docs:websiteDocs'
+    dependsOn releaseVersion, assemble
 }
 
+//TODO RG - we depend here on the alphabetical order of dependent task execution. We Should change that to fail early if javaversion is inappropriate
 task release {
     description = 'Builds, tests and uploads the release artifacts'
     group = 'release'
-    dependsOn releaseVersion, tag, releaseArtifacts, testedDists, uploadArchives, uploadDists, ':docs:uploadDocs'
+    dependsOn releaseVersion, checkJavaVersion, testedTag, releaseArtifacts, testedDists, 'uploadAll'
+    doLast {
+        releases.incrementNextVersion()
+    }
 }
 
-task wrapper(type: Wrapper) {
-    gradleVersion = '1.0-milestone-2'
+task incrementNextVersion << {
+    releases.incrementNextVersion()
 }
 
-def groovyProjects() {
-    subprojects.findAll { project -> project.name != 'docs' }
+task uploadAll {
+    description = 'Uploads binaries, sources and documentation. Does not upload the website! Useful when release procedure breaks at upload and only upload tasks should executed again'
+    dependsOn uploadArchives, "website:uploadDistributions", "website:uploadDocs", "website:pushReleasesXml"
 }
 
-def publishedProjects() {
-    return [project(':core'), project(':toolingApi'), project(':wrapper')]
+def wrapperUpdateTask = { name, label ->
+    task "${name}Wrapper"(type: Wrapper) {
+        doFirst {
+            def version = new groovy.json.JsonSlurper().parseText(new URL("http://services.gradle.org/versions/$label").text)
+            if (version.empty) {
+                throw new GradleException("Cannot update wrapper to '${label}' version as there is currently no version of that label")
+            }
+            println "updating wrapper to $label version: $version.version (downloadUrl: $version.downloadUrl)"
+            distributionUrl version.downloadUrl
+        }
+        doLast {
+            def jvmOpts = "-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8"
+            inputs.property("jvmOpts", jvmOpts)
+            def optsEnvVar = "DEFAULT_JVM_OPTS"
+            scriptFile.write scriptFile.text.replace("$optsEnvVar=\"\"", "$optsEnvVar=\"$jvmOpts\"")
+            batchScript.write batchScript.text.replace("set $optsEnvVar=", "set $optsEnvVar=$jvmOpts")
+        }
+    }
+}
+
+wrapperUpdateTask("nightly", "nightly")
+wrapperUpdateTask("rc", "release-candidate")
+wrapperUpdateTask("current", "current")
+
+def groovyProjects() {
+    subprojects.findAll { !(it.name in ["docs", "website"]) }
 }
 
-def runtimeProjects() {
-    groovyProjects() - pluginProjects() - [project(':integTest'), project(':coreImpl')]
+def publishedProjects() {
+    [project(':core'), project(':toolingApi'), project(':wrapper'), project(':baseServices')]
 }
 
 def pluginProjects() {
-    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'ide', 'announce', 'scala', 'sonar'].collect {
+    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'ide', 'announce', 'scala', 'sonar', 'signing', 'cpp', 'ear'].collect {
         project(it)
     }
 }
 
-switch (System.getProperty("user.name")) {
-    case "pniederw":
-        allprojects {
-            tasks.withType(Test) {
-                maxParallelForks = 1
-                forkEvery = 0
-            }
-        }
-        break
-    default:
-        break
+class TestReportAggregator extends Copy {
+    def projects
+
+    File testResultsDir
+
+    @OutputDirectory
+    File testReportDir
+
+    def TestReportAggregator() {
+        dependsOn { testTasks }
+        from { inputTestResultDirs }
+        into { testResultsDir }
+    }
+
+    @TaskAction
+    def aggregate() {
+        def report = new org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport(testReportDir: testReportDir, testResultsDir: testResultsDir)
+        report.generateReport()
+    }
+
+    def getTestTasks() {
+        projects.collect { it.tasks.withType(Test) }.flatten()
+    }
+
+    def getInputTestResultDirs() {
+        testTasks*.testResultsDir
+    }
+
 }
 
+class Tag extends DefaultTask {
+    @TaskAction
+    def tagNow() {
+        def version = project.version
+        def git = new Git(project)
+        git.checkNoModifications()
+        git.tag("REL_$version", "Release $version")
+        git.branch("RB_$version")
+    }
+}
+
+
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 1fff211..f422e07 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -15,21 +15,38 @@
  */
 
 apply plugin: 'groovy'
-apply plugin: 'code-quality'
+apply plugin: 'checkstyle'
+apply plugin: 'codenarc'
 apply plugin: 'idea'
 apply plugin: 'eclipse'
 
 repositories {
-    mavenRepo(urls: 'http://repo.gradle.org/gradle/libs')
+    maven { url 'http://repo.gradle.org/gradle/libs' }
+    mavenCentral()
 }
 
 dependencies {
     compile gradleApi()
-    compile 'com.google.collections:google-collections:1.0 at jar'
+    compile 'com.google.guava:guava:11.0.1 at jar'
+    compile 'commons-lang:commons-lang:2.6 at jar'
     groovy localGroovy()
-    testCompile 'junit:junit:4.8.2 at jar'
-    testCompile 'org.spockframework:spock-core:0.5-groovy-1.7 at jar', 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2'
+    testCompile 'junit:junit:4.10 at jar'
+    testCompile 'org.spockframework:spock-core:0.6-groovy-1.8 at jar', 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2'
+
+    compile "org.pegdown:pegdown:1.1.0"
+    compile "org.jsoup:jsoup:1.6.3"
+}
+
+configurations {
+	all {
+		resolutionStrategy {
+            //we cannot use 'hours' for now due to java 1.5 problem
+			cacheDynamicVersionsFor 24*60*60, 'seconds'
+			cacheChangingModulesFor 24*60*60, 'seconds'
+		}
+	}
 }
 
 apply from: '../gradle/compile.gradle'
 apply from: '../gradle/codeQuality.gradle'
+apply from: '../gradle/classycle.gradle'
diff --git a/buildSrc/src/main/groovy/org/gradle/build/GenerateReleasesXml.groovy b/buildSrc/src/main/groovy/org/gradle/build/GenerateReleasesXml.groovy
new file mode 100644
index 0000000..1aea477
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/GenerateReleasesXml.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.build
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+class GenerateReleasesXml extends DefaultTask {
+    @Input
+    String getVersion() { return project.version.versionNumber }
+
+    @Input
+    Date getBuildTime() { return project.version.buildTime }
+
+    @OutputFile
+    File destFile
+
+    @InputFile
+    File getSrcFile() { return project.releases.releasesFile }
+
+    @TaskAction
+    def void generate() {
+        logger.info('Write release xml to: {}', destFile)
+        project.releases.generateTo(destFile)
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/Releases.groovy b/buildSrc/src/main/groovy/org/gradle/build/Releases.groovy
new file mode 100644
index 0000000..67f2e85
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/Releases.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.build
+
+import java.util.regex.Pattern
+import java.text.SimpleDateFormat
+import org.gradle.api.Project
+
+class Releases {
+    final File releasesFile
+    final Project project
+
+    Releases(File releasesFile, Project project) {
+        this.releasesFile = releasesFile
+        this.project = project
+    }
+
+    String getNextVersion() {
+        def releases = load()
+        def next = releases.next
+        assert next && next[0].'@version'
+        return next[0].'@version'
+    }
+
+    void generateTo(File resourceFile) {
+        modifyTo(resourceFile) {
+            next.each { remove(it) }
+            current[0].'@version' = this.project.version.versionNumber
+            current[0].'@build-time' = this.formattedBuildTime
+            current[0].'@type' = this.project.version.release ? 'release' : 'snapshot'
+        }
+    }
+
+    String calculateNextVersion(String version) {
+        def matcher = Pattern.compile("(\\d+(\\.\\d+)*?\\.)(\\d+)((-\\w+-)(\\d)[a-z]?)?").matcher(version)
+        if (!matcher.matches()) {
+            throw new RuntimeException("Cannot determine the next version after '$version'")
+        }
+        if (!matcher.group(4)) {
+            def minor = matcher.group(3) as Integer
+            return "${matcher.group(1)}${minor+1}-milestone-1"
+        }
+        def minor = matcher.group(6) as Integer
+        return "${matcher.group(1)}${matcher.group(3)}${matcher.group(5)}${minor+1}"
+    }
+
+    void incrementNextVersion() {
+        modifyTo(releasesFile) {
+            def nextRelease = next[0]
+            assert nextRelease && nextRelease.'@version'
+            def thisRelease = nextRelease.'@version'
+            nextRelease. at version = this.calculateNextVersion(thisRelease)
+            def currentRelease = current[0]
+            assert currentRelease
+            currentRelease + {
+                release(version: thisRelease, "build-time": this.formattedBuildTime)
+            }
+        }
+    }
+
+    private String getFormattedBuildTime() {
+        return new SimpleDateFormat("yyyyMMddHHmmssZ").format(project.version.buildTime)
+    }
+
+    private load() {
+        assert releasesFile.exists()
+        new XmlParser().parse(releasesFile)
+    }
+
+    public modifyTo(File destination, Closure modifications) {
+        def releases = load()
+        project.configure(releases, modifications)
+        destination.parentFile.mkdirs()
+        destination.createNewFile()
+        destination.withPrintWriter { writer -> new XmlNodePrinter(writer, "  ").print(releases) }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/Version.groovy b/buildSrc/src/main/groovy/org/gradle/build/Version.groovy
index 5631deb..56c533e 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/Version.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/Version.groovy
@@ -17,14 +17,31 @@ package org.gradle.build
 
 import java.text.SimpleDateFormat
 import org.gradle.api.GradleException
+import org.gradle.api.Project
 
 class Version {
-    String versionNumber
-    Date buildTime
-    Boolean release = null
 
-    def Version(project) {
-        this.versionNumber = project.nextVersion
+    enum Type {
+        ADHOC({ !it.isReleaseBuild() }, { it }), 
+        NIGHTLY({ it.isNightlyBuild() }, { "nightly" }), 
+        RC({ it.isRcBuild() }, { "release-candidate" }), 
+        FINAL({ it.isFinalReleaseBuild() }, { it })
+        
+        final Closure detector
+        final Closure labelProvider
+        
+        Type(Closure detector, Closure labelProvider) {
+            this.detector = detector
+            this.labelProvider = labelProvider
+        }
+    }
+
+    private final Closure versionNumberProvider
+    final Date buildTime
+    private final Closure typeProvider
+
+    static forProject(Project project) {
+        def versionNumber = project.releases.nextVersion
         File timestampFile = new File(project.buildDir, 'timestamp.txt')
         if (timestampFile.isFile()) {
             boolean uptodate = true
@@ -42,38 +59,69 @@ class Version {
             timestampFile.parentFile.mkdirs()
             timestampFile.createNewFile()
         }
-        buildTime = new Date(timestampFile.lastModified())
-
-        project.gradle.taskGraph.whenReady {graph ->
-            if (graph.hasTask(':releaseVersion')) {
-                release = true
-            } else {
-                this.versionNumber += "-" + getTimestamp()
-                release = false
+        def buildTime = createDateFormat().format(new Date(timestampFile.lastModified()))
+
+        def type = null
+        project.gradle.taskGraph.whenReady { graph ->
+            type = Type.values().find { it.detector(project) }
+            if (type != Type.FINAL) {
+                versionNumber += "-" + buildTime
+            }
+        }
+
+        def typeProvider = {
+            if (type == null) {
+                throw new GradleException("Can't determine whether the type of version for this build before the task graph is populated")
             }
+            type
         }
+
+        new Version({ versionNumber }, buildTime, typeProvider)
+    }
+
+    Version(Closure versionNumberProvider, String buildTime, Closure typeProvider) {
+        this(versionNumberProvider, createDateFormat().parse(buildTime), typeProvider)
+    }
+
+    Version(Closure versionNumberProvider, Date buildTime, Closure typeProvider) {
+        this.versionNumberProvider = versionNumberProvider
+        this.buildTime = buildTime
+        this.typeProvider = typeProvider
+    }
+
+    static createDateFormat() {
+        new SimpleDateFormat('yyyyMMddHHmmssZ')
     }
 
     String toString() {
         versionNumber
     }
 
+    String getVersionNumber() {
+        versionNumberProvider()
+    }
+
     String getTimestamp() {
-        new SimpleDateFormat('yyyyMMddHHmmssZ').format(buildTime)
+        createDateFormat().format(buildTime)
     }
 
     boolean isRelease() {
-        if (release == null) {
-            throw new GradleException("Can't determine whether this is a release build before the task graph is populated")
-        }
-        return release
+        type == Type.FINAL
     }
 
+    Type getType() {
+        typeProvider()
+    }
+
+    String getLabel() {
+        type.labelProvider(versionNumber)
+    }
+    
     String getDistributionUrl() {
         if (release) {
             'https://gradle.artifactoryonline.com/gradle/distributions'
         } else {
-            'https://gradle.artifactoryonline.com/gradle/distributions/gradle-snapshots'
+            'https://gradle.artifactoryonline.com/gradle/distributions-snapshots'
         }
     }
 
@@ -84,4 +132,20 @@ class Version {
             'https://gradle.artifactoryonline.com/gradle/libs-snapshots-local'
         }
     }
+
+    def docUrl(docLabel) {
+        "http://www.gradle.org/doc/${-> release ? 'current' : label}/$docLabel"
+    }
+
+    def getJavadocUrl() {
+        docUrl("javadoc")
+    }
+
+    def getGroovydocUrl() {
+        docUrl("groovydoc")
+    }
+
+    def getDsldocUrl() {
+        docUrl("dsl")
+    }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy
index 034703c..a1d99ca 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy
@@ -16,7 +16,6 @@
 package org.gradle.build.docs
 
 import groovy.xml.DOMBuilder
-import groovy.xml.dom.DOMUtil
 import org.gradle.api.file.FileVisitDetails
 import org.gradle.api.tasks.SourceTask
 import org.gradle.api.tasks.TaskAction
@@ -25,6 +24,7 @@ import javax.xml.parsers.DocumentBuilderFactory
 import org.w3c.dom.Element
 import groovy.xml.dom.DOMCategory
 import org.gradle.api.tasks.OutputFile
+import groovy.xml.XmlUtil
 
 /**
  * Generates a chapter containing a summary of the readme files for the samples.
@@ -113,7 +113,7 @@ class AssembleSamplesDocTask extends SourceTask {
             }
 
             destFile.withOutputStream {OutputStream stream ->
-                DOMUtil.serialize(doc.documentElement, stream)
+                XmlUtil.serialize(doc.documentElement, stream)
             }
         }
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java b/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java
new file mode 100644
index 0000000..07beede
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/DocGenerationException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.build.docs;
+
+import org.gradle.api.internal.Contextual;
+
+ at Contextual
+public class DocGenerationException extends RuntimeException {
+    public DocGenerationException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy
new file mode 100755
index 0000000..2ddf336
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/Docbook2XHtml.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 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.build.docs
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.util.ClasspathUtil
+import org.gradle.api.tasks.*
+import org.gradle.api.logging.LogLevel
+
+class Docbook2Xhtml extends SourceTask {
+    @InputFiles
+    FileCollection classpath
+
+    @OutputFile @Optional
+    File destFile
+
+    @OutputDirectory @Optional
+    File destDir
+
+    @InputDirectory
+    File stylesheetsDir
+
+    String stylesheetName
+
+    @InputFiles @Optional
+    FileCollection resources
+
+    @TaskAction
+    def transform() {
+        if (!((destFile != null) ^ (destDir != null))) {
+            throw new InvalidUserDataException("Must specify exactly 1 of output file or dir.")
+        }
+
+        logging.captureStandardOutput(LogLevel.INFO)
+        logging.captureStandardError(LogLevel.INFO)
+
+        source.visit { FileVisitDetails fvd ->
+            if (fvd.isDirectory()) {
+                return
+            }
+
+            File result
+            if (destFile) {
+                result = destFile
+            } else {
+                File outFile = fvd.relativePath.replaceLastName(fvd.file.name.replaceAll('.xml$', '.html')).getFile(destDir)
+                outFile.parentFile.mkdirs()
+                result = outFile
+            }
+            project.javaexec {
+                main = XslTransformer.name
+                args new File(stylesheetsDir, stylesheetName).absolutePath
+                args fvd.file.absolutePath
+                args result.absolutePath
+                args destDir ?: ""
+                jvmArgs '-Xmx256m'
+                classpath ClasspathUtil.getClasspathForClass(XslTransformer)
+                classpath this.classpath
+                classpath new File(stylesheetsDir, 'extensions/xalan27.jar')
+                systemProperty 'xslthl.config', new File("$stylesheetsDir/highlighting/xslthl-config.xml").toURI()
+                systemProperty 'org.apache.xerces.xni.parser.XMLParserConfiguration', 'org.apache.xerces.parsers.XIncludeParserConfiguration'
+            }
+        }
+
+        if (resources) {
+            project.copy {
+                into this.destDir ?: destFile.parentFile
+                from resources
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy
index f320b60..812b563 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy
@@ -47,7 +47,7 @@ public class ExtractSnippetsTask extends SourceTask {
 
                 destFile.parentFile.mkdirs()
 
-                if (['.jar', '.zip'].find{ name.endsWith(it) }) {
+                if (['.jar', '.zip', '.gpg'].find{ name.endsWith(it) }) {
                     destFile.withOutputStream { it.write(srcFile.readBytes()) }
                     return
                 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
index 2b36e8d..cb0421b 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
@@ -20,7 +20,6 @@ package org.gradle.build.docs
 import groovy.xml.dom.DOMCategory
 import org.gradle.api.DefaultTask
 import org.gradle.api.InvalidUserDataException
-import org.gradle.api.file.FileCollection
 import org.gradle.build.docs.dsl.ClassLinkMetaData
 import org.gradle.build.docs.dsl.LinkMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
@@ -42,14 +41,12 @@ import org.gradle.api.tasks.*
 public class UserGuideTransformTask extends DefaultTask {
     @Input
     String getVersion() { return project.version.toString() }
-    @Input
-    String javadocUrl
-    @Input
-    String groovydocUrl
-    @Input
-    String dsldocUrl
-    @Input
-    String websiteUrl
+
+    def javadocUrl
+    def groovydocUrl
+    def dsldocUrl
+    def websiteUrl
+
     @InputFile
     File sourceFile
     @InputFile
@@ -60,14 +57,28 @@ public class UserGuideTransformTask extends DefaultTask {
     File snippetsDir
     @Input
     Set<String> tags = new HashSet()
-    @InputFiles
-    FileCollection classpath;
 
     final SampleElementValidator validator = new SampleElementValidator();
 
+    @Input String getJavadocUrl() {
+        javadocUrl
+    }
+
+    @Input String getGroovydocUrl() {
+        groovydocUrl
+    }
+
+    @Input String getDsldocUrl() {
+        dsldocUrl
+    }
+
+    @Input String getWebsiteUrl() {
+        websiteUrl
+    }
+
     @TaskAction
     def transform() {
-        XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider(classpath)
+        XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider()
         provider.parse(sourceFile)
         transform(provider.document)
         provider.write(destFile)
@@ -122,19 +133,16 @@ public class UserGuideTransformTask extends DefaultTask {
             Element ulinkElement = doc.createElement('ulink')
 
             String href
-            switch (style) {
-                case 'dsldoc':
-                    href = "$dsldocUrl/${className}.html"
-                    break
-                case 'groovydoc':
-                    href = "$groovydocUrl/${className.replace('.', '/')}.html"
-                    break;
-                case 'javadoc':
-                    href = "$javadocUrl/${className.replace('.', '/')}.html"
-                    break;
-                default:
-                    throw new InvalidUserDataException("Unknown api link style '$style'.")
+            if (style == 'dsldoc') {
+                href = "$dsldocUrl/${className}.html"
+            } else if (style == "groovydoc" || style == "javadoc") {
+                def base = style == "groovydoc" ? groovydocUrl : javadocUrl
+                def packageName = classMetaData.packageName
+                href = "$base/${packageName.replace('.', '/')}/${className.substring(packageName.length()+1)}.html"
+            } else {
+                throw new InvalidUserDataException("Unknown api link style '$style'.")
             }
+
             if (linkMetaData.urlFragment) {
                 href = "$href#$linkMetaData.urlFragment"
             }
@@ -164,7 +172,7 @@ public class UserGuideTransformTask extends DefaultTask {
     }
 
     def transformSamples(Document doc) {
-        XIncludeAwareXmlProvider samplesXmlProvider = new XIncludeAwareXmlProvider(classpath)
+        XIncludeAwareXmlProvider samplesXmlProvider = new XIncludeAwareXmlProvider()
         samplesXmlProvider.emptyDoc() << {
             samples()
         }
@@ -200,7 +208,7 @@ public class UserGuideTransformTask extends DefaultTask {
                     exampleElement.appendChild(sourcefileTitle);
 
                     Element programListingElement = doc.createElement('programlisting')
-                    if (file.endsWith('.gradle') || file.endsWith('.groovy')) {
+                    if (file.endsWith('.gradle') || file.endsWith('.groovy') || file.endsWith('.java')) {
                         programListingElement.setAttribute('language', 'java')
                     }
                     else if (file.endsWith('.xml')) {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/XIncludeAwareXmlProvider.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/XIncludeAwareXmlProvider.groovy
index e4ae6ee..57b60bd 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/XIncludeAwareXmlProvider.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/XIncludeAwareXmlProvider.groovy
@@ -27,27 +27,10 @@ import org.w3c.dom.Element
 import org.w3c.dom.Node
 
 class XIncludeAwareXmlProvider {
-    final Iterable<java.io.File> classpath
     Document root
 
-    XIncludeAwareXmlProvider(Iterable<File> classpath) {
-        this.classpath = classpath
-    }
-
     Element parse(File sourceFile) {
-        System.setProperty("org.apache.xerces.xni.parser.XMLParserConfiguration",
-                "org.apache.xerces.parsers.XIncludeParserConfiguration")
-
-        // Set the thread context classloader to pick up the correct XML parser
-        def uris = classpath.collect {it.toURI().toURL()}
-        def classloader = new URLClassLoader(uris as URL[], getClass().classLoader)
-        def oldClassloader = Thread.currentThread().getContextClassLoader()
-        Thread.currentThread().setContextClassLoader(classloader)
-        try {
-            root = parseSourceFile(sourceFile)
-        } finally {
-            Thread.currentThread().setContextClassLoader(oldClassloader)
-        }
+        root = parseSourceFile(sourceFile)
         return root.documentElement
     }
 
@@ -73,8 +56,9 @@ class XIncludeAwareXmlProvider {
     }
     
     private Document parseSourceFile(File sourceFile) {
-        DocumentBuilderFactory factory = Class.forName('com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl', true, Thread.currentThread().contextClassLoader).newInstance()
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
         factory.setNamespaceAware(true)
+        factory.setXIncludeAware(true)
         DocumentBuilder builder = factory.newDocumentBuilder()
         return builder.parse(sourceFile)
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/XslTransformer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/XslTransformer.java
new file mode 100755
index 0000000..ed6a1a7
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/XslTransformer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 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.build.docs;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class XslTransformer {
+    public static void main(String[] args) throws TransformerException, IOException {
+        if (args.length < 3 || args.length > 4) {
+            throw new IllegalArgumentException("USAGE: <style-sheet> <source-file> <dest-file> [dest-dir]");
+        }
+        File stylesheet = new File(args[0]);
+        File source = new File(args[1]);
+        File dest = new File(args[2]);
+        String destDir = "";
+        if (args.length > 3) {
+            destDir = args[3];
+        }
+
+        System.out.format("=> stylesheet %s%n", stylesheet);
+        System.out.format("=> source %s%n", source);
+        System.out.format("=> dest %s%n", dest);
+        System.out.format("=> destDir %s%n", destDir);
+
+        TransformerFactory factory = TransformerFactory.newInstance();
+        Transformer transformer = factory.newTransformer(new StreamSource(stylesheet));
+
+        System.out.format("=> transformer %s (%s)%n", transformer, transformer.getClass().getName());
+
+        if (destDir.length() > 0) {
+            transformer.setParameter("base.dir", destDir + "/");
+        }
+        FileOutputStream outstr = new FileOutputStream(dest);
+        try {
+            transformer.transform(new StreamSource(source), new StreamResult(outstr));
+        } finally {
+            outstr.close();
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassLinkMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassLinkMetaData.java
index b428a01..38a5989 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassLinkMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ClassLinkMetaData.java
@@ -30,15 +30,17 @@ import java.util.Map;
 public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMetaData> {
     private final String className;
     private final String simpleName;
+    private final String packageName;
     private LinkMetaData.Style style;
     private final Map<String, MethodLinkMetaData> methods = new HashMap<String, MethodLinkMetaData>();
 
     public ClassLinkMetaData(ClassMetaData classMetaData) {
         this.className = classMetaData.getClassName();
         this.simpleName = classMetaData.getSimpleName();
+        this.packageName = classMetaData.getPackageName();
         this.style = classMetaData.isGroovy() ? LinkMetaData.Style.Groovydoc : LinkMetaData.Style.Javadoc;
         for (MethodMetaData method : classMetaData.getDeclaredMethods()) {
-            addMethod(method, method.getOverrideSignature(), style);
+            addMethod(method, style);
         }
     }
 
@@ -46,9 +48,16 @@ public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMeta
         return new LinkMetaData(style, simpleName, null);
     }
 
+    public String getPackageName() {
+        return packageName;
+    }
+
     public LinkMetaData getMethod(String method) {
         MethodLinkMetaData methodMetaData = findMethod(method);
-        return new LinkMetaData(methodMetaData.style, String.format("%s.%s()", simpleName, methodMetaData.name), methodMetaData.id);
+        String displayName;
+        String urlFragment = methodMetaData.getUrlFragment(className);
+        displayName = String.format("%s.%s", simpleName, methodMetaData.getDisplayName());
+        return new LinkMetaData(methodMetaData.style, displayName, urlFragment);
     }
 
     private MethodLinkMetaData findMethod(String method) {
@@ -64,11 +73,18 @@ public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMeta
             }
         }
         if (candidates.isEmpty()) {
-            throw new RuntimeException(String.format("No method '%s' found for class '%s'.", method, className));
+            String message = String.format("No method '%s' found for class '%s'.", method, className);
+            message += "\nThis problem may happen when some apilink from docbook template xmls refers to unknown method."
+                    +  "\nExample: <apilink class=\"org.gradle.api.Project\" method=\"someMethodThatDoesNotExist\"/>";
+            throw new RuntimeException(message);
         }
         if (candidates.size() != 1) {
-            throw new RuntimeException(String.format("Found multiple methods called '%s' in class '%s'. Candidates: %s",
-                    method, className, GUtil.join(candidates, ", ")));
+            String message = String.format("Found multiple methods called '%s' in class '%s'. Candidates: %s",
+                    method, className, GUtil.join(candidates, ", "));
+            message += "\nThis problem may happen when some apilink from docbook template xmls is incorrect. Example:"
+                    +  "\nIncorrect: <apilink class=\"org.gradle.api.Project\" method=\"tarTree\"/>"
+                    +  "\nCorrect:   <apilink class=\"org.gradle.api.Project\" method=\"tarTree(Object)\"/>";
+            throw new RuntimeException(message);
         }
         return candidates.get(0);
     }
@@ -81,29 +97,68 @@ public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMeta
         this.style = style;
     }
 
-    public void addMethod(MethodMetaData method, String id, LinkMetaData.Style style) {
-        methods.put(method.getOverrideSignature(), new MethodLinkMetaData(method.getName(), method.getOverrideSignature(), id, style));
+    public void addMethod(MethodMetaData method, LinkMetaData.Style style) {
+        methods.put(method.getOverrideSignature(), new MethodLinkMetaData(method.getName(), method.getOverrideSignature(), style));
+    }
+
+    public void addBlockMethod(MethodMetaData method) {
+        methods.put(method.getOverrideSignature(), new BlockLinkMetaData(method.getName(), method.getOverrideSignature()));
+    }
+
+    public void addGetterMethod(String propertyName, MethodMetaData method) {
+        methods.put(method.getOverrideSignature(), new GetterLinkMetaData(propertyName, method.getName(), method.getOverrideSignature()));
     }
 
     public void attach(ClassMetaDataRepository<ClassLinkMetaData> linkMetaDataClassMetaDataRepository) {
     }
 
     private static class MethodLinkMetaData implements Serializable {
-        private final String name;
-        private final String signature;
-        private final String id;
-        private final LinkMetaData.Style style;
+        final String name;
+        final String signature;
+        final LinkMetaData.Style style;
 
-        private MethodLinkMetaData(String name, String signature, String id, LinkMetaData.Style style) {
+        private MethodLinkMetaData(String name, String signature, LinkMetaData.Style style) {
             this.name = name;
             this.signature = signature;
-            this.id = id;
             this.style = style;
         }
 
+        public String getDisplayName() {
+            return String.format("%s()", name);
+        }
+        
+        public String getUrlFragment(String className) {
+            return style == LinkMetaData.Style.Dsldoc ? String.format("%s:%s", className, signature) : signature;
+        }
+        
         @Override
         public String toString() {
             return signature;
         }
     }
+
+    private static class BlockLinkMetaData extends MethodLinkMetaData {
+        private BlockLinkMetaData(String name, String signature) {
+            super(name, signature, LinkMetaData.Style.Dsldoc);
+        }
+
+        @Override
+        public String getDisplayName() {
+            return String.format("%s{}", name);
+        }
+    }
+
+    private static class GetterLinkMetaData extends MethodLinkMetaData {
+        private final String propertyName;
+
+        private GetterLinkMetaData(String propertyName, String methodName, String signature) {
+            super(methodName, signature, LinkMetaData.Style.Dsldoc);
+            this.propertyName = propertyName;
+        }
+
+        @Override
+        public String getUrlFragment(String className) {
+            return String.format("%s:%s", className, propertyName);
+        }
+    }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
index 03f8c43..a1589b8 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
@@ -36,6 +36,7 @@ import org.gradle.build.docs.dsl.model.TypeMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
 import org.gradle.build.docs.model.SimpleClassMetaDataRepository
 import org.gradle.util.Clock
+import org.gradle.build.docs.DocGenerationException
 
 /**
  * Extracts meta-data from the Groovy and Java source files which make up the Gradle DSL. Persists the meta-data to a file
@@ -49,6 +50,8 @@ class ExtractDslMetaDataTask extends SourceTask {
     def extract() {
         Clock clock = new Clock()
 
+        //parsing all input files into metadata
+        //and placing them in the repository object
         SimpleClassMetaDataRepository<ClassMetaData> repository = new SimpleClassMetaDataRepository<ClassMetaData>()
         int counter = 0
         source.each { File f ->
@@ -56,9 +59,11 @@ class ExtractDslMetaDataTask extends SourceTask {
             counter++
         }
 
+        //updating/modifying the metadata and making sure every type reference across the metadata is fully qualified
+        //so, the superClassName, interafaces and types needed by declared properties and declared methods will have fully qualified name
         TypeNameResolver resolver = new TypeNameResolver(repository)
         repository.each { name, metaData ->
-            resolve(metaData, resolver)
+            fullyQualifyAllTypeNames(metaData, resolver)
         }
         repository.store(destFile)
 
@@ -75,7 +80,7 @@ class ExtractDslMetaDataTask extends SourceTask {
                 }
             }
         } catch (Exception e) {
-            throw new RuntimeException("Could not parse '$sourceFile'.", e)
+            throw new DocGenerationException("Could not parse '$sourceFile'.", e)
         }
     }
 
@@ -119,7 +124,7 @@ class ExtractDslMetaDataTask extends SourceTask {
         visitor.complete()
     }
 
-    def resolve(ClassMetaData classMetaData, TypeNameResolver resolver) {
+    def fullyQualifyAllTypeNames(ClassMetaData classMetaData, TypeNameResolver resolver) {
         try {
             if (classMetaData.superClassName) {
                 classMetaData.superClassName = resolver.resolve(classMetaData.superClassName, classMetaData)
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/LinkMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/LinkMetaData.java
index 38a2a7e..de27580 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/LinkMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/LinkMetaData.java
@@ -18,7 +18,7 @@ package org.gradle.build.docs.dsl;
 import java.io.Serializable;
 
 public class LinkMetaData implements Serializable {
-    enum Style { Javadoc, Groovydoc, Dsldoc }
+    public enum Style { Javadoc, Groovydoc, Dsldoc }
 
     private final Style style;
     private final String displayName;
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/TypeNameResolver.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/TypeNameResolver.java
index 1afb245..7ef8321 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/TypeNameResolver.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/TypeNameResolver.java
@@ -20,7 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.build.docs.dsl.model.ClassMetaData;
 import org.gradle.build.docs.dsl.model.TypeMetaData;
 import org.gradle.build.docs.model.ClassMetaDataRepository;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -59,7 +59,7 @@ public class TypeNameResolver {
         try {
             getClass().getClassLoader().loadClass("groovy.lang.Closure");
         } catch (ClassNotFoundException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/AssembleDslDocTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/AssembleDslDocTask.groovy
index b8aa980..20cb246 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/AssembleDslDocTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/AssembleDslDocTask.groovy
@@ -15,24 +15,24 @@
  */
 package org.gradle.build.docs.dsl.docbook
 
+import groovy.xml.dom.DOMCategory
 import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.OutputFile
+import org.gradle.api.Project
+import org.gradle.api.tasks.InputDirectory
 import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
-import org.gradle.api.file.FileCollection
-import org.gradle.api.tasks.InputFiles
-import org.w3c.dom.Document
-import groovy.xml.dom.DOMCategory
-import org.w3c.dom.Element
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.build.docs.XIncludeAwareXmlProvider
 import org.gradle.build.docs.BuildableDOMCategory
+import org.gradle.build.docs.DocGenerationException
+import org.gradle.build.docs.XIncludeAwareXmlProvider
+import org.gradle.build.docs.dsl.ClassLinkMetaData
+import org.gradle.build.docs.dsl.LinkMetaData
+import org.gradle.build.docs.dsl.model.ClassExtensionMetaData
 import org.gradle.build.docs.dsl.model.ClassMetaData
 import org.gradle.build.docs.model.ClassMetaDataRepository
 import org.gradle.build.docs.model.SimpleClassMetaDataRepository
-import org.gradle.build.docs.dsl.LinkMetaData
-import org.gradle.api.Project
-import org.gradle.build.docs.dsl.ClassLinkMetaData
+import org.w3c.dom.Document
+import org.w3c.dom.Element
 
 /**
  * Generates the docbook source for the DSL reference guide.
@@ -41,8 +41,8 @@ import org.gradle.build.docs.dsl.ClassLinkMetaData
  * <ul>
  * <li>Meta-data extracted from the source by {@link org.gradle.build.docs.dsl.ExtractDslMetaDataTask}.</li>
  * <li>Meta-data about the plugins, in the form of an XML file.</li>
- * <li>A docbook template file containing the introductory material and a list of classes to document.</li>
- * <li>A docbook template file for each class, contained in the {@code classDocbookDir} directory.</li>
+ * <li>{@code sourceFile} - A main docbook template file containing the introductory material and a list of classes to document.</li>
+ * <li>{@code classDocbookDir} - A directory that should contain docbook template for each class referenced in main docbook template.</li>
  * </ul>
  *
  * Produces the following:
@@ -60,38 +60,42 @@ class AssembleDslDocTask extends DefaultTask {
     @InputFile
     File pluginsMetaDataFile
     @InputDirectory
-    File classDocbookDir
+    File classDocbookDir //TODO SF - it would be nice to do some renames, docbookTemplatesDir, destLinksFile
     @OutputFile
     File destFile
     @OutputFile
     File linksFile
-    @InputFiles
-    FileCollection classpath;
 
     @TaskAction
     def transform() {
-        XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider(classpath)
+        XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider()
         provider.parse(sourceFile)
         transformDocument(provider.document)
         provider.write(destFile)
     }
 
-    private def transformDocument(Document document) {
+    private def transformDocument(Document mainDocbookTemplate) {
         ClassMetaDataRepository<ClassMetaData> classRepository = new SimpleClassMetaDataRepository<ClassMetaData>()
         classRepository.load(classMetaDataFile)
         ClassMetaDataRepository<ClassLinkMetaData> linkRepository = new SimpleClassMetaDataRepository<ClassLinkMetaData>()
+        //for every method found in class meta, create a javadoc/groovydoc link
         classRepository.each {name, ClassMetaData metaData ->
             linkRepository.put(name, new ClassLinkMetaData(metaData))
         }
 
         use(DOMCategory) {
             use(BuildableDOMCategory) {
-                Map<String, ExtensionMetaData> extensions = loadPluginsMetaData()
-                DslDocModel model = new DslDocModel(classDocbookDir, document, classpath, classRepository, extensions)
-                def root = document.documentElement
+                Map<String, ClassExtensionMetaData> extensions = loadPluginsMetaData()
+                DslDocModel model = new DslDocModel(classDocbookDir, mainDocbookTemplate, classRepository, extensions)
+                def root = mainDocbookTemplate.documentElement
                 root.section.table.each { Element table ->
                     mergeContent(table, model, linkRepository)
                 }
+                extensions.each { name, plugin ->
+                    plugin.extensionClasses.each { extension ->
+                        generateDocForType(root.ownerDocument, model, linkRepository, model.getClassDoc(extension.extensionClass))
+                    }
+                }
             }
         }
 
@@ -99,20 +103,27 @@ class AssembleDslDocTask extends DefaultTask {
     }
 
     def loadPluginsMetaData() {
-        XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider(classpath)
+        XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider()
         provider.parse(pluginsMetaDataFile)
-        Map<String, ExtensionMetaData> extensions = [:]
+        Map<String, ClassExtensionMetaData> extensions = [:]
         provider.root.plugin.each { Element plugin ->
             def pluginId = plugin.'@id'
             plugin.extends.each { Element e ->
                 def targetClass = e.'@targetClass'
-                def extensionClass = e.'@extensionClass'
                 def extension = extensions[targetClass]
                 if (!extension) {
-                    extension = new ExtensionMetaData(targetClass)
+                    extension = new ClassExtensionMetaData(targetClass)
                     extensions[targetClass] = extension
                 }
-                extension.add(pluginId, extensionClass)
+                def mixinClass = e.'@mixinClass'
+                if (mixinClass) {
+                    extension.addMixin(pluginId, mixinClass)
+                }
+                def extensionClass = e.'@extensionClass'
+                if (extensionClass) {
+                    def extensionId = e.'@id'
+                    extension.addExtension(pluginId, extensionId, extensionClass)
+                }
             }
         }
         return extensions
@@ -121,6 +132,8 @@ class AssembleDslDocTask extends DefaultTask {
     def mergeContent(Element typeTable, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository) {
         def title = typeTable.title[0].text()
 
+        //TODO below checks makes it harder to add new sections
+        //because the new section will work correctly only when the section title ends with 'types' :)
         if (title.matches('(?i).* types')) {
             mergeTypes(typeTable, model, linkRepository)
         } else if (title.matches('(?i).* blocks')) {
@@ -173,26 +186,37 @@ class AssembleDslDocTask extends DefaultTask {
         }
     }
 
-    def mergeType(Element tr, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository) {
-        String className = tr.td[0].text().trim()
+    def mergeType(Element typeTr, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository) {
+        String className = typeTr.td[0].text().trim()
         ClassDoc classDoc = model.getClassDoc(className)
+        generateDocForType(typeTr.ownerDocument, model, linkRepository, classDoc)
+        typeTr.children = {
+            td {
+                link(linkend: classDoc.id) { literal(classDoc.simpleName) }
+            }
+            td(classDoc.description)
+        }
+    }
+
+    def generateDocForType(Document document, DslDocModel model, ClassMetaDataRepository<ClassLinkMetaData> linkRepository, ClassDoc classDoc) {
         try {
-            new ClassDocRenderer(new LinkRenderer(tr.ownerDocument, model)).mergeContent(classDoc)
-            def linkMetaData = linkRepository.get(className)
+            //classDoc renderer renders the content of the class and also links to properties/methods
+            new ClassDocRenderer(new LinkRenderer(document, model)).mergeContent(classDoc)
+            def linkMetaData = linkRepository.get(classDoc.name)
             linkMetaData.style = LinkMetaData.Style.Dsldoc
             classDoc.classMethods.each { methodDoc ->
-                linkMetaData.addMethod(methodDoc.metaData, methodDoc.id, LinkMetaData.Style.Dsldoc)
+                linkMetaData.addMethod(methodDoc.metaData, LinkMetaData.Style.Dsldoc)
             }
-            Element root = tr.ownerDocument.documentElement
-            root << classDoc.classSection
-            tr.children = {
-                td {
-                    link(linkend: classDoc.id) { literal(classDoc.simpleName) }
-                }
-                td(classDoc.description)
+            classDoc.classBlocks.each { blockDoc ->
+                linkMetaData.addBlockMethod(blockDoc.blockMethod.metaData)
+            }
+            classDoc.classProperties.each { propertyDoc ->
+                linkMetaData.addGetterMethod(propertyDoc.name, propertyDoc.metaData.getter)
             }
+            document.documentElement << classDoc.classSection
         } catch (Exception e) {
-            throw new RuntimeException("Failed to generate documentation for class '$className'.", e)
+            throw new DocGenerationException("Failed to generate documentation for class '$className'.", e)
         }
     }
 }
+
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
index 3f11251..fa18579 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
@@ -22,6 +22,9 @@ import org.w3c.dom.Element
 import org.w3c.dom.Node
 import org.gradle.build.docs.dsl.model.MethodMetaData
 import org.w3c.dom.Text
+import org.gradle.build.docs.dsl.model.MixinMetaData
+import org.gradle.build.docs.dsl.model.ClassExtensionMetaData
+import org.gradle.build.docs.dsl.model.ExtensionMetaData
 
 class ClassDoc {
     private final String className
@@ -29,7 +32,7 @@ class ClassDoc {
     private final String simpleName
     final ClassMetaData classMetaData
     private final Element classSection
-    private final ExtensionMetaData extensionMetaData
+    private final ClassExtensionMetaData extensionMetaData
     private final List<PropertyDoc> classProperties = []
     private final List<MethodDoc> classMethods = []
     private final List<BlockDoc> classBlocks = []
@@ -43,7 +46,7 @@ class ClassDoc {
     private List<Element> comment
     private final GenerationListener listener = new DefaultGenerationListener()
 
-    ClassDoc(String className, Element classContent, Document targetDocument, ClassMetaData classMetaData, ExtensionMetaData extensionMetaData, DslDocModel model, JavadocConverter javadocConverter) {
+    ClassDoc(String className, Element classContent, Document targetDocument, ClassMetaData classMetaData, ClassExtensionMetaData extensionMetaData, DslDocModel model, JavadocConverter javadocConverter) {
         this.className = className
         id = className
         simpleName = className.tokenize('.').last()
@@ -135,7 +138,7 @@ class ClassDoc {
         }
 
         ClassDoc superClass = classMetaData.superClassName ? model.getClassDoc(classMetaData.superClassName) : null
-
+        //adding the properties from the super class onto the inheriting class
         Map<String, PropertyDoc> props = new TreeMap<String, PropertyDoc>()
         if (superClass) {
             superClass.getClassProperties().each { propertyDoc ->
@@ -244,15 +247,28 @@ class ClassDoc {
     }
 
     ClassDoc buildExtensions() {
-        extensionMetaData.extensionClasses.keySet().each { String pluginId ->
-            List<ClassDoc> extensionClasses = []
-            extensionMetaData.extensionClasses.get(pluginId).each { String extensionClass ->
-                extensionClasses << model.getClassDoc(extensionClass)
+        def plugins = [:]
+        extensionMetaData.mixinClasses.each { MixinMetaData mixin ->
+            def pluginId = mixin.pluginId
+            def classExtensionDoc = plugins[pluginId]
+            if (!classExtensionDoc) {
+                classExtensionDoc = new ClassExtensionDoc(pluginId, classMetaData)
+                plugins[pluginId] = classExtensionDoc
+            }
+            classExtensionDoc.mixinClasses << model.getClassDoc(mixin.mixinClass)
+        }
+        extensionMetaData.extensionClasses.each { ExtensionMetaData extension ->
+            def pluginId = extension.pluginId
+            def classExtensionDoc = plugins[pluginId]
+            if (!classExtensionDoc) {
+                classExtensionDoc = new ClassExtensionDoc(pluginId, classMetaData)
+                plugins[pluginId] = classExtensionDoc
             }
-            extensionClasses.sort { it.name }
-            classExtensions << new ClassExtensionDoc(pluginId, extensionClasses)
+            classExtensionDoc.extensionClasses[extension.extensionId] = model.getClassDoc(extension.extensionClass)
         }
 
+        classExtensions.addAll(plugins.values())
+        classExtensions.each { extension -> extension.buildMetaData(model) }
         classExtensions.sort { it.pluginId }
 
         return this
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocGenerationException.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocGenerationException.java
new file mode 100644
index 0000000..aa49931
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocGenerationException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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.build.docs.dsl.docbook;
+
+import org.gradle.build.docs.DocGenerationException;
+
+public class ClassDocGenerationException extends DocGenerationException {
+    public ClassDocGenerationException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassExtensionDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassExtensionDoc.groovy
index 59e0dd4..a9bbb8d 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassExtensionDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassExtensionDoc.groovy
@@ -15,33 +15,81 @@
  */
 package org.gradle.build.docs.dsl.docbook
 
+import javax.xml.parsers.DocumentBuilderFactory
+import org.gradle.build.docs.dsl.model.ClassMetaData
+import org.gradle.build.docs.dsl.model.MethodMetaData
+import org.gradle.build.docs.dsl.model.PropertyMetaData
+import org.gradle.build.docs.dsl.model.TypeMetaData
+import org.w3c.dom.Document
+import org.gradle.build.docs.DomBuilder
+
+/**
+ * Represents the documentation model for extensions contributed by a given plugin.
+ */
 class ClassExtensionDoc {
-    private final List<ClassDoc> extensionClass
+    private final Set<ClassDoc> mixinClasses = []
+    private final Map<String, ClassDoc> extensionClasses = [:]
     private final String pluginId
+    private final ClassMetaData targetClass
+    private final List<PropertyDoc> extraProperties = []
+    private final List<BlockDoc> extraBlocks = []
 
-    ClassExtensionDoc(String pluginId, List<ClassDoc> extensionClass) {
+    ClassExtensionDoc(String pluginId, ClassMetaData targetClass) {
         this.pluginId = pluginId
-        this.extensionClass = extensionClass
+        this.targetClass = targetClass
     }
 
     String getPluginId() {
         return pluginId
     }
 
-    List<ClassDoc> getExtensionClasses() {
-        extensionClass
+    Set<ClassDoc> getMixinClasses() {
+        mixinClasses
+    }
+
+    void buildMetaData(DslDocModel model) {
+        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
+        def linkRenderer = new LinkRenderer(doc, model)
+        extensionClasses.each { id, type ->
+            def propertyMetaData = new PropertyMetaData(id, targetClass)
+            propertyMetaData.type = new TypeMetaData(type.name)
+
+            def builder = new DomBuilder(doc, null)
+            builder.para {
+                text("The ")
+                appendChild(linkRenderer.link(propertyMetaData.type, new DefaultGenerationListener()))
+                text(" added by the ${pluginId} plugin.")
+            }
+            def propertyDoc = new PropertyDoc(propertyMetaData, builder.elements, [])
+            extraProperties.add(propertyDoc)
+
+            builder = new DomBuilder(doc, null)
+            builder.para {
+                text("Configures the ")
+                appendChild(linkRenderer.link(propertyMetaData.type, new DefaultGenerationListener()))
+                text(" added by the ${pluginId} plugin.")
+            }
+            def methodMetaData = new MethodMetaData(id, targetClass)
+            methodMetaData.addParameter("configClosure", new TypeMetaData(Closure.name))
+            def methodDoc = new MethodDoc(methodMetaData, builder.elements)
+            extraBlocks.add(new BlockDoc(methodDoc, propertyDoc, propertyMetaData.type, false))
+        }
     }
 
     List<PropertyDoc> getExtensionProperties() {
-        return extensionClass.inject([]) {list, eClass -> eClass.classProperties.inject(list) {x, prop -> x << prop } }.sort { it.name }
+        def properties = mixinClasses.inject([]) {list, eClass -> eClass.classProperties.inject(list) {x, prop -> x << prop } }
+        properties.addAll(extraProperties)
+        return properties.sort { it.name }
     }
 
     List<MethodDoc> getExtensionMethods() {
-        return extensionClass.inject([]) {list, eClass -> eClass.classMethods.inject(list) {x, method -> x << method } }.sort { it.name }
+        return mixinClasses.inject([]) {list, eClass -> eClass.classMethods.inject(list) {x, method -> x << method } }.sort { it.name }
     }
 
     List<BlockDoc> getExtensionBlocks() {
-        return extensionClass.inject([]) {list, eClass -> eClass.classBlocks.inject(list) {x, block -> x << block } }.sort { it.name }
+        def blocks = mixinClasses.inject([]) {list, eClass -> eClass.classBlocks.inject(list) {x, block -> x << block } }
+        blocks.addAll(extraBlocks)
+        return blocks.sort { it.name }
     }
 }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslDocModel.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslDocModel.groovy
index 1b31d02..eddff84 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslDocModel.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslDocModel.groovy
@@ -16,25 +16,23 @@
 package org.gradle.build.docs.dsl.docbook
 
 import org.gradle.build.docs.XIncludeAwareXmlProvider
-
+import org.gradle.build.docs.dsl.TypeNameResolver
+import org.gradle.build.docs.dsl.model.ClassExtensionMetaData
 import org.gradle.build.docs.dsl.model.ClassMetaData
-import org.w3c.dom.Document
 import org.gradle.build.docs.model.ClassMetaDataRepository
-import org.gradle.build.docs.dsl.TypeNameResolver
+import org.w3c.dom.Document
 
 class DslDocModel {
     private final File classDocbookDir
     private final Document document
-    private final Iterable<File> classpath
     private final Map<String, ClassDoc> classes = [:]
     private final ClassMetaDataRepository<ClassMetaData> classMetaData
-    private final Map<String, ExtensionMetaData> extensionMetaData
+    private final Map<String, ClassExtensionMetaData> extensionMetaData
     private final JavadocConverter javadocConverter
 
-    DslDocModel(File classDocbookDir, Document document, Iterable<File> classpath, ClassMetaDataRepository<ClassMetaData> classMetaData, Map<String, ExtensionMetaData> extensionMetaData) {
+    DslDocModel(File classDocbookDir, Document document, ClassMetaDataRepository<ClassMetaData> classMetaData, Map<String, ClassExtensionMetaData> extensionMetaData) {
         this.classDocbookDir = classDocbookDir
         this.document = document
-        this.classpath = classpath
         this.classMetaData = classMetaData
         this.extensionMetaData = extensionMetaData
         javadocConverter = new JavadocConverter(document, new JavadocLinkConverter(document, new TypeNameResolver(classMetaData), new LinkRenderer(document, this), classMetaData))
@@ -62,20 +60,22 @@ class DslDocModel {
             classMetaData = new ClassMetaData(className)
         }
         try {
-            ExtensionMetaData extensionMetaData = extensionMetaData[className]
+            ClassExtensionMetaData extensionMetaData = extensionMetaData[className]
             if (!extensionMetaData) {
-                extensionMetaData = new ExtensionMetaData(className)
+                extensionMetaData = new ClassExtensionMetaData(className)
             }
             File classFile = new File(classDocbookDir, "${className}.xml")
             if (!classFile.isFile()) {
                 throw new RuntimeException("Docbook source file not found for class '$className' in $classDocbookDir.")
             }
-            XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider(classpath)
+            XIncludeAwareXmlProvider provider = new XIncludeAwareXmlProvider()
             def doc = new ClassDoc(className, provider.parse(classFile), document, classMetaData, extensionMetaData, this, javadocConverter)
             doc.mergeContent()
             return doc
+        } catch (ClassDocGenerationException e) {
+            throw e
         } catch (Exception e) {
-            throw new RuntimeException("Could not load the class documentation for class '$className'.", e)
+            throw new ClassDocGenerationException("Could not load the class documentation for class '$className'.", e)
         }
     }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionMetaData.groovy
deleted file mode 100644
index 0ae0bf2..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ExtensionMetaData.groovy
+++ /dev/null
@@ -1,31 +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.build.docs.dsl.docbook
-
-import com.google.common.collect.*
-
-class ExtensionMetaData {
-    final String targetClass
-    final SetMultimap<String, String> extensionClasses = HashMultimap.create()
-
-    ExtensionMetaData(String targetClass) {
-        this.targetClass = targetClass
-    }
-    
-    def void add(String plugin, String extensionClass) {
-        extensionClasses.put(plugin, extensionClass)
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverter.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverter.java
index 05a7bbb..22464d3 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverter.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverter.java
@@ -117,6 +117,7 @@ public class JavadocConverter {
         NodeStack nodes = new NodeStack(document);
         final HtmlGeneratingTokenHandler handler = new HtmlGeneratingTokenHandler(nodes, document);
         handler.add(new HtmlElementTranslatingHandler(nodes, document));
+        handler.add(new PreElementHandler(nodes, document));
         handler.add(new JavadocTagToElementTranslatingHandler(nodes, document));
         handler.add(new HeaderHandler(nodes, document));
         handler.add(new LinkHandler(nodes, linkConverter, classMetaData, listener));
@@ -125,6 +126,7 @@ public class JavadocConverter {
         handler.add(new TableHandler(nodes, document));
         handler.add(new AnchorElementHandler(nodes, document, classMetaData));
         handler.add(new AToLinkTranslatingHandler(nodes, document, classMetaData));
+        handler.add(new AToUlinkTranslatingHandler(nodes, document));
         handler.add(new UnknownJavadocTagHandler(nodes, document, listener));
         handler.add(new UnknownHtmlElementHandler(nodes, document, listener));
 
@@ -436,7 +438,6 @@ public class JavadocConverter {
             this.nodes = nodes;
             this.document = document;
             elementToElementMap.put("p", "para");
-            elementToElementMap.put("pre", "programlisting");
             elementToElementMap.put("ul", "itemizedlist");
             elementToElementMap.put("ol", "orderedlist");
             elementToElementMap.put("li", "listitem");
@@ -444,6 +445,7 @@ public class JavadocConverter {
             elementToElementMap.put("i", "emphasis");
             elementToElementMap.put("b", "emphasis");
             elementToElementMap.put("code", "literal");
+            elementToElementMap.put("tt", "literal");
         }
 
         public boolean onStartElement(String element, Map<String, String> attributes) {
@@ -464,6 +466,37 @@ public class JavadocConverter {
         }
     }
 
+    private static class PreElementHandler implements HtmlElementHandler {
+        private final NodeStack nodes;
+        private final Document document;
+
+        private PreElementHandler(NodeStack nodes, Document document) {
+            this.nodes = nodes;
+            this.document = document;
+        }
+
+        public boolean onStartElement(String element, Map<String, String> attributes) {
+            if (!"pre".equals(element)) {
+                return false;
+            }
+            Element newElement = document.createElement("programlisting");
+            //we're making an assumption that all <pre> elements contain java code
+            //this should mostly be true :)
+            //if it isn't true then the syntax highlighting won't spoil the view too much anyway
+            newElement.setAttribute("language", "java");
+            nodes.push(element, newElement);
+            return true;
+        }
+
+        public void onText(String text) {
+            nodes.appendChild(text);
+        }
+
+        public void onEndElement(String element) {
+            nodes.pop(element);
+        }
+    }
+
     private static class HeaderHandler implements HtmlElementHandler {
         final NodeStack nodes;
         final Document document;
@@ -628,6 +661,38 @@ public class JavadocConverter {
         }
     }
 
+    private static class AToUlinkTranslatingHandler implements HtmlElementHandler {
+        private final NodeStack nodes;
+        private final Document document;
+
+        private AToUlinkTranslatingHandler(NodeStack nodes, Document document) {
+            this.nodes = nodes;
+            this.document = document;
+        }
+
+        public boolean onStartElement(String elementName, Map<String, String> attributes) {
+            if (!elementName.equals("a") || !attributes.containsKey("href")) {
+                return false;
+            }
+            String href = attributes.get("href");
+            if (href.startsWith("#")) {
+                return false;
+            }
+            Element element = document.createElement("ulink");
+            element.setAttribute("url", href);
+            nodes.push(elementName, element);
+            return true;
+        }
+
+        public void onEndElement(String element) {
+            nodes.pop(element);
+        }
+
+        public void onText(String text) {
+            nodes.appendChild(text);
+        }
+    }
+
     private static class ValueHtmlElementHandler implements JavadocTagHandler {
         private final JavadocLinkConverter linkConverter;
         private final ClassMetaData classMetaData;
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocScanner.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocScanner.java
index 56e7c2f..ac81446 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocScanner.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocScanner.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.build.docs.dsl.docbook;
 
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -148,7 +148,7 @@ class JavadocScanner {
                 builder.append("\n");
             }
         } catch (IOException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
         input.insert(pos, builder.toString().trim());
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java
index dd11c54..56f0b99 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java
@@ -97,7 +97,10 @@ public class LinkRenderer {
             return linkElement;
         }
 
-        listener.warning(String.format("Could not generate link for unknown class '%s'", className));
+        //this if is a bit cheesy but 1-letter classname surely means a generic type and the warning will be useless
+        if (className.length() > 1) {
+            listener.warning(String.format("Could not generate link for unknown class '%s'", className));
+        }
         Element element = document.createElement("classname");
         element.appendChild(document.createTextNode(className));
         return element;
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
index 3af0131..8b22a4a 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
@@ -35,6 +35,9 @@ class PropertyDoc {
         this.metaData = propertyMetaData
         id = "${referringClass.className}:$name"
         this.comment = comment
+        if (additionalValues == null) {
+            throw new NullPointerException("additionalValues constructor var is null for referringClass: $referringClass")
+        }
         this.additionalValues = additionalValues
     }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassExtensionMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassExtensionMetaData.groovy
new file mode 100644
index 0000000..d08f30f
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassExtensionMetaData.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.build.docs.dsl.model
+
+class ClassExtensionMetaData {
+    final String targetClass
+    final Set<MixinMetaData> mixinClasses = []
+    final Set<ExtensionMetaData> extensionClasses = []
+
+    ClassExtensionMetaData(String targetClass) {
+        this.targetClass = targetClass
+    }
+
+    def void addMixin(String plugin, String mixinClass) {
+        mixinClasses.add(new MixinMetaData(plugin, mixinClass))
+    }
+
+    def void addExtension(String plugin, String extension, String extensionClass) {
+        extensionClasses.add(new ExtensionMetaData(plugin, extension, extensionClass))
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ExtensionMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ExtensionMetaData.groovy
new file mode 100644
index 0000000..3e34d63
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ExtensionMetaData.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.build.docs.dsl.model
+
+class ExtensionMetaData {
+    final String pluginId
+    final String extensionId
+    final String extensionClass
+
+    ExtensionMetaData(String pluginId, String extensionId, String extensionClass) {
+        this.pluginId = pluginId
+        this.extensionId = extensionId
+        this.extensionClass = extensionClass
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
index 9423e8d..deecfff 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
@@ -114,6 +114,9 @@ public class MethodMetaData implements Serializable, LanguageElement, TypeContai
         return builder.toString();
     }
 
+    /**
+     * Returns the signature of this method, excluding the return type, and converting generic types to their raw types.
+     */
     public String getOverrideSignature() {
         StringBuilder builder = new StringBuilder();
         builder.append(name);
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MixinMetaData.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MixinMetaData.groovy
new file mode 100644
index 0000000..843beed
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MixinMetaData.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.build.docs.dsl.model
+
+class MixinMetaData {
+    final String pluginId
+    final String mixinClass
+
+    MixinMetaData(String pluginId, String mixinClass) {
+        this.pluginId = pluginId
+        this.mixinClass = mixinClass
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/samples/WrapperProjectCreator.groovy b/buildSrc/src/main/groovy/org/gradle/build/samples/WrapperProjectCreator.groovy
deleted file mode 100644
index da4c223..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/samples/WrapperProjectCreator.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.build.samples
-
-import org.gradle.api.tasks.wrapper.Wrapper
-
-/**
- * @author Hans Dockter
- */
-class WrapperProjectCreator {
-    final static String WRAPPER_PROJECT_NAME = 'wrapper-project'
-    final static String WRAPPER_TASK_NAME = 'wrapper'
-    final static String TEST_TASK_NAME = 'hello'
-    final static String TEST_TASK_OUTPUT = 'hello'
-
-    static void createProject(File baseDir, File downloadUrlRoot, String gradleVersion) {
-        String gradleScript = """
-task $WRAPPER_TASK_NAME(type: $Wrapper.name) {
-    gradleVersion = '$gradleVersion'
-    urlRoot = '${downloadUrlRoot.toURI().toURL()}'
-    zipBase = Wrapper.PathBase.PROJECT
-    zipPath = 'wrapper'
-    archiveBase = Wrapper.PathBase.PROJECT
-    archivePath = 'dist'
-    distributionBase = Wrapper.PathBase.PROJECT
-    distributionPath = 'dist'
-}
-
-task $TEST_TASK_NAME << {
-    println '$TEST_TASK_OUTPUT'
-}
-"""
-        File wrapperRoot = new File(baseDir, WRAPPER_PROJECT_NAME)
-        wrapperRoot.mkdirs()
-        new File(wrapperRoot, "build.gradle").withPrintWriter {PrintWriter writer -> writer.write(gradleScript)}
-    }
-
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/startscripts/StartScriptGenerator.groovy b/buildSrc/src/main/groovy/org/gradle/build/startscripts/StartScriptGenerator.groovy
deleted file mode 100644
index 939afee..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/startscripts/StartScriptGenerator.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.build.startscripts
-
-/**
- * @author Hans Dockter
- */
-class StartScriptsGenerator {
-    static void generate(String gradleJarName, File binDir, String projectName) {
-        String unixStartScriptHead = StartScriptsGenerator.getResourceAsStream('unixStartScriptHead.txt').text
-        String unixStartScriptTail = StartScriptsGenerator.getResourceAsStream('unixStartScriptTail.txt').text
-        String windowsStartScriptHead = StartScriptsGenerator.getResourceAsStream('windowsStartScriptHead.txt').text
-        String windowsStartScriptTail = StartScriptsGenerator.getResourceAsStream('windowsStartScriptTail.txt').text
-
-        String gradleHome = 'GRADLE_HOME'
-
-        String unixLibPath = "\$$gradleHome/lib/$gradleJarName"
-        String windowsLibPath = "%$gradleHome%\\lib\\$gradleJarName"
-
-        def unixScript = "$unixStartScriptHead\nCLASSPATH=$unixLibPath\n$unixStartScriptTail"
-        def windowsScript = "$windowsStartScriptHead\nset CLASSPATH=$windowsLibPath\n$windowsStartScriptTail"
-
-        new File(binDir, projectName).withWriter {writer ->
-            writer.write(unixScript)
-        }
-
-        new File(binDir, projectName + ".bat").withWriter {writer ->
-            writer.write(transformIntoWindowsNewLines(windowsScript))
-        }
-    }
-
-    static String transformIntoWindowsNewLines(String s) {
-        StringWriter writer = new StringWriter()
-        s.toCharArray().each {c ->
-            if (c == '\n') {
-                writer.write('\r')
-                writer.write('\n')
-            } else if (c != '\r') {
-                writer.write(c);
-            }
-        }
-        writer.toString()
-    }
-
-}
-
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/Jsoup.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/Jsoup.groovy
new file mode 100644
index 0000000..414e29c
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/Jsoup.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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.jsoup
+
+import org.gradle.api.tasks.SourceTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.OutputFile
+import org.gradle.util.ConfigureUtil
+import org.jsoup.nodes.Document
+
+class Jsoup extends SourceTask {
+    
+    @Input
+    String inputEncoding
+
+    @Input
+    String outputEncoding
+
+    List<Closure> transforms = []
+
+    private destination
+
+    void setDestination(destination) {
+        this.destination = destination
+    }
+
+    @OutputFile
+    File getDestination() {
+        project.file(destination)
+    }
+    
+    @TaskAction 
+    void doTransform() {
+        Document document = org.jsoup.Jsoup.parse(getSource().singleFile.getText(getInputEncoding()))
+        document.outputSettings().charset(getOutputEncoding())
+        getTransforms().each { ConfigureUtil.configure(it, document) }
+        getDestination().setText(document.toString(), getOutputEncoding())
+    }
+
+    void transform(Closure transform) {
+        this.transforms << transform
+    }
+    
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy
new file mode 100644
index 0000000..13f93cb
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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.jsoup
+
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+
+class JsoupFilterReader extends FilterReader {
+
+    Closure withDocument
+
+    JsoupFilterReader(Reader reader) {
+        super(new DeferringReader(reader));
+        this.in.parent = this
+    }
+
+    private static class DeferringReader extends Reader {
+        private final Reader source
+        private Reader delegate
+        private JsoupFilterReader parent
+
+        DeferringReader(Reader source) {
+            this.source = source
+        }
+
+        int read(char[] cbuf, int off, int len) {
+            if (delegate == null) {
+                Document document = Jsoup.parse(source.text)
+                Closure config = parent.withDocument?.clone() as Closure
+                if (config) {
+                    config.resolveStrategy = Closure.DELEGATE_FIRST
+                    config.delegate = document
+                    config(document)
+                }
+
+                delegate = new StringReader(document.toString())
+            }
+
+            delegate.read(cbuf, off, len)
+        }
+
+        void close() {}
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy
new file mode 100644
index 0000000..65039d8
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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.jsoup
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+import java.nio.charset.Charset
+
+class JsoupPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.tasks.withType(Jsoup) { Jsoup task ->
+            task.conventionMapping.with {
+                inputEncoding = { Charset.defaultCharset().name() }
+                outputEncoding = { task.inputEncoding }
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/pegdown/PegDown.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/pegdown/PegDown.groovy
new file mode 100644
index 0000000..d5718ab
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/pegdown/PegDown.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.pegdown
+
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.SourceTask
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.pegdown.Extensions
+import org.gradle.api.InvalidUserDataException
+import org.pegdown.PegDownProcessor
+
+class PegDown extends SourceTask {
+
+    @Input
+    @Optional
+    List<String> options = []
+
+    @Input
+    String inputEncoding
+
+    @Input
+    String outputEncoding
+
+    private destination
+    
+    void setDestination(destination) {
+        this.destination = destination
+    }
+
+    @OutputFile
+    File getDestination() {
+        project.file(destination)
+    }
+
+    @TaskAction
+    void process() {
+        int optionsValue = getCalculatedOptions()
+        PegDownProcessor processor = new PegDownProcessor(optionsValue)
+        String markdown = getSource().singleFile.getText(getInputEncoding())
+        String html = processor.markdownToHtml(markdown)
+        getDestination().write(html, getOutputEncoding())
+    }
+    
+    int getCalculatedOptions() {
+        getOptions().inject(0) { acc, val -> acc | toOptionValue(val) } as int
+    }
+    
+    protected int toOptionValue(String optionName) {
+        String upName = val.toUpperCase()
+        try {
+            Extensions."$upName"
+        } catch (MissingPropertyException e) {
+            throw new InvalidUserDataException("$optionName is not a valid PegDown extension name")
+        }
+    }
+    
+    void options(String... options) {
+        this.options.addAll(options)
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/pegdown/PegDownPlugin.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/pegdown/PegDownPlugin.groovy
new file mode 100644
index 0000000..e694523
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/pegdown/PegDownPlugin.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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.pegdown
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import java.nio.charset.Charset
+
+class PegDownPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.tasks.withType(PegDown) { task ->
+            task.conventionMapping.with {
+                inputEncoding = { Charset.defaultCharset().name() }
+                outputEncoding = { task.inputEncoding }
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/jsoup.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/jsoup.properties
new file mode 100644
index 0000000..086daea
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/jsoup.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.jsoup.JsoupPlugin
\ No newline at end of file
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/pegdown.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/pegdown.properties
new file mode 100644
index 0000000..4840258
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/pegdown.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.pegdown.PegDownPlugin
\ No newline at end of file
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptHead.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptHead.txt
deleted file mode 100644
index 858305d..0000000
--- a/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptHead.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/bash
-
-##############################################################################
-##                                                                          ##
-##  Gradle start up script for UN*X                                         ##
-##                                                                          ##
-##############################################################################
-
-# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
-# GRADLE_OPTS="$GRADLE_OPTS -Xmx512m"
-# JAVA_OPTS="$JAVA_OPTS -Xmx512m"
-
-GRADLE_APP_NAME=Gradle
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# Attempt to set JAVA_HOME if it's not already set.
-if [ -z "$JAVA_HOME" ] ; then
-    if $darwin ; then
-        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
-        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
-    else
-        javaExecutable="`which javac`"
-        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
-        # readlink(1) is not available as standard on Solaris 10.
-        readLink=`which readlink`
-        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
-        javaExecutable="`readlink -f \"$javaExecutable\"`"
-        javaHome="`dirname \"$javaExecutable\"`"
-        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
-        export JAVA_HOME="$javaHome"
-    fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set GRADLE_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/.."
-GRADLE_HOME="`pwd -P`"
-cd "$SAVED"
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptTail.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptTail.txt
deleted file mode 100644
index 738c587..0000000
--- a/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptTail.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-# Determine the Java command to use to start the JVM.
-if [ -z "$JAVACMD" ] ; then
-    if [ -n "$JAVA_HOME" ] ; then
-        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-            # IBM's JDK on AIX uses strange locations for the executables
-            JAVACMD="$JAVA_HOME/jre/sh/java"
-        else
-            JAVACMD="$JAVA_HOME/bin/java"
-        fi
-    else
-        JAVACMD="java"
-    fi
-fi
-if [ ! -x "$JAVACMD" ] ; then
-    die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-if [ -z "$JAVA_HOME" ] ; then
-    warn "JAVA_HOME environment variable is not set"
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
-if $darwin; then
-    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
-# we may also want to set -Xdock:image
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    GRADLE_HOME=`cygpath --path --mixed "$GRADLE_HOME"`
-    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done 
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-GRADLE_APP_BASE_NAME=`basename "$0"`
-
-STARTER_MAIN_CLASS=org.gradle.launcher.GradleMain
-
-exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
-        -classpath "$CLASSPATH" \
-        -Dorg.gradle.appname="$GRADLE_APP_BASE_NAME" \
-        $STARTER_MAIN_CLASS \
-        "$@"
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptHead.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptHead.txt
deleted file mode 100644
index c53c85b..0000000
--- a/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptHead.txt
+++ /dev/null
@@ -1,59 +0,0 @@
- at if "%DEBUG%" == "" @echo off
- at rem ##########################################################################
- at rem                                                                         ##
- at rem  Gradle startup script for Windows                                      ##
- at rem                                                                         ##
- at rem ##########################################################################
-
- at rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
- at rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
- at rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m
- at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.\
-
-set GRADLE_HOME=%DIRNAME%..
-
- at rem Find java.exe
-set JAVA_EXE=java.exe
-if not defined JAVA_HOME goto init
-
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-echo.
-goto fail
-
-:init
- at rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
- at rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
- at rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
- at rem Setup the command line
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptTail.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptTail.txt
deleted file mode 100644
index 23ffb0e..0000000
--- a/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptTail.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-set STARTER_MAIN_CLASS=org.gradle.launcher.GradleMain
-
-set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS%
-
- at rem Execute Gradle
-"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
-
-:end
- at rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
-
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
\ No newline at end of file
diff --git a/buildSrc/src/test/groovy/org/gradle/build/ReleasesTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/ReleasesTest.groovy
new file mode 100644
index 0000000..98bb2c4
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/ReleasesTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2011 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.build
+
+import spock.lang.Specification
+import java.text.SimpleDateFormat
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.api.Project
+import java.text.DateFormat
+
+class ReleasesTest extends Specification {
+    Releases releases
+    Project project
+    File releasesXml
+    final DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssZ")
+    final Date buildTime = new Date()
+
+    def setup() {
+        project = ProjectBuilder.builder().build()
+        releasesXml = project.file('releases.xml')
+        releases = new Releases(releasesXml, project)
+    }
+
+    def "determines next release version from releases xml"() {
+        releasesXml << '''
+<releases>
+    <next version="1.2-preview-45"/>
+    <current version="ignore-me" build-time="ignore-me" type="release"/>
+    <release version="ignore-me" build-time="ignore-me"/>
+</releases>
+'''
+
+        expect:
+        releases.nextVersion == '1.2-preview-45'
+    }
+
+    def "generates resources xml resource"() {
+        def destFile = project.file('dest.xml')
+        releasesXml << '''
+<releases>
+    <next version="1.2-preview-45"/>
+    <current version="ignore-me" build-time="ignore-me"/>
+    <release version="ignore-me" build-time="ignore-me"/>
+</releases>
+'''
+        project.version = [versionNumber: '1.0-milestone-2', buildTime: buildTime]
+
+        when:
+        releases.generateTo(destFile)
+
+        then:
+        destFile.text == """<releases>
+  <current version="1.0-milestone-2" build-time="${dateFormat.format(buildTime)}" type="snapshot"/>
+  <release version="ignore-me" build-time="ignore-me"/>
+</releases>
+"""
+
+        when:
+        project.version.release = true
+        releases.generateTo(destFile)
+
+        then:
+        destFile.text == """<releases>
+  <current version="1.0-milestone-2" build-time="${dateFormat.format(buildTime)}" type="release"/>
+  <release version="ignore-me" build-time="ignore-me"/>
+</releases>
+"""
+    }
+
+    def "calculates next version"() {
+        expect:
+        releases.calculateNextVersion('1.0') == '1.1-milestone-1'
+        releases.calculateNextVersion('1.1.0') == '1.1.1-milestone-1'
+        releases.calculateNextVersion('1.1.2.45') == '1.1.2.46-milestone-1'
+        releases.calculateNextVersion('1.0-milestone-2') == '1.0-milestone-3'
+        releases.calculateNextVersion('1.0-milestone-2a') == '1.0-milestone-3'
+        releases.calculateNextVersion('1.0-rc-2') == '1.0-rc-3'
+    }
+
+    def "updates releases xml To increment next version"() {
+        releasesXml << '''
+<releases>
+  <next version="1.0-milestone-2"/>
+  <current version="${version}" build-time="${build=time}"/>
+  <release version="previous" build-time="20101220123412-0200"/>
+</releases>
+'''
+        project.version = [buildTime: buildTime]
+
+        when:
+        releases.incrementNextVersion()
+
+        then:
+        releasesXml.text == """<releases>
+  <next version="1.0-milestone-3"/>
+  <current version="\${version}" build-time="\${build=time}"/>
+  <release version="1.0-milestone-2" build-time="${dateFormat.format(buildTime)}"/>
+  <release version="previous" build-time="20101220123412-0200"/>
+</releases>
+"""
+    }
+
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
index a4c4972..2be625e 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
@@ -623,6 +623,6 @@ class ExtractDslMetaDataTaskTest extends Specification {
         URL resource = getClass().classLoader.getResource(fileName)
         assert resource != null
         assert resource.protocol == 'file'
-        return new File(resource.path)
+        return new File(resource.toURI())
     }
 }
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocTest.groovy
index 6fafbdb..3a89de8 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/ClassDocTest.groovy
@@ -222,12 +222,12 @@ class ClassDocTest extends XmlSpecification {
         doc.classBlocks[1].multiValued
     }
 
-    def buildsExtensionsForClass() {
+    def buildsExtensionsForClassMixins() {
         ClassMetaData classMetaData = classMetaData()
-        ExtensionMetaData extensionMetaData = new ExtensionMetaData('org.gradle.Class')
-        extensionMetaData.add('a', 'org.gradle.ExtensionA1')
-        extensionMetaData.add('a', 'org.gradle.ExtensionA2')
-        extensionMetaData.add('b', 'org.gradle.ExtensionB')
+        ClassExtensionMetaData extensionMetaData = new ClassExtensionMetaData('org.gradle.Class')
+        extensionMetaData.addMixin('a', 'org.gradle.ExtensionA1')
+        extensionMetaData.addMixin('a', 'org.gradle.ExtensionA2')
+        extensionMetaData.addMixin('b', 'org.gradle.ExtensionB')
         ClassDoc extensionA1 = classDoc('org.gradle.ExtensionA1')
         ClassDoc extensionA2 = classDoc('org.gradle.ExtensionA2')
         ClassDoc extensionB = classDoc('org.gradle.ExtensionB')
@@ -254,10 +254,55 @@ class ClassDocTest extends XmlSpecification {
         doc.classExtensions.size() == 2
 
         doc.classExtensions[0].pluginId == 'a'
-        doc.classExtensions[0].extensionClasses == [extensionA1, extensionA2]
+        doc.classExtensions[0].mixinClasses == [extensionA1, extensionA2] as Set
 
         doc.classExtensions[1].pluginId == 'b'
-        doc.classExtensions[1].extensionClasses == [extensionB]
+        doc.classExtensions[1].mixinClasses == [extensionB] as Set
+    }
+
+    def buildsExtensionsForClassExtensions() {
+        ClassMetaData classMetaData = classMetaData()
+        ClassExtensionMetaData extensionMetaData = new ClassExtensionMetaData('org.gradle.Class')
+        extensionMetaData.addExtension('a', 'n1', 'org.gradle.ExtensionA1')
+        extensionMetaData.addExtension('a', 'n2', 'org.gradle.ExtensionA2')
+        extensionMetaData.addExtension('b', 'n1', 'org.gradle.ExtensionB')
+        ClassDoc extensionA1 = classDoc('org.gradle.ExtensionA1')
+        ClassDoc extensionA2 = classDoc('org.gradle.ExtensionA2')
+        ClassDoc extensionB = classDoc('org.gradle.ExtensionB')
+        _ * docModel.getClassDoc('org.gradle.ExtensionA1') >> extensionA1
+        _ * docModel.isKnownType('org.gradle.ExtensionA1') >> true
+        _ * docModel.getClassDoc('org.gradle.ExtensionA2') >> extensionA2
+        _ * docModel.isKnownType('org.gradle.ExtensionA2') >> true
+        _ * docModel.getClassDoc('org.gradle.ExtensionB') >> extensionB
+        _ * docModel.isKnownType('org.gradle.ExtensionB') >> true
+
+        def content = parse('''<section>
+                <section><title>Properties</title>
+                    <table><thead><tr><td/></tr></thead></table>
+                </section>
+                <section><title>Methods</title>
+                    <table><thead><tr><td/></tr></thead></table>
+                </section>
+            </section>
+        ''')
+
+        when:
+        ClassDoc doc = withCategories {
+            new ClassDoc('org.gradle.Class', content, document, classMetaData, extensionMetaData, docModel, javadocConverter).buildExtensions()
+        }
+
+        then:
+        doc.classExtensions.size() == 2
+
+        doc.classExtensions[0].pluginId == 'a'
+        doc.classExtensions[0].extensionClasses == [n1: extensionA1, n2: extensionA2]
+        doc.classExtensions[0].extensionProperties.size() == 2
+        doc.classExtensions[0].extensionBlocks.size() == 2
+
+        doc.classExtensions[1].pluginId == 'b'
+        doc.classExtensions[1].extensionClasses == [n1: extensionB]
+        doc.classExtensions[1].extensionProperties.size() == 1
+        doc.classExtensions[1].extensionBlocks.size() == 1
     }
 
     def classMetaData(String name = 'org.gradle.Class') {
@@ -289,7 +334,7 @@ class ClassDocTest extends XmlSpecification {
     }
 
     def propertyDoc(Map<String, ?> args = [:], String name) {
-        return new PropertyDoc(classMetaData(), property(name, null), [parse("<para>$name comment</para>")], args.additionalValues)
+        return new PropertyDoc(classMetaData(), property(name, null), [parse("<para>$name comment</para>")], args.additionalValues ?: [])
     }
 
     def method(String name, ClassMetaData classMetaData) {
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverterTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverterTest.groovy
index 54bf335..ff5041b 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverterTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocConverterTest.groovy
@@ -160,7 +160,7 @@ line 2</para>'''
         def result = parser.parse(classMetaData, listener)
 
         then:
-        format(result.docbook) == '''<programlisting>this is some
+        format(result.docbook) == '''<programlisting language="java">this is some
 
 literal code</programlisting>'''
     }
@@ -176,7 +176,7 @@ literal code</programlisting>'''
         def result = parser.parse(classMetaData, listener)
 
         then:
-        format(result.docbook) == '''<para>for example: </para><programlisting>this is some
+        format(result.docbook) == '''<para>for example: </para><programlisting language="java">this is some
 literal code</programlisting><para> does something.
 </para><para>another para.
 </para><itemizedlist><listitem>item1</listitem></itemizedlist>'''
@@ -247,6 +247,16 @@ literal code</programlisting><para> does something.
         _ * classMetaData.className >> 'org.gradle.Class'
     }
 
+    def convertsAnAElementWithAnHref() {
+        _ * classMetaData.rawCommentText >> '<a href="http://gradle.org">some value</a>'
+
+        when:
+        def result = parser.parse(classMetaData, listener)
+
+        then:
+        format(result.docbook) == '<para><ulink url="http://gradle.org">some value</ulink></para>'
+    }
+
     def convertsAnEmElementToAnEmphasisElement() {
         _ * classMetaData.rawCommentText >> '<em>text</em>'
 
@@ -267,6 +277,16 @@ literal code</programlisting><para> does something.
         format(result.docbook) == '''<para><emphasis>text</emphasis> <emphasis>other</emphasis></para>'''
     }
 
+    def convertsTTElementToALiteralElement() {
+        _ * classMetaData.rawCommentText >> '<tt>text</tt>'
+
+        when:
+        def result = parser.parse(classMetaData, listener)
+
+        then:
+        format(result.docbook) == '''<para><literal>text</literal></para>'''
+    }
+
     def convertsHeadingsToSections() {
         _ * classMetaData.rawCommentText >> '''
 <h2>section1</h2>
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index d04b040..a8b4ae4 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -64,9 +64,9 @@
         <module name="MultipleVariableDeclarations"/>
         <module name="NoClone"/>
         <module name="NoFinalizer"/>
-        <module name="RedundantThrows">
-            <property name="allowUnchecked" value="true"/>
-        </module>
+        <!--<module name="RedundantThrows">-->
+            <!--<property name="allowUnchecked" value="true"/>-->
+        <!--</module>-->
         <module name="SimplifyBooleanExpression"/>
         <module name="SimplifyBooleanReturn"/>
         <module name="StringLiteralEquality"/>
@@ -85,7 +85,9 @@
         <module name="LocalFinalVariableName"/>
         <module name="LocalVariableName"/>
         <module name="MemberName"/>
-        <module name="MethodName"/>
+        <module name="MethodName">
+            <property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
+        </module>
         <module name="MethodTypeParameterName"/>
         <module name="PackageName">
             <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
@@ -94,9 +96,14 @@
         <module name="StaticVariableName"/>
         <module name="TypeName"/>
 
+        <!-- to enable SuppressionCommentFilter -->
+        <module name="FileContentsHolder"/>
     </module>
     <module name="RegexpHeader">
         <property name="headerFile" value="${checkstyleConfigDir}/required-header.txt"/>
     </module>
     <module name="FileTabCharacter"/>
+
+    <!-- allows suppressing using the //CHECKSTYLE:ON //CHECKSTYLE:OFF -->
+    <module name="SuppressionCommentFilter"/>
 </module>
\ No newline at end of file
diff --git a/config/checkstyle/required-header.txt b/config/checkstyle/required-header.txt
index 5c9e925..719aef8 100644
--- a/config/checkstyle/required-header.txt
+++ b/config/checkstyle/required-header.txt
@@ -1,7 +1,7 @@
-^/\*$
-^ \* Copyright \d\d\d\d((\s*-\s*\d\d\d\d)|(,\s*\d\d\d\d)+)? the original author or authors.$
-^ \*$
-^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\);$
+^/\*\s*$
+^ \* Copyright \d\d\d\d((\s*-\s*\d\d\d\d)|(,\s*\d\d\d\d)+)? the original author or authors.\s*$
+^ \*\s*$
+^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\);\s*$
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index b210d3b..8f1a87e 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -14,4 +14,9 @@
               files=".*[/\\]subprojects[/\\]plugins[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
     <suppress checks="JavadocPackage"
               files=".*[/\\]subprojects[/\\]scala[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
+
+    <!-- Don't require api docs for projects only used internally -->
+    <suppress checks="Javadoc.*"
+              files=".*[/\\]subprojects[/\\]internal-.+[/\\]src[/\\]main[/\\].+"/>
+
 </suppressions>
\ No newline at end of file
diff --git a/config/codenarc.xml b/config/codenarc.xml
index e130e11..88a18be 100644
--- a/config/codenarc.xml
+++ b/config/codenarc.xml
@@ -22,7 +22,10 @@
         <!--<exclude name='ExplicitCallToOrMethod'/>-->
     <!--</ruleset-ref>-->
     <ruleset-ref path='rulesets/braces.xml'/>
-    <ruleset-ref path='rulesets/imports.xml'/>
+    <ruleset-ref path='rulesets/imports.xml'>
+        <exclude name="ImportFromSunPackages"/>
+        <exclude name="MisorderedStaticImports"/>
+    </ruleset-ref>
     <ruleset-ref path='rulesets/naming.xml'>
         <rule-config name='ClassName'>
             <property name='regex' value='^[A-Z][\$a-zA-Z0-9]*$'/>
@@ -37,5 +40,7 @@
         <rule-config name='VariableName'>
             <property name='finalRegex' value='^[a-z][a-zA-Z0-9]*$'/>
         </rule-config>
+        <exclude name="ConfusingMethodName"/>
+        <exclude name="FactoryMethodName"/>
     </ruleset-ref>
 </ruleset>
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 022a988..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-previousVersion=1.0-milestone-2
-nextVersion=1.0-milestone-3
\ No newline at end of file
diff --git a/gradle/classycle.gradle b/gradle/classycle.gradle
new file mode 100644
index 0000000..1d8b4f8
--- /dev/null
+++ b/gradle/classycle.gradle
@@ -0,0 +1,32 @@
+configurations {
+    classycle
+}
+
+dependencies {
+    classycle 'classycle:classycle:1.4 at jar'
+}
+
+sourceSets.all { sourceSet ->
+    def taskName = sourceSet.getTaskName('classycle', null)
+    task(taskName){
+        def reportFile = reporting.file("classcycle/${sourceSet.name}.xml")
+        inputs.files sourceSet.output
+        outputs.file reportFile
+        doLast {
+            if (!sourceSet.output.classesDir.directory) {
+                return;
+            }
+            ant.taskdef(name: "classycleDependencyCheck", classname: "classycle.ant.DependencyCheckingTask", classpath: configurations.classycle.asPath)
+            reportFile.parentFile.mkdirs()
+            ant.classycleDependencyCheck(reportFile: reportFile, failOnUnwantedDependencies: true, mergeInnerClasses: true,
+                """
+                    check absenceOfPackageCycles > 1 in org.gradle.*
+                """
+            ) {
+                fileset(dir: sourceSet.output.classesDir)
+            }
+        }
+    }
+    check.dependsOn taskName
+    codeQuality.dependsOn taskName
+}
diff --git a/gradle/codeQuality.gradle b/gradle/codeQuality.gradle
index 4e4608e..af0254d 100644
--- a/gradle/codeQuality.gradle
+++ b/gradle/codeQuality.gradle
@@ -1,11 +1,18 @@
-apply plugin: 'code-quality'
+apply plugin: 'checkstyle'
+apply plugin: 'codenarc'
 
 def configDir = new File(buildscript.sourceFile.parentFile.parentFile, 'config')
 
-checkstyleConfigDir = "$configDir/checkstyle"
-checkstyleConfigFileName = new File(checkstyleConfigDir, "checkstyle.xml")
-codeNarcConfigFileName = "$configDir/codenarc.xml"
-checkstyleProperties.checkstyleConfigDir = checkstyleConfigDir
+ext.checkstyleConfigDir = "$configDir/checkstyle"
+
+checkstyle {
+    configFile = new File(checkstyleConfigDir, "checkstyle.xml")
+    configProperties.checkstyleConfigDir = checkstyleConfigDir
+}
+
+codenarc {
+    configFile = new File(configDir, "codenarc.xml")
+}
 
 plugins.withType(GroovyBasePlugin) {
     sourceSets.all { sourceSet ->
@@ -13,7 +20,13 @@ plugins.withType(GroovyBasePlugin) {
             configFile = new File(checkstyleConfigDir, "checkstyle-groovy.xml")
             source sourceSet.allGroovy
             classpath = sourceSet.compileClasspath
-            resultFile = new File(checkstyleResultsDir, "${sourceSet.name}-groovy.xml")
+            reports.xml.destination new File(checkstyle.reportsDir, "${sourceSet.name}-groovy.xml")
         }
     }
 }
+
+task codeQuality {
+    dependsOn tasks.matching { task ->
+        [org.gradle.api.plugins.quality.CodeNarc, org.gradle.api.plugins.quality.Checkstyle].any { it.isInstance(task) }
+    }
+}
\ No newline at end of file
diff --git a/gradle/conventions-dsl.gradle b/gradle/conventions-dsl.gradle
new file mode 100644
index 0000000..d14950a
--- /dev/null
+++ b/gradle/conventions-dsl.gradle
@@ -0,0 +1,18 @@
+/*
+    Provides methods that configure projects with our conventions.
+*/
+
+// Configures the project to use the test fixtures from another project, which by default is core.
+// Note this is not used to provide test fixtures, see gradle/testFixtures.gradle for that
+ext.useTestFixtures = { params = [:] ->
+    def projectPath = params.project ?: ":core"
+    def sourceSet = params.sourceSet ?: "test"
+    def compileConfiguration = sourceSet == "main" ? "compile" : "${sourceSet}Compile"
+    def runtimeConfiguration = sourceSet == "main" ? "runtime" : "${sourceSet}Runtime"
+
+    dependencies {
+        add(compileConfiguration, project(path: projectPath, configuration: "testFixturesUsageCompile"))
+        add(compileConfiguration, project(':internalTesting'))
+        add(runtimeConfiguration, project(path: projectPath, configuration: "testFixturesUsageRuntime"))
+    }
+}
diff --git a/gradle/eclipse.gradle b/gradle/eclipse.gradle
new file mode 100644
index 0000000..c9762f1
--- /dev/null
+++ b/gradle/eclipse.gradle
@@ -0,0 +1,16 @@
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
+
+allprojects {
+	apply plugin: "eclipse"
+}
+
+configure(groovyProjects()) {
+    eclipse {
+        classpath {
+            plusConfigurations.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files { sourceSets*.resources*.srcDirs*.findAll { it.isDirectory() }} )))
+            file.whenMerged { classpath ->
+                classpath.entries.removeAll { it instanceof org.gradle.plugins.ide.eclipse.model.SourceFolder && it.path.endsWith('/resources') }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/gradle/groovyProject.gradle b/gradle/groovyProject.gradle
new file mode 100644
index 0000000..ffe1e17
--- /dev/null
+++ b/gradle/groovyProject.gradle
@@ -0,0 +1,71 @@
+import java.util.jar.Attributes
+
+apply plugin: 'groovy'
+
+archivesBaseName = "gradle-${name.replaceAll("\\p{Upper}") { "-${it.toLowerCase()}" } }"
+
+if (!sourceSets.main.groovy.srcDirs.any{ it.exists() }) {
+    // Remove configurations.groovy from compile and runtime classpaths.
+    configurations {
+        compile.extendsFrom = []
+    }
+}
+
+dependencies {
+    testCompile libraries.junit, libraries.jmock, libraries.spock
+}
+
+apply from: "$rootDir/gradle/compile.gradle"
+
+test {
+    maxParallelForks = guessMaxForks(project)
+    doFirst {
+        if (isCIBuild()) {
+            maxParallelForks = 2
+        }
+    }
+}
+
+tasks.matching { it instanceof Compile || it instanceof GroovyCompile }.all {
+    options.useAnt = false
+}
+
+tasks.withType(Jar).each { jar ->
+    jar.manifest.mainAttributes([
+            (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
+            (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
+    ])
+}
+
+ext.generatedResourcesDir = file("$buildDir/generated-resources/main")
+
+task classpathManifest(type: ClasspathManifest)
+sourceSets.main.output.dir generatedResourcesDir, builtBy: classpathManifest
+
+if (file("src/testFixtures").exists()) {
+    apply from: "$rootDir/gradle/testFixtures.gradle"
+}
+
+if (file("src/integTest").exists()) {
+    apply from: "$rootDir/gradle/integTest.gradle"
+}
+
+class ClasspathManifest extends DefaultTask {
+    @OutputFile
+    File getManifestFile() {
+        return new File(project.generatedResourcesDir, "${project.archivesBaseName}-classpath.properties")
+    }
+
+    @Input
+    Properties getProperties() {
+        def properties = new Properties()
+        properties.runtime = project.configurations.runtime.fileCollection { it instanceof ExternalDependency }.collect {it.name}.join(',')
+        properties.projects = project.configurations.runtime.allDependencies.withType(ProjectDependency).collect {it.dependencyProject.archivesBaseName}.join(',')
+        return properties
+    }
+
+    @TaskAction
+    def generate() {
+        manifestFile.withOutputStream { properties.save(it, 'module definition') }
+    }
+}
\ No newline at end of file
diff --git a/gradle/idea.gradle b/gradle/idea.gradle
new file mode 100644
index 0000000..312f87b
--- /dev/null
+++ b/gradle/idea.gradle
@@ -0,0 +1,263 @@
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
+
+allprojects {
+	apply plugin: "idea"
+}
+
+configure(groovyProjects()) {
+    idea {
+        module {
+            scopes.RUNTIME.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files { sourceSets.main.resources.srcDirs })))
+            scopes.TEST.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files { sourceSets*.resources*.srcDirs })))
+        }
+    }
+}
+
+evaluationDependsOn(':docs')
+
+idea {
+    module {
+        excludeDirs += file('intTestHomeDir')
+        excludeDirs += file('buildSrc/build')
+        excludeDirs += file('buildSrc/.gradle')
+    }
+
+    project {
+        wildcards += ['?*.gradle']
+
+        jdkName = '1.6'
+        languageLevel = '1.5'
+
+        ipr {
+
+            withXml { provider ->
+                // Exclude resource directories from compilation and add them back in as classpath resources
+                def node = provider.asNode()
+                def compilerConfig = node.component.find { it.'@name' == 'CompilerConfiguration' }
+                def exclude = compilerConfig.excludeFromCompile
+                if (exclude) {
+                    compilerConfig.remove(exclude)
+                }
+                exclude = compilerConfig.appendNode('excludeFromCompile')
+                Collection resourceFolder = groovyProjects().collect { project -> project.sourceSets*.resources*.srcDirs }.flatten()
+                resourceFolder.each {
+                    if (it.exists()) {
+                        exclude.appendNode('directory', [url: "file://\$PROJECT_DIR\$/${rootProject.relativePath(it)}", includeSubdirectories: true])
+                    }
+                }
+
+                // exclude jdk7/** from compilation if current jvm is not JDK7
+                if (!Jvm.current().isJava7()) {
+                    Collection sourceDirs = groovyProjects().collect { project -> project.sourceSets*.allSource*.srcDirs }.flatten()
+                    def findAndExcludeJdk7PackageFolder = { packageFolder ->
+                        if (packageFolder.name == 'jdk7') {
+                            exclude.appendNode('directory', [url: "file://\$PROJECT_DIR\$/${rootProject.relativePath(packageFolder)}", includeSubdirectories: true])
+                        }
+                    }
+                    sourceDirs.each { sourceFolder ->
+                        if (sourceFolder.exists()) {
+                            sourceFolder.eachDirRecurse(findAndExcludeJdk7PackageFolder)
+                        }
+                    }
+                }
+
+                // Use git
+                def vcsConfig = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
+                vcsConfig.mapping[0].'@vcs' = 'Git'
+
+                // Set gradle home
+                def gradleSettings = node.appendNode('component', [name: 'GradleSettings'])
+                gradleSettings.appendNode('option', [name: 'SDK_HOME', value: gradle.gradleHomeDir.absolutePath])
+
+                // license header
+                def copyrightManager = node.component.find { it.'@name' == 'CopyrightManager' }
+                copyrightManager. at default = "ASL2"
+                def aslCopyright = copyrightManager.copyright.find { it.option.find { it. at name == "myName" }?. at value == "ASL2" }
+                if (aslCopyright == null) {
+                  copyrightManager.append(new XmlParser().parseText('''
+                      <copyright>
+                          <option name="notice" value="Copyright 2012 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 &quot [...]
+                          <option name="keyword" value="Copyright" />
+                          <option name="allowReplaceKeyword" value="" />
+                          <option name="myName" value="ASL2" />
+                          <option name="myLocal" value="true" />
+                      </copyright>
+                '''))
+                }
+
+                // Code formatting options
+                def codeFormatSettings = new XmlParser().parseText('''
+          <component name="CodeStyleSettingsManager">
+            <option name="PER_PROJECT_SETTINGS">
+              <value>
+                <option name="USE_SAME_INDENTS" value="true" />
+                <option name="RIGHT_MARGIN" value="200" />
+                <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
+                <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
+                <option name="JD_P_AT_EMPTY_LINES" value="false" />
+                <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
+                <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
+                <option name="JD_KEEP_EMPTY_RETURN" value="false" />
+                <option name="WRAP_COMMENTS" value="true" />
+                <option name="IF_BRACE_FORCE" value="3" />
+                <option name="DOWHILE_BRACE_FORCE" value="3" />
+                <option name="WHILE_BRACE_FORCE" value="3" />
+                <option name="FOR_BRACE_FORCE" value="3" />
+                <ADDITIONAL_INDENT_OPTIONS fileType="groovy">
+                  <option name="INDENT_SIZE" value="4" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="java">
+                  <option name="INDENT_SIZE" value="4" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="js">
+                  <option name="INDENT_SIZE" value="4" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="jsp">
+                  <option name="INDENT_SIZE" value="4" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="php">
+                  <option name="INDENT_SIZE" value="4" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="scala">
+                  <option name="INDENT_SIZE" value="2" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="2" />
+                  <option name="TAB_SIZE" value="2" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="sql">
+                  <option name="INDENT_SIZE" value="2" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+                <ADDITIONAL_INDENT_OPTIONS fileType="xml">
+                  <option name="INDENT_SIZE" value="4" />
+                  <option name="CONTINUATION_INDENT_SIZE" value="8" />
+                  <option name="TAB_SIZE" value="4" />
+                  <option name="USE_TAB_CHARACTER" value="false" />
+                  <option name="SMART_TABS" value="false" />
+                  <option name="LABEL_INDENT_SIZE" value="0" />
+                  <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+                  <option name="USE_RELATIVE_INDENTS" value="false" />
+                </ADDITIONAL_INDENT_OPTIONS>
+              </value>
+            </option>
+            <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+          </component>
+        ''')
+                node.append(codeFormatSettings)
+            }
+        }
+    }
+
+    workspace.iws.withXml { provider ->
+        Node node = provider.asNode()
+
+        Node runManagerConfig = node.component.find { it.'@name' == 'RunManager' }
+
+        // Add int test configuration to JUnit defaults
+        Node runConfig = runManagerConfig.configuration.find { it.'@type' == 'JUnit'}
+
+        Node vmParameters = runConfig.option.find { it.'@name' == 'VM_PARAMETERS' }
+
+        vmParameters.'@value' = "\"-DintegTest.samplesdir=${project(":docs").samplesDir.absolutePath}\" \"-DintegTest.gradleHomeDir=${intTestImage.destinationDir}\" -ea -Dorg.gradle.integtest.executer=embedded -XX:MaxPermSize=256m -Xmx512m"
+
+        // Add an application configuration
+        runManagerConfig.'@selected' = 'Application.Gradle'
+        def appConfig =
+
+        runManagerConfig.append(new XmlParser().parseText('''
+            <configuration default="false" name="Gradle" type="Application" factoryName="Application">
+              <extension name="coverage" enabled="false" merge="false" />
+              <option name="MAIN_CLASS_NAME" value="org.gradle.debug.GradleRunConfiguration" />
+              <option name="VM_PARAMETERS" value="" />
+              <option name="PROGRAM_PARAMETERS" value="" />
+              <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
+              <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+              <option name="ALTERNATIVE_JRE_PATH" value="" />
+              <option name="ENABLE_SWING_INSPECTOR" value="false" />
+              <option name="ENV_VARIABLES" />
+              <option name="PASS_PARENT_ENVS" value="true" />
+              <module name="integTest" />
+              <envs />
+              <RunnerSettings RunnerId="Debug">
+                <option name="DEBUG_PORT" value="63810" />
+                <option name="TRANSPORT" value="0" />
+                <option name="LOCAL" value="true" />
+              </RunnerSettings>
+              <RunnerSettings RunnerId="Run" />
+              <ConfigurationWrapper RunnerId="Debug" />
+              <ConfigurationWrapper RunnerId="Run" />
+              <method />
+            </configuration>
+        '''))
+
+        runManagerConfig.append(new XmlParser().parseText('''
+            <configuration default="false" name="Gradle Precommit Check" type="Application" factoryName="Application">
+              <extension name="coverage" enabled="false" merge="false" />
+              <option name="MAIN_CLASS_NAME" value="org.gradle.testing.internal.util.IdeQuickCheckRunner" />
+              <option name="VM_PARAMETERS" value="" />
+              <option name="PROGRAM_PARAMETERS" value="" />
+              <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
+              <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+              <option name="ALTERNATIVE_JRE_PATH" value="" />
+              <option name="ENABLE_SWING_INSPECTOR" value="false" />
+              <option name="ENV_VARIABLES" />
+              <option name="PASS_PARENT_ENVS" value="true" />
+              <module name="internalTesting" />
+              <envs />
+              <method />
+            </configuration>
+        '''))
+
+        runManagerConfig.append(new XmlParser().parseText('''
+            <list size="2">
+              <item index="0" class="java.lang.String" itemvalue="Application.Gradle" />
+              <item index="1" class="java.lang.String" itemvalue="Application.Gradle Precommit Check" />
+            </list>
+        '''))
+    }
+}
diff --git a/gradle/integTest.gradle b/gradle/integTest.gradle
index d8b34f1..e6f4537 100644
--- a/gradle/integTest.gradle
+++ b/gradle/integTest.gradle
@@ -1,3 +1,6 @@
+/*
+ * Adds an 'integTest' source set, which contains the integration tests for the project.
+ */
 apply plugin: 'java'
 rootProject.apply plugin: IntegTestPlugin
 
@@ -10,44 +13,66 @@ configurations {
     }
 }
 
+dependencies {
+    integTestCompile project(":internalIntegTesting")
+}
+
 sourceSets {
     integTest {
-        compileClasspath = sourceSets.main.classes + sourceSets.test.classes + configurations.integTestCompile
-        runtimeClasspath = classes + compileClasspath + configurations.integTestRuntime
+        compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integTestCompile
+        runtimeClasspath = output + compileClasspath + configurations.integTestRuntime
     }
 }
 
-ideaModule {
-    testSourceDirs += sourceSets.integTest.groovy.srcDirs
-    testSourceDirs += sourceSets.integTest.resources.srcDirs
-    scopes.TEST.plus.add(configurations.integTestCompile)
-    scopes.TEST.plus.add(configurations.integTestRuntime)
+plugins.withType(org.gradle.plugins.ide.idea.IdeaPlugin) { // lazy as plugin not applied yet
+    idea {
+        module {
+            testSourceDirs += sourceSets.integTest.groovy.srcDirs
+            testSourceDirs += sourceSets.integTest.resources.srcDirs
+            scopes.TEST.plus.add(configurations.integTestCompile)
+            scopes.TEST.plus.add(configurations.integTestRuntime)
+        }
+    }
 }
 
-eclipseClasspath {
-    plusConfigurations.add(configurations.integTestCompile)
-    plusConfigurations.add(configurations.integTestRuntime)
+plugins.withType(org.gradle.plugins.ide.eclipse.EclipsePlugin) { // lazy as plugin not applied yet
+    eclipse {
+        classpath {
+            plusConfigurations.add(configurations.integTestCompile)
+            plusConfigurations.add(configurations.integTestRuntime)
+        }
+    }
 }
 
-integTestTasks = tasks.withType(Test).matching { it.name.toLowerCase().endsWith('integtest') }
-rootProject.integTests << integTestTasks
+ext.integTestTasks = tasks.withType(Test).matching { it.name.toLowerCase().endsWith('integtest') }
 
 integTestTasks.all {
-    dependsOn ':intTestImage'
-    testClassesDir = sourceSets.integTest.classesDir
+    dependsOn ':intTestImage', project(":docs").tasks.matching { it.name == "samples" } // lazy as doc not eval'd yet
+    testClassesDir = sourceSets.integTest.output.classesDir
     classpath = sourceSets.integTest.runtimeClasspath
     testSrcDirs = []
-    jvmArgs '-XX:+HeapDumpOnOutOfMemoryError'
+    jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m', '-XX:+HeapDumpOnOutOfMemoryError'
+    maxParallelForks = guessMaxForks(project)
+
+    testResultsDir = file("${project.testResultsDir}/$name")
+
+    systemProperties['org.gradle.integtest.versions'] = project.hasProperty("testAllVersions") ? 'all' : 'latest'
+    if (project.hasProperty('crossVersionTestsOnly')) {
+        include '**/*CrossVersion*'
+    }
 
     doFirst {
-        testResultsDir = file("${project.testResultsDir}/$name")
-        testReportDir = file("${project.testReportDir}/$name")
+        testReportDir = file("${project.reporting.baseDir}/$name")
         systemProperties['integTest.gradleHomeDir'] = integTestImageDir.absolutePath
         systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
+        systemProperties['integTest.samplesdir'] = project(":docs").samplesDir.absolutePath
+        if (isCIBuild()) {
+            maxParallelForks = 4
+        }
     }
 }
 
-['embedded', 'forking', 'daemon'].each {
+['embedded', 'forking', 'daemon', 'embeddedDaemon'].each {
     def mode = it
     def taskName = "${it}IntegTest"
     tasks.addRule(taskName) { name ->
@@ -58,12 +83,24 @@ integTestTasks.all {
     }
 }
 
+daemonIntegTest {
+    systemProperties['org.gradle.integtest.daemon.registry'] = file("$rootProject.buildDir/daemon").absolutePath
+}
+
+task waitForDaemonsToDie << {
+    def mins = 5
+    println "I'm waiting for $mins mins so that existing deamons can die with honor. It's a workaround until we fix it properly."
+    Thread.sleep(mins * 60 * 1000);
+}
+
 task integTest(type: Test) {
     doFirst {
         systemProperties['org.gradle.integtest.executer'] = integTestMode
     }
 }
 
+tasks.findByName("check")?.dependsOn(integTest)
+
 class IntegTestPlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.convention.plugins.integTest = new IntegTestConvention(project)
@@ -82,10 +119,10 @@ class IntegTestConvention {
         if (!project.tasks.findByName('ciBuild') || !project.gradle.taskGraph.populated) {
             return null
         }
-        if (project.isCIBuild() || OperatingSystem.current().isWindows()) {
+        if (project.isCIBuild() || project.isReleaseBuild() ) {
             return 'forking'
         }
-        return 'embedded'
+        return System.getProperty("org.gradle.integtest.executer") ?: 'embedded'
     }
 
     File getIntegTestUserDir() {
diff --git a/gradle/providedConfiguration.gradle b/gradle/providedConfiguration.gradle
new file mode 100644
index 0000000..fea86a7
--- /dev/null
+++ b/gradle/providedConfiguration.gradle
@@ -0,0 +1,19 @@
+/**
+ * Adds a configuration named 'provided'. 'Provided' dependencies
+ * are incoming compile dependencies that aren't outgoing
+ * dependencies. In other words, they have no effect on transitive
+ * dependency management.
+ */
+
+configurations {
+    provided.extendsFrom(compile)
+    testCompile.extendsFrom(provided)
+}
+
+sourceSets.main {
+    compileClasspath = configurations.provided
+}
+
+plugins.withType(IdeaPlugin) {
+    idea.module.scopes.PROVIDED.plus += configurations.provided
+}
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 7c72ead..0f6e9ae 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -14,13 +14,13 @@ task sourceJar(type: Jar) {
 }
 
 task generatePom {
-    pomFile = new File(temporaryDir, 'pom.xml')
+    ext.pomFile = new File(temporaryDir, 'pom.xml')
     doLast {
         dependencies {
-            configurations.publishCompile.getAllDependencies(ProjectDependency).each {
-                publishRuntime "org.gradle:gradle-${it.dependencyProject.name}:${version}"
+            configurations.publishCompile.allDependencies.withType(ProjectDependency).each {
+                publishRuntime "org.gradle:${it.dependencyProject.archivesBaseName}:${version}"
             }
-            configurations.publishCompile.getAllDependencies(ExternalModuleDependency).each {
+            configurations.publishCompile.allDependencies.withType(ExternalModuleDependency).each {
                 publishRuntime it
             }
         }
@@ -52,10 +52,11 @@ uploadArchives {
     doFirst {
         repositories {
             ivy {
-                name = 'gradleLibs'
                 artifactPattern "${version.libsUrl}/${project.group.replaceAll('\\.', '/')}/${archivesBaseName}/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-                userName = artifactoryUserName
-                password = artifactoryUserPassword
+                credentials {
+                    username = artifactoryUserName
+                    password = artifactoryUserPassword
+                }
             }
         }
     }
@@ -65,8 +66,9 @@ task publishLocalArchives(type: Upload) {
     configuration = configurations.publishRuntime
     dependsOn generatePom
     uploadDescriptor = false
-    repositories.add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-        name = 'gradleReleases'
-        addArtifactPattern("${rootProject.file('build/repo')}/${project.group.replaceAll('\\.', '/')}/${archivesBaseName}/[revision]/[artifact]-[revision](-[classifier]).[ext]" as String)
+    repositories {
+        ivy {
+            artifactPattern "${rootProject.file('build/repo')}/${project.group.replaceAll('\\.', '/')}/${archivesBaseName}/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/gradle/resumeBuild.gradle b/gradle/resumeBuild.gradle
new file mode 100644
index 0000000..3d0e35c
--- /dev/null
+++ b/gradle/resumeBuild.gradle
@@ -0,0 +1,10 @@
+def resumeTask = project.property("resume")
+
+if (resumeTask) {
+    gradle.taskGraph.whenReady { graph ->
+        def allTasks = graph.allTasks
+        def resumeIndex = allTasks.findIndexOf { it.path == resumeTask }
+        if (resumeIndex < 0) throw new GradleException("Can't resume from $resumeTask because no such task is scheduled for execution")
+        allTasks.subList(0, resumeIndex)*.enabled = false
+    }
+}
\ No newline at end of file
diff --git a/gradle/ssh.gradle b/gradle/ssh.gradle
deleted file mode 100644
index 6ca174c..0000000
--- a/gradle/ssh.gradle
+++ /dev/null
@@ -1,54 +0,0 @@
-
-project.Scp = Scp.class
-
-configurations {
-    sshAntTask
-}
-
-dependencies {
-    sshAntTask "org.apache.ant:ant-jsch:1.8.2"
-}
-
-tasks.withType(Scp) {
-    sshAntClasspath = configurations.sshAntTask
-}
-
-class Scp extends DefaultTask {
-    @InputFiles
-    FileCollection sshAntClasspath
-
-    def from(Object path) {
-        source << path
-    }
-
-    @InputDirectory
-    File sourceDir
-
-    @Input
-    String host
-
-    @Input
-    String userName
-
-    @Input
-    String password
-
-    @Input
-    String destinationDir
-
-    @TaskAction
-    def executeActions() {
-        ant.taskdef(name: 'scp',
-                classname: 'org.apache.tools.ant.taskdefs.optional.ssh.Scp',
-                classpath: sshAntClasspath.asPath,
-                loaderref: 'ssh')
-        ant.taskdef(name: 'sshexec',
-                classname: 'org.apache.tools.ant.taskdefs.optional.ssh.SSHExec',
-                classpath: sshAntClasspath.asPath,
-                loaderref: 'ssh')
-        ant.sshexec(host: host, username: userName, password: password, command: "mkdir -p ${destinationDir}")
-        ant.scp(remotetodir: "${userName}@${host}:${destinationDir}", password: password) {
-            fileset(dir: sourceDir)
-        }
-    }
-}
diff --git a/gradle/testFixtures.gradle b/gradle/testFixtures.gradle
new file mode 100644
index 0000000..c97205b
--- /dev/null
+++ b/gradle/testFixtures.gradle
@@ -0,0 +1,54 @@
+/*
+    Adds a new testFixtures source set which should contain utilities/fixtures to assist in unit testing 
+    classes from the main source set.
+    
+    The test fixtures are automatically made available to the test classpath.
+    
+    The gradle/groovyProject.gradle script automatically applies this if a project has a src/testFixtures dir.
+*/
+apply plugin: 'java'
+
+configurations {
+    testFixturesCompile.extendsFrom compile
+    testFixturesRuntime.extendsFrom runtime, testFixturesCompile
+        
+    // Expose configurations that include the test fixture classes for clients to use
+    testFixturesUsageCompile.extendsFrom testFixturesCompile
+    testFixturesUsageRuntime.extendsFrom testFixturesRuntime
+    
+    // Assume that the project wants to use the fixtures for its tests
+    testCompile.extendsFrom testFixturesUsageCompile
+    testRuntime.extendsFrom testFixturesUsageRuntime
+}
+
+sourceSets {
+    testFixtures {
+        compileClasspath = sourceSets.main.output + configurations.testFixturesCompile
+        runtimeClasspath = output + compileClasspath + configurations.testFixturesRuntime
+    }
+}
+
+dependencies {
+    testFixturesUsageCompile sourceSets.testFixtures.output
+    testFixturesCompile libraries.junit, libraries.jmock, libraries.spock
+}
+
+plugins.withType(org.gradle.plugins.ide.idea.IdeaPlugin) { // lazy as plugin not applied yet
+    idea {
+        module {
+            testSourceDirs += sourceSets.testFixtures.groovy.srcDirs
+            testSourceDirs += sourceSets.testFixtures.resources.srcDirs
+            scopes.TEST.plus.add(configurations.testFixturesCompile)
+            scopes.TEST.plus.add(configurations.testFixturesRuntime)
+        }
+    }
+}
+
+plugins.withType(org.gradle.plugins.ide.eclipse.EclipsePlugin) { // lazy as plugin not applied yet
+    eclipse {
+        classpath {
+            plusConfigurations.add(configurations.testFixturesCompile)
+            plusConfigurations.add(configurations.testFixturesRuntime)
+        }
+    }
+}
diff --git a/gradle/testWithUnknownOS.gradle b/gradle/testWithUnknownOS.gradle
new file mode 100644
index 0000000..7c67676
--- /dev/null
+++ b/gradle/testWithUnknownOS.gradle
@@ -0,0 +1,7 @@
+if (project.hasProperty("testWithUnknownOS")) {
+    tasks.withType(Test) {
+        systemProperty "os.arch", "unknown architecture"
+        systemProperty "os.name", "unknown operating system"
+        systemProperty "os.version", "unknown version"
+    }
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d17b943..d9cd04d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Apr 11 08:31:29 EST 2011
+#Fri Mar 30 20:19:42 MDT 2012
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://repo.gradle.org/gradle/distributions/gradle-snapshots/gradle-1.0-milestone-3-20110424172210+1000-bin.zip
\ No newline at end of file
+distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.0-rc-1-20120331034014+0200-bin.zip
diff --git a/gradlew b/gradlew
index d8809f1..569f6ed 100755
--- a/gradlew
+++ b/gradlew
@@ -1,16 +1,16 @@
 #!/bin/bash
 
 ##############################################################################
-##                                                                          ##
-##  Gradle wrapper script for UN*X                                         ##
-##                                                                          ##
+##
+##  Gradle start up script for UN*X
+##
 ##############################################################################
 
-# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
-# GRADLE_OPTS="$GRADLE_OPTS -Xmx512m"
-# JAVA_OPTS="$JAVA_OPTS -Xmx512m"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS="-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8"
 
-GRADLE_APP_NAME=Gradle
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD="maximum"
@@ -42,54 +42,51 @@ case "`uname`" in
     ;;
 esac
 
-# Attempt to set JAVA_HOME if it's not already set.
-if [ -z "$JAVA_HOME" ] ; then
-    if $darwin ; then
-        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
-        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
-    else
-        javaExecutable="`which javac`"
-        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
-        # readlink(1) is not available as standard on Solaris 10.
-        readLink=`which readlink`
-        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
-        javaExecutable="`readlink -f \"$javaExecutable\"`"
-        javaHome="`dirname \"$javaExecutable\"`"
-        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
-        export JAVA_HOME="$javaHome"
-    fi
-fi
-
 # For Cygwin, ensure paths are in UNIX format before anything is touched.
 if $cygwin ; then
-    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
     [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
 fi
 
-STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain
-CLASSPATH=`dirname "$0"`/gradle/wrapper/gradle-wrapper.jar
-WRAPPER_PROPERTIES=`dirname "$0"`/gradle/wrapper/gradle-wrapper.properties
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/"
+APP_HOME="`pwd -P`"
+cd "$SAVED"
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
 # Determine the Java command to use to start the JVM.
-if [ -z "$JAVACMD" ] ; then
-    if [ -n "$JAVA_HOME" ] ; then
-        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-            # IBM's JDK on AIX uses strange locations for the executables
-            JAVACMD="$JAVA_HOME/jre/sh/java"
-        else
-            JAVACMD="$JAVA_HOME/bin/java"
-        fi
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
     else
-        JAVACMD="java"
+        JAVACMD="$JAVA_HOME/bin/java"
     fi
-fi
-if [ ! -x "$JAVACMD" ] ; then
-    die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
-fi
-if [ -z "$JAVA_HOME" ] ; then
-    warn "JAVA_HOME environment variable is not set"
 fi
 
 # Increase the maximum file descriptors if we can.
@@ -108,15 +105,14 @@ if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
     fi
 fi
 
-# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
+# For Darwin, add options to specify how the application appears in the dock
 if $darwin; then
-    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
-# we may also want to set -Xdock:image
+    JAVA_OPTS="$JAVA_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
 fi
 
 # For Cygwin, switch paths to Windows format before running java
 if $cygwin ; then
-    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
 
     # We build the pattern for arguments to be converted via cygpath
@@ -143,7 +139,7 @@ if $cygwin ; then
             eval `echo args$i`="\"$arg\""
         fi
         i=$((i+1))
-    done 
+    done
     case $i in
         (0) set -- ;;
         (1) set -- "$args0" ;;
@@ -158,11 +154,11 @@ if $cygwin ; then
     esac
 fi
 
-GRADLE_APP_BASE_NAME=`basename "$0"`
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
 
-exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
-        -classpath "$CLASSPATH" \
-        -Dorg.gradle.appname="$GRADLE_APP_BASE_NAME" \
-        -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \
-        $STARTER_MAIN_CLASS \
-        "$@"
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 4855abb..11192ca 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,24 +1,37 @@
 @if "%DEBUG%" == "" @echo off
 @rem ##########################################################################
- at rem                                                                         ##
- at rem  Gradle startup script for Windows                                      ##
- at rem                                                                         ##
+ at rem
+ at rem  Gradle startup script for Windows
+ at rem
 @rem ##########################################################################
 
 @rem Set local scope for the variables with windows NT shell
 if "%OS%"=="Windows_NT" setlocal
 
- at rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
- at rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m
- at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m
+ at rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8
 
 set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.\
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
 
 @rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
 set JAVA_EXE=java.exe
-if not defined JAVA_HOME goto init
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
 
+goto fail
+
+:findJavaFromJavaHome
 set JAVA_HOME=%JAVA_HOME:"=%
 set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
@@ -29,14 +42,14 @@ echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
 echo.
 echo Please set the JAVA_HOME variable in your environment to match the
 echo location of your Java installation.
-echo.
-goto end
+
+goto fail
 
 :init
 @rem Get command-line arguments, handling Windowz variants
 
 if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%eval[2+2]" == "4" goto 4NT_args
+if "%@eval[2+2]" == "4" goto 4NT_args
 
 :win9xME_args
 @rem Slurp the command line arguments.
@@ -56,27 +69,22 @@ set CMD_LINE_ARGS=%$
 :execute
 @rem Setup the command line
 
-set STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain
-set CLASSPATH=%DIRNAME%\gradle\wrapper\gradle-wrapper.jar
-set WRAPPER_PROPERTIES=%DIRNAME%\gradle\wrapper\gradle-wrapper.properties
-
-set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%"
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 
 @rem Execute Gradle
-"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
 
 :end
 @rem End local scope for the variables with windows NT shell
 if "%ERRORLEVEL%"=="0" goto mainEnd
 
-if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
-
+:fail
 rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
 rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
-exit /b "%ERRORLEVEL%"
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
 
 :mainEnd
 if "%OS%"=="Windows_NT" endlocal
 
-:omega
\ No newline at end of file
+:omega
diff --git a/settings.gradle b/settings.gradle
index e5cc1ba..2e16d21 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+include 'baseServices'
 include 'core'
 include 'coreImpl'
 include 'wrapper'
+include 'cli'
 include 'launcher'
 include 'plugins'
 include 'scala'
@@ -32,6 +34,14 @@ include 'toolingApi'
 include 'docs'
 include 'integTest'
 include 'sonar'
+include 'signing'
+include 'cpp'
+include 'ear'
+include 'native'
+include 'internalTesting'
+include 'internalIntegTesting'
+include 'website'
+include 'performance'
 
 rootProject.name = 'gradle'
 rootProject.children.each {project ->
diff --git a/src/toplevel/LICENSE b/src/toplevel/LICENSE
index 35df624..3bec7a1 100644
--- a/src/toplevel/LICENSE
+++ b/src/toplevel/LICENSE
@@ -467,3 +467,511 @@ Agreement will bring a legal action under this Agreement more than one year
 after the cause of action arose. Each party waives its rights to a jury trial in
 any resulting litigation.
 
+------------------------------------------------------------------------------
+License for the JCIFS package
+------------------------------------------------------------------------------
+JCIFS License
+
+          GNU LESSER GENERAL PUBLIC LICENSE
+               Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+          GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+

+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+

+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+             END OF TERMS AND CONDITIONS
+

+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
diff --git a/src/toplevel/NOTICE b/src/toplevel/NOTICE
index 7955958..c0082da 100644
--- a/src/toplevel/NOTICE
+++ b/src/toplevel/NOTICE
@@ -13,6 +13,7 @@ Groovy (http://groovy.codehaus.org)
 Logback (http://logback.qos.ch)
 SLF4J (http://www.slf4j.org)
 Junit (http://www.junit.org)
+JCIFS (http://jcifs.samba.org)
 
 For licenses see the LICENSE file.
 
diff --git a/src/toplevel/changelog.txt b/src/toplevel/changelog.txt
index 171bfcf..4b2128a 100644
--- a/src/toplevel/changelog.txt
+++ b/src/toplevel/changelog.txt
@@ -1,4 +1,3 @@
-
 Release Notes - Gradle - Version ${version}
 
-See http://docs.codehaus.org/display/GRADLE/Gradle+${version}+Release+Notes
\ No newline at end of file
+See http://wiki.gradle.org/display/GRADLE/Gradle+${version}+Release+Notes
diff --git a/src/toplevel/init.d/readme.txt b/src/toplevel/init.d/readme.txt
new file mode 100644
index 0000000..dfccbb5
--- /dev/null
+++ b/src/toplevel/init.d/readme.txt
@@ -0,0 +1 @@
+You can add .gradle init scripts to this directory. Each one is executed at the start of the build.
\ No newline at end of file
diff --git a/src/toplevel/media/gradle-icon-128x128.png b/src/toplevel/media/gradle-icon-128x128.png
new file mode 100644
index 0000000..b4b35a9
Binary files /dev/null and b/src/toplevel/media/gradle-icon-128x128.png differ
diff --git a/src/toplevel/media/gradle-icon-16x16.png b/src/toplevel/media/gradle-icon-16x16.png
new file mode 100644
index 0000000..91c0991
Binary files /dev/null and b/src/toplevel/media/gradle-icon-16x16.png differ
diff --git a/src/toplevel/media/gradle-icon-24x24.png b/src/toplevel/media/gradle-icon-24x24.png
new file mode 100644
index 0000000..06fa563
Binary files /dev/null and b/src/toplevel/media/gradle-icon-24x24.png differ
diff --git a/src/toplevel/media/gradle-icon-256x256.png b/src/toplevel/media/gradle-icon-256x256.png
new file mode 100644
index 0000000..963cea1
Binary files /dev/null and b/src/toplevel/media/gradle-icon-256x256.png differ
diff --git a/src/toplevel/media/gradle-icon-32x32.png b/src/toplevel/media/gradle-icon-32x32.png
new file mode 100644
index 0000000..7f46c24
Binary files /dev/null and b/src/toplevel/media/gradle-icon-32x32.png differ
diff --git a/src/toplevel/media/gradle-icon-48x48.png b/src/toplevel/media/gradle-icon-48x48.png
new file mode 100644
index 0000000..50c15f4
Binary files /dev/null and b/src/toplevel/media/gradle-icon-48x48.png differ
diff --git a/src/toplevel/media/gradle-icon-512x512.png b/src/toplevel/media/gradle-icon-512x512.png
new file mode 100644
index 0000000..cc41c34
Binary files /dev/null and b/src/toplevel/media/gradle-icon-512x512.png differ
diff --git a/src/toplevel/media/gradle-icon-64x64.png b/src/toplevel/media/gradle-icon-64x64.png
new file mode 100644
index 0000000..8c9524d
Binary files /dev/null and b/src/toplevel/media/gradle-icon-64x64.png differ
diff --git a/src/toplevel/media/gradle.icns b/src/toplevel/media/gradle.icns
new file mode 100644
index 0000000..c03220d
Binary files /dev/null and b/src/toplevel/media/gradle.icns differ
diff --git a/subprojects/announce/announce.gradle b/subprojects/announce/announce.gradle
index f842788..7c52134 100644
--- a/subprojects/announce/announce.gradle
+++ b/subprojects/announce/announce.gradle
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile libraries.slf4j_api
     compile project(':core')
     compile project(':plugins')
+}
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+if (!Jvm.current().java6Compatible) {
+    sourceSets.main.groovy.exclude '**/jdk6/**'
 }
+
+useTestFixtures()
diff --git a/subprojects/announce/src/integTest/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginIntegrationTest.groovy b/subprojects/announce/src/integTest/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginIntegrationTest.groovy
new file mode 100644
index 0000000..b60b666
--- /dev/null
+++ b/subprojects/announce/src/integTest/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginIntegrationTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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.plugins.announce
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class BuildAnnouncementsPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getPluginId() {
+        return "build-announcements"
+    }
+
+    @Override
+    String getMainTask() {
+        return "tasks"
+    }
+
+    def "does not blow up when a local notification mechanism is not available"() {
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'build-announcements'
+"""
+
+        expect:
+        succeeds 'assemble'
+    }
+
+    def "can use custom announcer to receive announcements"() {
+        buildFile << """
+apply plugin: 'build-announcements'
+
+task a
+task b(dependsOn: a)
+
+announce.local = ({title, message -> println "[\$title][\$message]" } as Announcer)
+"""
+
+        when:
+        run 'b'
+
+        then:
+        output.contains("[Build successful][2 tasks executed]")
+    }
+
+    def "announces build failure"() {
+        buildFile << """
+apply plugin: 'build-announcements'
+
+task broken << { throw new RuntimeException() }
+
+announce.local = ({title, message -> println "[\$title][\$message]" } as Announcer)
+"""
+
+        when:
+        fails 'broken'
+
+        then:
+        output.contains("[Build failed][task ':broken' failed\n1 task executed]")
+    }
+
+    def "announces multiple build failures"() {
+        buildFile << """
+apply plugin: 'build-announcements'
+
+task broken1 << { throw new RuntimeException() }
+task broken2 << { throw new RuntimeException() }
+
+announce.local = ({title, message -> println "[\$title][\$message]" } as Announcer)
+"""
+
+        when:
+        executer.withArguments("--continue")
+        fails 'broken1', 'broken2'
+
+        then:
+        output.contains("[task ':broken1' failed][1 task failed]")
+        output.contains("[Build failed][2 tasks failed\n2 tasks executed]")
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
index 3833e20..87d879d 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
@@ -18,10 +18,7 @@ package org.gradle.api.plugins.announce
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.plugins.announce.internal.AnnouncerFactory
-import org.gradle.api.plugins.announce.internal.DefaultAnnouncerFactory
-import org.gradle.api.logging.Logging
-import org.gradle.api.logging.Logger
+import org.gradle.api.internal.project.ProjectInternal
 
 /**
  * This plugin allows to send announce messages to Twitter.
@@ -30,51 +27,7 @@ import org.gradle.api.logging.Logger
  */
 class AnnouncePlugin implements Plugin<Project> {
     void apply(Project project) {
-        project.convention.plugins.announce = new AnnouncePluginConvention(project)
-    }
-}
-
-class AnnouncePluginConvention {
-    private static final Logger logger = Logging.getLogger(AnnouncePlugin)
-
-    /**
-     * The username to use for announcements.
-     */
-    String username
-
-    /**
-     * The password to use for announcements.
-     */
-    String password
-
-    Project project
-    AnnouncerFactory announcerFactory
-
-    AnnouncePluginConvention(project) {
-        this.project = project
-        this.announcerFactory = new DefaultAnnouncerFactory(this)
-    }
-
-    /**
-     * Configures the announce plugin convention. The given closure configures the {@link AnnouncePluginConvention}.
-     */
-    void announce(Closure closure) {
-        closure.delegate = this
-        closure()
-    }
-
-    /**
-     * Sends an announcement of the given type.
-     *
-     * @param msg The content of the announcement
-     * @param type The announcement type.
-     */
-    void announce(String msg, String type) {
-        try {
-            announcerFactory.createAnnouncer(type).send(project.name, msg)
-        } catch (Exception e) {
-            logger.warn("Failed to send message '$msg' to '$type'", e)
-        }
+        project.extensions.add("announce", new AnnouncePluginExtension((ProjectInternal)project))
     }
 }
 
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy
new file mode 100644
index 0000000..ab6f62c
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtension.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 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.plugins.announce
+
+import org.gradle.api.Project
+import org.gradle.api.internal.GradleDistributionLocator
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.api.plugins.announce.internal.AnnouncerFactory
+import org.gradle.api.plugins.announce.internal.DefaultAnnouncerFactory
+import org.gradle.api.plugins.announce.internal.DefaultIconProvider
+
+class AnnouncePluginExtension {
+    private static final Logger logger = Logging.getLogger(AnnouncePlugin)
+
+    /**
+     * The username to use for announcements.
+     */
+    String username
+
+    /**
+     * The password to use for announcements.
+     */
+    String password
+
+    Announcer local
+
+    private final Project project
+    AnnouncerFactory announcerFactory
+
+    AnnouncePluginExtension(ProjectInternal project) {
+        this.project = project
+        this.announcerFactory = new DefaultAnnouncerFactory(this, project, new DefaultIconProvider(project.services.get(GradleDistributionLocator)))
+    }
+
+    /**
+     * Returns an {@link Announcer} that sends announcements to the local desktop, if a notification mechanism is available.
+     *
+     * @return The announcer.
+     */
+    Announcer getLocal() {
+        return new Announcer() {
+            void send(String title, String message) {
+                if (local == null) {
+                    local = announcerFactory.createAnnouncer("local")
+                }
+                local.send(title, message)
+            }
+        }
+    }
+
+    /**
+     * Sends an announcement of the given type.
+     *
+     * @param msg The content of the announcement
+     * @param type The announcement type.
+     */
+    void announce(String msg, String type) {
+        try {
+            announcerFactory.createAnnouncer(type).send(project.name, msg)
+        } catch (Exception e) {
+            logger.warn("Failed to send message '$msg' to '$type'", e)
+        }
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPlugin.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPlugin.groovy
new file mode 100644
index 0000000..0616880
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPlugin.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.plugins.announce
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+
+import org.gradle.api.plugins.announce.internal.AnnouncingBuildListener
+
+/**
+ * A plugin which announces interesting build lifecycle events.
+ */
+class BuildAnnouncementsPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.plugins.apply(AnnouncePlugin)
+        AnnouncePluginExtension extension = project.extensions.findByType(AnnouncePluginExtension)
+        def listener = new AnnouncingBuildListener(extension.local)
+        project.gradle.addBuildListener(listener)
+    }
+}
+
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerUnavailableException.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerUnavailableException.groovy
new file mode 100644
index 0000000..3ffe1a2
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncerUnavailableException.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+/**
+ * Thrown when the target announcer is not available.
+ */
+class AnnouncerUnavailableException extends RuntimeException {
+    AnnouncerUnavailableException(String message) {
+        super(message)
+    }
+
+    AnnouncerUnavailableException(String message, Throwable cause) {
+        super(message, cause)
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncingBuildListener.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncingBuildListener.groovy
new file mode 100644
index 0000000..d7ad2a7
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/AnnouncingBuildListener.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import org.gradle.BuildAdapter
+import org.gradle.api.execution.TaskExecutionListener
+import org.gradle.api.plugins.announce.Announcer
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskState
+import org.gradle.BuildResult
+
+class AnnouncingBuildListener extends BuildAdapter implements TaskExecutionListener {
+    private final Announcer announcer
+    private List<Task> failed = []
+    private Task lastFailed
+    private int taskCount
+
+    AnnouncingBuildListener(Announcer announcer) {
+        this.announcer = announcer
+    }
+
+    void beforeExecute(Task task) {
+        taskCount++
+        if (lastFailed) {
+            announcer.send("$lastFailed failed", taskFailureCountMessage)
+            lastFailed = null
+        }
+    }
+
+    void afterExecute(Task task, TaskState state) {
+        if (state.failure) {
+            lastFailed = task
+            failed << task
+        }
+    }
+
+    @Override
+    void buildFinished(BuildResult result) {
+        if (result.failure != null) {
+            if (failed.isEmpty()) {
+                announcer.send("Build failed", taskCountMessage)
+            } else {
+                announcer.send("Build failed", taskFailureMessage + "\n" + taskCountMessage)
+            }
+        } else {
+            announcer.send("Build successful", taskCountMessage)
+        }
+    }
+
+    private String getTaskFailureCountMessage() {
+        if (failed.size() == 1) {
+            return "1 task failed"
+        }
+        return "${failed.size()} tasks failed"
+    }
+
+    private String getTaskFailureMessage() {
+        if (failed.size() == 1) {
+            return "${failed[0]} failed"
+        } else {
+            return "${failed.size()} tasks failed"
+        }
+    }
+
+    private String getTaskCountMessage() {
+        if (taskCount == 0) {
+            return "No tasks executed"
+        }
+        if (taskCount == 1) {
+            return "1 task executed"
+        }
+        return "$taskCount tasks executed"
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
old mode 100644
new mode 100755
index a4b62ae..1804945
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
@@ -15,34 +15,61 @@
  */
 package org.gradle.api.plugins.announce.internal
 
-import org.gradle.api.plugins.announce.AnnouncePluginConvention
-import org.gradle.api.plugins.announce.Announcer
 import org.gradle.api.InvalidUserDataException
+import org.gradle.api.plugins.announce.AnnouncePluginExtension
+import org.gradle.api.plugins.announce.Announcer
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.jvm.Jvm
+import org.gradle.api.internal.ProcessOperations
 
 /**
  * @author Hans Dockter
  */
 class DefaultAnnouncerFactory implements AnnouncerFactory {
-    private final AnnouncePluginConvention announcePluginConvention
+    private final AnnouncePluginExtension announcePluginConvention
+    private final IconProvider iconProvider
+    private final ProcessOperations processOperations
 
-    DefaultAnnouncerFactory(announcePluginConvention) {
+    DefaultAnnouncerFactory(AnnouncePluginExtension announcePluginConvention, ProcessOperations processOperations, IconProvider iconProvider) {
         this.announcePluginConvention = announcePluginConvention
+        this.iconProvider = iconProvider
+        this.processOperations = processOperations
     }
 
     Announcer createAnnouncer(String type) {
+        def announcer = createActualAnnouncer(type)
+        return announcer ? new IgnoreUnavailableAnnouncer(announcer) : new UnknownAnnouncer()
+    }
+
+    private Announcer createActualAnnouncer(String type) {
         switch (type) {
+            case "local":
+                if (OperatingSystem.current().windows) {
+                    return createActualAnnouncer("snarl")
+                } else if (OperatingSystem.current().macOsX) {
+                    return createActualAnnouncer("growl")
+                } else {
+                    return createActualAnnouncer("notify-send")
+                }
             case "twitter":
                 String username = announcePluginConvention.username
                 String password = announcePluginConvention.password
                 return new Twitter(username, password)
             case "notify-send":
-                return new NotifySend(announcePluginConvention.project)
+                return new NotifySend(processOperations, iconProvider)
             case "snarl":
-                return new Snarl()
+                return new Snarl(iconProvider)
             case "growl":
-                return new Growl(announcePluginConvention.project)
+                if (Jvm.current().java6Compatible && Jvm.current().supportsAppleScript) {
+                    try {
+                        return getClass().getClassLoader().loadClass("org.gradle.api.plugins.announce.internal.jdk6.AppleScriptBackedGrowlAnnouncer").newInstance(iconProvider)
+                    } catch (ClassNotFoundException e) {
+                        // Ignore and fall back to growl notify
+                    }
+                }
+                return new GrowlNotifyBackedAnnouncer(processOperations, iconProvider)
             default:
-                return new UnknownAnnouncer()
+                return null
         }
     }
 }
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProvider.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProvider.groovy
new file mode 100644
index 0000000..ddc5cde
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProvider.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import org.gradle.api.internal.GradleDistributionLocator
+
+class DefaultIconProvider implements IconProvider {
+    private final GradleDistributionLocator locator
+    
+    DefaultIconProvider(GradleDistributionLocator locator) {
+        this.locator = locator
+    }
+
+    File getIcon(int width, int height) {
+        def homeDir = locator.gradleHome
+        if (!homeDir) {
+            return null
+        }
+        def candidate = new File(homeDir, "media/gradle-icon-${width}x${height}.png")
+        return candidate.file ? candidate : null
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Growl.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Growl.groovy
index 29be1cc..413587e 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Growl.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Growl.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -16,19 +16,6 @@
 package org.gradle.api.plugins.announce.internal
 
 import org.gradle.api.plugins.announce.Announcer
-import org.gradle.api.Project
 
-class Growl implements Announcer {
-    private final Project project
-
-    Growl(Project project) {
-        this.project = project
-    }
-
-    void send(String title, String message) {
-        project.exec {
-            executable 'growlnotify'
-            args '-m', message, title
-        }
-    }
+abstract class Growl implements Announcer {
 }
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/GrowlNotifyBackedAnnouncer.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/GrowlNotifyBackedAnnouncer.groovy
new file mode 100644
index 0000000..d25c27a
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/GrowlNotifyBackedAnnouncer.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import org.gradle.api.internal.ProcessOperations
+import org.gradle.internal.os.OperatingSystem
+
+class GrowlNotifyBackedAnnouncer extends Growl {
+    private final ProcessOperations processOperations
+    private final IconProvider iconProvider
+
+    GrowlNotifyBackedAnnouncer(ProcessOperations processOperations, IconProvider iconProvider) {
+        this.processOperations = processOperations
+        this.iconProvider = iconProvider
+    }
+
+    void send(String title, String message) {
+        def exe = OperatingSystem.current().findInPath('growlnotify')
+        if (exe == null) {
+            throw new AnnouncerUnavailableException("Could not find 'growlnotify' in path.")
+        }
+        processOperations.exec {
+            executable exe
+            args '-m', message
+            def icon = iconProvider.getIcon(48, 48)
+            if (icon) {
+                args '--image', icon.absolutePath
+            }
+            args '-t', title
+        }
+    }
+
+    private def escape(String value) {
+        return value.replace("\\", "\\\\").replace("\r", "\\r")
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/IconProvider.java b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/IconProvider.java
new file mode 100644
index 0000000..8c8ee08
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/IconProvider.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal;
+
+import java.io.File;
+
+public interface IconProvider {
+    /**
+     * Returns an icon appropriate to use for the given dimensions. Returns null if no icon available.
+     */
+    File getIcon(int width, int height);
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/IgnoreUnavailableAnnouncer.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/IgnoreUnavailableAnnouncer.groovy
new file mode 100644
index 0000000..6a7cb00
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/IgnoreUnavailableAnnouncer.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import org.gradle.api.plugins.announce.Announcer
+import org.slf4j.LoggerFactory
+import org.slf4j.Logger
+
+class IgnoreUnavailableAnnouncer implements Announcer {
+    private static final Logger LOGGER = LoggerFactory.getLogger(IgnoreUnavailableAnnouncer)
+    private final Announcer announcer
+
+    IgnoreUnavailableAnnouncer(Announcer announcer) {
+        this.announcer = announcer
+    }
+
+    void send(String title, String message) {
+        try {
+            announcer.send(title, message)
+        } catch (AnnouncerUnavailableException e) {
+            // Ignore
+            LOGGER.debug("Discarding message [$title][$message] as announcer is not available: $e.message")
+        }
+    }
+}
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
index 1e5a89a..d69be48 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
@@ -16,10 +16,10 @@
 
 package org.gradle.api.plugins.announce.internal;
 
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
+
+import org.gradle.api.internal.ProcessOperations
 import org.gradle.api.plugins.announce.Announcer
-import org.gradle.api.Project
+import org.gradle.internal.os.OperatingSystem
 
 /**
  * This class wraps the Ubuntu Notify Send functionality.
@@ -28,17 +28,25 @@ import org.gradle.api.Project
  */
 
 class NotifySend implements Announcer {
-    private static final Logger logger = LoggerFactory.getLogger(NotifySend)
-
-    private final Project project
+    private final IconProvider iconProvider
+    private final ProcessOperations processOperations
 
-    NotifySend(Project project) {
-        this.project = project
+    NotifySend(ProcessOperations processOperations, IconProvider iconProvider) {
+        this.processOperations = processOperations
+        this.iconProvider = iconProvider
     }
 
     void send(String title, String message) {
-        project.exec {
-            executable 'notify-send'
+        File exe = OperatingSystem.current().findInPath("notify-send")
+        if (exe == null) {
+            throw new AnnouncerUnavailableException("Could not find 'notify-send' in the path.")
+        }
+        processOperations.exec {
+            executable exe
+            def icon = iconProvider.getIcon(32, 32)
+            if (icon) {
+                args '-i', icon.absolutePath
+            }
             args title, message
         }
     }
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Snarl.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Snarl.groovy
old mode 100644
new mode 100755
index 85f46a1..3459128
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Snarl.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/Snarl.groovy
@@ -21,19 +21,25 @@ import org.gradle.api.plugins.announce.Announcer
 class Snarl implements Announcer {
     private static final float SNP_VERSION = 1.1f
     private static final String HEAD = "type=SNP#?version=" + SNP_VERSION
+    private final IconProvider iconProvider
 
-    public void send(String title, String message) {
-        send("localhost", title, message)
+    Snarl(IconProvider iconProvider) {
+        this.iconProvider = iconProvider
     }
 
-    public void send(Collection hosts, String title, String message) {
-        hosts.each { host ->
-            send(host, title, message)
-        }
+    public void send(String title, String message) {
+        send(InetAddress.getByName(null), title, message)
     }
 
-    public void send(String host, String title, String message) {
-        with(new Socket(InetAddress.getByName(host), 9887)) { sock ->
+    public void send(InetAddress host, String title, String message) {
+        Socket socket
+        try {
+            socket = new Socket(host, 9887)
+        } catch (ConnectException e) {
+            // Snarl is not running
+            throw new AnnouncerUnavailableException("Snarl is not running on host $host.", e)
+        }
+        with(socket) { sock ->
             with(new PrintWriter(sock.getOutputStream(), true)) { out ->
                 out.println(formatMessage(title, message))
             }
@@ -47,7 +53,7 @@ class Snarl implements Announcer {
                 formatProperty("class", "alert"),
                 formatProperty("title", title),
                 formatProperty("text", message),
-                formatProperty("icon", null),
+                formatProperty("icon", iconProvider.getIcon(32, 32)?.absolutePath),
                 formatProperty("timeout", "10")]
 
         HEAD + properties.join('') + "\r\n"
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy
new file mode 100644
index 0000000..b9fe6d0
--- /dev/null
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/jdk6/AppleScriptBackedGrowlAnnouncer.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal.jdk6
+
+import javax.script.ScriptEngine
+import javax.script.ScriptEngineManager
+import org.gradle.api.plugins.announce.internal.Growl
+import org.gradle.api.plugins.announce.internal.IconProvider
+import org.gradle.api.plugins.announce.internal.AnnouncerUnavailableException
+
+class AppleScriptBackedGrowlAnnouncer extends Growl {
+    private final IconProvider iconProvider
+
+    AppleScriptBackedGrowlAnnouncer(IconProvider iconProvider) {
+        this.iconProvider = iconProvider
+    }
+
+    void send(String title, String message) {
+        ScriptEngineManager mgr = new ScriptEngineManager();
+        ScriptEngine engine = mgr.getEngineByName("AppleScript");
+
+        String isRunning = """
+tell application "System Events"
+set isRunning to count of (every process whose bundle identifier is "com.Growl.GrowlHelperApp") > 0
+end tell
+return isRunning
+"""
+        def value = engine.eval(isRunning)
+        if (value == 0) {
+            throw new AnnouncerUnavailableException("Growl is not running.")
+        }
+
+        def icon = iconProvider.getIcon(48, 48)
+        def iconDef = icon ? "image from location \"${icon.absolutePath}\"" : ""
+
+        def script = """
+tell application id "com.Growl.GrowlHelperApp"
+register as application "Gradle" all notifications {"Build Notification"} default notifications {"Build Notification"}
+notify with name "Build Notification" title "${escape(title)}" description "${escape(message)}" application name "Gradle"${iconDef}
+end tell
+"""
+
+        engine.eval(script)
+    }
+
+    private def escape(String value) {
+        return value.replace("\\", "\\\\").replace("\r", "\\r")
+    }
+}
diff --git a/subprojects/announce/src/main/resources/META-INF/gradle-plugins/build-announcements.properties b/subprojects/announce/src/main/resources/META-INF/gradle-plugins/build-announcements.properties
new file mode 100644
index 0000000..ac12343
--- /dev/null
+++ b/subprojects/announce/src/main/resources/META-INF/gradle-plugins/build-announcements.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.announce.BuildAnnouncementsPlugin
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginConventionTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginConventionTest.groovy
deleted file mode 100644
index f8ab6d6..0000000
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginConventionTest.groovy
+++ /dev/null
@@ -1,55 +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.api.plugins.announce
-
-import org.gradle.api.Project
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-import org.gradle.api.plugins.announce.internal.AnnouncerFactory
-
-/**
- * @author Hans Dockter
- */
-
-class AnnouncePluginConventionTest extends Specification {
-    Project project = HelperUtil.createRootProject()
-    AnnouncePluginConvention announcePluginConvention = new AnnouncePluginConvention(project)
-
-    def announceConfigureMethod() {
-        when:
-        announcePluginConvention.announce {
-            username = 'someUser'
-            password = 'somePassword'
-        }
-
-        then:
-        announcePluginConvention.username == 'someUser'
-        announcePluginConvention.password == 'somePassword'
-    }
-
-    def announce() {
-        AnnouncerFactory announcerFactory = Mock()
-        announcePluginConvention.announcerFactory = announcerFactory
-        Announcer announcer = Mock()
-        announcerFactory.createAnnouncer("someType") >> announcer
-
-        when:
-        announcePluginConvention.announce('someMessage', "someType")
-
-        then:
-        1 * announcer.send(project.name, 'someMessage')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy
new file mode 100644
index 0000000..a2279f4
--- /dev/null
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginExtensionTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 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.plugins.announce
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.announce.internal.AnnouncerFactory
+import spock.lang.Specification
+import org.gradle.util.HelperUtil
+
+/**
+ * @author Hans Dockter
+ */
+
+class AnnouncePluginExtensionTest extends Specification {
+    final AnnouncerFactory announcerFactory = Mock()
+    final ProjectInternal project = HelperUtil.createRootProject()
+    final AnnouncePluginExtension announcePluginConvention = new AnnouncePluginExtension(project)
+
+    def setup() {
+        announcePluginConvention.announcerFactory = announcerFactory
+    }
+
+    def announce() {
+        Announcer announcer = Mock()
+
+        when:
+        announcePluginConvention.announce('someMessage', "someType")
+
+        then:
+        1 * announcerFactory.createAnnouncer("someType") >> announcer
+        1 * announcer.send(project.name, 'someMessage')
+    }
+
+    def "creates and caches a local announcer on demand"() {
+        Announcer announcer = Mock()
+
+        when:
+        announcePluginConvention.local.send("title", "message")
+        announcePluginConvention.local.send("title2", "message2")
+
+        then:
+        1 * announcerFactory.createAnnouncer("local") >> announcer
+        1 * announcer.send("title", "message")
+        1 * announcer.send("title2", "message2")
+    }
+
+    def "can use a custom local announcer"() {
+        Announcer announcer = Mock()
+
+        given:
+        def local = announcePluginConvention.local
+
+        when:
+        announcePluginConvention.local = announcer
+        local.send("title", "message")
+
+        then:
+        1 * announcer.send("title", "message")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
index 38b8c9f..939c63b 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
@@ -26,11 +26,11 @@ class AnnouncePluginTest extends Specification {
     AnnouncePlugin announcePlugin = new AnnouncePlugin()
     Project project = HelperUtil.createRootProject()
 
-    def addConventionObject() {
+    def addExtensionObject() {
         when:
         announcePlugin.apply project
 
         then:
-        project.convention.plugins.announce instanceof AnnouncePluginConvention
+        project.extensions.announce instanceof AnnouncePluginExtension
     }
 }
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy
new file mode 100644
index 0000000..3604998
--- /dev/null
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/BuildAnnouncementsPluginTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.plugins.announce
+
+import spock.lang.Specification
+import org.gradle.util.HelperUtil
+import org.gradle.api.Project
+
+class BuildAnnouncementsPluginTest extends Specification {
+    final Project project = HelperUtil.createRootProject()
+    final BuildAnnouncementsPlugin plugin = new BuildAnnouncementsPlugin()
+
+    def "applies announce plugin"() {
+        when:
+        plugin.apply(project)
+
+        then:
+        project.plugins.hasPlugin(AnnouncePlugin)
+    }
+}
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/AnnouncingBuildListenerTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/AnnouncingBuildListenerTest.groovy
new file mode 100644
index 0000000..aa78d0b
--- /dev/null
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/AnnouncingBuildListenerTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import spock.lang.Specification
+import org.gradle.api.plugins.announce.Announcer
+import org.gradle.api.Task
+import org.gradle.BuildResult
+import org.gradle.api.tasks.TaskState
+
+class AnnouncingBuildListenerTest extends Specification {
+    final Announcer announcer = Mock()
+    final AnnouncingBuildListener listener = new AnnouncingBuildListener(announcer)
+
+    def "announces build successful with task count"() {
+        Task task = task()
+        TaskState taskState = taskSuccessful()
+        BuildResult result = buildSuccessful()
+
+        given:
+        listener.beforeExecute(task)
+        listener.afterExecute(task, taskState)
+
+        when:
+        listener.buildFinished(result)
+
+        then:
+        1 * announcer.send("Build successful", "1 task executed")
+    }
+
+    def "announces build failed when no tasks failed"() {
+        Task task = task()
+        TaskState taskState = taskSuccessful()
+        BuildResult result = buildFailed()
+
+        given:
+        listener.beforeExecute(task)
+        listener.afterExecute(task, taskState)
+
+        when:
+        listener.buildFinished(result)
+
+        then:
+        1 * announcer.send("Build failed", "1 task executed")
+    }
+
+    def "announces build failed when 1 task failed"() {
+        Task task = task()
+        TaskState taskState = taskFailed()
+        BuildResult result = buildFailed()
+
+        given:
+        listener.beforeExecute(task)
+        listener.afterExecute(task, taskState)
+
+        when:
+        listener.buildFinished(result)
+
+        then:
+        1 * announcer.send("Build failed", "task 'task' failed\n1 task executed")
+    }
+
+    def "announces build failed when multiple tasks failed"() {
+        Task task1 = task('task1')
+        Task task2 = task('task2')
+        TaskState taskState = taskFailed()
+        BuildResult result = buildFailed()
+
+        given:
+        listener.beforeExecute(task1)
+        listener.afterExecute(task1, taskState)
+
+        when:
+        listener.beforeExecute(task2)
+        listener.afterExecute(task2, taskState)
+
+        then:
+        1 * announcer.send("task 'task1' failed", "1 task failed")
+
+        when:
+        listener.buildFinished(result)
+
+        then:
+        1 * announcer.send("Build failed", "2 tasks failed\n2 tasks executed")
+    }
+
+    def task(String name = 'task') {
+        Task task = Mock()
+        task.toString() >> "task '$name'"
+        return task
+    }
+
+    def taskSuccessful() {
+        TaskState state = Mock()
+        return state
+    }
+
+    def taskFailed() {
+        TaskState state = Mock()
+        state.failure >> new RuntimeException()
+        return state
+    }
+
+    def buildSuccessful() {
+        BuildResult result = Mock()
+        return result
+    }
+
+    def buildFailed() {
+        BuildResult result = Mock()
+        result.failure >> new RuntimeException()
+        return result
+    }
+}
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy
index e44e45e..48dcbe3 100644
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactoryTest.groovy
@@ -15,44 +15,75 @@
  */
 package org.gradle.api.plugins.announce.internal
 
-import org.gradle.api.Project
+import org.gradle.api.internal.ProcessOperations
+import org.gradle.api.plugins.announce.AnnouncePluginExtension
+import org.gradle.internal.os.OperatingSystem
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
-import org.gradle.api.plugins.announce.AnnouncePluginConvention
 
 /**
  * @author Hans Dockter
  */
 class DefaultAnnouncerFactoryTest extends Specification {
-    AnnouncePluginConvention announcePluginConvention = new AnnouncePluginConvention(project)
-    DefaultAnnouncerFactory announcerFactory = new DefaultAnnouncerFactory(announcePluginConvention)
-    Project project = HelperUtil.createRootProject()
+    final project = HelperUtil.createRootProject()
+    final extension = new AnnouncePluginExtension(project)
+    final ProcessOperations processOperations = Mock()
+    final IconProvider iconProvider = Mock()
+    final announcerFactory = new DefaultAnnouncerFactory(extension, processOperations, iconProvider)
 
     def createForTwitter() {
-        announcePluginConvention.username = 'username'
-        announcePluginConvention.password = 'password'
+        extension.username = 'username'
+        extension.password = 'password'
 
         when:
-        Twitter twitter = announcerFactory.createAnnouncer('twitter')
+        def announcer = announcerFactory.createAnnouncer('twitter')
 
         then:
-        twitter.username == announcePluginConvention.username
-        twitter.password == announcePluginConvention.password
+        announcer instanceof IgnoreUnavailableAnnouncer
+        def twitter = announcer.announcer
+        twitter.username == extension.username
+        twitter.password == extension.password
     }
 
     def createForSnarl() {
-        expect:
-        announcerFactory.createAnnouncer('snarl') instanceof Snarl
+        when:
+        def announcer = announcerFactory.createAnnouncer('snarl')
+
+        then:
+        announcer instanceof IgnoreUnavailableAnnouncer
+        announcer.announcer instanceof Snarl
     }
 
     def createForNotifySend() {
-        expect:
-        announcerFactory.createAnnouncer('notify-send') instanceof NotifySend
+        when:
+        def announcer = announcerFactory.createAnnouncer('notify-send')
+
+        then:
+        announcer instanceof IgnoreUnavailableAnnouncer
+        announcer.announcer instanceof NotifySend
     }
 
     def createForGrowl() {
+        when:
+        def announcer = announcerFactory.createAnnouncer('growl')
+
+        then:
+        announcer instanceof IgnoreUnavailableAnnouncer
+        announcer.announcer instanceof Growl
+    }
+
+    def createForLocal() {
+        def expectedType
+        if (OperatingSystem.current().windows) {
+            expectedType = Snarl
+        } else if (OperatingSystem.current().macOsX) {
+            expectedType = Growl
+        } else {
+            expectedType = NotifySend
+        }
+
         expect:
-        announcerFactory.createAnnouncer('growl') instanceof Growl
+        expectedType.isInstance(announcerFactory.createAnnouncer('local').announcer)
     }
 
     def createWithUnknownType() {
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy
new file mode 100644
index 0000000..cb4697a
--- /dev/null
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/DefaultIconProviderTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import org.gradle.api.internal.GradleDistributionLocator
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultIconProviderTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final GradleDistributionLocator locator = Mock()
+    final DefaultIconProvider provider = new DefaultIconProvider(locator)
+    
+    def "returns png file that matches specified dimensions exactly"() {
+        given:
+        def homeDir = tmpDir.dir
+        def pngFile = tmpDir.createFile("media/gradle-icon-48x18.png")
+        _ * locator.gradleHome >> homeDir
+
+        expect:
+        provider.getIcon(48, 18) == pngFile
+    }
+    
+    def "returns null when no distribution home"() {
+        given:
+        _ * locator.gradleHome >> null
+
+        expect:
+        provider.getIcon(48, 18) == null
+    }
+
+    def "returns null when no icon with exact dimension"() {
+        given:
+        def homeDir = tmpDir.dir
+        _ * locator.gradleHome >> homeDir
+
+        expect:
+        provider.getIcon(48, 18) == null
+    }
+}
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/IgnoreUnavailableAnnouncerTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/IgnoreUnavailableAnnouncerTest.groovy
new file mode 100644
index 0000000..097fc1d
--- /dev/null
+++ b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/IgnoreUnavailableAnnouncerTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.plugins.announce.internal
+
+import spock.lang.Specification
+import org.gradle.api.plugins.announce.Announcer
+
+class IgnoreUnavailableAnnouncerTest extends Specification {
+    final Announcer target = Mock()
+    final IgnoreUnavailableAnnouncer announcer = new IgnoreUnavailableAnnouncer(target)
+
+    def "ignores unavailable exception"() {
+        given:
+        target.send("title", "message") >> { throw new AnnouncerUnavailableException("broken") }
+
+        when:
+        announcer.send("title", "message")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "throws other exceptions"() {
+        def failure = new RuntimeException()
+
+        given:
+        target.send("title", "message") >> { throw failure }
+
+        when:
+        announcer.send("title", "message")
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+}
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/NotifySendTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/NotifySendTest.groovy
deleted file mode 100644
index 5cd45a6..0000000
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/NotifySendTest.groovy
+++ /dev/null
@@ -1,40 +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.api.plugins.announce.internal
-
-import org.gradle.api.Project
-import spock.lang.Specification
-import org.gradle.util.ConfigureUtil
-import org.gradle.process.internal.ExecHandleBuilder
-import org.gradle.process.ExecResult
-
-class NotifySendTest extends Specification {
-    def "sending of an announcement invokes notify-send command"() {
-        def execClosure
-        def project = Mock(Project)
-        def notifier = new NotifySend(project)
-
-        when:
-        notifier.send("title", "body")
-
-        then:
-        1 * project.exec(!null) >> { execClosure = it[0]; Mock(ExecResult) }
-        def execSpec = ConfigureUtil.configure(execClosure, new ExecHandleBuilder())
-        execSpec.executable == 'notify-send'
-        execSpec.args.contains 'title'
-        execSpec.args.contains 'body'
-    }
-}
diff --git a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/SnarlTest.groovy b/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/SnarlTest.groovy
deleted file mode 100644
index 63d5cbc..0000000
--- a/subprojects/announce/src/test/groovy/org/gradle/api/plugins/announce/internal/SnarlTest.groovy
+++ /dev/null
@@ -1,41 +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.api.plugins.announce.internal;
-
-
-class SnarlTest extends GroovyTestCase {
-//	public void testSend() {
-//		new Snarl().send("JUnit Test", "Hello from Groovy!");
-//	}
-//	public void testSend_MultipleHosts() {
-//		new Snarl().send(["localhost", "localhost", "localhost"], "JUnit Test", "Hello from Groovy!");
-//	}
-
-    void testMockSend() {
-//		use(PrintWriterCapture) {
-//			new Snarl().send("some title", "some message")
-//			assert PrintWriterCapture.capture == "type=SNP#?version=1.1#?action=notification#?app=Gradle Snarl Notifier#?class=alert#?title=some title#?text=some message#?timeout=10\r\n"
-//		}
-    }
-
-}
-class PrintWriterCapture {
-    static capture
-
-    static void println(PrintWriter p, String input) {
-        capture = input
-    }
-}
diff --git a/subprojects/antlr/antlr.gradle b/subprojects/antlr/antlr.gradle
index d6b6446..5c614f1 100644
--- a/subprojects/antlr/antlr.gradle
+++ b/subprojects/antlr/antlr.gradle
@@ -13,19 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-apply plugin: 'groovy'
-
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
 
-    compile libraries.slf4j_api,
-            libraries.ant,
-            libraries.ant_antlr,
-            libraries.antlr
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    compile libraries.slf4j_api
+    compile libraries.ant
+    compile libraries.ant_antlr
+    compile libraries.antlr
 }
+
+useTestFixtures()
diff --git a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
index da6089b..b8b3cce 100644
--- a/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
+++ b/subprojects/antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
@@ -16,35 +16,31 @@
 
 package org.gradle.api.plugins.antlr;
 
-import java.io.File;
-
 import org.gradle.api.Action;
 import org.gradle.api.Plugin;
-import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.DynamicObjectAware;
-import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.DefaultSourceSet;
-import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.JavaPlugin;
-
-import static org.gradle.api.plugins.JavaPlugin.COMPILE_CONFIGURATION_NAME;
-
 import org.gradle.api.plugins.JavaPluginConvention;
 import org.gradle.api.plugins.antlr.internal.AntlrSourceVirtualDirectoryImpl;
-import org.gradle.api.tasks.ConventionValue;
 import org.gradle.api.tasks.SourceSet;
 
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.gradle.api.plugins.JavaPlugin.COMPILE_CONFIGURATION_NAME;
+
 /**
  * A plugin for adding Antlr support to {@link JavaPlugin java projects}.
  *
  * @author Steve Ebersole
  */
-public class AntlrPlugin implements Plugin<Project> {
+public class AntlrPlugin implements Plugin<ProjectInternal> {
     public static final String ANTLR_CONFIGURATION_NAME = "antlr";
 
-    public void apply(final Project project) {
+    public void apply(final ProjectInternal project) {
         project.getPlugins().apply(JavaPlugin.class);
 
         // set up a configuration named 'antlr' for the user to specify the antlr libs to use in case
@@ -53,16 +49,14 @@ public class AntlrPlugin implements Plugin<Project> {
                 .setTransitive(false).setDescription("The Antlr libraries to be used for this project.");
         project.getConfigurations().getByName(COMPILE_CONFIGURATION_NAME).extendsFrom(antlrConfiguration);
 
-        final ProjectInternal projectInternal = (ProjectInternal) project;
         project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().all(
                 new Action<SourceSet>() {
                     public void execute(SourceSet sourceSet) {
                         // for each source set we will:
                         // 1) Add a new 'antlr' virtual directory mapping
                         final AntlrSourceVirtualDirectoryImpl antlrDirectoryDelegate
-                                = new AntlrSourceVirtualDirectoryImpl(((DefaultSourceSet) sourceSet).getDisplayName(),
-                                projectInternal.getFileResolver());
-                        ((DynamicObjectAware) sourceSet).getConvention().getPlugins().put(
+                                = new AntlrSourceVirtualDirectoryImpl(((DefaultSourceSet) sourceSet).getDisplayName(), project.getFileResolver());
+                        new DslObject(sourceSet).getConvention().getPlugins().put(
                                 AntlrSourceVirtualDirectory.NAME, antlrDirectoryDelegate);
                         final String srcDir = String.format("src/%s/antlr", sourceSet.getName());
                         antlrDirectoryDelegate.getAntlr().srcDir(srcDir);
@@ -76,15 +70,11 @@ public class AntlrPlugin implements Plugin<Project> {
                                 sourceSet.getName()));
 
                         // 3) set up convention mapping for default sources (allows user to not have to specify)
-                        antlrTask.conventionMapping("defaultSource", new ConventionValue() {
-                            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                                return antlrDirectoryDelegate.getAntlr();
-                            }
-                        });
+                        antlrTask.setSource(antlrDirectoryDelegate.getAntlr());
 
                         // 4) set up convention mapping for handling the 'antlr' dependency configuration
-                        antlrTask.getConventionMapping().map("antlrClasspath", new ConventionValue() {
-                            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        antlrTask.getConventionMapping().map("antlrClasspath", new Callable<Object>() {
+                            public Object call() throws Exception {
                                 return project.getConfigurations().getByName(ANTLR_CONFIGURATION_NAME).copy()
                                         .setTransitive(true);
                             }
diff --git a/subprojects/base-services/base-services.gradle b/subprojects/base-services/base-services.gradle
new file mode 100644
index 0000000..10a94af
--- /dev/null
+++ b/subprojects/base-services/base-services.gradle
@@ -0,0 +1,15 @@
+/*
+ * A set of generic services and utilities.
+ *
+ * Should have a very small set of dependencies, and should be appropriate to embed in an external
+ * application (eg as part of the tooling API).
+ */
+apply from: "$rootDir/gradle/classycle.gradle"
+
+dependencies {
+    groovy libraries.groovy
+
+    publishCompile libraries.slf4j_api
+}
+
+useTestFixtures()
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
new file mode 100644
index 0000000..77ccb4c
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * @deprecated This is here because Groovy tasks compiled against older versions have this type baked into their byte-code, and cannot be loaded if it's not found.
+ */
+ at Deprecated
+public interface Factory<T> extends org.gradle.internal.Factory<T> {
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
new file mode 100644
index 0000000..05a3cce
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
@@ -0,0 +1,23 @@
+/*
+ * 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.project;
+
+/**
+ * @deprecated This is here because Groovy tasks compiled against older versions have this type baked into their byte-code, and cannot be loaded if it's not found.
+ */
+ at Deprecated
+public interface ServiceRegistry extends org.gradle.internal.service.ServiceRegistry {
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
new file mode 100644
index 0000000..548a4ee
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CompositeStoppable implements Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeStoppable.class);
+    private final List<Stoppable> elements = new CopyOnWriteArrayList<Stoppable>();
+
+    public CompositeStoppable(Object... elements) {
+        add(elements);
+    }
+
+    public CompositeStoppable(Iterable<?> elements) {
+        add(elements);
+    }
+
+    public CompositeStoppable add(Iterable<?> elements) {
+        for (Object element : elements) {
+            this.elements.add(toStoppable(element));
+        }
+        return this;
+    }
+
+    public CompositeStoppable add(Object... elements) {
+        for (Object closeable : elements) {
+            this.elements.add(toStoppable(closeable));
+        }
+        return this;
+    }
+
+    private static Object invoke(Method method, Object target, Object... args) {
+        try {
+            method.setAccessible(true);
+            return method.invoke(target, args);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private static Stoppable toStoppable(final Object object) {
+        if (object == null) {
+            return new Stoppable() {
+                public void stop() {
+                }
+            };
+        }
+        if (object instanceof Iterable) {
+            throw new UnsupportedOperationException("Not implemented yet.");
+        }
+        if (object instanceof Stoppable) {
+            return (Stoppable) object;
+        }
+        if (object instanceof Closeable) {
+            final Closeable closeable = (Closeable) object;
+            return new Stoppable() {
+                @Override
+                public String toString() {
+                    return closeable.toString();
+                }
+
+                public void stop() {
+                    try {
+                        closeable.close();
+                    } catch (IOException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+            };
+        }
+        return new Stoppable() {
+            @Override
+            public String toString() {
+                return object.toString();
+            }
+
+            public void stop() {
+                try {
+                    invoke(object.getClass().getMethod("stop"), object);
+                } catch (NoSuchMethodException e) {
+                    // ignore
+                }
+                try {
+                    invoke(object.getClass().getMethod("close"), object);
+                } catch (NoSuchMethodException e) {
+                    // ignore
+                }
+            }
+        };
+    }
+
+    public void stop() {
+        Throwable failure = null;
+        try {
+            for (Stoppable element : elements) {
+                try {
+                    element.stop();
+                } catch (Throwable throwable) {
+                    if (failure == null) {
+                        failure = throwable;
+                    } else {
+                        LOGGER.error(String.format("Could not stop %s.", element), throwable);
+                    }
+                }
+            }
+        } finally {
+            elements.clear();
+        }
+
+        if (failure != null) {
+            throw UncheckedException.throwAsUncheckedException(failure);
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Factory.java b/subprojects/base-services/src/main/java/org/gradle/internal/Factory.java
new file mode 100644
index 0000000..6253ed4
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Factory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal;
+
+/**
+ * A generic factory which creates instances of type T.
+ *
+ * @param <T> The type of object created.
+ */
+public interface Factory<T> {
+    /**
+     * Creates a new instance of type T.
+     * @return The instance. Never returns null.
+     */
+    T create();
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Stoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/Stoppable.java
new file mode 100755
index 0000000..af5dc11
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/Stoppable.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal;
+
+/**
+ * Represents an object which performs concurrent activity.
+ */
+public interface Stoppable {
+    /**
+     * <p>Requests a graceful stop of this object. Blocks until all concurrent activity has been completed.</p>
+     *
+     * <p>If this object has already been stopped, this method does nothing.</p>
+     */
+    void stop();
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
new file mode 100644
index 0000000..49d9559
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.internal;
+
+import java.util.Map;
+
+/**
+ * Provides access to frequently used system properties.
+ */
+public class SystemProperties {
+    @SuppressWarnings("unchecked")
+    public static Map<String, String> asMap() {
+        return (Map) System.getProperties();
+    }
+
+    public static String getLineSeparator() {
+        return System.getProperty("line.separator");
+    }
+
+    public static String getJavaIoTmpDir() {
+        return System.getProperty("java.io.tmpdir");
+    }
+
+    public static String getUserHome() {
+        return System.getProperty("user.home");
+    }
+
+    public static String getJavaVersion() {
+        return System.getProperty("java.version");
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java b/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java
new file mode 100644
index 0000000..8e6119a
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/UncheckedException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.internal;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Wraps a checked exception. Carries no other context.
+ */
+public final class UncheckedException extends RuntimeException {
+    public UncheckedException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Note: always throws the failure in some form. The return value is to keep the compiler happy.
+     */
+    public static RuntimeException throwAsUncheckedException(Throwable t) {
+        if (t instanceof RuntimeException) {
+            throw (RuntimeException) t;
+        }
+        if (t instanceof Error) {
+            throw (Error) t;
+        }
+        throw new UncheckedException(t);
+    }
+
+    /**
+     * Uwraps passed InvocationTargetException hence making the stack of exceptions cleaner without losing information.
+     *
+     * Note: always throws the failure in some form. The return value is to keep the compiler happy.
+     *
+     * @param e to be unwrapped
+     * @return an instance of RuntimeException based on the target exception of the parameter.
+     */
+    public static RuntimeException unwrapAndRethrow(InvocationTargetException e) {
+        return UncheckedException.throwAsUncheckedException(e.getTargetException());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ThreadSafe.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ThreadSafe.java
new file mode 100644
index 0000000..bcc82d1
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ThreadSafe.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.internal.concurrent;
+
+/**
+ * A marker interface to indicate that the implementing class is thread-safe.
+ */
+public interface ThreadSafe {
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java
new file mode 100644
index 0000000..92335e4
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaHomeException.java
@@ -0,0 +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.internal.jvm;
+
+/**
+ * by Szczepan Faber, created at: 1/23/12
+ */
+public class JavaHomeException extends RuntimeException {
+    public JavaHomeException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.java
new file mode 100644
index 0000000..c52f229
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/JavaInfo.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.internal.jvm;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 2/6/12
+ */
+public interface JavaInfo {
+    /**
+     * @return the executable
+     * @throws JavaHomeException when executable cannot be found
+     */
+    File getJavaExecutable() throws JavaHomeException;
+
+    /**
+     * @return the executable
+     * @throws JavaHomeException when executable cannot be found
+     */
+    File getJavadocExecutable() throws JavaHomeException;
+
+    /**
+     * @return the executable
+     * @throws JavaHomeException when executable cannot be found
+     */
+    File getExecutable(String name) throws JavaHomeException;
+
+    /**
+     * The location of java.
+     *
+     * @return the java home location
+     */
+    File getJavaHome();
+
+    /**
+     * Returns the runtime jar. May return null, for example when Jvm was created
+     * with custom jdk location.
+     */
+    File getRuntimeJar();
+
+    /**
+     * Returns the tools jar. May return null, for example when Jvm was created via
+     * with custom jre location or if jdk is not installed.
+     */
+    File getToolsJar();
+
+    Map<String, ?> getInheritableEnvironmentVariables(Map<String, ?> envVars);
+
+    boolean getSupportsAppleScript();
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
new file mode 100644
index 0000000..8d4c436
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2012 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.internal.jvm;
+
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.os.OperatingSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Jvm implements JavaInfo {
+    
+    private final static Logger LOGGER = LoggerFactory.getLogger(Jvm.class);
+    
+    private final OperatingSystem os;
+    //supplied java location
+    private final File javaBase;
+    //discovered java location
+    private final File javaHome;
+    private final boolean userSupplied;
+
+    public static Jvm current() {
+        return create(null);
+    }
+
+    private static Jvm create(File javaBase) {
+        String vendor = System.getProperty("java.vm.vendor");
+        if (vendor.toLowerCase().startsWith("apple inc.")) {
+            return new AppleJvm(OperatingSystem.current(), javaBase);
+        }
+        if (vendor.toLowerCase().startsWith("ibm corporation")) {
+            return new IbmJvm(OperatingSystem.current(), javaBase);
+        }
+        return new Jvm(OperatingSystem.current(), javaBase);
+    }
+
+    Jvm(OperatingSystem os) {
+        this(os, null);
+    }
+
+    /**
+     * @param os the OS
+     * @param suppliedJavaBase initial location to discover from. May be jdk or jre.
+     */
+    Jvm(OperatingSystem os, File suppliedJavaBase) {
+        this.os = os;
+        if (suppliedJavaBase == null) {
+            //discover based on what's in the sys. property
+            try {
+                this.javaBase = new File(System.getProperty("java.home")).getCanonicalFile();
+            } catch (IOException e) {
+                throw new UncheckedException(e);
+            }
+            this.javaHome = findJavaHome(javaBase);
+            this.userSupplied = false;
+        } else {
+            //precisely use what the user wants and validate strictly further on
+            this.javaBase = suppliedJavaBase;
+            this.javaHome = suppliedJavaBase;
+            this.userSupplied = true;
+        }
+    }
+    
+    /**
+     * Creates jvm instance for given java home. Attempts to validate if provided javaHome is a valid jdk or jre location.
+     *
+     * @param javaHome - location of your jdk or jre (jdk is safer), cannot be null
+     * @return jvm for given java home
+     *
+     * @throws org.gradle.internal.jvm.JavaHomeException when supplied javaHome does not seem to be a valid jdk or jre location
+     * @throws IllegalArgumentException when supplied javaHome is not a valid folder
+     */
+    public static JavaInfo forHome(File javaHome) throws JavaHomeException, IllegalArgumentException {
+        if (javaHome == null || !javaHome.isDirectory()) {
+            throw new IllegalArgumentException("Supplied javaHome must be a valid directory. You supplied: " + javaHome);
+        }
+        Jvm jvm = create(javaHome);
+        //some validation:
+        jvm.getJavaExecutable();
+        return jvm;
+    }
+
+    @Override
+    public String toString() {
+        if (userSupplied) {
+            return "User-supplied java: " + javaBase;
+        }
+        return String.format("%s (%s %s)", SystemProperties.getJavaVersion(), System.getProperty("java.vm.vendor"), System.getProperty("java.vm.version"));
+    }
+
+    private File findExecutable(String command) {
+        File exec = new File(getJavaHome(), "bin/" + command);
+        File executable = new File(os.getExecutableName(exec.getAbsolutePath()));
+        if (executable.isFile()) {
+            return executable;
+        }
+
+        if (userSupplied) { //then we want to validate strictly
+            throw new JavaHomeException(String.format("The supplied javaHome seems to be invalid."
+                    + " I cannot find the %s executable. Tried location: %s", command, executable.getAbsolutePath()));
+        }
+
+        File pathExecutable = os.findInPath(command);
+        if (pathExecutable != null) {
+            LOGGER.info(String.format("Unable to find the '%s' executable using home: %s. We found it on the PATH: %s.",
+                    command, getJavaHome(), pathExecutable));
+            return pathExecutable;
+        }
+
+        LOGGER.warn("Unable to find the '{}' executable. Tried the java home: {} and the PATH."
+                + " We will assume the executable can be ran in the current working folder.",
+                command, getJavaHome());
+        return new File(os.getExecutableName(command));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getJavaExecutable() throws JavaHomeException {
+        return findExecutable("java");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getJavadocExecutable() throws JavaHomeException {
+        return findExecutable("javadoc");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getExecutable(String name) throws JavaHomeException {
+        return findExecutable(name);
+    }
+
+    public boolean isJava5() {
+        return SystemProperties.getJavaVersion().startsWith("1.5");    
+    }
+
+    public boolean isJava6() {
+        return SystemProperties.getJavaVersion().startsWith("1.6");
+    }
+
+    public boolean isJava7() {
+        return SystemProperties.getJavaVersion().startsWith("1.7");
+    }
+
+    public boolean isJava5Compatible() {
+         return isJava5() || isJava6Compatible();
+    }
+
+    public boolean isJava6Compatible() {
+        return isJava6() || isJava7();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getJavaHome() {
+        return javaHome;
+    }
+
+    private File findJavaHome(File javaBase) {
+        File toolsJar = findToolsJar(javaBase);
+        if (toolsJar != null) {
+            return toolsJar.getParentFile().getParentFile();
+        } else if (javaBase.getName().equalsIgnoreCase("jre") && new File(javaBase.getParentFile(), "bin/java").exists()) {
+            return javaBase.getParentFile();
+        } else {
+            return javaBase;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getRuntimeJar() {
+        File runtimeJar = new File(javaBase, "lib/rt.jar");
+        return runtimeJar.exists() ? runtimeJar : null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getToolsJar() {
+        return findToolsJar(javaBase);
+    }
+
+    private File findToolsJar(File javaHome) {
+        File toolsJar = new File(javaHome, "lib/tools.jar");
+        if (toolsJar.exists()) {
+            return toolsJar;
+        }
+        if (javaHome.getName().equalsIgnoreCase("jre")) {
+            javaHome = javaHome.getParentFile();
+            toolsJar = new File(javaHome, "lib/tools.jar");
+            if (toolsJar.exists()) {
+                return toolsJar;
+            }
+        }
+        if (javaHome.getName().matches("jre\\d+") && os.isWindows()) {
+            javaHome = new File(javaHome.getParentFile(), String.format("jdk%s", SystemProperties.getJavaVersion()));
+            toolsJar = new File(javaHome, "lib/tools.jar");
+            if (toolsJar.exists()) {
+                return toolsJar;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String, ?> getInheritableEnvironmentVariables(Map<String, ?> envVars) {
+        return envVars;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getSupportsAppleScript() {
+        return false;
+    }
+
+    public boolean isIbmJvm() {
+        return false;
+    }
+
+    static class IbmJvm extends Jvm {
+        IbmJvm(OperatingSystem os, File suppliedJavaBase) {
+            super(os, suppliedJavaBase);
+        }
+
+        @Override
+        public boolean isIbmJvm() {
+            return true;
+        }
+    }
+
+    /**
+     * Note: Implementation assumes that an Apple JVM always comes with a JDK rather than a JRE,
+     * but this is likely an over-simplification.
+     */
+    static class AppleJvm extends Jvm {
+        AppleJvm(OperatingSystem os) {
+            super(os);
+        }
+
+        AppleJvm(OperatingSystem current, File javaHome) {
+            super(current, javaHome);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public File getJavaHome() {
+            return super.getJavaHome();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public File getRuntimeJar() {
+            File javaHome = super.getJavaHome();
+            File runtimeJar = new File(javaHome.getParentFile(), "Classes/classes.jar");
+            return runtimeJar.exists() ? runtimeJar : null;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public File getToolsJar() {
+            return getRuntimeJar();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public Map<String, ?> getInheritableEnvironmentVariables(Map<String, ?> envVars) {
+            Map<String, Object> vars = new HashMap<String, Object>();
+            for (Map.Entry<String, ?> entry : envVars.entrySet()) {
+                if (entry.getKey().matches("APP_NAME_\\d+") || entry.getKey().matches("JAVA_MAIN_CLASS_\\d+")) {
+                    continue;
+                }
+                vars.put(entry.getKey(), entry.getValue());
+            }
+            return vars;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean getSupportsAppleScript() {
+            return true;
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java b/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
new file mode 100755
index 0000000..08dd03d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
@@ -0,0 +1,278 @@
+/*
+ * 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.internal.os;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class OperatingSystem {
+    private static final Windows WINDOWS = new Windows();
+    private static final MacOs MAC_OS = new MacOs();
+    private static final Solaris SOLARIS = new Solaris();
+    private static final Linux LINUX = new Linux();
+    private static final Unix UNIX = new Unix();
+
+    public static OperatingSystem current() {
+        String osName = System.getProperty("os.name").toLowerCase();
+        if (osName.contains("windows")) {
+            return WINDOWS;
+        } else if (osName.contains("mac os x") || osName.contains("darwin")) {
+            return MAC_OS;
+        } else if (osName.contains("sunos")) {
+            return SOLARIS;
+        } else if (osName.contains("linux")) {
+            return LINUX;
+        } else {
+            // Not strictly true
+            return UNIX;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s %s %s", getName(), getVersion(), System.getProperty("os.arch"));
+    }
+
+    public String getName() {
+        return System.getProperty("os.name");
+    }
+    
+    public String getVersion() {
+        return System.getProperty("os.version");
+    }
+
+    public boolean isWindows() {
+        return false;
+    }
+
+    public boolean isUnix() {
+        return false;
+    }
+
+    public boolean isMacOsX() {
+        return false;
+    }
+
+    public boolean isLinux() {
+        return false;
+    }
+
+    public abstract String getNativePrefix();
+
+    public abstract String getScriptName(String scriptPath);
+
+    public abstract String getExecutableName(String executablePath);
+
+    public abstract String getSharedLibraryName(String libraryName);
+
+    /**
+     * Locates the given executable in the system path. Returns null if not found.
+     */
+    public File findInPath(String name) {
+        String exeName = getExecutableName(name);
+        if (exeName.contains(File.separator)) {
+            File candidate = new File(exeName);
+            if (candidate.isFile()) {
+                return candidate;
+            }
+            return null;
+        }
+        for (File dir : getPath()) {
+            File candidate = new File(dir, exeName);
+            if (candidate.isFile()) {
+                return candidate;
+            }
+        }
+
+        return null;
+    }
+
+    public List<File> findAllInPath(String name) {
+        List<File> all = new LinkedList<File>();
+
+        for (File dir : getPath()) {
+            File candidate = new File(dir, name);
+            if (candidate.isFile()) {
+                all.add(candidate);
+            }
+        }
+
+        return all;
+    }
+    
+    List<File> getPath() {                       
+        String path = System.getenv("PATH");
+        if (path == null) {
+            return Collections.emptyList();
+        }
+        List<File> entries = new ArrayList<File>();
+        for (String entry : path.split(Pattern.quote(File.pathSeparator))) {
+            entries.add(new File(entry));
+        }
+        return entries;
+    }
+
+    static class Windows extends OperatingSystem {
+        @Override
+        public boolean isWindows() {
+            return true;
+        }
+
+        @Override
+        public String getScriptName(String scriptPath) {
+            if (scriptPath.toLowerCase().endsWith(".bat")) {
+                return scriptPath;
+            }
+            return scriptPath + ".bat";
+        }
+
+        @Override
+        public String getExecutableName(String executablePath) {
+            return withSuffix(executablePath, ".exe");
+        }
+
+        @Override
+        public String getSharedLibraryName(String libraryPath) {
+            return withSuffix(libraryPath, ".dll");
+        }
+
+        @Override
+        public String getNativePrefix() {
+            String arch = System.getProperty("os.arch");
+            if ("i386".equals(arch)) {
+                arch = "x86";
+            }
+            return "win32-" + arch;
+        }
+
+        private String withSuffix(String executablePath, String extension) {
+            if (executablePath.toLowerCase().endsWith(extension)) {
+                return executablePath;
+            }
+            return executablePath + extension;
+        }
+    }
+
+    static class Unix extends OperatingSystem {
+        @Override
+        public String getScriptName(String scriptPath) {
+            return scriptPath;
+        }
+
+        @Override
+        public String getExecutableName(String executablePath) {
+            return executablePath;
+        }
+
+        @Override
+        public String getSharedLibraryName(String libraryName) {
+            String suffix = getSharedLibSuffix();
+            if (libraryName.endsWith(suffix)) {
+                return libraryName;
+            }
+            int pos = libraryName.lastIndexOf('/');
+            if (pos >= 0) {
+                return libraryName.substring(0, pos + 1) + "lib" + libraryName.substring(pos + 1) + suffix;
+            } else {
+                return "lib" + libraryName + suffix;
+            }
+        }
+
+        protected String getSharedLibSuffix() {
+            return ".so";
+        }
+
+        @Override
+        public boolean isUnix() {
+            return true;
+        }
+
+        public String getNativePrefix() {
+            String arch = getArch();
+            String osPrefix = getOsPrefix();
+            osPrefix += "-" + arch;
+            return osPrefix;
+        }
+
+        protected String getArch() {
+            String arch = System.getProperty("os.arch");
+            if ("x86".equals(arch)) {
+                arch = "i386";
+            }
+            if ("x86_64".equals(arch)) {
+                arch = "amd64";
+            }
+            if ("powerpc".equals(arch)) {
+                arch = "ppc";
+            }
+            return arch;
+        }
+
+        protected String getOsPrefix() {
+            String osPrefix = getName().toLowerCase();
+            int space = osPrefix.indexOf(" ");
+            if (space != -1) {
+                osPrefix = osPrefix.substring(0, space);
+            }
+            return osPrefix;
+        }
+    }
+
+    static class MacOs extends Unix {
+        @Override
+        public boolean isMacOsX() {
+            return true;
+
+        }
+
+        @Override
+        protected String getSharedLibSuffix() {
+            return ".dylib";
+        }
+
+        @Override
+        public String getNativePrefix() {
+            return "darwin";
+        }
+    }
+
+    static class Linux extends Unix {
+        @Override
+        public boolean isLinux() {
+            return true;
+        }
+    }
+
+    static class Solaris extends Unix {
+        @Override
+        protected String getOsPrefix() {
+            return "sunos";
+        }
+
+        @Override
+        protected String getArch() {
+            String arch = System.getProperty("os.arch");
+            if (arch.equals("i386") || arch.equals("x86")) {
+                return "x86";
+            }
+            return super.getArch();
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java
new file mode 100644
index 0000000..70520d8
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java
@@ -0,0 +1,459 @@
+/*
+ * 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.internal.service;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Factory;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * A hierarchical {@link ServiceRegistry} implementation.
+ *
+ * <p>Subclasses can register services by:</p>
+ *
+ * <li>Calling {@link #add(Class, Object)} to register a service instance.</li>
+ *
+ * <li>Calling {@link #add(ServiceRegistry)} to register a set of services.</li>
+ *
+ * <li>Adding a factory method. A factory method should have a name that starts with 'create', take no parameters, and have a non-void return type. For example, <code>protected SomeService
+ * createSomeService() { .... }</code>.</li>
+ *
+ * <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single parameter, and a have a non-void return type. The before invoking the method, the
+ * parameter is located in the parent service registry and then passed to the method.</li>
+ *
+ * </ul>
+ *
+ * <p>Service instances are created on demand. {@link #getFactory(Class)} looks for a service instance which implements {@code Factory<T>} where {@code T} is the expected type.</p>.
+ *
+ * <p>Service registries are arranged in a hierarchy. If a service of a given type cannot be located, the registry uses its parent registry, if any, to locate the service.</p>
+ */
+public class DefaultServiceRegistry implements ServiceRegistry {
+    private final List<Provider> providers = new LinkedList<Provider>();
+    private final OwnServices ownServices;
+    private final List<Provider> registeredProviders;
+    private final ServiceRegistry parent;
+    private boolean closed;
+
+    public DefaultServiceRegistry() {
+        this(null);
+    }
+
+    public DefaultServiceRegistry(ServiceRegistry parent) {
+        this.parent = parent;
+        this.ownServices = new OwnServices();
+        providers.add(ownServices);
+        if (parent != null) {
+            providers.add(new ParentServices());
+        }
+        registeredProviders = providers.subList(1, 1);
+
+        findProviderMethods();
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
+    private void findProviderMethods() {
+        Set<String> factoryMethods = new HashSet<String>();
+        Set<String> decoratorMethods = new HashSet<String>();
+        for (Class<?> type = getClass(); type != Object.class; type = type.getSuperclass()) {
+            findFactoryMethods(type, factoryMethods, ownServices);
+            findDecoratorMethods(type, decoratorMethods, ownServices);
+        }
+    }
+
+    private void findFactoryMethods(Class<?> type, Set<String> factoryMethods, OwnServices ownServices) {
+        for (Method method : type.getDeclaredMethods()) {
+            if (method.getName().startsWith("create")
+                    && method.getParameterTypes().length == 0
+                    && method.getReturnType() != Void.class) {
+                if (factoryMethods.add(method.getName())) {
+                    ownServices.add(new FactoryMethodService(method));
+                }
+            }
+        }
+    }
+
+    private void findDecoratorMethods(Class<?> type, Set<String> decoratorMethods, OwnServices ownServices) {
+        for (Method method : type.getDeclaredMethods()) {
+            if (method.getName().startsWith("create")
+                    && method.getParameterTypes().length == 1
+                    && method.getReturnType() != Void.class
+                    && method.getParameterTypes()[0].equals(method.getReturnType())) {
+                if (parent == null) {
+                    throw new IllegalArgumentException("Cannot use decorator methods when no parent registry is provided.");
+                }
+                if (decoratorMethods.add(method.getName())) {
+                    ownServices.add(new DecoratorMethodService(method));
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a set of services to this registry. The given registry is closed when this registry is closed.
+     */
+    public void add(ServiceRegistry nested) {
+        registeredProviders.add(new NestedServices(nested));
+    }
+
+    /**
+     * Adds a service to this registry. The given object is closed when this registry is closed.
+     */
+    public <T> void add(Class<T> serviceType, final T serviceInstance) {
+        ownServices.add(new FixedInstanceService<T>(serviceType, serviceInstance));
+    }
+
+    /**
+     * Closes all services for this registry. For each service, if the service has a public void close() method, that method is called to close the service.
+     */
+    public void close() {
+        try {
+            new CompositeStoppable(providers).stop();
+        } finally {
+            closed = true;
+            providers.clear();
+        }
+    }
+
+    public <T> T get(Class<T> serviceType) throws IllegalArgumentException {
+        if (closed) {
+            throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.",
+                    serviceType.getSimpleName(), this));
+        }
+
+        for (Provider service : providers) {
+            T t = service.getService(serviceType);
+            if (t != null) {
+                return t;
+            }
+        }
+
+        throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.",
+                serviceType.getSimpleName(), this));
+    }
+
+    public <T> Factory<T> getFactory(Class<T> type) {
+        if (closed) {
+            throw new IllegalStateException(String.format("Cannot locate factory for objects of type %s, as %s has been closed.",
+                    type.getSimpleName(), this));
+        }
+
+        for (Provider service : providers) {
+            Factory<T> factory = service.getFactory(type);
+            if (factory != null) {
+                return factory;
+            }
+        }
+
+        throw new UnknownServiceException(type, String.format("No factory for objects of type %s available in %s.",
+                type.getSimpleName(), this));
+    }
+
+    public <T> T newInstance(Class<T> type) {
+        return getFactory(type).create();
+    }
+
+    private static Object invoke(Method method, Object target, Object... args) {
+        try {
+            method.setAccessible(true);
+            return method.invoke(target, args);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    interface Provider extends Stoppable {
+        /**
+         * Locates a service instance of the given type. Returns null if this provider does not provide a service of this type.
+         */
+        <T> T getService(Class<T> serviceType);
+
+        /**
+         * Locates a factory for services of the given type. Returns null if this provider does not provide any services of this type.
+         */
+        <T> Factory<T> getFactory(Class<T> type);
+    }
+
+    private class OwnServices implements Provider {
+        private final List<Provider> providers = new ArrayList<Provider>();
+
+        public <T> Factory<T> getFactory(Class<T> type) {
+            Factory<T> match = null;
+            for (Provider provider : providers) {
+                Factory<T> factory = provider.getFactory(type);
+                if (factory != null) {
+                    if (match != null) {
+                        throw new IllegalArgumentException(String.format("Multiple factories for objects of type %s available in %s.", type.getSimpleName(), DefaultServiceRegistry.this.toString()));
+                    }
+                    match = factory;
+                }
+            }
+            return match;
+        }
+
+        public <T> T getService(Class<T> serviceType) {
+            T match = null;
+            for (Provider provider : providers) {
+                T service = provider.getService(serviceType);
+                if (service != null) {
+                    if (match != null) {
+                        throw new IllegalArgumentException(String.format("Multiple services of type %s available in %s.", serviceType.getSimpleName(), DefaultServiceRegistry.this.toString()));
+                    }
+                    match = service;
+                }
+            }
+            return match;
+        }
+
+        public void stop() {
+            new CompositeStoppable(providers).stop();
+        }
+
+        public void add(Provider provider) {
+            this.providers.add(provider);
+        }
+    }
+
+    private static abstract class ManagedObjectProvider<T> implements Provider {
+        private T instance;
+
+        public T getInstance() {
+            if (instance == null) {
+                instance = create();
+                assert instance != null : String.format("create() of %s returned null", toString());
+            }
+            return instance;
+        }
+
+        protected abstract T create();
+
+        public void stop() {
+            try {
+                new CompositeStoppable().add(instance).stop();
+            } finally {
+                instance = null;
+            }
+        }
+    }
+
+    private static abstract class SingletonService extends ManagedObjectProvider<Object> {
+        final Type serviceType;
+        final Class serviceClass;
+
+        SingletonService(Type serviceType) {
+            this.serviceType = serviceType;
+            serviceClass = toClass(serviceType);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Service %s", serviceType);
+        }
+
+        public <T> T getService(Class<T> serviceType) {
+            if (!serviceType.isAssignableFrom(this.serviceClass)) {
+                return null;
+            }
+            return serviceType.cast(getInstance());
+        }
+
+
+        public <T> Factory<T> getFactory(Class<T> elementType) {
+            if (!Factory.class.isAssignableFrom(serviceClass)) {
+                return null;
+            }
+            return getFactory(serviceType, elementType);
+        }
+
+        private <T> Factory<T> getFactory(Type type, Class<T> elementType) {
+            Class c = toClass(type);
+            if (!Factory.class.isAssignableFrom(c)) {
+                return null;
+            }
+
+            if (type instanceof ParameterizedType) {
+                // Check if type is Factory<? extends ElementType>
+                ParameterizedType parameterizedType = (ParameterizedType) type;
+                if (parameterizedType.getRawType().equals(Factory.class)) {
+                    Type actualType = parameterizedType.getActualTypeArguments()[0];
+                    if (actualType instanceof Class<?> && elementType.isAssignableFrom((Class<?>) actualType)) {
+                        @SuppressWarnings("unchecked")
+                        Factory<T> f = getService(Factory.class);
+                        return f;
+                    }
+                }
+            }
+
+            // Check if type extends Factory<? extends ElementType>
+            for (Type interfaceType : c.getGenericInterfaces()) {
+                Factory<T> f = getFactory(interfaceType, elementType);
+                if (f != null) {
+                    return f;
+                }
+            }
+
+            return null;
+        }
+
+        private Class toClass(Type type) {
+            if (type instanceof Class) {
+                return (Class) type;
+            } else {
+                ParameterizedType parameterizedType = (ParameterizedType) type;
+                return (Class) parameterizedType.getRawType();
+            }
+        }
+    }
+
+    private class FactoryMethodService extends SingletonService {
+        private final Method method;
+
+        public FactoryMethodService(Method method) {
+            super(method.getGenericReturnType());
+            this.method = method;
+        }
+
+        @Override
+        protected Object create() {
+            return invoke(method, DefaultServiceRegistry.this);
+        }
+    }
+
+    private static class FixedInstanceService<T> extends SingletonService {
+        private final T serviceInstance;
+
+        public FixedInstanceService(Class<T> serviceType, T serviceInstance) {
+            super(serviceType);
+            this.serviceInstance = serviceInstance;
+            getService(serviceType);
+        }
+
+        @Override
+        protected Object create() {
+            return serviceInstance;
+        }
+    }
+
+    private class DecoratorMethodService extends SingletonService {
+        private final Method method;
+
+        public DecoratorMethodService(Method method) {
+            super(method.getGenericReturnType());
+            this.method = method;
+        }
+
+        @Override
+        protected Object create() {
+            Object value;
+            if (Factory.class.isAssignableFrom(method.getParameterTypes()[0])) {
+                ParameterizedType fatoryType = (ParameterizedType) method.getGenericParameterTypes()[0];
+                Type typeArg = fatoryType.getActualTypeArguments()[0];
+                Class<?> type;
+                if (typeArg instanceof WildcardType) {
+                    WildcardType wildcardType = (WildcardType) typeArg;
+                    type = (Class) wildcardType.getUpperBounds()[0];
+                } else {
+                    type = (Class) typeArg;
+                }
+                value = parent.getFactory(type);
+            } else {
+                value = parent.get(method.getParameterTypes()[0]);
+            }
+            return invoke(method, DefaultServiceRegistry.this, value);
+        }
+    }
+
+    private static class NestedServices extends ManagedObjectProvider<ServiceRegistry> {
+        private final ServiceRegistry nested;
+
+        public NestedServices(ServiceRegistry nested) {
+            this.nested = nested;
+            getInstance();
+        }
+
+        @Override
+        protected ServiceRegistry create() {
+            return nested;
+        }
+
+        public <T> Factory<T> getFactory(Class<T> type) {
+            try {
+                Factory<T> factory = getInstance().getFactory(type);
+                assert factory != null;
+                assert factory != null : String.format("nested registry returned null for factory type '%s'", type.getName());
+                return factory;
+            } catch (UnknownServiceException e) {
+                if (e.getType().equals(type)) {
+                    return null;
+                }
+                throw e;
+            }
+        }
+
+        public <T> T getService(Class<T> serviceType) {
+            try {
+                T service = getInstance().get(serviceType);
+                assert service != null : String.format("nested registry returned null for service type '%s'", serviceType.getName());
+                return service;
+            } catch (UnknownServiceException e) {
+                if (e.getType().equals(serviceType)) {
+                    return null;
+                }
+                throw e;
+            }
+        }
+    }
+
+    private class ParentServices implements Provider {
+        public <T> Factory<T> getFactory(Class<T> type) {
+            try {
+                Factory<T> factory = parent.getFactory(type);
+                assert factory != null : String.format("parent returned null for factory type '%s'", type.getName());
+                return factory;
+            } catch (UnknownServiceException e) {
+                if (e.getType().equals(type)) {
+                    return null;
+                }
+                throw e;
+            }
+        }
+
+        public <T> T getService(Class<T> serviceType) {
+            try {
+                T service = parent.get(serviceType);
+                assert service != null : String.format("parent returned null for service type '%s'", serviceType.getName());
+                return service;
+            } catch (UnknownServiceException e) {
+                if (e.getType().equals(serviceType)) {
+                    return null;
+                }
+                throw e;
+            }
+        }
+
+        public void stop() {
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java
new file mode 100644
index 0000000..3b255a2
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java
@@ -0,0 +1,53 @@
+/*
+ * 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.internal.service;
+
+import org.gradle.internal.Factory;
+
+/**
+ * A registry of services.
+ */
+public interface ServiceRegistry {
+    /**
+     * Locates the service of the given type. There is a single instance for each service type.
+     *
+     * @param serviceType The service type.
+     * @param <T>         The service type.
+     * @return The service instance. Never returns null.
+     * @throws org.gradle.internal.service.UnknownServiceException When there is no service of the given type available.
+     */
+    <T> T get(Class<T> serviceType) throws UnknownServiceException;
+
+    /**
+     * Locates a factory which can create services of the given type.
+     *
+     * @param type The service type that the factory should create.
+     * @param <T>  The service type that the factory should create.
+     * @return The factory. Never returns null.
+     * @throws UnknownServiceException When there is no factory available for services of the given type.
+     */
+    <T> Factory<T> getFactory(Class<T> type) throws UnknownServiceException;
+
+    /**
+     * Creates a new service instance of the given type.
+     *
+     * @param type The service type
+     * @param <T>  The service type.
+     * @return The instance. Never returns null.
+     * @throws UnknownServiceException When there is no factory available for services of the given type.
+     */
+    <T> T newInstance(Class<T> type) throws UnknownServiceException;
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java
new file mode 100644
index 0000000..8904621
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/UnknownServiceException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.internal.service;
+
+public class UnknownServiceException extends IllegalArgumentException {
+    private final Class<?> type;
+
+    public UnknownServiceException(Class<?> type, String message) {
+        super(message);
+        this.type = type;
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/CompositeStoppableTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/CompositeStoppableTest.groovy
new file mode 100644
index 0000000..0eb0f80
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/CompositeStoppableTest.groovy
@@ -0,0 +1,145 @@
+/*
+ * 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.internal
+
+import spock.lang.Specification
+
+class CompositeStoppableTest extends Specification {
+    private final CompositeStoppable stoppable = new CompositeStoppable()
+
+    def stopsAllElementsOnStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+        1 * b.stop()
+    }
+
+    def stopsAllElementsWhenOneFailsToStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        RuntimeException failure = new RuntimeException()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop() >> { throw failure }
+        1 * b.stop()
+        def e = thrown(RuntimeException)
+        e == failure
+    }
+
+    def stopsAllElementsWhenMultipleFailToStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        RuntimeException failure1 = new RuntimeException()
+        RuntimeException failure2 = new RuntimeException()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop() >> { throw failure1 }
+        1 * b.stop() >> { throw failure2 }
+        def e = thrown(RuntimeException)
+        e == failure1
+    }
+
+    def closesACloseableElement() {
+        Closeable a = Mock()
+        Stoppable b = Mock()
+
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.close()
+        1 * b.stop()
+    }
+
+    def callsACloseMethodOnArbitraryObject() {
+        ClassWithCloseMethod a = Mock()
+
+        stoppable.add(a)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.close()
+    }
+
+    def callsAStopMethodOnArbitraryObject() {
+        ClassWithStopMethod a = Mock()
+
+        stoppable.add(a)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+    }
+
+    def doesNothingWhenArbitraryObjectDoesNotHaveStopOrCloseMethod() {
+        Runnable r = Mock()
+
+        stoppable.add(r)
+
+        when:
+        stoppable.stop()
+
+        then:
+        0 * r._
+    }
+
+    def ignoresNullElements() {
+        Stoppable a = Mock()
+        stoppable.add([null])
+        stoppable.add(a)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+    }
+
+    static class ClassWithCloseMethod {
+        void close() {
+        }
+    }
+
+    static class ClassWithStopMethod {
+        void stop() {
+        }
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
new file mode 100644
index 0000000..f7aa9cf
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
@@ -0,0 +1,94 @@
+/*
+ * 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.internal.jvm
+
+import org.gradle.internal.jvm.Jvm.AppleJvm
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class AppleJvmTest extends Specification {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+    OperatingSystem os = Mock(OperatingSystem)
+
+    AppleJvm getJvm() {
+        new AppleJvm(os)
+    }
+
+    def "looks for runtime Jar in Java home directory"() {
+        TestFile javaHomeDir = tmpDir.createDir('Home')
+        TestFile runtimeJar = javaHomeDir.parentFile.file('Classes/classes.jar').createFile()
+        System.properties['java.home'] = javaHomeDir.absolutePath
+
+        expect:
+        jvm.javaHome == javaHomeDir
+        jvm.runtimeJar == runtimeJar
+    }
+
+    def "looks for tools Jar in Java home directory"() {
+        TestFile javaHomeDir = tmpDir.createDir('Home')
+        TestFile toolsJar = javaHomeDir.parentFile.file('Classes/classes.jar').createFile()
+        System.properties['java.home'] = javaHomeDir.absolutePath
+
+        expect:
+        jvm.javaHome == javaHomeDir
+        jvm.toolsJar == toolsJar
+    }
+
+    def "filters environment variables"() {
+        Map<String, String> env = ['APP_NAME_1234': 'App', 'JAVA_MAIN_CLASS_1234': 'MainClass', 'OTHER': 'value']
+
+        expect:
+        jvm.getInheritableEnvironmentVariables(env) == ['OTHER': 'value']
+    }
+
+    def "finds executable if java home supplied"() {
+        when:
+        def home = tmpDir.createDir("home")
+        home.create {
+            bin { file 'java' }
+        }
+        os.getExecutableName(_ as String) >> home.file('bin/java').absolutePath
+
+        AppleJvm jvm = new AppleJvm(os, home)
+
+        then:
+        home.file('bin/java').absolutePath == jvm.getExecutable('java').absolutePath
+    }
+
+    def "provides decent feedback when executable not found"() {
+        given:
+        def home = tmpDir.createDir("home")
+        home.create {
+            bin { file 'java' }
+        }
+        os.getExecutableName({it.contains('java')}) >> home.file('bin/java').absolutePath
+        os.getExecutableName({it.contains('foobar')}) >> home.file('bin/foobar').absolutePath
+
+        AppleJvm jvm = new AppleJvm(os, home)
+
+        when:
+        jvm.getExecutable('foobar')
+        
+        then:
+        def ex = thrown(JavaHomeException)
+        ex.message.contains('foobar')
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
new file mode 100644
index 0000000..241df64
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
@@ -0,0 +1,286 @@
+/*
+ * 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.internal.jvm
+
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class JvmTest extends Specification {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+    OperatingSystem os = Mock()
+    OperatingSystem theOs = OperatingSystem.current()
+
+    Jvm getJvm() {
+        new Jvm(os)
+    }
+
+    def "uses system property to determine if Java 5/6/7"() {
+        System.properties['java.version'] = "1.$version" as String
+
+        expect:
+        jvm."java$version"
+        !jvm."java$other1"
+        !jvm."java$other2"
+
+        where:
+        version | other1 | other2
+        5       | 6      | 7
+        6       | 7      | 5
+        7       | 5      | 6
+    }
+
+    def "uses system property to determine if compatible with Java 5"() {
+        System.properties['java.version'] = '1.5'
+
+        expect:
+        jvm.java5Compatible
+        !jvm.java6Compatible
+    }
+
+    def "uses system property to determine if compatible with Java 6"() {
+        System.properties['java.version'] = '1.6'
+
+        expect:
+        jvm.java5Compatible
+        jvm.java6Compatible
+    }
+
+    def "looks for runtime Jar in Java home directory"() {
+        TestFile javaHomeDir = tmpDir.createDir('jdk')
+        TestFile runtimeJar = javaHomeDir.file('lib/rt.jar').createFile()
+        System.properties['java.home'] = javaHomeDir.absolutePath
+
+        expect:
+        jvm.javaHome == javaHomeDir
+        jvm.runtimeJar == runtimeJar
+    }
+
+    def "looks for tools Jar in Java home directory"() {
+        TestFile javaHomeDir = tmpDir.createDir('jdk')
+        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
+        System.properties['java.home'] = javaHomeDir.absolutePath
+
+        expect:
+        jvm.javaHome == javaHomeDir
+        jvm.toolsJar == toolsJar
+    }
+
+    def "provides information when typical jdk installed"() {
+        given:
+        TestFile software = tmpDir.createDir('software')
+        software.create {
+            jdk {
+                jre { lib { file 'rt.jar' }}
+                lib { file 'tools.jar'}
+            }
+        }
+
+        when:
+        System.properties['java.home'] = software.file('jdk/jre').absolutePath
+
+        then:
+        jvm.javaHome.absolutePath == software.file('jdk').absolutePath
+        jvm.runtimeJar == software.file('jdk/jre/lib/rt.jar')
+        jvm.toolsJar == software.file('jdk/lib/tools.jar')
+    }
+
+    def "provides information when typical jre installed"() {
+        given:
+        TestFile software = tmpDir.createDir('software')
+        software.create {
+            jre { lib { file 'rt.jar' }}
+        }
+
+        when:
+        System.properties['java.home'] = software.file('jre').absolutePath
+
+        then:
+        jvm.javaHome.absolutePath == software.file('jre').absolutePath
+        jvm.runtimeJar == software.file('jre/lib/rt.jar')
+        jvm.toolsJar == null
+    }
+
+    def "looks for tools Jar in parent of JRE's Java home directory"() {
+        TestFile javaHomeDir = tmpDir.createDir('jdk')
+        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
+        System.properties['java.home'] = javaHomeDir.file('jre').absolutePath
+
+        expect:
+        def jvm = new Jvm(os)
+        jvm.javaHome == javaHomeDir
+        jvm.toolsJar == toolsJar
+    }
+
+    def "looks for tools Jar in sibling of JRE's Java home directory on Windows"() {
+        TestFile javaHomeDir = tmpDir.createDir('jdk1.6.0')
+        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
+        System.properties['java.home'] = tmpDir.createDir('jre6').absolutePath
+        System.properties['java.version'] = '1.6.0'
+        _ * os.windows >> true
+
+        expect:
+        jvm.javaHome == javaHomeDir
+        jvm.toolsJar == toolsJar
+    }
+
+    def "uses system property to locate Java home directory when tools Jar not found"() {
+        TestFile javaHomeDir = tmpDir.createDir('jdk')
+        System.properties['java.home'] = javaHomeDir.absolutePath
+
+        expect:
+        jvm.javaHome == javaHomeDir
+        jvm.toolsJar == null
+    }
+
+    def "uses system property to determine if Apple JVM"() {
+        when:
+        System.properties['java.vm.vendor'] = 'Apple Inc.'
+        def jvm = Jvm.current()
+
+        then:
+        jvm.getClass() == Jvm.AppleJvm
+
+        when:
+        System.properties['java.vm.vendor'] = 'Sun'
+        jvm = Jvm.current()
+
+        then:
+        jvm.getClass() == Jvm
+    }
+
+    def "uses system property to determine if IBM JVM"() {
+        when:
+        System.properties['java.vm.vendor'] = 'IBM Corporation'
+        def jvm = Jvm.current()
+
+        then:
+        jvm.getClass() == Jvm.IbmJvm
+    }
+
+    def "finds executable if for java home supplied"() {
+        System.properties['java.vm.vendor'] = 'Sun'
+
+        when:
+        def home = tmpDir.createDir("home")
+        home.create {
+            jre {
+                bin {
+                    file theOs.getExecutableName('java')
+                    file theOs.getExecutableName('javadoc')
+                }
+            }
+        }
+
+        then:
+        home.file(theOs.getExecutableName("jre/bin/javadoc")).absolutePath ==
+            Jvm.forHome(home.file("jre")).getExecutable("javadoc").absolutePath
+    }
+
+    def "finds tools.jar if java home supplied"() {
+        System.properties['java.vm.vendor'] = 'Sun'
+
+        when:
+        def home = tmpDir.createDir("home")
+        home.create {
+            jdk {
+                bin { file theOs.getExecutableName('java') }
+                lib { file 'tools.jar' }
+            }
+        }
+
+        then:
+        home.file("jdk/lib/tools.jar").absolutePath ==
+            Jvm.forHome(home.file("jdk")).toolsJar.absolutePath
+    }
+
+    def "provides decent feedback if executable not found"() {
+        given:
+        def home = tmpDir.createDir("home")
+        home.create {
+            bin { file theOs.getExecutableName('java') }
+        }
+
+        when:
+        Jvm.forHome(home).getExecutable("foobar")
+
+        then:
+        def ex = thrown(JavaHomeException)
+        ex.message.contains('foobar')
+    }
+
+    def "falls back to PATH if executable cannot be found when using default java"() {
+        given:
+        def home = tmpDir.createDir("home")
+        System.properties['java.home'] = home.absolutePath
+        1 * os.getExecutableName(_ as String) >> "foobar.exe"
+        1 * os.findInPath("foobar") >> new File('/path/foobar.exe')
+
+        when:
+        def exec = jvm.getExecutable("foobar")
+
+        then:
+        exec == new File('/path/foobar.exe')
+    }
+
+    def "falls back to current dir if executable cannot be found anywhere"() {
+        given:
+        def home = tmpDir.createDir("home")
+        System.properties['java.home'] = home.absolutePath
+
+        os.getExecutableName(_ as String) >> "foobar.exe"
+        1 * os.findInPath("foobar") >> null
+
+        when:
+        def exec = jvm.getExecutable("foobar")
+
+        then:
+        exec == new File('foobar.exe')
+    }
+
+    def "provides decent feedback for invalid java home"() {
+        given:
+        def someHome = tmpDir.createDir("someHome")
+
+        when:
+        Jvm.forHome(someHome)
+
+        then:
+        def ex = thrown(JavaHomeException)
+        ex.message.contains('someHome')
+    }
+
+    def "provides basic validation for java home"() {
+        when:
+        Jvm.forHome(new File('i dont exist'))
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+    
+    def "describes accurately when created for supplied java home"() {
+        when:
+        def jvm = new Jvm(theOs, new File('dummyFolder'))
+
+        then:
+        jvm.toString().contains('dummyFolder')
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
new file mode 100644
index 0000000..86e8c9d
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/os/OperatingSystemTest.groovy
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2011 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.internal.os
+
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class OperatingSystemTest extends Specification {
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def "uses os.name property to determine OS name"() {
+        System.properties['os.name'] = 'GradleOS 1.0'
+        
+        expect:
+        OperatingSystem.current().name == 'GradleOS 1.0'
+    }
+    
+    def "uses os.version property to determine OS version"() {
+        System.properties['os.version'] = '42'
+        
+        expect:
+        OperatingSystem.current().version == '42'
+    }
+    
+    def "uses os.name property to determine if windows"() {
+        System.properties['os.name'] = 'Windows 7'
+
+        expect:
+        OperatingSystem.current() instanceof OperatingSystem.Windows
+    }
+
+    def "windows identifies itself correctly"() {
+        def os = new OperatingSystem.Windows()
+
+        expect:
+        os.windows
+        !os.unix
+        !os.macOsX
+    }
+
+    def "windows transforms script names"() {
+        def os = new OperatingSystem.Windows()
+
+        expect:
+        os.getScriptName("a.bat") == "a.bat"
+        os.getScriptName("a.BAT") == "a.BAT"
+        os.getScriptName("a") == "a.bat"
+    }
+
+    def "windows transforms executable names"() {
+        def os = new OperatingSystem.Windows()
+
+        expect:
+        os.getExecutableName("a.exe") == "a.exe"
+        os.getExecutableName("a.EXE") == "a.EXE"
+        os.getExecutableName("a") == "a.exe"
+    }
+
+    def "windows transforms shared library names"() {
+        def os = new OperatingSystem.Windows()
+
+        expect:
+        os.getSharedLibraryName("a.dll") == "a.dll"
+        os.getSharedLibraryName("a.DLL") == "a.DLL"
+        os.getSharedLibraryName("a") == "a.dll"
+    }
+
+    def "windows searches for executable in path"() {
+        def exe = tmpDir.createFile("bin/a.exe")
+        tmpDir.createFile("bin2/a.exe")
+        def os = new OperatingSystem.Windows() {
+            @Override
+            List<File> getPath() {
+                return [tmpDir.file("bin"), tmpDir.file("bin2")]
+            }
+        }
+
+        expect:
+        os.findInPath("a.exe") == exe
+        os.findInPath("a") == exe
+        os.findInPath("unknown") == null
+    }
+
+    def "uses os.name property to determine if Mac OS X"() {
+        when:
+        System.properties['os.name'] = 'Mac OS X'
+
+        then:
+        OperatingSystem.current() instanceof OperatingSystem.MacOs
+
+        when:
+        System.properties['os.name'] = 'Darwin'
+
+        then:
+        OperatingSystem.current() instanceof OperatingSystem.MacOs
+    }
+
+    def "Mac OS X identifies itself correctly"() {
+        def os = new OperatingSystem.MacOs()
+
+        expect:
+        !os.windows
+        os.unix
+        os.macOsX
+    }
+
+    def "uses os.name property to determine if solaris"() {
+        System.properties['os.name'] = 'SunOS'
+
+        expect:
+        OperatingSystem.current() instanceof OperatingSystem.Solaris
+    }
+
+    def "uses os.name property to determine if linux"() {
+        System.properties['os.name'] = 'Linux'
+
+        expect:
+        OperatingSystem.current() instanceof OperatingSystem.Linux
+    }
+
+    def "uses default implementation for other os"() {
+        System.properties['os.name'] = 'unknown'
+
+        expect:
+        OperatingSystem.current() instanceof OperatingSystem.Unix
+    }
+
+    def "UNIX identifies itself correctly"() {
+        def os = new OperatingSystem.Unix()
+
+        expect:
+        !os.windows
+        os.unix
+        !os.macOsX
+    }
+
+    def "UNIX does not transform script names"() {
+        def os = new OperatingSystem.Unix()
+
+        expect:
+        os.getScriptName("a.sh") == "a.sh"
+        os.getScriptName("a") == "a"
+    }
+
+    def "UNIX does not transforms executable names"() {
+        def os = new OperatingSystem.Unix()
+
+        expect:
+        os.getExecutableName("a.sh") == "a.sh"
+        os.getExecutableName("a") == "a"
+    }
+
+    def "UNIX transforms shared library names"() {
+        def os = new OperatingSystem.Unix()
+
+        expect:
+        os.getSharedLibraryName("a.so") == "a.so"
+        os.getSharedLibraryName("liba.so") == "liba.so"
+        os.getSharedLibraryName("a") == "liba.so"
+        os.getSharedLibraryName("lib1") == "liblib1.so"
+        os.getSharedLibraryName("path/liba.so") == "path/liba.so"
+        os.getSharedLibraryName("path/a") == "path/liba.so"
+    }
+
+    def "UNIX searches for executable in path"() {
+        def exe = tmpDir.createFile("bin/a")
+        tmpDir.createFile("bin2/a")
+        def os = new OperatingSystem.Unix() {
+            @Override
+            List<File> getPath() {
+                return [tmpDir.file("bin"), tmpDir.file("bin2")]
+            }
+        }
+
+        expect:
+        os.findInPath("a") == exe
+        os.findInPath("unknown") == null
+    }
+
+    def "solaris uses prefix of x86 for 32bit intel"() {
+        def solaris = new OperatingSystem.Solaris()
+
+        when:
+        System.properties['os.arch'] = 'i386'
+
+        then:
+        solaris.nativePrefix == 'sunos-x86'
+
+        when:
+        System.properties['os.arch'] = 'x86'
+
+        then:
+        solaris.nativePrefix == 'sunos-x86'
+    }
+
+    def "unix uses prefix of i386 for 32bit intel"() {
+        def unix = new OperatingSystem.Unix()
+        System.properties['os.name'] = 'unknown'
+
+        when:
+        System.properties['os.arch'] = 'x86'
+
+        then:
+        unix.nativePrefix == 'unknown-i386'
+
+        when:
+        System.properties['os.arch'] = 'i386'
+
+        then:
+        unix.nativePrefix == 'unknown-i386'
+    }
+
+    def "os x uses same prefix for all architectures"() {
+        def osx = new OperatingSystem.MacOs()
+
+        expect:
+        osx.nativePrefix == 'darwin'
+    }
+
+    def "os x transforms shared library names"() {
+        def os = new OperatingSystem.MacOs()
+
+        expect:
+        os.getSharedLibraryName("a.dylib") == "a.dylib"
+        os.getSharedLibraryName("liba.dylib") == "liba.dylib"
+        os.getSharedLibraryName("a") == "liba.dylib"
+        os.getSharedLibraryName("lib1") == "liblib1.dylib"
+        os.getSharedLibraryName("path/liba.dylib") == "path/liba.dylib"
+        os.getSharedLibraryName("path/a") == "path/liba.dylib"
+    }
+
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java
new file mode 100755
index 0000000..4f0d2b8
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java
@@ -0,0 +1,492 @@
+/*
+ * 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.internal.service;
+
+import org.gradle.internal.Factory;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+ at RunWith(JMock.class)
+public class DefaultServiceRegistryTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final TestRegistry registry = new TestRegistry();
+
+    @Test
+    public void throwsExceptionForUnknownService() {
+        try {
+            registry.get(Map.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void delegatesToParentForUnknownService() {
+        final BigDecimal value = BigDecimal.TEN;
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        TestRegistry registry = new TestRegistry(parent);
+
+        context.checking(new Expectations(){{
+            one(parent).get(BigDecimal.class);
+            will(returnValue(value));
+        }});
+
+        assertThat(registry.get(BigDecimal.class), sameInstance(value));
+    }
+
+    @Test
+    public void throwsExceptionForUnknownParentService() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        TestRegistry registry = new TestRegistry(parent);
+
+        context.checking(new Expectations(){{
+            one(parent).get(Map.class);
+            will(throwException(new UnknownServiceException(Map.class, "fail")));
+        }});
+
+        try {
+            registry.get(Map.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void returnsAddedServiceInstance() {
+        BigDecimal value = BigDecimal.TEN;
+        DefaultServiceRegistry registry = new DefaultServiceRegistry();
+        registry.add(BigDecimal.class, value);
+        assertThat(registry.get(BigDecimal.class), sameInstance(value));
+        assertThat(registry.get(Number.class), sameInstance((Object) value));
+    }
+
+    @Test
+    public void cachesRegisteredServiceInstance() {
+        assertThat(registry.get(Integer.class), sameInstance(registry.get(Integer.class)));
+        assertThat(registry.get(Integer.class), sameInstance((Object) registry.get(Integer.class)));
+    }
+
+    @Test
+    public void usesFactoryMethodToCreateServiceInstance() {
+        assertThat(registry.get(String.class), equalTo("12"));
+        assertThat(registry.get(Integer.class), equalTo(12));
+    }
+
+    @Test
+    public void usesOverriddenFactoryMethodToCreateServiceInstance() {
+        TestRegistry registry = new TestRegistry(){
+            @Override
+            protected String createString() {
+                return "overridden";
+            }
+        };
+        assertThat(registry.get(String.class), equalTo("overridden"));
+    }
+
+    @Test
+    public void failsWhenMultipleServiceFactoriesCanCreateRequestedServiceType() {
+        ServiceRegistry registry = new RegistryWithAmbiguousFactoryMethods();
+        assertThat(registry.get(String.class), equalTo("hello"));
+
+        try {
+            registry.get(Object.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("Multiple services of type Object available in RegistryWithAmbiguousFactoryMethods."));
+        }
+    }
+
+    @Test
+    public void usesDecoratorMethodToDecorateParentServiceInstance() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        ServiceRegistry registry = new RegistryWithDecoratorMethods(parent);
+
+        context.checking(new Expectations() {{
+            one(parent).get(Long.class);
+            will(returnValue(110L));
+        }});
+
+        assertThat(registry.get(Long.class), equalTo(120L));
+    }
+
+    @Test
+    public void decoratorMethodFailsWhenNoParentRegistry() {
+        try {
+            new RegistryWithDecoratorMethods();
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("Cannot use decorator methods when no parent registry is provided."));
+        }
+    }
+
+    @Test
+    public void canGetServiceAsFactoryWhenTheServiceImplementsFactoryInterface() {
+        assertThat(registry.getFactory(BigDecimal.class), instanceOf(TestFactory.class));
+        assertThat(registry.getFactory(BigDecimal.class), sameInstance((Object) registry.getFactory(BigDecimal.class)));
+        assertThat(registry.getFactory(Number.class), sameInstance((Object) registry.getFactory(BigDecimal.class)));
+    }
+
+    @Test
+    public void canLocateFactoryWhenServiceInterfaceExtendsFactory() {
+        registry.add(StringFactory.class, new StringFactory() {
+            public String create() {
+                return "value";
+            }
+        });
+        assertThat(registry.getFactory(String.class).create(), equalTo("value"));
+    }
+
+    @Test
+    public void usesAFactoryServiceToCreateInstances() {
+        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(0)));
+        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(1)));
+        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(2)));
+    }
+
+    @Test
+    public void delegatesToParentForUnknownFactory() {
+        @SuppressWarnings("unchecked") final Factory<Map> factory = context.mock(Factory.class);
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        TestRegistry registry = new TestRegistry(parent);
+
+        context.checking(new Expectations() {{
+            one(parent).getFactory(Map.class);
+            will(returnValue(factory));
+        }});
+
+        assertThat(registry.getFactory(Map.class), sameInstance((Object) factory));
+    }
+
+    @Test
+    public void usesDecoratorMethodToDecorateParentFactoryInstance() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        @SuppressWarnings("unchecked") final Factory<Long> factory = context.mock(Factory.class);
+        ServiceRegistry registry = new RegistryWithDecoratorMethods(parent);
+
+        context.checking(new Expectations() {{
+            one(parent).getFactory(Long.class);
+            will(returnValue(factory));
+            allowing(factory).create();
+            will(onConsecutiveCalls(returnValue(10L), returnValue(20L)));
+        }});
+
+        assertThat(registry.newInstance(Long.class), equalTo(12L));
+        assertThat(registry.newInstance(Long.class), equalTo(22L));
+    }
+    
+    @Test
+    public void throwsExceptionForUnknownFactory() {
+        try {
+            registry.getFactory(String.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No factory for objects of type String available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void failsWhenMultipleFactoriesAreAvailableForServiceType() {
+        ServiceRegistry registry = new RegistryWithAmbiguousFactoryMethods();
+
+        try {
+            registry.getFactory(Object.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("Multiple factories for objects of type Object available in RegistryWithAmbiguousFactoryMethods."));
+        }
+    }
+
+    @Test
+    public void returnsServiceInstancesManagedByNestedServiceRegistry() {
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        final Runnable runnable = context.mock(Runnable.class);
+        registry.add(nested);
+
+        context.checking(new Expectations() {{
+            one(nested).get(Runnable.class);
+            will(returnValue(runnable));
+        }});
+
+        assertThat(registry.get(Runnable.class), sameInstance(runnable));
+    }
+
+    @Test
+    public void throwsExceptionForUnknownServiceInNestedRegistry() {
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        registry.add(nested);
+
+        context.checking(new Expectations(){{
+            one(nested).get(Runnable.class);
+            will(throwException(new UnknownServiceException(Runnable.class, "fail")));
+        }});
+
+        try {
+            registry.get(Runnable.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No service of type Runnable available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void returnsServiceFactoriesManagedByNestedServiceRegistry() {
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        @SuppressWarnings("unchecked") final Factory<Runnable> factory = context.mock(Factory.class);
+        registry.add(nested);
+
+        context.checking(new Expectations() {{
+            one(nested).getFactory(Runnable.class);
+            will(returnValue(factory));
+        }});
+
+        assertThat(registry.getFactory(Runnable.class), sameInstance(factory));
+    }
+
+    @Test
+    public void throwsExceptionForUnknownFactoryInNestedRegistry() {
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        registry.add(nested);
+
+        context.checking(new Expectations(){{
+            one(nested).getFactory(Runnable.class);
+            will(throwException(new UnknownServiceException(Runnable.class, "fail")));
+        }});
+
+        try {
+            registry.getFactory(Runnable.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No factory for objects of type Runnable available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void servicesCreatedByFactoryMethodsAreVisibleWhenUsingASubClass() {
+        ServiceRegistry registry = new SubType();
+        assertThat(registry.get(String.class), equalTo("12"));
+        assertThat(registry.get(Integer.class), equalTo(12));
+    }
+    
+    @Test
+    public void closeInvokesCloseMethodOnEachService() {
+        final TestCloseService service = context.mock(TestCloseService.class);
+        registry.add(TestCloseService.class, service);
+
+        context.checking(new Expectations() {{
+            one(service).close();
+        }});
+
+        registry.close();
+    }
+
+    @Test
+    public void prefersServicesCreatedByFactoryMethodsOverNestedServices() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        final TestRegistry registry = new TestRegistry(parent);
+        registry.add(nested);
+
+        assertThat(registry.get(String.class), equalTo("12"));
+    }
+
+    @Test
+    public void prefersRegisteredServicesOverNestedServices() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        final TestRegistry registry = new TestRegistry(parent);
+        registry.add(nested);
+        registry.add(BigDecimal.class, BigDecimal.ONE);
+
+        assertThat(registry.get(BigDecimal.class), equalTo(BigDecimal.ONE));
+    }
+
+    @Test
+    public void prefersNestedServicesOverParentServices() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        final ServiceRegistry nested = context.mock(ServiceRegistry.class);
+        final TestRegistry registry = new TestRegistry(parent);
+        final Runnable runnable = context.mock(Runnable.class);
+        registry.add(nested);
+
+        context.checking(new Expectations() {{
+            one(nested).get(Runnable.class);
+            will(returnValue(runnable));
+        }});
+
+        assertThat(registry.get(Runnable.class), sameInstance(runnable));
+    }
+
+    @Test
+    public void closeInvokesStopMethodOnEachService() {
+        final TestStopService service = context.mock(TestStopService.class);
+        registry.add(TestStopService.class, service);
+
+        context.checking(new Expectations() {{
+            one(service).stop();
+        }});
+
+        registry.close();
+    }
+
+    @Test
+    public void closeIgnoresServiceWithNoCloseOrStopMethod() {
+        registry.add(String.class, "service");
+
+        registry.close();
+    }
+
+    @Test
+    public void closeInvokesCloseMethodOnEachNestedServiceRegistry() {
+        final ClosableServiceRegistry nested = context.mock(ClosableServiceRegistry.class);
+        registry.add(nested);
+
+        context.checking(new Expectations() {{
+            one(nested).close();
+        }});
+
+        registry.close();
+    }
+
+    @Test
+    public void discardsServicesOnClose() {
+        registry.get(String.class);
+        registry.close();
+        try {
+            registry.get(String.class);
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo("Cannot locate service of type String, as TestRegistry has been closed."));
+        }
+    }
+
+    @Test
+    public void discardsFactoriesOnClose() {
+        registry.getFactory(BigDecimal.class);
+        registry.close();
+        try {
+            registry.getFactory(BigDecimal.class);
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo("Cannot locate factory for objects of type BigDecimal, as TestRegistry has been closed."));
+        }
+    }
+
+    private static class TestRegistry extends DefaultServiceRegistry {
+        public TestRegistry() {
+        }
+
+        public TestRegistry(ServiceRegistry parent) {
+            super(parent);
+        }
+
+        protected String createString() {
+            return get(Integer.class).toString();
+        }
+
+        protected Integer createInt() {
+            return 12;
+        }
+
+        protected Factory<BigDecimal> createTestFactory() {
+            return new TestFactory();
+        }
+    }
+
+    private static class RegistryWithDecoratorMethods extends DefaultServiceRegistry {
+        public RegistryWithDecoratorMethods() {
+        }
+
+        public RegistryWithDecoratorMethods(ServiceRegistry parent) {
+            super(parent);
+        }
+
+        protected Long createLong(Long value) {
+            return value + 10;
+        }
+
+        protected Factory<Long> createLongFactory(final Factory<Long> factory) {
+            return new Factory<Long>() {
+                public Long create() {
+                    return factory.create() + 2;
+                }
+            };
+        }
+    }
+
+    private static class SubType extends TestRegistry {
+    }
+
+    private static class RegistryWithAmbiguousFactoryMethods extends DefaultServiceRegistry {
+        Object createObject() {
+            return "hello";
+        }
+        
+        String createString() {
+            return "hello";
+        }
+        
+        Factory<Object> createObjectFactory() {
+            return new Factory<Object>() {
+                public Object create() {
+                    return createObject();
+                }
+            };
+        }
+        
+        Factory<String> createStringFactory() {
+            return new Factory<String>() {
+                public String create() {
+                    return createString();
+                }
+            };
+        }
+    }
+
+    private static class TestFactory implements Factory<BigDecimal> {
+        int value;
+        public BigDecimal create() {
+            return BigDecimal.valueOf(value++);
+        }
+    }
+
+    private interface StringFactory extends Factory<String> {
+    }
+
+    public interface TestCloseService {
+        void close();
+    }
+
+    public interface TestStopService {
+        void stop();
+    }
+
+    public interface ClosableServiceRegistry extends ServiceRegistry {
+        void close();
+    }
+}
diff --git a/subprojects/cli/cli.gradle b/subprojects/cli/cli.gradle
new file mode 100644
index 0000000..2419010
--- /dev/null
+++ b/subprojects/cli/cli.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/*
+    This project contains some utilities for parsing command line arguments.
+
+    It's packaged separately because it's used by multiple “applications” (i.e. gradle core and the wrapper).
+    It has no dependencies, and should never have any.
+*/
+apply from: "$rootDir/gradle/classycle.gradle"
+
+dependencies {
+    groovy libraries.groovy
+}
\ No newline at end of file
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/AbstractCommandLineConverter.java b/subprojects/cli/src/main/java/org/gradle/cli/AbstractCommandLineConverter.java
new file mode 100644
index 0000000..07081fe
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/AbstractCommandLineConverter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.cli;
+
+public abstract class AbstractCommandLineConverter<T> implements CommandLineConverter<T> {
+    public T convert(Iterable<String> args) throws CommandLineArgumentException {
+        CommandLineParser parser = new CommandLineParser();
+        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/cli/src/main/java/org/gradle/cli/AbstractPropertiesCommandLineConverter.java b/subprojects/cli/src/main/java/org/gradle/cli/AbstractPropertiesCommandLineConverter.java
new file mode 100644
index 0000000..b66d3f6
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/AbstractPropertiesCommandLineConverter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 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.cli;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractPropertiesCommandLineConverter extends AbstractCommandLineConverter<Map<String, String>> {
+    protected abstract String getPropertyOption();
+    protected abstract String getPropertyOptionDetailed();
+    protected abstract String getPropertyOptionDescription();
+
+    public void configure(CommandLineParser parser) {
+        CommandLineOption option = parser.option(getPropertyOption(), getPropertyOptionDetailed());
+        option = option.hasArguments();
+        option.hasDescription(getPropertyOptionDescription());
+    }
+
+    protected Map<String, String> newInstance() {
+        return new HashMap<String, String>();
+    }
+
+    public Map<String, String> convert(ParsedCommandLine options, Map<String, String> properties) throws CommandLineArgumentException {
+        for (String keyValueExpression : options.option(getPropertyOption()).getValues()) {
+            int pos = keyValueExpression.indexOf("=");
+            if (pos < 0) {
+                properties.put(keyValueExpression, "");
+            } else {
+                properties.put(keyValueExpression.substring(0, pos), keyValueExpression.substring(pos+1));
+            }
+        }
+        return properties;
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java
new file mode 100644
index 0000000..82a5ff3
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineArgumentException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.cli;
+
+/**
+ * A {@code CommandLineArgumentException} is thrown when command-line arguments cannot be parsed.
+ * 
+ * @author Hans Dockter
+ */
+public class CommandLineArgumentException extends RuntimeException {
+    public CommandLineArgumentException(String message) {
+        super(message);
+    }
+
+    public CommandLineArgumentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java
new file mode 100644
index 0000000..bc9bac4
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineConverter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.cli;
+
+/**
+ * @author Hans Dockter
+ */
+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/cli/src/main/java/org/gradle/cli/CommandLineOption.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
new file mode 100644
index 0000000..69133a2
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineOption.java
@@ -0,0 +1,107 @@
+/*
+ * 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.cli;
+
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+public class CommandLineOption {
+    private final Set<String> options = new HashSet<String>();
+    private Class<?> argumentType = Void.TYPE;
+    private String description;
+    private String subcommand;
+    private String deprecationWarning;
+    private boolean experimental;
+
+    public CommandLineOption(Iterable<String> options) {
+        for (String option : options) {
+            this.options.add(option);
+        }
+    }
+
+    public Set<String> getOptions() {
+        return options;
+    }
+
+    public CommandLineOption hasArgument() {
+        argumentType = String.class;
+        return this;
+    }
+
+    public CommandLineOption hasArguments() {
+        argumentType = List.class;
+        return this;
+    }
+
+    public String getSubcommand() {
+        return subcommand;
+    }
+
+    public CommandLineOption mapsToSubcommand(String command) {
+        this.subcommand = command;
+        return this;
+    }
+
+    public String getDescription() {
+        StringBuilder result = new StringBuilder();
+        if (description != null) {
+            result.append(description);
+        }
+        if (deprecationWarning != null) {
+            if (result.length() > 0) {
+                result.append(' ');
+            }
+            result.append("[deprecated - ");
+            result.append(deprecationWarning);
+            result.append("]");
+        }
+        if (experimental) {
+            if (result.length() > 0) {
+                result.append(' ');
+            }
+            result.append("[experimental]");
+        }
+        return result.toString();
+    }
+
+    public CommandLineOption hasDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public boolean getAllowsArguments() {
+        return argumentType != Void.TYPE;
+    }
+
+    public boolean getAllowsMultipleArguments() {
+        return argumentType == List.class;
+    }
+
+    public CommandLineOption deprecated(String deprecationWarning) {
+        this.deprecationWarning = deprecationWarning;
+        return this;
+    }
+
+    public CommandLineOption experimental() {
+        experimental = true;
+        return this;
+    }
+    
+    public String getDeprecationWarning() {
+        return deprecationWarning;
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java
new file mode 100644
index 0000000..c238506
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/CommandLineParser.java
@@ -0,0 +1,519 @@
+/*
+ * 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.cli;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * <p>A command-line parser which supports a command/sub-command style command-line interface. Supports the following
+ * syntax:</p>
+ * <pre>
+ * <option>* (<sub-command> <sub-command-option>*)*
+ * </pre>
+ *
+ * <ul> <li>Short options are a '-' followed by a single character. For example: {@code -a}.</li>
+ *
+ * <li>Long options are '--' followed by multiple characters. For example: {@code --long-option}.</li>
+ *
+ * <li>Options can take arguments. The argument follows the option. For example: {@code -a arg} or {@code --long
+ * arg}.</li>
+ *
+ * <li>Arguments can be attached to the option using '='. For example: {@code -a=arg} or {@code --long=arg}.</li>
+ *
+ * <li>Arguments can be attached to short options. For example: {@code -aarg}.</li>
+ *
+ * <li>Short options can be combined. For example {@code -ab} is equivalent to {@code -a -b}.</li>
+ *
+ * <li>Anything else is treated as an extra argument. This includes a single {@code -} character.</li>
+ *
+ * <li>'--' indicates the end of the options. Anything following is not parsed and is treated as extra arguments.</li>
+ *
+ * <li>The parser is forgiving, and allows '--' to be used with short options and '-' to be used with long
+ * options.</li>
+ *
+ * <li>The set of options must be known at parse time. Sub-commands and their options do not need to be known at parse
+ * time. Use {@link ParsedCommandLine#getExtraArguments()} to obtain the non-option command-line arguments.</li>
+ *
+ * </ul>
+ */
+public class CommandLineParser {
+    private Map<String, CommandLineOption> optionsByString = new HashMap<String, CommandLineOption>();
+    private boolean allowMixedOptions;
+    private boolean allowUnknownOptions;
+    private final PrintWriter deprecationPrinter;
+
+    public CommandLineParser() {
+        this(new OutputStreamWriter(System.out));
+    }
+
+    public CommandLineParser(Writer deprecationPrinter) {
+        this.deprecationPrinter = new PrintWriter(deprecationPrinter);
+    }
+
+    /**
+     * Parses the given command-line.
+     *
+     * @param commandLine The command-line.
+     * @return The parsed command line.
+     * @throws org.gradle.cli.CommandLineArgumentException
+     *          On parse failure.
+     */
+    public ParsedCommandLine parse(String... commandLine) throws CommandLineArgumentException {
+        return parse(Arrays.asList(commandLine));
+    }
+
+    /**
+     * Parses the given command-line.
+     *
+     * @param commandLine The command-line.
+     * @return The parsed command line.
+     * @throws org.gradle.cli.CommandLineArgumentException
+     *          On parse failure.
+     */
+    public ParsedCommandLine parse(Iterable<String> commandLine) throws CommandLineArgumentException {
+        ParsedCommandLine parsedCommandLine = new ParsedCommandLine(new HashSet<CommandLineOption>(optionsByString.values()));
+        ParserState parseState = new BeforeFirstSubCommand(parsedCommandLine);
+        for (String arg : commandLine) {
+            if (parseState.maybeStartOption(arg)) {
+                if (arg.equals("--")) {
+                    parseState = new AfterOptions(parsedCommandLine);
+                } else if (arg.matches("--[^=]+")) {
+                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2));
+                    parseState = parsedOption.onStartNextArg();
+                } else if (arg.matches("--[^=]+=.*")) {
+                    int endArg = arg.indexOf('=');
+                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2, endArg));
+                    parseState = parsedOption.onArgument(arg.substring(endArg + 1));
+                } else if (arg.matches("-[^=]=.*")) {
+                    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)) {
+                        OptionParserState parsedOption = parseState.onStartOption(arg, option);
+                        parseState = parsedOption.onStartNextArg();
+                    } else {
+                        String option1 = arg.substring(1, 2);
+                        OptionParserState parsedOption;
+                        if (optionsByString.containsKey(option1)) {
+                            parsedOption = parseState.onStartOption("-" + option1, option1);
+                            if (parsedOption.getHasArgument()) {
+                                parseState = parsedOption.onArgument(arg.substring(2));
+                            } else {
+                                parseState = parsedOption.onComplete();
+                                for (int i = 2; i < arg.length(); i++) {
+                                    String optionStr = arg.substring(i, i + 1);
+                                    parsedOption = parseState.onStartOption("-" + optionStr, optionStr);
+                                    parseState = parsedOption.onComplete();
+                                }
+                            }
+                        } else {
+                            if (allowUnknownOptions) {
+                                // if we are allowing unknowns, just pass through the whole arg
+                                parsedOption = parseState.onStartOption(arg, option);
+                                parseState = parsedOption.onComplete();
+                            } else {
+                                // We are going to throw a CommandLineArgumentException below, but want the message
+                                // to reflect that we didn't recognise the first char (i.e. the option specifier)
+                                parsedOption = parseState.onStartOption("-" + option1, option1);
+                                parseState = parsedOption.onComplete();
+                            }
+                        }
+                    }
+                }
+            } else {
+                parseState = parseState.onNonOption(arg);
+            }
+        }
+
+        parseState.onCommandLineEnd();
+        return parsedCommandLine;
+    }
+
+    public CommandLineParser allowMixedSubcommandsAndOptions() {
+        allowMixedOptions = true;
+        return this;
+    }
+
+    public CommandLineParser allowUnknownOptions() {
+        allowUnknownOptions = true;
+        return this;
+    }
+
+    /**
+     * Prints a usage message to the given stream.
+     *
+     * @param out The output stream to write to.
+     */
+    public void printUsage(Appendable out) {
+        Formatter formatter = new Formatter(out);
+        Set<CommandLineOption> orderedOptions = new TreeSet<CommandLineOption>(new OptionComparator());
+        orderedOptions.addAll(optionsByString.values());
+        Map<String, String> lines = new LinkedHashMap<String, String>();
+        for (CommandLineOption option : orderedOptions) {
+            Set<String> orderedOptionStrings = new TreeSet<String>(new OptionStringComparator());
+            orderedOptionStrings.addAll(option.getOptions());
+            List<String> prefixedStrings = new ArrayList<String>();
+            for (String optionString : orderedOptionStrings) {
+                if (optionString.length() == 1) {
+                    prefixedStrings.add("-" + optionString);
+                } else {
+                    prefixedStrings.add("--" + optionString);
+                }
+            }
+
+            String key = join(prefixedStrings, ", ");
+            String value = option.getDescription();
+            if (value == null || value.length() == 0) {
+                value = "";
+            }
+
+            lines.put(key, value);
+        }
+        int max = 0;
+        for (String optionStr : lines.keySet()) {
+            max = Math.max(max, optionStr.length());
+        }
+        for (Map.Entry<String, String> entry : lines.entrySet()) {
+            if (entry.getValue().length() == 0) {
+                formatter.format("%s%n", entry.getKey());
+            } else {
+                formatter.format("%-" + max + "s  %s%n", entry.getKey(), entry.getValue());
+            }
+        }
+        formatter.flush();
+    }
+
+    private static String join(Collection<?> things, String separator) {
+        StringBuffer buffer = new StringBuffer();
+        boolean first = true;
+
+        if (separator == null) {
+            separator = "";
+        }
+
+        for (Object thing : things) {
+            if (!first) {
+                buffer.append(separator);
+            }
+            buffer.append(thing.toString());
+            first = false;
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Defines a new option. By default, the option takes no arguments and has no description.
+     *
+     * @param options The options values.
+     * @return The option, which can be further configured.
+     */
+    public CommandLineOption option(String... options) {
+        for (String option : options) {
+            if (optionsByString.containsKey(option)) {
+                throw new IllegalArgumentException(String.format("Option '%s' is already defined.", option));
+            }
+            if (option.startsWith("-")) {
+                throw new IllegalArgumentException(String.format("Cannot add option '%s' as an option cannot start with '-'.", option));
+            }
+        }
+        CommandLineOption option = new CommandLineOption(Arrays.asList(options));
+        for (String optionStr : option.getOptions()) {
+            this.optionsByString.put(optionStr, option);
+        }
+        return option;
+    }
+
+    private static class OptionString {
+        private final String arg;
+        private final String option;
+
+        private OptionString(String arg, String option) {
+            this.arg = arg;
+            this.option = option;
+        }
+
+        public String getDisplayName() {
+            return arg.startsWith("--") ? "--" + option : "-" + option;
+        }
+
+        @Override
+        public String toString() {
+            return getDisplayName();
+        }
+    }
+
+    private static abstract class ParserState {
+        public abstract boolean maybeStartOption(String arg);
+
+        boolean isOption(String arg) {
+            return arg.matches("-.+");
+        }
+
+        public abstract OptionParserState onStartOption(String arg, String option);
+
+        public abstract ParserState onNonOption(String arg);
+
+        public void onCommandLineEnd() {
+        }
+    }
+
+    private abstract class OptionAwareParserState extends ParserState {
+        protected final ParsedCommandLine commandLine;
+
+        protected OptionAwareParserState(ParsedCommandLine commandLine) {
+            this.commandLine = commandLine;
+        }
+
+        @Override
+        public boolean maybeStartOption(String arg) {
+            return isOption(arg);
+        }
+
+        @Override
+        public ParserState onNonOption(String arg) {
+            commandLine.addExtraValue(arg);
+            return allowMixedOptions ? new AfterFirstSubCommand(commandLine) : new AfterOptions(commandLine);
+        }
+    }
+
+    private class BeforeFirstSubCommand extends OptionAwareParserState {
+        private BeforeFirstSubCommand(ParsedCommandLine commandLine) {
+            super(commandLine);
+        }
+
+        @Override
+        public OptionParserState onStartOption(String arg, String option) {
+            OptionString optionString = new OptionString(arg, option);
+            CommandLineOption commandLineOption = optionsByString.get(option);
+            if (commandLineOption == null) {
+                if (allowUnknownOptions) {
+                    return new UnknownOptionParserState(arg, commandLine, this);
+                } else {
+                    throw new CommandLineArgumentException(String.format("Unknown command-line option '%s'.", optionString));
+                }
+            }
+            return new KnownOptionParserState(optionString, commandLineOption, commandLine, this);
+        }
+    }
+
+    private class AfterFirstSubCommand extends OptionAwareParserState {
+        private AfterFirstSubCommand(ParsedCommandLine commandLine) {
+            super(commandLine);
+        }
+
+        @Override
+        public OptionParserState onStartOption(String arg, String option) {
+            CommandLineOption commandLineOption = optionsByString.get(option);
+            if (commandLineOption == null) {
+                return new UnknownOptionParserState(arg, commandLine, this);
+            }
+            return new KnownOptionParserState(new OptionString(arg, option), commandLineOption, commandLine, this);
+        }
+    }
+
+    private static class AfterOptions extends ParserState {
+        private final ParsedCommandLine commandLine;
+
+        private AfterOptions(ParsedCommandLine commandLine) {
+            this.commandLine = commandLine;
+        }
+
+        @Override
+        public boolean maybeStartOption(String arg) {
+            return false;
+        }
+
+        @Override
+        public OptionParserState onStartOption(String arg, String option) {
+            return new UnknownOptionParserState(arg, commandLine, this);
+        }
+
+        @Override
+        public ParserState onNonOption(String arg) {
+            commandLine.addExtraValue(arg);
+            return this;
+        }
+    }
+
+    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 void onCommandLineEnd() {
+            option.onComplete();
+        }
+    }
+
+    private static abstract class OptionParserState {
+        public abstract ParserState onStartNextArg();
+
+        public abstract ParserState onArgument(String argument);
+
+        public abstract boolean getHasArgument();
+
+        public abstract ParserState onComplete();
+    }
+
+    private 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 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
+        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.getDeprecationWarning() != null) {
+                deprecationPrinter.println("The " + optionString + " option is deprecated - " + option.getDeprecationWarning());
+            }
+            if (option.getSubcommand() != null) {
+                return state.onNonOption(option.getSubcommand());
+            }
+
+            return state;
+        }
+    }
+
+    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 getHasArgument() {
+            return true;
+        }
+
+        @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> {
+        public int compare(CommandLineOption option1, CommandLineOption option2) {
+            String min1 = Collections.min(option1.getOptions(), new OptionStringComparator());
+            String min2 = Collections.min(option2.getOptions(), new OptionStringComparator());
+            return new CaseInsensitiveStringComparator().compare(min1, min2);
+        }
+    }
+
+    private static final class CaseInsensitiveStringComparator implements Comparator<String> {
+        public int compare(String option1, String option2) {
+            int diff = option1.compareToIgnoreCase(option2);
+            if (diff != 0) {
+                return diff;
+            }
+            return option1.compareTo(option2);
+        }
+    }
+
+    private static final class OptionStringComparator implements Comparator<String> {
+        public int compare(String option1, String option2) {
+            boolean short1 = option1.length() == 1;
+            boolean short2 = option2.length() == 1;
+            if (short1 && !short2) {
+                return -1;
+            }
+            if (!short1 && short2) {
+                return 1;
+            }
+            return new CaseInsensitiveStringComparator().compare(option1, option2);
+        }
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java
new file mode 100644
index 0000000..8179d31
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLine.java
@@ -0,0 +1,107 @@
+/*
+ * 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.cli;
+
+import java.util.*;
+
+public class ParsedCommandLine {
+    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();
+            for (String optionStr : option.getOptions()) {
+                optionsByString.put(optionStr, parsedOption);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("options: %s, extraArguments: %s", quoteAndJoin(presentOptions), quoteAndJoin(extraArguments));
+    }
+
+    private String quoteAndJoin(Iterable<String> strings) {
+        StringBuilder output = new StringBuilder();
+        boolean isFirst = true;
+        for (String string : strings) {
+            if (!isFirst) {
+                output.append(", ");
+            }
+            output.append("'");
+            output.append(string);
+            output.append("'");
+            isFirst = false;
+        }
+        return output.toString();
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * See also {@link #hasOption}.
+     *
+     * @param logLevelOptions the options to check
+     * @return true if any of the passed options is present
+     */
+    public boolean hasAnyOption(Collection<String> logLevelOptions) {
+        for (String option : logLevelOptions) {
+            if (hasOption(option)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 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) {
+            throw new IllegalArgumentException(String.format("Option '%s' not defined.", option));
+        }
+        return parsedOption;
+    }
+
+    public List<String> getExtraArguments() {
+        return extraArguments;
+    }
+
+    void addExtraValue(String value) {
+        extraArguments.add(value);
+    }
+
+    ParsedCommandLineOption addOption(String optionStr, CommandLineOption option) {
+        ParsedCommandLineOption parsedOption = optionsByString.get(optionStr);
+        presentOptions.addAll(option.getOptions());
+        return parsedOption;
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.java b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.java
new file mode 100644
index 0000000..a130de2
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/ParsedCommandLineOption.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.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ParsedCommandLineOption {
+    private final List<String> values = new ArrayList<String>();
+
+    public String getValue() {
+        if (values.isEmpty()) {
+            throw new IllegalStateException("Option does not have any value.");
+        }
+        if (values.size() > 1) {
+            throw new IllegalStateException("Option has multiple values.");
+        }
+        return values.get(0);
+    }
+
+    public List<String> getValues() {
+        return values;
+    }
+
+    public void addArgument(String argument) {
+        values.add(argument);
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/ProjectPropertiesCommandLineConverter.java b/subprojects/cli/src/main/java/org/gradle/cli/ProjectPropertiesCommandLineConverter.java
new file mode 100644
index 0000000..9d1b5f8
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/ProjectPropertiesCommandLineConverter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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.cli;
+
+public class ProjectPropertiesCommandLineConverter extends AbstractPropertiesCommandLineConverter{
+
+    @Override
+    protected String getPropertyOption() {
+        return "P";
+    }
+
+    @Override
+    protected String getPropertyOptionDetailed() {
+        return "project-prop";
+    }
+
+    @Override
+    protected String getPropertyOptionDescription() {
+        return "Set project property for the build script (e.g. -Pmyprop=myvalue).";
+    }
+}
diff --git a/subprojects/cli/src/main/java/org/gradle/cli/SystemPropertiesCommandLineConverter.java b/subprojects/cli/src/main/java/org/gradle/cli/SystemPropertiesCommandLineConverter.java
new file mode 100644
index 0000000..a0532e0
--- /dev/null
+++ b/subprojects/cli/src/main/java/org/gradle/cli/SystemPropertiesCommandLineConverter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.cli;
+
+public class SystemPropertiesCommandLineConverter extends AbstractPropertiesCommandLineConverter {
+
+    @Override
+    protected String getPropertyOption() {
+        return "D";
+    }
+
+    @Override
+    protected String getPropertyOptionDetailed() {
+        return "system-prop";
+    }
+
+    @Override
+    protected String getPropertyOptionDescription() {
+        return "Set system property of the JVM (e.g. -Dmyprop=myvalue).";
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/AbstractPropertiesCommandLineConverterTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/AbstractPropertiesCommandLineConverterTest.groovy
new file mode 100644
index 0000000..0a3832f
--- /dev/null
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/AbstractPropertiesCommandLineConverterTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 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.cli
+
+import spock.lang.Specification
+
+class AbstractPropertiesCommandLineConverterTest extends Specification {
+
+    final AbstractPropertiesCommandLineConverter converter = new AbstractPropertiesCommandLineConverter(){
+        @Override
+        protected String getPropertyOption() {
+            return "A"
+        }
+
+        @Override
+        protected String getPropertyOptionDetailed() {
+            return "another-prop"
+        }
+
+        @Override
+        protected String getPropertyOptionDescription() {
+            return "Test Description of a test property based option"
+        }
+    }
+
+    def convert(String... args) {
+        converter.convert(Arrays.asList(args)).sort()
+    }
+
+    def "configures property based options"() {
+        def parser = Mock(CommandLineParser);
+        def option = Mock(CommandLineOption);
+        setup:
+            parser.option(_,_) >> option
+            option.hasArguments() >> option
+            option.hasDescription(_) >> option
+        when:
+            converter.configure(parser)
+        then:
+            1 * option.hasArguments() >> option
+            1 * option.hasDescription(converter.propertyOptionDescription) >> option
+            1 * parser.option(converter.propertyOption, converter.propertyOptionDetailed) >> option
+    }
+
+    def "parses properties args"() {
+        expect:
+        convert("-Aa=b", "-Ac=d") == [a: "b", c: "d"]
+    }
+
+    def "parses properties args with no property value"() {
+        expect:
+        convert("-Aa", "-Ab=") == [a: "", b: ""]
+    }
+
+    def "parses properties arg containing equals"() {
+        expect:
+        convert("-Aprop=a b=c", "-Aprop2==", "-Aprop3=ab=") == [prop: 'a b=c', prop2: '=', prop3: 'ab=']
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
new file mode 100644
index 0000000..b3d321e
--- /dev/null
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/CommandLineParserTest.groovy
@@ -0,0 +1,621 @@
+/*
+ * 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.cli
+
+import spock.lang.*
+
+class CommandLineParserTest extends Specification {
+    private final CommandLineParser parser = new CommandLineParser()
+
+    def parsesEmptyCommandLine() {
+        parser.option('a')
+        parser.option('long-value')
+
+        expect:
+        def result = parser.parse([])
+        !result.hasOption('a')
+        !result.hasOption('long-value')
+        result.extraArguments == []
+    }
+
+    def parsesShortOption() {
+        parser.option('a')
+        parser.option('b')
+
+        expect:
+        def result = parser.parse(['-a'])
+        result.hasOption('a')
+        !result.hasOption('b')
+    }
+
+    def canUseDoubleDashesForShortOptions() {
+        parser.option('a')
+
+        expect:
+        def result = parser.parse(['--a'])
+        result.hasOption('a')
+    }
+
+    def parsesShortOptionWithArgument() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse(['-a', 'arg'])
+        result.hasOption('a')
+        result.option('a').value == 'arg'
+        result.option('a').values == ['arg']
+    }
+
+    def parsesShortOptionWithAttachedArgument() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse(['-aarg'])
+        result.hasOption('a')
+        result.option('a').value == 'arg'
+        result.option('a').values == ['arg']
+    }
+
+    def attachedArgumentTakesPrecedenceOverCombinedOption() {
+        parser.option('a').hasArgument()
+        parser.option('b')
+
+        expect:
+        def result = parser.parse(['-ab'])
+        result.hasOption('a')
+        result.option('a').value == 'b'
+        !result.hasOption('b')
+    }
+
+    def parsesShortOptionWithEqualArgument() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse(['-a=arg'])
+        result.hasOption('a')
+        result.option('a').value == 'arg'
+        result.option('a').values == ['arg']
+    }
+
+    def parsesShortOptionWithEqualsCharacterInAttachedArgument() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse(['-avalue=arg'])
+        result.hasOption('a')
+        result.option('a').value == 'value=arg'
+        result.option('a').values == ['value=arg']
+    }
+
+    def parsesShortOptionWithDashCharacterInAttachedArgument() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse(['-avalue-arg'])
+        result.hasOption('a')
+        result.option('a').value == 'value-arg'
+        result.option('a').values == ['value-arg']
+    }
+
+    def parsesCombinedShortOptions() {
+        parser.option('a')
+        parser.option('b')
+
+        expect:
+        def result = parser.parse(['-ab'])
+        result.hasOption('a')
+        result.hasOption('b')
+    }
+
+    def parsesLongOption() {
+        parser.option('long-option-a')
+        parser.option('long-option-b')
+
+        expect:
+        def result = parser.parse(['--long-option-a'])
+        result.hasOption('long-option-a')
+        !result.hasOption('long-option-b')
+    }
+
+    def canUseSingleDashForLongOptions() {
+        parser.option('long')
+        parser.option('other').hasArgument()
+
+        expect:
+        def result = parser.parse(['-long', '-other', 'arg'])
+        result.hasOption('long')
+        result.hasOption('other')
+        result.option('other').value == 'arg'
+    }
+
+    def parsesLongOptionWithArgument() {
+        parser.option('long-option-a').hasArgument()
+        parser.option('long-option-b')
+
+        expect:
+        def result = parser.parse(['--long-option-a', 'arg'])
+        result.hasOption('long-option-a')
+        result.option('long-option-a').value == 'arg'
+        result.option('long-option-a').values == ['arg']
+    }
+
+    def parsesLongOptionWithEqualsArgument() {
+        parser.option('long-option-a').hasArgument()
+
+        expect:
+        def result = parser.parse(['--long-option-a=arg'])
+        result.hasOption('long-option-a')
+        result.option('long-option-a').value == 'arg'
+        result.option('long-option-a').values == ['arg']
+    }
+
+    def parsesMultipleOptions() {
+        parser.option('a').hasArgument()
+        parser.option('long-option')
+
+        expect:
+        def result = parser.parse(['--long-option', '-a', 'arg'])
+        result.hasOption('long-option')
+        result.hasOption('a')
+        result.option('a').value == 'arg'
+    }
+
+    def parsesOptionWithMultipleAliases() {
+        parser.option('a', 'b', 'long-option-a')
+
+        expect:
+        def longOptionResult = parser.parse(['--long-option-a'])
+        longOptionResult.hasOption('a')
+        longOptionResult.hasOption('b')
+        longOptionResult.hasOption('long-option-a')
+        longOptionResult.option('a') == longOptionResult.option('long-option-a')
+        longOptionResult.option('a') == longOptionResult.option('b')
+
+        def shortOptionResult = parser.parse(['-a'])
+        shortOptionResult.hasOption('a')
+        shortOptionResult.hasOption('b')
+        shortOptionResult.hasOption('long-option-a')
+    }
+
+    def parsesCommandLineWhenOptionAppearsMultipleTimes() {
+        parser.option('a', 'b', 'long-option-a')
+
+        expect:
+        def result = parser.parse(['--long-option-a', '-a', '-a', '-b'])
+        result.hasOption('a')
+        result.hasOption('b')
+        result.hasOption('long-option-a')
+    }
+
+    def parsesOptionWithMultipleArguments() {
+        parser.option('a', 'long').hasArguments()
+
+        expect:
+        def result = parser.parse(['-a', 'arg1', '--long', 'arg2', '-aarg3', '--long=arg4'])
+        result.hasOption('a')
+        result.hasOption('long')
+        result.option('a').values == ['arg1', 'arg2', 'arg3', 'arg4']
+    }
+
+    def parsesCommandLineWithSubcommand() {
+        parser.option('a')
+
+        expect:
+        def singleArgResult = parser.parse(['a'])
+        singleArgResult.extraArguments == ['a']
+        !singleArgResult.hasOption('a')
+
+        def multipleArgsResult = parser.parse(['a', 'b'])
+        multipleArgsResult.extraArguments == ['a', 'b']
+        !multipleArgsResult.hasOption('a')
+    }
+
+    def parsesCommandLineWithOptionsAndSubcommand() {
+        parser.option('a')
+
+        expect:
+        def optionBeforeSubcommandResult = parser.parse(['-a', 'a'])
+        optionBeforeSubcommandResult.extraArguments == ['a']
+        optionBeforeSubcommandResult.hasOption('a')
+
+        def optionAfterSubcommandResult = parser.parse(['a', '-a'])
+        optionAfterSubcommandResult.extraArguments == ['a', '-a']
+        !optionAfterSubcommandResult.hasOption('a')
+    }
+
+    def parsesCommandLineWithOptionsAndSubcommandWhenMixedOptionsAllowed() {
+        parser.option('a')
+        parser.allowMixedSubcommandsAndOptions()
+
+        expect:
+        def optionBeforeSubcommandResult = parser.parse(['-a', 'a'])
+        optionBeforeSubcommandResult.extraArguments == ['a']
+        optionBeforeSubcommandResult.hasOption('a')
+
+        def optionAfterSubcommandResult = parser.parse(['a', '-a'])
+        optionAfterSubcommandResult.extraArguments == ['a']
+        optionAfterSubcommandResult.hasOption('a')
+    }
+
+    def parsesCommandLineWithSubcommandThatHasOptions() {
+        when:
+        def result = parser.parse(['a', '--option', 'b'])
+
+        then:
+        result.extraArguments == ['a', '--option', 'b']
+
+        when:
+        parser.allowMixedSubcommandsAndOptions()
+        result = parser.parse(['a', '--option', 'b'])
+
+        then:
+        result.extraArguments == ['a', '--option', 'b']
+    }
+
+    def canMapOptionToSubcommand() {
+        parser.option('a').mapsToSubcommand('subcmd')
+
+        expect:
+        def result = parser.parse(['-a', '--option', 'b'])
+        result.extraArguments == ['subcmd', '--option', 'b']
+        result.hasOption('a')
+    }
+
+    def canCombineSubcommandShortOptionWithOtherShortOptions() {
+        parser.option('a').mapsToSubcommand('subcmd')
+        parser.option('b')
+
+        when:
+        def result = parser.parse(['-abc', '--option', 'b'])
+
+        then:
+        result.extraArguments == ['subcmd', '-b', '-c', '--option', 'b']
+        result.hasOption('a')
+        !result.hasOption('b')
+
+        when:
+        result = parser.parse(['-bac', '--option', 'b'])
+
+        then:
+        result.extraArguments == ['subcmd', '-c', '--option', 'b']
+        result.hasOption('a')
+        result.hasOption('b')
+
+        when:
+        parser.allowMixedSubcommandsAndOptions()
+        result = parser.parse(['-abc', '--option', 'b'])
+
+        then:
+        result.extraArguments == ['subcmd', '-c', '--option', 'b']
+        result.hasOption('a')
+        result.hasOption('b')
+
+        when:
+        result = parser.parse(['-bac', '--option', 'b'])
+
+        then:
+        result.extraArguments == ['subcmd', '-c', '--option', 'b']
+        result.hasOption('a')
+        result.hasOption('b')
+    }
+
+    def singleDashIsNotConsideredAnOption() {
+        expect:
+        def result = parser.parse(['-'])
+        result.extraArguments == ['-']
+    }
+
+    def doubleDashMarksEndOfOptions() {
+        parser.option('a')
+
+        expect:
+        def result = parser.parse(['--', '-a'])
+        result.extraArguments == ['-a']
+        !result.hasOption('a')
+    }
+
+    def valuesEmptyWhenOptionIsNotPresentInCommandLine() {
+        parser.option('a').hasArgument()
+
+        expect:
+        def result = parser.parse([])
+        result.option('a').values == []
+    }
+
+    def formatsUsageMessage() {
+        parser.option('a', 'long-option').hasDescription('this is option a')
+        parser.option('b')
+        parser.option('another-long-option').hasDescription('this is a long option')
+        parser.option('z', 'y', 'last-option', 'end-option').hasDescription('this is the last option')
+        parser.option('B')
+        def outstr = new StringWriter()
+
+        expect:
+        parser.printUsage(outstr)
+        outstr.toString().readLines() == [
+                '-a, --long-option                    this is option a',
+                '--another-long-option                this is a long option',
+                '-B',
+                '-b',
+                '-y, -z, --end-option, --last-option  this is the last option'
+        ]
+    }
+    
+    def formatsUsageMessageForDeprecatedAndExperimentalOptions() {
+        parser.option('a', 'long-option').hasDescription('this is option a').deprecated("don't use this")
+        parser.option('b').deprecated('will be removed')
+        parser.option('c').hasDescription('option c').experimental()
+        parser.option('d').experimental()
+        def outstr = new StringWriter()
+
+        expect:
+        parser.printUsage(outstr)
+        outstr.toString().readLines() == [
+                '-a, --long-option  this is option a [deprecated - don\'t use this]',
+                '-b                 [deprecated - will be removed]',
+                '-c                 option c [experimental]',
+                '-d                 [experimental]'
+        ]
+    }
+
+    def showsDeprecationWarning() {
+        def outstr = new StringWriter()
+        def parser = new CommandLineParser(outstr)
+        parser.option("foo").hasDescription("usless option, just for testing").deprecated("Please use --bar instead.")
+        parser.option("x").hasDescription("I'm not deprecated")
+
+        when:
+        parser.parse(["-x"])
+
+        then:
+        outstr.toString() == ''
+
+        when:
+        parser.parse(["--foo"])
+
+        then:
+        outstr.toString().startsWith("The --foo option is deprecated - Please use --bar instead.")
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownShortOption() {
+        when:
+        parser.parse(['-a'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownShortOptionWithDoubleDashes() {
+        when:
+        parser.parse(['--a'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'--a\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownShortOptionWithEqualsArgument() {
+        when:
+        parser.parse(['-a=arg'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownShortOptionWithAttachedArgument() {
+        when:
+        parser.parse(['-aarg'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownLongOption() {
+        when:
+        parser.parse(['--unknown'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'--unknown\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownLongOptionWithSingleDashes() {
+        when:
+        parser.parse(['-unknown'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'-u\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsUnknownLongOptionWithEqualsArgument() {
+        when:
+        parser.parse(['--unknown=arg'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'--unknown\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsLongOptionWithAttachedArgument() {
+        parser.option("long").hasArgument()
+
+        when:
+        parser.parse(['--longvalue'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'--longvalue\'.'
+    }
+
+    def parseFailsWhenCommandLineContainsDashAndEquals() {
+        parser.option("long").hasArgument()
+
+        when:
+        parser.parse(['-='])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'-=\'.'
+    }
+
+    def getOptionFailsForUnknownOption() {
+        def result = parser.parse(['other'])
+
+        when:
+        result.option('unknown')
+
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == 'Option \'unknown\' not defined.'
+
+        when:
+        result.hasOption('unknown')
+
+        then:
+        e = thrown(IllegalArgumentException)
+        e.message == 'Option \'unknown\' not defined.'
+    }
+
+    def parseFailsWhenSingleValueOptionHasMultipleArguments() {
+        parser.option('a').hasArgument()
+
+        when:
+        parser.parse(['-a=arg1', '-a', 'arg2'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Multiple arguments were provided for command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenArgumentIsMissing() {
+        parser.option('a').hasArgument()
+
+        when:
+        parser.parse(['-a'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'No argument was provided for command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenArgumentIsMissingFromEqualsForm() {
+        parser.option('a').hasArgument()
+
+        when:
+        parser.parse(['-a='])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'An empty argument was provided for command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenEmptyArgumentIsProvided() {
+        parser.option('a').hasArgument()
+
+        when:
+        parser.parse(['-a', ''])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'An empty argument was provided for command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenArgumentIsMissingAndAnotherOptionFollows() {
+        parser.option('a').hasArgument()
+
+        when:
+        parser.parse(['-a', '-b'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'No argument was provided for command-line option \'-a\'.'
+    }
+
+    def parseFailsWhenArgumentIsMissingAndOptionsAreCombined() {
+        parser.option('a')
+        parser.option('b').hasArgument()
+
+        when:
+        parser.parse(['-ab'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'No argument was provided for command-line option \'-b\'.'
+    }
+
+    def parseFailsWhenAttachedArgumentIsProvidedForOptionWhichDoesNotTakeAnArgument() {
+        parser.option('a')
+
+        when:
+        parser.parse(['-aarg'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Unknown command-line option \'-r\'.'
+    }
+
+    def parseFailsWhenEqualsArgumentIsProvidedForOptionWhichDoesNotTakeAnArgument() {
+        parser.option('a')
+
+        when:
+        parser.parse(['-a=arg'])
+
+        then:
+        def e = thrown(CommandLineArgumentException)
+        e.message == 'Command-line option \'-a\' does not take an argument.'
+    }
+    
+    def "allow unknown options mode collects unknown options"() {
+        given:
+        parser.option("a")
+
+        and:
+        parser.allowUnknownOptions()
+
+        when:
+        def result = parser.parse(['-a', '-b'])
+
+        then:
+        result.option("a") != null
+        
+        and:
+        result.extraArguments.contains("-b")
+    }
+    
+    @Issue("http://issues.gradle.org/browse/GRADLE-1871")
+    def "unknown options containing known arguments in their value are allowed"() {
+        given:
+        parser.option("a")
+
+        and:
+        parser.allowUnknownOptions()
+
+        when:
+        def result = parser.parse(['-a', '-ba', '-ba=c'])
+
+        then:
+        result.option("a") != null
+        
+        and:
+        "-ba" in result.extraArguments
+        "-ba=c" in result.extraArguments
+    }
+
+}
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy
new file mode 100644
index 0000000..1e3f52e
--- /dev/null
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/ParsedCommandLineTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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.cli
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 4/16/12
+ */
+class ParsedCommandLineTest extends Specification {
+
+    def "knows if contains an option"() {
+        when:
+        def line = new ParsedCommandLine([cmdOption("a"), cmdOption("b"), cmdOption("c")]);
+        line.addOption("b", cmdOption("b"))
+
+        then:
+        !line.hasOption("a")
+        line.hasOption("b")
+
+        !line.hasAnyOption(["a", "c"])
+        line.hasAnyOption(["b", "c"])
+    }
+
+    private CommandLineOption cmdOption(String opt) {
+        new CommandLineOption([opt])
+    }
+}
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/ProjectPropertiesCommandLineConverterTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/ProjectPropertiesCommandLineConverterTest.groovy
new file mode 100644
index 0000000..00623ba
--- /dev/null
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/ProjectPropertiesCommandLineConverterTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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.cli
+
+import spock.lang.Specification
+
+class ProjectPropertiesCommandLineConverterTest extends Specification{
+  def converter = new ProjectPropertiesCommandLineConverter();
+
+  def convert(String... args) {
+    converter.convert(Arrays.asList(args)).sort()
+  }
+
+  def "parses project properties args"() {
+    expect:
+    convert("-Pa=b", "-Pc=d") == [a: "b", c: "d"]
+  }
+}
diff --git a/subprojects/cli/src/test/groovy/org/gradle/cli/SystemPropertiesCommandLineConverterTest.groovy b/subprojects/cli/src/test/groovy/org/gradle/cli/SystemPropertiesCommandLineConverterTest.groovy
new file mode 100644
index 0000000..595e168
--- /dev/null
+++ b/subprojects/cli/src/test/groovy/org/gradle/cli/SystemPropertiesCommandLineConverterTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.cli
+
+import spock.lang.*
+
+class SystemPropertiesCommandLineConverterTest extends Specification {
+
+    def converter = new SystemPropertiesCommandLineConverter();
+
+    def convert(String... args) {
+        converter.convert(Arrays.asList(args)).sort()
+    }
+
+    def "parses system properties args"() {
+        expect:
+        convert("-Da=b", "-Dc=d") == [a: "b", c: "d"]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/code-quality/code-quality.gradle b/subprojects/code-quality/code-quality.gradle
index 833e67b..509652d 100644
--- a/subprojects/code-quality/code-quality.gradle
+++ b/subprojects/code-quality/code-quality.gradle
@@ -13,25 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+apply from: "$rootDir/gradle/providedConfiguration.gradle"
+
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
 
-    compile "org.codenarc:CodeNarc:0.13 at jar"
     compile libraries.slf4j_api
 
-    // CodeNarc dependencies
-    runtime libraries.log4j_to_slf4j,
-            "org.gmetrics:GMetrics:0.3 at jar"
+    // minimal dependencies to make our code compile
+    // we don't ship these dependencies because findbugs plugin will download them (and more) at runtime
+    provided "com.google.code.findbugs:findbugs:2.0.0 at jar"
+    provided "com.google.code.findbugs:bcel:2.0.0 at jar"
+    provided "dom4j:dom4j:1.6.1 at jar"
+    provided "jaxen:jaxen:1.1.1 at jar"
+}
+
+useTestFixtures()
 
-    // Checkstyle dependencies
-    runtime "com.puppycrawl.tools:checkstyle:5.3 at jar",
-            libraries.guava,
-            libraries.antlr,
-            "commons-beanutils:commons-beanutils-core:1.8.3 at jar"
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
-}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
new file mode 100644
index 0000000..beff97d
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import org.hamcrest.Matcher
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.startsWith
+
+class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "check"
+    }
+
+    def setup() {
+        writeBuildFile()
+        writeConfigFile()
+    }
+
+    def "analyze good code"() {
+        goodCode()
+
+        expect:
+        succeeds('check')
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.Class1"))
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.Class2"))
+        file("build/reports/checkstyle/test.xml").assertContents(containsClass("org.gradle.TestClass1"))
+        file("build/reports/checkstyle/test.xml").assertContents(containsClass("org.gradle.TestClass2"))
+    }
+
+    def "analyze bad code"() {
+        file("src/main/java/org/gradle/class1.java") << "package org.gradle; class class1 { }"
+        file("src/test/java/org/gradle/testclass1.java") << "package org.gradle; class testclass1 { }"
+        file("src/main/groovy/org/gradle/class2.java") << "package org.gradle; class class2 { }"
+        file("src/test/groovy/org/gradle/testclass2.java") << "package org.gradle; class testclass2 { }"
+
+        expect:
+        fails("check")
+        failure.assertHasDescription("Execution failed for task ':checkstyleMain'")
+        failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at"))
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class2"))
+    }
+
+    def "is incremental"() {
+        given:
+        goodCode()
+
+        expect:
+        succeeds("checkstyleMain") && ":checkstyleMain" in nonSkippedTasks
+        succeeds(":checkstyleMain") && ":checkstyleMain" in skippedTasks
+
+        when:
+        file("build/reports/checkstyle/main.xml").delete()
+
+        then:
+        succeeds("checkstyleMain") && ":checkstyleMain" in nonSkippedTasks
+    }
+    
+    def "can configure reporting"() {
+        given:
+        goodCode()
+        
+        when:
+        buildFile << """
+            checkstyleMain.reports { xml.destination "foo.xml" }
+        """
+        
+        then:
+        succeeds "checkstyleMain"
+        file("foo.xml").exists()
+    }
+
+    private goodCode() {
+        file('src/main/java/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        file('src/test/java/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
+        file('src/main/groovy/org/gradle/Class2.java') << 'package org.gradle; class Class1 { }'
+        file('src/test/groovy/org/gradle/TestClass2.java') << 'package org.gradle; class TestClass1 { }'
+    }
+
+    private Matcher<String> containsClass(String className) {
+        containsLine(containsString(className.replace(".", File.separator)))
+    }
+
+    private void writeBuildFile() {
+        file("build.gradle") << """
+apply plugin: "groovy"
+apply plugin: "checkstyle"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy localGroovy()
+}
+        """
+    }
+
+    private void writeConfigFile() {
+        file("config/checkstyle/checkstyle.xml") << """
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+    <module name="TreeWalker">
+        <module name="TypeName"/>
+    </module>
+</module>
+        """
+    }
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
new file mode 100644
index 0000000..e1de805
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import static org.hamcrest.Matchers.startsWith
+
+class CodeNarcPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "check"
+    }
+
+    def setup() {
+        writeBuildFile()
+        writeConfigFile()
+    }
+
+    def "analyze good code"() {
+        goodCode()
+
+        expect:
+        succeeds("check")
+        file("build/reports/codenarc/main.html").exists()
+        file("build/reports/codenarc/test.html").exists()
+    }
+
+    def "is incremental"() {
+        given:
+        goodCode()
+        
+        expect:
+        succeeds("codenarcMain") && ":codenarcMain" in nonSkippedTasks
+        succeeds(":codenarcMain") && ":codenarcMain" in skippedTasks
+
+        when:
+        file("build/reports/codenarc/main.html").delete()
+
+        then:
+        succeeds("codenarcMain") && ":codenarcMain" in nonSkippedTasks
+    }
+    
+    def "can generate multiple reports"() {
+        given:
+        buildFile << """
+            codenarcMain.reports {
+                xml.enabled true
+                text.enabled true
+            }
+        """
+
+        and:
+        goodCode()
+        
+        expect:
+        succeeds("check")
+        ":codenarcMain" in nonSkippedTasks
+        file("build/reports/codenarc/main.html").exists()
+        file("build/reports/codenarc/main.xml").exists()
+        file("build/reports/codenarc/main.txt").exists()
+    }
+    
+    def "analyze bad code"() {
+        file("src/main/groovy/org/gradle/class1.java") << "package org.gradle; class class1 { }"
+        file("src/main/groovy/org/gradle/Class2.groovy") << "package org.gradle; class Class2 { }"
+        file("src/test/groovy/org/gradle/TestClass1.java") << "package org.gradle; class TestClass1 { }"
+        file("src/test/groovy/org/gradle/testclass2.groovy") << "package org.gradle; class testclass2 { }"
+
+        expect:
+        fails("check")
+        failure.assertHasDescription("Execution failed for task ':codenarcTest'")
+        failure.assertThatCause(startsWith("CodeNarc rule violations were found. See the report at "))
+        !file("build/reports/codenarc/main.html").text.contains("Class2")
+        file("build/reports/codenarc/test.html").text.contains("testclass2")
+    }
+
+    private goodCode() {
+        file("src/main/groovy/org/gradle/class1.java") << "package org.gradle; class class1 { }"
+        file("src/test/groovy/org/gradle/testclass1.java") << "package org.gradle; class testclass1 { }"
+        file("src/main/groovy/org/gradle/Class2.groovy") << "package org.gradle; class Class2 { }"
+        file("src/test/groovy/org/gradle/TestClass2.groovy") << "package org.gradle; class TestClass2 { }"
+    }
+    
+    private void writeBuildFile() {
+        file("build.gradle") << """
+apply plugin: "groovy"
+apply plugin: "codenarc"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies { 
+    groovy localGroovy()
+}
+        """
+    }
+
+    private void writeConfigFile() {
+        file("config/codenarc/codenarc.xml") << """
+<ruleset xmlns="http://codenarc.org/ruleset/1.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
+        xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
+    <ruleset-ref path="rulesets/naming.xml"/>
+</ruleset>
+        """
+    }
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
new file mode 100644
index 0000000..68ca63f
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
@@ -0,0 +1,206 @@
+/*
+ * 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.plugins.quality
+
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.util.TestFile
+import org.hamcrest.Matcher
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+
+class CodeQualityPluginIntegrationTest extends AbstractIntegrationTest {
+    {
+        // code-quality plugin is deprecated
+        executer.withDeprecationChecksDisabled()
+    }
+
+    @Test
+    public void handlesEmptyProjects() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'java'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+        inTestDirectory().withTasks('check').run()
+    }
+
+    @Test
+    public void generatesReportForJavaSource() {
+        testFile('build.gradle') << '''
+apply plugin: 'java'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/java/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        testFile('src/test/java/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
+
+        inTestDirectory().withTasks('check').run()
+
+        testFile('build/checkstyle/main.xml').assertContents(containsClass('org.gradle.Class1'))
+        testFile('build/checkstyle/test.xml').assertContents(containsClass('org.gradle.TestClass1'))
+    }
+
+    @Test
+    public void generatesReportForJavaSourceInGroovySourceDirs() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/groovy/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        testFile('src/test/groovy/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
+
+        inTestDirectory().withTasks('check').run()
+
+        testFile('build/checkstyle/main.xml').assertContents(containsClass('org.gradle.Class1'))
+        testFile('build/checkstyle/test.xml').assertContents(containsClass('org.gradle.TestClass1'))
+    }
+
+    private Matcher<String> containsClass(String classname) {
+        return containsLine(containsString(classname.replace('.', File.separator) + '.java'))
+    }
+
+    @Test
+    public void checkstyleOnlyChecksJavaSource() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/groovy/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        testFile('src/main/groovy/org/gradle/Class2.java') << 'package org.gradle; class Class2 { }'
+        testFile('src/main/groovy/org/gradle/class3.groovy') << 'package org.gradle; class class3 { }'
+
+        inTestDirectory().withTasks('checkstyleMain').run()
+
+        testFile('build/checkstyle/main.xml').assertExists()
+        testFile('build/checkstyle/main.xml').assertContents(not(containsClass('org.gradle.class3')))
+    }
+
+    @Test
+    public void checkstyleViolationBreaksBuild() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/java/org/gradle/class1.java') << 'package org.gradle; class class1 { }'
+        testFile('src/main/groovy/org/gradle/class2.java') << 'package org.gradle; class class2 { }'
+
+        ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
+        failure.assertHasDescription('Execution failed for task \':checkstyleMain\'')
+        failure.assertThatCause(startsWith('Checkstyle rule violations were found. See the report at'))
+
+        testFile('build/checkstyle/main.xml').assertExists()
+    }
+
+    @Test
+    public void generatesReportForGroovySource() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+        writeCodeNarcConfigFile()
+
+        testFile('src/main/groovy/org/gradle/Class1.groovy') << 'package org.gradle; class Class1 { }'
+        testFile('src/test/groovy/org/gradle/TestClass1.groovy') << 'package org.gradle; class TestClass1 { }'
+
+        inTestDirectory().withTasks('check').run()
+
+        testFile('build/reports/codenarc/main.html').assertExists()
+        testFile('build/reports/codenarc/test.html').assertExists()
+    }
+
+    @Test
+    public void codeNarcOnlyChecksGroovySource() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+
+        writeCodeNarcConfigFile()
+
+        testFile('src/main/groovy/org/gradle/class1.java') << 'package org.gradle; class class1 { }'
+        testFile('src/main/groovy/org/gradle/Class2.groovy') << 'package org.gradle; class Class2 { }'
+
+        inTestDirectory().withTasks('codenarcMain').run()
+
+        testFile('build/reports/codenarc/main.html').assertExists()
+    }
+
+    @Test
+    public void codeNarcViolationBreaksBuild() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+repositories { mavenCentral() }
+dependencies { groovy localGroovy() }
+'''
+
+        writeCodeNarcConfigFile()
+
+        testFile('src/main/groovy/org/gradle/class1.groovy') << 'package org.gradle; class class1 { }'
+
+        ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
+        failure.assertHasDescription('Execution failed for task \':codenarcMain\'')
+        failure.assertThatCause(startsWith('CodeNarc rule violations were found. See the report at '))
+
+        testFile('build/reports/codenarc/main.html').assertExists()
+    }
+
+    private TestFile writeCheckstyleConfig() {
+        return testFile('config/checkstyle/checkstyle.xml') << '''
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+    <module name="TreeWalker">
+        <module name="TypeName"/>
+    </module>
+</module>'''
+    }
+
+    private TestFile writeCodeNarcConfigFile() {
+        return testFile('config/codenarc/codenarc.xml') << '''
+<ruleset xmlns="http://codenarc.org/ruleset/1.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
+        xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
+    <ruleset-ref path='rulesets/naming.xml'/>
+</ruleset>
+'''
+    }
+
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy
new file mode 100644
index 0000000..b7d5395
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import org.hamcrest.Matcher
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.startsWith
+
+class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "check"
+    }
+
+    def setup() {
+        writeBuildFile()
+    }
+
+    def "analyze good code"() {
+        goodCode()
+        expect:
+        succeeds("check")
+		file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+		file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+    }
+
+    void "analyze bad code"() {
+        file("src/main/java/org/gradle/Class1.java") << "package org.gradle; class Class1 { public boolean equals(Object arg) { return true; } }"
+
+        expect:
+        fails("check")
+		failure.assertHasDescription("Execution failed for task ':findbugsMain'")
+        failure.assertThatCause(startsWith("FindBugs rule violations were found. See the report at"))
+		file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+    }
+
+    def "is incremental"() {
+        given:
+        goodCode()
+
+        expect:
+        succeeds("findbugsMain") && ":findbugsMain" in nonSkippedTasks
+        succeeds(":findbugsMain") && ":findbugsMain" in skippedTasks
+
+        when:
+        file("build/reports/findbugs/main.xml").delete()
+
+        then:
+        succeeds("findbugsMain") && ":findbugsMain" in nonSkippedTasks
+    }
+
+    def "cannot generate multiple reports"() {
+        given:
+        buildFile << """
+            findbugsMain.reports {
+                xml.enabled true
+                html.enabled true
+            }
+        """
+
+        and:
+        goodCode()
+
+        expect:
+        fails "findbugsMain"
+
+        failure.assertHasCause "Findbugs tasks can only have one report enabled"
+    }
+    
+    def "can generate html reports"() {
+        given:
+        buildFile << """
+            findbugsMain.reports {
+                xml.enabled false
+                html.enabled true
+            }
+        """
+        
+        and:
+        goodCode()
+        
+        when:
+        run "findbugsMain"
+        
+        then:
+        file("build/reports/findbugs/main.html").exists()
+    }
+
+    def "can generate no reports"() {
+        given:
+        buildFile << """
+            findbugsMain.reports {
+                xml.enabled false
+                html.enabled false
+            }
+        """
+
+        and:
+        goodCode()
+
+        expect:
+        succeeds "findbugsMain"
+
+        and:
+        !file("build/reports/findbugs/main.html").exists()
+        !file("build/reports/findbugs/main.xml").exists()
+    }
+
+    private goodCode() {
+        file("src/main/java/org/gradle/Class1.java") << "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") << "package org.gradle; class Class1Test { public boolean isFoo(Object arg) { return true; } }"
+    }
+
+    private Matcher<String> containsClass(String className) {
+        containsLine(containsString(className.replace(".", File.separator)))
+    }
+  
+    private void writeBuildFile() {
+        file("build.gradle") << """
+        apply plugin: "java"
+        apply plugin: "findbugs"
+        
+        repositories {
+            mavenCentral()
+        }
+        """
+    }
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/JDependPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/JDependPluginIntegrationTest.groovy
new file mode 100644
index 0000000..8f42125
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/JDependPluginIntegrationTest.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import static org.hamcrest.Matchers.containsString
+
+class JDependPluginIntegrationTest extends WellBehavedPluginTest {
+    def setup() {
+        writeBuildFile()
+    }
+
+    @Override
+    String getMainTask() {
+        return "check"
+    }
+
+    def "analyze code"() {
+        goodCode()
+
+        expect:
+        succeeds("check")
+        file("build/reports/jdepend/main.xml").assertContents(containsString("org.gradle.Class1"))
+        file("build/reports/jdepend/test.xml").assertContents(containsString("org.gradle.Class1Test"))
+    }
+
+    def "is incremental"() {
+        given:
+        goodCode()
+
+        expect:
+        succeeds("jdependMain") && ":jdependMain" in nonSkippedTasks
+        succeeds(":jdependMain") && ":jdependMain" in skippedTasks
+
+        when:
+        file("build/reports/jdepend/main.xml").delete()
+
+        then:
+        succeeds("jdependMain") && ":jdependMain" in nonSkippedTasks
+    }
+
+    def "cannot generate multiple reports"() {
+        given:
+        buildFile << """
+            jdependMain.reports {
+                xml.enabled true
+                text.enabled true
+            }
+        """
+
+        and:
+        goodCode()
+
+        expect:
+        fails "jdependMain"
+
+        failure.assertHasCause "JDepend tasks can only have one report enabled"
+    }
+
+    def "can generate text reports"() {
+        given:
+        buildFile << """
+            jdependMain.reports {
+                xml.enabled false
+                text.enabled true
+            }
+        """
+
+        and:
+        goodCode()
+
+        when:
+        run "jdependMain"
+
+        then:
+        file("build/reports/jdepend/main.txt").exists()
+    }
+
+    def "can't generate no reports"() {
+        given:
+        buildFile << """
+            jdependMain.reports {
+                xml.enabled false
+                text.enabled false
+            }
+        """
+
+        and:
+        goodCode()
+
+        expect:
+        fails "jdependMain"
+
+        and:
+        failure.assertHasCause "JDepend tasks must have one report enabled"
+    }
+
+    private goodCode() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; class Class1 { public boolean is() { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; class Class1Test { public boolean is() { return true; } }"
+    }
+
+    private void writeBuildFile() {
+        file("build.gradle") << """
+apply plugin: "java"
+apply plugin: "jdepend"
+
+repositories {
+    mavenCentral()
+}
+        """
+    }
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
new file mode 100644
index 0000000..7d2bee4
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import org.hamcrest.Matcher
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+
+class PmdPluginIntegrationTest extends WellBehavedPluginTest {
+    def setup() {
+        writeBuildFile()
+    }
+
+    @Override
+    String getMainTask() {
+        return "check"
+    }
+
+    def "analyze good code"() {
+        goodCode()
+
+        expect:
+        executer.withArguments("--info")
+        succeeds("check")
+        file("build/reports/pmd/main.xml").exists()
+        file("build/reports/pmd/test.xml").exists()
+    }
+
+    private goodCode() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; class Class1Test { public boolean isFoo(Object arg) { return true; } }"
+    }
+
+    def "analyze bad code"() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; class Class1Test { {} public boolean equals(Object arg) { return true; } }"
+        
+        expect:
+        fails("check")
+		failure.assertHasDescription("Execution failed for task ':pmdTest'")
+		failure.assertThatCause(containsString("PMD found 2 rule violations"))
+        file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
+		file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+    }
+
+    def "can configure reporting"() {
+        given:
+        goodCode()
+
+        and:
+        buildFile << """
+            pmdMain {
+                reports {
+                    xml.enabled false
+                    html.destination "htmlReport.html"
+                }
+            }
+        """
+        expect:
+        succeeds("check")
+        
+        !file("build/reports/pmd/main.xml").exists()
+        file("htmlReport.html").exists()
+    }
+    
+    private void writeBuildFile() {
+        file("build.gradle") << """
+apply plugin: "java"
+apply plugin: "pmd"
+
+repositories {
+    mavenCentral()
+}
+        """
+    }
+
+    private Matcher<String> containsClass(String className) {
+        containsLine(containsString(className.replace(".", File.separator)))
+    }
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy
new file mode 100644
index 0000000..67fe949
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/internal/FindBugsSpecBuilderTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal
+
+import spock.lang.Specification
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.NamedDomainObjectSet
+import org.gradle.api.file.FileCollection
+import org.gradle.api.reporting.SingleFileReport
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
+
+class FindBugsSpecBuilderTest extends Specification {
+
+    FileCollection classes = Mock()
+    FindBugsSpecBuilder builder = new FindBugsSpecBuilder(classes)
+
+    def setup(){
+        _ * classes.getFiles() >> Collections.emptyList()
+    }
+    def "fails with empty classes Collection"() {
+        when:
+        new FindBugsSpecBuilder(null)
+        then:
+        thrown(InvalidUserDataException)
+
+        when:
+        classes.empty >> true
+        new FindBugsSpecBuilder(classes)
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "with reports disabled"() {
+        setup:
+        NamedDomainObjectSet enabledReportSet = Mock()
+        FindBugsReportsImpl report = Mock()
+
+        report.enabled >> enabledReportSet;
+        enabledReportSet.empty >> true
+        when:
+        builder.configureReports(report)
+        def spec = builder.build()
+        then:
+        !spec.arguments.contains("-outputFile")
+    }
+
+    def "with debugging"() {
+        when:
+        builder.withDebugging(debug)
+        def spec = builder.build()
+        then:
+        spec.debugEnabled == debug
+
+        where:
+        debug << [false, true]
+    }
+
+    def "more than 1 enabled report throws exception"() {
+        setup:
+        NamedDomainObjectSet enabledReportSet = Mock()
+        FindBugsReportsImpl report = Mock()
+        report.enabled >> enabledReportSet;
+        enabledReportSet.empty >> false
+        enabledReportSet.size() >> 2
+
+        when:
+        builder.configureReports(report)
+        builder.build()
+
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message == "Findbugs tasks can only have one report enabled, however both the xml and html report are enabled. You need to disable one of them."
+    }
+
+    def "with report configured"() {
+        setup:
+        SingleFileReport singleReport = Mock()
+        File destination = Mock()
+        NamedDomainObjectSet enabledReportSet = Mock()
+        FindBugsReportsImpl report = Mock()
+
+        report.enabled >> enabledReportSet;
+        report.firstEnabled >> singleReport
+        singleReport.name >> reportType
+        destination.absolutePath >> "/absolute/report/output"
+        singleReport.destination >> destination
+        enabledReportSet.empty >> false
+        enabledReportSet.size() >> 1
+
+
+        when:
+        builder.configureReports(report)
+        def args = builder.build().arguments
+
+        then:
+        args.contains("-$reportType".toString())
+        args.contains("-outputFile")
+        args.contains(destination.absolutePath)
+
+        where:
+        reportType << ["xml", "html"];
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
new file mode 100644
index 0000000..71ecb3a
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
@@ -0,0 +1,169 @@
+/*
+ * 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.plugins.quality
+
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.project.IsolatedAntBuilder
+import org.gradle.api.plugins.quality.internal.CheckstyleReportsImpl
+import org.gradle.api.reporting.Reporting
+import org.gradle.util.DeprecationLogger
+import org.gradle.api.tasks.*
+
+/**
+ * Runs Checkstyle against some source files.
+ */
+class Checkstyle extends SourceTask implements VerificationTask, Reporting<CheckstyleReports> {
+    /**
+     * The class path containing the Checkstyle library to be used.
+     */
+    @InputFiles
+    FileCollection checkstyleClasspath
+
+    /**
+     * The class path containing the compiled classes for the source files to be analyzed.
+     */
+    @InputFiles
+    FileCollection classpath
+
+    /**
+     * The Checkstyle configuration file to use.
+     */
+    @InputFile
+    File configFile
+
+    /**
+     * The properties available for use in the configuration file. These are substituted into the configuration
+     * file.
+     */
+    @Input
+    @Optional
+    Map<String, Object> configProperties = [:]
+
+    /**
+     * The properties available for use in the configuration file. These are substituted into the configuration
+     * file.
+     * 
+     * @deprecated renamed to <tt>configProperties</tt>
+     */
+    @Deprecated
+    Map<String, Object> getProperties() {
+        DeprecationLogger.nagUserOfReplacedProperty("Checkstyle.properties", "configProperties")
+        getConfigProperties()
+    }
+
+    /**
+     * The properties available for use in the configuration file. These are substituted into the configuration
+     * file.
+     *
+     * @deprecated renamed to <tt>configProperties</tt>
+     */
+    @Deprecated
+    void setProperties(Map<String, Object> properties) {
+        DeprecationLogger.nagUserOfReplacedProperty("Checkstyle.properties", "configProperties")
+        setConfigProperties(properties)
+    }
+
+    @Nested
+    private final CheckstyleReportsImpl reports = services.get(Instantiator).newInstance(CheckstyleReportsImpl, this)
+
+    /**
+     * The reports to be generated by this task.
+     *
+     * @return The reports container
+     */
+    CheckstyleReports getReports() {
+        reports
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     *
+     * The contained reports can be configured by name and closures. Example:
+     *
+     * <pre>
+     * checkstyleTask {
+     *   reports {
+     *     xml {
+     *       destination "build/codenarc.xml"
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param closure The configuration
+     * @return The reports container
+     */
+    CheckstyleReports reports(Closure closure) {
+        reports.configure(closure)
+    }
+
+    /**
+     * Returns the destination file for the XML report.
+     *
+     * @deprecated Use {@code reports.xml.destination} instead.
+     */
+    @Deprecated
+    File getResultFile() {
+        DeprecationLogger.nagUserOfReplacedProperty("Checkstyle.resultFile", "reports.xml.destination")
+        return reports.xml.destination
+    }
+
+    /**
+     * @deprecated Use {@code reports.xml.destination} instead.
+     */
+    @Deprecated
+    void setResultFile(File file) {
+        DeprecationLogger.nagUserOfReplacedProperty("Checkstyle.resultFile", "reports.xml.destination")
+        reports.xml.destination = file
+    }
+
+    /**
+     * Whether or not this task will ignore failures and continue running the build.
+     */
+    boolean ignoreFailures
+
+    @TaskAction
+    public void run() {
+        def propertyName = "org.gradle.checkstyle.violations"
+        def antBuilder = services.get(IsolatedAntBuilder)
+        antBuilder.withClasspath(getCheckstyleClasspath()).execute {
+            ant.taskdef(name: 'checkstyle', classname: 'com.puppycrawl.tools.checkstyle.CheckStyleTask')
+
+            ant.checkstyle(config: getConfigFile(), failOnViolation: false, failureProperty: propertyName) {
+                getSource().addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+                getClasspath().addToAntBuilder(ant, 'classpath')
+                formatter(type: 'plain', useFile: false)
+                if (reports.xml.enabled) {
+                    formatter(type: 'xml', toFile: reports.xml.destination)
+                }
+
+                getConfigProperties().each { key, value ->
+                    property(key: key, value: value.toString())
+                }
+            }
+
+            if (!getIgnoreFailures() && ant.project.properties[propertyName]) {
+                if (reports.xml.enabled) {
+                    throw new GradleException("Checkstyle rule violations were found. See the report at ${reports.xml.destination}.")
+                } else {
+                    throw new GradleException("Checkstyle rule violations were found")
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java
deleted file mode 100644
index 2dce827..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java
+++ /dev/null
@@ -1,133 +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.plugins.quality;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.plugins.quality.internal.AntCheckstyle;
-import org.gradle.api.tasks.*;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Runs Checkstyle against some source files.
- */
-public class Checkstyle extends SourceTask implements VerificationTask {
-    private File configFile;
-
-    private File resultFile;
-
-    private FileCollection classpath;
-
-    private Map<String, Object> properties = new HashMap<String, Object>();
-
-    private AntCheckstyle antCheckstyle = new AntCheckstyle();
-
-    private boolean ignoreFailures;
-
-    @TaskAction
-    public void check() {
-        antCheckstyle.checkstyle(getAnt(), getSource(), getConfigFile(), getResultFile(), getClasspath(), getProperties(), isIgnoreFailures());
-    }
-
-    /**
-     * Returns the Checkstyle configuration file to use.
-     *
-     * @return The configuration file.
-     */
-    @InputFile
-    public File getConfigFile() {
-        return configFile;
-    }
-
-    /**
-     * Specifies the Checkstyle configuration file to use.
-     *
-     * @param configFile The configuration file. Must not be null.
-     */
-    public void setConfigFile(File configFile) {
-        this.configFile = configFile;
-    }
-
-    /**
-     * Returns the file to generate the XML results to.
-     *
-     * @return The result XML file.
-     */
-    @OutputFile
-    public File getResultFile() {
-        return resultFile;
-    }
-
-    /**
-     * Specifies the file to generate the XML results to.
-     *
-     * @param resultFile The result XML file. Must not be null.
-     */
-    public void setResultFile(File resultFile) {
-        this.resultFile = resultFile;
-    }
-
-    /**
-     * Returns the classpath containing the compiled classes for the source files to be inspected.
-     *
-     * @return The classpath.
-     */
-    @InputFiles
-    public FileCollection getClasspath() {
-        return classpath;
-    }
-
-    /**
-     * Specified the classpath containing the compiled classes for the source file to be inspected.
-     *
-     * @param classpath The classpath. Must not be null.
-     */
-    public void setClasspath(FileCollection classpath) {
-        this.classpath = classpath;
-    }
-
-    /**
-     * Returns the properties available for use in the configuration file. These are substituted into the configuration
-     * file.
-     *
-     * @return The properties available in the configuration file. Returns an empty map when there are no such
-     *         properties.
-     */
-    public Map<String, Object> getProperties() {
-        return properties;
-    }
-
-    public void setProperties(Map<String, Object> properties) {
-        this.properties = properties;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean isIgnoreFailures() {
-        return ignoreFailures;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public Checkstyle setIgnoreFailures(boolean ignoreFailures) {
-        this.ignoreFailures = ignoreFailures;
-        return this;
-    }
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy
new file mode 100644
index 0000000..b1c7b5f
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleExtension.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+class CheckstyleExtension extends CodeQualityExtension {
+    /**
+     * The Checkstyle configuration file to use.
+     */
+    File configFile
+    
+    /**
+     * The properties available for use in the configuration file. These are substituted into the configuration
+     * file.
+     */
+    Map<String, Object> configProperties = [:]
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
new file mode 100644
index 0000000..3957427
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstylePlugin.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.plugins.quality.internal.AbstractCodeQualityPlugin
+import org.gradle.api.tasks.SourceSet
+
+class CheckstylePlugin extends AbstractCodeQualityPlugin<Checkstyle> {
+    private CheckstyleExtension extension
+
+    @Override
+    protected String getToolName() {
+        return "Checkstyle"
+    }
+
+    @Override
+    protected Class<Checkstyle> getTaskType() {
+        return Checkstyle
+    }
+
+    @Override
+    protected CodeQualityExtension createExtension() {
+        extension = project.extensions.create("checkstyle", CheckstyleExtension)
+
+        extension.with {
+            toolVersion = "5.5"
+            configFile = project.file("config/checkstyle/checkstyle.xml")
+        }
+
+        return extension
+    }
+
+    @Override
+    protected void configureTaskDefaults(Checkstyle task, String baseName) {
+        task.conventionMapping.with {
+            checkstyleClasspath = {
+                def config = project.configurations['checkstyle']
+                if (config.dependencies.empty) {
+                    project.dependencies {
+                        checkstyle "com.puppycrawl.tools:checkstyle:$extension.toolVersion"
+                    }
+                }
+                config
+            }
+            configFile = { extension.configFile }
+            configProperties = { extension.configProperties }
+            ignoreFailures = { extension.ignoreFailures }
+        }
+
+        task.reports.xml.conventionMapping.with {
+            enabled = { true }
+            destination = { new File(extension.reportsDir, "${baseName}.xml") }
+        }
+    }
+
+    @Override
+    protected void configureForSourceSet(SourceSet sourceSet, Checkstyle task) {
+        task.with {
+            description = "Run Checkstyle analysis for ${sourceSet.name} classes"
+            classpath = sourceSet.output
+        }
+        task.setSource(sourceSet.allJava)
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java
new file mode 100644
index 0000000..2c8307a
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CheckstyleReports.java
@@ -0,0 +1,34 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.reporting.ReportContainer;
+import org.gradle.api.reporting.SingleFileReport;
+
+/**
+ * The reporting configuration for the the {@link Checkstyle} test.
+ */
+public interface CheckstyleReports extends ReportContainer<SingleFileReport> {
+
+    /**
+     * The checkstyle xml report
+     *
+     * @return The checkstyle xml report
+     */
+    SingleFileReport getXml();
+
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy
new file mode 100644
index 0000000..76ab134
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy
@@ -0,0 +1,145 @@
+/*
+ * 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.plugins.quality
+
+//import org.gradle.api.plugins.quality.internal.ConsoleReportWriter
+
+
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.project.IsolatedAntBuilder
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.plugins.quality.internal.CodeNarcReportsImpl
+import org.gradle.api.reporting.Report
+import org.gradle.api.reporting.Reporting
+import org.gradle.util.DeprecationLogger
+import org.gradle.api.tasks.*
+
+/**
+ * Runs CodeNarc against some source files.
+ */
+class CodeNarc extends SourceTask implements VerificationTask, Reporting<CodeNarcReports> {
+    /**
+     * The class path containing the CodeNarc library to be used.
+     */
+    @InputFiles
+    FileCollection codenarcClasspath
+
+    /**
+     * The CodeNarc configuration file to use.
+     */
+    @InputFile
+    File configFile
+
+    /**
+     * The format type of the CodeNarc report.
+     *
+     * @deprecated Use {@code reports.<report-type>.enabled} instead.
+     */
+    @Deprecated
+    String getReportFormat() {
+        DeprecationLogger.nagUserOfReplacedProperty("CodeNarc.reportFormat", "reports.<report-type>.enabled")
+        reports.firstEnabled?.name
+    }
+
+    /**
+     * @deprecated Use {@code reports.<report-type>.enabled} instead.
+     */
+    @Deprecated
+    void setReportFormat(String reportFormat) {
+        DeprecationLogger.nagUserOfReplacedProperty("CodeNarc.reportFormat", "reports.<report-type>.enabled")
+        reports.each {
+            it.enabled == it.name == reportFormat
+        }
+    }
+
+    /**
+     * The file to write the report to.
+     *
+     * @deprecated Use {@code reports.<report-type>.destination} instead.
+     */
+    @Deprecated
+    File getReportFile() {
+        DeprecationLogger.nagUserOfReplacedProperty("CodeNarc.reportFile", "reports.<report-type>.destination")
+        reports.firstEnabled?.destination
+    }
+
+    /**
+     * @deprecated Use {@code reports.<report-type>.destination} instead.
+     */
+    @Deprecated
+    void setReportFile(File reportFile) {
+        DeprecationLogger.nagUserOfReplacedProperty("CodeNarc.reportFile", "reports.<report-type>.destination")
+        reports.firstEnabled?.destination = reportFile
+    }
+
+    @Nested
+    private final CodeNarcReportsImpl reports = services.get(Instantiator).newInstance(CodeNarcReportsImpl, this)
+
+    /**
+     * Whether or not the build should break when the verifications performed by this task fail.
+     */
+    boolean ignoreFailures
+
+    @TaskAction
+    void run() {
+        logging.captureStandardOutput(LogLevel.INFO)
+        def antBuilder = services.get(IsolatedAntBuilder)
+        antBuilder.withClasspath(getCodenarcClasspath()).execute {
+            ant.taskdef(name: 'codenarc', classname: 'org.codenarc.ant.CodeNarcTask')
+            try {
+                ant.codenarc(ruleSetFiles: "file:${getConfigFile()}", maxPriority1Violations: 0, maxPriority2Violations: 0, maxPriority3Violations: 0) {
+                    reports.enabled.each { Report r ->
+                        report(type: r.name) {
+                            option(name: 'outputFile', value: r.destination)
+                        }
+                    }
+
+                    source.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+                }
+            } catch (Exception e) {
+                if (e.message.matches('Exceeded maximum number of priority \\d* violations.*')) {
+                    if (getIgnoreFailures()) {
+                        return
+                    }
+                    if (reports.html.enabled) {
+                        throw new GradleException("CodeNarc rule violations were found. See the report at ${reports.html.destination}.", e)
+                    } else {
+                        throw new GradleException("CodeNarc rule violations were found.", e)
+                    }
+                }
+                throw e
+            }
+        }
+    }
+
+    /**
+     * Returns the reports to be generated by this task.
+     */
+    CodeNarcReports getReports() {
+        return reports
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     */
+    CodeNarcReports reports(Closure closure) {
+        reports.configure(closure)
+    }
+
+
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.java
deleted file mode 100644
index c124c85..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.java
+++ /dev/null
@@ -1,107 +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.plugins.quality;
-
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.plugins.quality.internal.AntCodeNarc;
-import org.gradle.api.tasks.*;
-
-import java.io.File;
-
-/**
- * Runs CodeNarc against some source files.
- */
-public class CodeNarc extends SourceTask implements VerificationTask {
-    private AntCodeNarc antCodeNarc = new AntCodeNarc();
-
-    private File configFile;
-    private String reportFormat;
-    private File reportFile;
-    private boolean ignoreFailures;
-
-    @TaskAction
-    public void check() {
-        getLogging().captureStandardOutput(LogLevel.INFO);
-        antCodeNarc.execute(getAnt(), getSource(), getConfigFile(), getReportFormat(), getReportFile(), isIgnoreFailures());
-    }
-
-    /**
-     * Returns the CodeNarc configuration file to use.
-     *
-     * @return The CodeNarc configuration file.
-     */
-    @InputFile
-    public File getConfigFile() {
-        return configFile;
-    }
-
-    /**
-     * Specifies the CodeNarc configuration file to use.
-     *
-     * @param configFile The CodeNarc configuration file.
-     */
-    public void setConfigFile(File configFile) {
-        this.configFile = configFile;
-    }
-
-    /**
-     * Returns the format type of the CodeNarc report.
-     *
-     * @return The format type of the CodeNarc report.
-     */
-    @Input
-    public String getReportFormat() {
-        return reportFormat;
-    }
-
-    /**
-     * Specifies the format type of the CodeNarc report.
-     *
-     * @param reportFormat The format type of the CodeNarc report.
-     */
-    public void setReportFormat(String reportFormat) {
-        this.reportFormat = reportFormat;
-    }
-
-    /**
-     * Returns the file to write the report to.
-     *
-     * @return The report file. Must not be null.
-     */
-    @OutputFile
-    public File getReportFile() {
-        return reportFile;
-    }
-
-    public void setReportFile(File reportFile) {
-        this.reportFile = reportFile;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean isIgnoreFailures() {
-        return ignoreFailures;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public CodeNarc setIgnoreFailures(boolean ignoreFailures) {
-        this.ignoreFailures = ignoreFailures;
-        return this;
-    }
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy
new file mode 100644
index 0000000..86d2e57
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcExtension.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.InvalidUserDataException
+
+class CodeNarcExtension extends CodeQualityExtension {
+    /**
+     * The CodeNarc configuration file to use.
+     */
+    File configFile
+
+    /**
+     * The format type of the CodeNarc report. One of <tt>html</tt>, <tt>xml</tt>, <tt>text</tt>, <tt>console</tt>.
+     */
+    String reportFormat
+
+    void setReportFormat(String reportFormat) {
+        if (reportFormat in ["xml", "html", "console", "text"]) {
+            this.reportFormat = reportFormat    
+        } else {
+            throw new InvalidUserDataException("'$reportFormat' is not a valid codenarc report format")
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
new file mode 100644
index 0000000..77acf6f
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcPlugin.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.plugins.GroovyBasePlugin
+import org.gradle.api.plugins.quality.internal.AbstractCodeQualityPlugin
+import org.gradle.api.tasks.SourceSet
+
+class CodeNarcPlugin extends AbstractCodeQualityPlugin<CodeNarc> {
+    private CodeNarcExtension extension
+
+    @Override
+    protected String getToolName() {
+        return "CodeNarc"
+    }
+
+    @Override
+    protected Class<CodeNarc> getTaskType() {
+        return CodeNarc
+    }
+
+    @Override
+    protected Class<?> getBasePlugin() {
+        return GroovyBasePlugin
+    }
+
+    @Override
+    protected CodeQualityExtension createExtension() {
+        extension = project.extensions.create("codenarc", CodeNarcExtension)
+        extension.with {
+            toolVersion = "0.16.1"
+            configFile = project.rootProject.file("config/codenarc/codenarc.xml")
+            reportFormat = "html"
+        }
+        return extension
+    }
+
+    @Override
+    protected void configureTaskDefaults(CodeNarc task, String baseName) {
+        task.conventionMapping.with {
+            codenarcClasspath = {
+                def config = project.configurations['codenarc']
+                if (config.dependencies.empty) {
+                    project.dependencies {
+                        codenarc "org.codenarc:CodeNarc:$extension.toolVersion"
+                    }
+                }
+                config
+            }
+            configFile = { extension.configFile }
+            ignoreFailures = { extension.ignoreFailures }
+        }
+
+        task.reports.all { report ->
+            report.conventionMapping.with {
+                enabled = { report.name == extension.reportFormat }
+                destination = {
+                    def fileSuffix = report.name == 'text' ? 'txt' : report.name
+                    new File(extension.reportsDir, "$baseName.$fileSuffix")
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void configureForSourceSet(SourceSet sourceSet, CodeNarc task) {
+        task.with {
+            description = "Run CodeNarc analysis for $sourceSet.name classes"
+        }
+        task.setSource( sourceSet.allGroovy )
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java
new file mode 100644
index 0000000..808dda9
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarcReports.java
@@ -0,0 +1,48 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.reporting.ReportContainer;
+import org.gradle.api.reporting.SingleFileReport;
+
+/**
+ * The reporting configuration for the the {@link CodeNarc} test.
+ */
+public interface CodeNarcReports extends ReportContainer<SingleFileReport> {
+
+    /**
+     * The codenarc xml report
+     *
+     * @return The codenarc xml report
+     */
+    SingleFileReport getXml();
+
+    /**
+     * The codenarc html report
+     *
+     * @return The codenarc html report
+     */
+    SingleFileReport getHtml();
+
+    /**
+     * The codenarc text report
+     *
+     * @return The codenarc text report
+     */
+    SingleFileReport getText();
+
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityExtension.groovy
new file mode 100644
index 0000000..bf0d5c6
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityExtension.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.tasks.SourceSet
+
+abstract class CodeQualityExtension {
+    /**
+     * The version of the code quality tool to be used.
+     */
+    String toolVersion
+
+    /**
+     * The source sets to be analyzed as part of the <tt>check</tt> and <tt>build</tt> tasks.
+     */
+    Collection<SourceSet> sourceSets
+
+    /**
+     * Whether or not to allow the build to continue if there are warnings.
+     *
+     * Example: ignoreFailures = true
+     */
+    boolean ignoreFailures = false
+
+    /**
+     * The directory where reports will be generated.
+     */
+    File reportsDir
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy
index e13775a..1e7cb80 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy
@@ -18,84 +18,54 @@ package org.gradle.api.plugins.quality
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.plugins.GroovyBasePlugin
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.plugins.JavaPluginConvention
 import org.gradle.api.plugins.ReportingBasePlugin
-import org.gradle.api.tasks.GroovySourceSet
-import org.gradle.api.tasks.SourceSet
+import org.gradle.util.DeprecationLogger
 
 /**
- * A {@link Plugin} which measures and enforces code quality for Java and Groovy projects.
+ * A plugin which measures and enforces code quality for Java and Groovy projects.
+ *
+ * @deprecated use {@link CheckstylePlugin} and {@link CodeNarcPlugin} instead
  */
+ at Deprecated
 public class CodeQualityPlugin implements Plugin<Project> {
+    private Project project
+
     static final String CHECKSTYLE_MAIN_TASK = "checkstyleMain"
     static final String CHECKSTYLE_TEST_TASK = "checkstyleTest"
     static final String CODE_NARC_MAIN_TASK = "codenarcMain"
     static final String CODE_NARC_TEST_TASK = "codenarcTest"
 
     void apply(Project project) {
-        project.plugins.apply(ReportingBasePlugin)
-
-        def javaPluginConvention = new JavaCodeQualityPluginConvention(project)
-        project.convention.plugins.javaCodeQuality = javaPluginConvention
+        DeprecationLogger.nagUserOfReplacedPlugin('code-quality', 'checkstyle or codenarc')
 
-        def groovyPluginConvention = new GroovyCodeQualityPluginConvention(project)
-        project.convention.plugins.groovyCodeQuality = groovyPluginConvention
-
-        configureCheckstyleDefaults(project, javaPluginConvention)
-        configureCodeNarcDefaults(project, groovyPluginConvention)
+        this.project = project
 
-        project.plugins.withType(JavaBasePlugin) {
-            configureForJavaPlugin(project, javaPluginConvention)
-        }
-        project.plugins.withType(GroovyBasePlugin) {
-            configureForGroovyPlugin(project, groovyPluginConvention)
-        }
+        project.plugins.apply(ReportingBasePlugin)
+        configureCheckstyle()
+        configureCodeNarc()
     }
 
-    private void configureCheckstyleDefaults(Project project, JavaCodeQualityPluginConvention pluginConvention) {
-        project.tasks.withType(Checkstyle) { Checkstyle checkstyle ->
-            checkstyle.conventionMapping.configFile = { pluginConvention.checkstyleConfigFile }
-            checkstyle.conventionMapping.map('properties') { pluginConvention.checkstyleProperties }
-        }
-    }
+    private void configureCheckstyle() {
+        def javaPluginConvention = new JavaCodeQualityPluginConvention(project)
+        project.convention.plugins.javaCodeQuality = javaPluginConvention
 
-    private void configureCodeNarcDefaults(Project project, GroovyCodeQualityPluginConvention pluginConvention) {
-        project.tasks.withType(CodeNarc) { CodeNarc codenarc ->
-            codenarc.conventionMapping.configFile = { pluginConvention.codeNarcConfigFile }
+        project.plugins.apply(CheckstylePlugin)
+        project.checkstyle.conventionMapping.with {
+            configFile = { javaPluginConvention.checkstyleConfigFile }
+            configProperties = { javaPluginConvention.checkstyleProperties }
+            reportsDir = { javaPluginConvention.checkstyleResultsDir }
         }
     }
 
-    private void configureCheckTask(Project project) {
-        def task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
-        task.description = "Executes all quality checks"
-        task.dependsOn project.tasks.withType(Checkstyle)
-        task.dependsOn project.tasks.withType(CodeNarc)
-    }
-
-    private void configureForJavaPlugin(Project project, JavaCodeQualityPluginConvention pluginConvention) {
-        configureCheckTask(project)
-
-        project.convention.getPlugin(JavaPluginConvention).sourceSets.all {SourceSet set ->
-            def checkstyle = project.tasks.add(set.getTaskName("checkstyle", null), Checkstyle)
-            checkstyle.description = "Runs Checkstyle against the $set.name Java source code."
-            checkstyle.conventionMapping.defaultSource = { set.allJava }
-            checkstyle.conventionMapping.configFile = { pluginConvention.checkstyleConfigFile }
-            checkstyle.conventionMapping.resultFile = { new File(pluginConvention.checkstyleResultsDir, "${set.name}.xml") }
-            checkstyle.conventionMapping.classpath = { set.compileClasspath }
-        }
-    }
+    private void configureCodeNarc() {
+        def groovyPluginConvention = new GroovyCodeQualityPluginConvention(project)
+        project.convention.plugins.groovyCodeQuality = groovyPluginConvention
 
-    private void configureForGroovyPlugin(Project project, GroovyCodeQualityPluginConvention pluginConvention) {
-        project.convention.getPlugin(JavaPluginConvention).sourceSets.all {SourceSet set ->
-            def groovySourceSet = set.convention.getPlugin(GroovySourceSet)
-            def codeNarc = project.tasks.add(set.getTaskName("codenarc", null), CodeNarc)
-            codeNarc.description = "Runs CodeNarc against the $set.name Groovy source code."
-            codeNarc.conventionMapping.defaultSource = { groovySourceSet.allGroovy }
-            codeNarc.conventionMapping.configFile = { pluginConvention.codeNarcConfigFile }
-            codeNarc.conventionMapping.reportFormat = { pluginConvention.codeNarcReportsFormat }
-            codeNarc.conventionMapping.reportFile = { new File(pluginConvention.codeNarcReportsDir, "${set.name}.${pluginConvention.codeNarcReportsFormat}") }
+        project.plugins.apply(CodeNarcPlugin)
+        project.codenarc.conventionMapping.with {
+            configFile = { groovyPluginConvention.codeNarcConfigFile }
+            reportFormat = { groovyPluginConvention.codeNarcReportsFormat }
+            reportsDir = { groovyPluginConvention.codeNarcReportsDir }
         }
     }
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
new file mode 100644
index 0000000..94a1606
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.plugins.quality.internal.FindBugsReportsImpl
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsDaemonManager
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsResult
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpec
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
+import org.gradle.api.reporting.Reporting
+import org.gradle.api.reporting.SingleFileReport
+import org.gradle.api.tasks.*
+
+/**
+ * Analyzes code with <a href="http://findbugs.sourceforge.net">FindBugs</a>.
+ */
+class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBugsReports> {
+    /**
+     * The classes to be analyzed.
+     */
+    @SkipWhenEmpty
+    @InputFiles
+    FileCollection classes
+
+    /**
+     * Compile class path for the classes to be analyzed.
+     * The classes on this class path are used during analysis
+     * but aren't analyzed themselves.
+     */
+    @InputFiles
+    FileCollection classpath
+
+    /**
+     * Class path holding the FindBugs library.
+     */
+    @InputFiles
+    FileCollection findbugsClasspath
+
+    /**
+     * Class path holding any additional FindBugs plugins.
+     */
+    @InputFiles
+    FileCollection pluginClasspath
+
+    /**
+     * Whether or not to allow the build to continue if there are warnings.
+     */
+    boolean ignoreFailures
+
+    @Nested
+    private final FindBugsReportsImpl reports = services.get(Instantiator).newInstance(FindBugsReportsImpl, this)
+
+    /**
+     * The reports to be generated by this task.
+     *
+     * @return The reports container
+     */
+    FindBugsReports getReports() {
+        reports
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     *
+     * The contained reports can be configured by name and closures. Example:
+     *
+     * <pre>
+     * findbugsTask {
+     *   reports {
+     *     xml {
+     *       destination "build/findbugs.xml"
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param closure The configuration
+     * @return The reports container
+     */
+    FindBugsReports reports(Closure closure) {
+        reports.configure(closure)
+    }
+
+    @TaskAction
+    void run() {
+        FindBugsSpecBuilder argumentBuilder = new FindBugsSpecBuilder(getClasses())
+                .withPluginsList(getPluginClasspath())
+                .withSources(getSource())
+                .withClasspath(getClasspath())
+                .withDebugging(logger.isDebugEnabled())
+                .configureReports(reports)
+
+        FindBugsSpec spec = argumentBuilder.build()
+        FindBugsDaemonManager manager = new FindBugsDaemonManager();
+        FindBugsResult findbugsResult = manager.runDaemon(getProject(), getFindbugsClasspath(), spec)
+        evaluateResult(findbugsResult);
+    }
+
+    void evaluateResult(FindBugsResult findbugsResult) {
+        if (findbugsResult.errorCount){
+            throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.")
+        }
+        if (findbugsResult.bugCount && !ignoreFailures) {
+            SingleFileReport reportSetup = reports.firstEnabled
+            if (reports.firstEnabled) {
+                throw new GradleException("FindBugs rule violations were found. See the report at ${reportSetup.destination}.")
+            } else {
+                throw new GradleException("FindBugs rule violations were found.")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsExtension.groovy
new file mode 100644
index 0000000..32574a7
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsExtension.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+/**
+ * Configuration options for the FindBugs plugin.
+ *
+ * @see FindBugsPlugin
+ */
+class FindBugsExtension extends CodeQualityExtension {
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsPlugin.groovy
new file mode 100644
index 0000000..67b7eab
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsPlugin.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.plugins.quality.internal.AbstractCodeQualityPlugin
+import org.gradle.api.reporting.Report
+import org.gradle.api.tasks.SourceSet
+
+/**
+ * A plugin for the <a href="http://findbugs.sourceforge.net">FindBugs</a> byte code analyzer.
+ *
+ * <p>
+ * Declares a <tt>findbugs</tt> configuration which needs to be configured with the FindBugs library to be used.
+ * Additional plugins can be added to the <tt>findbugsPlugins</tt> configuration.
+ *
+ * <p>
+ * For projects that have the Java (base) plugin applied, a {@link FindBugs} task is
+ * created for each source set.
+ *
+ * @see FindBugs
+ * @see FindBugsExtension
+ */
+class FindBugsPlugin extends AbstractCodeQualityPlugin<FindBugs> {
+    private FindBugsExtension extension
+
+    @Override
+    protected String getToolName() {
+        return "FindBugs"
+    }
+
+    @Override
+    protected Class<FindBugs> getTaskType() {
+        return FindBugs
+    }
+
+    @Override
+    protected void beforeApply() {
+        configureFindBugsConfigurations()
+    }
+
+    private configureFindBugsConfigurations() {
+        project.configurations.add('findbugsPlugins').with {
+            visible = false
+            transitive = true
+            description = 'The FindBugs plugins to be used for this project.'
+        }
+    }
+
+    @Override
+    protected CodeQualityExtension createExtension() {
+        extension = project.extensions.create("findbugs", FindBugsExtension)
+        extension.with {
+            toolVersion = "2.0.0"
+        }
+        return extension
+    }
+
+    @Override
+    protected void configureTaskDefaults(FindBugs task, String baseName) {
+        task.with {
+            pluginClasspath = project.configurations['findbugsPlugins']
+        }
+        task.conventionMapping.with {
+            findbugsClasspath = {
+                def config = project.configurations['findbugs']
+                if (config.dependencies.empty) {
+                    project.dependencies {
+                        findbugs("com.google.code.findbugs:findbugs:$extension.toolVersion")
+                    }
+                }
+                config
+            }
+            ignoreFailures = { extension.ignoreFailures }
+        }
+        task.reports.all { Report report ->
+            report.conventionMapping.with {
+                enabled = { report.name == "xml" }
+                destination = { new File(extension.reportsDir, "${baseName}.${report.name}") }
+            }
+        }
+    }
+
+    @Override
+    protected void configureForSourceSet(SourceSet sourceSet, FindBugs task) {
+        task.with {
+            description = "Run FindBugs analysis for ${sourceSet.name} classes"
+        }
+        task.source = sourceSet.allJava
+        task.conventionMapping.with {
+            classes = {
+                // the simple "classes = sourceSet.output" may lead to non-existing resources directory
+                // being passed to FindBugs Ant task, resulting in an error
+                project.fileTree(sourceSet.output.classesDir) {
+                    builtBy sourceSet.output
+                }
+            }
+            classpath = { sourceSet.compileClasspath }
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java
new file mode 100644
index 0000000..6c0a28c
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugsReports.java
@@ -0,0 +1,44 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.reporting.ReportContainer;
+import org.gradle.api.reporting.SingleFileReport;
+
+/**
+ * The reporting configuration for the the {@link FindBugs} task.
+ *
+ * Only one of the xml or html reports can be enabled when the task executes. If more than one is enabled, an {@link org.gradle.api.InvalidUserDataException}
+ * will be thrown.
+ */
+public interface FindBugsReports extends ReportContainer<SingleFileReport> {
+
+    /**
+     * The findbugs xml report
+     *
+     * @return The findbugs xml report
+     */
+    SingleFileReport getXml();
+
+    /**
+     * The findbugs html report
+     *
+     * @return The findbugs html report
+     */
+    SingleFileReport getHtml();
+    
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy
index 34beb60..ddc705e 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy
@@ -17,6 +17,7 @@ package org.gradle.api.plugins.quality
 
 import org.gradle.api.Project
 import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.reporting.ReportingExtension
 
 class GroovyCodeQualityPluginConvention {
     /**
@@ -54,6 +55,6 @@ class GroovyCodeQualityPluginConvention {
      * The directory to write CodeNarc reports into.
      */
     File getCodeNarcReportsDir() {
-        project.fileResolver.withBaseDir(project.reportsDir).resolve(codeNarcReportsDirName)
+        project.extensions.getByType(ReportingExtension).file(codeNarcReportsDirName)
     }
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy
new file mode 100644
index 0000000..3b8d167
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.project.IsolatedAntBuilder
+import org.gradle.api.plugins.quality.internal.JDependReportsImpl
+import org.gradle.api.reporting.Reporting
+import org.gradle.api.tasks.*
+
+/**
+ * Analyzes code with <a href="http://clarkware.com/software/JDepend.html">JDepend</a>.
+ */
+class JDepend extends DefaultTask implements Reporting<JDependReports> {
+    /**
+     * The class path containing the JDepend library to be used.
+     */
+    @InputFiles
+    FileCollection jdependClasspath
+
+    /**
+     * The directory containing the classes to be analyzed.
+     */
+    @InputDirectory
+    File classesDir
+
+    // workaround for GRADLE-2020
+    /**
+     * Returns the directory containing the classes to be analyzed.
+     */
+    @SkipWhenEmpty
+    File getClassesDir() {
+        return classesDir
+    }
+
+    @Nested
+    private final JDependReportsImpl reports = services.get(Instantiator).newInstance(JDependReportsImpl, this)
+
+    /**
+     * The reports to be generated by this task.
+     *
+     * @return The reports container.
+     */
+    JDependReports getReports() {
+        reports
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     *
+     * The contained reports can be configured by name and closures. Example:
+     *
+     * <pre>
+     * jdependTask {
+     *   reports {
+     *     xml {
+     *       destination "build/jdepend.xml"
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param closure The configuration
+     * @return The reports container
+     */
+    JDependReports reports(Closure closure) {
+        reports.configure(closure)
+    }
+
+    @TaskAction
+    void run() {
+        Map<String, ?> reportArguments = [:]
+        if (reports.enabled.empty) {
+            throw new InvalidUserDataException("JDepend tasks must have one report enabled, however neither the xml or text report are enabled for task '$path'. You need to enable one of them")
+        } else if (reports.enabled.size() == 1) {
+            reportArguments.outputFile = reports.firstEnabled.destination
+            reportArguments.format = reports.firstEnabled.name
+        } else {
+            throw new InvalidUserDataException("JDepend tasks can only have one report enabled, however both the xml and text report are enabled for task '$path'. You need to disable one of them.")
+        }
+
+        def antBuilder = services.get(IsolatedAntBuilder)
+        antBuilder.withClasspath(getJdependClasspath()).execute {
+            ant.taskdef(name: 'jdependreport', classname: 'org.apache.tools.ant.taskdefs.optional.jdepend.JDependTask')
+            ant.jdependreport(*:reportArguments, haltonerror: true) {
+                classespath {
+                    pathElement(location: getClassesDir())
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependExtension.groovy
new file mode 100644
index 0000000..f4f66d4
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependExtension.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+/**
+ * Configuration options for the JDepend plugin.
+ * 
+ * @see JDependPlugin
+ */
+class JDependExtension extends CodeQualityExtension {
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy
new file mode 100644
index 0000000..e2863f1
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependPlugin.groovy
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Plugin
+import org.gradle.api.plugins.quality.internal.AbstractCodeQualityPlugin
+import org.gradle.api.reporting.Report
+import org.gradle.api.tasks.SourceSet
+
+/**
+ * <p>
+ * A {@link Plugin} that generates design quality metrics by
+ * scanning your source packages.  This is done using the JDepend
+ * tool.
+ * </p>
+ * <p>
+ * This plugin will automatically generate a task for each Java source set.
+ * </p>
+ * See <a href="http://www.clarkware.com/software/JDepend.html">JDepend</a> for more information.
+ *
+ * @see JDependExtension
+ * @see JDepend
+ */
+class JDependPlugin extends AbstractCodeQualityPlugin<JDepend> {
+    private JDependExtension extension
+
+    @Override
+    protected String getToolName() {
+        return "JDepend"
+    }
+
+    @Override
+    protected Class<JDepend> getTaskType() {
+        return JDepend
+    }
+
+    @Override
+    protected CodeQualityExtension createExtension() {
+        extension = project.extensions.create("jdepend", JDependExtension)
+        extension.with {
+            toolVersion = "2.9.1"
+        }
+        return extension
+    }
+
+    @Override
+    protected void configureTaskDefaults(JDepend task, String baseName) {
+        task.conventionMapping.with {
+            jdependClasspath = {
+                def config = project.configurations['jdepend']
+                if (config.dependencies.empty) {
+                    project.dependencies {
+                        jdepend "jdepend:jdepend:$extension.toolVersion"
+                        jdepend("org.apache.ant:ant-jdepend:1.8.2")
+                    }
+                }
+                config
+            }
+        }
+        task.reports.all { Report report ->
+            report.conventionMapping.with {
+                enabled = { report.name == "xml" }
+                destination = {
+                    def fileSuffix = report.name == 'text' ? 'txt' : report.name
+                    new File(extension.reportsDir, "${baseName}.${fileSuffix}")
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void configureForSourceSet(SourceSet sourceSet, JDepend task) {
+        task.with {
+            dependsOn(sourceSet.output)
+            description = "Run JDepend analysis for ${sourceSet.name} classes"
+        }
+        task.conventionMapping.with {
+            classesDir = { sourceSet.output.classesDir }
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java
new file mode 100644
index 0000000..e6d038d
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDependReports.java
@@ -0,0 +1,44 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.reporting.ReportContainer;
+import org.gradle.api.reporting.SingleFileReport;
+
+/**
+ * The reporting configuration for the the {@link JDepend} task.
+ *
+ * Exactly one of the xml or html reports can be enabled when the task executes. If more than one or none is enabled, an {@link org.gradle.api.InvalidUserDataException}
+ * will be thrown.
+ */
+public interface JDependReports extends ReportContainer<SingleFileReport> {
+
+    /**
+     * The jdepend xml report
+     *
+     * @return The jdepend xml report
+     */
+    SingleFileReport getXml();
+
+    /**
+     * The jdepend text report
+     *
+     * @return The jdepend text report
+     */
+    SingleFileReport getText();
+     
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
new file mode 100644
index 0000000..88682cc
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.project.IsolatedAntBuilder
+import org.gradle.api.plugins.quality.internal.PmdReportsImpl
+import org.gradle.api.reporting.Reporting
+import org.gradle.api.tasks.*
+
+/**
+ * Runs a set of static code analysis rules on Java source code files and
+ * generates a report of problems found.
+ *
+ * @see PmdPlugin
+ */
+class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports> {
+    /**
+     * The class path containing the PMD library to be used.
+     */
+    @InputFiles
+    FileCollection pmdClasspath
+
+    /**
+     * The built-in rule sets to be used. See the <a href="http://pmd.sourceforge.net/rules/index.html">official list</a> of built-in rule sets.
+     *
+     * Example: ruleSets = ["basic", "braces"]
+     */
+    @Input
+    List<String> ruleSets
+
+    /**
+     * The custom rule set files to be used. See the <a href="http://pmd.sourceforge.net/howtomakearuleset.html">official documentation</a> for
+     * how to author a rule set file.
+     *
+     * Example: ruleSetFiles = files("config/pmd/myRuleSets.xml")
+     */
+    @InputFiles
+    FileCollection ruleSetFiles
+
+    @Nested
+    private final PmdReportsImpl reports = services.get(Instantiator).newInstance(PmdReportsImpl, this)
+
+    /**
+     * Whether or not to allow the build to continue if there are warnings.
+     *
+     * Example: ignoreFailures = true
+     */
+    boolean ignoreFailures
+
+    @TaskAction
+    void run() {
+        def antBuilder = services.get(IsolatedAntBuilder)
+        antBuilder.withClasspath(getPmdClasspath()).execute {
+            ant.taskdef(name: 'pmd', classname: 'net.sourceforge.pmd.ant.PMDTask')
+            ant.pmd(failOnRuleViolation: !getIgnoreFailures()) {
+                getSource().addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+                getRuleSets().each {
+                    ruleset(it)
+                }
+                getRuleSetFiles().each {
+                    ruleset(it)
+                }
+
+                if (reports.html.enabled) {
+                    assert reports.html.destination.parentFile.exists()
+                    formatter(type: 'betterhtml', toFile: reports.html.destination)
+                }
+                if (reports.xml.enabled) {
+                    formatter(type: 'xml', toFile: reports.xml.destination)
+                }
+            }
+        }
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     */
+    PmdReports reports(Closure closure) {
+        reports.configure(closure)
+    }
+
+    /**
+     * Returns the reports to be generated by this task.
+     */
+    PmdReports getReports() {
+        reports
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
new file mode 100644
index 0000000..d13dc6b
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+
+/**
+ * Configuration options for the PMD plugin.
+ *
+ * @see PmdPlugin
+ */
+class PmdExtension extends CodeQualityExtension {
+    private final Project project
+
+    PmdExtension(Project project) {
+        this.project = project
+    }
+
+    /**
+     *
+     * The built-in rule sets to be used. See the <a href="http://pmd.sourceforge.net/rules/index.html">official list</a> of built-in rule sets.
+     *
+     * Example: ruleSets = ["basic", "braces"]
+     */
+    List<String> ruleSets
+    
+    /**
+     * Convenience method for adding rule sets.
+     *
+     * Example: ruleSets "basic", "braces"
+     *
+     * @param ruleSets the rule sets to be added
+     */
+    void ruleSets(String... ruleSets) {
+        this.ruleSets.addAll(ruleSets)
+    }
+
+    /**
+     * The custom rule set files to be used. See the <a href="http://pmd.sourceforge.net/howtomakearuleset.html">official documentation</a> for
+     * how to author a rule set file.
+     *
+     * Example: ruleSetFiles = files("config/pmd/myRuleSet.xml")
+     *
+     */
+    FileCollection ruleSetFiles
+
+    /**
+     * Convenience method for adding rule set files.
+     *
+     * Example: ruleSetFiles "config/pmd/myRuleSet.xml"
+     *
+     * @param ruleSetFiles the rule set files to be added
+     */
+    void ruleSetFiles(Object... ruleSetFiles) {
+        this.ruleSetFiles.add(project.files(ruleSetFiles))
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy
new file mode 100644
index 0000000..f084a9c
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.plugins.quality.internal.AbstractCodeQualityPlugin
+import org.gradle.api.tasks.SourceSet
+
+/**
+ *  A plugin for the <a href="http://pmd.sourceforge.net/">PMD source code analyzer.
+ * <p>
+ * Declares a <tt>findbugs</tt> configuration which needs to be configured with the FindBugs library to be used.
+ * Additional plugins can be added to the <tt>findbugsPlugins</tt> configuration.
+ * <p>
+ * For each source set that is to be analyzed, a {@link Pmd} task is created and configured to analyze all Java code.
+ * <p
+ * All PMD tasks (including user-defined ones) are added to the <tt>check</tt> lifecycle task.
+ *
+ * @see PmdExtension
+ * @see Pmd
+ */
+class PmdPlugin extends AbstractCodeQualityPlugin<Pmd> {
+    private PmdExtension extension
+
+    @Override
+    protected String getToolName() {
+        return "PMD"
+    }
+
+    @Override
+    protected Class<Pmd> getTaskType() {
+        return Pmd
+    }
+
+    @Override
+    protected CodeQualityExtension createExtension() {
+        extension = project.extensions.create("pmd", PmdExtension, project)
+        extension.with {
+            toolVersion = "4.3"
+            ruleSets = ["basic"]
+            ruleSetFiles = project.files()
+        }
+        return extension
+    }
+
+    @Override
+    protected void configureTaskDefaults(Pmd task, String baseName) {
+        task.conventionMapping.with {
+            pmdClasspath = {
+                def config = project.configurations['pmd']
+                if (config.dependencies.empty) {
+                    project.dependencies {
+                        pmd "pmd:pmd:$extension.toolVersion"
+                    }
+                }
+                config
+            }
+            ruleSets = { extension.ruleSets }
+            ruleSetFiles = { extension.ruleSetFiles }
+            ignoreFailures = { extension.ignoreFailures }
+
+            task.reports.all { report ->
+                report.conventionMapping.with {
+                    enabled = { true }
+                    destination = { new File(extension.reportsDir, "${baseName}.${report.name}") }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void configureForSourceSet(SourceSet sourceSet, Pmd task) {
+        task.with {
+            description = "Run PMD analysis for ${sourceSet.name} classes"
+        }
+        task.setSource(sourceSet.allJava)
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java
new file mode 100644
index 0000000..3307f0b
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdReports.java
@@ -0,0 +1,40 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.reporting.ReportContainer;
+import org.gradle.api.reporting.SingleFileReport;
+
+/**
+ * The reporting configuration for the the {@link Pmd} task.
+ */
+public interface PmdReports extends ReportContainer<SingleFileReport> {
+
+    /**
+     * The pmd (single file) html report
+     *
+     * @return The pmd (single file) html report
+     */
+    SingleFileReport getHtml();
+
+    /**
+     * The pmd (single file) xml report
+     *
+     * @return The pmd (single file) xml report
+     */
+    SingleFileReport getXml();
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy
new file mode 100644
index 0000000..23aef56
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AbstractCodeQualityPlugin.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal
+
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.plugins.quality.CodeQualityExtension
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.api.tasks.SourceSet
+
+abstract class AbstractCodeQualityPlugin<T> implements Plugin<ProjectInternal> {
+    protected ProjectInternal project
+    protected CodeQualityExtension extension
+
+    final void apply(ProjectInternal project) {
+        this.project = project
+
+        beforeApply()
+        project.plugins.apply(ReportingBasePlugin)
+        createConfigurations()
+        extension = createExtension()
+        configureExtensionRule()
+        configureTaskRule()
+        configureSourceSetRule()
+        configureCheckTask()
+    }
+
+    protected abstract String getToolName()
+
+    protected abstract Class<T> getTaskType()
+
+    protected String getTaskBaseName() {
+        return toolName.toLowerCase()
+    }
+
+    protected String getConfigurationName() {
+        return toolName.toLowerCase()
+    }
+
+    protected String getReportName() {
+        return toolName.toLowerCase()
+    }
+
+    protected Class<?> getBasePlugin() {
+        return JavaBasePlugin
+    }
+
+    protected void beforeApply() {
+    }
+
+    protected void createConfigurations() {
+        project.configurations.add(configurationName).with {
+            visible = false
+            transitive = true
+            description = "The ${toolName} libraries to be used for this project."
+            // Don't need these things, they're provided by the runtime
+            exclude group: 'ant', module: 'ant'
+            exclude group: 'org.apache.ant', module: 'ant'
+            exclude group: 'org.apache.ant', module: 'ant-launcher'
+            exclude group: 'org.codehaus.groovy', module: 'groovy'
+            exclude group: 'org.codehaus.groovy', module: 'groovy-all'
+            exclude group: 'org.slf4j', module: 'slf4j-api'
+            exclude group: 'org.slf4j', module: 'jcl-over-slf4j'
+            exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
+            exclude group: 'commons-logging', module: 'commons-logging'
+            exclude group: 'log4j', module: 'log4j'
+        }
+    }
+
+    protected abstract CodeQualityExtension createExtension()
+
+    private void configureExtensionRule() {
+        extension.conventionMapping.with {
+            sourceSets = { [] }
+            reportsDir = { project.extensions.getByType(ReportingExtension).file(reportName) }
+        }
+
+        project.plugins.withType(basePlugin) {
+            extension.conventionMapping.sourceSets = { project.sourceSets }
+        }
+    }
+
+    private void configureTaskRule() {
+        project.tasks.withType(taskType) { T task ->
+            def prunedName = (task.name - taskBaseName ?: task.name)
+            prunedName = prunedName[0].toLowerCase() + prunedName.substring(1)
+            configureTaskDefaults(task, prunedName)
+        }
+    }
+
+    protected void configureTaskDefaults(T task, String baseName) {
+    }
+
+    private void configureSourceSetRule() {
+        project.plugins.withType(basePlugin) {
+            project.sourceSets.all { SourceSet sourceSet ->
+                T task = project.tasks.add(sourceSet.getTaskName(taskBaseName, null), taskType)
+                configureForSourceSet(sourceSet, task)
+            }
+        }
+    }
+
+    protected void configureForSourceSet(SourceSet sourceSet, T task) {
+    }
+
+    private void configureCheckTask() {
+        project.plugins.withType(basePlugin) {
+            project.tasks['check'].dependsOn { extension.sourceSets.collect { it.getTaskName(taskBaseName, null) }}
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AntCheckstyle.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AntCheckstyle.groovy
deleted file mode 100644
index 3af0d29..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AntCheckstyle.groovy
+++ /dev/null
@@ -1,42 +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.plugins.quality.internal
-
-import org.gradle.api.AntBuilder
-import org.gradle.api.GradleException
-import org.gradle.api.file.FileCollection
-import org.gradle.api.tasks.AntBuilderAware
-
-class AntCheckstyle {
-    def checkstyle(AntBuilder ant, FileCollection source, File configFile, File resultFile, AntBuilderAware classpath, Map<String, ?> properties, boolean ignoreFailures) {
-        String propertyName = "org.gradle.checkstyle.violations"
-
-        ant.project.addTaskDefinition('checkstyle', getClass().classLoader.loadClass('com.puppycrawl.tools.checkstyle.CheckStyleTask'))
-        ant.checkstyle(config: configFile, failOnViolation: false, failureProperty: propertyName) {
-            source.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
-            classpath.addToAntBuilder(ant, 'classpath')
-            formatter(type: 'plain', useFile: false)
-            formatter(type: 'xml', toFile: resultFile)
-            properties.each {key, value ->
-                property(key: key, value: value.toString())
-            }
-        }
-
-        if (!ignoreFailures && ant.properties[propertyName]) {
-            throw new GradleException("Checkstyle check violations were found in $source. See the report at $resultFile.")
-        }
-    }
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AntCodeNarc.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AntCodeNarc.groovy
deleted file mode 100644
index c9e1940..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/AntCodeNarc.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 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.plugins.quality.internal
-
-import org.apache.tools.ant.BuildException
-import org.codenarc.ant.CodeNarcTask
-import org.gradle.api.AntBuilder
-import org.gradle.api.GradleException
-import org.gradle.api.file.FileCollection
-
-class AntCodeNarc {
-    def execute(AntBuilder ant, FileCollection source, File configFile, String reportFormat, File reportFile, boolean ignoreFailures) {
-        ant.project.addTaskDefinition('codenarc', CodeNarcTask)
-        try {
-            ant.codenarc(ruleSetFiles: "file:$configFile", maxPriority1Violations: 0, maxPriority2Violations: 0, maxPriority3Violations: 0) {
-                report(type: ConsoleReportWriter.name)
-                report(type: reportFormat) {
-                    option(name: 'outputFile', value: reportFile)
-                }
-                source.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
-            }
-        } catch (BuildException e) {
-            if (e.message.matches('Exceeded maximum number of priority \\d* violations.*')) {
-                if (ignoreFailures) {
-                    return
-                }
-                throw new GradleException("CodeNarc check violations were found in $source. See the report at $reportFile.", e)
-            }
-            throw e
-        }
-    }
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/CheckstyleReportsImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/CheckstyleReportsImpl.java
new file mode 100644
index 0000000..4194b6a
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/CheckstyleReportsImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.plugins.quality.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.plugins.quality.CheckstyleReports;
+import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+
+public class CheckstyleReportsImpl extends TaskReportContainer<SingleFileReport> implements CheckstyleReports {
+    public CheckstyleReportsImpl(Task task) {
+        super(SingleFileReport.class, task);
+        
+        add(TaskGeneratedSingleFileReport.class, "xml", task);
+    }
+
+    public SingleFileReport getXml() {
+        return getByName("xml");
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/CodeNarcReportsImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/CodeNarcReportsImpl.java
new file mode 100644
index 0000000..b1e543b
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/CodeNarcReportsImpl.java
@@ -0,0 +1,46 @@
+/*
+ * 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.plugins.quality.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.plugins.quality.CodeNarcReports;
+import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+
+public class CodeNarcReportsImpl extends TaskReportContainer<SingleFileReport> implements CodeNarcReports {
+
+    public CodeNarcReportsImpl(Task task) {
+        super(SingleFileReport.class, task);
+
+        add(TaskGeneratedSingleFileReport.class, "xml", task);
+        add(TaskGeneratedSingleFileReport.class, "html", task);
+        add(TaskGeneratedSingleFileReport.class, "text", task);
+    }
+
+    public SingleFileReport getXml() {
+        return getByName("xml");
+    }
+
+    public SingleFileReport getHtml() {
+        return getByName("html");
+    }
+
+    public SingleFileReport getText() {
+        return getByName("text");
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/ConsoleReportWriter.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/ConsoleReportWriter.java
deleted file mode 100644
index bdec2dc..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/ConsoleReportWriter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 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.plugins.quality.internal;
-
-import org.codenarc.AnalysisContext;
-import org.codenarc.report.ReportWriter;
-import org.codenarc.results.Results;
-import org.codenarc.rule.Violation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Formatter;
-
-public class ConsoleReportWriter implements ReportWriter {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleReportWriter.class);
-
-    public void writeReport(AnalysisContext analysisContext, Results results) {
-        report(results);
-    }
-
-    private void report(Results results) {
-        if (!results.getChildren().isEmpty()) {
-            for (Object child : results.getChildren()) {
-                report((Results) child);
-            }
-            return;
-        }
-
-        for (int priority = 1; priority <= 3; priority++) {
-            for (Object o : results.getViolationsWithPriority(priority)) {
-                Violation v = (Violation) o;
-                Formatter formatter = new Formatter();
-                formatter.format("%s:%s%n", results.getPath(), v.getLineNumber());
-                formatter.format("%s: %s", v.getRule().getName(), v.getMessage());
-                LOGGER.error(formatter.toString());
-            }
-        }
-    }
-
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java
new file mode 100644
index 0000000..200d8a0
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/FindBugsReportsImpl.java
@@ -0,0 +1,41 @@
+/*
+ * 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.plugins.quality.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.plugins.quality.FindBugsReports;
+import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+
+public class FindBugsReportsImpl extends TaskReportContainer<SingleFileReport> implements FindBugsReports {
+
+    public FindBugsReportsImpl(Task task) {
+        super(SingleFileReport.class, task);
+
+        add(TaskGeneratedSingleFileReport.class, "xml", task);
+        add(TaskGeneratedSingleFileReport.class, "html", task);
+    }
+
+    public SingleFileReport getXml() {
+        return getByName("xml");
+    }
+
+    public SingleFileReport getHtml() {
+        return getByName("html");
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/JDependReportsImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/JDependReportsImpl.java
new file mode 100644
index 0000000..231881f
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/JDependReportsImpl.java
@@ -0,0 +1,42 @@
+/*
+ * 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.plugins.quality.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.plugins.quality.JDependReports;
+import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+
+public class JDependReportsImpl extends TaskReportContainer<SingleFileReport> implements JDependReports {
+
+    public JDependReportsImpl(Task task) {
+        super(SingleFileReport.class, task);
+        
+        add(TaskGeneratedSingleFileReport.class, "xml", task);
+        add(TaskGeneratedSingleFileReport.class, "text", task);
+    }
+
+    public SingleFileReport getXml() {
+        return getByName("xml");
+    }
+
+    public SingleFileReport getText() {
+        return getByName("text");
+    }
+
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/PmdReportsImpl.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/PmdReportsImpl.java
new file mode 100644
index 0000000..2c94542
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/PmdReportsImpl.java
@@ -0,0 +1,41 @@
+/*
+ * 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.plugins.quality.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.plugins.quality.PmdReports;
+import org.gradle.api.reporting.SingleFileReport;
+import org.gradle.api.reporting.internal.TaskGeneratedSingleFileReport;
+import org.gradle.api.reporting.internal.TaskReportContainer;
+
+public class PmdReportsImpl extends TaskReportContainer<SingleFileReport> implements PmdReports {
+
+    public PmdReportsImpl(Task task) {
+        super(SingleFileReport.class, task);
+        
+        add(TaskGeneratedSingleFileReport.class, "html", task);
+        add(TaskGeneratedSingleFileReport.class, "xml", task);
+    }
+
+    public SingleFileReport getHtml() {
+        return getByName("html");
+    }
+
+    public SingleFileReport getXml() {
+        return getByName("xml");
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemon.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemon.java
new file mode 100644
index 0000000..cef9116
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemon.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import org.gradle.internal.Stoppable;
+
+public interface FindBugsDaemon extends Stoppable {
+    FindBugsResult execute();
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClient.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClient.java
new file mode 100644
index 0000000..70e6db5
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClient.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import org.gradle.internal.UncheckedException;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+
+public class FindBugsDaemonClient implements FindBugsDaemonClientProtocol {
+
+    private final BlockingQueue<FindBugsResult> findbugsResults = new SynchronousQueue<FindBugsResult>();
+
+    public void executed(FindBugsResult result) {
+        try {
+            findbugsResults.put(result);
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public FindBugsResult getResult() {
+        try {
+            return findbugsResults.take();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClientProtocol.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClientProtocol.java
new file mode 100644
index 0000000..677d6de
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClientProtocol.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+public interface FindBugsDaemonClientProtocol {
+    void executed(FindBugsResult result);
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonManager.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonManager.groovy
new file mode 100644
index 0000000..9d3dbf3
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonManager.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.process.internal.JavaExecHandleBuilder
+import org.gradle.process.internal.WorkerProcess
+import org.gradle.process.internal.WorkerProcessBuilder
+
+class FindBugsDaemonManager {
+    private final Logger logger = Logging.getLogger(getClass())
+
+    public FindBugsResult runDaemon(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
+        logger.info("Starting Gradle findbugs daemon.");
+        if (logger.isDebugEnabled()) {
+            logger.debug(findBugsClasspath.asPath);
+        }
+
+        WorkerProcess process = createWorkerProcess(project, findBugsClasspath, spec);
+        process.start();
+
+        FindBugsDaemonClient clientCallBack = new FindBugsDaemonClient()
+        process.connection.addIncoming(FindBugsDaemonClientProtocol.class, clientCallBack);
+        FindBugsResult result = clientCallBack.getResult();
+
+        process.waitForStop();
+        logger.info("Gradle findbugs daemon stopped.");
+        return result;
+    }
+
+    private WorkerProcess createWorkerProcess(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
+        WorkerProcessBuilder builder = project.getServices().getFactory(WorkerProcessBuilder.class).create();
+        builder.setLogLevel(project.getGradle().getStartParameter().getLogLevel());
+        builder.applicationClasspath(findBugsClasspath);   //findbugs classpath
+        builder.sharedPackages(Arrays.asList("edu.umd.cs.findbugs"));
+        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
+        javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
+
+        WorkerProcess process = builder.worker(new FindBugsDaemonServer(spec)).build()
+        return process
+    }
+}
\ No newline at end of file
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonServer.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonServer.java
new file mode 100644
index 0000000..6671246
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonServer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.process.internal.WorkerProcessContext;
+
+import java.io.Serializable;
+
+public class FindBugsDaemonServer implements Action<WorkerProcessContext>, Serializable {
+    private static final Logger LOGGER = Logging.getLogger(FindBugsDaemonServer.class);
+    private FindBugsSpec spec;
+
+    public FindBugsDaemonServer(FindBugsSpec spec) {
+        this.spec = spec;
+    }
+
+    public void execute(WorkerProcessContext context) {
+        final FindBugsResult result = execute();
+        final FindBugsDaemonClientProtocol clientProtocol = context.getServerConnection().addOutgoing(FindBugsDaemonClientProtocol.class);
+        clientProtocol.executed(result);
+    }
+
+    public FindBugsResult execute() {
+        LOGGER.info("Executing findbugs daemon.");
+        try {
+            FindBugsExecuter findBugsExecuter = new FindBugsExecuter(this);
+            return findBugsExecuter.runFindbugs(spec);
+        } catch (Exception e) {
+            LOGGER.warn("Exception occured while running FindBugs.", e);
+            return new FindBugsResult(0, 0, 1); //mark result with error count 1
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java
new file mode 100644
index 0000000..fe45fc8
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import edu.umd.cs.findbugs.FindBugs;
+import edu.umd.cs.findbugs.FindBugs2;
+import edu.umd.cs.findbugs.IFindBugsEngine;
+import edu.umd.cs.findbugs.TextUICommandLine;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.util.List;
+
+public class FindBugsExecuter implements Serializable {
+    private final FindBugsDaemonServer findBugsDaemonServer;
+
+    public FindBugsExecuter(FindBugsDaemonServer findBugsDaemonServer) {
+        this.findBugsDaemonServer = findBugsDaemonServer;
+    }
+
+    FindBugsResult runFindbugs(FindBugsSpec spec) throws IOException, InterruptedException {
+        final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        final PrintStream origOut = System.out;
+        final PrintStream origErr = System.err;
+        try {
+//            FindBugsDaemonServer.LOGGER.debug("Running findbugs specification {}", spec);
+            final List<String> args = spec.getArguments();
+            String[] strArray = new String[args.size()];
+            args.toArray(strArray);
+            // TODO RG: replace ByteArrayOutputStream by OutputStream that handles logging directly.
+            // TODO RG: use seperate streams for out and err.
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            System.setOut(new PrintStream(baos));
+            System.setErr(new PrintStream(baos));
+            Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader());
+
+            FindBugs2 findBugs2 = new FindBugs2();
+            TextUICommandLine commandLine = new TextUICommandLine();
+            FindBugs.processCommandLine(commandLine, strArray, findBugs2);
+            findBugs2.execute();
+
+//            FindBugsDaemonServer.LOGGER.debug(baos.toString());
+//            FindBugsDaemonServer.LOGGER.info("Successfully executed in findbugs daemon.");
+            return createFindbugsResult(findBugs2);
+        } finally {
+            System.setOut(origOut);
+            System.setErr(origErr);
+            Thread.currentThread().setContextClassLoader(contextClassLoader);
+        }
+    }
+
+    FindBugsResult createFindbugsResult(IFindBugsEngine findBugs) {
+            int bugCount = findBugs.getBugCount();
+            int missingClassCount = findBugs.getMissingClassCount();
+            int errorCount = findBugs.getErrorCount();
+            return new FindBugsResult(bugCount, missingClassCount, errorCount);
+        }
+}
\ No newline at end of file
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java
new file mode 100644
index 0000000..4ec2bb5
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import java.io.Serializable;
+
+public class FindBugsResult implements Serializable {
+
+    private final int bugCount;
+    private final int missingClassCount;
+    private final int errorCount;
+
+    public FindBugsResult(int bugCount, int missingClassCount, int errorCount) {
+        this.bugCount = bugCount;
+        this.missingClassCount = missingClassCount;
+        this.errorCount = errorCount;
+    }
+
+    public int getBugCount() {
+        return bugCount;
+    }
+
+    public int getMissingClassCount() {
+        return missingClassCount;
+    }
+
+    public int getErrorCount() {
+        return errorCount;
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java
new file mode 100644
index 0000000..d5a331d
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class FindBugsSpec implements Serializable {
+    private boolean debugEnabled;
+
+    private List<String> arguments;
+
+    public FindBugsSpec(List<String> arguments, boolean debugEnabled) {
+        this.debugEnabled = debugEnabled;
+        this.arguments = arguments;
+    }
+
+    public List<String> getArguments() {
+        return arguments;
+    }
+
+    public boolean isDebugEnabled() {
+        return debugEnabled;
+    }
+
+    public String toString() {
+        StringBuffer buffer = new StringBuffer("[FindBugsSpec: \n");
+        buffer.append("  debugEnabled: ").append(debugEnabled).append("\n");
+        if (arguments == null) {
+            buffer.append("  args: null \n");
+        } else {
+            buffer.append("  args: \n    [\n");
+            for (String arg : arguments) {
+                buffer.append("    ").append(arg).append(", \n");
+            }
+            buffer.append("    ]");
+        }
+        return buffer.toString();
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java
new file mode 100644
index 0000000..0e26e3e
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.plugins.quality.FindBugsReports;
+import org.gradle.api.plugins.quality.internal.FindBugsReportsImpl;
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class FindBugsSpecBuilder {
+    private FileCollection pluginsList;
+    private FileCollection sources;
+    private FileCollection classpath;
+
+    private ArrayList<String> args;
+    private FileCollection classes;
+    private FindBugsReports reports;
+
+    private boolean debugEnabled;
+
+    public FindBugsSpecBuilder(FileCollection classes) {
+        if(classes == null || classes.isEmpty()){
+            throw new InvalidUserDataException("Classes must be configured to be analyzed by the Findbugs.");
+        }
+        this.classes = classes;
+    }
+
+    public FindBugsSpecBuilder withPluginsList(FileCollection pluginsClasspath) {
+        this.pluginsList = pluginsClasspath;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withSources(FileCollection sources) {
+        this.sources = sources;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+        return this;
+    }
+
+    public FindBugsSpecBuilder configureReports(FindBugsReports reports){
+        this.reports = reports;
+        return this;
+    }
+
+    public FindBugsSpecBuilder withDebugging(boolean debugEnabled){
+        this.debugEnabled = debugEnabled;
+        return this;
+    }
+
+    public FindBugsSpec build() {
+        args = new ArrayList<String>();
+        args.add("-pluginList");
+        args.add(pluginsList==null ? "" : pluginsList.getAsPath());
+        args.add("-sortByClass");
+        args.add("-timestampNow");
+        args.add("-progress");
+
+        if (reports != null && !reports.getEnabled().isEmpty()) {
+            if (reports.getEnabled().size() == 1) {
+                FindBugsReportsImpl reportsImpl = (FindBugsReportsImpl) reports;
+                args.add("-" + reportsImpl.getFirstEnabled().getName());
+                args.add("-outputFile");
+                args.add(reportsImpl.getFirstEnabled().getDestination().getAbsolutePath());
+            } else {
+                throw new InvalidUserDataException("Findbugs tasks can only have one report enabled, however both the xml and html report are enabled. You need to disable one of them.");
+            }
+        }
+
+        if (has(sources)) {
+            args.add("-sourcepath");
+            args.add(sources.getAsPath());
+        }
+
+        if (has(classpath)) {
+            args.add("-auxclasspath");
+
+            // Filter unexisting files as findbugs can't handle them.
+            args.add(classpath.filter(new Spec<File>() {
+                public boolean isSatisfiedBy(File element) {
+                    return element.exists();
+                }
+            }).getAsPath());
+        }
+        for (File classFile : classes.getFiles()) {
+            args.add(classFile.getAbsolutePath());
+        }
+        FindBugsSpec spec = new FindBugsSpec(args, debugEnabled);
+        return spec;
+    }
+
+    private boolean has(FileCollection fileCollection) {
+        return fileCollection != null && !fileCollection.isEmpty();
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/package-info.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/package-info.java
index fa4278b..04b2962 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/package-info.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * A {@link org.gradle.api.Plugin} which measures and enforces code quality for Java and Groovy projects.
+ * Plugins which measure and enforce code quality.
  */
 package org.gradle.api.plugins.quality;
diff --git a/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/checkstyle.properties b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/checkstyle.properties
new file mode 100755
index 0000000..e8de0c6
--- /dev/null
+++ b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/checkstyle.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.quality.CheckstylePlugin
diff --git a/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/code-quality.properties b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/code-quality.properties
old mode 100644
new mode 100755
diff --git a/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/codenarc.properties b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/codenarc.properties
new file mode 100755
index 0000000..fd73c55
--- /dev/null
+++ b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/codenarc.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.quality.CodeNarcPlugin
diff --git a/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/findbugs.properties b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/findbugs.properties
new file mode 100644
index 0000000..402cd0c
--- /dev/null
+++ b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/findbugs.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.quality.FindBugsPlugin
diff --git a/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/jdepend.properties b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/jdepend.properties
new file mode 100644
index 0000000..7eeedf4
--- /dev/null
+++ b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/jdepend.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.quality.JDependPlugin
\ No newline at end of file
diff --git a/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/pmd.properties b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/pmd.properties
new file mode 100644
index 0000000..2463992
--- /dev/null
+++ b/subprojects/code-quality/src/main/resources/META-INF/gradle-plugins/pmd.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.quality.PmdPlugin
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
new file mode 100644
index 0000000..3e19918
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CheckstylePluginTest.groovy
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.tasks.SourceSet
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import static org.gradle.util.Matchers.dependsOn
+import static org.hamcrest.Matchers.*
+import static spock.util.matcher.HamcrestSupport.that
+import org.gradle.api.plugins.JavaBasePlugin
+
+class CheckstylePluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(CheckstylePlugin)
+    }
+
+    def "applies reporting-base plugin"() {
+        expect:
+        project.plugins.hasPlugin(ReportingBasePlugin)
+    }
+    
+    def "configures checkstyle configuration"() {
+        def config = project.configurations.findByName("checkstyle")    
+        
+        expect:
+        config != null
+        !config.visible
+        config.transitive
+        config.description == 'The Checkstyle libraries to be used for this project.'
+    }
+    
+    def "configures checkstyle extension"() {
+        expect:
+        CheckstyleExtension extension = project.extensions.checkstyle
+        extension.configFile == project.file("config/checkstyle/checkstyle.xml")
+        extension.configProperties == [:]
+        extension.reportsDir == project.file("build/reports/checkstyle")
+        !extension.ignoreFailures
+    }
+
+    def "configures checkstyle task for each source set"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        configuresCheckstyleTask("checkstyleMain", project.sourceSets.main)
+        configuresCheckstyleTask("checkstyleTest", project.sourceSets.test)
+        configuresCheckstyleTask("checkstyleOther", project.sourceSets.other)
+    }
+
+    private void configuresCheckstyleTask(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof Checkstyle
+        task.with {
+            assert description == "Run Checkstyle analysis for ${sourceSet.name} classes"
+            assert checkstyleClasspath == project.configurations["checkstyle"]
+            assert classpath == sourceSet.output
+            assert configFile == project.file("config/checkstyle/checkstyle.xml")
+            assert configProperties == [:]
+            assert resultFile == project.file("build/reports/checkstyle/${sourceSet.name}.xml")
+            assert ignoreFailures == false
+        }
+    }
+    
+    def "configures any additional checkstyle tasks"() {
+        def task = project.tasks.add("checkstyleCustom", Checkstyle)
+
+        expect:
+        task.description == null
+        task.source.isEmpty()
+        task.checkstyleClasspath == project.configurations.checkstyle
+        task.configFile == project.file("config/checkstyle/checkstyle.xml")
+        task.configProperties == [:]
+        task.resultFile == project.file("build/reports/checkstyle/custom.xml")
+        task.ignoreFailures == false
+    }
+
+    def "adds checkstyle tasks to check lifecycle task"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+        
+        expect:
+        that(project.tasks['check'], dependsOn(hasItems("checkstyleMain", "checkstyleTest", "checkstyleOther")))
+    }
+    
+    def "can customize settings via extension"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+        
+        project.checkstyle {
+            sourceSets = [project.sourceSets.main]
+            configFile = project.file("checkstyle-config")
+            configProperties = [foo: "foo"]
+            reportsDir = project.file("checkstyle-reports")
+            ignoreFailures = true
+        }
+        
+        expect:
+        hasCustomizedSettings("checkstyleMain", project.sourceSets.main)
+        hasCustomizedSettings("checkstyleTest", project.sourceSets.test)
+        hasCustomizedSettings("checkstyleOther", project.sourceSets.other)
+        that(project.check, dependsOn(hasItem('checkstyleMain')))
+        that(project.check, dependsOn(not(hasItems('checkstyleTest', 'checkstyleOther'))))
+    }
+
+    private void hasCustomizedSettings(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof Checkstyle
+        task.with {
+            assert description == "Run Checkstyle analysis for ${sourceSet.name} classes"
+            assert source as List == sourceSet.allJava as List
+            assert checkstyleClasspath == project.configurations["checkstyle"]
+            assert configFile == project.file("checkstyle-config")
+            assert configProperties == [foo: "foo"]
+            assert resultFile == project.file("checkstyle-reports/${sourceSet.name}.xml")
+            assert ignoreFailures == true
+        }
+    }
+    
+    def "can customize any additional checkstyle tasks via extension"() {
+        def task = project.tasks.add("checkstyleCustom", Checkstyle)
+        project.checkstyle {
+            configFile = project.file("checkstyle-config")
+            configProperties = [foo: "foo"]
+            reportsDir = project.file("checkstyle-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        task.description == null
+        task.source.isEmpty()
+        task.checkstyleClasspath == project.configurations.checkstyle
+        task.configFile == project.file("checkstyle-config")
+        task.configProperties == [foo: "foo"]
+        task.resultFile == project.file("checkstyle-reports/custom.xml")
+        task.ignoreFailures == true
+    }
+    
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy
new file mode 100644
index 0000000..17ac7b4
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeNarcPluginTest.groovy
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.GroovyBasePlugin
+import org.gradle.api.tasks.SourceSet
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import static org.gradle.util.Matchers.dependsOn
+import static org.hamcrest.Matchers.hasItems
+import static spock.util.matcher.HamcrestSupport.that
+import org.gradle.api.plugins.ReportingBasePlugin
+
+class CodeNarcPluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(CodeNarcPlugin)
+    }
+
+    def "applies reporting-base plugin"() {
+        expect:
+        project.plugins.hasPlugin(ReportingBasePlugin)
+    }
+    
+    def "adds codenarc configuration"() {
+        def config = project.configurations.findByName("codenarc")    
+        
+        expect:
+        config != null
+        !config.visible
+        config.transitive
+        config.description == 'The CodeNarc libraries to be used for this project.'
+    }
+    
+    def "adds codenarc extension"() {
+        expect:
+        CodeNarcExtension codenarc = project.extensions.codenarc
+        codenarc.configFile == project.file("config/codenarc/codenarc.xml")
+        codenarc.reportFormat == "html"
+        codenarc.reportsDir == project.file("build/reports/codenarc")
+        codenarc.sourceSets == []
+        !codenarc.ignoreFailures
+    }
+
+    def "adds codenarc task for each source set"() {
+        project.plugins.apply(GroovyBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        configuresCodeNarcTask("codenarcMain", project.sourceSets.main)
+        configuresCodeNarcTask("codenarcTest", project.sourceSets.test)
+        configuresCodeNarcTask("codenarcOther", project.sourceSets.other)
+    }
+
+    private void configuresCodeNarcTask(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof CodeNarc
+        task.with {
+            assert description == "Run CodeNarc analysis for ${sourceSet.name} classes"
+            assert source as List == sourceSet.allGroovy  as List
+            assert codenarcClasspath == project.configurations.codenarc
+            assert configFile == project.file("config/codenarc/codenarc.xml")
+            assert reportFormat == "html"
+            assert reportFile == project.file("build/reports/codenarc/${sourceSet.name}.html")
+            assert ignoreFailures == false
+        }
+    }
+
+    def "can customize per-source-set tasks via extension"() {
+        project.plugins.apply(GroovyBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        project.codenarc {
+            checkTasks = ["codenarcMain"]
+            configFile = project.file("codenarc-config")
+            reportFormat = "xml"
+            reportsDir = project.file("codenarc-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        hasCustomizedSettings("codenarcMain", project.sourceSets.main)
+        hasCustomizedSettings("codenarcTest", project.sourceSets.test)
+        hasCustomizedSettings("codenarcOther", project.sourceSets.other)
+    }
+
+    private void hasCustomizedSettings(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof CodeNarc
+        task.with {
+            assert description == "Run CodeNarc analysis for ${sourceSet.name} classes"
+            assert source as List == sourceSet.allGroovy as List
+            assert codenarcClasspath == project.configurations.codenarc
+            assert configFile == project.file("codenarc-config")
+            assert reportFormat == "xml"
+            assert reportFile == project.file("codenarc-reports/${sourceSet.name}.xml")
+            assert ignoreFailures == true
+        }
+    }
+
+    def "configures any additional codenarc tasks"() {
+        def task = project.tasks.add("codenarcCustom", CodeNarc)
+
+        expect:
+        task.description == null
+        task.source.isEmpty()
+        task.codenarcClasspath == project.configurations.codenarc
+        task.configFile == project.file("config/codenarc/codenarc.xml")
+        task.reportFormat == "html"
+        task.reportFile == project.file("build/reports/codenarc/custom.html")
+        task.ignoreFailures == false
+    }
+
+    def "can customize additional tasks via extension"() {
+        def task = project.tasks.add("codenarcCustom", CodeNarc)
+
+        project.codenarc {
+            configFile = project.file("codenarc-config")
+            reportFormat = "xml"
+            reportsDir = project.file("codenarc-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        task.description == null
+        task.source.isEmpty()
+        task.codenarcClasspath == project.configurations.codenarc
+        task.configFile == project.file("codenarc-config")
+        task.reportFormat == "xml"
+        task.reportFile == project.file("codenarc-reports/custom.xml")
+        task.ignoreFailures == true
+    }
+    
+    def "adds codenarc tasks from each source sets to check lifecycle task"() {
+        project.plugins.apply(GroovyBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        project.tasks.add("codenarcCustom", CodeNarc)
+        
+        expect:
+        that(project.check, dependsOn(hasItems("codenarcMain", "codenarcTest", "codenarcOther")))
+    }
+
+    def "can customize which tasks are added to check lifecycle task"() {
+        project.plugins.apply(GroovyBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        project.tasks.add("codenarcCustom", CodeNarc)
+
+        project.codenarc {
+            sourceSets = [project.sourceSets.main]
+        }
+
+        expect:
+        that(project.check, dependsOn(hasItems("codenarcMain")))
+    }
+
+    def "can customize task directly"() {
+        CodeNarc task = project.tasks.add("codenarcCustom", CodeNarc)
+
+        task.reports.xml {
+            enabled true
+            destination "build/foo.xml" 
+        }
+        
+        expect:
+        task.reports {
+            assert enabled == [html, xml] as Set
+            assert xml.destination == project.file("build/foo.xml")
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
index c454446..147bd99 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
@@ -25,22 +25,33 @@ import static org.gradle.util.Matchers.*
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.GroovyBasePlugin
 
 class CodeQualityPluginTest {
     private final Project project = HelperUtil.createRootProject()
     private final CodeQualityPlugin plugin = new CodeQualityPlugin()
 
-    @Test public void appliesReportingBasePlugin() {
+    @Test public void appliesCheckstyleAndCodeNarcPlugins() {
         plugin.apply(project)
 
         assertTrue(project.plugins.hasPlugin(ReportingBasePlugin))
+        assertTrue(project.plugins.hasPlugin(CheckstylePlugin))
+        assertTrue(project.plugins.hasPlugin(CodeNarcPlugin))
+        assertFalse(project.plugins.hasPlugin(JavaBasePlugin))
+        assertFalse(project.plugins.hasPlugin(GroovyBasePlugin))
     }
 
     @Test public void addsConventionObjectsToProject() {
         plugin.apply(project)
 
         assertThat(project.convention.plugins.javaCodeQuality, instanceOf(JavaCodeQualityPluginConvention))
+        assertThat(project.checkstyleProperties, equalTo([:]))
+        assertThat(project.checkstyleConfigFile, equalTo(project.file("config/checkstyle/checkstyle.xml")))
+        assertThat(project.file("build/checkstyle"), equalTo(project.checkstyleResultsDir))
+
         assertThat(project.convention.plugins.groovyCodeQuality, instanceOf(GroovyCodeQualityPluginConvention))
+        assertThat(project.codeNarcConfigFile, equalTo(project.file("config/codenarc/codenarc.xml")))
+        assertThat(project.codeNarcReportsDir, equalTo(project.file("build/reports/codenarc")))
     }
 
     @Test public void createsTasksAndAppliesMappingsForEachJavaSourceSet() {
@@ -51,28 +62,31 @@ class CodeQualityPluginTest {
 
         def task = project.tasks[CodeQualityPlugin.CHECKSTYLE_MAIN_TASK]
         assertThat(task, instanceOf(Checkstyle))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.allJava))
+        assertThat(task.source as List, equalTo(project.sourceSets.main.allJava as List))
+        assertThat(task.checkstyleClasspath, equalTo(project.configurations.checkstyle))
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
         assertThat(task.resultFile, equalTo(project.file("build/checkstyle/main.xml")))
-        assertThat(task.properties, equalTo(project.checkstyleProperties))
-        assertThat(task, dependsOn())
+        assertThat(task.configProperties, equalTo(project.checkstyleProperties))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
 
         task = project.tasks[CodeQualityPlugin.CHECKSTYLE_TEST_TASK]
         assertThat(task, instanceOf(Checkstyle))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.test.allJava))
+        assertThat(task.source as List, equalTo(project.sourceSets.test.allJava as List))
+        assertThat(task.checkstyleClasspath, equalTo(project.configurations.checkstyle))
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
         assertThat(task.resultFile, equalTo(project.file("build/checkstyle/test.xml")))
         assertThat(task.properties, equalTo(project.checkstyleProperties))
-        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task, dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME))
 
         project.sourceSets.add('custom')
         task = project.tasks['checkstyleCustom']
         assertThat(task, instanceOf(Checkstyle))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.allJava))
+        assertThat(task.source as List, equalTo(project.sourceSets.custom.allJava as List))
+        assertThat(task.checkstyleClasspath, equalTo(project.configurations.checkstyle))
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
         assertThat(task.resultFile, equalTo(project.file("build/checkstyle/custom.xml")))
         assertThat(task.properties, equalTo(project.checkstyleProperties))
-        assertThat(task, dependsOn())
+        assertThat(task, dependsOn("customClasses"))
 
         task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
         assertThat(task, dependsOn(hasItems(CodeQualityPlugin.CHECKSTYLE_MAIN_TASK, CodeQualityPlugin.CHECKSTYLE_TEST_TASK, 'checkstyleCustom')))
@@ -85,14 +99,16 @@ class CodeQualityPluginTest {
 
         def task = project.tasks[CodeQualityPlugin.CODE_NARC_MAIN_TASK]
         assertThat(task, instanceOf(CodeNarc))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.allGroovy))
+        assertThat(task.source as List, equalTo(project.sourceSets.main.allGroovy as List))
+        assertThat(task.codenarcClasspath, equalTo(project.configurations.codenarc))
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
         assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/main.html")))
         assertThat(task, dependsOn())
 
         task = project.tasks[CodeQualityPlugin.CODE_NARC_TEST_TASK]
         assertThat(task, instanceOf(CodeNarc))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.test.allGroovy))
+        assertThat(task.source as List, equalTo(project.sourceSets.test.allGroovy as List))
+        assertThat(task.codenarcClasspath, equalTo(project.configurations.codenarc))
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
         assertThat(task.reportFormat, equalTo(project.codeNarcReportsFormat))
         assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/test.html")))
@@ -101,7 +117,8 @@ class CodeQualityPluginTest {
         project.sourceSets.add('custom')
         task = project.tasks['codenarcCustom']
         assertThat(task, instanceOf(CodeNarc))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.allGroovy))
+        assertThat(task.source as List, equalTo(project.sourceSets.custom.allGroovy as List))
+        assertThat(task.codenarcClasspath, equalTo(project.configurations.codenarc))
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
         assertThat(task.reportFormat, equalTo(project.codeNarcReportsFormat))
         assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/custom.html")))
@@ -113,18 +130,29 @@ class CodeQualityPluginTest {
         assertThat(task, dependsOn(hasItem('codenarcCustom')))
     }
 
-    @Test public void configuresAdditionalTasksDefinedByTheBuildScript() {
+    @Test public void appliesMappingsForCheckstyleTasksWithoutRequiringTheJavaBasePluginToBeApplied() {
         plugin.apply(project)
 
-        def task = project.tasks.add('customCheckstyle', Checkstyle)
+        project.checkstyleProperties.someProp = 'someValue'
+
+        def task = project.tasks.add('checkstyleApi', Checkstyle)
         assertThat(task.source, isEmpty())
+        assertThat(task.checkstyleClasspath, equalTo(project.configurations.checkstyle))
         assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
-        assertThat(task.resultFile, nullValue())
-        assertThat(task.classpath, nullValue())
+        assertThat(task.resultFile, equalTo(project.file("build/checkstyle/api.xml")))
+        assertThat(task.configProperties, equalTo(project.checkstyleProperties))
+    }
+
+    @Test public void appliesMappingsForCodeNarcTasksWithoutRequiringTheGroovyBasePluginToBeApplied() {
+        plugin.apply(project)
 
-        task = project.tasks.add('customCodeNarc', CodeNarc)
+        project.checkstyleProperties.someProp = 'someValue'
+
+        def task = project.tasks.add('codenarcApi', CodeNarc)
         assertThat(task.source, isEmpty())
+        assertThat(task.codenarcClasspath, equalTo(project.configurations.codenarc))
         assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
-        assertThat(task.reportFile, nullValue())
+        assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/api.html")))
     }
+
 }
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy
new file mode 100644
index 0000000..367cc22
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsPluginTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.tasks.SourceSet
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import static org.gradle.util.Matchers.dependsOn
+import static org.hamcrest.Matchers.*
+import static spock.util.matcher.HamcrestSupport.that
+
+class FindBugsPluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(FindBugsPlugin)
+    }
+
+    def "applies reporting-base plugin"() {
+        expect:
+        project.plugins.hasPlugin(ReportingBasePlugin)
+    }
+
+    def "configures findbugs configuration"() {
+        def config = project.configurations.findByName("findbugs")
+
+        expect:
+        config != null
+        !config.visible
+        config.transitive
+        config.description == 'The FindBugs libraries to be used for this project.'
+    }
+
+    def "configures findbugs extension"() {
+        expect:
+        FindBugsExtension extension = project.extensions.findbugs
+        extension.reportsDir == project.file("build/reports/findbugs")
+        !extension.ignoreFailures
+    }
+
+    def "configures findbugs task for each source set"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        configuresFindBugsTask("findbugsMain", project.sourceSets.main)
+        configuresFindBugsTask("findbugsTest", project.sourceSets.test)
+        configuresFindBugsTask("findbugsOther", project.sourceSets.other)
+    }
+
+    private void configuresFindBugsTask(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof FindBugs
+        task.with {
+            assert description == "Run FindBugs analysis for ${sourceSet.name} classes"
+            assert source as List == sourceSet.allJava as List
+            assert findbugsClasspath == project.configurations.findbugs
+            assert classes.empty // no classes to analyze
+            assert reports.xml.destination == project.file("build/reports/findbugs/${sourceSet.name}.xml")
+            assert ignoreFailures == false
+        }
+    }
+
+    def "configures any additional FindBugs tasks"() {
+        def task = project.tasks.add("findbugsCustom", FindBugs)
+
+        expect:
+        task.description == null
+        task.source.empty
+        task.classes == null
+        task.classpath == null
+        task.findbugsClasspath == project.configurations.findbugs
+        task.pluginClasspath == project.configurations.findbugsPlugins
+        task.reports.xml.destination == project.file("build/reports/findbugs/custom.xml")
+        task.ignoreFailures == false
+    }
+
+    def "adds findbugs tasks to check lifecycle task"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        that(project.check, dependsOn(hasItems("findbugsMain", "findbugsTest", "findbugsOther")))
+    }
+
+    def "can customize settings via extension"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        project.findbugs {
+            sourceSets = [project.sourceSets.main]
+            reportsDir = project.file("findbugs-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        hasCustomizedSettings("findbugsMain", project.sourceSets.main)
+        hasCustomizedSettings("findbugsTest", project.sourceSets.test)
+        hasCustomizedSettings("findbugsOther", project.sourceSets.other)
+        that(project.check, dependsOn(hasItem("findbugsMain")))
+        that(project.check, dependsOn(not(hasItems("findbugsTest", "findbugsOther"))))
+    }
+
+    private void hasCustomizedSettings(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof FindBugs
+        task.with {
+            assert description == "Run FindBugs analysis for ${sourceSet.name} classes"
+            assert source as List == sourceSet.allJava as List
+            assert findbugsClasspath == project.configurations.findbugs
+            assert reports.xml.destination == project.file("findbugs-reports/${sourceSet.name}.xml")
+            assert ignoreFailures == true
+        }
+    }
+    
+    def "can customize any additional FindBugs tasks via extension"() {
+        def task = project.tasks.add("findbugsCustom", FindBugs)
+        project.findbugs {
+            reportsDir = project.file("findbugs-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        task.description == null
+        task.source.empty
+        task.classes == null
+        task.classpath == null
+        task.findbugsClasspath == project.configurations.findbugs
+        task.pluginClasspath == project.configurations.findbugsPlugins
+        task.reports.xml.destination == project.file("findbugs-reports/custom.xml")
+        task.ignoreFailures == true
+    }
+
+    def "can configure reporting"() {
+        given:
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+        }
+
+        when:
+        project.findbugsMain.reports {
+            html {
+                enabled true
+            }
+            xml.destination "foo"
+        }
+
+        then:
+        notThrown()
+    }
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy
new file mode 100644
index 0000000..95fcd42
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 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.plugins.quality
+
+import spock.lang.Specification
+
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsResult
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.api.Project
+import org.gradle.api.GradleException
+
+class FindBugsTest extends Specification {
+    private FindBugs findbugs
+
+    def setup() {
+        Project project = ProjectBuilder.builder().build()
+        findbugs = project.task("findbugTask", type: FindBugs)
+    }
+
+    def "errorCount > 0 causes failing FindBugsTask"() {
+        setup:
+        FindBugsResult result = Mock(FindBugsResult)
+        result.errorCount >> 1
+        when:
+        findbugs.evaluateResult(result)
+        then:
+        def e = thrown(GradleException)
+        e.message == "FindBugs encountered an error. Run with --debug to get more information."
+    }
+
+    def "bugsCount > 0 does causes failing FindBugsTask"() {
+        setup:
+        FindBugsResult result = Mock(FindBugsResult)
+        result.bugCount >> 1
+        when:
+        findbugs.evaluateResult(result)
+        then:
+        def e = thrown(GradleException)
+        e.message == "FindBugs rule violations were found."
+    }
+
+    def "task not failing for bugsCount>0 when ignoreFailures is set to true"() {
+        setup:
+        FindBugsResult result = Mock(FindBugsResult)
+        result.bugCount >> 1
+        when:
+        findbugs.ignoreFailures = true
+        then:
+        findbugs.evaluateResult(result)
+    }
+
+    def "task error message refer to findbugs reports if reports are configured"() {
+        setup:
+        FindBugsResult result = Mock(FindBugsResult)
+        result.bugCount >> 1
+        findbugs.reports {
+            xml {
+                enabled = true
+                destination "build/findbugs.xml"
+            }
+        }
+        when:
+        findbugs.evaluateResult(result)
+        then:
+        def e = thrown(GradleException)
+        e.message.startsWith("FindBugs rule violations were found. See the report at")
+    }
+
+    def "ignoreFailures flag is ignored for errorCount"() {
+        setup:
+        FindBugsResult result = Mock(FindBugsResult)
+        result.errorCount >> 1
+        findbugs.ignoreFailures = true
+        when:
+        findbugs.evaluateResult(result)
+        then:
+        def e = thrown(GradleException)
+        e.message == "FindBugs encountered an error. Run with --debug to get more information."
+    }
+
+    /**
+     * keep behaviour in sync with findbugs ant task
+     * */
+    def "missingClassCount > 0 does not cause failing FindBugsTask"() {
+        setup:
+        FindBugsResult result = Mock(FindBugsResult)
+        when:
+        result.missingClassCount >> 1
+        then:
+        findbugs.evaluateResult(result);
+    }
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy
new file mode 100644
index 0000000..424a8eb
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/JDependPluginTest.groovy
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.tasks.SourceSet
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import static org.gradle.util.Matchers.dependsOn
+import static org.hamcrest.Matchers.*
+import static spock.util.matcher.HamcrestSupport.that
+
+class JDependPluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(JDependPlugin)
+    }
+
+    def "applies reporting-base plugin"() {
+        expect:
+        project.plugins.hasPlugin(ReportingBasePlugin)
+    }
+
+    def "configures jdepend configuration"() {
+        def config = project.configurations.findByName("jdepend")
+
+        expect:
+        config != null
+        !config.visible
+        config.transitive
+        config.description == 'The JDepend libraries to be used for this project.'
+    }
+
+    def "configures jdepend extension"() {
+        expect:
+        JDependExtension extension = project.extensions.jdepend
+        extension.reportsDir == project.file("build/reports/jdepend")
+        !extension.ignoreFailures
+    }
+
+    def "configures jdepend task for each source set"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        configuresJDependTask("jdependMain", project.sourceSets.main)
+        configuresJDependTask("jdependTest", project.sourceSets.test)
+        configuresJDependTask("jdependOther", project.sourceSets.other)
+    }
+
+    private void configuresJDependTask(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof JDepend
+        task.with {
+            assert description == "Run JDepend analysis for ${sourceSet.name} classes"
+            assert jdependClasspath == project.configurations.jdepend
+            assert classesDir == sourceSet.output.classesDir
+            assert reports.xml.destination == project.file("build/reports/jdepend/${sourceSet.name}.xml")
+        }
+    }
+
+    def "configures any additional JDepend tasks"() {
+        def task = project.tasks.add("jdependCustom", JDepend)
+
+        expect:
+        task.description == null
+        task.classesDir == null
+        task.jdependClasspath == project.configurations.jdepend
+        task.reports.xml.destination == project.file("build/reports/jdepend/custom.xml")
+    }
+
+    def "adds jdepend tasks to check lifecycle task"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        that(project.tasks['check'], dependsOn(hasItems("jdependMain", "jdependTest", "jdependOther")))
+    }
+
+    def "can customize settings via extension"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        project.jdepend {
+            sourceSets = [project.sourceSets.main]
+            reportsDir = project.file("jdepend-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        hasCustomizedSettings("jdependMain", project.sourceSets.main)
+        hasCustomizedSettings("jdependTest", project.sourceSets.test)
+        hasCustomizedSettings("jdependOther", project.sourceSets.other)
+        that(project.check, dependsOn(hasItem('jdependMain')))
+        that(project.check, dependsOn(not(hasItems('jdependTest', 'jdependOther'))))
+    }
+
+    def "can customize any additional JDepend tasks via extension"() {
+        def task = project.tasks.add("jdependCustom", JDepend)
+        project.jdepend {
+            reportsDir = project.file("jdepend-reports")
+        }
+
+        expect:
+        task.description == null
+        task.classesDir == null
+        task.jdependClasspath == project.configurations.jdepend
+        task.reports.xml.destination == project.file("jdepend-reports/custom.xml")
+    }
+
+    def "can configure reporting"() {
+        given:
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+        }
+
+        when:
+        project.jdependMain.reports {
+            text {
+                enabled true
+            }
+            xml.destination "foo"
+        }
+
+        then:
+        notThrown()
+    }
+
+    private void hasCustomizedSettings(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof JDepend
+        task.with {
+            assert description == "Run JDepend analysis for ${sourceSet.name} classes"
+            assert jdependClasspath == project.configurations.jdepend
+            assert classesDir == sourceSet.output.classesDir
+            assert reports.xml.destination == project.file("jdepend-reports/${sourceSet.name}.xml")
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
new file mode 100644
index 0000000..04c9172
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2011 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.tasks.SourceSet
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import static org.gradle.util.Matchers.dependsOn
+import static org.hamcrest.Matchers.*
+import static spock.util.matcher.HamcrestSupport.that
+
+class PmdPluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(PmdPlugin)
+    }
+
+    def "applies reporting-base plugin"() {
+        expect:
+        project.plugins.hasPlugin(ReportingBasePlugin)
+    }
+
+    def "configures pmd configuration"() {
+        def config = project.configurations.findByName("pmd")
+
+        expect:
+        config != null
+        !config.visible
+        config.transitive
+        config.description == 'The PMD libraries to be used for this project.'
+    }
+
+    def "configures pmd extension"() {
+        expect:
+        PmdExtension extension = project.extensions.pmd
+        extension.ruleSets == ["basic"]
+        extension.ruleSetFiles.empty
+        extension.reportsDir == project.file("build/reports/pmd")
+        !extension.ignoreFailures
+    }
+
+    def "configures pmd task for each source set"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        configuresPmdTask("pmdMain", project.sourceSets.main)
+        configuresPmdTask("pmdTest", project.sourceSets.test)
+        configuresPmdTask("pmdOther", project.sourceSets.other)
+    }
+
+    private void configuresPmdTask(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof Pmd
+        task.with {
+            assert description == "Run PMD analysis for ${sourceSet.name} classes"
+            source as List == sourceSet.allJava as List
+            assert pmdClasspath == project.configurations.pmd
+            assert ruleSets == ["basic"]
+            assert ruleSetFiles.empty
+            assert reports.xml.destination == project.file("build/reports/pmd/${sourceSet.name}.xml")
+            assert reports.html.destination == project.file("build/reports/pmd/${sourceSet.name}.html")
+            assert ignoreFailures == false
+        }
+    }
+    
+    def "configures any additional PMD tasks"() {
+        def task = project.tasks.add("pmdCustom", Pmd)
+
+        expect:
+        task.description == null
+        task.source.empty
+        task.pmdClasspath == project.configurations.pmd
+        task.ruleSets == ["basic"]
+        task.ruleSetFiles.empty
+        task.reports.xml.destination == project.file("build/reports/pmd/custom.xml")
+        task.reports.html.destination == project.file("build/reports/pmd/custom.html")
+        task.ignoreFailures == false
+    }
+
+    def "adds pmd tasks to check lifecycle task"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        expect:
+        that(project.tasks['check'], dependsOn(hasItems("pmdMain", "pmdTest", "pmdOther")))
+    }
+
+    def "can customize settings via extension"() {
+        project.plugins.apply(JavaBasePlugin)
+        project.sourceSets {
+            main
+            test
+            other
+        }
+
+        project.pmd {
+            sourceSets = [project.sourceSets.main]
+            ruleSets = ["braces", "unusedcode"]
+            ruleSetFiles = project.files("my-ruleset.xml")
+            reportsDir = project.file("pmd-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        hasCustomizedSettings("pmdMain", project.sourceSets.main)
+        hasCustomizedSettings("pmdTest", project.sourceSets.test)
+        hasCustomizedSettings("pmdOther", project.sourceSets.other)
+        that(project.check, dependsOn(hasItem('pmdMain')))
+        that(project.check, dependsOn(not(hasItems('pmdTest', 'pmdOther'))))
+    }
+
+    private void hasCustomizedSettings(String taskName, SourceSet sourceSet) {
+        def task = project.tasks.findByName(taskName)
+        assert task instanceof Pmd
+        task.with {
+            assert description == "Run PMD analysis for ${sourceSet.name} classes"
+            source as List == sourceSet.allJava as List
+            assert pmdClasspath == project.configurations.pmd
+            assert ruleSets == ["braces", "unusedcode"]
+            assert ruleSetFiles.files == project.files("my-ruleset.xml").files
+            assert reports.xml.destination == project.file("pmd-reports/${sourceSet.name}.xml")
+            assert reports.html.destination == project.file("pmd-reports/${sourceSet.name}.html")
+            assert ignoreFailures == true
+        }
+    }
+    
+    def "can customize any additional PMD tasks via extension"() {
+        def task = project.tasks.add("pmdCustom", Pmd)
+        project.pmd {
+            ruleSets = ["braces", "unusedcode"]
+            ruleSetFiles = project.files("my-ruleset.xml")
+            reportsDir = project.file("pmd-reports")
+            ignoreFailures = true
+        }
+
+        expect:
+        task.description == null
+        task.source.empty
+        task.pmdClasspath == project.configurations.pmd
+        task.ruleSets == ["braces", "unusedcode"]
+        task.ruleSetFiles.files == project.files("my-ruleset.xml").files
+        task.reports.xml.destination == project.file("pmd-reports/custom.xml")
+        task.reports.html.destination == project.file("pmd-reports/custom.html")
+        task.outputs.files.files == task.reports.enabled*.destination as Set
+        task.ignoreFailures == true
+    }
+    
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuterTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuterTest.groovy
new file mode 100644
index 0000000..f0f4319
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuterTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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.plugins.quality.internal.findbugs
+
+import edu.umd.cs.findbugs.IFindBugsEngine
+import spock.lang.Specification
+
+class FindBugsExecuterTest extends Specification {
+
+    def FindBugsExecuter findBugsExecuter = new FindBugsExecuter();
+
+    def "FindBugsResult contains bugCount from FindBugsEngine"() {
+        setup:
+        IFindBugsEngine findbugs = Mock(IFindBugsEngine)
+        findbugs.bugCount >> 1;
+        when:
+        def result = findBugsExecuter.createFindbugsResult(findbugs)
+        then:
+        result.bugCount == 1
+    }
+
+    def "FindBugsResult contains errorCount from FindBugsEngine"() {
+        setup:
+        IFindBugsEngine findbugs = Mock(IFindBugsEngine)
+        findbugs.errorCount >> 1;
+        when:
+        def result = findBugsExecuter.createFindbugsResult(findbugs)
+        then:
+        result.errorCount == 1
+    }
+
+    def "FindBugsResult contains missingClassCount from FindBugsEngine"() {
+        setup:
+        IFindBugsEngine findbugs = Mock(IFindBugsEngine)
+        findbugs.missingClassCount >> 1;
+        when:
+        def result = findBugsExecuter.createFindbugsResult(findbugs)
+        then:
+        result.missingClassCount == 1
+    }
+}
diff --git a/subprojects/core-impl/core-impl.gradle b/subprojects/core-impl/core-impl.gradle
index b810424..064d6ed 100644
--- a/subprojects/core-impl/core-impl.gradle
+++ b/subprojects/core-impl/core-impl.gradle
@@ -1,17 +1,18 @@
 apply plugin: "groovy"
 
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(":core")
-    compile libraries.guava
+
+    compile libraries.commons_httpclient
     compile libraries.commons_lang
+    compile libraries.commons_io
     compile libraries.ivy
-    compile "org.apache.maven:maven-ant-tasks:2.1.1 at jar"
-    compile "org.sonatype.pmaven:pmaven-common:0.8-20100325 at jar"
-    compile "org.sonatype.pmaven:pmaven-groovy:0.8-20100325 at jar"
-    compile "org.codehaus.plexus:plexus-component-annotations:1.5.2 at jar"
+    compile libraries.slf4j_api
+    compile libraries.maven_ant_tasks
 
     testCompile libraries.junit
-    testCompile project(path: ':core', configuration: 'testFixtures')
-}
\ No newline at end of file
+}
+
+useTestFixtures()
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
new file mode 100644
index 0000000..92fbe7d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryDelegate;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.notations.ClientModuleNotationParser;
+import org.gradle.api.internal.notations.DependencyNotationParser;
+import org.gradle.api.internal.notations.ProjectDependencyFactory;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDependencyFactory implements DependencyFactory {
+    private final DependencyNotationParser dependencyNotationParser;
+    private final ClientModuleNotationParser clientModuleNotationParser;
+    private ProjectDependencyFactory projectDependencyFactory;
+
+    public DefaultDependencyFactory(DependencyNotationParser dependencyNotationParser,
+                                    ClientModuleNotationParser clientModuleNotationParser,
+                                    ProjectDependencyFactory projectDependencyFactory) {
+        this.dependencyNotationParser = dependencyNotationParser;
+        this.clientModuleNotationParser = clientModuleNotationParser;
+        this.projectDependencyFactory = projectDependencyFactory;
+    }
+
+    public Dependency createDependency(Object dependencyNotation) {
+        return dependencyNotationParser.parseNotation(dependencyNotation);
+    }
+
+    public ClientModule createModule(Object dependencyNotation, Closure configureClosure) {
+        ClientModule clientModule = clientModuleNotationParser.parseNotation(dependencyNotation);
+        ModuleFactoryDelegate moduleFactoryDelegate = new ModuleFactoryDelegate(clientModule, this);
+        moduleFactoryDelegate.prepareDelegation(configureClosure);
+        if (configureClosure != null) {
+            configureClosure.call();
+        }
+        return clientModule;
+    }
+
+    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder, Map<? extends String, ? extends Object> map) {
+        return projectDependencyFactory.createFromMap(projectFinder, map);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
index 7f1b6fe..0c1fc29 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
@@ -15,25 +15,378 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.gradle.api.artifacts.maven.MavenFactory;
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
-import org.gradle.api.internal.artifacts.publish.maven.DefaultLocalMavenCacheLocator;
-import org.gradle.api.internal.artifacts.publish.maven.DefaultMavenFactory;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.StartParameter;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.dsl.ArtifactHandler;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
+import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
+import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.SingleFileBackedModuleResolutionCache;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.StartParameterResolutionOverride;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.DefaultModuleDescriptorCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.*;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.*;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.DefaultProjectModuleRegistry;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultDependencyResolver;
+import org.gradle.api.internal.artifacts.mvnsettings.DefaultLocalMavenRepositoryLocator;
+import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenFileLocations;
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
 import org.gradle.api.internal.artifacts.repositories.DefaultResolverFactory;
-import org.gradle.api.internal.project.DefaultServiceRegistry;
-import org.gradle.api.internal.project.ServiceRegistry;
-import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.externalresource.cached.ByUrlCachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryCachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.local.ivy.LocallyAvailableResourceFinderFactory;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.internal.filestore.UniquePathFileStore;
+import org.gradle.api.internal.filestore.ivy.ArtifactRevisionIdFileStore;
+import org.gradle.api.internal.notations.*;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.cache.CacheRepository;
+import org.gradle.internal.Factory;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.BuildCommencedTimeProvider;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.List;
 
 public class DefaultDependencyManagementServices extends DefaultServiceRegistry implements DependencyManagementServices {
+
     public DefaultDependencyManagementServices(ServiceRegistry parent) {
         super(parent);
     }
 
-    protected ResolverFactory createResolverFactory() {
-        return new DefaultResolverFactory(getFactory(LoggingManagerInternal.class), get(MavenFactory.class), new DefaultLocalMavenCacheLocator());
+    public DependencyResolutionServices create(FileResolver resolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
+        return new DefaultDependencyResolutionServices(this, resolver, dependencyMetaDataProvider, projectFinder, domainObjectContext);
+    }
+
+    protected ResolveModuleDescriptorConverter createResolveModuleDescriptorConverter() {
+        DependencyDescriptorFactory dependencyDescriptorFactoryDelegate = get(DependencyDescriptorFactoryDelegate.class);
+        return new ResolveModuleDescriptorConverter(
+                get(ModuleDescriptorFactory.class),
+                get(ConfigurationsToModuleDescriptorConverter.class),
+                new DefaultDependenciesToModuleDescriptorConverter(
+                        dependencyDescriptorFactoryDelegate,
+                        get(ExcludeRuleConverter.class)));
+
+    }
+
+    protected PublishModuleDescriptorConverter createPublishModuleDescriptorConverter() {
+        return new PublishModuleDescriptorConverter(
+                get(ResolveModuleDescriptorConverter.class),
+                new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY));
+    }
+
+    protected ModuleDescriptorFactory createModuleDescriptorFactory() {
+        return new DefaultModuleDescriptorFactory(get(IvyFactory.class), get(SettingsConverter.class));
+    }
+
+    protected ExcludeRuleConverter createExcludeRuleConverter() {
+        return new DefaultExcludeRuleConverter();
+    }
+
+    protected ExternalModuleDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory() {
+        return new ExternalModuleDependencyDescriptorFactory(get(ExcludeRuleConverter.class));
+    }
+
+    protected ConfigurationsToModuleDescriptorConverter createConfigurationsToModuleDescriptorConverter() {
+        return new DefaultConfigurationsToModuleDescriptorConverter();
+    }
+
+    protected DependencyDescriptorFactoryDelegate createDependencyDescriptorFactory() {
+        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory = new DefaultModuleDescriptorFactoryForClientModule();
+        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
+                new ClientModuleDependencyDescriptorFactory(
+                        get(ExcludeRuleConverter.class),
+                        clientModuleDescriptorFactory
+                ),
+                new ProjectDependencyDescriptorFactory(
+                        get(ExcludeRuleConverter.class)),
+                get(ExternalModuleDependencyDescriptorFactory.class));
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactoryDelegate);
+        return dependencyDescriptorFactoryDelegate;
+    }
+
+    protected DependencyFactory createDependencyFactory() {
+        Instantiator instantiator = get(Instantiator.class);
+
+        ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(get(StartParameter.class).isBuildProjectDependencies());
+
+        ProjectDependencyFactory projectDependencyFactory = new ProjectDependencyFactory(
+                projectDependenciesBuildInstruction,
+                instantiator);
+
+        DependencyProjectNotationParser projParser = new DependencyProjectNotationParser(
+                projectDependenciesBuildInstruction,
+                instantiator);
+
+        NotationParser<? extends Dependency> moduleMapParser = new DependencyMapNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
+        NotationParser<? extends Dependency> moduleStringParser = new DependencyStringNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
+        NotationParser<? extends Dependency> selfResolvingDependencyFactory = new DependencyFilesNotationParser(instantiator);
+
+        List<NotationParser<? extends Dependency>> notationParsers = WrapUtil.toList(
+                moduleStringParser,
+                moduleMapParser,
+                selfResolvingDependencyFactory,
+                projParser,
+                new DependencyClassPathNotationParser(instantiator, get(ClassPathRegistry.class), new IdentityFileResolver()));
+
+        DependencyNotationParser dependencyNotationParser = new DependencyNotationParser(notationParsers);
+
+        return new DefaultDependencyFactory(
+                dependencyNotationParser,
+                new ClientModuleNotationParser(instantiator),
+                projectDependencyFactory);
+    }
+
+    protected CacheLockingManager createCacheLockingManager() {
+        return new DefaultCacheLockingManager(
+                get(CacheRepository.class)
+        );
+    }
+
+    protected BuildCommencedTimeProvider createBuildTimeProvider() {
+        return new BuildCommencedTimeProvider();
+    }
+
+    protected ModuleResolutionCache createModuleResolutionCache() {
+        return new SingleFileBackedModuleResolutionCache(
+                get(ArtifactCacheMetaData.class),
+                get(BuildCommencedTimeProvider.class),
+                get(CacheLockingManager.class)
+        );
+    }
+
+    protected ModuleDescriptorCache createModuleDescriptorCache() {
+        return new DefaultModuleDescriptorCache(
+                get(ArtifactCacheMetaData.class),
+                get(BuildCommencedTimeProvider.class),
+                get(CacheLockingManager.class)
+        );
     }
 
-    protected MavenFactory createMavenFactory() {
-        return new DefaultMavenFactory();
+    protected ArtifactAtRepositoryCachedExternalResourceIndex createArtifactAtRepositoryCachedResolutionIndex() {
+        return new ArtifactAtRepositoryCachedExternalResourceIndex(
+                new File(get(ArtifactCacheMetaData.class).getCacheDir(), "artifact-at-repository.bin"),
+                get(BuildCommencedTimeProvider.class),
+                get(CacheLockingManager.class)
+        );
+    }
+
+    protected ByUrlCachedExternalResourceIndex createArtifactUrlCachedResolutionIndex() {
+        return new ByUrlCachedExternalResourceIndex(
+                new File(get(ArtifactCacheMetaData.class).getCacheDir(), "artifact-at-url.bin"),
+                get(BuildCommencedTimeProvider.class),
+                get(CacheLockingManager.class)
+        );
+    }
+
+    protected UniquePathFileStore createUniquePathFileStore() {
+        return new UniquePathFileStore(new File(get(ArtifactCacheMetaData.class).getCacheDir(), "filestore"));
+    }
+
+    protected ArtifactRevisionIdFileStore createArtifactRevisionIdFileStore() {
+        return new ArtifactRevisionIdFileStore(get(UniquePathFileStore.class));
+    }
+
+    protected SettingsConverter createSettingsConverter() {
+        return new DefaultSettingsConverter(
+                new IvySettingsFactory(
+                        get(ArtifactCacheMetaData.class)
+                )
+        );
+    }
+
+    protected IvyFactory createIvyFactory() {
+        return new DefaultIvyFactory();
+    }
+
+    protected LocalMavenRepositoryLocator createLocalMavenRepositoryLocator() {
+        return new DefaultLocalMavenRepositoryLocator(new DefaultMavenFileLocations(), SystemProperties.asMap(), System.getenv());
+    }
+
+    protected LocallyAvailableResourceFinder<ArtifactRevisionId> createArtifactRevisionIdLocallyAvailableResourceFinder() {
+        LocallyAvailableResourceFinderFactory finderFactory = new LocallyAvailableResourceFinderFactory(
+                get(ArtifactCacheMetaData.class), get(LocalMavenRepositoryLocator.class), get(ArtifactRevisionIdFileStore.class)
+        );
+        
+        return finderFactory.create();
+    }
+    protected RepositoryTransportFactory createRepositoryTransportFactory() {
+        return new RepositoryTransportFactory(
+                get(ProgressLoggerFactory.class), get(ArtifactRevisionIdFileStore.class), get(ByUrlCachedExternalResourceIndex.class)
+        );
+    }
+
+    private class DefaultDependencyResolutionServices implements DependencyResolutionServices {
+        private final ServiceRegistry parent;
+        private final FileResolver fileResolver;
+        private final DependencyMetaDataProvider dependencyMetaDataProvider;
+        private final ProjectFinder projectFinder;
+        private final DomainObjectContext domainObjectContext;
+        private DefaultRepositoryHandler repositoryHandler;
+        private ConfigurationContainerInternal configurationContainer;
+        private DependencyHandler dependencyHandler;
+        private DefaultArtifactHandler artifactHandler;
+
+        private DefaultDependencyResolutionServices(ServiceRegistry parent, FileResolver fileResolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
+            this.parent = parent;
+            this.fileResolver = fileResolver;
+            this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+            this.projectFinder = projectFinder;
+            this.domainObjectContext = domainObjectContext;
+        }
+
+        public DefaultRepositoryHandler getResolveRepositoryHandler() {
+            if (repositoryHandler == null) {
+                repositoryHandler = createRepositoryHandler();
+            }
+            return repositoryHandler;
+        }
+
+        private DefaultRepositoryHandler createRepositoryHandler() {
+            Instantiator instantiator = parent.get(Instantiator.class);
+            @SuppressWarnings("unchecked") ResolverFactory resolverFactory = new DefaultResolverFactory(
+                    get(LocalMavenRepositoryLocator.class),
+                    fileResolver,
+                    instantiator,
+                    get(RepositoryTransportFactory.class),
+                    get(LocallyAvailableResourceFinder.class),
+                    get(ByUrlCachedExternalResourceIndex.class)
+                    );
+            return instantiator.newInstance(DefaultRepositoryHandler.class, resolverFactory, instantiator);
+        }
+
+        public ConfigurationContainerInternal getConfigurationContainer() {
+            if (configurationContainer == null) {
+                Instantiator instantiator = parent.get(Instantiator.class);
+                ArtifactDependencyResolver dependencyResolver = createDependencyResolver(getResolveRepositoryHandler());
+                configurationContainer = instantiator.newInstance(DefaultConfigurationContainer.class,
+                        dependencyResolver, instantiator, domainObjectContext, parent.get(ListenerManager.class),
+                        dependencyMetaDataProvider);
+            }
+            return configurationContainer;
+        }
+
+        public DependencyHandler getDependencyHandler() {
+            if (dependencyHandler == null) {
+                dependencyHandler = new DefaultDependencyHandler(getConfigurationContainer(), parent.get(DependencyFactory.class), projectFinder);
+            }
+            return dependencyHandler;
+        }
+
+        public ArtifactHandler getArtifactHandler() {
+            if (artifactHandler == null) {
+                artifactHandler = new DefaultArtifactHandler(
+                        getConfigurationContainer(),
+                        new DefaultPublishArtifactFactory(
+                                get(Instantiator.class),
+                                dependencyMetaDataProvider));
+            }
+            return artifactHandler;
+        }
+
+        public Factory<ArtifactPublicationServices> getPublishServicesFactory() {
+            return new Factory<ArtifactPublicationServices>() {
+                public ArtifactPublicationServices create() {
+                    return new DefaultArtifactPublicationServices(DefaultDependencyResolutionServices.this);
+                }
+            };
+        }
+
+        ArtifactDependencyResolver createDependencyResolver(DefaultRepositoryHandler resolverProvider) {
+            StartParameter startParameter = get(StartParameter.class);
+            StartParameterResolutionOverride startParameterResolutionOverride = new StartParameterResolutionOverride(startParameter);
+            ResolveIvyFactory ivyFactory = new ResolveIvyFactory(
+                    get(IvyFactory.class),
+                    resolverProvider,
+                    get(SettingsConverter.class),
+                    get(ModuleResolutionCache.class),
+                    get(ModuleDescriptorCache.class),
+                    get(ArtifactAtRepositoryCachedExternalResourceIndex.class),
+                    get(CacheLockingManager.class),
+                    startParameterResolutionOverride,
+                    get(BuildCommencedTimeProvider.class));
+
+            ResolvedArtifactFactory resolvedArtifactFactory = new ResolvedArtifactFactory(
+                    get(CacheLockingManager.class)
+            );
+
+            ArtifactDependencyResolver resolver = new DefaultDependencyResolver(
+                    ivyFactory,
+                    get(PublishModuleDescriptorConverter.class),
+                    resolvedArtifactFactory,
+                    new DefaultProjectModuleRegistry(
+                            get(PublishModuleDescriptorConverter.class))
+            );
+            return new ErrorHandlingArtifactDependencyResolver(
+                    new ShortcircuitEmptyConfigsArtifactDependencyResolver(
+                            new SelfResolvingDependencyResolver(
+                                    new CacheLockingArtifactDependencyResolver(
+                                            get(CacheLockingManager.class),
+                                            resolver))));
+        }
+
+        ArtifactPublisher createArtifactPublisher(DefaultRepositoryHandler resolverProvider) {
+            PublishModuleDescriptorConverter fileModuleDescriptorConverter = new PublishModuleDescriptorConverter(
+                    get(ResolveModuleDescriptorConverter.class),
+                    new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
+
+            return new ErrorHandlingArtifactPublisher(
+                    new IvyBackedArtifactPublisher(
+                            resolverProvider,
+                            get(SettingsConverter.class),
+                            get(PublishModuleDescriptorConverter.class),
+                            fileModuleDescriptorConverter,
+                            get(IvyFactory.class),
+                            new DefaultIvyDependencyPublisher(
+                                    new DefaultPublishOptionsFactory())));
+        }
+    }
+
+    private static class DefaultArtifactPublicationServices implements ArtifactPublicationServices {
+        private final DefaultDependencyResolutionServices dependencyResolutionServices;
+        private DefaultRepositoryHandler repositoryHandler;
+        private ArtifactPublisher artifactPublisher;
+
+        public DefaultArtifactPublicationServices(DefaultDependencyResolutionServices dependencyResolutionServices) {
+            this.dependencyResolutionServices = dependencyResolutionServices;
+        }
+
+        public ArtifactPublisher getArtifactPublisher() {
+            if (artifactPublisher == null) {
+                artifactPublisher = dependencyResolutionServices.createArtifactPublisher(getRepositoryHandler());
+            }
+            return artifactPublisher;
+        }
+
+        public DefaultRepositoryHandler getRepositoryHandler() {
+            if (repositoryHandler == null) {
+                repositoryHandler = dependencyResolutionServices.createRepositoryHandler();
+            }
+            return repositoryHandler;
+        }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
new file mode 100644
index 0000000..c788fa9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.file.FileSource;
+import org.gradle.util.DeprecationLogger;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedArtifact implements ResolvedArtifact {
+    private final ResolvedDependency resolvedDependency;
+    private final Artifact artifact;
+    private FileSource artifactSource;
+    private File file;
+
+    public DefaultResolvedArtifact(ResolvedDependency resolvedDependency, Artifact artifact, FileSource artifactSource) {
+        this.resolvedDependency = resolvedDependency;
+        this.artifact = artifact;
+        this.artifactSource = artifactSource;
+    }
+
+    public ResolvedDependency getResolvedDependency() {
+        DeprecationLogger.nagUserWith("ResolvedArtifact.getResolvedDependency() is deprecated. For version info use ResolvedArtifact.getModuleVersion(), to access the dependency graph use ResolvedConfiguration.getFirstLevelModuleDependencies()");
+        return resolvedDependency;
+    }
+
+    public ResolvedModuleVersion getModuleVersion() {
+        return resolvedDependency.getModule();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[ResolvedArtifact dependency:%s name:%s classifier:%s extension:%s type:%s]", resolvedDependency, getName(), getClassifier(), getExtension(), getType());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultResolvedArtifact other = (DefaultResolvedArtifact) obj;
+        if (!other.resolvedDependency.getModule().getId().equals(resolvedDependency.getModule().getId())) {
+            return false;
+        }
+        if (!other.getName().equals(getName())) {
+            return false;
+        }
+        if (!other.getType().equals(getType())) {
+            return false;
+        }
+        if (!other.getExtension().equals(getExtension())) {
+            return false;
+        }
+        if (!other.artifact.getExtraAttributes().equals(artifact.getExtraAttributes())) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return resolvedDependency.getModule().getId().hashCode() ^ getName().hashCode() ^ getType().hashCode() ^ getExtension().hashCode() ^ artifact.getExtraAttributes().hashCode();
+    }
+
+    public String getName() {
+        return artifact.getName();
+    }
+
+    public String getType() {
+        return artifact.getType();
+    }
+
+    public String getExtension() {
+        return artifact.getExt();
+    }
+
+    public String getClassifier() {
+        return artifact.getExtraAttribute(Dependency.CLASSIFIER);
+    }
+    
+    public File getFile() {
+        if (file == null) {
+            file = artifactSource.get();
+            artifactSource = null;
+        }
+        return file;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
new file mode 100644
index 0000000..3c8d725
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
@@ -0,0 +1,196 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedDependency implements ResolvedDependency {
+    private final Set<ResolvedDependency> children = new LinkedHashSet<ResolvedDependency>();
+    private final Set<ResolvedDependency> parents = new LinkedHashSet<ResolvedDependency>();
+    private final Map<ResolvedDependency, Set<ResolvedArtifact>> parentArtifacts = new LinkedHashMap<ResolvedDependency, Set<ResolvedArtifact>>();
+    private final String name;
+    private final ResolvedConfigurationIdentifier id;
+    private final Set<ResolvedArtifact> moduleArtifacts;
+    private final Map<ResolvedDependency, Set<ResolvedArtifact>> allArtifactsCache = new HashMap<ResolvedDependency, Set<ResolvedArtifact>>();
+    private Set<ResolvedArtifact> allModuleArtifactsCache;
+
+    public DefaultResolvedDependency(String name, String moduleGroup, String moduleName, String moduleVersion, String configuration) {
+        this.name = name;
+        id = new ResolvedConfigurationIdentifier(moduleGroup, moduleName, moduleVersion, configuration);
+        this.moduleArtifacts = new TreeSet<ResolvedArtifact>(new ResolvedArtifactComparator());
+    }
+
+    public DefaultResolvedDependency(String moduleGroup, String moduleName, String moduleVersion, String configuration) {
+        this(moduleGroup + ":" + moduleName + ":" + moduleVersion, moduleGroup, moduleName, moduleVersion, configuration);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ResolvedConfigurationIdentifier getId() {
+        return id;
+    }
+
+    public String getModuleGroup() {
+        return id.getModuleGroup();
+    }
+
+    public String getModuleName() {
+        return id.getModuleName();
+    }
+
+    public String getModuleVersion() {
+        return id.getModuleVersion();
+    }
+
+    public String getConfiguration() {
+        return id.getConfiguration();
+    }
+
+    public ResolvedModuleVersion getModule() {
+        return new ResolvedModuleVersion() {
+            public ModuleVersionIdentifier getId() {
+                return new DefaultModuleVersionIdentifier(id.getModuleGroup(), id.getModuleName(), id.getModuleVersion());
+            }
+        };
+    }
+
+    public Set<ResolvedDependency> getChildren() {
+        return children;
+    }
+
+    public Set<ResolvedArtifact> getModuleArtifacts() {
+        return moduleArtifacts;
+    }
+
+    public Set<ResolvedArtifact> getAllModuleArtifacts() {
+        if (allModuleArtifactsCache == null) {
+            Set<ResolvedArtifact> allArtifacts = new LinkedHashSet<ResolvedArtifact>();
+            allArtifacts.addAll(getModuleArtifacts());
+            for (ResolvedDependency childResolvedDependency : getChildren()) {
+                allArtifacts.addAll(childResolvedDependency.getAllModuleArtifacts());
+            }
+            allModuleArtifactsCache = allArtifacts;
+        }
+        return allModuleArtifactsCache;
+    }
+
+    public Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent) {
+        if (!parents.contains(parent)) {
+            throw new InvalidUserDataException("Unknown Parent");
+        }
+        Set<ResolvedArtifact> artifacts = parentArtifacts.get(parent);
+        return artifacts == null ? Collections.<ResolvedArtifact>emptySet() : artifacts;
+    }
+
+    public Set<ResolvedArtifact> getArtifacts(ResolvedDependency parent) {
+        return getParentArtifacts(parent);
+    }
+
+    public Set<ResolvedArtifact> getAllArtifacts(ResolvedDependency parent) {
+        if (allArtifactsCache.get(parent) == null) {
+            Set<ResolvedArtifact> allArtifacts = new LinkedHashSet<ResolvedArtifact>();
+            allArtifacts.addAll(getArtifacts(parent));
+            for (ResolvedDependency childResolvedDependency : getChildren()) {
+                for (ResolvedDependency childParent : childResolvedDependency.getParents()) {
+                    allArtifacts.addAll(childResolvedDependency.getAllArtifacts(childParent));
+                }
+            }
+            allArtifactsCache.put(parent, allArtifacts);
+        }
+        return allArtifactsCache.get(parent);
+    }
+
+    public Set<ResolvedDependency> getParents() {
+        return parents;
+    }
+
+    public String toString() {
+        return name + ";" + getConfiguration();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultResolvedDependency that = (DefaultResolvedDependency) o;
+        return id.equals(that.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    public void addChild(DefaultResolvedDependency child) {
+        children.add(child);
+        child.parents.add(this);
+    }
+
+    public void addParentSpecificArtifacts(ResolvedDependency parent, Set<ResolvedArtifact> artifacts) {
+        Set<ResolvedArtifact> parentArtifacts = this.parentArtifacts.get(parent);
+        if (parentArtifacts == null) {
+            parentArtifacts = new TreeSet<ResolvedArtifact>(new ResolvedArtifactComparator());
+            this.parentArtifacts.put(parent, parentArtifacts);
+        }
+        parentArtifacts.addAll(artifacts);
+        moduleArtifacts.addAll(artifacts);
+    }
+
+    public void addModuleArtifact(ResolvedArtifact artifact) {
+        moduleArtifacts.add(artifact);
+    }
+
+    private static class ResolvedArtifactComparator implements Comparator<ResolvedArtifact> {
+        public int compare(ResolvedArtifact artifact1, ResolvedArtifact artifact2) {
+            int diff = artifact1.getName().compareTo(artifact2.getName());
+            if (diff != 0) {
+                return diff;
+            }
+            diff = ObjectUtils.compare(artifact1.getClassifier(), artifact2.getClassifier());
+            if (diff != 0) {
+                return diff;
+            }
+            diff = artifact1.getExtension().compareTo(artifact2.getExtension());
+            if (diff != 0) {
+                return diff;
+            }
+            diff = artifact1.getType().compareTo(artifact2.getType());
+            if (diff != 0) {
+                return diff;
+            }
+            // Use an arbitrary ordering when the artifacts have the same public attributes
+            return artifact1.hashCode() - artifact2.hashCode();
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java
new file mode 100644
index 0000000..4bb5c77
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/PlexusLoggerAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import org.codehaus.plexus.logging.Logger;
+
+/**
+ * @author Hans Dockter
+ */
+public class PlexusLoggerAdapter implements Logger {
+    org.slf4j.Logger logger;
+
+    public PlexusLoggerAdapter(org.slf4j.Logger logger) {
+        this.logger = logger;
+    }
+
+    public void debug(String s) {
+        logger.debug(s);
+    }
+
+    public void debug(String s, Throwable throwable) {
+        logger.debug(s, throwable);
+    }
+
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    public void info(String s) {
+        logger.info(s);
+    }
+
+    public void info(String s, Throwable throwable) {
+        logger.info(s, throwable);
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public void warn(String s) {
+        logger.warn(s);
+    }
+
+    public void warn(String s, Throwable throwable) {
+        logger.warn(s, throwable);
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
+    }
+
+    public void error(String s) {
+        logger.error(s);
+    }
+
+    public void error(String s, Throwable throwable) {
+        logger.error(s, throwable);
+    }
+
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public void fatalError(String s) {
+        logger.error(s);
+    }
+
+    public void fatalError(String s, Throwable throwable) {
+        logger.error(s, throwable);
+    }
+
+    public boolean isFatalErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public Logger getChildLogger(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getThreshold() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getName() {
+        return logger.getName();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
new file mode 100644
index 0000000..6fd0bb3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.dsl
+
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.dsl.ArtifactHandler
+import org.gradle.util.ConfigureUtil
+import org.gradle.util.GUtil
+import org.gradle.api.internal.notations.api.NotationParser
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultArtifactHandler implements ArtifactHandler {
+
+    ConfigurationContainer configurationContainer
+    NotationParser<PublishArtifact> publishArtifactFactory
+
+    def DefaultArtifactHandler(ConfigurationContainer configurationContainer, NotationParser<PublishArtifact> publishArtifactFactory) {
+        this.configurationContainer = configurationContainer;
+        this.publishArtifactFactory = publishArtifactFactory;
+    }
+
+    private PublishArtifact pushArtifact(org.gradle.api.artifacts.Configuration configuration, Object notation, Closure configureClosure) {
+        PublishArtifact publishArtifact = publishArtifactFactory.parseNotation(notation)
+        configuration.artifacts.add(publishArtifact)
+        ConfigureUtil.configure(configureClosure, publishArtifact)
+        return publishArtifact
+    }
+
+    PublishArtifact add(String configurationName, Object artifactNotation) {
+        return pushArtifact(configurationContainer.getByName(configurationName), artifactNotation, null)
+    }
+
+    PublishArtifact add(String configurationName, Object artifactNotation, Closure configureClosure) {
+        return pushArtifact(configurationContainer.getByName(configurationName), artifactNotation, configureClosure)
+    }
+
+    public def methodMissing(String name, args) {
+        Configuration configuration = configurationContainer.findByName(name)
+        if (configuration == null) {
+            if (!getMetaClass().respondsTo(this, name, args.size())) {
+                throw new MissingMethodException(name, this.getClass(), args);
+            }
+        }
+        Object[] normalizedArgs = GUtil.flatten(args as List, false)
+        if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
+            return pushArtifact(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
+        } 
+        args.each {notation ->
+            pushArtifact(configuration, notation, null)
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
new file mode 100644
index 0000000..08b7dc9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
@@ -0,0 +1,140 @@
+/*
+ * 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.dsl;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.tools.ant.Task;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.TopLevelNotationParser;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPublishArtifactFactory implements NotationParser<PublishArtifact>, TopLevelNotationParser {
+    private final Instantiator instantiator;
+    private final DependencyMetaDataProvider metaDataProvider;
+    private final NotationParser<PublishArtifact> delegate;
+
+    public DefaultPublishArtifactFactory(Instantiator instantiator, DependencyMetaDataProvider metaDataProvider) {
+        this.instantiator = instantiator;
+        this.metaDataProvider = metaDataProvider;
+        FileNotationParser fileParser = new FileNotationParser();
+        delegate = new NotationParserBuilder<PublishArtifact>()
+                .resultingType(PublishArtifact.class)
+                .parser(new ArchiveTaskNotationParser())
+                .parser(new FileMapNotationParser(fileParser))
+                .parser(fileParser)
+                .toComposite();
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    public PublishArtifact parseNotation(Object notation) {
+        return delegate.parseNotation(notation);
+    }
+
+    private class ArchiveTaskNotationParser extends TypedNotationParser<AbstractArchiveTask, PublishArtifact> {
+        private ArchiveTaskNotationParser() {
+            super(AbstractArchiveTask.class);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Instances of AbstractArchiveTask, e.g. jar.");
+        }
+
+        @Override
+        protected PublishArtifact parseType(AbstractArchiveTask notation) {
+            return instantiator.newInstance(ArchivePublishArtifact.class, notation);
+        }
+    }
+
+    private class FileMapNotationParser extends MapNotationParser<PublishArtifact> {
+        private final FileNotationParser fileParser;
+
+        private FileMapNotationParser(FileNotationParser fileParser) {
+            this.fileParser = fileParser;
+        }
+
+        protected PublishArtifact parseMap(@MapKey("file") File file) {
+            return fileParser.parseType(file);
+        }
+    }
+
+    private class FileNotationParser extends TypedNotationParser<File, PublishArtifact> {
+        private FileNotationParser() {
+            super(File.class);
+        }
+
+        @Override
+        protected PublishArtifact parseType(File file) {
+            Module module = metaDataProvider.getModule();
+
+            String name = file.getName();
+            String extension = "";
+            String classifier = "";
+            boolean done = false;
+
+            int startVersion = StringUtils.lastIndexOf(name, "-" + module.getVersion());
+            if (startVersion >= 0) {
+                int endVersion = startVersion + module.getVersion().length() + 1;
+                if (endVersion == name.length()) {
+                    name = name.substring(0, startVersion);
+                    done = true;
+                } else if (endVersion < name.length() && name.charAt(endVersion) == '-') {
+                    String tail = name.substring(endVersion + 1);
+                    name = name.substring(0, startVersion);
+                    classifier = StringUtils.substringBeforeLast(tail, ".");
+                    extension = StringUtils.substringAfterLast(tail, ".");
+                    done = true;
+                } else if (endVersion < name.length() && StringUtils.lastIndexOf(name, ".") == endVersion) {
+                    extension = name.substring(endVersion + 1);
+                    name = name.substring(0, startVersion);
+                    done = true;
+                }
+            }
+            if (!done) {
+                extension = StringUtils.substringAfterLast(name, ".");
+                name = StringUtils.substringBeforeLast(name, ".");
+            }
+            if (extension.length() == 0) {
+                extension = null;
+            }
+            if (classifier.length() == 0) {
+                classifier = null;
+            }
+
+            return instantiator.newInstance(DefaultPublishArtifact.class, name, extension, extension, classifier, null, file, new Task[0]);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheMetaData.java
new file mode 100644
index 0000000..e371646
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheMetaData.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import java.io.File;
+
+public interface ArtifactCacheMetaData {
+    public File getCacheDir();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java
new file mode 100644
index 0000000..56a86d1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolveResult.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+
+import java.io.File;
+
+public interface ArtifactResolveResult {
+    /**
+     * Returns the resolve failure, if any.
+     */
+    @Nullable
+    ArtifactResolveException getFailure();
+
+    /**
+     * @throws ArtifactResolveException If the resolution was unsuccessful.
+     */
+    File getFile() throws ArtifactResolveException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolver.java
new file mode 100644
index 0000000..30ae67f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ArtifactResolver.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public interface ArtifactResolver {
+    /**
+     * Resolves the given artifact. Any failures are packaged up in the result.
+     */
+    ArtifactResolveResult resolve(Artifact artifact);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenArtifactResolveResult.java
new file mode 100644
index 0000000..24c2bcc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenArtifactResolveResult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+
+import java.io.File;
+
+public class BrokenArtifactResolveResult implements ArtifactResolveResult {
+    private final ArtifactResolveException failure;
+
+    public BrokenArtifactResolveResult(ArtifactResolveException failure) {
+        this.failure = failure;
+    }
+
+    public ArtifactResolveException getFailure() {
+        return failure;
+    }
+
+    public File getFile() throws ArtifactResolveException {
+        throw failure;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenModuleVersionResolveResult.java
new file mode 100644
index 0000000..7b8288b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BrokenModuleVersionResolveResult.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class BrokenModuleVersionResolveResult implements ModuleVersionResolveResult {
+    private final ModuleVersionResolveException failure;
+
+    public BrokenModuleVersionResolveResult(ModuleVersionResolveException failure) {
+        this.failure = failure;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        return failure;
+    }
+
+    public ModuleRevisionId getId() throws ModuleVersionResolveException {
+        throw failure;
+    }
+
+    public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+        throw failure;
+    }
+
+    public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
+        throw failure;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
new file mode 100644
index 0000000..9fc7aac
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+
+public class CacheLockingArtifactDependencyResolver implements ArtifactDependencyResolver {
+    private final CacheLockingManager lockingManager;
+    private final ArtifactDependencyResolver resolver;
+
+    public CacheLockingArtifactDependencyResolver(CacheLockingManager lockingManager, ArtifactDependencyResolver resolver) {
+        this.lockingManager = lockingManager;
+        this.resolver = resolver;
+    }
+
+    public ResolvedConfiguration resolve(final ConfigurationInternal configuration) throws ResolveException {
+        return lockingManager.useCache(String.format("resolve %s", configuration), new Factory<ResolvedConfiguration>() {
+            public ResolvedConfiguration create() {
+                return resolver.resolve(configuration);
+            }
+        });
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
new file mode 100644
index 0000000..de55e64
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.cache.CacheAccess;
+import org.gradle.cache.PersistentIndexedCache;
+
+import java.io.File;
+
+/**
+ * Provides synchronized access to the artifact cache.
+ */
+ at ThreadSafe
+public interface CacheLockingManager extends ArtifactCacheMetaData, CacheAccess {
+    /**
+     * Creates a cache implementation that is managed by this locking manager. This method may be used at any time.
+     *
+     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
+     * In this instance, an exclusive lock will be held on the cache.
+     *
+     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
+     */
+    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
new file mode 100644
index 0000000..e75d408
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.internal.Factory;
+import org.gradle.cache.CacheBuilder;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.internal.FileLockManager;
+
+import java.io.File;
+
+public class DefaultCacheLockingManager implements CacheLockingManager {
+    public static final int CACHE_LAYOUT_VERSION = 13;
+    private final PersistentCache cache;
+
+    public DefaultCacheLockingManager(CacheRepository cacheRepository) {
+        cache = cacheRepository
+                .store(String.format("artifacts-%d", CACHE_LAYOUT_VERSION))
+                .withDisplayName("artifact cache")
+                .withVersionStrategy(CacheBuilder.VersionStrategy.SharedCache)
+                .withLockMode(FileLockManager.LockMode.None) // Don't need to lock anything until we use the caches
+                .open();
+    }
+
+    public File getCacheDir() {
+        return cache.getBaseDir();
+    }
+
+    public void longRunningOperation(String operationDisplayName, final Runnable action) {
+        cache.longRunningOperation(operationDisplayName, action);
+    }
+
+    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+        return cache.useCache(operationDisplayName, action);
+    }
+
+    public void useCache(String operationDisplayName, Runnable action) {
+        cache.useCache(operationDisplayName, action);
+    }
+
+    public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
+        return cache.longRunningOperation(operationDisplayName, action);
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
+        return cache.createCache(cacheFile, keyType, valueType);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
new file mode 100644
index 0000000..7d42d31
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.settings.IvySettings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyFactory implements IvyFactory {
+    private final Map<IvySettings, Ivy> instanceCache = new HashMap<IvySettings, Ivy>();
+    
+    public Ivy createIvy(IvySettings ivySettings) {
+        Ivy ivy = instanceCache.get(ivySettings);
+        if (ivy == null) {
+            ivy = Ivy.newInstance(ivySettings);
+            instanceCache.put(ivySettings, ivy);
+        }
+        return ivy;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
new file mode 100644
index 0000000..c8c0107
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.CachingDirectedGraphWalker;
+import org.gradle.api.internal.DirectedGraphWithEdgeValues;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder, LenientConfiguration {
+    private final ResolvedDependency root;
+    private final Configuration configuration;
+    private final Map<ModuleDependency, ResolvedDependency> firstLevelDependencies = new LinkedHashMap<ModuleDependency, ResolvedDependency>();
+    private final Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
+    private final Set<UnresolvedDependency> unresolvedDependencies = new LinkedHashSet<UnresolvedDependency>();
+    private final CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact> walker
+            = new CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact>(new ResolvedDependencyArtifactsGraph());
+
+    public DefaultLenientConfiguration(Configuration configuration, ResolvedDependency root) {
+        this.configuration = configuration;
+        this.root = root;
+    }
+
+    public boolean hasError() {
+        return !unresolvedDependencies.isEmpty();
+    }
+
+    public void rethrowFailure() throws ResolveException {
+        if (!unresolvedDependencies.isEmpty()) {
+            List<Throwable> failures = new ArrayList<Throwable>();
+            for (UnresolvedDependency unresolvedDependency : unresolvedDependencies) {
+                failures.add(unresolvedDependency.getProblem());
+            }
+            throw new ResolveException(configuration, failures);
+        }
+    }
+
+    public Set<UnresolvedDependency> getUnresolvedModuleDependencies() {
+        return unresolvedDependencies;
+    }
+
+    public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+        return artifacts;
+    }
+
+    public ResolvedDependency getRoot() {
+        return root;
+    }
+
+    public void addFirstLevelDependency(ModuleDependency moduleDependency, ResolvedDependency refersTo) {
+        firstLevelDependencies.put(moduleDependency, refersTo);
+    }
+
+    public void addArtifact(ResolvedArtifact artifact) {
+        artifacts.add(artifact);
+    }
+
+    public void addUnresolvedDependency(UnresolvedDependency unresolvedDependency) {
+        unresolvedDependencies.add(unresolvedDependency);
+    }
+
+    public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+        return root.getChildren();
+    }
+
+    public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) {
+        Set<ResolvedDependency> matches = new LinkedHashSet<ResolvedDependency>();
+        for (Map.Entry<ModuleDependency, ResolvedDependency> entry : firstLevelDependencies.entrySet()) {
+            if (dependencySpec.isSatisfiedBy(entry.getKey())) {
+                matches.add(entry.getValue());
+            }
+        }
+        return matches;
+    }
+
+    public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+        return getFiles(dependencySpec, new LenientArtifactToFileResolver());
+    }
+    
+    public Set<File> getFilesStrict(Spec<? super Dependency> dependencySpec) {
+        return getFiles(dependencySpec, new ArtifactFileResolver());
+    }
+
+    private Set<File> getFiles(Spec<? super Dependency> dependencySpec, ArtifactFileResolver artifactFileResolver) {
+        Set<ResolvedDependency> firstLevelModuleDependencies = getFirstLevelModuleDependencies(dependencySpec);
+
+        Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
+
+        for (ResolvedDependency resolvedDependency : firstLevelModuleDependencies) {
+            artifacts.addAll(resolvedDependency.getParentArtifacts(root));
+            walker.add(resolvedDependency);
+        }
+
+        artifacts.addAll(walker.findValues());
+
+        Set<File> files = new LinkedHashSet<File>();
+        for (ResolvedArtifact artifact : artifacts) {
+            File depFile = artifactFileResolver.getFile(artifact);
+            if (depFile != null) {
+                files.add(depFile);
+            }
+        }
+        return files;
+    }
+
+    private static class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
+        public void getNodeValues(ResolvedDependency node, Collection<ResolvedArtifact> values,
+                                  Collection<ResolvedDependency> connectedNodes) {
+            connectedNodes.addAll(node.getChildren());
+        }
+
+        public void getEdgeValues(ResolvedDependency from, ResolvedDependency to,
+                                  Collection<ResolvedArtifact> values) {
+            values.addAll(to.getParentArtifacts(from));
+        }
+    }
+
+    private static class ArtifactFileResolver {
+        public File getFile(ResolvedArtifact artifact) {
+            return artifact.getFile();
+        }
+    }
+    
+    private static class LenientArtifactToFileResolver extends ArtifactFileResolver {
+        public File getFile(ResolvedArtifact artifact) {
+            try {
+                return super.getFile(artifact);
+            } catch (ArtifactResolveException e) {
+                return null;
+            }
+        }
+    }    
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
new file mode 100644
index 0000000..5ee98a5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.api.artifacts.*;
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.Set;
+
+public class DefaultResolvedConfiguration implements ResolvedConfiguration {
+    private final DefaultLenientConfiguration configuration;
+
+    public DefaultResolvedConfiguration(DefaultLenientConfiguration configuration) {
+        this.configuration = configuration;
+    }
+
+    public boolean hasError() {
+        return configuration.hasError();
+    }
+
+    public void rethrowFailure() throws ResolveException {
+        configuration.rethrowFailure();
+    }
+
+    public LenientConfiguration getLenientConfiguration() {
+        return configuration;
+    }
+
+    public Set<File> getFiles(Spec<? super Dependency> dependencySpec) throws ResolveException {
+        rethrowFailure();
+        return configuration.getFilesStrict(dependencySpec);
+    }
+
+    public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
+        rethrowFailure();
+        return configuration.getFirstLevelModuleDependencies();
+    }
+
+    public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
+        rethrowFailure();
+        return configuration.getFirstLevelModuleDependencies(dependencySpec);
+    }
+
+    public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+        rethrowFailure();
+        return configuration.getResolvedArtifacts();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
new file mode 100644
index 0000000..c4ea717
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.gradle.internal.Factory;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultSettingsConverter implements SettingsConverter {
+    private final Factory<IvySettings> settingsFactory;
+    private IvySettings publishSettings;
+    private IvySettings resolveSettings;
+
+    public DefaultSettingsConverter(Factory<IvySettings> settingsFactory) {
+        this.settingsFactory = settingsFactory;
+        Message.setDefaultLogger(new IvyLoggingAdaper());
+    }
+
+    public IvySettings convertForPublish(List<DependencyResolver> publishResolvers) {
+        if (publishSettings == null) {
+            publishSettings = settingsFactory.create();
+        } else {
+            publishSettings.getResolvers().clear();
+        }
+        for (DependencyResolver dependencyResolver : publishResolvers) {
+            dependencyResolver.setSettings(publishSettings);
+        }
+        return publishSettings;
+    }
+
+    public IvySettings convertForResolve(DependencyResolver defaultResolver, List<DependencyResolver> resolvers) {
+        if (resolveSettings == null) {
+            resolveSettings = settingsFactory.create();
+        } else {
+            resolveSettings.getResolvers().clear();
+        }
+
+        resolveSettings.addResolver(defaultResolver);
+        resolveSettings.setDefaultResolver(defaultResolver.getName());
+        
+        for (DependencyResolver resolver : resolvers) {
+            resolveSettings.addResolver(resolver);
+        }
+        return resolveSettings;
+    }
+
+    public IvySettings getForResolve() {
+        if (resolveSettings == null) {
+            resolveSettings = settingsFactory.create();
+        }
+        return resolveSettings;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
new file mode 100755
index 0000000..fbc894b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.api.artifacts.UnresolvedDependency;
+
+public class DefaultUnresolvedDependency implements UnresolvedDependency {
+    private final String id;
+    private final Throwable problem;
+
+    public DefaultUnresolvedDependency(String id, Throwable problem) {
+        this.id = id;
+        this.problem = problem;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Throwable getProblem() {
+        return problem;
+    }
+
+    public String toString() {
+        return id;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java
new file mode 100755
index 0000000..f95d9b9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+
+/**
+ * Resolves a dependency to the meta-data for a module.
+ */
+public interface DependencyToModuleResolver {
+    /**
+     * Resolves the given dependency to a module version id. Failures are packaged up in the returned result.
+     *
+     * @return null if not found.
+     */
+    ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
new file mode 100755
index 0000000..da67224
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+
+/**
+ * Resolves a dependency to the id for a module.
+ */
+public interface DependencyToModuleVersionIdResolver {
+    /**
+     * Resolves the given dependency to a module version id. Note that failures are packaged up into the result.
+     */
+    ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
new file mode 100644
index 0000000..39da9b3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.Set;
+
+public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependencyResolver {
+    private final ArtifactDependencyResolver dependencyResolver;
+
+    public ErrorHandlingArtifactDependencyResolver(ArtifactDependencyResolver dependencyResolver) {
+        this.dependencyResolver = dependencyResolver;
+    }
+
+    public ResolvedConfiguration resolve(final ConfigurationInternal configuration) {
+        final ResolvedConfiguration resolvedConfiguration;
+        try {
+            resolvedConfiguration = dependencyResolver.resolve(configuration);
+        } catch (final Throwable e) {
+            return new BrokenResolvedConfiguration(e, configuration);
+        }
+        return new ErrorHandlingResolvedConfiguration(resolvedConfiguration, configuration);
+    }
+
+    private static ResolveException wrapException(Throwable e, Configuration configuration) {
+        if (e instanceof ResolveException) {
+            return (ResolveException) e;
+        }
+        return new ResolveException(configuration, e);
+    }
+
+    private static class ErrorHandlingResolvedConfiguration implements ResolvedConfiguration {
+        private final ResolvedConfiguration resolvedConfiguration;
+        private final Configuration configuration;
+
+        public ErrorHandlingResolvedConfiguration(ResolvedConfiguration resolvedConfiguration,
+                                                  Configuration configuration) {
+            this.resolvedConfiguration = resolvedConfiguration;
+            this.configuration = configuration;
+        }
+
+        public boolean hasError() {
+            return resolvedConfiguration.hasError();
+        }
+
+        public LenientConfiguration getLenientConfiguration() {
+            return resolvedConfiguration.getLenientConfiguration();
+        }
+
+        public void rethrowFailure() throws ResolveException {
+            try {
+                resolvedConfiguration.rethrowFailure();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<File> getFiles(Spec<? super Dependency> dependencySpec) throws ResolveException {
+            try {
+                return resolvedConfiguration.getFiles(dependencySpec);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
+            try {
+                return resolvedConfiguration.getFirstLevelModuleDependencies();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
+            try {
+                return resolvedConfiguration.getFirstLevelModuleDependencies(dependencySpec);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+            try {
+                return resolvedConfiguration.getResolvedArtifacts();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+    }
+
+    private static class BrokenResolvedConfiguration implements ResolvedConfiguration {
+        private final Throwable e;
+        private final Configuration configuration;
+
+        public BrokenResolvedConfiguration(Throwable e, Configuration configuration) {
+            this.e = e;
+            this.configuration = configuration;
+        }
+
+        public boolean hasError() {
+            return true;
+        }
+
+        public LenientConfiguration getLenientConfiguration() {
+            throw wrapException(e, configuration);
+        }
+
+        public void rethrowFailure() throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<File> getFiles(Spec<? super Dependency> dependencySpec) throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisher.java
new file mode 100644
index 0000000..ae203e5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisher.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+
+import java.io.File;
+
+public class ErrorHandlingArtifactPublisher implements ArtifactPublisher {
+    private final ArtifactPublisher artifactPublisher;
+
+    public ErrorHandlingArtifactPublisher(ArtifactPublisher artifactPublisher) {
+        this.artifactPublisher = artifactPublisher;
+    }
+
+    public void publish(ConfigurationInternal configuration, File descriptorDestination) {
+        try {
+            artifactPublisher.publish(configuration, descriptorDestination);
+        } catch (Throwable e) {
+            throw new PublishException(String.format("Could not publish %s.", configuration), e);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/FileBackedArtifactResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/FileBackedArtifactResolveResult.java
new file mode 100644
index 0000000..fdc525c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/FileBackedArtifactResolveResult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+
+import java.io.File;
+
+public class FileBackedArtifactResolveResult implements ArtifactResolveResult {
+    private final File file;
+
+    public FileBackedArtifactResolveResult(File file) {
+        this.file = file;
+    }
+
+    public ArtifactResolveException getFailure() {
+        return null;
+    }
+
+    public File getFile() throws ArtifactResolveException {
+        return file;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
new file mode 100644
index 0000000..02624f5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
@@ -0,0 +1,94 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class IvyBackedArtifactPublisher implements ArtifactPublisher {
+    private final SettingsConverter settingsConverter;
+    private final ModuleDescriptorConverter publishModuleDescriptorConverter;
+    private final ModuleDescriptorConverter fileModuleDescriptorConverter;
+    private final IvyFactory ivyFactory;
+    private final IvyDependencyPublisher dependencyPublisher;
+    private final ResolverProvider resolverProvider;
+
+    public IvyBackedArtifactPublisher(ResolverProvider resolverProvider,
+                                      SettingsConverter settingsConverter,
+                                      ModuleDescriptorConverter publishModuleDescriptorConverter,
+                                      ModuleDescriptorConverter fileModuleDescriptorConverter,
+                                      IvyFactory ivyFactory,
+                                      IvyDependencyPublisher dependencyPublisher) {
+        this.resolverProvider = resolverProvider;
+        this.settingsConverter = settingsConverter;
+        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
+        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
+        this.ivyFactory = ivyFactory;
+        this.dependencyPublisher = dependencyPublisher;
+    }
+
+    private Ivy ivyForPublish(List<DependencyResolver> publishResolvers) {
+        return ivyFactory.createIvy(settingsConverter.convertForPublish(publishResolvers));
+    }
+
+    public void publish(ConfigurationInternal configuration, File descriptorDestination) throws PublishException {
+        List<DependencyResolver> publishResolvers = resolverProvider.getResolvers();
+        Ivy ivy = ivyForPublish(publishResolvers);
+        Set<Configuration> configurationsToPublish = configuration.getHierarchy();
+        Set<String> confs = Configurations.getNames(configurationsToPublish, false);
+        writeDescriptorFile(descriptorDestination, configurationsToPublish, configuration.getModule());
+        dependencyPublisher.publish(
+                confs,
+                publishResolvers,
+                publishModuleDescriptorConverter.convert(configurationsToPublish, configuration.getModule()),
+                descriptorDestination,
+                ivy.getPublishEngine());
+    }
+
+    private void writeDescriptorFile(File descriptorDestination, Set<Configuration> configurationsToPublish, Module module) {
+        if (descriptorDestination == null) {
+            return;
+        }
+        assert configurationsToPublish.size() > 0;
+        Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
+        ModuleDescriptor moduleDescriptor = fileModuleDescriptorConverter.convert(allConfigurations, module);
+        try {
+            moduleDescriptor.toIvyFile(descriptorDestination);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyLoggingAdaper.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactory.java
new file mode 100644
index 0000000..11f49c3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+
+public class IvySettingsFactory implements Factory<IvySettings> {
+    private final ArtifactCacheMetaData cacheMetaData;
+
+    public IvySettingsFactory(ArtifactCacheMetaData cacheMetaData) {
+        this.cacheMetaData = cacheMetaData;
+    }
+
+    public IvySettings create() {
+        IvySettings ivySettings = new IvySettings();
+        ivySettings.setDefaultCache(new File(cacheMetaData.getCacheDir(), "ivy"));
+        ivySettings.setDefaultCacheIvyPattern(ArtifactRepositoryContainer.DEFAULT_CACHE_IVY_PATTERN);
+        ivySettings.setDefaultCacheArtifactPattern(ArtifactRepositoryContainer.DEFAULT_CACHE_ARTIFACT_PATTERN);
+        ivySettings.setVariable("ivy.log.modules.in.use", "false");
+
+        return ivySettings;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
new file mode 100644
index 0000000..856cd82
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.Module;
+import org.gradle.util.GUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class IvyUtil {
+
+    public static ModuleRevisionId createModuleRevisionId(Module module) {
+        return createModuleRevisionId(module, new HashMap<String, String>());
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(Module module, Map<String, String> extraAttributes) {
+        return createModuleRevisionId(module.getGroup(), module.getName(), module.getVersion(), extraAttributes);
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(Dependency dependency) {
+        return createModuleRevisionId(dependency, new HashMap<String, String>());
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(Dependency dependency, Map<String, String> extraAttributes) {
+        return createModuleRevisionId(dependency.getGroup(), dependency.getName(), dependency.getVersion(), extraAttributes);
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(String group, String name, String version, Map<String, String> extraAttributes) {
+        return ModuleRevisionId.newInstance(emptyStringIfNull(group), name, emptyStringIfNull(version), extraAttributes);
+    }
+
+    private static String emptyStringIfNull(String value) {
+        return GUtil.elvis(value, "");
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
new file mode 100644
index 0000000..9501319
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ModuleDescriptorConverter {
+    ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java
new file mode 100644
index 0000000..374166d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionIdResolveResult.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.Nullable;
+
+public interface ModuleVersionIdResolveResult {
+    /**
+     * Returns the resolve failure, if any.
+     */
+    @Nullable
+    ModuleVersionResolveException getFailure();
+
+    /**
+     * Returns the id of this module version.
+     *
+     * @throws ModuleVersionResolveException If resolution was unsuccessful and the id is unknown.
+     */
+    ModuleRevisionId getId() throws ModuleVersionResolveException;
+
+    /**
+     * Resolves the meta-data for this module version, if required. Failures are packaged up in the result.
+     */
+    ModuleVersionResolveResult resolve();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
new file mode 100755
index 0000000..9735431
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class ModuleVersionNotFoundException extends ModuleVersionResolveException {
+    public ModuleVersionNotFoundException(ModuleRevisionId id) {
+        super(String.format("Could not find group:%s, module:%s, version:%s.", id.getOrganisation(), id.getName(), id.getRevision()));
+    }
+
+    public ModuleVersionNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
new file mode 100644
index 0000000..ece8d64
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.AbstractMultiCauseException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.internal.UncheckedException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Formatter;
+import java.util.List;
+
+ at Contextual
+public class ModuleVersionResolveException extends AbstractMultiCauseException {
+    private final List<List<ModuleRevisionId>> paths = new ArrayList<List<ModuleRevisionId>>();
+
+    public ModuleVersionResolveException(String message) {
+        super(message);
+    }
+
+    public ModuleVersionResolveException(ModuleRevisionId id, Throwable cause) {
+        super(format(id), cause);
+    }
+
+    public ModuleVersionResolveException(ModuleRevisionId id, Iterable<? extends Throwable> causes) {
+        super(format(id), causes);
+    }
+
+    public ModuleVersionResolveException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    private static String format(ModuleRevisionId id) {
+        return String.format("Could not resolve group:%s, module:%s, version:%s.", id.getOrganisation(), id.getName(), id.getRevision());
+    }
+
+    /**
+     * Creates a copy of this exception, with the given incoming paths.
+     */
+    public ModuleVersionResolveException withIncomingPaths(Collection<? extends List<ModuleRevisionId>> paths) {
+        ModuleVersionResolveException copy = createCopy(super.getMessage());
+        copy.paths.addAll(paths);
+        copy.initCauses(getCauses());
+        copy.setStackTrace(getStackTrace());
+        return copy;
+    }
+
+    @Override
+    public String getMessage() {
+        if (paths.isEmpty()) {
+            return super.getMessage();
+        }
+        Formatter formatter = new Formatter();
+        formatter.format("%s%nRequired by:", super.getMessage());
+        for (List<ModuleRevisionId> path : paths) {
+            formatter.format("%n    %s", toString(path.get(0)));
+            for (int i = 1; i < path.size(); i++) {
+                formatter.format(" > %s", toString(path.get(i)));
+            }
+        }
+        return formatter.toString();
+    }
+
+    private String toString(ModuleRevisionId moduleRevisionId) {
+        return String.format("%s:%s:%s", moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    }
+
+    protected ModuleVersionResolveException createCopy(String message) {
+        try {
+            return getClass().getConstructor(String.class).newInstance(message);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
new file mode 100644
index 0000000..5168767
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.Nullable;
+
+public interface ModuleVersionResolveResult {
+    /**
+     * Returns the id of this module version.
+     *
+     * @throws ModuleVersionResolveException If resolution was unsuccessful and the id is unknown.
+     */
+    ModuleRevisionId getId() throws ModuleVersionResolveException;
+
+    /**
+     * Returns the descriptor for this module version.
+     *
+     * @throws ModuleVersionResolveException If resolution was unsuccessful and the descriptor is not available.
+     */
+    ModuleDescriptor getDescriptor() throws ModuleVersionResolveException;
+
+    /**
+     * Returns the resolve failure, if any.
+     */
+    @Nullable
+    ModuleVersionResolveException getFailure();
+
+    /**
+     * Returns the artifact resolver for this module.
+     *
+     * @throws ModuleVersionResolveException If resolution was unsuccessful and artifacts are not available.
+     */
+    ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java
new file mode 100644
index 0000000..5c96dcb
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
+import org.gradle.api.internal.file.FileSource;
+
+import java.io.File;
+
+public class ResolvedArtifactFactory {
+    private final CacheLockingManager lockingManager;
+
+    public ResolvedArtifactFactory(CacheLockingManager lockingManager) {
+        this.lockingManager = lockingManager;
+    }
+
+    public ResolvedArtifact create(ResolvedDependency owner, final Artifact artifact, final ArtifactResolver resolver) {
+        return new DefaultResolvedArtifact(owner, artifact, new FileSource() {
+            public File get() {
+                return lockingManager.useCache(String.format("download %s", artifact), new Factory<File>() {
+                    public File create() {
+                        return resolver.resolve(artifact).getFile();
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedConfigurationBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedConfigurationBuilder.java
new file mode 100644
index 0000000..1ecee6c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedConfigurationBuilder.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.artifacts.UnresolvedDependency;
+
+public interface ResolvedConfigurationBuilder {
+    void addArtifact(ResolvedArtifact artifact);
+
+    void addFirstLevelDependency(ModuleDependency moduleDependency, ResolvedDependency dependency);
+
+    void addUnresolvedDependency(UnresolvedDependency unresolvedDependency);
+
+    ResolvedDependency getRoot();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
new file mode 100644
index 0000000..f0e57ea
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
@@ -0,0 +1,85 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class SelfResolvingDependencyResolver implements ArtifactDependencyResolver {
+    private final ArtifactDependencyResolver resolver;
+
+    public SelfResolvingDependencyResolver(ArtifactDependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public ArtifactDependencyResolver getResolver() {
+        return resolver;
+    }
+
+    public ResolvedConfiguration resolve(final ConfigurationInternal configuration) {
+        final ResolvedConfiguration resolvedConfiguration = resolver.resolve(configuration);
+        final Set<Dependency> dependencies = configuration.getAllDependencies();
+
+        return new ResolvedConfiguration() {
+            private final CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
+
+            public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+                Set<File> files = new LinkedHashSet<File>();
+
+                Set<Dependency> selectedDependencies = CollectionUtils.filter(dependencies, dependencySpec);
+                for (Dependency dependency : selectedDependencies) {
+                    resolveContext.add(dependency);
+                }
+                files.addAll(resolveContext.resolve().getFiles());
+                files.addAll(resolvedConfiguration.getFiles(dependencySpec));
+                return files;
+            }
+
+            public Set<ResolvedArtifact> getResolvedArtifacts() {
+                return resolvedConfiguration.getResolvedArtifacts();
+            }
+
+            public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+                return resolvedConfiguration.getFirstLevelModuleDependencies();
+            }
+
+            public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
+                return resolvedConfiguration.getFirstLevelModuleDependencies(dependencySpec);
+            }
+
+            public boolean hasError() {
+                return resolvedConfiguration.hasError();
+            }
+
+            public LenientConfiguration getLenientConfiguration() {
+                return resolvedConfiguration.getLenientConfiguration();
+            }
+
+            public void rethrowFailure() throws GradleException {
+                resolvedConfiguration.rethrowFailure();
+            }
+        };
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
new file mode 100644
index 0000000..d532b73
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public interface SettingsConverter {
+    String LOOPBACK_RESOLVER_NAME = "main";
+
+    IvySettings convertForPublish(List<DependencyResolver> publishResolvers);
+
+    IvySettings convertForResolve(DependencyResolver defaultResolver, List<DependencyResolver> resolvers);
+
+    IvySettings getForResolve();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
new file mode 100644
index 0000000..c46a926
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements ArtifactDependencyResolver {
+    private final ResolvedConfiguration emptyConfig = new ResolvedConfiguration() {
+        public boolean hasError() {
+            return false;
+        }
+
+        public LenientConfiguration getLenientConfiguration() {
+            return new LenientConfiguration() {
+                public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) {
+                    return Collections.emptySet();
+                }
+
+                public Set<UnresolvedDependency> getUnresolvedModuleDependencies() {
+                    return Collections.emptySet();
+                }
+
+                public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+                    return Collections.emptySet();
+                }
+            };
+        }
+
+        public void rethrowFailure() throws ResolveException {
+        }
+
+        public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
+            return Collections.emptySet();
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+            return Collections.emptySet();
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException {
+            return Collections.emptySet();
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() {
+            return Collections.emptySet();
+        }
+    };
+    private final ArtifactDependencyResolver dependencyResolver;
+
+    public ShortcircuitEmptyConfigsArtifactDependencyResolver(ArtifactDependencyResolver dependencyResolver) {
+        this.dependencyResolver = dependencyResolver;
+    }
+
+    public ResolvedConfiguration resolve(ConfigurationInternal configuration) {
+        if (configuration.getAllDependencies().isEmpty()) {
+            return emptyConfig;
+        }
+        return dependencyResolver.resolve(configuration);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
new file mode 100644
index 0000000..e21c923
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class VersionForcingDependencyToModuleResolver implements DependencyToModuleVersionIdResolver {
+    private final DependencyToModuleVersionIdResolver resolver;
+    private final Map<ModuleId, ModuleRevisionId> forcedModules = new HashMap<ModuleId, ModuleRevisionId>();
+
+    public VersionForcingDependencyToModuleResolver(DependencyToModuleVersionIdResolver resolver, Iterable<? extends ModuleVersionSelector> forcedModules) {
+        this.resolver = resolver;
+        for (ModuleVersionSelector forcedModule : forcedModules) {
+            ModuleId moduleId = new ModuleId(forcedModule.getGroup(), forcedModule.getName());
+            this.forcedModules.put(moduleId, new ModuleRevisionId(moduleId, forcedModule.getVersion()));
+        }
+    }
+
+    public ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
+        ModuleRevisionId newRevisionId = forcedModules.get(dependencyDescriptor.getDependencyId());
+        if (newRevisionId != null) {
+            return resolver.resolve(dependencyDescriptor.clone(newRevisionId));
+        }
+        return resolver.resolve(dependencyDescriptor);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
new file mode 100644
index 0000000..4aaebef
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
@@ -0,0 +1,76 @@
+/*
+ * 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.ivyservice.clientmodule;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactResolver;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveResult;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleResolver implements DependencyToModuleResolver {
+    private final DependencyToModuleResolver resolver;
+
+    public ClientModuleResolver(DependencyToModuleResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
+        final ModuleVersionResolveResult resolveResult = resolver.resolve(dependencyDescriptor);
+
+        if (resolveResult.getFailure() != null || !(dependencyDescriptor instanceof ClientModuleDependencyDescriptor)) {
+            return resolveResult;
+        }
+
+        ClientModuleDependencyDescriptor clientModuleDependencyDescriptor = (ClientModuleDependencyDescriptor) dependencyDescriptor;
+        ModuleDescriptor moduleDescriptor = clientModuleDependencyDescriptor.getTargetModule();
+
+        return new ClientModuleResolveResult(resolveResult, moduleDescriptor);
+    }
+
+    private static class ClientModuleResolveResult implements ModuleVersionResolveResult {
+        private final ModuleVersionResolveResult resolveResult;
+        private final ModuleDescriptor moduleDescriptor;
+
+        public ClientModuleResolveResult(ModuleVersionResolveResult resolveResult, ModuleDescriptor moduleDescriptor) {
+            this.resolveResult = resolveResult;
+            this.moduleDescriptor = moduleDescriptor;
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return null;
+        }
+
+        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+            return moduleDescriptor.getModuleRevisionId();
+        }
+
+        public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+            return moduleDescriptor;
+        }
+
+        public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
+            return resolveResult.getArtifactResolver();
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
new file mode 100644
index 0000000..caa5088
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.ivyservice.dynamicversions;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.util.TimeProvider;
+
+import java.io.Serializable;
+
+class DefaultCachedModuleResolution implements ModuleResolutionCache.CachedModuleResolution, Serializable {
+    private final ModuleRevisionId requestedVersion;
+    private final ModuleRevisionId resolvedVersion;
+    private final long ageMillis;
+
+    public DefaultCachedModuleResolution(ModuleRevisionId requestedVersion, ModuleResolutionCacheEntry entry, TimeProvider timeProvider) {
+        this.requestedVersion = requestedVersion;
+        this.resolvedVersion = ModuleRevisionId.decode(entry.encodedRevisionId);
+        ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
+    }
+
+    public ModuleRevisionId getRequestedVersion() {
+        return requestedVersion;
+    }
+
+    public ModuleRevisionId getResolvedVersion() {
+        return resolvedVersion;
+    }
+
+    public ResolvedModuleVersion getResolvedModule() {
+        return new DefaultResolvedModuleVersion(resolvedVersion);
+    }
+
+    public long getAgeMillis() {
+        return ageMillis;
+    }
+
+    public boolean isDynamicVersion() {
+        return !requestedVersion.equals(resolvedVersion);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java
new file mode 100644
index 0000000..706f4a1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultResolvedModuleVersion.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.ivyservice.dynamicversions;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+
+public class DefaultResolvedModuleVersion implements ResolvedModuleVersion {
+    private final ModuleRevisionId moduleRevisionId;
+
+    public DefaultResolvedModuleVersion(ModuleRevisionId moduleRevisionId) {
+        this.moduleRevisionId = moduleRevisionId;
+    }
+
+    public ModuleVersionIdentifier getId() {
+        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ForceChangeDependencyDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ForceChangeDependencyDescriptor.java
new file mode 100644
index 0000000..24ab2b1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ForceChangeDependencyDescriptor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.ivyservice.dynamicversions;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.GradleException;
+
+import java.lang.reflect.Field;
+
+public class ForceChangeDependencyDescriptor {
+    public static DependencyDescriptor forceChangingFlag(DependencyDescriptor original, boolean isChanging) {
+        if (original.isChanging() == isChanging) {
+            return original;
+        }
+
+        DependencyDescriptor forcedChanging = original.clone(original.getDependencyRevisionId());
+        try {
+            Field field = DefaultDependencyDescriptor.class.getDeclaredField("isChanging");
+            field.setAccessible(true);
+            field.set(forcedChanging, isChanging);
+            return forcedChanging;
+        } catch (Exception e) {
+            throw new GradleException("Could not get cache options from AbstractResolver", e);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
new file mode 100644
index 0000000..7f979aa
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCache.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.ivyservice.dynamicversions;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+
+public interface ModuleResolutionCache {
+
+    void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion, ModuleRevisionId resolvedVersion);
+
+    CachedModuleResolution getCachedModuleResolution(ModuleVersionRepository repository, ModuleRevisionId dynamicVersion);
+
+    interface CachedModuleResolution {
+        ModuleRevisionId getRequestedVersion();
+        ModuleRevisionId getResolvedVersion();
+        ResolvedModuleVersion getResolvedModule();
+
+        boolean isDynamicVersion();
+
+        long getAgeMillis();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
new file mode 100644
index 0000000..e274041
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.ivyservice.dynamicversions;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.util.TimeProvider;
+
+import java.io.Serializable;
+
+class ModuleResolutionCacheEntry implements Serializable {
+    public String encodedRevisionId;
+    public long createTimestamp;
+
+    ModuleResolutionCacheEntry(ModuleRevisionId revisionId, TimeProvider timeProvider) {
+        this.encodedRevisionId = revisionId == null ? null : revisionId.encodeToString();
+        this.createTimestamp = timeProvider.getCurrentTime();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
new file mode 100644
index 0000000..1bd650c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 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.ivyservice.dynamicversions;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.util.TimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class SingleFileBackedModuleResolutionCache implements ModuleResolutionCache {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SingleFileBackedModuleResolutionCache.class);
+
+    private final TimeProvider timeProvider;
+    private final ArtifactCacheMetaData cacheMetadata;
+    private final CacheLockingManager cacheLockingManager;
+    private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> cache;
+
+    public SingleFileBackedModuleResolutionCache(ArtifactCacheMetaData cacheMetadata, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        this.timeProvider = timeProvider;
+        this.cacheLockingManager = cacheLockingManager;
+        this.cacheMetadata = cacheMetadata;
+    }
+
+    private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> getCache() {
+        if (cache == null) {
+            cache = initCache();
+        }
+        return cache;
+    }
+
+    private PersistentIndexedCache<RevisionKey, ModuleResolutionCacheEntry> initCache() {
+        File dynamicRevisionsFile = new File(cacheMetadata.getCacheDir(), "dynamic-revisions.bin");
+        return cacheLockingManager.createCache(dynamicRevisionsFile, RevisionKey.class, ModuleResolutionCacheEntry.class);
+    }
+
+    public void cacheModuleResolution(ModuleVersionRepository repository, ModuleRevisionId requestedVersion, ModuleRevisionId resolvedVersion) {
+        if (requestedVersion.equals(resolvedVersion)) {
+            return;
+        }
+
+        LOGGER.debug("Caching resolved revision in dynamic revision cache: Will use '{}' for '{}'", resolvedVersion, requestedVersion);
+        getCache().put(createKey(repository, requestedVersion), createEntry(resolvedVersion));
+    }
+
+    public CachedModuleResolution getCachedModuleResolution(ModuleVersionRepository repository, ModuleRevisionId moduleId) {
+        ModuleResolutionCacheEntry moduleResolutionCacheEntry = getCache().get(createKey(repository, moduleId));
+        if (moduleResolutionCacheEntry == null) {
+            return null;
+        }
+        return new DefaultCachedModuleResolution(moduleId, moduleResolutionCacheEntry, timeProvider);
+    }
+
+    private RevisionKey createKey(ModuleVersionRepository repository, ModuleRevisionId revisionId) {
+        return new RevisionKey(repository, revisionId);
+    }
+
+    private ModuleResolutionCacheEntry createEntry(ModuleRevisionId revisionId) {
+        return new ModuleResolutionCacheEntry(revisionId, timeProvider);
+    }
+
+    private static class RevisionKey implements Serializable {
+        private final String resolverId;
+        private final String revisionId;
+
+        private RevisionKey(ModuleVersionRepository repository, ModuleRevisionId revision) {
+            this.resolverId = repository.getId();
+            this.revisionId = revision.encodeToString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || !(o instanceof RevisionKey)) {
+                return false;
+            }
+            RevisionKey other = (RevisionKey) o;
+            return resolverId.equals(other.resolverId) && revisionId.equals(other.revisionId);
+        }
+
+        @Override
+        public int hashCode() {
+            return resolverId.hashCode() ^ revisionId.hashCode();
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java
new file mode 100644
index 0000000..902334b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactNotFoundException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+public class ArtifactNotFoundException extends ArtifactResolveException {
+    public ArtifactNotFoundException(Artifact artifact) {
+        super(format(artifact));
+    }
+
+    private static String format(Artifact artifact) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Artifact '");
+        formatTo(artifact, builder);
+        builder.append("' not found.");
+        return builder.toString();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactOriginWithMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactOriginWithMetaData.java
new file mode 100644
index 0000000..0fc8894
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactOriginWithMetaData.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.repository.Resource;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+public class ArtifactOriginWithMetaData extends ArtifactOrigin {
+
+    private final ExternalResourceMetaData metaData;
+
+    public ArtifactOriginWithMetaData(Artifact artifact, Resource resource) {
+        this(artifact, resource.isLocal(), getMetaData(resource));
+    }
+    public ArtifactOriginWithMetaData(Artifact artifact, boolean isLocal, ExternalResourceMetaData metaData) {
+        super(artifact, isLocal, metaData.getLocation());
+        this.metaData = metaData;
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        return metaData;
+    }
+    
+    private static ExternalResourceMetaData getMetaData(Resource resource) {
+        if (resource instanceof ExternalResource) {
+            return ((ExternalResource) resource).getMetaData();
+        } else {
+            return new DefaultExternalResourceMetaData(resource.getName());
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java
new file mode 100644
index 0000000..778e9a7
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ArtifactResolveException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.util.GUtil;
+
+ at Contextual
+public class ArtifactResolveException extends GradleException {
+    public ArtifactResolveException(String message) {
+        super(message);
+    }
+
+    public ArtifactResolveException(Artifact artifact, Throwable cause) {
+        super(format(artifact, ""), cause);
+    }
+
+    public ArtifactResolveException(Artifact artifact, String message) {
+        super(format(artifact, message));
+    }
+
+    private static String format(Artifact artifact, String message) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Could not download artifact '");
+        formatTo(artifact, builder);
+        builder.append("'");
+        if (GUtil.isTrue(message)) {
+            builder.append(": ");
+            builder.append(message);
+        }
+        return builder.toString();
+    }
+
+    protected static void formatTo(Artifact artifact, StringBuilder builder) {
+        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
+        builder.append(moduleRevisionId.getOrganisation())
+                .append(":").append(moduleRevisionId.getName())
+                .append(":").append(moduleRevisionId.getRevision());
+        String classifier = artifact.getExtraAttribute("classifier");
+        if (GUtil.isTrue(classifier)) {
+            builder.append(":").append(classifier);
+        }
+        builder.append("@").append(artifact.getExt());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
new file mode 100644
index 0000000..16d45c4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.internal.Factory;
+
+/**
+ * A wrapper around a {@link ModuleVersionRepository} that handles locking/unlocking the cache.
+ */
+public class CacheLockingModuleVersionRepository implements ModuleVersionRepository {
+    private final ModuleVersionRepository repository;
+    private final CacheLockingManager cacheLockingManager;
+
+    public CacheLockingModuleVersionRepository(ModuleVersionRepository repository, CacheLockingManager cacheLockingManager) {
+        this.repository = repository;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    public String getId() {
+        return repository.getId();
+    }
+
+    public String getName() {
+        return repository.getName();
+    }
+
+    public boolean isLocal() {
+        return repository.isLocal();
+    }
+
+    public ModuleVersionDescriptor getDependency(final DependencyDescriptor dd) throws ModuleVersionResolveException {
+        return cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", dd, getId()), new Factory<ModuleVersionDescriptor>() {
+            public ModuleVersionDescriptor create() {
+                return repository.getDependency(dd);
+            }
+        });
+    }
+
+    public DownloadedArtifact download(final Artifact artifact) throws ArtifactResolveException {
+        return cacheLockingManager.longRunningOperation(String.format("Download %s using repository %s", artifact, getId()), new Factory<DownloadedArtifact>() {
+            public DownloadedArtifact create() {
+                return repository.download(artifact);
+            }
+        });
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
new file mode 100644
index 0000000..c8597d6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DefaultArtifactIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ForceChangeDependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
+import org.gradle.util.TimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class CachingModuleVersionRepository implements ModuleVersionRepository {
+    private static final Logger LOGGER = LoggerFactory.getLogger(CachingModuleVersionRepository.class);
+
+    private final ModuleResolutionCache moduleResolutionCache;
+    private final ModuleDescriptorCache moduleDescriptorCache;
+    private final CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex;
+    
+    private final CachePolicy cachePolicy;
+
+    private final ModuleVersionRepository delegate;
+    private final TimeProvider timeProvider;
+
+    public CachingModuleVersionRepository(ModuleVersionRepository delegate, ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
+                                          CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex,
+                                          CachePolicy cachePolicy, TimeProvider timeProvider) {
+        this.delegate = delegate;
+        this.moduleDescriptorCache = moduleDescriptorCache;
+        this.moduleResolutionCache = moduleResolutionCache;
+        this.artifactAtRepositoryCachedResolutionIndex = artifactAtRepositoryCachedResolutionIndex;
+        this.timeProvider = timeProvider;
+        this.cachePolicy = cachePolicy;
+    }
+
+    public String getId() {
+        return delegate.getId();
+    }
+
+    public String getName() {
+        return delegate.getName();
+    }
+
+    @Override
+    public String toString() {
+        return "Caching " + delegate.toString();
+    }
+
+    public boolean isLocal() {
+        return delegate.isLocal();
+    }
+
+    public ModuleVersionDescriptor getDependency(DependencyDescriptor dd) {
+        if (isLocal()) {
+            return delegate.getDependency(dd);
+        }
+
+        return findModule(dd);
+    }
+
+    public ModuleVersionDescriptor findModule(DependencyDescriptor requestedDependencyDescriptor) {
+        DependencyDescriptor resolvedDependencyDescriptor = maybeUseCachedDynamicVersion(delegate, requestedDependencyDescriptor);
+        CachedModuleLookup lookup = lookupModuleInCache(delegate, resolvedDependencyDescriptor);
+        if (lookup.wasFound) {
+            return lookup.module;
+        }
+        return resolveModule(resolvedDependencyDescriptor, requestedDependencyDescriptor);
+    }
+
+    private DependencyDescriptor maybeUseCachedDynamicVersion(ModuleVersionRepository repository, DependencyDescriptor original) {
+        ModuleRevisionId originalId = original.getDependencyRevisionId();
+        ModuleResolutionCache.CachedModuleResolution cachedModuleResolution = moduleResolutionCache.getCachedModuleResolution(repository, originalId);
+        if (cachedModuleResolution != null && cachedModuleResolution.isDynamicVersion()) {
+            ModuleVersionSelector selector = createModuleVersionSelector(originalId);
+            ModuleVersionIdentifier resolvedVersion = cachedModuleResolution.getResolvedModule() == null ? null : cachedModuleResolution.getResolvedModule().getId();
+            if (cachePolicy.mustRefreshDynamicVersion(selector, resolvedVersion, cachedModuleResolution.getAgeMillis())) {
+                LOGGER.debug("Resolved revision in dynamic revision cache is expired: will perform fresh resolve of '{}' in '{}'", selector, repository.getName());
+                return original;
+            } else {
+                LOGGER.debug("Found resolved revision in dynamic revision cache of '{}': Using '{}' for '{}'",
+                        new Object[] {repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId});
+                return original.clone(cachedModuleResolution.getResolvedVersion());
+            }
+        }
+        return original;
+    }
+
+    public CachedModuleLookup lookupModuleInCache(ModuleVersionRepository repository, DependencyDescriptor resolvedDependencyDescriptor) {
+        ModuleRevisionId resolvedModuleVersionId = resolvedDependencyDescriptor.getDependencyRevisionId();
+        ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(resolvedModuleVersionId);
+        ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.getCachedModuleDescriptor(repository, resolvedModuleVersionId);
+        if (cachedModuleDescriptor == null) {
+            return notFound();
+        }
+        if (cachedModuleDescriptor.isMissing()) {
+            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, null, cachedModuleDescriptor.getAgeMillis())) {
+                LOGGER.debug("Cached meta-data for missing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
+                return notFound();
+            }
+            LOGGER.debug("Detected non-existence of module '{}' in resolver cache '{}'", resolvedModuleVersionId, repository.getName());
+            return found(null);
+        }
+        if (cachedModuleDescriptor.isChangingModule() || resolvedDependencyDescriptor.isChanging()) {
+            if (cachePolicy.mustRefreshChangingModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), cachedModuleDescriptor.getAgeMillis())) {
+                expireArtifactsForChangingModule(repository, cachedModuleDescriptor.getModuleDescriptor());
+                LOGGER.debug("Cached meta-data for changing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
+                return notFound();
+            }
+            LOGGER.debug("Found cached version of changing module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
+        } else {
+            if (cachePolicy.mustRefreshModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), cachedModuleDescriptor.getAgeMillis())) {
+                LOGGER.debug("Cached meta-data for module must be refreshed: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
+                return notFound();
+            }
+        }
+
+        LOGGER.debug("Using cached module metadata for module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
+        // TODO:DAZ Could provide artifact metadata and file here from artifactFileStore (it's not needed currently)
+        ModuleVersionDescriptor cachedModule = new DefaultModuleVersionDescriptor(cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule());
+        return found(cachedModule);
+    }
+
+    private void expireArtifactsForChangingModule(ModuleVersionRepository repository, ModuleDescriptor descriptor) {
+        for (Artifact artifact : descriptor.getAllArtifacts()) {
+            artifactAtRepositoryCachedResolutionIndex.clear(new ArtifactAtRepositoryKey(repository, artifact.getId()));
+        }
+    }
+    
+    public ModuleVersionDescriptor resolveModule(DependencyDescriptor resolvedDependencyDescriptor, DependencyDescriptor requestedDependencyDescriptor) {
+        ModuleVersionDescriptor module = delegate.getDependency(ForceChangeDependencyDescriptor.forceChangingFlag(resolvedDependencyDescriptor, true));
+
+        if (module == null) {
+            moduleDescriptorCache.cacheModuleDescriptor(delegate, resolvedDependencyDescriptor.getDependencyRevisionId(), null, requestedDependencyDescriptor.isChanging());
+        } else {
+            moduleResolutionCache.cacheModuleResolution(delegate, requestedDependencyDescriptor.getDependencyRevisionId(), module.getId());
+            moduleDescriptorCache.cacheModuleDescriptor(delegate, module.getId(), module.getDescriptor(), isChangingDependency(requestedDependencyDescriptor, module));
+        }
+        return module;
+    }
+
+    private boolean isChangingDependency(DependencyDescriptor descriptor, ModuleVersionDescriptor downloadedModule) {
+        if (descriptor.isChanging()) {
+            return true;
+        }
+
+        return downloadedModule.isChanging();
+    }
+
+    private CachedModuleLookup notFound() {
+        return new CachedModuleLookup(false, null);
+    }
+
+    private CachedModuleLookup found(ModuleVersionDescriptor module) {
+        return new CachedModuleLookup(true, module);
+    }
+
+    private static class CachedModuleLookup {
+        public final boolean wasFound;
+        public final ModuleVersionDescriptor module;
+
+        private CachedModuleLookup(boolean wasFound, ModuleVersionDescriptor module) {
+            this.module = module;
+            this.wasFound = wasFound;
+        }
+    }
+
+    public DownloadedArtifact download(Artifact artifact) {
+        if (isLocal()) {
+            return delegate.download(artifact);
+        }
+
+        ArtifactAtRepositoryKey resolutionCacheIndexKey = new ArtifactAtRepositoryKey(delegate, artifact.getId());
+
+        // Look in the cache for this resolver
+        CachedExternalResource cached = artifactAtRepositoryCachedResolutionIndex.lookup(resolutionCacheIndexKey);
+
+        if (cached != null) {
+            ArtifactIdentifier artifactIdentifier = createArtifactIdentifier(artifact);
+            long age = timeProvider.getCurrentTime() - cached.getCachedAt();
+            if (cached.isMissing()) {
+                if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, null, age)) {
+                    LOGGER.debug("Detected non-existence of artifact '{}' in resolver cache", artifact.getId());
+                    return null;
+                }
+            } else {
+                File cachedArtifactFile = cached.getCachedFile();
+                if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, cachedArtifactFile, age)) {
+                    LOGGER.debug("Found artifact '{}' in resolver cache: {}", artifact.getId(), cachedArtifactFile);
+                    return new DownloadedArtifact(cachedArtifactFile, cached.getExternalResourceMetaData());
+                }
+            }
+        }
+
+        DownloadedArtifact downloadedArtifact = delegate.download(artifact);
+        LOGGER.debug("Downloaded artifact '{}' from resolver: {}", artifact.getId(), delegate);
+
+        if (downloadedArtifact == null) {
+            artifactAtRepositoryCachedResolutionIndex.storeMissing(resolutionCacheIndexKey);
+        } else {
+            artifactAtRepositoryCachedResolutionIndex.store(resolutionCacheIndexKey, downloadedArtifact.getLocalFile(), downloadedArtifact.getExternalResourceMetaData());
+        }
+
+        return downloadedArtifact;
+    }
+
+    private ModuleVersionSelector createModuleVersionSelector(ModuleRevisionId moduleRevisionId) {
+        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    }
+
+    private ModuleVersionIdentifier createModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
+        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+    }
+
+    private ArtifactIdentifier createArtifactIdentifier(Artifact artifact) {
+        ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(artifact.getModuleRevisionId());
+        return new DefaultArtifactIdentifier(moduleVersionIdentifier, artifact.getName(), artifact.getType(), artifact.getExt(), artifact.getExtraAttribute("classifier"));
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
new file mode 100644
index 0000000..abe8b46
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ChangingModuleDetector.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.matcher.Matcher;
+import org.apache.ivy.plugins.matcher.NoMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.GradleException;
+
+import java.lang.reflect.Method;
+
+class ChangingModuleDetector {
+    private final DependencyResolver resolver;
+
+    public ChangingModuleDetector(DependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public boolean isChangingModule(ModuleDescriptor moduleDescriptor) {
+        return getChangingMatcher().matches(moduleDescriptor.getResolvedModuleRevisionId().getRevision());
+    }
+
+    private Matcher getChangingMatcher() {
+        if (!(resolver instanceof AbstractResolver)) {
+            return NoMatcher.INSTANCE;
+        }
+
+        AbstractResolver abstractResolver = (AbstractResolver) resolver;
+        String changingMatcherName = readAbstractResolverProperty(resolver, "getChangingMatcherName");
+        String changingPattern = readAbstractResolverProperty(resolver, "getChangingPattern");
+        if (changingMatcherName == null || changingPattern == null) {
+            return NoMatcher.INSTANCE;
+        }
+        PatternMatcher matcher = abstractResolver.getSettings().getMatcher(changingMatcherName);
+        if (matcher == null) {
+            throw new IllegalStateException("unknown matcher '" + changingMatcherName
+                    + "'. It is set as changing matcher in " + this);
+        }
+        return matcher.getMatcher(changingPattern);
+    }
+
+    private String readAbstractResolverProperty(DependencyResolver resolver, String getter) {
+        try {
+            Method method = AbstractResolver.class.getDeclaredMethod(getter);
+            method.setAccessible(true);
+            return (String) method.invoke(resolver);
+        } catch (Exception e) {
+            throw new GradleException("Could not get cache options from AbstractResolver", e);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultIvyAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultIvyAdapter.java
new file mode 100644
index 0000000..880cfa5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultIvyAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.resolve.ResolveData;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
+
+class DefaultIvyAdapter implements IvyAdapter {
+    private final ResolveData resolveData;
+    private final UserResolverChain userResolver;
+
+    public DefaultIvyAdapter(ResolveData resolveData, UserResolverChain userResolverChain) {
+        this.resolveData = resolveData;
+        userResolver = userResolverChain;
+    }
+
+    public ResolveData getResolveData() {
+        return resolveData;
+    }
+
+    public DependencyToModuleResolver getDependencyToModuleResolver() {
+        return userResolver;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionDescriptor.java
new file mode 100644
index 0000000..f74740a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultModuleVersionDescriptor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class DefaultModuleVersionDescriptor implements ModuleVersionDescriptor {
+    private final ModuleDescriptor moduleDescriptor;
+    private final boolean changing;
+
+    public DefaultModuleVersionDescriptor(ModuleDescriptor moduleDescriptor, boolean changing) {
+        this.moduleDescriptor = moduleDescriptor;
+        this.changing = changing;
+    }
+
+    public ModuleRevisionId getId() {
+        return moduleDescriptor.getResolvedModuleRevisionId();
+    }
+
+    public ModuleDescriptor getDescriptor() {
+        return moduleDescriptor;
+    }
+
+    public boolean isChanging() {
+        return changing;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DelegatingDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DelegatingDependencyResolver.java
new file mode 100644
index 0000000..c40b50a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DelegatingDependencyResolver.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.ModuleEntry;
+import org.apache.ivy.core.search.OrganisationEntry;
+import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+
+abstract class DelegatingDependencyResolver implements DependencyResolver {
+    private final DependencyResolver resolver;
+    private ResolverSettings settings;
+
+    public DelegatingDependencyResolver(DependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public DependencyResolver getResolver() {
+        return resolver;
+    }
+
+    public String getName() {
+        return resolver.getName();
+    }
+
+    public ResolverSettings getSettings() {
+        return settings;
+    }
+
+    public void setSettings(ResolverSettings settings) {
+        this.settings = settings;
+        resolver.setSettings(settings);
+    }
+
+    public Namespace getNamespace() {
+        return resolver.getNamespace();
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        return resolver.getRepositoryCacheManager();
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        return resolver.listModules(org);
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        return resolver.listOrganisations();
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry module) {
+        return resolver.listRevisions(module);
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        return resolver.listTokenValues(token, otherTokenValues);
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        return resolver.listTokenValues(tokens, criteria);
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        return resolver.locate(artifact);
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+        return resolver.getDependency(dd, data);
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        return resolver.findIvyFileRef(dd, data);
+    }
+
+    public boolean exists(Artifact artifact) {
+        return resolver.exists(artifact);
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        return resolver.download(artifacts, options);
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        return resolver.download(artifact, options);
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setName(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void reportFailure() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void reportFailure(Artifact art) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void dumpSettings() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverAdapter.java
new file mode 100644
index 0000000..d8a6301
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.repositories.EnhancedArtifactDownloadReport;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.text.ParseException;
+
+/**
+ * A {@link ModuleVersionRepository} wrapper around an Ivy {@link DependencyResolver}.
+ */
+public class DependencyResolverAdapter implements ModuleVersionRepository {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DependencyResolverAdapter.class);
+
+    private final DependencyResolverIdentifier identifier;
+    private final DependencyResolver resolver;
+    private final DownloadOptions downloadOptions = new DownloadOptions();
+
+    public DependencyResolverAdapter(DependencyResolver resolver) {
+        this.identifier = new DependencyResolverIdentifier(resolver);
+        this.resolver = resolver;
+    }
+
+    public String getId() {
+        return identifier.getUniqueId();
+    }
+
+    public String getName() {
+        return identifier.getName();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Repository '%s'", resolver.getName());
+    }
+
+    public boolean isLocal() {
+        return resolver.getRepositoryCacheManager() instanceof LocalFileRepositoryCacheManager;
+    }
+
+    public DownloadedArtifact download(Artifact artifact) {
+        ArtifactDownloadReport artifactDownloadReport = resolver.download(new Artifact[]{artifact}, downloadOptions).getArtifactReport(artifact);
+        if (downloadFailed(artifactDownloadReport)) {
+            if (artifactDownloadReport instanceof EnhancedArtifactDownloadReport) {
+                EnhancedArtifactDownloadReport enhancedReport = (EnhancedArtifactDownloadReport) artifactDownloadReport;
+                throw new ArtifactResolveException(artifactDownloadReport.getArtifact(), enhancedReport.getFailure());
+            }
+            throw new ArtifactResolveException(artifactDownloadReport.getArtifact(), artifactDownloadReport.getDownloadDetails());
+        }
+
+        ArtifactOrigin artifactOrigin = artifactDownloadReport.getArtifactOrigin();
+
+        File localFile = artifactDownloadReport.getLocalFile();
+        if (localFile != null) {
+            ExternalResourceMetaData metaData = null;
+            if (artifactOrigin instanceof ArtifactOriginWithMetaData) {
+                metaData = ((ArtifactOriginWithMetaData) artifactOrigin).getMetaData();
+            }
+            return new DownloadedArtifact(localFile, metaData);
+        } else {
+            return null;
+        }
+    }
+
+    private boolean downloadFailed(ArtifactDownloadReport artifactReport) {
+        // Ivy reports FAILED with MISSING_ARTIFACT message when the artifact doesn't exist.
+        return artifactReport.getDownloadStatus() == DownloadStatus.FAILED
+                && !artifactReport.getDownloadDetails().equals(ArtifactDownloadReport.MISSING_ARTIFACT);
+    }
+
+    public ModuleVersionDescriptor getDependency(final DependencyDescriptor dd) {
+        ResolveData resolveData = IvyContextualiser.getIvyContext().getResolveData();
+        try {
+            ResolvedModuleRevision revision = resolver.getDependency(dd, resolveData);
+            if (revision == null) {
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dd.getDependencyRevisionId(), getName());
+                return null;
+            }
+            LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dd.getDependencyRevisionId(), getName());
+            return new DefaultModuleVersionDescriptor(revision.getDescriptor(), isChanging(revision));
+        } catch (ParseException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private boolean isChanging(ResolvedModuleRevision resolvedModuleRevision) {
+        return new ChangingModuleDetector(resolver).isChangingModule(resolvedModuleRevision.getDescriptor());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifier.java
new file mode 100644
index 0000000..4017b65
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifier.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.plugins.resolver.AbstractPatternsBasedResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceResolver;
+import org.gradle.util.GUtil;
+import org.gradle.util.hash.HashUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DependencyResolverIdentifier {
+    private final String resolverId;
+    private final String resolverName;
+
+    public DependencyResolverIdentifier(DependencyResolver resolver) {
+        resolverName = resolver.getName();
+
+        List<String> parts = new ArrayList<String>();
+        parts.add(resolver.getClass().getName());
+        if (resolver instanceof ExternalResourceResolver) {
+            ExternalResourceResolver externalResourceResolver = (ExternalResourceResolver) resolver;
+            parts.add(joinPatterns(externalResourceResolver.getIvyPatterns()));
+            parts.add(joinPatterns(externalResourceResolver.getArtifactPatterns()));
+            if (externalResourceResolver.isM2compatible()) {
+                parts.add("m2compatible");
+            }
+        } else if (resolver instanceof AbstractPatternsBasedResolver) {
+            AbstractPatternsBasedResolver patternsBasedResolver = (AbstractPatternsBasedResolver) resolver;
+            parts.add(joinPatterns(patternsBasedResolver.getIvyPatterns()));
+            parts.add(joinPatterns(patternsBasedResolver.getArtifactPatterns()));
+            if (patternsBasedResolver.isM2compatible()) {
+                parts.add("m2compatible");
+            }
+        } else {
+            parts.add(resolver.getName());
+            // TODO We should not be assuming equality between resolvers here based on name...
+        }
+
+        resolverId = calculateId(parts);
+    }
+
+    private String joinPatterns(List<String> patterns) {
+        return GUtil.join(patterns, ",");
+    }
+
+    private String calculateId(List<String> parts) {
+        String idString = GUtil.join(parts, "::");
+        return HashUtil.createHash(idString, "MD5").asHexString();
+    }
+
+    public String getUniqueId() {
+        return resolverId;
+    }
+
+    public String getName() {
+        return resolverName;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DownloadedArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DownloadedArtifact.java
new file mode 100644
index 0000000..91abceb
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DownloadedArtifact.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.ivyservice.ivyresolve;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+
+public class DownloadedArtifact {
+
+    private final File localFile;
+    private final ExternalResourceMetaData externalResourceMetaData;
+
+    public DownloadedArtifact(File localFile, @Nullable ExternalResourceMetaData externalResourceMetaData) {
+        this.localFile = localFile;
+        this.externalResourceMetaData = externalResourceMetaData;
+    }
+
+    public File getLocalFile() {
+        return localFile;
+    }
+
+    @Nullable
+    public ExternalResourceMetaData getExternalResourceMetaData() {
+        return externalResourceMetaData;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAdapter.java
new file mode 100644
index 0000000..824b41b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAdapter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.resolve.ResolveData;
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
+
+public interface IvyAdapter {
+    ResolveData getResolveData();
+
+    DependencyToModuleResolver getDependencyToModuleResolver();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java
new file mode 100755
index 0000000..b92dafb
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyContextualiser.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class IvyContextualiser {
+    private final Ivy ivy;
+    private final ResolveData resolveData;
+
+    public IvyContextualiser(Ivy ivy, ResolveData resolveData) {
+        this.ivy = ivy;
+        this.resolveData = resolveData;
+    }
+    
+    public <T> T contextualise(Class<T> type, final T delegate) {
+        Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                IvyContext context = IvyContext.pushNewCopyContext();
+                try {
+                    context.setIvy(ivy);
+                    context.setResolveData(resolveData);
+                    return method.invoke(delegate, args);
+                } catch (InvocationTargetException e) {
+                    throw UncheckedException.throwAsUncheckedException(e.getTargetException());
+                } finally {
+                    IvyContext.popContext();
+                }
+            }
+        });
+        return type.cast(proxy);
+    }
+
+    public static IvyContext getIvyContext() {
+        IvyContext context = IvyContext.getContext();
+        if (context.peekIvy() == null || context.getResolveData() == null) {
+            throw new IllegalStateException("Ivy context not established");
+        }
+        return context;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
new file mode 100755
index 0000000..96dd754
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+
+/**
+ * A {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleVersionIdResolver} implementation which returns lazy resolvers that don't actually retrieve module descriptors until
+ * required.
+ */
+public class LazyDependencyToModuleResolver implements DependencyToModuleVersionIdResolver {
+    private final DependencyToModuleResolver dependencyResolver;
+    private final VersionMatcher versionMatcher;
+
+    public LazyDependencyToModuleResolver(DependencyToModuleResolver dependencyResolver, VersionMatcher versionMatcher) {
+        this.dependencyResolver = dependencyResolver;
+        this.versionMatcher = versionMatcher;
+    }
+
+    public ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
+        if (versionMatcher.isDynamic(dependencyDescriptor.getDependencyRevisionId())) {
+            DynamicVersionResolveResult result = new DynamicVersionResolveResult(dependencyDescriptor);
+            result.resolve();
+            return result;
+        }
+        return new StaticVersionResolveResult(dependencyDescriptor);
+    }
+
+    private static class ErrorHandlingArtifactResolver implements ArtifactResolver {
+        private final ArtifactResolver resolver;
+
+        private ErrorHandlingArtifactResolver(ArtifactResolver resolver) {
+            this.resolver = resolver;
+        }
+
+        public ArtifactResolveResult resolve(Artifact artifact) throws ArtifactResolveException {
+            ArtifactResolveResult result;
+            try {
+                result = resolver.resolve(artifact);
+            } catch (Throwable t) {
+                return new BrokenArtifactResolveResult(new ArtifactResolveException(artifact, t));
+            }
+            return result;
+        }
+    }
+
+    private static class DefaultModuleVersionResolveResult implements ModuleVersionResolveResult {
+        private final ModuleVersionResolveResult resolver;
+
+        private DefaultModuleVersionResolveResult(ModuleVersionResolveResult result) {
+            this.resolver = result;
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return null;
+        }
+
+        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+            return resolver.getId();
+        }
+
+        public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+            return resolver.getDescriptor();
+        }
+
+        public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
+            return new ErrorHandlingArtifactResolver(resolver.getArtifactResolver());
+        }
+    }
+
+    private class StaticVersionResolveResult implements ModuleVersionIdResolveResult {
+        private final DependencyDescriptor dependencyDescriptor;
+        private ModuleVersionResolveResult resolveResult;
+
+        public StaticVersionResolveResult(DependencyDescriptor dependencyDescriptor) {
+            this.dependencyDescriptor = dependencyDescriptor;
+        }
+
+        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+            return dependencyDescriptor.getDependencyRevisionId();
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return null;
+        }
+
+        public ModuleVersionResolveResult resolve() {
+            if (resolveResult == null) {
+                ModuleVersionResolveException failure = null;
+                ModuleVersionResolveResult resolveResult = null;
+                try {
+                    try {
+                        resolveResult = dependencyResolver.resolve(dependencyDescriptor);
+                    } catch (Throwable t) {
+                        throw new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), t);
+                    }
+                    if (resolveResult.getFailure() instanceof ModuleVersionNotFoundException) {
+                        throw notFound(dependencyDescriptor.getDependencyRevisionId());
+                    }
+                    if (resolveResult.getFailure() != null) {
+                        throw resolveResult.getFailure();
+                    }
+                    checkDescriptor(resolveResult.getDescriptor());
+                } catch (ModuleVersionResolveException e) {
+                    failure = e;
+                }
+
+                if (failure != null) {
+                    this.resolveResult = new BrokenModuleVersionResolveResult(failure);
+                } else {
+                    this.resolveResult = new DefaultModuleVersionResolveResult(resolveResult);
+                }
+            }
+
+            return resolveResult;
+        }
+
+        private void checkDescriptor(ModuleDescriptor descriptor) {
+            ModuleRevisionId id = descriptor.getModuleRevisionId();
+            if (!copy(id).equals(copy(dependencyDescriptor.getDependencyRevisionId()))) {
+                onUnexpectedModuleRevisionId(descriptor);
+            }
+            for (Configuration configuration : descriptor.getConfigurations()) {
+                for (String parent : configuration.getExtends()) {
+                    if (descriptor.getConfiguration(parent) == null) {
+                        throw new ModuleVersionResolveException(String.format("Configuration '%s' extends unknown configuration '%s' in module descriptor for group:%s, module:%s, version:%s.",
+                                configuration.getName(), parent, id.getOrganisation(), id.getName(), id.getRevision()));
+                    }
+                }
+            }
+        }
+
+        private ModuleRevisionId copy(ModuleRevisionId id) {
+            // Copy to get rid of extra attributes
+            return new ModuleRevisionId(new ModuleId(id.getOrganisation(), id.getName()), id.getRevision());
+        }
+
+        protected ModuleVersionNotFoundException notFound(ModuleRevisionId id) {
+            return new ModuleVersionNotFoundException(id);
+        }
+
+        protected void onUnexpectedModuleRevisionId(ModuleDescriptor descriptor) {
+            throw new ModuleVersionResolveException(String.format("Received unexpected module descriptor %s for dependency %s.", descriptor.getModuleRevisionId(), dependencyDescriptor.getDependencyRevisionId()));
+        }
+    }
+
+    private class DynamicVersionResolveResult extends StaticVersionResolveResult {
+        public DynamicVersionResolveResult(DependencyDescriptor dependencyDescriptor) {
+            super(dependencyDescriptor);
+        }
+
+        @Override
+        public ModuleVersionResolveException getFailure() {
+            return resolve().getFailure();
+        }
+
+        @Override
+        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+            return resolve().getId();
+        }
+
+        @Override
+        protected ModuleVersionNotFoundException notFound(ModuleRevisionId id) {
+            return new ModuleVersionNotFoundException(String.format("Could not find any version that matches group:%s, module:%s, version:%s.", id.getOrganisation(), id.getName(), id.getRevision()));
+        }
+
+        @Override
+        protected void onUnexpectedModuleRevisionId(ModuleDescriptor descriptor) {
+            // Don't care
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java
new file mode 100644
index 0000000..b37424e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionNotFoundException;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveResult;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+import java.text.ParseException;
+
+/**
+ * The main entry point for a {@link DependencyResolver} to call back into the dependency resolution mechanism.
+ */
+public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
+    private final String name;
+    private final UserResolverChain userResolverChain;
+    private final CacheLockingManager cacheLockingManager;
+
+    public LoopbackDependencyResolver(String name, UserResolverChain userResolverChain, CacheLockingManager cacheLockingManager) {
+        this.name = name;
+        this.userResolverChain = userResolverChain;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setSettings(ResolverSettings settings) {
+        userResolverChain.setSettings(settings);
+    }
+
+    @Override
+    public ResolvedModuleRevision getDependency(final DependencyDescriptor dd, final ResolveData data) throws ParseException {
+        final DependencyResolver loopback = this;
+        return cacheLockingManager.useCache(String.format("Resolve %s", dd), new Factory<ResolvedModuleRevision>() {
+            public ResolvedModuleRevision create() {
+                ModuleVersionResolveResult dependency;
+                IvyContext ivyContext = IvyContext.pushNewCopyContext();
+                try {
+                    ivyContext.setResolveData(data);
+                    dependency = userResolverChain.resolve(dd);
+                } finally {
+                    IvyContext.popContext();
+                }
+                // TODO:DAZ Need to create a metadata download report here
+                return new ResolvedModuleRevision(loopback, loopback, dependency.getDescriptor(), null);
+            }
+        });
+    }
+
+    @Override
+    public ArtifactOrigin locate(final Artifact artifact) {
+        return cacheLockingManager.useCache(String.format("Locate %s", artifact), new Factory<ArtifactOrigin>() {
+            public ArtifactOrigin create() {
+                try {
+                    DependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(artifact.getModuleRevisionId(), false);
+                    File artifactFile = userResolverChain.resolve(dependencyDescriptor).getArtifactResolver().resolve(artifact).getFile();
+                    return new ArtifactOrigin(artifact, false, artifactFile.getAbsolutePath());
+                } catch (ModuleVersionNotFoundException e) {
+                    return null;
+                } catch (ArtifactNotFoundException e) {
+                    return null;
+                }
+            }
+        });
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
new file mode 100644
index 0000000..23a20f0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public interface ModuleVersionDescriptor {
+    ModuleRevisionId getId();
+
+    ModuleDescriptor getDescriptor();
+
+    boolean isChanging();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
new file mode 100644
index 0000000..4233e19
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+
+/**
+ * A repository of module versions.
+ *
+ * <p>Current contains a subset of methods from {@link org.apache.ivy.plugins.resolver.DependencyResolver}, while we transition away from it.
+ * The plan is to sync with (or replace with) {@link org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver}.
+ */
+public interface ModuleVersionRepository {
+    String getId();
+
+    String getName();
+
+    /**
+     * @return null if not found.
+     */
+    ModuleVersionDescriptor getDependency(DependencyDescriptor dd) throws ModuleVersionResolveException;
+
+    /**
+     * @return null if not found.
+     */
+    DownloadedArtifact download(Artifact artifact) throws ArtifactResolveException;
+
+    // TODO - should be internal to the implementation of this (is only used to communicate DependencyResolverAdapter -> CachingModuleVersionRepository)
+    boolean isLocal();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoOpRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoOpRepositoryCacheManager.java
new file mode 100644
index 0000000..96c190f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoOpRepositoryCacheManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.cache.*;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+import java.text.ParseException;
+
+/**
+ * A cache manager which does nothing. Only useful for local meta-data only repositories.
+ */
+public class NoOpRepositoryCacheManager implements RepositoryCacheManager {
+    private final String name;
+
+    public NoOpRepositoryCacheManager(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName, String artifactResolverName) {
+    }
+
+    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
+        return null;
+    }
+
+    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) {
+        return null;
+    }
+
+    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+        ArtifactDownloadReport report = new ArtifactDownloadReport(null);
+        report.setDownloadStatus(DownloadStatus.NO);
+        return report;
+    }
+
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, ResolvedResource orginalMetadataRef, DependencyDescriptor dd, Artifact requestedMetadataArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
+        return null;
+    }
+
+    public void originalToCachedModuleDescriptor(DependencyResolver resolver, ResolvedResource orginalMetadataRef, Artifact requestedMetadataArtifact, ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
+    }
+
+    public void clean() {
+    }
+
+    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision) {
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
new file mode 100644
index 0000000..28668b3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.IvyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter;
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
+import org.gradle.util.TimeProvider;
+import org.gradle.util.WrapUtil;
+
+import java.util.List;
+
+public class ResolveIvyFactory {
+    private final IvyFactory ivyFactory;
+    private final ResolverProvider resolverProvider;
+    private final SettingsConverter settingsConverter;
+    private final ModuleResolutionCache moduleResolutionCache;
+    private final ModuleDescriptorCache moduleDescriptorCache;
+    private final CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex;
+    private final CacheLockingManager cacheLockingManager;
+    private final StartParameterResolutionOverride startParameterResolutionOverride;
+    private final TimeProvider timeProvider;
+
+
+    public ResolveIvyFactory(IvyFactory ivyFactory, ResolverProvider resolverProvider, SettingsConverter settingsConverter,
+                             ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
+                             CachedExternalResourceIndex<ArtifactAtRepositoryKey> artifactAtRepositoryCachedResolutionIndex,
+                             CacheLockingManager cacheLockingManager, StartParameterResolutionOverride startParameterResolutionOverride,
+                             TimeProvider timeProvider) {
+        this.ivyFactory = ivyFactory;
+        this.resolverProvider = resolverProvider;
+        this.settingsConverter = settingsConverter;
+        this.moduleResolutionCache = moduleResolutionCache;
+        this.moduleDescriptorCache = moduleDescriptorCache;
+        this.artifactAtRepositoryCachedResolutionIndex = artifactAtRepositoryCachedResolutionIndex;
+        this.cacheLockingManager = cacheLockingManager;
+        this.startParameterResolutionOverride = startParameterResolutionOverride;
+        this.timeProvider = timeProvider;
+    }
+
+    public IvyAdapter create(ConfigurationInternal configuration) {
+        UserResolverChain userResolverChain = new UserResolverChain();
+        ResolutionRules resolutionRules = configuration.getResolutionStrategy().getResolutionRules();
+        startParameterResolutionOverride.addResolutionRules(resolutionRules);
+
+        LoopbackDependencyResolver loopbackDependencyResolver = new LoopbackDependencyResolver(SettingsConverter.LOOPBACK_RESOLVER_NAME, userResolverChain, cacheLockingManager);
+        List<DependencyResolver> rawResolvers = resolverProvider.getResolvers();
+
+        IvySettings ivySettings = settingsConverter.convertForResolve(loopbackDependencyResolver, rawResolvers);
+        Ivy ivy = ivyFactory.createIvy(ivySettings);
+        ResolveData resolveData = createResolveData(ivy, configuration.getName());
+
+        IvyContextualiser contextualiser = new IvyContextualiser(ivy, resolveData);
+        for (DependencyResolver rawResolver : rawResolvers) {
+            // TODO:DAZ This could be lazily provided via the ivy context. Then we can change resolverProvider.getResolvers() -> getRepositories().
+            rawResolver.setSettings(ivySettings);
+
+            ModuleVersionRepository moduleVersionRepository = new DependencyResolverAdapter(rawResolver);
+            moduleVersionRepository = new CacheLockingModuleVersionRepository(moduleVersionRepository, cacheLockingManager);
+            moduleVersionRepository = startParameterResolutionOverride.overrideModuleVersionRepository(moduleVersionRepository);
+            ModuleVersionRepository cachingRepository =
+                    new CachingModuleVersionRepository(moduleVersionRepository, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCachedResolutionIndex,
+                                                       configuration.getResolutionStrategy().getCachePolicy(), timeProvider);
+            // Need to contextualise outside of caching, since parsing of module descriptors in the cache requires ivy settings, which is provided via the context atm
+            ModuleVersionRepository ivyContextualisedRepository = contextualiser.contextualise(ModuleVersionRepository.class, cachingRepository);
+            userResolverChain.add(ivyContextualisedRepository);
+        }
+
+        return new DefaultIvyAdapter(resolveData, userResolverChain);
+    }
+    
+    private ResolveData createResolveData(Ivy ivy, String configurationName) {
+        ResolveOptions options = new ResolveOptions();
+        options.setDownload(false);
+        options.setConfs(WrapUtil.toArray(configurationName));
+        return new ResolveData(ivy.getResolveEngine(), options);
+    }
+
+    
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java
new file mode 100644
index 0000000..d10a0e3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+abstract class RestrictedDependencyResolver extends DelegatingDependencyResolver {
+    protected RestrictedDependencyResolver() {
+        super(createAngryDelegate());
+    }
+
+    private static DependencyResolver createAngryDelegate() {
+        return (DependencyResolver) Proxy.newProxyInstance(RestrictedDependencyResolver.class.getClassLoader(), new Class<?>[]{DependencyResolver.class}, new InvocationHandler() {
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                throw new UnsupportedOperationException();
+            }
+        });
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
new file mode 100644
index 0000000..69a1cff
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.cache.ArtifactResolutionControl;
+import org.gradle.api.artifacts.cache.DependencyResolutionControl;
+import org.gradle.api.artifacts.cache.ModuleResolutionControl;
+import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+
+import java.util.concurrent.TimeUnit;
+
+public class StartParameterResolutionOverride {
+    private final StartParameter startParameter;
+
+    public StartParameterResolutionOverride(StartParameter startParameter) {
+        this.startParameter = startParameter;
+    }
+
+    public void addResolutionRules(ResolutionRules resolutionRules) {
+        if (startParameter.isOffline()) {
+            resolutionRules.eachDependency(new Action<DependencyResolutionControl>() {
+                public void execute(DependencyResolutionControl dependencyResolutionControl) {
+                    dependencyResolutionControl.useCachedResult();
+                }
+            });
+            resolutionRules.eachModule(new Action<ModuleResolutionControl>() {
+                public void execute(ModuleResolutionControl moduleResolutionControl) {
+                    moduleResolutionControl.useCachedResult();
+                }
+            });
+            resolutionRules.eachArtifact(new Action<ArtifactResolutionControl>() {
+                public void execute(ArtifactResolutionControl artifactResolutionControl) {
+                    artifactResolutionControl.useCachedResult();
+                }
+            });
+        } else if (startParameter.isRefreshDependencies()) {
+            resolutionRules.eachDependency(new Action<DependencyResolutionControl>() {
+                public void execute(DependencyResolutionControl dependencyResolutionControl) {
+                    dependencyResolutionControl.cacheFor(0, TimeUnit.SECONDS);
+                }
+            });
+            resolutionRules.eachModule(new Action<ModuleResolutionControl>() {
+                public void execute(ModuleResolutionControl moduleResolutionControl) {
+                    moduleResolutionControl.cacheFor(0, TimeUnit.SECONDS);
+                }
+            });
+            resolutionRules.eachArtifact(new Action<ArtifactResolutionControl>() {
+                public void execute(ArtifactResolutionControl artifactResolutionControl) {
+                    artifactResolutionControl.cacheFor(0, TimeUnit.SECONDS);
+                }
+            });
+        }
+    }
+
+    public ModuleVersionRepository overrideModuleVersionRepository(ModuleVersionRepository original) {
+        if (startParameter.isOffline() && !original.isLocal()) {
+            return new OfflineModuleVersionRepository(original);
+        }
+        return original;
+    }
+
+    private static class OfflineModuleVersionRepository implements ModuleVersionRepository {
+        private final ModuleVersionRepository original;
+
+        public OfflineModuleVersionRepository(ModuleVersionRepository original) {
+            this.original = original;
+        }
+
+        public String getId() {
+            return original.getId();
+        }
+
+        public String getName() {
+            return original.getName();
+        }
+
+        public boolean isLocal() {
+            return false;
+        }
+
+        public ModuleVersionDescriptor getDependency(DependencyDescriptor dd) {
+            throw new ModuleVersionResolveException("No cached version available for offline mode");
+        }
+
+        public DownloadedArtifact download(Artifact artifact) {
+            throw new ArtifactResolveException(artifact, "No cached version available for offline mode");
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
new file mode 100644
index 0000000..d0f929e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.ComparatorLatestStrategy;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+public class UserResolverChain implements DependencyToModuleResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(UserResolverChain.class);
+
+    private final List<ModuleVersionRepository> moduleVersionRepositories = new ArrayList<ModuleVersionRepository>();
+    private final List<String> moduleVersionRepositoryNames = new ArrayList<String>();
+    private ResolverSettings settings;
+
+    public void setSettings(ResolverSettings settings) {
+        this.settings = settings;
+    }
+
+    public void add(ModuleVersionRepository repository) {
+        moduleVersionRepositories.add(repository);
+        moduleVersionRepositoryNames.add(repository.getName());
+    }
+
+    public ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
+        LOGGER.debug("Attempting to resolve module '{}' using repositories '{}'", dependencyDescriptor.getDependencyRevisionId(), moduleVersionRepositoryNames);
+        List<Throwable> errors = new ArrayList<Throwable>();
+        final ModuleResolution latestResolved = findLatestModule(dependencyDescriptor, errors);
+        if (latestResolved != null) {
+            final ModuleVersionDescriptor downloadedModule = latestResolved.module;
+            LOGGER.debug("Using module '{}' from repository '{}'", downloadedModule.getId(), latestResolved.repository.getName());
+            return latestResolved;
+        }
+        if (!errors.isEmpty()) {
+            return new BrokenModuleVersionResolveResult(new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), errors));
+        }
+        
+        return new BrokenModuleVersionResolveResult(new ModuleVersionNotFoundException(dependencyDescriptor.getDependencyRevisionId()));
+    }
+
+    private ModuleResolution findLatestModule(DependencyDescriptor dependencyDescriptor, Collection<Throwable> failures) {
+        boolean isStaticVersion = !settings.getVersionMatcher().isDynamic(dependencyDescriptor.getDependencyRevisionId());
+        
+        ModuleResolution best = null;
+        for (ModuleVersionRepository repository : moduleVersionRepositories) {
+            try {
+                ModuleVersionDescriptor module = repository.getDependency(dependencyDescriptor);
+                if (module != null) {
+                    ModuleResolution moduleResolution = new ModuleResolution(repository, module);
+                    if (isStaticVersion && !moduleResolution.isGeneratedModuleDescriptor()) {
+                        return moduleResolution;
+                    }
+                    best = chooseBest(best, moduleResolution);
+                }
+            } catch (Throwable e) {
+                failures.add(e);
+            }
+        }
+
+        return best;
+    }
+
+    private ModuleResolution chooseBest(ModuleResolution one, ModuleResolution two) {
+        if (one == null || two == null) {
+            return two == null ? one : two;
+        }
+        if (one.module == null || two.module == null) {
+            return two.module == null ? one : two;
+        }
+
+        ComparatorLatestStrategy latestStrategy = (ComparatorLatestStrategy) settings.getDefaultLatestStrategy();
+        Comparator<ArtifactInfo> comparator = latestStrategy.getComparator();
+        int comparison = comparator.compare(one, two);
+
+        if (comparison == 0) {
+            if (one.isGeneratedModuleDescriptor() && !two.isGeneratedModuleDescriptor()) {
+                return two;
+            }
+            return one;
+        }
+
+        return comparison < 0 ? two : one;
+    }
+
+    private static class ModuleResolution implements ArtifactInfo, ModuleVersionResolveResult {
+        public final ModuleVersionRepository repository;
+        public final ModuleVersionDescriptor module;
+
+        public ModuleResolution(ModuleVersionRepository repository, ModuleVersionDescriptor module) {
+            this.repository = repository;
+            this.module = module;
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return null;
+        }
+
+        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+            return module.getId();
+        }
+
+        public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+            return module.getDescriptor();
+        }
+
+        public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
+            return new ModuleVersionRepositoryBackedArtifactResolver(repository);
+        }
+
+        public boolean isGeneratedModuleDescriptor() {
+            if (module == null) {
+                throw new IllegalStateException();
+            }
+            return module.getDescriptor().isDefault();
+        }
+
+        public long getLastModified() {
+            return module.getDescriptor().getResolvedPublicationDate().getTime();
+        }
+
+        public String getRevision() {
+            return module.getId().getRevision();
+        }
+    }
+
+    private static final class ModuleVersionRepositoryBackedArtifactResolver implements ArtifactResolver {
+        private final ModuleVersionRepository repository;
+
+        private ModuleVersionRepositoryBackedArtifactResolver(ModuleVersionRepository repository) {
+            this.repository = repository;
+        }
+
+        public ArtifactResolveResult resolve(Artifact artifact) {
+            LOGGER.debug("Attempting to download {} using repository '{}'", artifact, repository.getName());
+            DownloadedArtifact downloadedArtifact;
+            try {
+                downloadedArtifact = repository.download(artifact);
+            } catch (ArtifactResolveException e) {
+                return new BrokenArtifactResolveResult(e);
+            }
+            if (downloadedArtifact == null) {
+                return new BrokenArtifactResolveResult(new ArtifactNotFoundException(artifact));
+            }
+            return new FileBackedArtifactResolveResult(downloadedArtifact.getLocalFile());
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
new file mode 100644
index 0000000..9d7c602
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.License;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.OverrideDependencyDescriptorMediator;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.m2.DefaultPomDependencyMgt;
+import org.apache.ivy.plugins.parser.m2.PomDependencyMgt;
+import org.apache.ivy.plugins.parser.m2.PomReader.PomDependencyData;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+
+/**
+ * This a straight copy of org.apache.ivy.plugins.parser.m2.PomModuleDescriptorBuilder, with minor changes: 1) Do not create artifact for empty classifier. (Previously did so for all non-null
+ * classifiers)
+ */
+public class GradlePomModuleDescriptorBuilder {
+
+    private static final int DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT = 4;
+
+    public static final Configuration[] MAVEN2_CONFIGURATIONS = new Configuration[]{
+            new Configuration("default", Visibility.PUBLIC,
+                    "runtime dependencies and master artifact can be used with this conf",
+                    new String[]{"runtime", "master"}, true, null),
+            new Configuration("master", Visibility.PUBLIC,
+                    "contains only the artifact published by this module itself, "
+                            + "with no transitive dependencies",
+                    new String[0], true, null),
+            new Configuration("compile", Visibility.PUBLIC,
+                    "this is the default scope, used if none is specified. "
+                            + "Compile dependencies are available in all classpaths.",
+                    new String[0], true, null),
+            new Configuration("provided", Visibility.PUBLIC,
+                    "this is much like compile, but indicates you expect the JDK or a container "
+                            + "to provide it. "
+                            + "It is only available on the compilation classpath, and is not transitive.",
+                    new String[0], true, null),
+            new Configuration("runtime", Visibility.PUBLIC,
+                    "this scope indicates that the dependency is not required for compilation, "
+                            + "but is for execution. It is in the runtime and test classpaths, "
+                            + "but not the compile classpath.",
+                    new String[]{"compile"}, true, null),
+            new Configuration("test", Visibility.PRIVATE,
+                    "this scope indicates that the dependency is not required for normal use of "
+                            + "the application, and is only available for the test compilation and "
+                            + "execution phases.",
+                    new String[]{"runtime"}, true, null),
+            new Configuration("system", Visibility.PUBLIC,
+                    "this scope is similar to provided except that you have to provide the JAR "
+                            + "which contains it explicitly. The artifact is always available and is not "
+                            + "looked up in a repository.",
+                    new String[0], true, null),
+            new Configuration("sources", Visibility.PUBLIC,
+                    "this configuration contains the source artifact of this module, if any.",
+                    new String[0], true, null),
+            new Configuration("javadoc", Visibility.PUBLIC,
+                    "this configuration contains the javadoc artifact of this module, if any.",
+                    new String[0], true, null),
+            new Configuration("optional", Visibility.PUBLIC,
+                    "contains all optional dependencies", new String[0], true, null)
+    };
+
+    static final Map<String, ConfMapper> MAVEN2_CONF_MAPPING = new HashMap<String, ConfMapper>();
+
+    private static final String DEPENDENCY_MANAGEMENT = "m:dependency.management";
+    private static final String PROPERTIES = "m:properties";
+    private static final String EXTRA_INFO_DELIMITER = "__";
+    private static final Collection<String> JAR_PACKAGINGS = Arrays.asList("ejb", "bundle", "maven-plugin", "eclipse-plugin");
+
+    static interface ConfMapper {
+        public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional);
+    }
+
+    static {
+        MAVEN2_CONF_MAPPING.put("compile", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                if (isOptional) {
+                    dd.addDependencyConfiguration("optional", "compile(*)");
+                    //dd.addDependencyConfiguration("optional", "provided(*)");
+                    dd.addDependencyConfiguration("optional", "master(*)");
+
+                } else {
+                    dd.addDependencyConfiguration("compile", "compile(*)");
+                    //dd.addDependencyConfiguration("compile", "provided(*)");
+                    dd.addDependencyConfiguration("compile", "master(*)");
+                    dd.addDependencyConfiguration("runtime", "runtime(*)");
+                }
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("provided", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                if (isOptional) {
+                    dd.addDependencyConfiguration("optional", "compile(*)");
+                    dd.addDependencyConfiguration("optional", "provided(*)");
+                    dd.addDependencyConfiguration("optional", "runtime(*)");
+                    dd.addDependencyConfiguration("optional", "master(*)");
+                } else {
+                    dd.addDependencyConfiguration("provided", "compile(*)");
+                    dd.addDependencyConfiguration("provided", "provided(*)");
+                    dd.addDependencyConfiguration("provided", "runtime(*)");
+                    dd.addDependencyConfiguration("provided", "master(*)");
+                }
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("runtime", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                if (isOptional) {
+                    dd.addDependencyConfiguration("optional", "compile(*)");
+                    dd.addDependencyConfiguration("optional", "provided(*)");
+                    dd.addDependencyConfiguration("optional", "master(*)");
+
+                } else {
+                    dd.addDependencyConfiguration("runtime", "compile(*)");
+                    dd.addDependencyConfiguration("runtime", "runtime(*)");
+                    dd.addDependencyConfiguration("runtime", "master(*)");
+                }
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("test", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                //optional doesn't make sense in the test scope
+                dd.addDependencyConfiguration("test", "runtime(*)");
+                dd.addDependencyConfiguration("test", "master(*)");
+            }
+        });
+        MAVEN2_CONF_MAPPING.put("system", new ConfMapper() {
+            public void addMappingConfs(DefaultDependencyDescriptor dd, boolean isOptional) {
+                //optional doesn't make sense in the system scope
+                dd.addDependencyConfiguration("system", "master(*)");
+            }
+        });
+    }
+
+
+    private final DefaultModuleDescriptor ivyModuleDescriptor;
+
+    private ModuleRevisionId mrid;
+
+    private DefaultArtifact mainArtifact;
+
+    private ParserSettings parserSettings;
+
+    private static final String WRONG_NUMBER_OF_PARTS_MSG = "what seemed to be a dependency "
+            + "management extra info exclusion had the wrong number of parts (should have 2) ";
+
+
+    public GradlePomModuleDescriptorBuilder(
+            ModuleDescriptorParser parser, Resource res, ParserSettings ivySettings) {
+        ivyModuleDescriptor = new DefaultModuleDescriptor(parser, res);
+        ivyModuleDescriptor.setResolvedPublicationDate(new Date(res.getLastModified()));
+        for (Configuration maven2Configuration : MAVEN2_CONFIGURATIONS) {
+            ivyModuleDescriptor.addConfiguration(maven2Configuration);
+        }
+        ivyModuleDescriptor.setMappingOverride(true);
+        ivyModuleDescriptor.addExtraAttributeNamespace("m", Ivy.getIvyHomeURL() + "maven");
+        parserSettings = ivySettings;
+    }
+
+    public ModuleDescriptor getModuleDescriptor() {
+        return ivyModuleDescriptor;
+    }
+
+    public void setModuleRevId(String groupId, String artifactId, String version) {
+        mrid = ModuleRevisionId.newInstance(groupId, artifactId, version);
+        ivyModuleDescriptor.setModuleRevisionId(mrid);
+
+        if ((version == null) || version.endsWith("SNAPSHOT")) {
+            ivyModuleDescriptor.setStatus("integration");
+        } else {
+            ivyModuleDescriptor.setStatus("release");
+        }
+    }
+
+    public void setHomePage(String homePage) {
+        ivyModuleDescriptor.setHomePage(homePage);
+    }
+
+    public void setDescription(String description) {
+        ivyModuleDescriptor.setDescription(description);
+    }
+
+    public void setLicenses(License[] licenses) {
+        for (License license : licenses) {
+            ivyModuleDescriptor.addLicense(license);
+        }
+    }
+
+    public void addMainArtifact(String artifactId, String packaging) {
+        String ext;
+
+        /*
+        * TODO: we should make packaging to ext mapping configurable, since it's not possible to cover all cases.
+        */
+        if ("pom".equals(packaging)) {
+            // no artifact defined! Add the default artifact if it exist.
+            DependencyResolver resolver = parserSettings.getResolver(mrid);
+
+            if (resolver != null) {
+                DefaultArtifact artifact = new DefaultArtifact(
+                        mrid, new Date(), artifactId, "jar", "jar");
+                ArtifactOrigin artifactOrigin = resolver.locate(artifact);
+
+                if (!ArtifactOrigin.isUnknown(artifactOrigin)) {
+                    mainArtifact = artifact;
+                    ivyModuleDescriptor.addArtifact("master", mainArtifact);
+                }
+            }
+
+            return;
+        } else if (JAR_PACKAGINGS.contains(packaging)) {
+            ext = "jar";
+        } else {
+            ext = packaging;
+        }
+
+        mainArtifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, ext);
+        ivyModuleDescriptor.addArtifact("master", mainArtifact);
+    }
+
+    public void addDependency(PomDependencyData dep) {
+        String scope = dep.getScope();
+        if ((scope != null) && (scope.length() > 0) && !MAVEN2_CONF_MAPPING.containsKey(scope)) {
+            // unknown scope, defaulting to 'compile'
+            scope = "compile";
+        }
+
+        String version = dep.getVersion();
+        version = (version == null || version.length() == 0) ? getDefaultVersion(dep) : version;
+        ModuleRevisionId moduleRevId = ModuleRevisionId.newInstance(dep.getGroupId(), dep.getArtifactId(), version);
+
+        // Some POMs depend on themselves, don't add this dependency: Ivy doesn't allow this!
+        // Example: http://repo2.maven.org/maven2/net/jini/jsk-platform/2.1/jsk-platform-2.1.pom
+        ModuleRevisionId mRevId = ivyModuleDescriptor.getModuleRevisionId();
+        if ((mRevId != null) && mRevId.getModuleId().equals(moduleRevId.getModuleId())) {
+            return;
+        }
+
+        DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(ivyModuleDescriptor, moduleRevId, true, false, true);
+        scope = (scope == null || scope.length() == 0) ? getDefaultScope(dep) : scope;
+        ConfMapper mapping = MAVEN2_CONF_MAPPING.get(scope);
+        mapping.addMappingConfs(dd, dep.isOptional());
+        Map<String, String> extraAtt = new HashMap<String, String>();
+        boolean hasClassifier = dep.getClassifier() != null && dep.getClassifier().length() > 0;
+        boolean hasNonJarType = dep.getType() != null && !"jar".equals(dep.getType());
+        if (hasClassifier || hasNonJarType) {
+            String type = "jar";
+            if (dep.getType() != null) {
+                type = dep.getType();
+            }
+            String ext = type;
+
+            // if type is 'test-jar', the extension is 'jar' and the classifier is 'tests'
+            // Cfr. http://maven.apache.org/guides/mini/guide-attached-tests.html
+            if ("test-jar".equals(type)) {
+                ext = "jar";
+                extraAtt.put("m:classifier", "tests");
+            } else if (JAR_PACKAGINGS.contains(type)) {
+                ext = "jar";
+            }
+
+            // we deal with classifiers by setting an extra attribute and forcing the
+            // dependency to assume such an artifact is published
+            if (dep.getClassifier() != null) {
+                extraAtt.put("m:classifier", dep.getClassifier());
+            }
+            DefaultDependencyArtifactDescriptor depArtifact =
+                    new DefaultDependencyArtifactDescriptor(dd, dd.getDependencyId().getName(), type, ext, null, extraAtt);
+            // here we have to assume a type and ext for the artifact, so this is a limitation
+            // compared to how m2 behave with classifiers
+            String optionalizedScope = dep.isOptional() ? "optional" : scope;
+            dd.addDependencyArtifact(optionalizedScope, depArtifact);
+        }
+
+        // experimentation shows the following, excluded modules are
+        // inherited from parent POMs if either of the following is true:
+        // the <exclusions> element is missing or the <exclusions> element
+        // is present, but empty.
+        List /*<ModuleId>*/ excluded = dep.getExcludedModules();
+        if (excluded.isEmpty()) {
+            excluded = getDependencyMgtExclusions(ivyModuleDescriptor, dep.getGroupId(), dep.getArtifactId());
+        }
+        for (Object anExcluded : excluded) {
+            ModuleId excludedModule = (ModuleId) anExcluded;
+            String[] confs = dd.getModuleConfigurations();
+            for (String conf : confs) {
+                dd.addExcludeRule(conf, new DefaultExcludeRule(new ArtifactId(
+                        excludedModule, PatternMatcher.ANY_EXPRESSION,
+                        PatternMatcher.ANY_EXPRESSION,
+                        PatternMatcher.ANY_EXPRESSION),
+                        ExactPatternMatcher.INSTANCE, null));
+            }
+        }
+
+        ivyModuleDescriptor.addDependency(dd);
+    }
+
+    public void addDependency(DependencyDescriptor descriptor) {
+        // Some POMs depend on theirselfves through their parent pom, don't add this dependency
+        // since Ivy doesn't allow this!
+        // Example: http://repo2.maven.org/maven2/com/atomikos/atomikos-util/3.6.4/atomikos-util-3.6.4.pom
+        ModuleId dependencyId = descriptor.getDependencyId();
+        ModuleRevisionId mRevId = ivyModuleDescriptor.getModuleRevisionId();
+        if ((mRevId != null) && mRevId.getModuleId().equals(dependencyId)) {
+            return;
+        }
+
+        ivyModuleDescriptor.addDependency(descriptor);
+    }
+
+
+    public void addDependencyMgt(PomDependencyMgt dep) {
+        String key = getDependencyMgtExtraInfoKeyForVersion(dep.getGroupId(), dep.getArtifactId());
+        ivyModuleDescriptor.addExtraInfo(key, dep.getVersion());
+        if (dep.getScope() != null) {
+            String scopeKey = getDependencyMgtExtraInfoKeyForScope(dep.getGroupId(), dep.getArtifactId());
+            ivyModuleDescriptor.addExtraInfo(scopeKey, dep.getScope());
+        }
+        if (!dep.getExcludedModules().isEmpty()) {
+            final String exclusionPrefix = getDependencyMgtExtraInfoPrefixForExclusion(dep.getGroupId(), dep.getArtifactId());
+            int index = 0;
+            for (Object o : dep.getExcludedModules()) {
+                final ModuleId excludedModule = (ModuleId) o;
+                ivyModuleDescriptor.addExtraInfo(exclusionPrefix + index,
+                        excludedModule.getOrganisation() + EXTRA_INFO_DELIMITER + excludedModule.getName());
+                index += 1;
+            }
+        }
+        // dependency management info is also used for version mediation of transitive dependencies
+        ivyModuleDescriptor.addDependencyDescriptorMediator(
+                ModuleId.newInstance(dep.getGroupId(), dep.getArtifactId()),
+                ExactPatternMatcher.INSTANCE,
+                new OverrideDependencyDescriptorMediator(null, dep.getVersion()));
+    }
+
+    public void addPlugin(PomDependencyMgt plugin) {
+        String pluginValue = plugin.getGroupId() + EXTRA_INFO_DELIMITER + plugin.getArtifactId()
+                + EXTRA_INFO_DELIMITER + plugin.getVersion();
+        String pluginExtraInfo = (String) ivyModuleDescriptor.getExtraInfo().get("m:maven.plugins");
+        if (pluginExtraInfo == null) {
+            pluginExtraInfo = pluginValue;
+        } else {
+            pluginExtraInfo = pluginExtraInfo + "|" + pluginValue;
+        }
+        ivyModuleDescriptor.getExtraInfo().put("m:maven.plugins", pluginExtraInfo);
+    }
+
+    public static List<PomDependencyMgt> getPlugins(ModuleDescriptor md) {
+        List<PomDependencyMgt> result = new ArrayList<PomDependencyMgt>();
+        String plugins = (String) md.getExtraInfo().get("m:maven.plugins");
+        if (plugins == null) {
+            return new ArrayList<PomDependencyMgt>();
+        }
+        String[] pluginsArray = plugins.split("\\|");
+        for (String plugin : pluginsArray) {
+            String[] parts = plugin.split(EXTRA_INFO_DELIMITER);
+            result.add(new PomPluginElement(parts[0], parts[1], parts[2]));
+        }
+
+        return result;
+    }
+
+    private static class PomPluginElement implements PomDependencyMgt {
+        private String groupId;
+        private String artifactId;
+        private String version;
+
+        public PomPluginElement(String groupId, String artifactId, String version) {
+            this.groupId = groupId;
+            this.artifactId = artifactId;
+            this.version = version;
+        }
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        public String getArtifactId() {
+            return artifactId;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public String getScope() {
+            return null;
+        }
+
+        public List /*<ModuleId>*/ getExcludedModules() {
+            return Collections.EMPTY_LIST; // probably not used?
+        }
+    }
+
+    private String getDefaultVersion(PomDependencyData dep) {
+        String key = getDependencyMgtExtraInfoKeyForVersion(dep.getGroupId(), dep.getArtifactId());
+        return (String) ivyModuleDescriptor.getExtraInfo().get(key);
+    }
+
+    private String getDefaultScope(PomDependencyData dep) {
+        String key = getDependencyMgtExtraInfoKeyForScope(dep.getGroupId(), dep.getArtifactId());
+        String result = (String) ivyModuleDescriptor.getExtraInfo().get(key);
+        if ((result == null) || !MAVEN2_CONF_MAPPING.containsKey(result)) {
+            result = "compile";
+        }
+        return result;
+    }
+
+    private static String getDependencyMgtExtraInfoKeyForVersion(
+            String groupId, String artifaceId) {
+        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId
+                + EXTRA_INFO_DELIMITER + artifaceId + EXTRA_INFO_DELIMITER + "version";
+    }
+
+    private static String getDependencyMgtExtraInfoKeyForScope(String groupId, String artifaceId) {
+        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId
+                + EXTRA_INFO_DELIMITER + artifaceId + EXTRA_INFO_DELIMITER + "scope";
+    }
+
+    private static String getPropertyExtraInfoKey(String propertyName) {
+        return PROPERTIES + EXTRA_INFO_DELIMITER + propertyName;
+    }
+
+    private static String getDependencyMgtExtraInfoPrefixForExclusion(
+            String groupId, String artifaceId) {
+        return DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + groupId
+                + EXTRA_INFO_DELIMITER + artifaceId + EXTRA_INFO_DELIMITER + "exclusion_";
+    }
+
+    private static List<ModuleId> getDependencyMgtExclusions(ModuleDescriptor descriptor, String groupId, String artifactId) {
+        String exclusionPrefix = getDependencyMgtExtraInfoPrefixForExclusion(groupId, artifactId);
+        List<ModuleId> exclusionIds = new LinkedList<ModuleId>();
+        Map<String, String> extras = descriptor.getExtraInfo();
+        for (Entry<String, String> entry : extras.entrySet()) {
+            String key = entry.getKey();
+            if (key.startsWith(exclusionPrefix)) {
+                String fullExclusion = entry.getValue();
+                String[] exclusionParts = fullExclusion.split(EXTRA_INFO_DELIMITER);
+                if (exclusionParts.length != 2) {
+                    Message.error(WRONG_NUMBER_OF_PARTS_MSG + exclusionParts.length + " : "
+                            + fullExclusion);
+                    continue;
+                }
+                exclusionIds.add(ModuleId.newInstance(exclusionParts[0], exclusionParts[1]));
+            }
+        }
+
+        return exclusionIds;
+    }
+
+    public static List getDependencyManagements(ModuleDescriptor md) {
+        List result = new ArrayList();
+
+        for (Iterator iterator = md.getExtraInfo().entrySet().iterator(); iterator.hasNext();) {
+            Entry entry = (Entry) iterator.next();
+            String key = (String) entry.getKey();
+            if (key.startsWith(DEPENDENCY_MANAGEMENT)) {
+                String[] parts = key.split(EXTRA_INFO_DELIMITER);
+                if (parts.length != DEPENDENCY_MANAGEMENT_KEY_PARTS_COUNT) {
+                    Message.warn("what seem to be a dependency management extra info doesn't match expected pattern: " + key);
+                } else {
+                    String versionKey = DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + parts[1]
+                            + EXTRA_INFO_DELIMITER + parts[2]
+                            + EXTRA_INFO_DELIMITER + "version";
+                    String scopeKey = DEPENDENCY_MANAGEMENT + EXTRA_INFO_DELIMITER + parts[1]
+                            + EXTRA_INFO_DELIMITER + parts[2]
+                            + EXTRA_INFO_DELIMITER + "scope";
+
+                    String version = (String) md.getExtraInfo().get(versionKey);
+                    String scope = (String) md.getExtraInfo().get(scopeKey);
+
+                    List<ModuleId> exclusions = getDependencyMgtExclusions(md, parts[1], parts[2]);
+                    result.add(new DefaultPomDependencyMgt(parts[1], parts[2], version, scope, exclusions));
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    public void addExtraInfos(Map extraAttributes) {
+        for (Iterator it = extraAttributes.entrySet().iterator(); it.hasNext();) {
+            Entry entry = (Entry) it.next();
+            String key = (String) entry.getKey();
+            String value = (String) entry.getValue();
+            addExtraInfo(key, value);
+        }
+    }
+
+    private void addExtraInfo(String key, String value) {
+        if (!ivyModuleDescriptor.getExtraInfo().containsKey(key)) {
+            ivyModuleDescriptor.addExtraInfo(key, value);
+        }
+    }
+
+    public static Map extractPomProperties(Map extraInfo) {
+        Map r = new HashMap();
+        for (Iterator it = extraInfo.entrySet().iterator(); it.hasNext();) {
+            Entry extraInfoEntry = (Entry) it.next();
+            if (((String) extraInfoEntry.getKey()).startsWith(PROPERTIES)) {
+                String prop = ((String) extraInfoEntry.getKey()).substring(PROPERTIES.length()
+                        + EXTRA_INFO_DELIMITER.length());
+                r.put(prop, extraInfoEntry.getValue());
+            }
+        }
+        return r;
+    }
+
+
+    public void addProperty(String propertyName, String value) {
+        addExtraInfo(getPropertyExtraInfoKey(propertyName), value);
+    }
+
+    public Artifact getMainArtifact() {
+        return mainArtifact;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
new file mode 100644
index 0000000..a32f4b3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.namespace.NameSpaceHelper;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.m2.PomDependencyMgt;
+import org.apache.ivy.plugins.parser.m2.PomReader;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.url.URLResource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.Message;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This a straight copy of org.apache.ivy.plugins.parser.m2.PomModuleDescriptorParser, with one change: we do NOT attempt to retrieve source and javadoc artifacts when parsing the POM. This cuts the
+ * number of remote call in half to resolve a module.
+ */
+public final class GradlePomModuleDescriptorParser implements ModuleDescriptorParser {
+
+    private static final GradlePomModuleDescriptorParser INSTANCE = new GradlePomModuleDescriptorParser();
+
+    public static GradlePomModuleDescriptorParser getInstance() {
+        return INSTANCE;
+    }
+
+    private GradlePomModuleDescriptorParser() {
+    }
+
+
+    public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
+            throws ParseException, IOException {
+        try {
+            XmlModuleDescriptorWriter.write(md, destFile);
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
+
+    public boolean accept(Resource res) {
+        return res.getName().endsWith(".pom") || res.getName().endsWith("pom.xml")
+                || res.getName().endsWith("project.xml");
+    }
+
+    public String toString() {
+        return "gradle pom parser";
+    }
+
+    public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) {
+        return DefaultArtifact.newPomArtifact(mrid, new Date(res.getLastModified()));
+    }
+
+    public String getType() {
+        return "pom";
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+                                            boolean validate) throws ParseException, IOException {
+        URLResource resource = new URLResource(descriptorURL);
+        return parseDescriptor(ivySettings, descriptorURL, resource, validate);
+    }
+
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
+                                            Resource res, boolean validate) throws ParseException, IOException {
+
+        GradlePomModuleDescriptorBuilder mdBuilder = new GradlePomModuleDescriptorBuilder(this, res, ivySettings);
+
+        try {
+            PomReader domReader = new PomReader(descriptorURL, res);
+            domReader.setProperty("parent.version", domReader.getParentVersion());
+            domReader.setProperty("parent.groupId", domReader.getParentGroupId());
+            domReader.setProperty("project.parent.version", domReader.getParentVersion());
+            domReader.setProperty("project.parent.groupId", domReader.getParentGroupId());
+
+            Map pomProperties = domReader.getPomProperties();
+            for (Object o : pomProperties.entrySet()) {
+                Map.Entry prop = (Map.Entry) o;
+                domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
+                mdBuilder.addProperty((String) prop.getKey(), (String) prop.getValue());
+            }
+
+            ModuleDescriptor parentDescr = null;
+            if (domReader.hasParent()) {
+                //Is there any other parent properties?
+
+                ModuleRevisionId parentModRevID = ModuleRevisionId.newInstance(
+                        domReader.getParentGroupId(),
+                        domReader.getParentArtifactId(),
+                        domReader.getParentVersion());
+                ResolvedModuleRevision parentModule = parseOtherPom(ivySettings,
+                        parentModRevID);
+                if (parentModule != null) {
+                    parentDescr = parentModule.getDescriptor();
+                } else {
+                    throw new IOException("Impossible to load parent for " + res.getName() + "."
+                            + " Parent=" + parentModRevID);
+                }
+                if (parentDescr != null) {
+                    Map parentPomProps = GradlePomModuleDescriptorBuilder.extractPomProperties(
+                            parentDescr.getExtraInfo());
+                    for (Object o : parentPomProps.entrySet()) {
+                        Map.Entry prop = (Map.Entry) o;
+                        domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
+                    }
+                }
+            }
+
+            String groupId = domReader.getGroupId();
+            String artifactId = domReader.getArtifactId();
+            String version = domReader.getVersion();
+            mdBuilder.setModuleRevId(groupId, artifactId, version);
+
+            mdBuilder.setHomePage(domReader.getHomePage());
+            mdBuilder.setDescription(domReader.getDescription());
+            mdBuilder.setLicenses(domReader.getLicenses());
+
+            ModuleRevisionId relocation = domReader.getRelocation();
+
+            if (relocation != null) {
+                if (groupId != null && artifactId != null
+                        && artifactId.equals(relocation.getName())
+                        && groupId.equals(relocation.getOrganisation())) {
+                    Message.error("Relocation to an other version number not supported in ivy : "
+                            + mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                            + " relocated to " + relocation
+                            + ". Please update your dependency to directly use the right version.");
+                    Message.warn("Resolution will only pick dependencies of the relocated element."
+                            + "  Artefact and other metadata will be ignored.");
+                    ResolvedModuleRevision relocatedModule = parseOtherPom(ivySettings, relocation);
+                    if (relocatedModule == null) {
+                        throw new ParseException("impossible to load module "
+                                + relocation + " to which "
+                                + mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                                + " has been relocated", 0);
+                    }
+                    DependencyDescriptor[] dds = relocatedModule.getDescriptor().getDependencies();
+                    for (DependencyDescriptor dd : dds) {
+                        mdBuilder.addDependency(dd);
+                    }
+                } else {
+                    Message.info(mdBuilder.getModuleDescriptor().getModuleRevisionId()
+                            + " is relocated to " + relocation
+                            + ". Please update your dependencies.");
+                    Message.verbose("Relocated module will be considered as a dependency");
+                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mdBuilder
+                            .getModuleDescriptor(), relocation, true, false, true);
+                    /* Map all public dependencies */
+                    Configuration[] m2Confs = GradlePomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS;
+                    for (Configuration m2Conf : m2Confs) {
+                        if (Visibility.PUBLIC.equals(m2Conf.getVisibility())) {
+                            dd.addDependencyConfiguration(m2Conf.getName(), m2Conf.getName());
+                        }
+                    }
+                    mdBuilder.addDependency(dd);
+                }
+            } else {
+                domReader.setProperty("project.groupId", groupId);
+                domReader.setProperty("pom.groupId", groupId);
+                domReader.setProperty("groupId", groupId);
+                domReader.setProperty("project.artifactId", artifactId);
+                domReader.setProperty("pom.artifactId", artifactId);
+                domReader.setProperty("artifactId", artifactId);
+                domReader.setProperty("project.version", version);
+                domReader.setProperty("pom.version", version);
+                domReader.setProperty("version", version);
+
+                if (parentDescr != null) {
+                    mdBuilder.addExtraInfos(parentDescr.getExtraInfo());
+
+                    // add dependency management info from parent
+                    List depMgt = GradlePomModuleDescriptorBuilder.getDependencyManagements(parentDescr);
+                    for (Object aDepMgt : depMgt) {
+                        mdBuilder.addDependencyMgt((PomDependencyMgt) aDepMgt);
+                    }
+
+                    // add plugins from parent
+                    List /*<PomDependencyMgt>*/ plugins =
+                            GradlePomModuleDescriptorBuilder.getPlugins(parentDescr);
+                    for (Object plugin : plugins) {
+                        mdBuilder.addPlugin((PomDependencyMgt) plugin);
+                    }
+                }
+
+                for (Object o : domReader.getDependencyMgt()) {
+                    PomDependencyMgt dep = (PomDependencyMgt) o;
+                    if ("import".equals(dep.getScope())) {
+                        ModuleRevisionId importModRevID = ModuleRevisionId.newInstance(
+                                dep.getGroupId(),
+                                dep.getArtifactId(),
+                                dep.getVersion());
+                        ResolvedModuleRevision importModule = parseOtherPom(ivySettings,
+                                importModRevID);
+                        if (importModule != null) {
+                            ModuleDescriptor importDescr = importModule.getDescriptor();
+
+                            // add dependency management info from imported module
+                            List depMgt = GradlePomModuleDescriptorBuilder.getDependencyManagements(importDescr);
+                            for (Object aDepMgt : depMgt) {
+                                mdBuilder.addDependencyMgt((PomDependencyMgt) aDepMgt);
+                            }
+                        } else {
+                            throw new IOException("Impossible to import module for " + res.getName() + "."
+                                    + " Import=" + importModRevID);
+                        }
+
+                    } else {
+                        mdBuilder.addDependencyMgt(dep);
+                    }
+                }
+
+                for (Object o : domReader.getDependencies()) {
+                    PomReader.PomDependencyData dep = (PomReader.PomDependencyData) o;
+                    mdBuilder.addDependency(dep);
+                }
+
+                if (parentDescr != null) {
+                    for (int i = 0; i < parentDescr.getDependencies().length; i++) {
+                        mdBuilder.addDependency(parentDescr.getDependencies()[i]);
+                    }
+                }
+
+                for (Object o : domReader.getPlugins()) {
+                    PomReader.PomPluginElement plugin = (PomReader.PomPluginElement) o;
+                    mdBuilder.addPlugin(plugin);
+                }
+
+                mdBuilder.addMainArtifact(artifactId, domReader.getPackaging());
+            }
+        } catch (SAXException e) {
+            throw newParserException(e);
+        }
+
+        return mdBuilder.getModuleDescriptor();
+    }
+
+    private ResolvedModuleRevision parseOtherPom(ParserSettings ivySettings,
+                                                 ModuleRevisionId parentModRevID) throws ParseException {
+        DependencyDescriptor dd = new DefaultDependencyDescriptor(parentModRevID, true);
+        ResolveData data = IvyContext.getContext().getResolveData();
+        if (data == null) {
+            ResolveEngine engine = IvyContext.getContext().getIvy().getResolveEngine();
+            ResolveOptions options = new ResolveOptions();
+            options.setDownload(false);
+            data = new ResolveData(engine, options);
+        }
+
+        DependencyResolver resolver = ivySettings.getResolver(parentModRevID);
+        if (resolver == null) {
+            // TODO: Throw exception here?
+            return null;
+        } else {
+            dd = NameSpaceHelper.toSystem(dd, ivySettings.getContextNamespace());
+            ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
+            return otherModule;
+        }
+    }
+
+    private ParseException newParserException(Exception e) {
+        Message.error(e.getMessage());
+        ParseException pe = new ParseException(e.getMessage(), 0);
+        pe.initCause(e);
+        return pe;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
new file mode 100644
index 0000000..6a67ff8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleScopedParserSettings.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * ParserSettings that control the scope of searches carried out during parsing.
+ * If the parser asks for a resolver for the currently resolving revision, the resolver scope is only the repository where the module was resolved.
+ * If the parser asks for a resolver for a different revision, the resolver scope is all repositories.
+ */
+public class ModuleScopedParserSettings implements ParserSettings {
+    private final ParserSettings settings;
+    private final DependencyResolver currentResolver;
+    private final ModuleRevisionId currentRevisionId;
+
+    public ModuleScopedParserSettings(ParserSettings settings, DependencyResolver currentResolver, ModuleRevisionId currentRevisionId) {
+        this.settings = settings;
+        this.currentResolver = currentResolver;
+        this.currentRevisionId = currentRevisionId;
+    }
+
+    public DependencyResolver getResolver(ModuleRevisionId mRevId) {
+        if (mRevId.equals(currentRevisionId)) {
+            return currentResolver;
+        }
+        return settings.getResolver(mRevId);
+    }
+
+    public ConflictManager getConflictManager(String name) {
+        return settings.getConflictManager(name);
+    }
+
+    public String substitute(String value) {
+        return settings.substitute(value);
+    }
+
+    public Map substitute(Map strings) {
+        return settings.substitute(strings);
+    }
+
+    public ResolutionCacheManager getResolutionCacheManager() {
+        return settings.getResolutionCacheManager();
+    }
+
+    public PatternMatcher getMatcher(String matcherName) {
+        return settings.getMatcher(matcherName);
+    }
+
+    public Namespace getNamespace(String namespace) {
+        return settings.getNamespace(namespace);
+    }
+
+    public StatusManager getStatusManager() {
+        return settings.getStatusManager();
+    }
+
+    public RelativeUrlResolver getRelativeUrlResolver() {
+        return settings.getRelativeUrlResolver();
+    }
+
+    public File resolveFile(String filename) {
+        return settings.resolveFile(filename);
+    }
+
+    public String getDefaultBranch(ModuleId moduleId) {
+        return settings.getDefaultBranch(moduleId);
+    }
+
+    public Namespace getContextNamespace() {
+        return settings.getContextNamespace();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java
new file mode 100644
index 0000000..3f83ffa
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.Resource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ParserRegistry {
+    private List<ModuleDescriptorParser> parsers = new ArrayList<ModuleDescriptorParser>();
+
+    public ParserRegistry() {
+        parsers.add(GradlePomModuleDescriptorParser.getInstance());
+        parsers.add(XmlModuleDescriptorParser.getInstance());
+    }
+
+    public ModuleDescriptorParser forResource(Resource resource) {
+        for (ModuleDescriptorParser parser : parsers) {
+            if (parser.accept(resource)) {
+                return parser;
+            }
+        }
+        throw new IllegalStateException("No registered parser for resource: " + resource.getName());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
new file mode 100644
index 0000000..f959167
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.ivyservice.modulecache;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
+import org.gradle.util.TimeProvider;
+
+import java.io.Serializable;
+
+class DefaultCachedModuleDescriptor implements ModuleDescriptorCache.CachedModuleDescriptor, Serializable {
+    private final ModuleDescriptor moduleDescriptor;
+    private final boolean isChangingModule;
+    private final long ageMillis;
+
+    public DefaultCachedModuleDescriptor(ModuleDescriptorCacheEntry entry, ModuleDescriptor moduleDescriptor, TimeProvider timeProvider) {
+        this.moduleDescriptor = moduleDescriptor;
+        this.isChangingModule = entry.isChanging;
+        ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
+    }
+
+    public boolean isMissing() {
+        return moduleDescriptor == null;
+    }
+
+    public ResolvedModuleVersion getModuleVersion() {
+        ModuleRevisionId moduleRevisionId = isMissing() ? null : moduleDescriptor.getModuleRevisionId();
+        return new DefaultResolvedModuleVersion(moduleRevisionId);
+    }
+
+    public ModuleDescriptor getModuleDescriptor() {
+        return moduleDescriptor;
+    }
+
+    public boolean isChangingModule() {
+        return isChangingModule;
+    }
+
+    public long getAgeMillis() {
+        return ageMillis;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
new file mode 100644
index 0000000..10b49a1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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.ivyservice.modulecache;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.util.TimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultModuleDescriptorCache.class);
+
+    private final TimeProvider timeProvider;
+    private final ArtifactCacheMetaData cacheMetadata;
+    private final CacheLockingManager cacheLockingManager;
+
+    private final ModuleDescriptorStore moduleDescriptorStore;
+    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> cache;
+
+    public DefaultModuleDescriptorCache(ArtifactCacheMetaData cacheMetadata, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        this.timeProvider = timeProvider;
+        this.cacheLockingManager = cacheLockingManager;
+        this.cacheMetadata = cacheMetadata;
+
+        // TODO:DAZ inject this
+        moduleDescriptorStore = new ModuleDescriptorStore(new ModuleDescriptorFileStore(cacheMetadata));
+    }
+
+    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> getCache() {
+        if (cache == null) {
+            cache = initCache();
+        }
+        return cache;
+    }
+
+    private PersistentIndexedCache<RevisionKey, ModuleDescriptorCacheEntry> initCache() {
+        File artifactResolutionCacheFile = new File(cacheMetadata.getCacheDir(), "module-metadata.bin");
+        return cacheLockingManager.createCache(artifactResolutionCacheFile, RevisionKey.class, ModuleDescriptorCacheEntry.class);
+    }
+
+    public CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
+        ModuleDescriptorCacheEntry moduleDescriptorCacheEntry = getCache().get(createKey(repository, moduleRevisionId));
+        if (moduleDescriptorCacheEntry == null) {
+            return null;
+        }
+        if (moduleDescriptorCacheEntry.isMissing) {
+            return new DefaultCachedModuleDescriptor(moduleDescriptorCacheEntry, null, timeProvider);
+        }
+        ModuleDescriptor descriptor = moduleDescriptorStore.getModuleDescriptor(repository, moduleRevisionId);
+        if (descriptor == null) {
+            // Descriptor file has been manually deleted - ignore the entry
+            return null;
+        }
+        return new DefaultCachedModuleDescriptor(moduleDescriptorCacheEntry, descriptor, timeProvider);
+    }
+
+    public void cacheModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId, ModuleDescriptor moduleDescriptor, boolean isChanging) {
+        if (moduleDescriptor == null) {
+            LOGGER.debug("Recording absence of module descriptor in cache: {} [changing = {}]", moduleRevisionId, isChanging);
+            getCache().put(createKey(repository, moduleRevisionId), createMissingEntry(isChanging));
+        } else {
+            LOGGER.debug("Recording module descriptor in cache: {} [changing = {}]", moduleDescriptor.getModuleRevisionId(), isChanging);
+            moduleDescriptorStore.putModuleDescriptor(repository, moduleDescriptor);
+            getCache().put(createKey(repository, moduleRevisionId), createEntry(isChanging));
+        }
+    }
+
+    private RevisionKey createKey(ModuleVersionRepository resolver, ModuleRevisionId moduleRevisionId) {
+        return new RevisionKey(resolver, moduleRevisionId);
+    }
+
+    private ModuleDescriptorCacheEntry createMissingEntry(boolean changing) {
+        return new ModuleDescriptorCacheEntry(changing, true, timeProvider);
+    }
+
+    private ModuleDescriptorCacheEntry createEntry(boolean changing) {
+        return new ModuleDescriptorCacheEntry(changing, false, timeProvider);
+    }
+
+    private static class RevisionKey implements Serializable {
+        private final String resolverId;
+        private final String moduleRevisionId;
+
+        private RevisionKey(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
+            this.resolverId = repository.getId();
+            this.moduleRevisionId = moduleRevisionId == null ? null : moduleRevisionId.encodeToString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || !(o instanceof RevisionKey)) {
+                return false;
+            }
+            RevisionKey other = (RevisionKey) o;
+            return resolverId.equals(other.resolverId) && moduleRevisionId.equals(other.moduleRevisionId);
+        }
+
+        @Override
+        public int hashCode() {
+            return resolverId.hashCode() ^ moduleRevisionId.hashCode();
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
new file mode 100644
index 0000000..6e739c6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCache.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.ivyservice.modulecache;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+
+public interface ModuleDescriptorCache {
+    void cacheModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId resolvedModuleVersionId, ModuleDescriptor moduleDescriptor, boolean isChanging);
+
+    CachedModuleDescriptor getCachedModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleId);
+
+    interface CachedModuleDescriptor {
+        ResolvedModuleVersion getModuleVersion();
+
+        ModuleDescriptor getModuleDescriptor();
+
+        boolean isChangingModule();
+
+        long getAgeMillis();
+
+        boolean isMissing();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
new file mode 100644
index 0000000..f510a23
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.ivyservice.modulecache;
+
+import org.gradle.util.TimeProvider;
+
+import java.io.Serializable;
+
+class ModuleDescriptorCacheEntry implements Serializable {
+    public boolean isChanging;
+    public boolean isMissing;
+    public long createTimestamp;
+
+    ModuleDescriptorCacheEntry(boolean isChanging, boolean isMissing, TimeProvider timeProvider) {
+        this.isChanging = isChanging;
+        this.isMissing = isMissing;
+        this.createTimestamp = timeProvider.getCurrentTime();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorFileStore.java
new file mode 100644
index 0000000..4bf0d7f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorFileStore.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.ivyservice.modulecache;
+
+import org.apache.ivy.core.IvyPatternHelper;
+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.internal.artifacts.ivyservice.ArtifactCacheMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+
+import java.io.File;
+import java.util.Collections;
+
+public class ModuleDescriptorFileStore {
+    private static final String DESCRIPTOR_ARTIFACT_PATTERN =
+            "module-metadata/[organisation]/[module](/[branch])/[revision]/[resolverId].ivy.xml";
+
+    private final ArtifactCacheMetaData cacheMetaData;
+
+    public ModuleDescriptorFileStore(ArtifactCacheMetaData cacheMetaData) {
+        this.cacheMetaData = cacheMetaData;
+    }
+    
+    public File getModuleDescriptorFile(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
+        String filePath = getFilePath(repository, moduleRevisionId);
+        return new File(cacheMetaData.getCacheDir(), filePath);
+    }
+
+    private String getFilePath(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
+        String resolverId = repository.getId();
+        Artifact artifact = new DefaultArtifact(moduleRevisionId, null, "ivy", "ivy", "xml", Collections.singletonMap("resolverId", resolverId));
+        return IvyPatternHelper.substitute(DESCRIPTOR_ARTIFACT_PATTERN, artifact);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java
new file mode 100644
index 0000000..08b1337
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorStore.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.ivyservice.modulecache;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyContextualiser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.net.URL;
+
+public class ModuleDescriptorStore {
+
+    private final ModuleDescriptorFileStore moduleDescriptorFileStore;
+    private final XmlModuleDescriptorParser parser = XmlModuleDescriptorParser.getInstance();
+
+    public ModuleDescriptorStore(ModuleDescriptorFileStore moduleDescriptorFileStore) {
+        this.moduleDescriptorFileStore = moduleDescriptorFileStore;
+    }
+
+    public ModuleDescriptor getModuleDescriptor(ModuleVersionRepository repository, ModuleRevisionId moduleRevisionId) {
+        File moduleDescriptorFile = moduleDescriptorFileStore.getModuleDescriptorFile(repository, moduleRevisionId);
+        if (moduleDescriptorFile.exists()) {
+            return parseModuleDescriptorFile(moduleDescriptorFile);
+        }
+        return null;
+    }
+
+    private ModuleDescriptor parseModuleDescriptorFile(File moduleDescriptorFile)  {
+        ParserSettings settings = IvyContextualiser.getIvyContext().getSettings();
+        try {
+            URL result = moduleDescriptorFile.toURI().toURL();
+            return parser.parseDescriptor(settings, result, false);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void putModuleDescriptor(ModuleVersionRepository repository, ModuleDescriptor moduleDescriptor) {
+        File moduleDescriptorFile = moduleDescriptorFileStore.getModuleDescriptorFile(repository, moduleDescriptor.getModuleRevisionId());
+        try {
+            XmlModuleDescriptorWriter.write(moduleDescriptor, moduleDescriptorFile);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..112e678
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactsToModuleDescriptorConverter {
+    void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..4e033fa
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ConfigurationsToModuleDescriptorConverter {
+    void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..b3f4f87
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactsToModuleDescriptorConverter implements ArtifactsToModuleDescriptorConverter {
+    public final static ArtifactsExtraAttributesStrategy IVY_FILE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
+        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
+            return new HashMap<String, String>();
+        }
+    };
+
+    public final static ArtifactsExtraAttributesStrategy RESOLVE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
+        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
+            return WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
+        }
+    };
+
+    private ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy;
+
+    public DefaultArtifactsToModuleDescriptorConverter(ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy) {
+        this.artifactsExtraAttributesStrategy = artifactsExtraAttributesStrategy;
+    }
+
+    public void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            for (PublishArtifact publishArtifact : configuration.getArtifacts()) {
+                Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleDescriptor.getModuleRevisionId());
+                moduleDescriptor.addArtifact(configuration.getName(), ivyArtifact);
+            }
+        }
+    }
+
+    public Artifact createIvyArtifact(PublishArtifact publishArtifact, ModuleRevisionId moduleRevisionId) {
+        Map<String, String> extraAttributes = artifactsExtraAttributesStrategy.createExtraAttributes(publishArtifact);
+        if (GUtil.isTrue(publishArtifact.getClassifier())) {
+            extraAttributes.put(Dependency.CLASSIFIER, publishArtifact.getClassifier());
+        }
+        return new DefaultArtifact(
+                moduleRevisionId,
+                publishArtifact.getDate(),
+                publishArtifact.getName(),
+                publishArtifact.getType(),
+                publishArtifact.getExtension(),
+                extraAttributes);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..47dce97
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+
+import java.util.Arrays;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurationsToModuleDescriptorConverter implements ConfigurationsToModuleDescriptorConverter {
+    public void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Iterable<? extends Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            moduleDescriptor.addConfiguration(getIvyConfiguration(configuration));
+        }
+    }
+
+    public org.apache.ivy.core.module.descriptor.Configuration getIvyConfiguration(Configuration configuration) {
+        String[] superConfigs = Configurations.getNames(configuration.getExtendsFrom(), false).toArray(new String[configuration.getExtendsFrom().size()]);
+        Arrays.sort(superConfigs);
+        return new org.apache.ivy.core.module.descriptor.Configuration(
+                configuration.getName(),
+                configuration.isVisible() ? org.apache.ivy.core.module.descriptor.Configuration.Visibility.PUBLIC : org.apache.ivy.core.module.descriptor.Configuration.Visibility.PRIVATE,
+                configuration.getDescription(),
+                superConfigs,
+                configuration.isTransitive(),
+                null);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
new file mode 100644
index 0000000..3b6392e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.util.GUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
+    public DefaultExcludeRule createExcludeRule(String configurationName, ExcludeRule excludeRule) {
+        String org = GUtil.elvis(excludeRule.getGroup(), PatternMatcher.ANY_EXPRESSION);
+        String module = GUtil.elvis(excludeRule.getModule(), PatternMatcher.ANY_EXPRESSION);
+        DefaultExcludeRule ivyExcludeRule = new DefaultExcludeRule(new ArtifactId(
+                new ModuleId(org, module), PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION),
+                ExactPatternMatcher.INSTANCE, null);
+        ivyExcludeRule.addConfiguration(configurationName);
+        return ivyExcludeRule;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
new file mode 100644
index 0000000..3abe439
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.IvyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactory implements ModuleDescriptorFactory {
+    private final IvyFactory ivyFactory;
+    private final SettingsConverter settingsConverter;
+
+    public DefaultModuleDescriptorFactory(IvyFactory ivyFactory, SettingsConverter settingsConverter) {
+        this.ivyFactory = ivyFactory;
+        this.settingsConverter = settingsConverter;
+    }
+
+    public DefaultModuleDescriptor createModuleDescriptor(Module module) {
+        IvyContext ivyContext = IvyContext.pushNewContext();
+        try {
+            Ivy ivy = ivyFactory.createIvy(settingsConverter.getForResolve());
+            ivyContext.setIvy(ivy);
+            return new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(module), module.getStatus(), null);
+        } finally {
+            IvyContext.popContext();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
new file mode 100644
index 0000000..3caa7ee
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class PublishModuleDescriptorConverter implements ModuleDescriptorConverter {
+    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
+    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
+
+    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
+    private ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter;
+
+    public PublishModuleDescriptorConverter(ModuleDescriptorConverter resolveModuleDescriptorConverter,
+                                            ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
+        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
+        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
+    }
+
+    public ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module) {
+        DefaultModuleDescriptor moduleDescriptor = (DefaultModuleDescriptor) resolveModuleDescriptorConverter
+                .convert(configurations, module);
+        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
+        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, configurations);
+        return moduleDescriptor;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
new file mode 100644
index 0000000..be6527e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class ResolveModuleDescriptorConverter implements ModuleDescriptorConverter {
+    private final ModuleDescriptorFactory moduleDescriptorFactory;
+    private final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
+    private final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
+
+    public ResolveModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
+                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
+                                            DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
+        this.moduleDescriptorFactory = moduleDescriptorFactory;
+        this.configurationsToModuleDescriptorConverter = configurationsToModuleDescriptorConverter;
+        this.dependenciesToModuleDescriptorConverter = dependenciesToModuleDescriptorConverter;
+    }
+
+    public ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module) {
+        assert configurations.size() > 0 : "No configurations found for module: " + module.getName() + ". Configure them or apply a plugin that does it.";
+        DefaultModuleDescriptor moduleDescriptor = moduleDescriptorFactory.createModuleDescriptor(module);
+        configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, configurations);
+        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(moduleDescriptor, configurations);
+        return moduleDescriptor;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
new file mode 100644
index 0000000..1806791
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
@@ -0,0 +1,94 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.util.WrapUtil;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractDependencyDescriptorFactoryInternal implements DependencyDescriptorFactoryInternal {
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public AbstractDependencyDescriptorFactoryInternal(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    public DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+        return addDependencyDescriptor(configuration.getName(), moduleDescriptor, dependency);
+    }
+
+    public EnhancedDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
+        EnhancedDependencyDescriptor newDescriptor = createDependencyDescriptor(dependency, configuration, moduleDescriptor, moduleRevisionId);
+        moduleDescriptor.addDependency(newDescriptor);
+        return newDescriptor;
+    }
+
+    protected abstract EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration,
+                                                                              ModuleDescriptor moduleDescriptor, ModuleRevisionId moduleRevisionId);
+
+    protected void addExcludesArtifactsAndDependencies(String configuration, ModuleDependency dependency,
+                                                       EnhancedDependencyDescriptor dependencyDescriptor) {
+        addArtifacts(configuration, dependency.getArtifacts(), dependencyDescriptor);
+        addExcludes(configuration, dependency.getExcludeRules(), dependencyDescriptor);
+        addDependencyConfiguration(configuration, dependency, dependencyDescriptor);
+    }
+
+    private void addArtifacts(String configuration, Set<DependencyArtifact> artifacts,
+                              EnhancedDependencyDescriptor dependencyDescriptor) {
+        for (DependencyArtifact artifact : artifacts) {
+            DefaultDependencyArtifactDescriptor artifactDescriptor;
+            try {
+                artifactDescriptor = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, artifact.getName(),
+                        artifact.getType(),
+                        artifact.getExtension() != null ? artifact.getExtension() : artifact.getType(),
+                        artifact.getUrl() != null ? new URL(artifact.getUrl()) : null,
+                        artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER,
+                                artifact.getClassifier()) : null);
+            } catch (MalformedURLException e) {
+                throw new InvalidUserDataException("URL for artifact can't be parsed: " + artifact.getUrl(), e);
+            }
+            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
+        }
+    }
+
+    private void addDependencyConfiguration(String configuration, ModuleDependency dependency,
+                                            DefaultDependencyDescriptor dependencyDescriptor) {
+        dependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
+    }
+
+    private void addExcludes(String configuration, Set<ExcludeRule> excludeRules,
+                             DefaultDependencyDescriptor dependencyDescriptor) {
+        for (ExcludeRule excludeRule : excludeRules) {
+            dependencyDescriptor.addExcludeRule(configuration, excludeRuleConverter.createExcludeRule(configuration,
+                    excludeRule));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java
new file mode 100644
index 0000000..77eed68
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ClientModule;
+
+public class ClientModuleDependencyDescriptor extends EnhancedDependencyDescriptor {
+    private final ModuleDescriptor targetModule;
+
+    public ClientModuleDependencyDescriptor(ClientModule moduleDependency, ModuleDescriptor md, ModuleDescriptor targetModule, ModuleRevisionId mrid, boolean force, boolean changing, boolean transitive) {
+        super(moduleDependency, md, mrid, force, changing, transitive);
+        this.targetModule = targetModule;
+    }
+
+    public ModuleDescriptor getTargetModule() {
+        return targetModule;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
new file mode 100644
index 0000000..12468ff
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+
+/**
+ * @author Hans Dockter
+*/
+public class ClientModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
+    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule;
+
+    public ClientModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule) {
+        super(excludeRuleConverter);
+        this.moduleDescriptorFactoryForClientModule = moduleDescriptorFactoryForClientModule;
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return IvyUtil.createModuleRevisionId(dependency);
+    }
+
+    public EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
+                                                           ModuleRevisionId moduleRevisionId) {
+        ClientModule clientModule = getClientModule(dependency);
+        ModuleDescriptor moduleDescriptor = moduleDescriptorFactoryForClientModule.createModuleDescriptor(
+                moduleRevisionId, clientModule.getDependencies());
+
+        EnhancedDependencyDescriptor dependencyDescriptor = new ClientModuleDependencyDescriptor(
+                clientModule,
+                parent,
+                moduleDescriptor,
+                moduleRevisionId,
+                clientModule.isForce(),
+                false,
+                clientModule.isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, clientModule, dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    private ClientModule getClientModule(ModuleDependency dependency) {
+        return (ClientModule) dependency;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ClientModule;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
new file mode 100644
index 0000000..cad3eba
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+
+import java.util.Collection;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDependenciesToModuleDescriptorConverter implements DependenciesToModuleDescriptorConverter {
+    private DependencyDescriptorFactory dependencyDescriptorFactory;
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public DefaultDependenciesToModuleDescriptorConverter(DependencyDescriptorFactory dependencyDescriptorFactory,
+                                                          ExcludeRuleConverter excludeRuleConverter) {
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    public void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Collection<? extends Configuration> configurations) {
+        assert !configurations.isEmpty();
+        addDependencies(moduleDescriptor, configurations);
+        addExcludeRules(moduleDescriptor, configurations);
+    }
+
+    private void addDependencies(DefaultModuleDescriptor moduleDescriptor, Collection<? extends Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            for (ModuleDependency dependency : configuration.getDependencies().withType(ModuleDependency.class)) {
+                dependencyDescriptorFactory.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+            }
+        }
+    }
+
+    private void addExcludeRules(DefaultModuleDescriptor moduleDescriptor, Collection<? extends Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            for (ExcludeRule excludeRule : configuration.getExcludeRules()) {
+                org.apache.ivy.core.module.descriptor.ExcludeRule rule = excludeRuleConverter.createExcludeRule(
+                        configuration.getName(), excludeRule);
+                moduleDescriptor.addExcludeRule(rule);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
new file mode 100644
index 0000000..3dc12b0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Collection;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependenciesToModuleDescriptorConverter {
+    void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Collection<? extends Configuration> configurations);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
new file mode 100644
index 0000000..75570ec
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ModuleDependency;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependencyDescriptorFactory {
+    /** Adds a dependency descriptor, using information in the configuration object to work around ivy limitations */
+    DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
+
+    /** Adds a dependency descriptor where no configuration object is available */
+    DefaultDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
+
+    ModuleRevisionId createModuleRevisionId(ModuleDependency dependency);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
new file mode 100644
index 0000000..4f377a8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
@@ -0,0 +1,63 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.util.WrapUtil;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencyDescriptorFactoryDelegate implements DependencyDescriptorFactory {
+    private Set<DependencyDescriptorFactoryInternal> dependencyDescriptorFactories;
+
+    public DependencyDescriptorFactoryDelegate(DependencyDescriptorFactoryInternal... dependencyDescriptorFactories) {
+        this.dependencyDescriptorFactories = WrapUtil.toSet(dependencyDescriptorFactories);
+    }
+
+    public DefaultDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                        ModuleDependency dependency) {
+        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
+        return factoryInternal.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+    }
+
+    public DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
+        return factoryInternal.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
+        return factoryInternal.createModuleRevisionId(dependency);
+    }
+
+    private DependencyDescriptorFactoryInternal findFactoryForDependency(ModuleDependency dependency) {
+        for (DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal : dependencyDescriptorFactories) {
+            if (dependencyDescriptorFactoryInternal.canConvert(dependency)) {
+                return dependencyDescriptorFactoryInternal;
+            }
+        }
+        throw new InvalidUserDataException("Can't map dependency of type: " + dependency.getClass());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/EnhancedDependencyDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/EnhancedDependencyDescriptor.java
new file mode 100644
index 0000000..372cb48
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/EnhancedDependencyDescriptor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleDependency;
+
+public class EnhancedDependencyDescriptor extends DefaultDependencyDescriptor {
+    private final ModuleDependency moduleDependency;
+
+    public EnhancedDependencyDescriptor(ModuleDependency moduleDependency, ModuleDescriptor md, ModuleRevisionId mrid, boolean force, boolean changing, boolean transitive) {
+        super(md, mrid, force, changing, transitive);
+        this.moduleDependency = moduleDependency;
+    }
+
+    public ModuleDependency getModuleDependency() {
+        return moduleDependency;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
new file mode 100644
index 0000000..2e6e4af
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+
+/**
+ * @author Hans Dockter
+*/
+public class ExternalModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
+    public ExternalModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        super(excludeRuleConverter);
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return IvyUtil.createModuleRevisionId(dependency);
+    }
+
+    public EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
+                                                           ModuleRevisionId moduleRevisionId) {
+        EnhancedDependencyDescriptor dependencyDescriptor = new EnhancedDependencyDescriptor(
+                dependency,
+                parent,
+                moduleRevisionId,
+                getExternalModuleDependency(dependency).isForce(),
+                getExternalModuleDependency(dependency).isChanging(),
+                getExternalModuleDependency(dependency).isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, getExternalModuleDependency(dependency), dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    private ExternalModuleDependency getExternalModuleDependency(ModuleDependency dependency) {
+        return (ExternalModuleDependency) dependency;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ExternalModuleDependency;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
rename to subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptor.java
new file mode 100644
index 0000000..10b070f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptor.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.project.ProjectInternal;
+
+public class ProjectDependencyDescriptor extends EnhancedDependencyDescriptor {
+    public ProjectDependencyDescriptor(ProjectDependency projectDependency, ModuleDescriptor md, ModuleRevisionId mrid, boolean force, boolean changing, boolean transitive) {
+        super(projectDependency, md, mrid, force, changing, transitive);
+    }
+
+    public ProjectInternal getTargetProject() {
+        return (ProjectInternal) ((ProjectDependency) getModuleDependency()).getDependencyProject();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
new file mode 100644
index 0000000..2fe1c20
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
+    public ProjectDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        super(excludeRuleConverter);
+    }
+
+    public EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
+                                                           ModuleRevisionId moduleRevisionId) {
+        ProjectDependency projectDependency = (ProjectDependency) dependency;
+        ProjectDependencyDescriptor dependencyDescriptor = new ProjectDependencyDescriptor(projectDependency, parent, moduleRevisionId, false, false, dependency.isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ProjectDependency;
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        ProjectDependency projectDependency = (ProjectDependency) dependency;
+        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
+        return IvyUtil.createModuleRevisionId(module);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
new file mode 100644
index 0000000..c628080
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.ivyservice.projectmodule;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.ReflectionUtil;
+
+public class DefaultProjectModuleRegistry implements ProjectModuleRegistry {
+    private final ModuleDescriptorConverter moduleDescriptorConverter;
+
+    public DefaultProjectModuleRegistry(ModuleDescriptorConverter moduleDescriptorConverter) {
+        this.moduleDescriptorConverter = moduleDescriptorConverter;
+    }
+
+    public ModuleDescriptor findProject(DependencyDescriptor descriptor) {
+        if (!(descriptor instanceof ProjectDependencyDescriptor)) {
+            return null;
+        }
+        ProjectDependencyDescriptor projectDependencyDescriptor = (ProjectDependencyDescriptor) descriptor;
+        ProjectInternal project = projectDependencyDescriptor.getTargetProject();
+        Module projectModule = project.getModule();
+        ModuleDescriptor projectDescriptor = moduleDescriptorConverter.convert(project.getConfigurations(), projectModule);
+
+        for (DependencyArtifactDescriptor artifactDescriptor : descriptor.getAllDependencyArtifacts()) {
+            for (Artifact artifact : projectDescriptor.getAllArtifacts()) {
+                if (artifact.getName().equals(artifactDescriptor.getName()) && artifact.getExt().equals(
+                        artifactDescriptor.getExt())) {
+                    String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
+                    ReflectionUtil.invoke(artifactDescriptor, "setExtraAttribute",
+                            new Object[]{DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, path});
+                }
+            }
+        }
+
+        return projectDescriptor;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
new file mode 100644
index 0000000..2705395
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ivyservice.projectmodule;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
+
+import java.io.File;
+
+public class ProjectDependencyResolver implements DependencyToModuleResolver {
+    private final ProjectModuleRegistry projectModuleRegistry;
+    private final DependencyToModuleResolver resolver;
+    private final ProjectArtifactResolver artifactResolver;
+
+    public ProjectDependencyResolver(ProjectModuleRegistry projectModuleRegistry, DependencyToModuleResolver resolver) {
+        this.projectModuleRegistry = projectModuleRegistry;
+        this.resolver = resolver;
+        artifactResolver = new ProjectArtifactResolver();
+    }
+
+    public ModuleVersionResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
+        ModuleDescriptor moduleDescriptor = projectModuleRegistry.findProject(dependencyDescriptor);
+
+        if (moduleDescriptor == null) {
+            return resolver.resolve(dependencyDescriptor);
+        }
+
+        return new ProjectDependencyModuleVersionResolveResult(moduleDescriptor, artifactResolver);
+    }
+
+    private static class ProjectArtifactResolver implements ArtifactResolver {
+        public ArtifactResolveResult resolve(Artifact artifact) throws ArtifactResolveException {
+            String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
+            return new FileBackedArtifactResolveResult(new File(path));
+        }
+    }
+
+    private static class ProjectDependencyModuleVersionResolveResult implements ModuleVersionResolveResult {
+        private final ModuleDescriptor moduleDescriptor;
+        private final ArtifactResolver artifactResolver;
+
+        public ProjectDependencyModuleVersionResolveResult(ModuleDescriptor moduleDescriptor, ArtifactResolver artifactResolver) {
+            this.moduleDescriptor = moduleDescriptor;
+            this.artifactResolver = artifactResolver;
+        }
+
+        public ModuleVersionResolveException getFailure() {
+            return null;
+        }
+
+        public ModuleRevisionId getId() throws ModuleVersionResolveException {
+            return moduleDescriptor.getModuleRevisionId();
+        }
+
+        public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+            return moduleDescriptor;
+        }
+
+        public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
+            return artifactResolver;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
new file mode 100644
index 0000000..5e413f0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.ivyservice.projectmodule;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+
+/**
+ * TODO - this probably should lookup a project by id, much like ClientModuleRegistry. Then, there would be no dependency on ivy DependencyDescriptor.
+ */
+public interface ProjectModuleRegistry {
+    ModuleDescriptor findProject(DependencyDescriptor descriptor);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
new file mode 100755
index 0000000..8f2d780
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.configurations.conflicts.StrictConflictResolution;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.clientmodule.ClientModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAdapter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LazyDependencyToModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyResolver;
+import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectModuleRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultDependencyResolver implements ArtifactDependencyResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDependencyResolver.class);
+    private final ModuleDescriptorConverter moduleDescriptorConverter;
+    private final ResolvedArtifactFactory resolvedArtifactFactory;
+    private final ResolveIvyFactory ivyFactory;
+    private final ProjectModuleRegistry projectModuleRegistry;
+
+    public DefaultDependencyResolver(ResolveIvyFactory ivyFactory, ModuleDescriptorConverter moduleDescriptorConverter, ResolvedArtifactFactory resolvedArtifactFactory,
+                                     ProjectModuleRegistry projectModuleRegistry) {
+        this.ivyFactory = ivyFactory;
+        this.moduleDescriptorConverter = moduleDescriptorConverter;
+        this.resolvedArtifactFactory = resolvedArtifactFactory;
+        this.projectModuleRegistry = projectModuleRegistry;
+    }
+
+    public ResolvedConfiguration resolve(ConfigurationInternal configuration) throws ResolveException {
+        LOGGER.debug("Resolving {}", configuration);
+
+        IvyAdapter ivyAdapter = ivyFactory.create(configuration);
+
+        DependencyToModuleResolver dependencyResolver = ivyAdapter.getDependencyToModuleResolver();
+        dependencyResolver = new ClientModuleResolver(dependencyResolver);
+        dependencyResolver = new ProjectDependencyResolver(projectModuleRegistry, dependencyResolver);
+        DependencyToModuleVersionIdResolver idResolver = new LazyDependencyToModuleResolver(dependencyResolver, ivyAdapter.getResolveData().getSettings().getVersionMatcher());
+        idResolver = new VersionForcingDependencyToModuleResolver(idResolver, configuration.getResolutionStrategy().getForcedModules());
+
+        ModuleConflictResolver conflictResolver;
+        if (configuration.getResolutionStrategy().getConflictResolution() instanceof StrictConflictResolution) {
+            conflictResolver = new StrictConflictResolver();
+        } else {
+            conflictResolver = new LatestModuleConflictResolver();
+        }
+
+        DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, idResolver, conflictResolver);
+        DefaultLenientConfiguration result = builder.resolve(configuration, ivyAdapter.getResolveData());
+        return new DefaultResolvedConfiguration(result);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
new file mode 100644
index 0000000..0d57a0b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
@@ -0,0 +1,900 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.internal.artifacts.DefaultResolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+public class DependencyGraphBuilder {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DependencyGraphBuilder.class);
+    private final ModuleDescriptorConverter moduleDescriptorConverter;
+    private final ResolvedArtifactFactory resolvedArtifactFactory;
+    private final DependencyToModuleVersionIdResolver dependencyResolver;
+    private final ForcedModuleConflictResolver conflictResolver;
+
+    public DependencyGraphBuilder(ModuleDescriptorConverter moduleDescriptorConverter, ResolvedArtifactFactory resolvedArtifactFactory, DependencyToModuleVersionIdResolver dependencyResolver, ModuleConflictResolver conflictResolver) {
+        this.moduleDescriptorConverter = moduleDescriptorConverter;
+        this.resolvedArtifactFactory = resolvedArtifactFactory;
+        this.dependencyResolver = dependencyResolver;
+        this.conflictResolver = new ForcedModuleConflictResolver(conflictResolver);
+    }
+
+    public DefaultLenientConfiguration resolve(ConfigurationInternal configuration, ResolveData resolveData) throws ResolveException {
+        ModuleDescriptor moduleDescriptor = moduleDescriptorConverter.convert(configuration.getAll(), configuration.getModule());
+
+        ResolveState resolveState = new ResolveState(moduleDescriptor, configuration.getName(), dependencyResolver, resolveData);
+        traverseGraph(resolveState);
+
+        DefaultLenientConfiguration result = new DefaultLenientConfiguration(configuration, resolveState.root.getResult());
+        assembleResult(resolveState, result);
+
+        return result;
+    }
+
+    /**
+     * Traverses the dependency graph, resolving conflicts and building the paths from the root configuration.
+     */
+    private void traverseGraph(ResolveState resolveState) {
+        Set<ModuleId> conflicts = new LinkedHashSet<ModuleId>();
+
+        resolveState.onMoreSelected(resolveState.root);
+
+        List<DependencyEdge> dependencies = new ArrayList<DependencyEdge>();
+        while (resolveState.peek() != null || !conflicts.isEmpty()) {
+            if (resolveState.peek() != null) {
+                ConfigurationNode node = resolveState.pop();
+                LOGGER.debug("Visiting configuration {}.", node);
+
+                // Calculate the outgoing edges of this configuration
+                dependencies.clear();
+                node.visitOutgoingDependencies(dependencies);
+
+                for (DependencyEdge dependency : dependencies) {
+                    LOGGER.debug("Visiting dependency {}", dependency);
+
+                    // Resolve dependency to a particular revision
+                    dependency.resolveModuleRevisionId();
+                    DefaultModuleRevisionResolveState moduleRevision = dependency.getTargetModuleRevision();
+                    if (moduleRevision == null) {
+                        // Failed to resolve.
+                        continue;
+                    }
+                    ModuleId moduleId= moduleRevision.id.getModuleId();
+
+                    // Check for a new conflict
+                    if (moduleRevision.state == ModuleState.New) {
+                        ModuleResolveState module = resolveState.getModule(moduleId);
+
+                        // A new module revision. Check for conflict
+                        Collection<DefaultModuleRevisionResolveState> versions = module.getVersions();
+                        if (versions.size() == 1) {
+                            // First version of this module. Select it for now
+                            LOGGER.debug("Selecting new module version {}", moduleRevision);
+                            module.select(moduleRevision);
+                        } else {
+                            // Not the first version of this module. We have a new conflict
+                            LOGGER.debug("Found new conflicting module version {}", moduleRevision);
+                            conflicts.add(moduleId);
+
+                            // Deselect the currently selected version, and remove all outgoing edges from the version
+                            // This will propagate through the graph and prune configurations that are no longer required
+                            DefaultModuleRevisionResolveState previouslySelected = module.clearSelection();
+                            if (previouslySelected != null) {
+                                for (ConfigurationNode configuration : previouslySelected.configurations) {
+                                    configuration.removeOutgoingEdges();
+                                }
+                            }
+                        }
+                    }
+
+                    dependency.attachToTargetConfigurations();
+                }
+            } else {
+                // We have some batched up conflicts. Resolve the first, and continue traversing the graph
+                ModuleId moduleId = conflicts.iterator().next();
+                conflicts.remove(moduleId);
+                ModuleResolveState module = resolveState.getModule(moduleId);
+                DefaultModuleRevisionResolveState selected = conflictResolver.select(module.getVersions(), resolveState.root.moduleRevision);
+                LOGGER.debug("Selected {} from conflicting modules {}.", selected, module.getVersions());
+
+                // Restart each configuration. For the evicted configuration, this means moving incoming dependencies across to the
+                // matching selected configuration. For the select configuration, this mean traversing its dependencies.
+                module.restart(selected);
+            }
+        }
+    }
+
+    /**
+     * Populates the result from the graph traversal state.
+     */
+    private void assembleResult(ResolveState resolveState, ResolvedConfigurationBuilder result) {
+        FailureState failureState = new FailureState(resolveState.root);
+        for (ConfigurationNode resolvedConfiguration : resolveState.getConfigurationNodes()) {
+            resolvedConfiguration.attachToParents(resolvedArtifactFactory, result);
+            resolvedConfiguration.collectFailures(failureState);
+        }
+        failureState.attachFailures(result);
+    }
+
+    private static class FailureState {
+        final Map<ModuleRevisionId, BrokenDependency> failuresByRevisionId = new LinkedHashMap<ModuleRevisionId, BrokenDependency>();
+        final ConfigurationNode root;
+
+        private FailureState(ConfigurationNode root) {
+            this.root = root;
+        }
+
+        public void attachFailures(ResolvedConfigurationBuilder result) {
+            for (Map.Entry<ModuleRevisionId, BrokenDependency> entry : failuresByRevisionId.entrySet()) {
+                Collection<List<ModuleRevisionId>> paths = calculatePaths(entry);
+                result.addUnresolvedDependency(new DefaultUnresolvedDependency(entry.getKey().toString(), entry.getValue().failure.withIncomingPaths(paths)));
+            }
+        }
+
+        private Collection<List<ModuleRevisionId>> calculatePaths(Map.Entry<ModuleRevisionId, BrokenDependency> entry) {
+            // Include the shortest path from each version that has a direct dependency on the broken dependency, back to the root
+            
+            Map<DefaultModuleRevisionResolveState, List<ModuleRevisionId>> shortestPaths = new LinkedHashMap<DefaultModuleRevisionResolveState, List<ModuleRevisionId>>();
+            List<ModuleRevisionId> rootPath = new ArrayList<ModuleRevisionId>();
+            rootPath.add(root.moduleRevision.id);
+            shortestPaths.put(root.moduleRevision, rootPath);
+
+            Set<DefaultModuleRevisionResolveState> directDependees = new LinkedHashSet<DefaultModuleRevisionResolveState>();
+            for (ConfigurationNode node : entry.getValue().requiredBy) {
+                directDependees.add(node.moduleRevision);
+            }
+
+            Set<DefaultModuleRevisionResolveState> seen = new HashSet<DefaultModuleRevisionResolveState>();
+            LinkedList<DefaultModuleRevisionResolveState> queue = new LinkedList<DefaultModuleRevisionResolveState>();
+            queue.addAll(directDependees);
+            while (!queue.isEmpty()) {
+                DefaultModuleRevisionResolveState version = queue.getFirst();
+                if (version == root.moduleRevision) {
+                    queue.removeFirst();
+                } else if (seen.add(version)) {
+                    for (ConfigurationNode configuration : version.configurations) {
+                        for (DependencyEdge dependencyEdge : configuration.incomingEdges) {
+                            queue.add(0, dependencyEdge.from.moduleRevision);
+                        }
+                    }
+                } else {
+                    queue.remove();
+                    List<ModuleRevisionId> shortest = null;
+                    for (ConfigurationNode configuration : version.configurations) {
+                        for (DependencyEdge dependencyEdge : configuration.incomingEdges) {
+                            List<ModuleRevisionId> candidate = shortestPaths.get(dependencyEdge.from.moduleRevision);
+                            if (candidate == null) {
+                                continue;
+                            }
+                            if (shortest == null) {
+                                shortest = candidate;
+                            } else if (shortest.size() > candidate.size()) {
+                                shortest = candidate;
+                            }
+                        }
+                    }
+                    if (shortest == null) {
+                        continue;
+                    }
+                    List<ModuleRevisionId> path = new ArrayList<ModuleRevisionId>();
+                    path.addAll(shortest);
+                    path.add(version.id);
+                    shortestPaths.put(version, path);
+                }
+            }
+
+            List<List<ModuleRevisionId>> paths = new ArrayList<List<ModuleRevisionId>>();
+            for (DefaultModuleRevisionResolveState version : directDependees) {
+                List<ModuleRevisionId> path = shortestPaths.get(version);
+                paths.add(path);
+            }
+            return paths;
+        }
+
+        public void addUnresolvedDependency(DependencyEdge dependency, ModuleRevisionId revisionId, ModuleVersionResolveException failure) {
+            BrokenDependency breakage = failuresByRevisionId.get(revisionId);
+            if (breakage == null) {
+                breakage = new BrokenDependency(failure);
+                failuresByRevisionId.put(revisionId, breakage);
+            }
+            breakage.requiredBy.add(dependency.from);
+        }
+        
+        private static class BrokenDependency {
+            final ModuleVersionResolveException failure;
+            final List<ConfigurationNode> requiredBy = new ArrayList<ConfigurationNode>();
+
+            private BrokenDependency(ModuleVersionResolveException failure) {
+                this.failure = failure;
+            }
+        }
+    }
+
+    private static class DependencyEdge {
+        private final ConfigurationNode from;
+        private final DependencyDescriptor dependencyDescriptor;
+        private final Set<String> targetConfigurationRules;
+        private final ResolveState resolveState;
+        private final ModuleVersionSpec selectorSpec;
+        private final Set<ConfigurationNode> targetConfigurations = new LinkedHashSet<ConfigurationNode>();
+        private ModuleVersionSelectorResolveState selector;
+        private DefaultModuleRevisionResolveState targetModuleRevision;
+
+        public DependencyEdge(ConfigurationNode from, DependencyDescriptor dependencyDescriptor, Set<String> targetConfigurationRules, ModuleVersionSpec selectorSpec, ResolveState resolveState) {
+            this.from = from;
+            this.dependencyDescriptor = dependencyDescriptor;
+            this.targetConfigurationRules = targetConfigurationRules;
+            this.selectorSpec = selectorSpec;
+            this.resolveState = resolveState;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s -> %s(%s)", from.toString(), dependencyDescriptor.getDependencyRevisionId(), targetConfigurationRules);
+        }
+
+        public DefaultModuleRevisionResolveState getTargetModuleRevision() {
+            return targetModuleRevision;
+        }
+
+        public void resolveModuleRevisionId() {
+            if (targetModuleRevision == null) {
+                selector = resolveState.getSelector(dependencyDescriptor);
+                targetModuleRevision = selector.resolveModuleRevisionId();
+                selector.module.addUnattachedDependency(this);
+            }
+        }
+
+        public boolean isTransitive() {
+            return from.isTransitive() && dependencyDescriptor.isTransitive();
+        }
+
+        public void attachToTargetConfigurations() {
+            if (targetModuleRevision.state != ModuleState.Selected) {
+                return;
+            }
+            calculateTargetConfigurations();
+            for (ConfigurationNode targetConfiguration : targetConfigurations) {
+                targetConfiguration.addIncomingEdge(this);
+            }
+            if (!targetConfigurations.isEmpty()) {
+                selector.module.removeUnattachedDependency(this);
+            }
+        }
+
+        public void removeFromTargetConfigurations() {
+            for (ConfigurationNode targetConfiguration : targetConfigurations) {
+                targetConfiguration.removeIncomingEdge(this);
+            }
+            targetConfigurations.clear();
+        }
+
+        public void restart(DefaultModuleRevisionResolveState selected) {
+            selector = selector.restart(selected);
+            targetModuleRevision = selected;
+            attachToTargetConfigurations();
+        }
+
+        private void calculateTargetConfigurations() {
+            targetConfigurations.clear();
+            ModuleDescriptor targetDescriptor = targetModuleRevision.getDescriptor();
+            if (targetDescriptor == null) {
+                // Broken version
+                return;
+            }
+
+            IvyNode node = new IvyNode(resolveState.resolveData, targetDescriptor);
+            Set<String> targets = new LinkedHashSet<String>();
+            for (String targetConfiguration : targetConfigurationRules) {
+                Collections.addAll(targets, node.getRealConfs(targetConfiguration));
+            }
+
+            for (String targetConfigurationName : targets) {
+                // TODO - this is the wrong spot for this check
+                if (targetDescriptor.getConfiguration(targetConfigurationName) == null) {
+                    throw new RuntimeException(String.format("Module version group:%s, module:%s, version:%s, configuration:%s declares a dependency on configuration '%s' which is not declared in the module descriptor for group:%s, module:%s, version:%s",
+                            from.moduleRevision.id.getOrganisation(), from.moduleRevision.id.getName(), from.moduleRevision.id.getRevision(), from.configurationName,
+                            targetConfigurationName, targetModuleRevision.id.getOrganisation(), targetModuleRevision.id.getName(), targetModuleRevision.id.getRevision()));
+                }
+                ConfigurationNode targetConfiguration = resolveState.getConfigurationNode(targetModuleRevision, targetConfigurationName);
+                targetConfigurations.add(targetConfiguration);
+            }
+        }
+
+        private Set<ResolvedArtifact> getArtifacts(ConfigurationNode childConfiguration, ResolvedArtifactFactory resolvedArtifactFactory) {
+            String[] targetConfigurations = from.heirarchy.toArray(new String[from.heirarchy.size()]);
+            DependencyArtifactDescriptor[] dependencyArtifacts = dependencyDescriptor.getDependencyArtifacts(targetConfigurations);
+            if (dependencyArtifacts.length == 0) {
+                return Collections.emptySet();
+            }
+            Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
+            for (DependencyArtifactDescriptor artifactDescriptor : dependencyArtifacts) {
+                MDArtifact artifact = new MDArtifact(childConfiguration.descriptor, artifactDescriptor.getName(), artifactDescriptor.getType(), artifactDescriptor.getExt(), artifactDescriptor.getUrl(), artifactDescriptor.getQualifiedExtraAttributes());
+                artifacts.add(resolvedArtifactFactory.create(childConfiguration.getResult(), artifact, selector.resolve().getArtifactResolver()));
+            }
+            return artifacts;
+        }
+
+        public void attachToParents(ConfigurationNode childConfiguration, ResolvedArtifactFactory artifactFactory, ResolvedConfigurationBuilder result) {
+            DefaultResolvedDependency parent = from.getResult();
+            DefaultResolvedDependency child = childConfiguration.getResult();
+            parent.addChild(child);
+
+            Set<ResolvedArtifact> artifacts = getArtifacts(childConfiguration, artifactFactory);
+            if (!artifacts.isEmpty()) {
+                child.addParentSpecificArtifacts(parent, artifacts);
+            }
+
+            if (artifacts.isEmpty()) {
+                child.addParentSpecificArtifacts(parent, childConfiguration.getArtifacts(artifactFactory));
+            }
+            for (ResolvedArtifact artifact : child.getParentArtifacts(parent)) {
+                result.addArtifact(artifact);
+            }
+
+            if (parent == result.getRoot()) {
+                EnhancedDependencyDescriptor enhancedDependencyDescriptor = (EnhancedDependencyDescriptor) dependencyDescriptor;
+                result.addFirstLevelDependency(enhancedDependencyDescriptor.getModuleDependency(), child);
+            }
+        }
+
+        public ModuleVersionSpec getSelector() {
+            String[] configurations = from.heirarchy.toArray(new String[from.heirarchy.size()]);
+            ModuleVersionSpec selector = ModuleVersionSpec.forExcludes(dependencyDescriptor.getExcludeRules(configurations));
+            return selector.intersect(selectorSpec);
+        }
+
+        public void collectFailures(FailureState failureState) {
+            if (selector != null && selector.failure != null) {
+                failureState.addUnresolvedDependency(this, selector.descriptor.getDependencyRevisionId(), selector.failure);
+            }
+        }
+    }
+
+    private static class ResolveState {
+        private final Map<ModuleId, ModuleResolveState> modules = new LinkedHashMap<ModuleId, ModuleResolveState>();
+        private final Map<ResolvedConfigurationIdentifier, ConfigurationNode> nodes = new LinkedHashMap<ResolvedConfigurationIdentifier, ConfigurationNode>();
+        private final Map<ModuleRevisionId, ModuleVersionSelectorResolveState> selectors = new LinkedHashMap<ModuleRevisionId, ModuleVersionSelectorResolveState>();
+        private final ConfigurationNode root;
+        private final DependencyToModuleVersionIdResolver resolver;
+        private final ResolveData resolveData;
+        private final Set<ConfigurationNode> queued = new HashSet<ConfigurationNode>();
+        private final LinkedList<ConfigurationNode> queue = new LinkedList<ConfigurationNode>();
+
+        public ResolveState(ModuleDescriptor rootModule, String rootConfigurationName, DependencyToModuleVersionIdResolver resolver, ResolveData resolveData) {
+            this.resolver = resolver;
+            this.resolveData = resolveData;
+            DefaultModuleRevisionResolveState rootVersion = getRevision(rootModule.getModuleRevisionId());
+            rootVersion.setDescriptor(rootModule);
+            root = getConfigurationNode(rootVersion, rootConfigurationName);
+            root.moduleRevision.module.select(root.moduleRevision);
+        }
+
+        public ModuleResolveState getModule(ModuleId moduleId) {
+            ModuleId id = new ModuleId(moduleId.getOrganisation(), moduleId.getName());
+            ModuleResolveState module = modules.get(id);
+            if (module == null) {
+                module = new ModuleResolveState(id, this);
+                modules.put(id, module);
+            }
+            return module;
+        }
+
+        public DefaultModuleRevisionResolveState getRevision(ModuleRevisionId moduleRevisionId) {
+            ModuleRevisionId id = new ModuleRevisionId(new ModuleId(moduleRevisionId.getOrganisation(), moduleRevisionId.getName()), moduleRevisionId.getRevision());
+            return getModule(id.getModuleId()).getVersion(id);
+        }
+
+        public Collection<ConfigurationNode> getConfigurationNodes() {
+            return nodes.values();
+        }
+
+        public ConfigurationNode getConfigurationNode(DefaultModuleRevisionResolveState module, String configurationName) {
+            ModuleRevisionId original = module.id;
+            ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(original.getOrganisation(), original.getName(), original.getRevision(), configurationName);
+            ConfigurationNode configuration = nodes.get(id);
+            if (configuration == null) {
+                configuration = new ConfigurationNode(module, module.descriptor, configurationName, this);
+                nodes.put(id, configuration);
+            }
+            return configuration;
+        }
+
+        public ModuleVersionSelectorResolveState getSelector(DependencyDescriptor dependencyDescriptor) {
+            ModuleRevisionId original = dependencyDescriptor.getDependencyRevisionId();
+            ModuleRevisionId selectorId = ModuleRevisionId.newInstance(original.getOrganisation(), original.getName(), original.getRevision());
+            ModuleVersionSelectorResolveState resolveState = selectors.get(selectorId);
+            if (resolveState == null) {
+                resolveState = new ModuleVersionSelectorResolveState(dependencyDescriptor, getModule(selectorId.getModuleId()), resolver, this);
+                selectors.put(selectorId, resolveState);
+            }
+            return resolveState;
+        }
+
+        public ConfigurationNode peek() {
+            return queue.isEmpty() ? null : queue.getFirst();
+        }
+
+        public ConfigurationNode pop() {
+            ConfigurationNode next = queue.removeFirst();
+            queued.remove(next);
+            return next;
+        }
+
+        /**
+         * Called when a change is made to a configuration node, such that its dependency graph <em>may</em> now be larger than it previously was, and the node should be visited.
+         */
+        public void onMoreSelected(ConfigurationNode configuration) {
+            // Add to the end of the queue, so that we traverse the graph in breadth-wise order to pick up as many conflicts as
+            // possible before attempting to resolve them
+            if (queued.add(configuration)) {
+                queue.addLast(configuration);
+            }
+        }
+
+        /**
+         * Called when a change is made to a configuration node, such that its dependency graph <em>may</em> now be smaller than it previously was, and the node should be visited.
+         */
+        public void onFewerSelected(ConfigurationNode configuration) {
+            // Add to the front of the queue, to flush out configurations that are no longer required.
+            if (queued.add(configuration)) {
+                queue.addFirst(configuration);
+            }
+        }
+    }
+
+    enum ModuleState {
+        New,
+        Selected,
+        Conflict,
+        Evicted
+    }
+
+    private static class ModuleResolveState {
+        final ModuleId id;
+        final Set<DependencyEdge> unattachedDependencies = new LinkedHashSet<DependencyEdge>();
+        final Map<ModuleRevisionId, DefaultModuleRevisionResolveState> versions = new LinkedHashMap<ModuleRevisionId, DefaultModuleRevisionResolveState>();
+        final ResolveState resolveState;
+        DefaultModuleRevisionResolveState selected;
+
+        private ModuleResolveState(ModuleId id, ResolveState resolveState) {
+            this.id = id;
+            this.resolveState = resolveState;
+        }
+
+        public Collection<DefaultModuleRevisionResolveState> getVersions() {
+            return versions.values();
+        }
+
+        public void select(DefaultModuleRevisionResolveState selected) {
+            assert this.selected == null;
+            this.selected = selected;
+            for (DefaultModuleRevisionResolveState version : versions.values()) {
+                version.state = ModuleState.Evicted;
+            }
+            selected.state = ModuleState.Selected;
+        }
+
+        public DefaultModuleRevisionResolveState clearSelection() {
+            DefaultModuleRevisionResolveState previousSelection = selected;
+            selected = null;
+            for (DefaultModuleRevisionResolveState version : versions.values()) {
+                version.state = ModuleState.Conflict;
+            }
+            return previousSelection;
+        }
+
+        public void restart(DefaultModuleRevisionResolveState selected) {
+            select(selected);
+            for (DefaultModuleRevisionResolveState version : versions.values()) {
+                version.restart(selected);
+            }
+            for (DependencyEdge dependency : new ArrayList<DependencyEdge>(unattachedDependencies)) {
+                dependency.restart(selected);
+            }
+            unattachedDependencies.clear();
+        }
+
+        public void addUnattachedDependency(DependencyEdge edge) {
+            unattachedDependencies.add(edge);
+        }
+
+        public void removeUnattachedDependency(DependencyEdge edge) {
+            unattachedDependencies.remove(edge);
+        }
+
+        public DefaultModuleRevisionResolveState getVersion(ModuleRevisionId id) {
+            DefaultModuleRevisionResolveState moduleRevision = versions.get(id);
+            if (moduleRevision == null) {
+                moduleRevision = new DefaultModuleRevisionResolveState(this, id, resolveState);
+                versions.put(id, moduleRevision);
+            }
+
+            return moduleRevision;
+        }
+    }
+
+    private static class DefaultModuleRevisionResolveState implements ModuleRevisionResolveState {
+        final ModuleResolveState module;
+        final ModuleRevisionId id;
+        final ResolveState resolveState;
+        final Set<ConfigurationNode> configurations = new LinkedHashSet<ConfigurationNode>();
+        List<DependencyDescriptor> dependencies;
+        ModuleDescriptor descriptor;
+        ModuleState state = ModuleState.New;
+        ModuleVersionSelectorResolveState resolver;
+
+        private DefaultModuleRevisionResolveState(ModuleResolveState module, ModuleRevisionId id, ResolveState resolveState) {
+            this.module = module;
+            this.id = id;
+            this.resolveState = resolveState;
+        }
+
+        @Override
+        public String toString() {
+            return id.toString();
+        }
+
+        public String getRevision() {
+            return id.getRevision();
+        }
+
+        public Iterable<DependencyDescriptor> getDependencies() {
+            if (dependencies == null) {
+                dependencies = Arrays.asList(getDescriptor().getDependencies());
+            }
+            return dependencies;
+        }
+
+        public String getId() {
+            return String.format("%s:%s:%s", id.getOrganisation(), id.getName(), id.getRevision());
+        }
+
+        public void restart(DefaultModuleRevisionResolveState selected) {
+            for (ConfigurationNode conflictConfiguration : configurations) {
+                conflictConfiguration.restart(selected);
+            }
+        }
+
+        public void addResolver(ModuleVersionSelectorResolveState resolver) {
+            if (this.resolver == null) {
+                this.resolver = resolver;
+            }
+        }
+
+        public ModuleDescriptor getDescriptor() {
+            if (descriptor == null) {
+                if (resolver == null) {
+                    throw new IllegalStateException(String.format("No resolver for %s.", this));
+                }
+                resolver.resolve();
+            }
+            return descriptor;
+        }
+
+        public void addConfiguration(ConfigurationNode configurationNode) {
+            configurations.add(configurationNode);
+        }
+
+        public void setDescriptor(ModuleDescriptor descriptor) {
+            if (this.descriptor == null) {
+                this.descriptor = descriptor;
+            }
+        }
+    }
+
+    private static class ConfigurationNode {
+        final DefaultModuleRevisionResolveState moduleRevision;
+        final ResolveState resolveState;
+        final DefaultModuleDescriptor descriptor;
+        final String configurationName;
+        final Set<String> heirarchy = new LinkedHashSet<String>();
+        final Set<DependencyEdge> incomingEdges = new LinkedHashSet<DependencyEdge>();
+        final Set<DependencyEdge> outgoingEdges = new LinkedHashSet<DependencyEdge>();
+        DefaultResolvedDependency result;
+        ModuleVersionSpec previousTraversal;
+        Set<ResolvedArtifact> artifacts;
+
+        private ConfigurationNode(DefaultModuleRevisionResolveState moduleRevision, ModuleDescriptor descriptor, String configurationName, ResolveState resolveState) {
+            this.moduleRevision = moduleRevision;
+            this.resolveState = resolveState;
+            this.descriptor = (DefaultModuleDescriptor) descriptor;
+            this.configurationName = configurationName;
+            findAncestors(configurationName, resolveState, heirarchy);
+            moduleRevision.addConfiguration(this);
+        }
+
+        void findAncestors(String config, ResolveState container, Set<String> ancestors) {
+            ancestors.add(config);
+            for (String parentConfig : descriptor.getConfiguration(config).getExtends()) {
+                ancestors.addAll(container.getConfigurationNode(moduleRevision, parentConfig).heirarchy);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s(%s)", moduleRevision, configurationName);
+        }
+
+        public Set<ResolvedArtifact> getArtifacts(ResolvedArtifactFactory resolvedArtifactFactory) {
+            if (artifacts == null) {
+                artifacts = new LinkedHashSet<ResolvedArtifact>();
+                for (String config : heirarchy) {
+                    for (Artifact artifact : descriptor.getArtifacts(config)) {
+                        artifacts.add(resolvedArtifactFactory.create(getResult(), artifact, moduleRevision.resolver.resolve().getArtifactResolver()));
+                    }
+                }
+            }
+            return artifacts;
+        }
+
+        public DefaultResolvedDependency getResult() {
+            if (result == null) {
+                result = new DefaultResolvedDependency(
+                        moduleRevision.id.getOrganisation(),
+                        moduleRevision.id.getName(),
+                        moduleRevision.id.getRevision(),
+                        configurationName);
+            }
+
+            return result;
+        }
+
+        public boolean isTransitive() {
+            return descriptor.getConfiguration(configurationName).isTransitive();
+        }
+
+        public void visitOutgoingDependencies(Collection<DependencyEdge> target) {
+            // If this configuration's version is in conflict, don't do anything
+            // If not traversed before, add all selected outgoing edges
+            // If traversed before, and the selected modules have changed, remove previous outgoing edges and add outgoing edges again with
+            //    the new selections.
+            // If traversed before, and the selected modules have not changed, ignore
+            // If none of the incoming edges is transitive, then the node has no outgoing edges
+
+            if (moduleRevision.state != ModuleState.Selected) {
+                LOGGER.debug("version for {} is not selected. ignoring.", this);
+                return;
+            }
+
+            List<DependencyEdge> transitiveIncoming = new ArrayList<DependencyEdge>();
+            for (DependencyEdge edge : incomingEdges) {
+                if (edge.isTransitive()) {
+                    transitiveIncoming.add(edge);
+                }
+            }
+
+            if (transitiveIncoming.isEmpty() && this != resolveState.root) {
+                if (previousTraversal != null) {
+                    removeOutgoingEdges();
+                }
+                if (incomingEdges.isEmpty()) {
+                    LOGGER.debug("{} has no incoming edges. ignoring.", this);
+                } else {
+                    LOGGER.debug("{} has no transitive incoming edges. ignoring outgoing edges.", this);
+                }
+                return;
+            }
+
+            ModuleVersionSpec selectorSpec = getSelector(transitiveIncoming);
+            if (previousTraversal != null) {
+                if (previousTraversal.acceptsSameModulesAs(selectorSpec)) {
+                    LOGGER.debug("Changed edges for {} selects same versions as previous traversal. ignoring", this);
+                    return;
+                }
+                removeOutgoingEdges();
+            }
+
+            for (DependencyDescriptor dependency : moduleRevision.getDependencies()) {
+                ModuleId targetModuleId = dependency.getDependencyRevisionId().getModuleId();
+                Set<String> targetConfigurations = getTargetConfigurations(dependency);
+                if (!targetConfigurations.isEmpty()) {
+                    if (!selectorSpec.isSatisfiedBy(targetModuleId)) {
+                        LOGGER.debug("{} is excluded from {}.", targetModuleId, this);
+                    } else {
+                        DependencyEdge dependencyEdge = new DependencyEdge(this, dependency, targetConfigurations, selectorSpec, resolveState);
+                        outgoingEdges.add(dependencyEdge);
+                        target.add(dependencyEdge);
+                    }
+                }
+            }
+            previousTraversal = selectorSpec;
+        }
+
+        Set<String> getTargetConfigurations(DependencyDescriptor dependencyDescriptor) {
+            Set<String> targetConfigurations = new LinkedHashSet<String>();
+            for (String moduleConfiguration : dependencyDescriptor.getModuleConfigurations()) {
+                if (moduleConfiguration.equals("*") || heirarchy.contains(moduleConfiguration)) {
+                    for (String targetConfiguration : dependencyDescriptor.getDependencyConfigurations(moduleConfiguration)) {
+                        if (targetConfiguration.equals("*")) {
+                            Collections.addAll(targetConfigurations, descriptor.getPublicConfigurationsNames());
+                        } else {
+                            targetConfigurations.add(targetConfiguration);
+                        }
+                    }
+                }
+            }
+            return targetConfigurations;
+        }
+
+        public void addIncomingEdge(DependencyEdge dependencyEdge) {
+            incomingEdges.add(dependencyEdge);
+            resolveState.onMoreSelected(this);
+        }
+
+        public void removeIncomingEdge(DependencyEdge dependencyEdge) {
+            incomingEdges.remove(dependencyEdge);
+            resolveState.onFewerSelected(this);
+        }
+
+        public void attachToParents(ResolvedArtifactFactory artifactFactory, ResolvedConfigurationBuilder result) {
+            if (moduleRevision.state != ModuleState.Selected) {
+                LOGGER.debug("Ignoring {} as it is not selected.", this);
+                return;
+            }
+            LOGGER.debug("Attaching {} to its parents.", this);
+            for (DependencyEdge dependency : incomingEdges) {
+                dependency.attachToParents(this, artifactFactory, result);
+            }
+        }
+
+        public void collectFailures(FailureState failureState) {
+            if (moduleRevision.state != ModuleState.Selected) {
+                return;
+            }
+            for (DependencyEdge dependency : outgoingEdges) {
+                dependency.collectFailures(failureState);
+            }
+        }
+
+        private ModuleVersionSpec getSelector(List<DependencyEdge> transitiveEdges) {
+            ModuleVersionSpec selector;
+            if (transitiveEdges.isEmpty()) {
+                selector = ModuleVersionSpec.forExcludes();
+            } else {
+                selector = transitiveEdges.get(0).getSelector();
+                for (int i = 1; i < transitiveEdges.size(); i++) {
+                    DependencyEdge dependencyEdge = transitiveEdges.get(i);
+                    selector = selector.union(dependencyEdge.getSelector());
+                }
+            }
+            String[] configurations = heirarchy.toArray(new String[heirarchy.size()]);
+            selector = selector.intersect(ModuleVersionSpec.forExcludes(descriptor.getExcludeRules(configurations)));
+            return selector;
+        }
+
+        public void removeOutgoingEdges() {
+            for (DependencyEdge outgoingDependency : outgoingEdges) {
+                outgoingDependency.removeFromTargetConfigurations();
+            }
+            outgoingEdges.clear();
+            previousTraversal = null;
+        }
+
+        public void restart(DefaultModuleRevisionResolveState state) {
+            // Restarting this configuration after conflict resolution.
+            // If this configuration belongs to the select version, queue ourselves up for traversal.
+            // If not, then move our incoming edges across to the selected configuration
+            if (moduleRevision == state) {
+                resolveState.onMoreSelected(this);
+            } else {
+                for (DependencyEdge dependency : incomingEdges) {
+                    dependency.restart(state);
+                }
+                incomingEdges.clear();
+            }
+        }
+    }
+
+    private static class ModuleVersionSelectorResolveState {
+        final DependencyDescriptor descriptor;
+        final DependencyToModuleVersionIdResolver resolver;
+        final ResolveState resolveState;
+        final ModuleResolveState module;
+        ModuleVersionResolveException failure;
+        DefaultModuleRevisionResolveState targetModuleRevision;
+        ModuleVersionIdResolveResult idResolveResult;
+        ModuleVersionResolveResult resolveResult;
+
+        private ModuleVersionSelectorResolveState(DependencyDescriptor descriptor, ModuleResolveState module, DependencyToModuleVersionIdResolver resolver, ResolveState resolveState) {
+            this.descriptor = descriptor;
+            this.module = module;
+            this.resolver = resolver;
+            this.resolveState = resolveState;
+        }
+
+        @Override
+        public String toString() {
+            return descriptor.toString();
+        }
+
+        /**
+         * @return The module version, or null if there is a failure to resolve this selector.
+         */
+        public DefaultModuleRevisionResolveState resolveModuleRevisionId() {
+            if (targetModuleRevision != null) {
+                return targetModuleRevision;
+            }
+            if (failure != null) {
+                return null;
+            }
+
+            idResolveResult = resolver.resolve(descriptor);
+            if (idResolveResult.getFailure() != null) {
+                failure = idResolveResult.getFailure();
+                return null;
+            }
+
+            targetModuleRevision = resolveState.getRevision(idResolveResult.getId());
+            targetModuleRevision.addResolver(this);
+            return targetModuleRevision;
+        }
+
+        public ModuleVersionResolveResult resolve() {
+            if (resolveResult != null) {
+                return resolveResult;
+            }
+            if (failure != null) {
+                return null;
+            }
+
+            try {
+                resolveResult = idResolveResult.resolve();
+                resolveState.getRevision(resolveResult.getId()).setDescriptor(resolveResult.getDescriptor());
+            } catch (ModuleVersionResolveException e) {
+                failure = e;
+            }
+            return resolveResult;
+        }
+
+        public ModuleVersionSelectorResolveState restart(DefaultModuleRevisionResolveState moduleRevision) {
+            return resolveState.getSelector(descriptor.clone(moduleRevision.id));
+        }
+    }
+
+    private static class ForcedModuleConflictResolver {
+        private final ModuleConflictResolver resolver;
+
+        private ForcedModuleConflictResolver(ModuleConflictResolver resolver) {
+            this.resolver = resolver;
+        }
+
+        DefaultModuleRevisionResolveState select(Collection<DefaultModuleRevisionResolveState> candidates, DefaultModuleRevisionResolveState root) {
+            for (ConfigurationNode configuration : root.configurations) {
+                for (DependencyEdge outgoingEdge : configuration.outgoingEdges) {
+                    if (outgoingEdge.dependencyDescriptor.isForce() && candidates.contains(outgoingEdge.targetModuleRevision)) {
+                        return outgoingEdge.targetModuleRevision;
+                    }
+                }
+            }
+            return (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/LatestModuleConflictResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/LatestModuleConflictResolver.java
new file mode 100644
index 0000000..ad368a2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/LatestModuleConflictResolver.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+class LatestModuleConflictResolver implements ModuleConflictResolver {
+    public ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root) {
+        List<ModuleResolveStateBackedArtifactInfo> artifactInfos = new ArrayList<ModuleResolveStateBackedArtifactInfo>();
+        for (ModuleRevisionResolveState moduleRevision : candidates) {
+            artifactInfos.add(new ModuleResolveStateBackedArtifactInfo(moduleRevision));
+        }
+        List<ModuleResolveStateBackedArtifactInfo> sorted = new LatestRevisionStrategy().sort(artifactInfos.toArray(new ArtifactInfo[artifactInfos.size()]));
+        return sorted.get(sorted.size() - 1).moduleRevision;
+    }
+
+    private static class ModuleResolveStateBackedArtifactInfo implements ArtifactInfo {
+        final ModuleRevisionResolveState moduleRevision;
+
+        public ModuleResolveStateBackedArtifactInfo(ModuleRevisionResolveState moduleRevision) {
+            this.moduleRevision = moduleRevision;
+        }
+
+        public String getRevision() {
+            return moduleRevision.getRevision();
+        }
+
+        public long getLastModified() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java
new file mode 100644
index 0000000..4336b9e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleConflictResolver.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+import java.util.Collection;
+
+interface ModuleConflictResolver {
+    ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
new file mode 100644
index 0000000..84871a5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+interface ModuleRevisionResolveState {
+    String getId();
+
+    String getRevision();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
new file mode 100644
index 0000000..533c3fe
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.MatcherHelper;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.gradle.api.specs.Spec;
+
+import java.util.*;
+
+/**
+ * Manages sets of exclude rules, allowing union and intersection operations on the rules.
+ *
+ * <p>This class attempts to reduce execution time, by flattening union and intersection specs, at the cost of more analysis at construction time. This is taken advantage of by {@link
+ * DependencyGraphBuilder}, on the assumption that there are many more edges in the dependency graph than there are exclude rules (ie we evaluate the rules much more often that we construct them).
+ * </p> <p>Also, this class attempts to be quite accurate in determining if 2 specs will match exactly the same set of modules. {@link DependencyGraphBuilder} uses this to avoid traversing the
+ * dependency graph of a particular version that has already been traversed when a new incoming edge is added (eg a newly discovered dependency) and when an incoming edge is removed (eg a conflict
+ * evicts a version that depends on the given version). </p>
+ */
+public abstract class ModuleVersionSpec implements Spec<ModuleId> {
+    private static final AcceptAllSpec ALL_SPEC = new AcceptAllSpec();
+
+    public static ModuleVersionSpec forExcludes(ExcludeRule... excludeRules) {
+        return forExcludes(Arrays.asList(excludeRules));
+    }
+
+    /**
+     * Returns a spec that accepts only those module versions that do not match any of the
+     */
+    public static ModuleVersionSpec forExcludes(Collection<ExcludeRule> excludeRules) {
+        if (excludeRules.isEmpty()) {
+            return ALL_SPEC;
+        }
+        return new ExcludeRuleBackedSpec(excludeRules);
+    }
+
+    /**
+     * Returns a spec that accepts the union of those module versions that are accepted by this spec and the given spec.
+     */
+    public final ModuleVersionSpec union(ModuleVersionSpec other) {
+        if (other == this) {
+            return this;
+        }
+        if (other == ALL_SPEC) {
+            return other;
+        }
+        if (this == ALL_SPEC) {
+            return this;
+        }
+        List<ModuleVersionSpec> specs = new ArrayList<ModuleVersionSpec>();
+        unpackUnion(specs);
+        other.unpackUnion(specs);
+        for (int i = 0; i < specs.size();) {
+            ModuleVersionSpec spec = specs.get(i);
+            ModuleVersionSpec merged = null;
+            for (int j = i + 1; j < specs.size(); j++) {
+                merged = spec.doUnion(specs.get(j));
+                if (merged != null) {
+                    specs.remove(j);
+                    break;
+                }
+            }
+            if (merged != null) {
+                specs.set(i, merged);
+            } else {
+                i++;
+            }
+        }
+        if (specs.size() == 1) {
+            return specs.get(0);
+        }
+        return new UnionSpec(specs);
+    }
+
+    protected void unpackUnion(Collection<ModuleVersionSpec> specs) {
+        specs.add(this);
+    }
+
+    protected ModuleVersionSpec doUnion(ModuleVersionSpec other) {
+        return null;
+    }
+
+    /**
+     * Determines if this spec accepts the same set of modules as the given spec.
+     *
+     * @return true if the specs accept the same set of modules. Returns false if they may not, or if it is unknown.
+     */
+    public final boolean acceptsSameModulesAs(ModuleVersionSpec other) {
+        if (other == this) {
+            return true;
+        }
+        if (!other.getClass().equals(getClass())) {
+            return false;
+        }
+        return doAcceptsSameModulesAs(other);
+    }
+
+    /**
+     * Only called when this and the other spec have the same class.
+     */
+    protected boolean doAcceptsSameModulesAs(ModuleVersionSpec other) {
+        return false;
+    }
+
+    /**
+     * Returns a spec that accepts the intersection of those module versions that are accepted by this spec and the given spec.
+     */
+    public final ModuleVersionSpec intersect(ModuleVersionSpec other) {
+        if (other == this) {
+            return this;
+        }
+        if (other == ALL_SPEC) {
+            return this;
+        }
+        if (this == ALL_SPEC) {
+            return other;
+        }
+        return doIntersection(other);
+    }
+
+    protected ModuleVersionSpec doIntersection(ModuleVersionSpec other) {
+        return new IntersectSpec(this, other);
+    }
+
+    private static class AcceptAllSpec extends ModuleVersionSpec {
+        @Override
+        public String toString() {
+            return "{accept-all}";
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            return true;
+        }
+    }
+
+    private static abstract class CompositeSpec extends ModuleVersionSpec {
+        abstract Collection<ModuleVersionSpec> getSpecs();
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("{");
+            builder.append(getClass().getSimpleName());
+            for (ModuleVersionSpec spec : getSpecs()) {
+                builder.append(' ');
+                builder.append(spec);
+            }
+            builder.append("}");
+            return builder.toString();
+        }
+
+        @Override
+        protected boolean doAcceptsSameModulesAs(ModuleVersionSpec other) {
+            CompositeSpec spec = (CompositeSpec) other;
+            return implies(spec) && spec.implies(this);
+        }
+
+        /**
+         * Returns true if for every spec in this spec, there is a corresponding spec in the given spec that acceptsSameModulesAs().
+         */
+        protected boolean implies(CompositeSpec spec) {
+            for (ModuleVersionSpec thisSpec : getSpecs()) {
+                boolean found = false;
+                for (ModuleVersionSpec otherSpec : spec.getSpecs()) {
+                    if (thisSpec.acceptsSameModulesAs(otherSpec)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    static class ExcludeRuleBackedSpec extends CompositeSpec {
+        private final Set<ModuleVersionSpec> excludeSpecs = new HashSet<ModuleVersionSpec>();
+
+        private ExcludeRuleBackedSpec(Iterable<ExcludeRule> excludeRules) {
+            for (ExcludeRule rule : excludeRules) {
+                if (!(rule.getMatcher() instanceof ExactPatternMatcher)) {
+                    excludeSpecs.add(new ExcludeRuleSpec(rule));
+                    continue;
+                }
+                ModuleId moduleId = rule.getId().getModuleId();
+                boolean wildcardGroup = PatternMatcher.ANY_EXPRESSION.equals(moduleId.getOrganisation());
+                boolean wildcardModule = PatternMatcher.ANY_EXPRESSION.equals(moduleId.getName());
+                if (wildcardGroup && wildcardModule) {
+                    excludeSpecs.add(new ExcludeRuleSpec(rule));
+                } else if (wildcardGroup) {
+                    excludeSpecs.add(new ModuleNameSpec(moduleId.getName()));
+                } else if (wildcardModule) {
+                    excludeSpecs.add(new GroupNameSpec(moduleId.getOrganisation()));
+                } else {
+                    excludeSpecs.add(new ModuleIdSpec(moduleId));
+                }
+            }
+        }
+
+        public ExcludeRuleBackedSpec(Collection<ModuleVersionSpec> specs) {
+            this.excludeSpecs.addAll(specs);
+        }
+
+        @Override
+        Collection<ModuleVersionSpec> getSpecs() {
+            return excludeSpecs;
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            for (ModuleVersionSpec excludeSpec : excludeSpecs) {
+                if (excludeSpec.isSatisfiedBy(element)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected ModuleVersionSpec doUnion(ModuleVersionSpec other) {
+            if (!(other instanceof ExcludeRuleBackedSpec)) {
+                return super.doUnion(other);
+            }
+
+            ExcludeRuleBackedSpec excludeRuleBackedSpec = (ExcludeRuleBackedSpec) other;
+            if (excludeSpecs.equals(excludeRuleBackedSpec.excludeSpecs)) {
+                return this;
+            }
+
+            // Can only merge exact match rules, so don't try if this or the other spec contains any other type of rule
+            for (ModuleVersionSpec excludeSpec : excludeSpecs) {
+                if (excludeSpec instanceof ExcludeRuleSpec) {
+                    return super.doUnion(other);
+                }
+            }
+            for (ModuleVersionSpec excludeSpec : excludeRuleBackedSpec.excludeSpecs) {
+                if (excludeSpec instanceof ExcludeRuleSpec) {
+                    return super.doUnion(other);
+                }
+            }
+
+            // Calculate the intersection of the rules
+            List<ModuleVersionSpec> merged = new ArrayList<ModuleVersionSpec>();
+            for (ModuleVersionSpec thisSpec : excludeSpecs) {
+                for (ModuleVersionSpec otherSpec : excludeRuleBackedSpec.excludeSpecs) {
+                    intersect(thisSpec, otherSpec, merged);
+                }
+            }
+            if (merged.isEmpty()) {
+                return ALL_SPEC;
+            }
+            return new ExcludeRuleBackedSpec(merged);
+        }
+
+        private void intersect(ModuleVersionSpec spec1, ModuleVersionSpec spec2, List<ModuleVersionSpec> merged) {
+            if (spec1 instanceof GroupNameSpec) {
+                intersect((GroupNameSpec) spec1, spec2, merged);
+            } else if (spec2 instanceof GroupNameSpec) {
+                intersect((GroupNameSpec) spec2, spec1, merged);
+            } else if (spec1 instanceof ModuleNameSpec) {
+                intersect((ModuleNameSpec) spec1, spec2, merged);
+            } else if (spec2 instanceof ModuleNameSpec) {
+                intersect((ModuleNameSpec) spec2, spec1, merged);
+            } else if ((spec1 instanceof ModuleIdSpec) && (spec2 instanceof ModuleIdSpec)) {
+                ModuleIdSpec moduleSpec1 = (ModuleIdSpec) spec1;
+                ModuleIdSpec moduleSpec2 = (ModuleIdSpec) spec2;
+                if (moduleSpec1.moduleId.equals(moduleSpec2.moduleId)) {
+                    merged.add(moduleSpec1);
+                }
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        private void intersect(GroupNameSpec spec1, ModuleVersionSpec spec2, List<ModuleVersionSpec> merged) {
+            if (spec2 instanceof GroupNameSpec) {
+                GroupNameSpec groupNameSpec = (GroupNameSpec) spec2;
+                if (spec1.group.equals(groupNameSpec.group)) {
+                    merged.add(spec1);
+                }
+            } else if (spec2 instanceof ModuleNameSpec) {
+                ModuleNameSpec moduleNameSpec = (ModuleNameSpec) spec2;
+                merged.add(new ModuleIdSpec(ModuleId.newInstance(spec1.group, moduleNameSpec.module)));
+            } else if (spec2 instanceof ModuleIdSpec) {
+                ModuleIdSpec moduleIdSpec = (ModuleIdSpec) spec2;
+                if (moduleIdSpec.moduleId.getOrganisation().equals(spec1.group)) {
+                    merged.add(spec2);
+                }
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        private void intersect(ModuleNameSpec spec1, ModuleVersionSpec spec2, List<ModuleVersionSpec> merged) {
+            if (spec2 instanceof ModuleNameSpec) {
+                ModuleNameSpec moduleNameSpec = (ModuleNameSpec) spec2;
+                if (spec1.module.equals(moduleNameSpec.module)) {
+                    merged.add(spec1);
+                }
+            } else if (spec2 instanceof ModuleIdSpec) {
+                ModuleIdSpec moduleIdSpec = (ModuleIdSpec) spec2;
+                if (moduleIdSpec.moduleId.getName().equals(spec1.module)) {
+                    merged.add(spec2);
+                }
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        @Override
+        protected ModuleVersionSpec doIntersection(ModuleVersionSpec other) {
+            if (!(other instanceof ExcludeRuleBackedSpec)) {
+                return super.doIntersection(other);
+            }
+
+            ExcludeRuleBackedSpec otherExcludeRuleSpec = (ExcludeRuleBackedSpec) other;
+            Set<ModuleVersionSpec> allSpecs = new HashSet<ModuleVersionSpec>();
+            allSpecs.addAll(excludeSpecs);
+            allSpecs.addAll(otherExcludeRuleSpec.excludeSpecs);
+            return new ExcludeRuleBackedSpec(allSpecs);
+        }
+    }
+
+    static class UnionSpec extends CompositeSpec {
+        private final List<ModuleVersionSpec> specs;
+
+        public UnionSpec(List<ModuleVersionSpec> specs) {
+            this.specs = specs;
+        }
+
+        @Override
+        Collection<ModuleVersionSpec> getSpecs() {
+            return specs;
+        }
+
+        @Override
+        protected void unpackUnion(Collection<ModuleVersionSpec> specs) {
+            specs.addAll(this.specs);
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            for (ModuleVersionSpec spec : specs) {
+                if (spec.isSatisfiedBy(element)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    private static class IntersectSpec extends CompositeSpec {
+        private final List<ModuleVersionSpec> specs;
+
+        private IntersectSpec(ModuleVersionSpec... specs) {
+            this.specs = Arrays.asList(specs);
+        }
+
+        @Override
+        Collection<ModuleVersionSpec> getSpecs() {
+            return specs;
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            for (ModuleVersionSpec spec : specs) {
+                if (!spec.isSatisfiedBy(element)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private static class ModuleIdSpec extends ModuleVersionSpec {
+        private final ModuleId moduleId;
+
+        private ModuleIdSpec(ModuleId moduleId) {
+            this.moduleId = moduleId;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{module-id %s}", moduleId);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+            ModuleIdSpec other = (ModuleIdSpec) o;
+            return moduleId.equals(other.moduleId);
+        }
+
+        @Override
+        public int hashCode() {
+            return moduleId.hashCode();
+        }
+
+        @Override
+        protected boolean doAcceptsSameModulesAs(ModuleVersionSpec other) {
+            ModuleIdSpec moduleIdSpec = (ModuleIdSpec) other;
+            return moduleId.equals(moduleIdSpec.moduleId);
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            return element.equals(moduleId);
+        }
+    }
+
+    private static class ModuleNameSpec extends ModuleVersionSpec {
+        private final String module;
+
+        private ModuleNameSpec(String module) {
+            this.module = module;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{module %s}", module);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+            ModuleNameSpec other = (ModuleNameSpec) o;
+            return module.equals(other.module);
+        }
+
+        @Override
+        public int hashCode() {
+            return module.hashCode();
+        }
+
+        @Override
+        public boolean doAcceptsSameModulesAs(ModuleVersionSpec other) {
+            ModuleNameSpec moduleNameSpec = (ModuleNameSpec) other;
+            return module.equals(moduleNameSpec.module);
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            return element.getName().equals(module);
+        }
+    }
+
+    private static class GroupNameSpec extends ModuleVersionSpec {
+        private final String group;
+
+        private GroupNameSpec(String group) {
+            this.group = group;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{group %s}", group);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+            GroupNameSpec other = (GroupNameSpec) o;
+            return group.equals(other.group);
+        }
+
+        @Override
+        public int hashCode() {
+            return group.hashCode();
+        }
+
+        @Override
+        public boolean doAcceptsSameModulesAs(ModuleVersionSpec other) {
+            GroupNameSpec groupNameSpec = (GroupNameSpec) other;
+            return group.equals(groupNameSpec.group);
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            return element.getOrganisation().equals(group);
+        }
+    }
+
+    private static class ExcludeRuleSpec extends ModuleVersionSpec {
+        private final ExcludeRule rule;
+
+        private ExcludeRuleSpec(ExcludeRule rule) {
+            this.rule = rule;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{exclude-rule %s}", rule);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+            ExcludeRuleSpec other = (ExcludeRuleSpec) o;
+            // Can't use equals(), as DefaultExcludeRule.equals() does not consider the pattern matcher
+            return rule == other.rule;
+        }
+
+        @Override
+        public int hashCode() {
+            return rule.hashCode();
+        }
+
+        @Override
+        protected boolean doAcceptsSameModulesAs(ModuleVersionSpec other) {
+            ExcludeRuleSpec excludeRuleSpec = (ExcludeRuleSpec) other;
+            // Can't use equals(), as DefaultExcludeRule.equals() does not consider the pattern matcher
+            return rule == excludeRuleSpec.rule;
+        }
+
+        public boolean isSatisfiedBy(ModuleId element) {
+            return MatcherHelper.matches(rule.getMatcher(), rule.getId().getModuleId(), element);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java
new file mode 100644
index 0000000..86f6daf
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/StrictConflictResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine;
+
+import java.util.Collection;
+import java.util.Formatter;
+
+class StrictConflictResolver implements ModuleConflictResolver {
+    public ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root) {
+        Formatter formatter = new Formatter();
+        formatter.format("A conflict was found between the following modules:");
+        for (ModuleRevisionResolveState candidate : candidates) {
+            formatter.format("%n - %s", candidate.getId());
+        }
+        throw new RuntimeException(formatter.toString());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
new file mode 100644
index 0000000..a801d4a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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.mvnsettings;
+
+public class CannotLocateLocalMavenRepositoryException extends RuntimeException {
+    public CannotLocateLocalMavenRepositoryException(Throwable cause) {
+        super(cause);
+    }
+
+    public CannotLocateLocalMavenRepositoryException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
new file mode 100644
index 0000000..6c57b88
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
@@ -0,0 +1,93 @@
+/*
+ * 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.mvnsettings;
+
+import org.apache.maven.settings.DefaultMavenSettingsBuilder;
+import org.apache.maven.settings.MavenSettingsBuilder;
+import org.apache.maven.settings.Settings;
+import org.gradle.api.internal.artifacts.PlexusLoggerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Steve Ebersole
+ */
+public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryLocator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLocalMavenRepositoryLocator.class);
+    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^\\}]*)\\}");
+
+    private final MavenFileLocations mavenFileLocations;
+    private final Map<String, String> systemProperties;
+    private final Map<String, String> environmentVariables;
+
+    public DefaultLocalMavenRepositoryLocator(MavenFileLocations mavenFileLocations, Map<String, String> systemProperties, Map<String, String> environmentVariables) {
+        this.mavenFileLocations = mavenFileLocations;
+        this.systemProperties = systemProperties;
+        this.environmentVariables = environmentVariables;
+    }
+
+    public File getLocalMavenRepository() {
+        Settings settings = buildSettings();
+        String repoPath = settings.getLocalRepository().trim();
+        return new File(resolvePlaceholders(repoPath));
+    }
+
+    private String resolvePlaceholders(String value) {
+        StringBuffer result = new StringBuffer();
+        Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
+
+        while (matcher.find()) {
+            String placeholder = matcher.group(1);
+            String replacement = placeholder.startsWith("env.") ? environmentVariables.get(placeholder.substring(4)) : systemProperties.get(placeholder);
+            if (replacement == null) {
+                throw new CannotLocateLocalMavenRepositoryException(String.format("Cannot resolve placeholder '%s' in value '%s'", placeholder, value));
+            }
+            matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
+        }
+        matcher.appendTail(result);
+
+        return result.toString();
+    }
+
+    private Settings buildSettings() {
+        try {
+            return createSettingsBuilder().buildSettings();
+        } catch (Exception e) {
+            throw new CannotLocateLocalMavenRepositoryException(e);
+        }
+    }
+
+    private MavenSettingsBuilder createSettingsBuilder() throws Exception {
+        DefaultMavenSettingsBuilder builder = new DefaultMavenSettingsBuilder();
+        builder.enableLogging(new PlexusLoggerAdapter(LOGGER));
+
+        Field userSettingsFileField = DefaultMavenSettingsBuilder.class.getDeclaredField("userSettingsFile");
+        userSettingsFileField.setAccessible(true);
+        userSettingsFileField.set(builder, mavenFileLocations.getUserSettingsFile());
+
+        Field globalSettingsFileField = DefaultMavenSettingsBuilder.class.getDeclaredField("globalSettingsFile");
+        globalSettingsFileField.setAccessible(true);
+        globalSettingsFileField.set(builder, mavenFileLocations.getGlobalSettingsFile());
+
+        return builder;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java
new file mode 100644
index 0000000..86592c6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.mvnsettings;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.SystemProperties;
+
+import java.io.File;
+
+/**
+* @author Szczepan Faber, created at: 3/30/11
+*/
+public class DefaultMavenFileLocations implements MavenFileLocations {
+    public File getUserMavenDir() {
+        return new File(SystemProperties.getUserHome(), ".m2");
+    }
+
+    @Nullable
+    public File getGlobalMavenDir() {
+        String m2Home = System.getProperty("M2_HOME");
+        if (m2Home == null) {
+            return null;
+        }
+        return new File(m2Home);
+    }
+
+    public File getUserSettingsFile() {
+        return new File(getUserMavenDir(), "settings.xml");
+    }
+
+    @Nullable
+    public File getGlobalSettingsFile() {
+        File dir = getGlobalMavenDir();
+        if (dir == null) {
+            return null;
+        }
+        return new File(dir, "conf/settings.xml");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/LocalMavenRepositoryLocator.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/LocalMavenRepositoryLocator.java
new file mode 100644
index 0000000..6b2c124
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/LocalMavenRepositoryLocator.java
@@ -0,0 +1,22 @@
+/*
+ * 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.mvnsettings;
+
+import java.io.File;
+
+public interface LocalMavenRepositoryLocator {
+    File getLocalMavenRepository();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/MavenFileLocations.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/MavenFileLocations.java
new file mode 100644
index 0000000..6d9c1ef
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/MavenFileLocations.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.mvnsettings;
+
+import java.io.File;
+
+public interface MavenFileLocations {
+    public File getUserMavenDir();
+    public File getGlobalMavenDir();
+    public File getUserSettingsFile();
+    public File getGlobalSettingsFile();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPom.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPom.java
deleted file mode 100644
index f50d3cf..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPom.java
+++ /dev/null
@@ -1,41 +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;
-
-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;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactPom {
-    /**
-     * @return The main artifact, may be null.
-     */
-    PublishArtifact getArtifact();
-
-    MavenPom getPom();
-
-    void addArtifact(Artifact artifact, File src);
-
-    Set<PublishArtifact> getAttachedArtifacts();
-
-    PublishArtifact writePom(File pomFile);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPomContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPomContainer.java
deleted file mode 100644
index d76d77e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPomContainer.java
+++ /dev/null
@@ -1,31 +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;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.artifacts.maven.MavenDeployment;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactPomContainer {
-    void addArtifact(Artifact artifact, File src);
-
-    Set<MavenDeployment> createDeployableFilesInfos();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPomFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPomFactory.java
deleted file mode 100644
index b78f654..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ArtifactPomFactory.java
+++ /dev/null
@@ -1,25 +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;
-
-import org.gradle.api.artifacts.maven.MavenPom;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactPomFactory {
-    ArtifactPom createArtifactPom(MavenPom pom);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultArtifactPomFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultArtifactPomFactory.java
deleted file mode 100644
index 6429118..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultArtifactPomFactory.java
+++ /dev/null
@@ -1,28 +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;
-
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.DefaultArtifactPom;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultArtifactPomFactory implements ArtifactPomFactory {
-    public ArtifactPom createArtifactPom(MavenPom pom) {
-        return new DefaultArtifactPom(pom);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultLocalMavenCacheLocator.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultLocalMavenCacheLocator.java
deleted file mode 100644
index bf27eed..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultLocalMavenCacheLocator.java
+++ /dev/null
@@ -1,95 +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.api.internal.artifacts.publish.maven;
-
-import org.apache.maven.settings.DefaultMavenSettingsBuilder;
-import org.apache.maven.settings.MavenSettingsBuilder;
-import org.apache.maven.settings.Settings;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.mvnsettings.MavenSettingsProvider;
-import org.gradle.api.internal.artifacts.publish.maven.pombuilder.PlexusLoggerAdapter;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.lang.reflect.Field;
-
-/**
- * @author Steve Ebersole
- */
-public class DefaultLocalMavenCacheLocator implements LocalMavenCacheLocator {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLocalMavenCacheLocator.class);
-    private static final String USER_HOME_MARKER = "${user.home}/";
-
-    public File getLocalMavenCache() {
-        MavenSettingsProvider mavenSettingsProvider = new MavenSettingsProvider();
-        File userHome = new File(System.getProperty("user.home"));
-        File userSettings = mavenSettingsProvider.getUserSettingsFile();
-
-        if (userSettings.exists()) {
-            File overriddenMavenLocal = extractMavenLocal(userSettings, userHome);
-            if (overriddenMavenLocal != null) {
-                return overriddenMavenLocal;
-            }
-        }
-
-        return mavenSettingsProvider.getLocalMavenRepository();
-    }
-
-    private File extractMavenLocal(File userSettings, File userHome) {
-        Settings settings = extractSettings(userSettings);
-        String override = settings.getLocalRepository();
-        if (override != null) {
-            override = override.trim();
-            if (override.length() > 0) {
-                // Nice, it does not even handle the interpolation for us, so we'll handle some common cases...
-                if (override.startsWith(USER_HOME_MARKER)) {
-                    override = userHome.getAbsolutePath() + '/' + override.substring(USER_HOME_MARKER.length());
-                }
-                return new File(override);
-            }
-        }
-        return null;
-    }
-
-    private Settings extractSettings(File userSettings) {
-        try {
-            MavenSettingsBuilder builder = buildSettingsBuilder(userSettings);
-            return builder.buildSettings();
-        } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-    }
-
-    private MavenSettingsBuilder buildSettingsBuilder(File userSettings) throws Exception {
-        final String userSettingsPath = userSettings.getAbsolutePath();
-
-        DefaultMavenSettingsBuilder builder = new DefaultMavenSettingsBuilder();
-        builder.enableLogging(new PlexusLoggerAdapter(LOGGER));
-
-        Field userSettingsPathField = DefaultMavenSettingsBuilder.class.getDeclaredField("userSettingsPath");
-        userSettingsPathField.setAccessible(true);
-        userSettingsPathField.set(builder, userSettingsPath);
-
-        Field globalSettingsPathField = DefaultMavenSettingsBuilder.class.getDeclaredField("globalSettingsPath");
-        globalSettingsPathField.setAccessible(true);
-        globalSettingsPathField.set(builder, userSettingsPath);
-
-        builder.initialize();
-
-        return builder;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenFactory.java
deleted file mode 100644
index 8887b20..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 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;
-
-import org.gradle.api.artifacts.maven.*;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultPomDependenciesConverter;
-import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultExcludeRuleConverter;
-import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer;
-
-import java.util.Map;
-
-public class DefaultMavenFactory implements MavenFactory {
-
-    public Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, FileResolver fileResolver) {
-        return new DefaultMavenPomFactory(configurationContainer, conf2ScopeMappingContainer, createPomDependenciesConverter(), fileResolver);
-    }
-
-    public Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Map<Configuration, Conf2ScopeMapping> mappings, FileResolver fileResolver) {
-        return new DefaultMavenPomFactory(configurationContainer, createConf2ScopeMappingContainer(mappings), createPomDependenciesConverter(), fileResolver);
-    }
-
-    private PomDependenciesConverter createPomDependenciesConverter() {
-        return new DefaultPomDependenciesConverter(new DefaultExcludeRuleConverter());
-    }
-
-    public Conf2ScopeMappingContainer createConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings) {
-        return new DefaultConf2ScopeMappingContainer(mappings);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
deleted file mode 100644
index cee4a60..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
+++ /dev/null
@@ -1,252 +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;
-
-import groovy.lang.Closure;
-import org.apache.commons.io.IOUtils;
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.Model;
-import org.apache.maven.project.MavenProject;
-import org.codehaus.groovy.runtime.InvokerHelper;
-import org.gradle.api.Action;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.XmlProvider;
-import org.gradle.api.internal.XmlTransformer;
-import org.gradle.api.internal.artifacts.publish.maven.pombuilder.CustomModelBuilder;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.listener.ActionBroadcast;
-
-import java.io.*;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultMavenPom implements MavenPom {
-    private PomDependenciesConverter pomDependenciesConverter;
-    private FileResolver fileResolver;
-    private MavenProject mavenProject = new MavenProject();
-    private Conf2ScopeMappingContainer scopeMappings;
-    private ActionBroadcast<MavenPom> whenConfiguredActions = new ActionBroadcast<MavenPom>();
-    private XmlTransformer withXmlActions = new XmlTransformer();
-    private ConfigurationContainer configurations;
-
-    public DefaultMavenPom(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMappings, PomDependenciesConverter pomDependenciesConverter,
-                           FileResolver fileResolver) {
-        this.configurations = configurationContainer;
-        this.scopeMappings = scopeMappings;
-        this.pomDependenciesConverter = pomDependenciesConverter;
-        this.fileResolver = fileResolver;
-        mavenProject.setModelVersion("4.0.0");
-    }
-
-    public Conf2ScopeMappingContainer getScopeMappings() {
-        return scopeMappings;
-    }
-
-    public ConfigurationContainer getConfigurations() {
-        return configurations;
-    }
-
-    public DefaultMavenPom setConfigurations(ConfigurationContainer configurations) {
-        this.configurations = configurations;
-        return this;
-    }
-    
-    public DefaultMavenPom setGroupId(String groupId) {
-        getModel().setGroupId(groupId);
-        return this;
-    }
-
-    public String getGroupId() {
-        return getModel().getGroupId();
-    }
-
-    public DefaultMavenPom setArtifactId(String artifactId) {
-        getModel().setArtifactId(artifactId);
-        return this;
-    }
-
-    public String getArtifactId() {
-        return getModel().getArtifactId();
-    }
-
-    @SuppressWarnings("unchecked")
-    public DefaultMavenPom setDependencies(List<?> dependencies) {
-        getModel().setDependencies((List<Dependency>) dependencies);
-        return this;
-    }
-
-    public List<Dependency> getDependencies() {
-        return getModel().getDependencies();
-    }
-
-    public DefaultMavenPom setName(String name) {
-        getModel().setName(name);
-        return this;
-    }
-
-    public String getName() {
-        return getModel().getName();
-    }
-
-    public DefaultMavenPom setVersion(String version) {
-        getModel().setVersion(version);
-        return this;
-    }
-
-    public String getVersion() {
-        return getModel().getVersion();
-    }
-
-    public String getPackaging() {
-        return getModel().getPackaging();
-    }
-
-    public DefaultMavenPom setPackaging(String packaging) {
-        getModel().setPackaging(packaging);
-        return this;
-    }
-
-    public DefaultMavenPom project(Closure cl) {
-        CustomModelBuilder pomBuilder = new CustomModelBuilder(getModel());
-        InvokerHelper.invokeMethod(pomBuilder, "project", cl);
-        return this;
-    }
-
-    public Model getModel() {
-        return mavenProject.getModel();
-    }
-
-    public DefaultMavenPom setModel(Object model) {
-        this.mavenProject = new MavenProject((Model) model);
-        return this;
-    }
-
-    public MavenProject getMavenProject() {
-        return mavenProject;
-    }
-
-    public DefaultMavenPom setMavenProject(MavenProject mavenProject) {
-        this.mavenProject = mavenProject;
-        return this;
-    }
-
-    @SuppressWarnings("unchecked")
-    public List<Dependency> getGeneratedDependencies() {
-        if (configurations == null) {
-            return Collections.emptyList();
-        }
-        return (List<Dependency>) pomDependenciesConverter.convert(getScopeMappings(), configurations.getAll());
-    }
-
-    public DefaultMavenPom getEffectivePom() {
-        DefaultMavenPom effectivePom = new DefaultMavenPom(null, this.scopeMappings, pomDependenciesConverter, fileResolver);
-        try {
-            effectivePom.setMavenProject((MavenProject) mavenProject.clone());
-        } catch (CloneNotSupportedException e) {
-            throw new RuntimeException(e);
-        }
-        effectivePom.getDependencies().addAll(getGeneratedDependencies());
-        effectivePom.withXmlActions = withXmlActions;
-        whenConfiguredActions.execute(effectivePom);
-        return effectivePom;
-    }
-
-    public PomDependenciesConverter getPomDependenciesConverter() {
-        return pomDependenciesConverter;
-    }
-
-    public FileResolver getFileResolver() {
-        return fileResolver;
-    }
-
-    public DefaultMavenPom setFileResolver(FileResolver fileResolver) {
-        this.fileResolver = fileResolver;
-        return this;
-    }
-
-    public DefaultMavenPom writeTo(final Writer pomWriter) {
-        getEffectivePom().writeNonEffectivePom(pomWriter);
-        return this;
-    }
-
-    public DefaultMavenPom writeTo(Object path) {
-        OutputStream stream = null;
-
-        try {
-            File file = fileResolver.resolve(path);
-            if (file.getParentFile() != null) {
-                file.getParentFile().mkdirs();
-            }
-            stream = new FileOutputStream(file);
-            getEffectivePom().writeNonEffectivePom(stream);
-            return this;
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        } finally {
-            IOUtils.closeQuietly(stream);
-        }
-    }
-
-    private void writeNonEffectivePom(final Writer pomWriter) {
-        try {
-            final StringWriter stringWriter = new StringWriter();
-            mavenProject.writeModel(stringWriter);
-            withXmlActions.transform(stringWriter.toString(), pomWriter);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        } finally {
-            IOUtils.closeQuietly(pomWriter);
-        }
-    }
-
-    private void writeNonEffectivePom(OutputStream stream) {
-        try {
-            final StringWriter stringWriter = new StringWriter();
-            mavenProject.writeModel(stringWriter);
-            withXmlActions.transform(stringWriter.toString(), stream);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        } finally {
-            IOUtils.closeQuietly(stream);
-        }
-    }
-
-    public DefaultMavenPom whenConfigured(final Closure closure) {
-        whenConfiguredActions.add(closure);
-        return this;
-    }
-
-    public DefaultMavenPom whenConfigured(final Action<MavenPom> action) {
-        whenConfiguredActions.add(action);
-        return this;
-    }
-
-    public DefaultMavenPom withXml(final Closure closure) {
-        withXmlActions.addAction(closure);
-        return this;
-    }
-
-    public DefaultMavenPom withXml(final Action<XmlProvider> action) {
-        withXmlActions.addAction(action);
-        return this;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactory.java
deleted file mode 100644
index ba27909..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactory.java
+++ /dev/null
@@ -1,47 +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;
-
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer;
-import org.gradle.api.internal.file.FileResolver;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultMavenPomFactory implements Factory<MavenPom> {
-    private ConfigurationContainer configurationContainer;
-    private Conf2ScopeMappingContainer conf2ScopeMappingContainer;
-    private PomDependenciesConverter pomDependenciesConverter;
-    private FileResolver fileResolver;
-
-
-    public DefaultMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, PomDependenciesConverter pomDependenciesConverter,
-                                  FileResolver fileResolver) {
-        this.configurationContainer = configurationContainer;
-        this.conf2ScopeMappingContainer = conf2ScopeMappingContainer;
-        this.pomDependenciesConverter = pomDependenciesConverter;
-        this.fileResolver = fileResolver;
-    }
-
-    public MavenPom create() {
-        return new DefaultMavenPom(configurationContainer,
-                new DefaultConf2ScopeMappingContainer(conf2ScopeMappingContainer.getMappings()), pomDependenciesConverter, fileResolver);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ExcludeRuleConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ExcludeRuleConverter.java
deleted file mode 100644
index 2843dda..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/ExcludeRuleConverter.java
+++ /dev/null
@@ -1,26 +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;
-
-import org.gradle.api.artifacts.ExcludeRule;
-
-
-/**
- * @author Hans Dockter
- */
-public interface ExcludeRuleConverter {
-    Object convert(ExcludeRule excludeRule);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/LocalMavenCacheLocator.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/LocalMavenCacheLocator.java
deleted file mode 100644
index be5ea95..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/LocalMavenCacheLocator.java
+++ /dev/null
@@ -1,22 +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.api.internal.artifacts.publish.maven;
-
-import java.io.File;
-
-public interface LocalMavenCacheLocator {
-    File getLocalMavenCache();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/PomDependenciesConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/PomDependenciesConverter.java
deleted file mode 100644
index 5a71a17..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/PomDependenciesConverter.java
+++ /dev/null
@@ -1,29 +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;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface PomDependenciesConverter {
-    public List<?> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainer.java
deleted file mode 100644
index 8464b10..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainer.java
+++ /dev/null
@@ -1,115 +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.api.internal.artifacts.publish.maven.dependencies;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.util.WrapUtil;
-
-import java.util.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultConf2ScopeMappingContainer implements Conf2ScopeMappingContainer {
-    private Map<Configuration, Conf2ScopeMapping> mappings = new HashMap<Configuration, Conf2ScopeMapping>();
-
-    private boolean skipUnmappedConfs = true;
-
-    public DefaultConf2ScopeMappingContainer() {
-    }
-
-    public DefaultConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings) {
-        this.mappings.putAll(mappings);
-    }
-
-    public Conf2ScopeMapping getMapping(Collection<Configuration> configurations) {
-        Set<Conf2ScopeMapping> result = getMappingsWithHighestPriority(configurations);
-        if (result.size() > 1) {
-            throw new InvalidUserDataException(
-                    "The configuration to scope mapping is not unique. The following configurations "
-                            + "have the same priority: " + result);
-        }
-        return result.size() == 0 ? null : result.iterator().next();
-    }
-
-    private Set<Conf2ScopeMapping> getMappingsWithHighestPriority(Collection<Configuration> configurations) {
-        Integer lastPriority = null;
-        Set<Conf2ScopeMapping> result = new HashSet<Conf2ScopeMapping>();
-        for (Conf2ScopeMapping conf2ScopeMapping : getMappingsForConfigurations(configurations)) {
-            Integer thisPriority = conf2ScopeMapping.getPriority();
-            if (lastPriority != null && lastPriority.equals(thisPriority)) {
-                result.add(conf2ScopeMapping);
-            } else if (lastPriority == null || (thisPriority != null && lastPriority < thisPriority)) {
-                lastPriority = thisPriority;
-                result = WrapUtil.toSet(conf2ScopeMapping);
-            }
-        }
-        return result;
-    }
-
-    private List<Conf2ScopeMapping> getMappingsForConfigurations(Collection<Configuration> configurations) {
-        List<Conf2ScopeMapping> existingMappings = new ArrayList<Conf2ScopeMapping>();
-        for (Configuration configuration : configurations) {
-            if (mappings.get(configuration) != null) {
-                existingMappings.add(mappings.get(configuration));
-            } else {
-                existingMappings.add(new Conf2ScopeMapping(null, configuration, null));
-            }
-        }
-        return existingMappings;
-    }
-
-    public Conf2ScopeMappingContainer addMapping(int priority, Configuration configuration, String scope) {
-        mappings.put(configuration, new Conf2ScopeMapping(priority, configuration, scope));
-        return this;
-    }
-
-    public Map<Configuration, Conf2ScopeMapping> getMappings() {
-        return mappings;
-    }
-
-    public boolean isSkipUnmappedConfs() {
-        return skipUnmappedConfs;
-    }
-
-    public void setSkipUnmappedConfs(boolean skipUnmappedConfs) {
-        this.skipUnmappedConfs = skipUnmappedConfs;
-    }
-
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        DefaultConf2ScopeMappingContainer that = (DefaultConf2ScopeMappingContainer) o;
-
-        if (!mappings.equals(that.mappings)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public int hashCode() {
-        return mappings.hashCode();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverter.java
deleted file mode 100644
index 3494aef..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverter.java
+++ /dev/null
@@ -1,40 +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.dependencies;
-
-import org.apache.maven.model.Exclusion;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.internal.artifacts.publish.maven.ExcludeRuleConverter;
-
-
-/**
- * @author Hans Dockter
- */
-public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
-    public Exclusion convert(ExcludeRule excludeRule) {
-        if (isConvertable(excludeRule)) {
-            Exclusion exclusion = new Exclusion();
-            exclusion.setGroupId(excludeRule.getExcludeArgs().get(ExcludeRule.GROUP_KEY));
-            exclusion.setArtifactId(excludeRule.getExcludeArgs().get(ExcludeRule.MODULE_KEY));
-            return exclusion;
-        }
-        return null;
-    }
-
-    private boolean isConvertable(ExcludeRule excludeRule) {
-        return excludeRule.getExcludeArgs().containsKey(ExcludeRule.GROUP_KEY) && excludeRule.getExcludeArgs().containsKey(ExcludeRule.MODULE_KEY);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java
deleted file mode 100644
index 863f738..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java
+++ /dev/null
@@ -1,149 +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.api.internal.artifacts.publish.maven.dependencies;
-
-import org.apache.maven.model.Dependency;
-import org.apache.maven.model.Exclusion;
-import org.gradle.api.GradleException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.DependencyArtifact;
-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.publish.maven.ExcludeRuleConverter;
-import org.gradle.api.internal.artifacts.publish.maven.PomDependenciesConverter;
-
-import java.util.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPomDependenciesConverter implements PomDependenciesConverter {
-    private ExcludeRuleConverter excludeRuleConverter;
-
-    public DefaultPomDependenciesConverter(ExcludeRuleConverter excludeRuleConverter) {
-        this.excludeRuleConverter = excludeRuleConverter;
-    }
-
-    public List<org.apache.maven.model.Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> 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), dependencyToConfigurations.get(dependency));
-            } else {
-                addFromArtifactDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency), dependencyToConfigurations.get(dependency));
-            }
-        }
-        return mavenDependencies;
-    }
-    
-    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));
-            if (!useScope(conf2ScopeMappingContainer, conf2ScopeDependencyMapping)) {
-                continue;
-            }
-            dependencyToScope.put(findDependency(dependency, conf2ScopeDependencyMapping.getConfiguration()),
-                    conf2ScopeDependencyMapping.getScope());
-        }
-        return dependencyToScope;
-    }
-
-    private ModuleDependency findDependency(ModuleDependency dependency, Configuration configuration) {
-        for (ModuleDependency configurationDependency : configuration.getDependencies(ModuleDependency.class)) {
-            if (dependency.equals(configurationDependency)) {
-                return configurationDependency;
-            }
-        }
-        throw new GradleException("Dependency could not be found. We should never get here!");
-    }
-
-    private boolean useScope(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Conf2ScopeMapping conf2ScopeMapping) {
-        return conf2ScopeMapping.getScope() != null || !conf2ScopeMappingContainer.isSkipUnmappedConfs();
-    }
-
-    private Map<ModuleDependency, Set<Configuration>> createDependencyToConfigurationsMap(Set<Configuration> configurations) {
-        Map<ModuleDependency, Set<Configuration>> dependencySetMap = new HashMap<ModuleDependency, Set<Configuration>>();
-        for (Configuration configuration : configurations) {
-            for (ModuleDependency dependency : configuration.getDependencies(ModuleDependency.class)) {
-                if (dependencySetMap.get(dependency) == null) {
-                    dependencySetMap.put(dependency, new HashSet<Configuration>());
-                }
-                dependencySetMap.get(dependency).add(configuration);
-            }
-        }
-        return dependencySetMap;
-    }
-
-    private void addFromArtifactDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope, 
-            Set<Configuration> configurations) {
-        for (DependencyArtifact artifact : dependency.getArtifacts()) {
-            mavenDependencies.add(createMavenDependencyFromArtifactDescriptor(dependency, artifact, scope, configurations));
-        }
-    }
-
-    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,
-            Set<Configuration> configurations) {
-        return createMavenDependency(dependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier(), configurations);
-    }
-
-    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,
-            Set<Configuration> configurations) {
-        Dependency mavenDependency =  new Dependency();
-        mavenDependency.setGroupId(dependency.getGroup());
-        mavenDependency.setArtifactId(name);
-        mavenDependency.setVersion(dependency.getVersion());
-        mavenDependency.setType(type);
-        mavenDependency.setScope(scope);
-        mavenDependency.setOptional(false);
-        mavenDependency.setClassifier(classifier);
-        mavenDependency.setExclusions(getExclusions(dependency, configurations));
-        return mavenDependency;
-    }
-
-    private List<Exclusion> getExclusions(ModuleDependency dependency, Set<Configuration> configurations) {
-        List<Exclusion> mavenExclusions = new ArrayList<Exclusion>();
-        Set<ExcludeRule> excludeRules = new HashSet<ExcludeRule>(dependency.getExcludeRules());
-        for (Configuration configuration : configurations) {
-            excludeRules.addAll(configuration.getExcludeRules());
-        }
-        for (ExcludeRule excludeRule : excludeRules) {
-            Exclusion mavenExclusion = (Exclusion) excludeRuleConverter.convert(excludeRule);
-            if (mavenExclusion != null) {
-                mavenExclusions.add(mavenExclusion);
-            }
-        }
-        return mavenExclusions;
-    }
-
-    public ExcludeRuleConverter getExcludeRuleConverter() {
-        return excludeRuleConverter;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
deleted file mode 100644
index edb830e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
+++ /dev/null
@@ -1,295 +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.api.internal.artifacts.publish.maven.deploy;
-
-import groovy.lang.Closure;
-import org.apache.ivy.core.cache.ArtifactOrigin;
-import org.apache.ivy.core.cache.RepositoryCacheManager;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadReport;
-import org.apache.ivy.core.resolve.DownloadOptions;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.core.search.ModuleEntry;
-import org.apache.ivy.core.search.OrganisationEntry;
-import org.apache.ivy.core.search.RevisionEntry;
-import org.apache.ivy.plugins.namespace.Namespace;
-import org.apache.ivy.plugins.resolver.ResolverSettings;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.apache.maven.artifact.ant.AttachedArtifact;
-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.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.*;
-import org.gradle.api.internal.artifacts.ivyservice.NoOpRepositoryCacheManager;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomContainer;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.mvnsettings.EmptyMavenSettingsSupplier;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.mvnsettings.MavenSettingsSupplier;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.listener.ActionBroadcast;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.util.AntUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractMavenResolver implements MavenResolver {
-
-    private String name;
-    
-    private ArtifactPomContainer artifactPomContainer;
-
-    private PomFilterContainer pomFilterContainer;
-
-    private Settings settings;
-
-    private LoggingManagerInternal loggingManager;
-
-    private final ActionBroadcast<MavenDeployment> beforeDeploymentActions = new ActionBroadcast<MavenDeployment>();
-
-    protected MavenSettingsSupplier mavenSettingsSupplier = new EmptyMavenSettingsSupplier();
-
-    public AbstractMavenResolver(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
-        this.name = name;
-        this.pomFilterContainer = pomFilterContainer;
-        this.artifactPomContainer = artifactPomContainer;
-        this.loggingManager = loggingManager;
-    }
-
-    protected abstract InstallDeployTaskSupport createPreConfiguredTask(Project project);
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-    
-    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public boolean exists(Artifact artifact) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public ArtifactOrigin locate(Artifact artifact) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public void reportFailure() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public void reportFailure(Artifact art) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public String[] listTokenValues(String token, Map otherTokenValues) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public Map[] listTokenValues(String[] tokens, Map criteria) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public OrganisationEntry[] listOrganisations() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public ModuleEntry[] listModules(OrganisationEntry org) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public RevisionEntry[] listRevisions(ModuleEntry module) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public Namespace getNamespace() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-    public void dumpSettings() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
-    }
-
-
-    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
-        if (isIgnorable(artifact)) {
-            return;
-        }
-        getArtifactPomContainer().addArtifact(artifact, src);
-    }
-
-    private boolean isIgnorable(Artifact artifact) {
-        return artifact.getType().equals("ivy");
-    }
-
-    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
-        // do nothing
-    }
-
-    public void abortPublishTransaction() throws IOException {
-        // do nothing
-    }
-
-    public void commitPublishTransaction() throws IOException {
-        InstallDeployTaskSupport installDeployTaskSupport = createPreConfiguredTask(AntUtil.createProject());
-        Set<MavenDeployment> mavenDeployments = getArtifactPomContainer().createDeployableFilesInfos();
-        mavenSettingsSupplier.supply(installDeployTaskSupport);
-        for (MavenDeployment mavenDeployment : mavenDeployments) {
-            beforeDeploymentActions.execute(mavenDeployment);
-            addPomAndArtifact(installDeployTaskSupport, mavenDeployment);
-            execute(installDeployTaskSupport);
-        }
-        mavenSettingsSupplier.done();
-        settings = ((CustomInstallDeployTaskSupport) installDeployTaskSupport).getSettings();
-    }
-
-    private void execute(InstallDeployTaskSupport deployTask) {
-        loggingManager.captureStandardOutput(LogLevel.INFO).start();
-        try {
-            deployTask.execute();
-        } finally {
-            loggingManager.stop();
-        }
-    }
-
-    private void addPomAndArtifact(InstallDeployTaskSupport installOrDeployTask, MavenDeployment mavenDeployment) {
-        Pom pom = new Pom();
-        pom.setProject(installOrDeployTask.getProject());
-        pom.setFile(mavenDeployment.getPomArtifact().getFile());
-        installOrDeployTask.addPom(pom);
-        if (mavenDeployment.getMainArtifact() != null) {
-            installOrDeployTask.setFile(mavenDeployment.getMainArtifact().getFile());
-        }
-        for (PublishArtifact classifierArtifact : mavenDeployment.getAttachedArtifacts()) {
-            AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
-            attachedArtifact.setClassifier(classifierArtifact.getClassifier());
-            attachedArtifact.setFile(classifierArtifact.getFile());
-            attachedArtifact.setType(classifierArtifact.getType());
-        }
-    }
-
-    public void setSettings(ResolverSettings settings) {
-        // do nothing
-    }
-
-    public RepositoryCacheManager getRepositoryCacheManager() {
-        return new NoOpRepositoryCacheManager(getName());
-    }
-
-    public ArtifactPomContainer getArtifactPomContainer() {
-        return artifactPomContainer;
-    }
-
-    public void setArtifactPomContainer(ArtifactPomContainer artifactPomContainer) {
-        this.artifactPomContainer = artifactPomContainer;
-    }
-
-    public Settings getSettings() {
-        return settings;
-    }
-
-    public PublishFilter getFilter() {
-        return pomFilterContainer.getFilter();
-    }
-
-    public void setFilter(PublishFilter defaultFilter) {
-        pomFilterContainer.setFilter(defaultFilter);
-    }
-
-    public MavenPom getPom() {
-        return pomFilterContainer.getPom();
-    }
-
-    public void setPom(MavenPom defaultPom) {
-        pomFilterContainer.setPom(defaultPom);
-    }
-
-    public MavenPom addFilter(String name, PublishFilter publishFilter) {
-        return pomFilterContainer.addFilter(name, publishFilter);
-    }
-
-    public MavenPom addFilter(String name, Closure filter) {
-        return pomFilterContainer.addFilter(name, filter);
-    }
-
-    public void filter(Closure filter) {
-        pomFilterContainer.filter(filter);
-    }
-
-    public PublishFilter filter(String name) {
-        return pomFilterContainer.filter(name);
-    }
-
-    public MavenPom pom(String name) {
-        return pomFilterContainer.pom(name);
-    }
-
-    public MavenPom pom(Closure configureClosure) {
-        return pomFilterContainer.pom(configureClosure);
-    }
-
-    public MavenPom pom(String name, Closure configureClosure) {
-        return pomFilterContainer.pom(name, configureClosure);
-    }
-
-    public Iterable<PomFilter> getActivePomFilters() {
-        return pomFilterContainer.getActivePomFilters();
-    }
-
-    public PomFilterContainer getPomFilterContainer() {
-        return pomFilterContainer;
-    }
-
-    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/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployer.java
deleted file mode 100644
index 7dac95f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployer.java
+++ /dev/null
@@ -1,129 +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.maven.artifact.ant.DeployTask;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.apache.tools.ant.Project;
-import org.codehaus.plexus.PlexusContainer;
-import org.codehaus.plexus.PlexusContainerException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.maven.MavenDeployer;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomContainer;
-import org.gradle.logging.LoggingManagerInternal;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDeployer {
-    private RemoteRepository remoteRepository;
-
-    private RemoteRepository remoteSnapshotRepository;
-
-    private Factory<CustomDeployTask> deployTaskFactory = new DefaultDeployTaskFactory();
-
-    private Configuration configuration;
-
-    // todo remove this property once configuration can handle normal file system dependencies
-    private List<File> protocolProviderJars = new ArrayList<File>();
-
-    private boolean uniqueVersion = true;
-
-    public BaseMavenDeployer(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
-        super(name, pomFilterContainer, artifactPomContainer, loggingManager);
-    }
-
-    protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
-        CustomDeployTask deployTask = deployTaskFactory.create();
-        deployTask.setProject(project);
-        deployTask.setUniqueVersion(isUniqueVersion());
-        addProtocolProvider(deployTask);
-        addRemoteRepositories(deployTask);
-        return deployTask;
-    }
-
-    private void addProtocolProvider(CustomDeployTask deployTask) {
-        PlexusContainer plexusContainer = deployTask.getContainer();
-        for (File wagonProviderJar : getJars()) {
-            try {
-                plexusContainer.addJarResource(wagonProviderJar);
-            } catch (PlexusContainerException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    private List<File> getJars() {
-        return configuration != null ? new ArrayList(configuration.resolve()) : protocolProviderJars;
-    }
-
-    private void addRemoteRepositories(DeployTask deployTask) {
-        deployTask.addRemoteRepository(remoteRepository);
-        deployTask.addRemoteSnapshotRepository(remoteSnapshotRepository);
-    }
-
-    public RemoteRepository getRepository() {
-        return remoteRepository;
-    }
-
-    public void setRepository(Object remoteRepository) {
-        this.remoteRepository = (RemoteRepository) remoteRepository;
-    }
-
-    public RemoteRepository getSnapshotRepository() {
-        return remoteSnapshotRepository;
-    }
-
-    public void setSnapshotRepository(Object remoteSnapshotRepository) {
-        this.remoteSnapshotRepository = (RemoteRepository) remoteSnapshotRepository;
-    }
-
-    public Factory<CustomDeployTask> getDeployTaskFactory() {
-        return deployTaskFactory;
-    }
-
-    public void setDeployTaskFactory(Factory<CustomDeployTask> deployTaskFactory) {
-        this.deployTaskFactory = deployTaskFactory;
-    }
-
-    public void addProtocolProviderJars(Collection<File> jars) {
-        protocolProviderJars.addAll(jars);
-    }
-
-    public Configuration getConfiguration() {
-        return configuration;
-    }
-
-    public void setConfiguration(Configuration configuration) {
-        this.configuration = configuration;
-    }
-
-    public boolean isUniqueVersion() {
-        return uniqueVersion;
-    }
-
-    public void setUniqueVersion(boolean uniqueVersion) {
-        this.uniqueVersion = uniqueVersion;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstaller.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstaller.java
deleted file mode 100644
index f015d38..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstaller.java
+++ /dev/null
@@ -1,51 +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.maven.artifact.ant.InstallDeployTaskSupport;
-import org.apache.maven.artifact.ant.InstallTask;
-import org.apache.tools.ant.Project;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomContainer;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.mvnsettings.MaybeUserMavenSettingsSupplier;
-import org.gradle.logging.LoggingManagerInternal;
-
-/**
- * @author Hans Dockter
- */
-public class BaseMavenInstaller extends AbstractMavenResolver {
-    private Factory<CustomInstallTask> installTaskFactory = new DefaultInstallTaskFactory();
-
-    public BaseMavenInstaller(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
-        super(name, pomFilterContainer, artifactPomContainer, loggingManager);
-        mavenSettingsSupplier = new MaybeUserMavenSettingsSupplier();
-    }
-
-    protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
-        InstallTask installTask = installTaskFactory.create();
-        installTask.setProject(project);
-        return installTask;
-    }
-
-    public Factory<CustomInstallTask> getInstallTaskFactory() {
-        return installTaskFactory;
-    }
-
-    public void setInstallTaskFactory(Factory<CustomInstallTask> installTaskFactory) {
-        this.installTaskFactory = installTaskFactory;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainer.java
deleted file mode 100644
index aa84e94..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainer.java
+++ /dev/null
@@ -1,137 +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.api.internal.artifacts.publish.maven.deploy;
-
-import groovy.lang.Closure;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
-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.Factory;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.WrapUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class BasePomFilterContainer implements PomFilterContainer {
-    private Map<String, PomFilter> pomFilters = new HashMap<String, PomFilter>();
-
-    private PomFilter defaultPomFilter;
-
-    private Factory<MavenPom> mavenPomFactory;
-
-    public BasePomFilterContainer(Factory<MavenPom> mavenPomFactory) {
-        this.mavenPomFactory = mavenPomFactory;
-    }
-
-    public PublishFilter getFilter() {
-        return getDefaultPomFilter().getFilter();
-    }
-
-    public void setFilter(PublishFilter defaultFilter) {
-        getDefaultPomFilter().setFilter(defaultFilter);
-    }
-
-    public MavenPom getPom() {
-        return getDefaultPomFilter().getPomTemplate();
-    }
-
-    public void setPom(MavenPom defaultPom) {
-        getDefaultPomFilter().setPomTemplate(defaultPom);
-    }
-
-    public void filter(Closure filter) {
-        setFilter(toFilter(filter));
-    }
-
-    public MavenPom addFilter(String name, Closure filter) {
-        return addFilter(name, toFilter(filter));
-    }
-
-    private PublishFilter toFilter(final Closure filter) {
-        return (PublishFilter) DefaultGroovyMethods.asType(filter, PublishFilter.class);
-    }
-
-    public MavenPom pom(Closure configureClosure) {
-        return ConfigureUtil.configure(configureClosure, getPom());
-    }
-
-    public MavenPom pom(String name, Closure configureClosure) {
-        return ConfigureUtil.configure(configureClosure, pom(name));
-    }
-
-    public MavenPom addFilter(String name, PublishFilter publishFilter) {
-        if (name == null || publishFilter == null) {
-            throw new InvalidUserDataException("Name and Filter must not be null.");
-        }
-        MavenPom pom = mavenPomFactory.create();
-        pomFilters.put(name, new DefaultPomFilter(name, pom, publishFilter));
-        return pom;
-    }
-
-    public PublishFilter filter(String name) {
-        if (name == null) {
-            throw new InvalidUserDataException("Name must not be null.");
-        }
-        return pomFilters.get(name).getFilter();
-    }
-
-    public MavenPom pom(String name) {
-        if (name == null) {
-            throw new InvalidUserDataException("Name must not be null.");
-        }
-        return pomFilters.get(name).getPomTemplate();
-    }
-
-    public Iterable<PomFilter> getActivePomFilters() {
-        Iterable<PomFilter> activeArtifactPoms;
-        if (pomFilters.size() == 0 && getDefaultPomFilter() != null) {
-            activeArtifactPoms = WrapUtil.toSet(getDefaultPomFilter());
-        } else {
-            activeArtifactPoms = pomFilters.values();
-        }
-        return activeArtifactPoms;
-    }
-
-    public Factory<MavenPom> getMavenPomFactory() {
-        return mavenPomFactory;
-    }
-
-    public PomFilter getDefaultPomFilter() {
-        if (defaultPomFilter == null) {
-            defaultPomFilter = new DefaultPomFilter(PomFilterContainer.DEFAULT_ARTIFACT_POM_NAME, mavenPomFactory.create(),
-                PublishFilter.ALWAYS_ACCEPT);
-        }
-        return defaultPomFilter;
-    }
-
-    public void setDefaultPomFilter(PomFilter defaultPomFilter) {
-        this.defaultPomFilter = defaultPomFilter;
-    }
-
-    public Map<String, PomFilter> getPomFilters() {
-        return pomFilters;
-    }
-
-    protected BasePomFilterContainer newInstance() {
-        return new BasePomFilterContainer(mavenPomFactory);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ClassifierArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ClassifierArtifact.java
deleted file mode 100644
index 1d14f46..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ClassifierArtifact.java
+++ /dev/null
@@ -1,71 +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;
-
-/**
- * @author Hans Dockter
- */
-public class ClassifierArtifact {
-    private String classifier;
-    private String type;
-    private File file;
-
-    public ClassifierArtifact(String classifier, String type, File file) {
-        this.classifier = classifier;
-        this.type = type;
-        this.file = file;
-    }
-
-    public String getClassifier() {
-        return classifier;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public File getFile() {
-        return file;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        ClassifierArtifact that = (ClassifierArtifact) o;
-
-        if (classifier != null ? !classifier.equals(that.classifier) : that.classifier != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = classifier != null ? classifier.hashCode() : 0;
-        result = 31 * result + (type != null ? type.hashCode() : 0);
-        result = 31 * result + (file != null ? file.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomDeployTask.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomDeployTask.java
deleted file mode 100644
index 76cc474..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomDeployTask.java
+++ /dev/null
@@ -1,44 +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.maven.artifact.ant.DeployTask;
-import org.apache.maven.settings.Settings;
-import org.codehaus.plexus.PlexusContainer;
-
-/**
- * We could also use reflection to get hold of the container property. But this would make it harder
- * to use a Mock for this class.
- *
- * @author Hans Dockter
- */
-public class CustomDeployTask extends DeployTask implements CustomInstallDeployTaskSupport {
-    @Override
-    public synchronized Settings getSettings() {
-        return super.getSettings();
-    }
-    
-    @Override
-    public synchronized PlexusContainer getContainer() {
-        return super.getContainer();
-    }
-
-    @Override
-    public void doExecute() {
-        LoggingHelper.injectLogger(getContainer(), getProject());
-        super.doExecute();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallDeployTaskSupport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallDeployTaskSupport.java
deleted file mode 100644
index 8351a0e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallDeployTaskSupport.java
+++ /dev/null
@@ -1,29 +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.maven.settings.Settings;
-import org.apache.maven.artifact.ant.AttachedArtifact;
-import org.apache.tools.ant.Project;
-
-/**
- * @author Hans Dockter
- */
-public interface CustomInstallDeployTaskSupport {
-    Settings getSettings();
-    Project getProject();
-    AttachedArtifact createAttach();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallTask.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallTask.java
deleted file mode 100644
index 4ff001a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallTask.java
+++ /dev/null
@@ -1,37 +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.maven.artifact.ant.InstallTask;
-import org.apache.maven.settings.Settings;
-
-/**
- * @author Hans Dockter
- */
-public class CustomInstallTask extends InstallTask implements CustomInstallDeployTaskSupport {
-    @Override
-    public synchronized Settings getSettings() {
-        return super.getSettings();   
-    }
-
-    @Override
-    public void doExecute() {
-        LoggingHelper.injectLogger(getContainer(), getProject());
-        super.doExecute();
-    }
-
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java
deleted file mode 100644
index a7cfe2e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java
+++ /dev/null
@@ -1,213 +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.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.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.internal.artifacts.publish.AbstractPublishArtifact;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPom;
-
-import java.io.File;
-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 PublishArtifact artifact;
-
-    private final Set<PublishArtifact> classifiers = new HashSet<PublishArtifact>();
-
-    public DefaultArtifactPom(MavenPom pom) {
-        this.pom = pom;
-    }
-
-    public MavenPom getPom() {
-        return pom;
-    }
-
-    public PublishArtifact getArtifact() {
-        return artifact;
-    }
-
-    public Set<PublishArtifact> getAttachedArtifacts() {
-        return Collections.unmodifiableSet(classifiers);
-    }
-
-    public PublishArtifact writePom(final File pomFile) {
-        getPom().writeTo(pomFile);
-        return new PomArtifact(pomFile);
-    }
-
-    public void addArtifact(Artifact artifact, File src) {
-        throwExceptionIfArtifactOrSrcIsNull(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) {
-            // 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 = publishArtifact;
-        this.artifacts.put(artifactKey, publishArtifact);
-        assignArtifactValuesToPom(artifact, pom, true);
-    }
-
-    private void addArtifact(PublishArtifact artifact) {
-        classifiers.add(artifact);
-        artifacts.put(new ArtifactKey(artifact), artifact);
-    }
-
-    private String getClassifier(Artifact artifact) {
-        return artifact.getExtraAttribute(Dependency.CLASSIFIER);
-    }
-
-    private void assignArtifactValuesToPom(Artifact artifact, MavenPom pom, boolean setType) {
-        if (pom.getGroupId().equals(MavenProject.EMPTY_PROJECT_GROUP_ID)) {
-            pom.setGroupId(artifact.getModuleRevisionId().getOrganisation());
-        }
-        if (pom.getArtifactId().equals(MavenProject.EMPTY_PROJECT_ARTIFACT_ID)) {
-            pom.setArtifactId(artifact.getName());
-        }
-        if (pom.getVersion().equals(MavenProject.EMPTY_PROJECT_VERSION)) {
-            pom.setVersion(artifact.getModuleRevisionId().getRevision());
-        }
-        if (setType) {
-            pom.setPackaging(artifact.getType());
-        }
-    }
-
-    private void throwExceptionIfArtifactOrSrcIsNull(Artifact artifact, File src) {
-        if (artifact == null) {
-            throw new InvalidUserDataException("Artifact must not be null.");
-        }
-        if (src == null) {
-            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/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
deleted file mode 100644
index 3401b53..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
+++ /dev/null
@@ -1,78 +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.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.MavenDeployment;
-import org.gradle.api.artifacts.maven.PomFilterContainer;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPom;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomContainer;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomFactory;
-import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultArtifactPomContainer implements ArtifactPomContainer {
-    private Map<String, ArtifactPom> artifactPoms = new HashMap<String, ArtifactPom>();
-    private final MavenPomMetaInfoProvider pomMetaInfoProvider;
-    private PomFilterContainer pomFilterContainer;
-    private ArtifactPomFactory artifactPomFactory;
-
-    public DefaultArtifactPomContainer(MavenPomMetaInfoProvider pomMetaInfoProvider, PomFilterContainer pomFilterContainer,
-                                       ArtifactPomFactory artifactPomFactory) {
-        this.pomMetaInfoProvider = pomMetaInfoProvider;
-        this.pomFilterContainer = pomFilterContainer;
-        this.artifactPomFactory = artifactPomFactory;
-    }
-
-    public void addArtifact(Artifact artifact, File src) {
-        if (artifact == null || src == null) {
-            throw new InvalidUserDataException("Artifact or source file must not be null!");
-        }
-        for (PomFilter activePomFilter : pomFilterContainer.getActivePomFilters()) {
-            if (activePomFilter.getFilter().accept(artifact, src)) {
-                if (artifactPoms.get(activePomFilter.getName()) == null) {
-                    artifactPoms.put(activePomFilter.getName(), artifactPomFactory.createArtifactPom(activePomFilter.getPomTemplate()));
-                }
-                artifactPoms.get(activePomFilter.getName()).addArtifact(artifact, src); 
-            }
-        }
-    }
-
-    public Set<MavenDeployment> createDeployableFilesInfos() {
-        Set<MavenDeployment> mavenDeployments = new HashSet<MavenDeployment>();
-        for (String activeArtifactPomName : artifactPoms.keySet()) {
-            ArtifactPom activeArtifactPom = artifactPoms.get(activeArtifactPomName);
-            File pomFile = createPomFile(activeArtifactPomName);
-            PublishArtifact pomArtifact = activeArtifactPom.writePom(pomFile);
-            mavenDeployments.add(new DefaultMavenDeployment(pomArtifact, activeArtifactPom.getArtifact(), activeArtifactPom.getAttachedArtifacts()));
-        }
-        return mavenDeployments;
-    }
-
-    private File createPomFile(String artifactPomName) {
-        return new File(pomMetaInfoProvider.getMavenPomDir(), "pom-" + artifactPomName + ".xml");
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultDeployTaskFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultDeployTaskFactory.java
deleted file mode 100644
index 7927936..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultDeployTaskFactory.java
+++ /dev/null
@@ -1,27 +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.gradle.api.internal.Factory;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultDeployTaskFactory implements Factory<CustomDeployTask> {
-    public CustomDeployTask create() {
-        return new CustomDeployTask();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultInstallTaskFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultInstallTaskFactory.java
deleted file mode 100644
index a7e0e29..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultInstallTaskFactory.java
+++ /dev/null
@@ -1,27 +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.gradle.api.internal.Factory;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultInstallTaskFactory implements Factory<CustomInstallTask> {
-    public CustomInstallTask create() {
-        return new CustomInstallTask();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultMavenDeployment.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultMavenDeployment.java
deleted file mode 100644
index 805f526..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultMavenDeployment.java
+++ /dev/null
@@ -1,64 +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.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/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilter.java
deleted file mode 100644
index 9158b3e..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilter.java
+++ /dev/null
@@ -1,56 +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.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.PublishFilter;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPomFilter implements PomFilter {
-    private String name;
-
-    private MavenPom pom;
-
-    private PublishFilter filter;
-
-    public DefaultPomFilter(String name, MavenPom pom, PublishFilter filter) {
-        this.name = name;
-        this.pom = pom;
-        this.filter = filter;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public MavenPom getPomTemplate() {
-        return pom;
-    }
-
-    public void setPomTemplate(MavenPom pom) {
-        this.pom = pom;
-    }
-
-    public PublishFilter getFilter() {
-        return filter;
-    }
-
-    public void setFilter(PublishFilter filter) {
-        this.filter = filter;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/LoggingHelper.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/LoggingHelper.java
deleted file mode 100644
index 4af2dff..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/LoggingHelper.java
+++ /dev/null
@@ -1,46 +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.maven.artifact.ant.AntDownloadMonitor;
-import org.apache.maven.artifact.manager.DefaultWagonManager;
-import org.apache.maven.artifact.manager.WagonManager;
-import org.apache.tools.ant.Project;
-import org.codehaus.plexus.PlexusContainer;
-import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
-
-import java.lang.reflect.Field;
-
-/**
- * @author Hans Dockter
- */
-public class LoggingHelper {
-    public static void injectLogger(PlexusContainer container, Project project) {
-        try {
-            WagonManager wagonManager = (WagonManager) container.lookup(WagonManager.ROLE);
-            Field field = DefaultWagonManager.class.getDeclaredField("downloadMonitor");
-            field.setAccessible(true);
-            AntDownloadMonitor antDownloadMonitor = (AntDownloadMonitor) field.get(wagonManager);
-            antDownloadMonitor.setProject(project);
-        } catch (ComponentLookupException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        } catch (NoSuchFieldException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployer.groovy b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployer.groovy
deleted file mode 100644
index f67c794..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployer.groovy
+++ /dev/null
@@ -1,51 +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.groovy
-
-import org.codehaus.groovy.runtime.InvokerHelper
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer
-import org.gradle.api.artifacts.maven.PomFilterContainer
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomContainer
-import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenDeployer
-import org.gradle.logging.LoggingManagerInternal
-
-/**
- * @author Hans Dockter
- */
-class DefaultGroovyMavenDeployer extends BaseMavenDeployer implements GroovyMavenDeployer, PomFilterContainer {
-    public static final String REPOSITORY_BUILDER = "repository"
-    public static final String SNAPSHOT_REPOSITORY_BUILDER = 'snapshotRepository'
-    
-    private RepositoryBuilder repositoryBuilder = new RepositoryBuilder()
-
-    DefaultGroovyMavenDeployer(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
-        super(name, pomFilterContainer, artifactPomContainer, loggingManager)
-    }
-    
-    def methodMissing(String name, args) {
-        if (name == REPOSITORY_BUILDER || name == SNAPSHOT_REPOSITORY_BUILDER) {
-            Object repository = InvokerHelper.invokeMethod(repositoryBuilder, REPOSITORY_BUILDER, args)
-            if (name == REPOSITORY_BUILDER) {
-                setRepository(repository)
-            } else {
-                setSnapshotRepository(repository)
-            }
-            return repository;
-        } else {
-            throw new MissingMethodException(name, this.class, args)
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryBuilder.java
deleted file mode 100644
index 0d24946..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryBuilder.java
+++ /dev/null
@@ -1,35 +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.groovy;
-
-import groovy.util.FactoryBuilderSupport;
-import org.apache.maven.artifact.ant.Authentication;
-import org.apache.maven.artifact.ant.Proxy;
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.apache.maven.artifact.ant.RepositoryPolicy;
-
-/**
- * @author Hans Dockter
- */
-public class RepositoryBuilder extends FactoryBuilderSupport {
-    public RepositoryBuilder() {
-        registerFactory("repository", new RepositoryFactory(RemoteRepository.class));
-        registerBeanFactory("authentication", Authentication.class);
-        registerBeanFactory("proxy", Proxy.class);
-        registerBeanFactory("snapshots", RepositoryPolicy.class);
-        registerBeanFactory("releases", RepositoryPolicy.class);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryFactory.java
deleted file mode 100644
index c5efe20..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryFactory.java
+++ /dev/null
@@ -1,54 +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.api.internal.artifacts.publish.maven.deploy.groovy;
-
-import groovy.swing.factory.BeanFactory;
-import groovy.util.FactoryBuilderSupport;
-import org.apache.maven.artifact.ant.Authentication;
-import org.apache.maven.artifact.ant.Proxy;
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.apache.maven.artifact.ant.RepositoryPolicy;
-
-/**
- * @author Hans Dockter
- */
-public class RepositoryFactory extends BeanFactory {
-    public RepositoryFactory(Class klass) {
-        super(klass);
-    }
-
-    public RepositoryFactory(Class klass, boolean leaf) {
-        super(klass, leaf);
-    }
-
-    public void setChild(FactoryBuilderSupport builder, Object parent, Object child) {
-        if (child instanceof Authentication) {
-            getRepository(parent).addAuthentication((Authentication) child);
-        } else if (child instanceof Proxy) {
-            getRepository(parent).addProxy((Proxy) child);
-        } else if (child instanceof RepositoryPolicy) {
-            if (builder.getCurrentName().equals("snapshots")) {
-                getRepository(parent).addSnapshots((RepositoryPolicy) child);
-            } else {
-                getRepository(parent).addReleases((RepositoryPolicy) child);
-            }
-        }
-    }
-
-    private RemoteRepository getRepository(Object parent) {
-        return (RemoteRepository) parent;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/EmptyMavenSettingsSupplier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/EmptyMavenSettingsSupplier.java
deleted file mode 100644
index d369abd..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/EmptyMavenSettingsSupplier.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2011 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.mvnsettings;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-import org.gradle.api.UncheckedIOException;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Author: Szczepan Faber, created at: 3/29/11
- */
-public class EmptyMavenSettingsSupplier implements MavenSettingsSupplier {
-
-    private File settingsXml;
-
-    public void supply(InstallDeployTaskSupport installDeployTaskSupport) {
-        try {
-            settingsXml = File.createTempFile("gradle_empty_settings", ".xml");
-            FileUtils.writeStringToFile(settingsXml, "<settings/>");
-            settingsXml.deleteOnExit();
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        installDeployTaskSupport.setSettingsFile(settingsXml);
-    }
-
-    public void done() {
-        if (settingsXml != null) {
-            settingsXml.delete();
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MavenSettingsProvider.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MavenSettingsProvider.java
deleted file mode 100644
index 7307bda..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MavenSettingsProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2011 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.mvnsettings;
-
-import java.io.File;
-
-/**
-* Author: Szczepan Faber, created at: 3/30/11
-*/
-public class MavenSettingsProvider {
-    public File getUserSettingsFile() {
-        File m2Dir = getUserMavenDir();
-        return new File(m2Dir, "settings.xml");
-    }
-
-    public File getUserMavenDir() {
-        File userHome = new File(System.getProperty("user.home"));
-        return new File(userHome, ".m2");
-    }
-
-    public File getLocalMavenRepository() {
-        File m2Dir = getUserMavenDir();
-        return new File(m2Dir, "repository");
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MavenSettingsSupplier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MavenSettingsSupplier.java
deleted file mode 100644
index 4c8f79f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MavenSettingsSupplier.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 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.mvnsettings;
-
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-
-/**
- * Author: Szczepan Faber, created at: 3/29/11
- */
-public interface MavenSettingsSupplier {
-    void done();
-    void supply(InstallDeployTaskSupport installDeployTaskSupport);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MaybeUserMavenSettingsSupplier.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MaybeUserMavenSettingsSupplier.java
deleted file mode 100644
index 058355c..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MaybeUserMavenSettingsSupplier.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 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.mvnsettings;
-
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-
-import java.io.File;
-
-/**
- * Author: Szczepan Faber, created at: 3/29/11
- */
-public class MaybeUserMavenSettingsSupplier implements MavenSettingsSupplier {
-
-    EmptyMavenSettingsSupplier emptySettingsSupplier = new EmptyMavenSettingsSupplier();
-    MavenSettingsProvider mavenSettingsProvider = new MavenSettingsProvider();
-
-    public void supply(InstallDeployTaskSupport installDeployTaskSupport) {
-        File userSettings = mavenSettingsProvider.getUserSettingsFile();
-        if (userSettings.exists()) {
-            installDeployTaskSupport.setSettingsFile(userSettings);
-            return;
-        }
-
-        emptySettingsSupplier.supply(installDeployTaskSupport);
-    }
-
-    public void done() {
-        emptySettingsSupplier.done();
-    }
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/CustomModelBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/CustomModelBuilder.java
deleted file mode 100644
index 8c9ad18..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/CustomModelBuilder.java
+++ /dev/null
@@ -1,81 +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.api.internal.artifacts.publish.maven.pombuilder;
-
-import groovy.util.FactoryBuilderSupport;
-import org.apache.maven.model.Model;
-import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
-import org.slf4j.LoggerFactory;
-import org.sonatype.maven.polyglot.execute.ExecuteManager;
-import org.sonatype.maven.polyglot.execute.ExecuteManagerImpl;
-import org.sonatype.maven.polyglot.groovy.builder.ModelBuilder;
-
-import java.lang.reflect.Field;
-import java.util.Map;
-
-/**
- * This is a slightly modified version as shipped with polyglot Maven.
- */
-public class CustomModelBuilder extends ModelBuilder {
-
-    public CustomModelBuilder(Model model) {
-        ExecuteManager executeManager = new ExecuteManagerImpl();
-        setProp(executeManager.getClass(), executeManager, "log",
-                new PlexusLoggerAdapter(LoggerFactory.getLogger(ExecuteManagerImpl.class)));
-        setProp(ModelBuilder.class, this, "executeManager", executeManager);
-        setProp(ModelBuilder.class, this, "log",
-                new PlexusLoggerAdapter(LoggerFactory.getLogger(ModelBuilder.class)));
-        try {
-            initialize();
-        } catch (InitializationException e) {
-            throw new RuntimeException(e);
-        }
-        Map factories = (Map) getProp(FactoryBuilderSupport.class, this, "factories");
-        factories.remove("project");
-        ModelFactory modelFactory = new ModelFactory(model);
-        registerFactory(modelFactory.getName(), null, modelFactory);
-    }
-
-    public static void setProp(Class c, Object obj, String fieldName, Object value) {
-        try {
-            Field f = c.getDeclaredField(fieldName);
-            f.setAccessible(true); // solution
-            f.set(obj, value); // IllegalAccessException
-            // production code should handle these exceptions more gracefully
-        } catch (NoSuchFieldException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalArgumentException e) {
-           throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-           throw new RuntimeException(e);
-        }
-    }
-
-    public static Object getProp(Class c, Object obj, String fieldName) {
-        try {
-            Field f = c.getDeclaredField(fieldName);
-            f.setAccessible(true); // solution
-            return f.get(obj); // IllegalAccessException
-            // production code should handle these exceptions more gracefully
-        } catch (NoSuchFieldException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalArgumentException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/ModelFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/ModelFactory.java
deleted file mode 100644
index 49b0f10..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/ModelFactory.java
+++ /dev/null
@@ -1,43 +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.api.internal.artifacts.publish.maven.pombuilder;
-
-import groovy.util.FactoryBuilderSupport;
-import org.apache.maven.model.Model;
-import org.sonatype.maven.polyglot.groovy.builder.factory.NamedFactory;
-
-import java.util.Map;
-
-/**
- * This is a slightly modified version as shipped with polyglot Maven.
- */
-public class ModelFactory extends NamedFactory {
-    private Model model;
-
-    public ModelFactory(Model model) {
-        super("project");
-        this.model = model;
-    }
-
-    public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attrs) throws InstantiationException, IllegalAccessException {
-        return model;
-    }
-
-    @Override
-    public void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) {
-        Model model = (Model)node;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/PlexusLoggerAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/PlexusLoggerAdapter.java
deleted file mode 100644
index 26dbb71..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/PlexusLoggerAdapter.java
+++ /dev/null
@@ -1,101 +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.api.internal.artifacts.publish.maven.pombuilder;
-
-import org.codehaus.plexus.logging.Logger;
-
-/**
- * @author Hans Dockter
- */
-public class PlexusLoggerAdapter implements Logger {
-    org.slf4j.Logger logger;
-
-    public PlexusLoggerAdapter(org.slf4j.Logger logger) {
-        this.logger = logger;
-    }
-
-    public void debug(String s) {
-        logger.debug(s);
-    }
-
-    public void debug(String s, Throwable throwable) {
-        logger.debug(s, throwable);
-    }
-
-    public boolean isDebugEnabled() {
-        return logger.isDebugEnabled();
-    }
-
-    public void info(String s) {
-        logger.info(s);
-    }
-
-    public void info(String s, Throwable throwable) {
-        logger.info(s, throwable);
-    }
-
-    public boolean isInfoEnabled() {
-        return logger.isInfoEnabled();
-    }
-
-    public void warn(String s) {
-        logger.warn(s);
-    }
-
-    public void warn(String s, Throwable throwable) {
-        logger.warn(s, throwable);
-    }
-
-    public boolean isWarnEnabled() {
-        return logger.isWarnEnabled();
-    }
-
-    public void error(String s) {
-        logger.error(s);
-    }
-
-    public void error(String s, Throwable throwable) {
-        logger.error(s, throwable);
-    }
-
-    public boolean isErrorEnabled() {
-        return logger.isErrorEnabled();
-    }
-
-    public void fatalError(String s) {
-        logger.error(s);
-    }
-
-    public void fatalError(String s, Throwable throwable) {
-        logger.error(s, throwable);
-    }
-
-    public boolean isFatalErrorEnabled() {
-        return logger.isErrorEnabled();
-    }
-
-    public Logger getChildLogger(String s) {
-        throw new UnsupportedOperationException();
-    }
-
-    public int getThreshold() {
-        throw new UnsupportedOperationException();
-    }
-
-    public String getName() {
-        return logger.getName();
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractAuthenticationSupportedRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractAuthenticationSupportedRepository.java
new file mode 100644
index 0000000..cca1315
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractAuthenticationSupportedRepository.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.repositories.AuthenticationSupported;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.util.ConfigureUtil;
+
+class AbstractAuthenticationSupportedRepository implements AuthenticationSupported {
+    private final PasswordCredentials passwordCredentials;
+
+    AbstractAuthenticationSupportedRepository(PasswordCredentials credentials) {
+        this.passwordCredentials = credentials;
+    }
+
+    public PasswordCredentials getCredentials() {
+        return passwordCredentials;
+    }
+
+    public void credentials(Closure closure) {
+        ConfigureUtil.configure(closure, passwordCredentials);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CommonsHttpClientBackedRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CommonsHttpClientBackedRepository.java
deleted file mode 100644
index e53f2df..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CommonsHttpClientBackedRepository.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright 2011 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.repositories;
-
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.HttpMethodRetryHandler;
-import org.apache.commons.httpclient.UsernamePasswordCredentials;
-import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.commons.httpclient.methods.RequestEntity;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.io.output.CloseShieldOutputStream;
-import org.apache.ivy.plugins.repository.*;
-import org.apache.ivy.util.FileUtil;
-import org.gradle.util.GUtil;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A repository which uses commons-httpclient to access resources using HTTP/HTTPS.
- */
-public class CommonsHttpClientBackedRepository extends AbstractRepository {
-    private static final Logger LOGGER = LoggerFactory.getLogger(CommonsHttpClientBackedRepository.class);
-    private final Map<String, HttpResource> resources = new HashMap<String, HttpResource>();
-    private final HttpClient client = new HttpClient();
-    private final RepositoryCopyProgressListener progress = new RepositoryCopyProgressListener(this);
-
-    public CommonsHttpClientBackedRepository(String username, String password) {
-        if (GUtil.isTrue(username)) {
-            client.getParams().setAuthenticationPreemptive(true);
-            client.getState().setCredentials(new AuthScope(null, -1, null), new UsernamePasswordCredentials(username, password));
-        }
-    }
-
-    public Resource getResource(final String source) throws IOException {
-        LOGGER.debug("Attempting to get resource {}.", source);
-        GetMethod method = new GetMethod(source);
-        configureMethod(method);
-        int result = client.executeMethod(method);
-        if (result == 404) {
-            return new MissingResource(source);
-        }
-        if (result != 200) {
-            throw new IOException(String.format("Could not GET '%s'. Received status code %s from server: %s", source, result, method.getStatusText()));
-        }
-
-        HttpResource resource = new HttpResource(source, method);
-        resources.put(source, resource);
-        return resource;
-    }
-
-    public void get(String source, File destination) throws IOException {
-        HttpResource resource = resources.get(source);
-        fireTransferInitiated(resource, TransferEvent.REQUEST_GET);
-        try {
-            progress.setTotalLength(resource.getContentLength());
-            resource.downloadTo(destination);
-        } catch (IOException e) {
-            fireTransferError(e);
-            throw e;
-        } catch (Exception e) {
-            fireTransferError(e);
-            throw UncheckedException.asUncheckedException(e);
-        } finally {
-            progress.setTotalLength(null);
-        }
-    }
-
-    @Override
-    protected void put(final File source, String destination, boolean overwrite) throws IOException {
-        LOGGER.debug("Attempting to put resource {}.", destination);
-        assert source.isFile();
-        fireTransferInitiated(new BasicResource(destination, true, source.length(), source.lastModified(), false), TransferEvent.REQUEST_PUT);
-        try {
-            progress.setTotalLength(source.length());
-            doPut(source, destination);
-        } catch (IOException e) {
-            fireTransferError(e);
-            throw e;
-        } catch (Exception e) {
-            fireTransferError(e);
-            throw UncheckedException.asUncheckedException(e);
-        } finally {
-            progress.setTotalLength(null);
-        }
-    }
-
-    private void doPut(File source, String destination) throws IOException {
-        PutMethod method = new PutMethod(destination);
-        configureMethod(method);
-        method.setRequestEntity(new FileRequestEntity(source));
-        int result = client.executeMethod(method);
-        if (result != 200) {
-            throw new IOException(String.format("Could not PUT '%s'. Received status code %s from server: %s", destination, result, method.getStatusText()));
-        }
-    }
-
-    private void configureMethod(HttpMethod method) {
-        method.setRequestHeader("User-Agent", "Gradle/" + GradleVersion.current().getVersion());
-        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new HttpMethodRetryHandler() {
-            public boolean retryMethod(HttpMethod method, IOException exception, int executionCount) {
-                return false;
-            }
-        });
-    }
-
-    public List list(String parent) throws IOException {
-        return Collections.EMPTY_LIST;
-    }
-
-    private class HttpResource implements Resource {
-        private final String source;
-        private final GetMethod method;
-
-        public HttpResource(String source, GetMethod method) {
-            this.source = source;
-            this.method = method;
-        }
-
-        public String getName() {
-            return source;
-        }
-
-        @Override
-        public String toString() {
-            return getName();
-        }
-
-        public long getLastModified() {
-            return 0;
-        }
-
-        public long getContentLength() {
-            return method.getResponseContentLength();
-        }
-
-        public boolean exists() {
-            return true;
-        }
-
-        public boolean isLocal() {
-            return false;
-        }
-
-        public Resource clone(String cloneName) {
-            throw new UnsupportedOperationException();
-        }
-
-        public InputStream openStream() throws IOException {
-            return method.getResponseBodyAsStream();
-        }
-
-        public void downloadTo(File destination) throws IOException {
-            FileOutputStream output = new FileOutputStream(destination);
-            try {
-                InputStream input = openStream();
-                try {
-                    FileUtil.copy(input, output, progress);
-                } finally {
-                    input.close();
-                }
-            } finally {
-                output.close();
-            }
-        }
-    }
-
-    private static class MissingResource implements Resource {
-        private final String source;
-
-        public MissingResource(String source) {
-            this.source = source;
-        }
-
-        public Resource clone(String cloneName) {
-            throw new UnsupportedOperationException();
-        }
-
-        public String getName() {
-            return source;
-        }
-
-        public long getLastModified() {
-            throw new UnsupportedOperationException();
-        }
-
-        public long getContentLength() {
-            throw new UnsupportedOperationException();
-        }
-
-        public boolean exists() {
-            return false;
-        }
-
-        public boolean isLocal() {
-            throw new UnsupportedOperationException();
-        }
-
-        public InputStream openStream() throws IOException {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    private class FileRequestEntity implements RequestEntity {
-        private final File source;
-
-        public FileRequestEntity(File source) {
-            this.source = source;
-        }
-
-        public boolean isRepeatable() {
-            return false;
-        }
-
-        public void writeRequest(OutputStream out) throws IOException {
-            FileInputStream inputStream = new FileInputStream(source);
-            try {
-                FileUtil.copy(inputStream, new CloseShieldOutputStream(out), progress);
-            } finally {
-                inputStream.close();
-            }
-        }
-
-        public long getContentLength() {
-            return source.length();
-        }
-
-        public String getContentType() {
-            return "application/octet-stream";
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CustomResolverArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CustomResolverArtifactRepository.java
new file mode 100644
index 0000000..e1dcddc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/CustomResolverArtifactRepository.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.resolver.*;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+
+import java.util.List;
+
+public class CustomResolverArtifactRepository extends FixedResolverArtifactRepository {
+    private final RepositoryTransportFactory repositoryTransportFactory;
+
+    public CustomResolverArtifactRepository(DependencyResolver resolver, RepositoryTransportFactory repositoryTransportFactory) {
+        super(resolver);
+        this.repositoryTransportFactory = repositoryTransportFactory;
+        configureResolver(resolver, true);
+    }
+
+    private void configureResolver(DependencyResolver dependencyResolver, boolean isTopLevel) {
+        if (isTopLevel) {
+            if (resolver instanceof AbstractResolver && !(resolver instanceof FileSystemResolver)) {
+                ((AbstractResolver) resolver).setRepositoryCacheManager(repositoryTransportFactory.getDownloadingCacheManager());
+            }
+        }
+        
+        if (dependencyResolver instanceof FileSystemResolver) {
+            ((FileSystemResolver) dependencyResolver).setLocal(true);
+            ((FileSystemResolver) dependencyResolver).setRepositoryCacheManager(repositoryTransportFactory.getLocalCacheManager());
+        }
+        if (dependencyResolver instanceof RepositoryResolver) {
+            Repository repository = ((RepositoryResolver) dependencyResolver).getRepository();
+            repositoryTransportFactory.attachListener(repository);
+        }
+        if (dependencyResolver instanceof DualResolver) {
+            DualResolver dualResolver = (DualResolver) dependencyResolver;
+            configureResolver(dualResolver.getIvyResolver(), false);
+            configureResolver(dualResolver.getArtifactResolver(), false);
+        }
+        if (dependencyResolver instanceof ChainResolver) {
+            ChainResolver chainResolver = (ChainResolver) dependencyResolver;
+            @SuppressWarnings("unchecked") List<DependencyResolver> resolvers = chainResolver.getResolvers();
+            for (DependencyResolver resolver : resolvers) {
+                configureResolver(resolver, false);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java
new file mode 100644
index 0000000..34576b6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012 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.repositories;
+
+
+import org.apache.ivy.plugins.repository.AbstractRepository;
+import org.apache.ivy.plugins.repository.BasicResource;
+import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.transfer.*;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class DefaultExternalResourceRepository extends AbstractRepository implements ExternalResourceRepository {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExternalResourceRepository.class);
+    private final RepositoryCopyProgressListener progress = new RepositoryCopyProgressListener(this);
+
+    private final ExternalResourceAccessor accessor;
+    private final ExternalResourceUploader uploader;
+    private final ExternalResourceLister lister;
+
+    private final CacheAwareExternalResourceAccessor cacheAwareAccessor;
+
+    public DefaultExternalResourceRepository(String name, ExternalResourceAccessor accessor, ExternalResourceUploader uploader, ExternalResourceLister lister) {
+        setName(name);
+        this.accessor = accessor;
+        this.uploader = uploader;
+        this.lister = lister;
+
+        this.cacheAwareAccessor = new DefaultCacheAwareExternalResourceAccessor(accessor);
+    }
+
+    public ExternalResource getResource(String source) throws IOException {
+        return accessor.getResource(source);
+    }
+
+    public ExternalResource getResource(String source, LocallyAvailableResourceCandidates localCandidates, CachedExternalResource cached) throws IOException{
+        return cacheAwareAccessor.getResource(source, localCandidates, cached);
+    }
+
+    public ExternalResourceMetaData getResourceMetaData(String source) throws IOException {
+        return accessor.getMetaData(source);
+    }
+
+    public void get(String source, File destination) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void downloadResource(ExternalResource resource, File destination) throws IOException {
+        fireTransferInitiated(resource, TransferEvent.REQUEST_GET);
+        try {
+            progress.setTotalLength(resource.getContentLength() > 0 ? resource.getContentLength() : null);
+            resource.writeTo(destination, progress);
+        } catch (IOException e) {
+            fireTransferError(e);
+            throw e;
+        } catch (Exception e) {
+            fireTransferError(e);
+            throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
+            progress.setTotalLength(null);
+            resource.close();
+        }
+    }
+
+    @Override
+    protected void put(File source, String destination, boolean overwrite) throws IOException {
+        LOGGER.debug("Attempting to put resource {}.", destination);
+        assert source.isFile();
+        fireTransferInitiated(new BasicResource(destination, true, source.length(), source.lastModified(), false), TransferEvent.REQUEST_PUT);
+        try {
+            progress.setTotalLength(source.length());
+            uploader.upload(source, destination, overwrite);
+        } catch (IOException e) {
+            fireTransferError(e);
+            throw e;
+        } catch (Exception e) {
+            fireTransferError(e);
+            throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
+            progress.setTotalLength(null);
+        }
+    }
+
+    public List list(String parent) throws IOException {
+        return lister.list(parent);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java
new file mode 100644
index 0000000..ab320c4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import com.google.common.collect.Lists;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.FileSystemResolver;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultFlatDirArtifactRepository implements FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
+    private final FileResolver fileResolver;
+    private final RepositoryTransportFactory repositoryTransportFactory;
+    private String name;
+    private List<Object> dirs = new ArrayList<Object>();
+
+    public DefaultFlatDirArtifactRepository(FileResolver fileResolver, RepositoryTransportFactory repositoryTransportFactory) {
+        this.fileResolver = fileResolver;
+        this.repositoryTransportFactory = repositoryTransportFactory;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Set<File> getDirs() {
+        return fileResolver.resolveFiles(dirs).getFiles();
+    }
+
+    public void setDirs(Iterable<?> dirs) {
+        this.dirs = Lists.newArrayList(dirs);
+    }
+
+    public void dir(Object dir) {
+        dirs(dir);
+    }
+
+    public void dirs(Object... dirs) {
+        this.dirs.addAll(Arrays.asList(dirs));
+    }
+
+    public DependencyResolver createResolver() {
+        Set<File> dirs = getDirs();
+        if (dirs.isEmpty()) {
+            throw new InvalidUserDataException("You must specify at least one directory for a flat directory repository.");
+        }
+
+        FileSystemResolver resolver = new FileSystemResolver();
+        resolver.setName(name);
+        for (File root : dirs) {
+            resolver.addArtifactPattern(root.getAbsolutePath() + "/[artifact]-[revision](-[classifier]).[ext]");
+            resolver.addArtifactPattern(root.getAbsolutePath() + "/[artifact](-[classifier]).[ext]");
+        }
+        resolver.setValidate(false);
+        resolver.setRepositoryCacheManager(repositoryTransportFactory.getLocalCacheManager());
+        resolver.getRepository().addTransferListener(repositoryTransportFactory.getTransferListener());
+        return resolver;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
index 98e04e4..5d7d02f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
@@ -15,62 +15,75 @@
  */
 package org.gradle.api.internal.artifacts.repositories;
 
+import groovy.lang.Closure;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.RepositoryResolver;
-import org.apache.ivy.plugins.resolver.URLResolver;
-import org.gradle.api.artifacts.dsl.IvyArtifactRepository;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.layout.*;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.WrapUtil;
 
 import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.*;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
-public class DefaultIvyArtifactRepository implements IvyArtifactRepository, ArtifactRepositoryInternal {
+public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepository, ArtifactRepositoryInternal {
     private String name;
-    private String username;
-    private String password;
-    private final Set<String> artifactPatterns = new LinkedHashSet<String>();
-
-    public void createResolvers(Collection<DependencyResolver> resolvers) {
-        List<String> httpPatterns = new ArrayList<String>();
-        List<String> otherPatterns = new ArrayList<String>();
-
-        for (String artifactPattern : artifactPatterns) {
-            try {
-                URI uri = new URI(artifactPattern.replaceAll("\\[.*\\]", "token"));
-                if (uri.getScheme() != null && (uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equalsIgnoreCase("https"))) {
-                    httpPatterns.add(artifactPattern);
-                    continue;
-                }
-            } catch (URISyntaxException e) {
-                // Ignore
-            }
-            otherPatterns.add(artifactPattern);
-        }
+    private Object baseUrl;
+    private RepositoryLayout layout;
+    private final AdditionalPatternsRepositoryLayout additionalPatternsLayout;
+    private final FileResolver fileResolver;
+    private final RepositoryTransportFactory transportFactory;
+    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
 
-        if (!otherPatterns.isEmpty()) {
-            URLResolver resolver = new URLResolver();
-            resolver.setName(name);
-            for (String artifactPattern : otherPatterns) {
-                resolver.addArtifactPattern(artifactPattern);
-                resolver.addIvyPattern(artifactPattern);
-            }
-            resolvers.add(resolver);
-        }
+    public DefaultIvyArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
+                                        LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                                        CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+        super(credentials);
+        this.fileResolver = fileResolver;
+        this.transportFactory = transportFactory;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
+        this.additionalPatternsLayout = new AdditionalPatternsRepositoryLayout(fileResolver);
+        this.layout = new GradleRepositoryLayout();
+    }
 
-        if (!httpPatterns.isEmpty()) {
-            RepositoryResolver resolver = new RepositoryResolver();
-            resolver.setRepository(new CommonsHttpClientBackedRepository(username, password));
-            resolver.setName(name);
-            for (String artifactPattern : httpPatterns) {
-                resolver.addArtifactPattern(artifactPattern);
-                resolver.addIvyPattern(artifactPattern);
-            }
-            resolvers.add(resolver);
-        }
+    public DependencyResolver createResolver() {
+        URI uri = getUrl();
+
+        Set<String> schemes = new LinkedHashSet<String>();
+        layout.addSchemes(uri, schemes);
+        additionalPatternsLayout.addSchemes(uri, schemes);
+
+        PatternBasedResolver resolver = createResolver(schemes);
+
+        layout.apply(uri, resolver);
+        additionalPatternsLayout.apply(uri, resolver);
+
+        return resolver;
     }
 
-    public void artifactPattern(String pattern) {
-        artifactPatterns.add(pattern);
+    private PatternBasedResolver createResolver(Set<String> schemes) {
+        if (schemes.isEmpty()) {
+            throw new InvalidUserDataException("You must specify a base url or at least one artifact pattern for an Ivy repository.");
+        }
+        if (!WrapUtil.toSet("http", "https", "file").containsAll(schemes)) {
+            throw new InvalidUserDataException("You may only specify 'file', 'http' and 'https' urls for an ivy repository.");
+        }
+        if (WrapUtil.toSet("http", "https").containsAll(schemes)) {
+            return new IvyResolver(name, transportFactory.createHttpTransport(name, getCredentials()), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+        }
+        if (WrapUtil.toSet("file").containsAll(schemes)) {
+            return new IvyResolver(name, transportFactory.createFileTransport(name), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+        }
+        throw new InvalidUserDataException("You cannot mix file and http(s) urls for a single ivy repository. Please declare 2 separate repositories.");
     }
 
     public String getName() {
@@ -81,19 +94,71 @@ public class DefaultIvyArtifactRepository implements IvyArtifactRepository, Arti
         this.name = name;
     }
 
-    public String getPassword() {
-        return password;
+    public URI getUrl() {
+        return baseUrl == null ? null : fileResolver.resolveUri(baseUrl);
+    }
+
+    public void setUrl(Object url) {
+        baseUrl = url;
     }
 
-    public void setPassword(String password) {
-        this.password = password;
+    public void artifactPattern(String pattern) {
+        additionalPatternsLayout.artifactPatterns.add(pattern);
     }
 
-    public String getUserName() {
-        return username;
+    public void ivyPattern(String pattern) {
+        additionalPatternsLayout.ivyPatterns.add(pattern);
     }
 
-    public void setUserName(String username) {
-        this.username = username;
+    public void layout(String layoutName) {
+        if ("maven".equals(layoutName)) {
+            layout = new MavenRepositoryLayout();
+        } else if ("pattern".equals(layoutName)) {
+            layout = new PatternRepositoryLayout();
+        } else {
+            layout = new GradleRepositoryLayout();
+        }
+    }
+
+    public void layout(String layoutName, Closure config) {
+        layout(layoutName);
+        ConfigureUtil.configure(config, layout);
+    }
+
+    /**
+     * Layout for applying additional patterns added via {@link #artifactPatterns} and {@link #ivyPatterns}.
+     */
+    private static class AdditionalPatternsRepositoryLayout extends RepositoryLayout {
+        private final FileResolver fileResolver;
+        private final Set<String> artifactPatterns = new LinkedHashSet<String>();
+        private final Set<String> ivyPatterns = new LinkedHashSet<String>();
+
+        public AdditionalPatternsRepositoryLayout(FileResolver fileResolver) {
+            this.fileResolver = fileResolver;
+        }
+
+        public void apply(URI baseUri, PatternBasedResolver resolver) {
+            for (String artifactPattern : artifactPatterns) {
+                ResolvedPattern resolvedPattern = new ResolvedPattern(artifactPattern, fileResolver);
+                resolver.addArtifactLocation(resolvedPattern.baseUri, resolvedPattern.pattern);
+            }
+
+            Set<String> usedIvyPatterns = ivyPatterns.isEmpty() ? artifactPatterns : ivyPatterns;
+            for (String ivyPattern : usedIvyPatterns) {
+                ResolvedPattern resolvedPattern = new ResolvedPattern(ivyPattern, fileResolver);
+                resolver.addDescriptorLocation(resolvedPattern.baseUri, resolvedPattern.pattern);
+            }
+        }
+
+        @Override
+        public void addSchemes(URI baseUri, Set<String> schemes) {
+            for (String pattern : artifactPatterns) {
+                schemes.add(new ResolvedPattern(pattern, fileResolver).scheme);
+            }
+            for (String pattern : ivyPatterns) {
+                schemes.add(new ResolvedPattern(pattern, fileResolver).scheme);
+            }
+        }
     }
+
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java
new file mode 100644
index 0000000..8d15ea2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import com.google.common.collect.Lists;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultMavenArtifactRepository extends AbstractAuthenticationSupportedRepository implements MavenArtifactRepository, ArtifactRepositoryInternal {
+    private final FileResolver fileResolver;
+    private final RepositoryTransportFactory transportFactory;
+    private String name;
+    private Object url;
+    private List<Object> additionalUrls = new ArrayList<Object>();
+    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
+
+    public DefaultMavenArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
+                                          LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                                          CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+        super(credentials);
+        this.fileResolver = fileResolver;
+        this.transportFactory = transportFactory;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public URI getUrl() {
+        return url == null ? null : fileResolver.resolveUri(url);
+    }
+
+    public void setUrl(Object url) {
+        this.url = url;
+    }
+
+    public Set<URI> getArtifactUrls() {
+        Set<URI> result = new LinkedHashSet<URI>();
+        for (Object additionalUrl : additionalUrls) {
+            result.add(fileResolver.resolveUri(additionalUrl));
+        }
+        return result;
+    }
+
+    public void artifactUrls(Object... urls) {
+        additionalUrls.addAll(Lists.newArrayList(urls));
+    }
+
+    public void setArtifactUrls(Iterable<?> urls) {
+        additionalUrls = Lists.newArrayList(urls);
+    }
+
+    public DependencyResolver createResolver() {
+        URI rootUri = getUrl();
+        if (rootUri == null) {
+            throw new InvalidUserDataException("You must specify a URL for a Maven repository.");
+        }
+
+        MavenResolver resolver = new MavenResolver(name, rootUri, getTransport(rootUri.getScheme()), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+        for (URI repoUrl : getArtifactUrls()) {
+            resolver.addArtifactLocation(repoUrl, null);
+        }
+        return resolver;
+    }
+
+    private RepositoryTransport getTransport(String scheme) {
+        if (scheme.equalsIgnoreCase("file")) {
+            return transportFactory.createFileTransport(name);
+        } else {
+            return transportFactory.createHttpTransport(name, getCredentials());
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultPasswordCredentials.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultPasswordCredentials.java
new file mode 100644
index 0000000..3bc8687
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultPasswordCredentials.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+
+public class DefaultPasswordCredentials implements PasswordCredentials {
+    private String username;
+    private String password;
+
+    public DefaultPasswordCredentials() {
+    }
+
+    public DefaultPasswordCredentials(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Credentials [username: %s]", username);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
index ca92521..4a8a8ab 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
@@ -16,151 +16,100 @@
 
 package org.gradle.api.internal.artifacts.repositories;
 
-import org.apache.ivy.plugins.resolver.*;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.ResolverContainer;
-import org.gradle.api.artifacts.dsl.IvyArtifactRepository;
-import org.gradle.api.artifacts.maven.*;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.artifacts.ivyservice.GradleIBiblioResolver;
-import org.gradle.api.internal.artifacts.ivyservice.LocalFileRepositoryCacheManager;
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
-import org.gradle.api.internal.artifacts.publish.maven.*;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.*;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.groovy.DefaultGroovyMavenDeployer;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.repositories.*;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.ResolverFactory;
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.ConfigureUtil;
 
-import java.io.File;
 import java.util.Map;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultResolverFactory implements ResolverFactory {
-    private final Factory<LoggingManagerInternal> loggingManagerFactory;
-    private final MavenFactory mavenFactory;
-    private final LocalMavenCacheLocator localMavenCacheLocator;
-
-    public DefaultResolverFactory(Factory<LoggingManagerInternal> loggingManagerFactory, MavenFactory mavenFactory, LocalMavenCacheLocator localMavenCacheLocator) {
-        this.loggingManagerFactory = loggingManagerFactory;
-        this.mavenFactory = mavenFactory;
-        this.localMavenCacheLocator = localMavenCacheLocator;
-    }
-
-    public DependencyResolver createResolver(Object userDescription) {
-        DependencyResolver result;
-        if (userDescription instanceof String) {
-            result = createMavenRepoResolver((String) userDescription, (String) userDescription);
-        } else if (userDescription instanceof Map) {
-            Map<String, String> userDescriptionMap = (Map<String, String>) userDescription;
-            result = createMavenRepoResolver(userDescriptionMap.get(ResolverContainer.RESOLVER_NAME),
-                    userDescriptionMap.get(ResolverContainer.RESOLVER_URL));
-        } else if (userDescription instanceof DependencyResolver) {
-            result = (DependencyResolver) userDescription;
-        } else {
-            throw new InvalidUserDataException("Illegal Resolver type");
-        }
-        return result;
+    private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
+    private final FileResolver fileResolver;
+    private final Instantiator instantiator;
+    private final RepositoryTransportFactory transportFactory;
+    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
+
+    public DefaultResolverFactory(LocalMavenRepositoryLocator localMavenRepositoryLocator, FileResolver fileResolver, Instantiator instantiator,
+                                  RepositoryTransportFactory transportFactory,
+                                  LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                                  CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+        this.localMavenRepositoryLocator = localMavenRepositoryLocator;
+        this.fileResolver = fileResolver;
+        this.instantiator = instantiator;
+        this.transportFactory = transportFactory;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
     }
 
-    public FileSystemResolver createFlatDirResolver(String name, File... roots) {
-        FileSystemResolver resolver = new FileSystemResolver();
-        resolver.setName(name);
-        for (File root : roots) {
-            resolver.addArtifactPattern(root.getAbsolutePath() + "/[artifact]-[revision](-[classifier]).[ext]");
-            resolver.addArtifactPattern(root.getAbsolutePath() + "/[artifact](-[classifier]).[ext]");
+    public ArtifactRepository createRepository(Object userDescription) {
+        if (userDescription instanceof ArtifactRepository) {
+            return (ArtifactRepository) userDescription;
         }
-        resolver.setValidate(false);
-        resolver.setRepositoryCacheManager(new LocalFileRepositoryCacheManager(name));
-        return resolver;
-    }
-
-    public AbstractResolver createMavenLocalResolver(String name) {
-        String cacheDir = localMavenCacheLocator.getLocalMavenCache().toURI().toString();
-        return createMavenRepoResolver(name, cacheDir);
-    }
 
-    public AbstractResolver createMavenRepoResolver(String name, String root, String... jarRepoUrls) {
-        GradleIBiblioResolver iBiblioResolver = createIBiblioResolver(name, root);
-        if (jarRepoUrls.length == 0) {
-            iBiblioResolver.setDescriptor(IBiblioResolver.DESCRIPTOR_OPTIONAL);
-            return iBiblioResolver;
+        if (userDescription instanceof String) {
+            MavenArtifactRepository repository = createMavenRepository();
+            repository.setUrl(userDescription);
+            return repository;
+        } else if (userDescription instanceof Map) {
+            Map<String, ?> userDescriptionMap = (Map<String, ?>) userDescription;
+            MavenArtifactRepository repository = createMavenRepository();
+            ConfigureUtil.configureByMap(userDescriptionMap, repository);
+            return repository;
         }
-        iBiblioResolver.setName(iBiblioResolver.getName() + "_poms");
-        URLResolver urlResolver = createUrlResolver(name, root, jarRepoUrls);
-        return createDualResolver(name, iBiblioResolver, urlResolver);
-    }
 
-    private GradleIBiblioResolver createIBiblioResolver(String name, String root) {
-        GradleIBiblioResolver iBiblioResolver = new GradleIBiblioResolver();
-        iBiblioResolver.setUsepoms(true);
-        iBiblioResolver.setName(name);
-        iBiblioResolver.setRoot(root);
-        iBiblioResolver.setPattern(ResolverContainer.MAVEN_REPO_PATTERN);
-        iBiblioResolver.setM2compatible(true);
-        iBiblioResolver.setUseMavenMetadata(true);
-        return iBiblioResolver;
-    }
-
-    private URLResolver createUrlResolver(String name, String root, String... jarRepoUrls) {
-        URLResolver urlResolver = new URLResolver();
-        urlResolver.setName(name + "_jars");
-        urlResolver.setM2compatible(true);
-        urlResolver.addArtifactPattern(root + '/' + ResolverContainer.MAVEN_REPO_PATTERN);
-        for (String jarRepoUrl : jarRepoUrls) {
-            urlResolver.addArtifactPattern(jarRepoUrl + '/' + ResolverContainer.MAVEN_REPO_PATTERN);
+        DependencyResolver result;
+        if (userDescription instanceof DependencyResolver) {
+            result = (DependencyResolver) userDescription;
+        } else {
+            throw new InvalidUserDataException(String.format("Cannot create a DependencyResolver instance from %s", userDescription));
         }
-        return urlResolver;
+        return new CustomResolverArtifactRepository(result, transportFactory);
     }
 
-    private DualResolver createDualResolver(String name, GradleIBiblioResolver iBiblioResolver, URLResolver urlResolver) {
-        DualResolver dualResolver = new DualResolver();
-        dualResolver.setName(name);
-        dualResolver.setIvyResolver(iBiblioResolver);
-        dualResolver.setArtifactResolver(urlResolver);
-        dualResolver.setDescriptor(DualResolver.DESCRIPTOR_OPTIONAL);
-        return dualResolver;
+    public FlatDirectoryArtifactRepository createFlatDirRepository() {
+        return instantiator.newInstance(DefaultFlatDirArtifactRepository.class, fileResolver, transportFactory);
     }
 
-    // todo use MavenPluginConvention pom factory after modularization is done
-
-    public GroovyMavenDeployer createMavenDeployer(String name, MavenPomMetaInfoProvider pomMetaInfoProvider,
-                                                   ConfigurationContainer configurationContainer,
-                                                   Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver) {
-        PomFilterContainer pomFilterContainer = createPomFilterContainer(
-                mavenFactory.createMavenPomFactory(configurationContainer, scopeMapping, fileResolver));
-        return new DefaultGroovyMavenDeployer(name, pomFilterContainer, createArtifactPomContainer(
-                pomMetaInfoProvider, pomFilterContainer, createArtifactPomFactory()), loggingManagerFactory.create());
+    public MavenArtifactRepository createMavenLocalRepository() {
+        MavenArtifactRepository mavenRepository = createMavenRepository();
+        mavenRepository.setUrl(localMavenRepositoryLocator.getLocalMavenRepository());
+        return mavenRepository;
     }
 
-    // todo use MavenPluginConvention pom factory after modularization is done
-
-    public MavenResolver createMavenInstaller(String name, MavenPomMetaInfoProvider pomMetaInfoProvider,
-                                              ConfigurationContainer configurationContainer,
-                                              Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver) {
-        PomFilterContainer pomFilterContainer = createPomFilterContainer(
-                mavenFactory.createMavenPomFactory(configurationContainer, scopeMapping, fileResolver));
-        return new BaseMavenInstaller(name, pomFilterContainer, createArtifactPomContainer(pomMetaInfoProvider,
-                pomFilterContainer, createArtifactPomFactory()), loggingManagerFactory.create());
+    public MavenArtifactRepository createMavenCentralRepository() {
+        MavenArtifactRepository mavenRepository = createMavenRepository();
+        mavenRepository.setUrl(RepositoryHandler.MAVEN_CENTRAL_URL);
+        return mavenRepository;
     }
 
     public IvyArtifactRepository createIvyRepository() {
-        return new DefaultIvyArtifactRepository();
-    }
-
-    private PomFilterContainer createPomFilterContainer(Factory<MavenPom> mavenPomFactory) {
-        return new BasePomFilterContainer(mavenPomFactory);
+        return instantiator.newInstance(DefaultIvyArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
+                locallyAvailableResourceFinder, cachedExternalResourceIndex
+        );
     }
 
-    private ArtifactPomFactory createArtifactPomFactory() {
-        return new DefaultArtifactPomFactory();
+    public MavenArtifactRepository createMavenRepository() {
+        return instantiator.newInstance(DefaultMavenArtifactRepository.class, fileResolver, createPasswordCredentials(), transportFactory,
+                locallyAvailableResourceFinder, cachedExternalResourceIndex
+        );
     }
 
-    private ArtifactPomContainer createArtifactPomContainer(MavenPomMetaInfoProvider pomMetaInfoProvider, PomFilterContainer filterContainer,
-                                                           ArtifactPomFactory pomFactory) {
-        return new DefaultArtifactPomContainer(pomMetaInfoProvider, filterContainer, pomFactory);
+    private PasswordCredentials createPasswordCredentials() {
+        return instantiator.newInstance(DefaultPasswordCredentials.class);
     }
 
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/EnhancedArtifactDownloadReport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/EnhancedArtifactDownloadReport.java
new file mode 100644
index 0000000..92c2d83
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/EnhancedArtifactDownloadReport.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 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.repositories;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+
+public class EnhancedArtifactDownloadReport extends ArtifactDownloadReport {
+    private Throwable failure;
+
+    public EnhancedArtifactDownloadReport(Artifact artifact) {
+        super(artifact);
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+
+    public void failed(Throwable throwable) {
+        setDownloadStatus(DownloadStatus.FAILED);
+        setDownloadDetails(throwable.getMessage());
+        failure = throwable;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceRepository.java
new file mode 100644
index 0000000..e39ad35
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceRepository.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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.repositories;
+
+import org.apache.ivy.plugins.repository.Repository;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface ExternalResourceRepository extends Repository {
+
+    void downloadResource(ExternalResource resource, File destination) throws IOException;
+
+    ExternalResource getResource(String source) throws IOException;
+
+    ExternalResource getResource(String source, @Nullable LocallyAvailableResourceCandidates localCandidates, @Nullable CachedExternalResource cached) throws IOException;
+
+    /**
+     * Fetches only the metadata for the result.
+     *
+     * @param source The location of the resource to obtain the metadata for
+     * @return The resource metadata, or null if the resource does not exist
+     * @throws IOException
+     */
+    @Nullable
+    ExternalResourceMetaData getResourceMetaData(String source) throws IOException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java
new file mode 100644
index 0000000..68a8732
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright 2012 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.repositories;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.plugins.latest.ArtifactInfo;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.util.MDResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResolverHelper;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.plugins.version.VersionMatcher;
+import org.apache.ivy.util.ChecksumHelper;
+import org.apache.ivy.util.FileUtil;
+import org.apache.ivy.util.Message;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.MetaDataOnlyExternalResource;
+import org.gradle.api.internal.externalresource.MissingExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+public class ExternalResourceResolver extends BasicResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceResolver.class);
+
+    private final TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
+    private List<String> ivyPatterns = new ArrayList<String>();
+    private List<String> artifactPatterns = new ArrayList<String>();
+    private boolean m2compatible;
+    private final ExternalResourceRepository repository;
+    private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
+
+    public ExternalResourceResolver(String name,
+                                    ExternalResourceRepository repository,
+                                    LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                                    CachedExternalResourceIndex<String> cachedExternalResourceIndex
+    ) {
+        setName(name);
+        this.repository = repository;
+        this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
+        this.cachedExternalResourceIndex = cachedExternalResourceIndex;
+    }
+
+    protected ExternalResourceRepository getRepository() {
+        return repository;
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        ModuleRevisionId mrid = dd.getDependencyRevisionId();
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(mrid);
+        }
+        return findResourceUsingPatterns(mrid, ivyPatterns, DefaultArtifact.newIvyArtifact(mrid, data.getDate()), getRMDParser(dd, data), data.getDate(), true);
+    }
+
+    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected ResolvedResource findFirstArtifactRef(ModuleDescriptor md, DependencyDescriptor dd,
+                                                    ResolveData data) {
+        for (String configuration : md.getConfigurationsNames()) {
+            for (Artifact artifact : md.getArtifacts(configuration)) {
+                ResolvedResource artifactRef = getArtifactRef(artifact, data.getDate(), false);
+                if (artifactRef != null) {
+                    return artifactRef;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean exists(Artifact artifact) {
+        return locate(artifact) != null;
+    }
+
+    @Override
+    public ArtifactOrigin locate(Artifact artifact) {
+        ResolvedResource artifactRef = getArtifactRef(artifact, null, false);
+        if (artifactRef != null && artifactRef.getResource().exists()) {
+            return new ArtifactOriginWithMetaData(artifact, artifactRef.getResource());
+        }
+        return null;
+    }
+
+    @Override
+    protected ResolvedResource getArtifactRef(Artifact artifact, Date date) {
+        return getArtifactRef(artifact, date, true);
+    }
+
+    protected ResolvedResource getArtifactRef(Artifact artifact, Date date, boolean forDownload) {
+        ModuleRevisionId mrid = artifact.getModuleRevisionId();
+        if (isM2compatible()) {
+            mrid = convertM2IdForResourceSearch(mrid);
+        }
+        return findResourceUsingPatterns(mrid, artifactPatterns, artifact,
+                getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date, forDownload);
+    }
+
+    protected ResolvedResource findResourceUsingPatterns(ModuleRevisionId moduleRevision, List<String> patternList, Artifact artifact, ResourceMDParser rmdparser, Date date, boolean forDownload) {
+        List<ResolvedResource> resolvedResources = new ArrayList<ResolvedResource>();
+        Set<String> foundRevisions = new HashSet<String>();
+        boolean dynamic = getSettings().getVersionMatcher().isDynamic(moduleRevision);
+        for (String pattern : patternList) {
+            ResolvedResource rres = findResourceUsingPattern(moduleRevision, pattern, artifact, rmdparser, date, forDownload);
+            if ((rres != null) && !foundRevisions.contains(rres.getRevision())) {
+                // only add the first found ResolvedResource for each revision
+                foundRevisions.add(rres.getRevision());
+                resolvedResources.add(rres);
+                if (!dynamic) {
+                    break;
+                }
+            }
+        }
+
+        if (resolvedResources.size() > 1) {
+            ResolvedResource[] rress = resolvedResources.toArray(new ResolvedResource[resolvedResources.size()]);
+            List<ResolvedResource> sortedResources = getLatestStrategy().sort(rress);
+            // Discard all but the last, which is returned
+            for (int i = 0; i < sortedResources.size() - 1; i++) {
+                ResolvedResource resolvedResource = sortedResources.get(i);
+                discardResource(resolvedResource.getResource());
+            }
+            return sortedResources.get(sortedResources.size() - 1);
+        } else if (resolvedResources.size() == 1) {
+            return resolvedResources.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    public ResolvedResource findLatestResource(ModuleRevisionId mrid, String[] versions, ResourceMDParser rmdparser, Date date, String pattern, Artifact artifact, boolean forDownload) throws IOException {
+        String name = getName();
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+
+        List<String> sorted = sortVersionsLatestFirst(versions);
+        for (String version : sorted) {
+            ModuleRevisionId foundMrid = ModuleRevisionId.newInstance(mrid, version);
+
+            if (!versionMatcher.accept(mrid, foundMrid)) {
+                LOGGER.debug(name + ": rejected by version matcher: " + version);
+                continue;
+            }
+
+            boolean needsModuleDescriptor = versionMatcher.needModuleDescriptor(mrid, foundMrid);
+            String resourcePath = IvyPatternHelper.substitute(pattern, foundMrid, artifact);
+            Resource resource = getResource(resourcePath, artifact, forDownload || needsModuleDescriptor);
+            String description = version + " [" + resource + "]";
+            if (!resource.exists()) {
+                LOGGER.debug(name + ": unreachable: " + description);
+                discardResource(resource);
+                continue;
+            }
+            if (date != null && resource.getLastModified() > date.getTime()) {
+                LOGGER.debug(name + ": too young: " + description);
+                discardResource(resource);
+                continue;
+            }
+            if (versionMatcher.needModuleDescriptor(mrid, foundMrid)) {
+                MDResolvedResource parsedResource = rmdparser.parse(resource, version);
+                if (parsedResource == null) {
+                    LOGGER.debug(name + ": impossible to get module descriptor resource: " + description);
+                    discardResource(resource);
+                    continue;
+                }
+                ModuleDescriptor md = parsedResource.getResolvedModuleRevision().getDescriptor();
+                if (md.isDefault()) {
+                    LOGGER.debug(name + ": default md rejected by version matcher requiring module descriptor: " + description);
+                    discardResource(resource);
+                    continue;
+                } else if (!versionMatcher.accept(mrid, md)) {
+                    LOGGER.debug(name + ": md rejected by version matcher: " + description);
+                    discardResource(resource);
+                    continue;
+                }
+
+                return parsedResource;
+            }
+            return new ResolvedResource(resource, version);
+        }
+        return null;
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        EventManager eventManager = getEventManager();
+        try {
+            if (eventManager != null) {
+                repository.addTransferListener(eventManager);
+            }
+            return super.download(artifacts, options);
+        } finally {
+            if (eventManager != null) {
+                repository.removeTransferListener(eventManager);
+            }
+        }
+    }
+
+    protected ResolvedResource findResourceUsingPattern(ModuleRevisionId moduleRevisionId, String pattern, Artifact artifact, ResourceMDParser resourceParser, Date date, boolean forDownload) {
+        String name = getName();
+        VersionMatcher versionMatcher = getSettings().getVersionMatcher();
+        try {
+            if (!versionMatcher.isDynamic(moduleRevisionId)) {
+                return findStaticResourceUsingPattern(moduleRevisionId, pattern, artifact, forDownload);
+            } else {
+                return findDynamicResourceUsingPattern(resourceParser, moduleRevisionId, pattern, artifact, date, forDownload);
+            }
+        } catch (IOException ex) {
+            throw new RuntimeException(name + ": unable to get resource for " + moduleRevisionId + ": res=" + IvyPatternHelper.substitute(pattern, moduleRevisionId, artifact) + ": " + ex, ex);
+        }
+    }
+
+    private ResolvedResource findStaticResourceUsingPattern(ModuleRevisionId moduleRevisionId, String pattern, Artifact artifact, boolean forDownload) throws IOException {
+        String resourceName = IvyPatternHelper.substitute(pattern, moduleRevisionId, artifact);
+        logAttempt(resourceName);
+
+        LOGGER.debug("Loading {}", resourceName);
+        Resource res = getResource(resourceName, artifact, forDownload);
+        if (res.exists()) {
+            String revision = moduleRevisionId.getRevision();
+            return new ResolvedResource(res, revision);
+        } else {
+            LOGGER.debug("Resource not reachable for {}: res={}", moduleRevisionId, res);
+            return null;
+        }
+    }
+
+    private ResolvedResource findDynamicResourceUsingPattern(ResourceMDParser resourceParser, ModuleRevisionId moduleRevisionId, String pattern, Artifact artifact, Date date, boolean forDownload) throws IOException {
+        logAttempt(IvyPatternHelper.substitute(pattern, ModuleRevisionId.newInstance(moduleRevisionId, IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY)), artifact));
+        String[] versions = listVersions(moduleRevisionId, pattern, artifact);
+        if (versions == null) {
+            LOGGER.debug("Unable to list versions for {}: pattern={}", moduleRevisionId, pattern);
+            return null;
+        } else {
+            ResolvedResource found = findLatestResource(moduleRevisionId, versions, resourceParser, date, pattern, artifact, forDownload);
+            if (found == null) {
+                LOGGER.debug("No resource found for {}: pattern={}", moduleRevisionId, pattern);
+            }
+            return found;
+        }
+    }
+
+    protected void discardResource(Resource resource) {
+        if (resource instanceof ExternalResource) {
+            try {
+                ((ExternalResource) resource).close();
+            } catch (IOException e) {
+                LOGGER.warn("Exception closing resource " + resource.getName(), e);
+            }
+        }
+    }
+
+    private List<String> sortVersionsLatestFirst(String[] versions) {
+        ArtifactInfo[] artifactInfos = new ArtifactInfo[versions.length];
+        for (int i = 0; i < versions.length; i++) {
+            String version = versions[i];
+            artifactInfos[i] = new VersionArtifactInfo(version);
+        }
+        List<ArtifactInfo> sorted = getLatestStrategy().sort(artifactInfos);
+        Collections.reverse(sorted);
+
+        List<String> sortedVersions = new ArrayList<String>();
+        for (ArtifactInfo info : sorted) {
+            sortedVersions.add(info.getRevision());
+        }
+        return sortedVersions;
+    }
+
+    protected Resource getResource(String source) throws IOException {
+        ExternalResource resource = repository.getResource(source);
+        return resource == null ? new MissingExternalResource(source) : resource;
+    }
+
+    protected Resource getResource(String source, Artifact target, boolean forDownload) throws IOException {
+        if (forDownload) {
+            ArtifactRevisionId arid = target.getId();
+            LocallyAvailableResourceCandidates localCandidates = locallyAvailableResourceFinder.findCandidates(arid);
+            CachedExternalResource cached = cachedExternalResourceIndex.lookup(source);
+            ExternalResource resource = repository.getResource(source, localCandidates, cached);
+            return resource == null ? new MissingExternalResource(source) : resource;
+        } else {
+            // TODO - there's a potential problem here in that we don't carry correct isLocal data in MetaDataOnlyExternalResource
+            ExternalResourceMetaData metaData = repository.getResourceMetaData(source);
+            return metaData == null ? new MissingExternalResource(source) : new MetaDataOnlyExternalResource(source, metaData);
+        }
+    }
+
+    protected String[] listVersions(ModuleRevisionId moduleRevisionId, String pattern, Artifact artifact) {
+        ModuleRevisionId idWithoutRevision = ModuleRevisionId.newInstance(moduleRevisionId, IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY));
+        String partiallyResolvedPattern = IvyPatternHelper.substitute(pattern, idWithoutRevision, artifact);
+        LOGGER.debug("Listing all in {}", partiallyResolvedPattern);
+        return ResolverHelper.listTokenValues(repository, partiallyResolvedPattern, IvyPatternHelper.REVISION_KEY);
+    }
+
+    protected long get(Resource resource, File destination) throws IOException {
+        LOGGER.debug("Downloading {} to {}", resource.getName(), destination);
+        if (destination.getParentFile() != null) {
+            destination.getParentFile().mkdirs();
+        }
+
+        if (!(resource instanceof ExternalResource)) {
+            throw new IllegalArgumentException("Can only download ExternalResource");
+        }
+
+        repository.downloadResource((ExternalResource) resource, destination);
+        return destination.length();
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        String destinationPattern;
+        if ("ivy".equals(artifact.getType()) && !getIvyPatterns().isEmpty()) {
+            destinationPattern = getIvyPatterns().get(0);
+        } else if (!getArtifactPatterns().isEmpty()) {
+            destinationPattern = getArtifactPatterns().get(0);
+        } else {
+            throw new IllegalStateException("impossible to publish " + artifact + " using " + this + ": no artifact pattern defined");
+        }
+        // Check for m2 compatibility
+        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
+        if (isM2compatible()) {
+            moduleRevisionId = convertM2IdForResourceSearch(moduleRevisionId);
+        }
+
+        String destination = getDestination(destinationPattern, artifact, moduleRevisionId);
+
+        put(artifact, src, destination, overwrite);
+        LOGGER.info("Published {} to {}", artifact.getName(), hidePassword(destination));
+    }
+
+    private String getDestination(String pattern, Artifact artifact, ModuleRevisionId moduleRevisionId) {
+        return IvyPatternHelper.substitute(pattern, moduleRevisionId, artifact);
+    }
+
+    private void put(Artifact artifact, File src, String destination, boolean overwrite) throws IOException {
+        // verify the checksum algorithms before uploading artifacts!
+        String[] checksums = getChecksumAlgorithms();
+        for (String checksum : checksums) {
+            if (!ChecksumHelper.isKnownAlgorithm(checksum)) {
+                throw new IllegalArgumentException("Unknown checksum algorithm: " + checksum);
+            }
+        }
+
+        repository.put(artifact, src, destination, overwrite);
+        for (String checksum : checksums) {
+            putChecksum(artifact, src, destination, overwrite, checksum);
+        }
+    }
+
+    private void putChecksum(Artifact artifact, File src, String destination, boolean overwrite,
+                             String algorithm) throws IOException {
+        File csFile = temporaryFileProvider.createTemporaryFile("ivytemp", algorithm);
+        try {
+            FileUtil.copy(new ByteArrayInputStream(ChecksumHelper.computeAsString(src, algorithm)
+                    .getBytes()), csFile, null);
+            repository.put(DefaultArtifact.cloneWithAnotherTypeAndExt(artifact, algorithm,
+                    artifact.getExt() + "." + algorithm), csFile, destination + "." + algorithm, overwrite);
+        } finally {
+            csFile.delete();
+        }
+    }
+
+    protected Collection findNames(Map tokenValues, String token) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addIvyPattern(String pattern) {
+        ivyPatterns.add(pattern);
+    }
+
+    public void addArtifactPattern(String pattern) {
+        artifactPatterns.add(pattern);
+    }
+
+    public List<String> getIvyPatterns() {
+        return Collections.unmodifiableList(ivyPatterns);
+    }
+
+    public List<String> getArtifactPatterns() {
+        return Collections.unmodifiableList(artifactPatterns);
+    }
+
+    protected void setIvyPatterns(List patterns) {
+        ivyPatterns = patterns;
+    }
+
+    protected void setArtifactPatterns(List patterns) {
+        artifactPatterns = patterns;
+    }
+
+    public void dumpSettings() {
+        super.dumpSettings();
+        Message.debug("\t\tm2compatible: " + isM2compatible());
+        Message.debug("\t\tivy patterns:");
+        for (String p : getIvyPatterns()) {
+            Message.debug("\t\t\t" + p);
+        }
+        Message.debug("\t\tartifact patterns:");
+        for (String p : getArtifactPatterns()) {
+            Message.debug("\t\t\t" + p);
+        }
+        Message.debug("\t\trepository: " + repository);
+    }
+
+    public boolean isM2compatible() {
+        return m2compatible;
+    }
+
+    public void setM2compatible(boolean compatible) {
+        m2compatible = compatible;
+    }
+
+    protected ModuleRevisionId convertM2IdForResourceSearch(ModuleRevisionId mrid) {
+        if (mrid.getOrganisation() == null || mrid.getOrganisation().indexOf('.') == -1) {
+            return mrid;
+        }
+        return ModuleRevisionId.newInstance(mrid.getOrganisation().replace('.', '/'),
+                mrid.getName(), mrid.getBranch(), mrid.getRevision(),
+                mrid.getQualifiedExtraAttributes());
+    }
+
+    private class VersionArtifactInfo implements ArtifactInfo {
+        private final String version;
+
+        private VersionArtifactInfo(String version) {
+            this.version = version;
+        }
+
+        public String getRevision() {
+            return version;
+        }
+
+        public long getLastModified() {
+            return 0;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyResolver.java
new file mode 100644
index 0000000..18742ea
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/IvyResolver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+
+import java.net.URI;
+
+public class IvyResolver extends ExternalResourceResolver implements PatternBasedResolver {
+
+    private final RepositoryTransport transport;
+
+    public IvyResolver(String name, RepositoryTransport transport,
+                       LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                       CachedExternalResourceIndex<String> cachedExternalResourceIndex
+    ) {
+        super(name, transport.getRepository(), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+        this.transport = transport;
+        this.transport.configureCacheManager(this);
+    }
+
+    public void addArtifactLocation(URI baseUri, String pattern) {
+        String artifactPattern = transport.convertToPath(baseUri) + pattern;
+        addArtifactPattern(artifactPattern);
+    }
+
+    public void addDescriptorLocation(URI baseUri, String pattern) {
+        String descriptorPattern = transport.convertToPath(baseUri) + pattern;
+        addIvyPattern(descriptorPattern);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/MavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/MavenResolver.java
new file mode 100644
index 0000000..4912aad
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/MavenResolver.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
+import org.apache.ivy.util.ContextualSAXHandler;
+import org.apache.ivy.util.Message;
+import org.apache.ivy.util.XMLHelper;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.*;
+
+public class MavenResolver extends ExternalResourceResolver implements PatternBasedResolver {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MavenResolver.class);
+
+    private static final String M2_PER_MODULE_PATTERN = "[revision]/[artifact]-[revision](-[classifier]).[ext]";
+    private static final String M2_PATTERN = "[organisation]/[module]/" + M2_PER_MODULE_PATTERN;
+
+    private final RepositoryTransport transport;
+    private final String root;
+    private final List<String> artifactRoots = new ArrayList<String>();
+    private String pattern = M2_PATTERN;
+    private boolean usepoms = true;
+    private boolean useMavenMetadata = true;
+
+    public MavenResolver(String name, URI rootUri, RepositoryTransport transport,
+                         LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder,
+                         CachedExternalResourceIndex<String> cachedExternalResourceIndex) {
+        super(name, transport.getRepository(), locallyAvailableResourceFinder, cachedExternalResourceIndex);
+        transport.configureCacheManager(this);
+
+        this.transport = transport;
+        this.root = transport.convertToPath(rootUri);
+
+        setDescriptor(DESCRIPTOR_OPTIONAL);
+        super.setM2compatible(true);
+
+        // SNAPSHOT revisions are changing revisions
+        setChangingMatcher(PatternMatcher.REGEXP);
+        setChangingPattern(".*-SNAPSHOT");
+
+        updatePatterns();
+    }
+
+    public void addArtifactLocation(URI baseUri, String pattern) {
+        if (pattern != null && pattern.length() > 0) {
+            throw new IllegalArgumentException("Maven Resolver only supports a single pattern. It cannot be provided on a per-location basis.");
+        }
+        artifactRoots.add(transport.convertToPath(baseUri));
+
+        updatePatterns();
+    }
+
+    public void addDescriptorLocation(URI baseUri, String pattern) {
+        throw new UnsupportedOperationException("Cannot have multiple descriptor urls for MavenResolver");
+    }
+
+    private String getWholePattern() {
+        return root + pattern;
+    }
+
+    private void updatePatterns() {
+        if (shouldResolveDependencyDescriptors()) {
+            setIvyPatterns(Collections.singletonList(getWholePattern()));
+        } else {
+            setIvyPatterns(Collections.EMPTY_LIST);
+        }
+
+        List<String> artifactPatterns = new ArrayList<String>();
+        artifactPatterns.add(getWholePattern());
+        for (String artifactRoot : artifactRoots) {
+            artifactPatterns.add(artifactRoot + pattern);
+        }
+        setArtifactPatterns(artifactPatterns);
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        if (shouldResolveDependencyDescriptors()) {
+            ModuleRevisionId moduleRevisionId = convertM2IdForResourceSearch(dd.getDependencyRevisionId());
+
+            if (moduleRevisionId.getRevision().endsWith("SNAPSHOT")) {
+                ResolvedResource resolvedResource = findSnapshotDescriptor(dd, data, moduleRevisionId, true);
+                if (resolvedResource != null) {
+                    return resolvedResource;
+                }
+            }
+
+            Artifact pomArtifact = DefaultArtifact.newPomArtifact(moduleRevisionId, data.getDate());
+            ResourceMDParser parser = getRMDParser(dd, data);
+            return findResourceUsingPatterns(moduleRevisionId, getIvyPatterns(), pomArtifact, parser, data.getDate(), true);
+        }
+
+        return null;
+    }
+
+    private ResolvedResource findSnapshotDescriptor(DependencyDescriptor dd, ResolveData data, ModuleRevisionId moduleRevisionId, boolean forDownload) {
+        String rev = findUniqueSnapshotVersion(moduleRevisionId);
+        if (rev != null) {
+            // here it would be nice to be able to store the resolved snapshot version, to avoid
+            // having to follow the same process to download artifacts
+
+            LOGGER.debug("[{}] {}", rev, moduleRevisionId);
+
+            // replace the revision token in file name with the resolved revision
+            String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev);
+            Artifact pomArtifact = DefaultArtifact.newPomArtifact(moduleRevisionId, data.getDate());
+            return findResourceUsingPattern(moduleRevisionId, pattern, pomArtifact, getRMDParser(dd, data), data.getDate(), forDownload);
+        }
+        return null;
+    }
+
+    protected ResolvedResource getArtifactRef(Artifact artifact, Date date, boolean forDownload) {
+        ModuleRevisionId moduleRevisionId = artifact.getModuleRevisionId();
+        if (isM2compatible()) {
+            moduleRevisionId = convertM2IdForResourceSearch(moduleRevisionId);
+        }
+
+        if (moduleRevisionId.getRevision().endsWith("SNAPSHOT")) {
+            ResolvedResource resolvedResource = findSnapshotArtifact(artifact, date, moduleRevisionId, forDownload);
+            if (resolvedResource != null) {
+                return resolvedResource;
+            }
+        }
+        ResourceMDParser parser = getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId());
+        return findResourceUsingPatterns(moduleRevisionId, getArtifactPatterns(), artifact, parser, date, forDownload);
+    }
+
+    private ResolvedResource findSnapshotArtifact(Artifact artifact, Date date, ModuleRevisionId moduleRevisionId, boolean forDownload) {
+        String rev = findUniqueSnapshotVersion(moduleRevisionId);
+        if (rev != null) {
+            // replace the revision token in file name with the resolved revision
+            // TODO:DAZ We're not using all available artifact patterns here, only the "main" pattern. This means that snapshot artifacts will not be resolved in additional artifact urls.
+            String pattern = getWholePattern().replaceFirst("\\-\\[revision\\]", "-" + rev);
+            return findResourceUsingPattern(moduleRevisionId, pattern, artifact, getDefaultRMDParser(artifact.getModuleRevisionId().getModuleId()), date, forDownload);
+        }
+        return null;
+    }
+
+    private String findUniqueSnapshotVersion(ModuleRevisionId moduleRevisionId) {
+        String metadataLocation = IvyPatternHelper.substitute(root + "[organisation]/[module]/[revision]/maven-metadata.xml", moduleRevisionId);
+        MavenMetadata mavenMetadata = parseMavenMetadata(metadataLocation);
+
+        if (mavenMetadata.timestamp != null) {
+            // we have found a timestamp, so this is a snapshot unique version
+            String rev = moduleRevisionId.getRevision();
+            rev = rev.substring(0, rev.length() - "SNAPSHOT".length());
+            rev = rev + mavenMetadata.timestamp + "-" + mavenMetadata.buildNumber;
+            return rev;
+        }
+        return null;
+    }
+
+    @Override
+    protected String[] listVersions(ModuleRevisionId moduleRevisionId, String pattern, Artifact artifact) {
+        List<String> revisions = listRevisionsWithMavenMetadata(moduleRevisionId.getModuleId().getAttributes());
+        if (revisions != null) {
+            return revisions.toArray(new String[revisions.size()]);
+        }
+        return super.listVersions(moduleRevisionId, pattern, artifact);
+    }
+
+    private List<String> listRevisionsWithMavenMetadata(Map tokenValues) {
+        String metadataLocation = IvyPatternHelper.substituteTokens(root + "[organisation]/[module]/maven-metadata.xml", tokenValues);
+        MavenMetadata mavenMetadata = parseMavenMetadata(metadataLocation);
+        return mavenMetadata.versions.isEmpty() ? null : mavenMetadata.versions;
+    }
+
+    private MavenMetadata parseMavenMetadata(String metadataLocation) {
+        final MavenMetadata mavenMetadata = new MavenMetadata();
+
+        if (shouldUseMavenMetadata(pattern)) {
+            parseMavenMetadataInto(metadataLocation, mavenMetadata);
+        }
+
+        return mavenMetadata;
+    }
+
+    private void parseMavenMetadataInto(String metadataLocation, final MavenMetadata mavenMetadata) {
+        try {
+            Resource metadata = getResource(metadataLocation);
+            try {
+                parseMavenMetadataInto(metadata, mavenMetadata);
+            } finally {
+                discardResource(metadata);
+            }
+        } catch (IOException e) {
+            LOGGER.warn("impossible to access maven metadata file, ignored.", e);
+        } catch (SAXException e) {
+            LOGGER.warn("impossible to parse maven metadata file, ignored.", e);
+        } catch (ParserConfigurationException e) {
+            LOGGER.warn("impossible to parse maven metadata file, ignored.", e);
+        }
+    }
+
+    private void parseMavenMetadataInto(Resource metadataResource, final MavenMetadata mavenMetadata) throws IOException, SAXException, ParserConfigurationException {
+        if (metadataResource.exists()) {
+            LOGGER.debug("parsing maven-metadata: {}", metadataResource);
+            InputStream metadataStream = metadataResource.openStream();
+            XMLHelper.parse(metadataStream, null, new ContextualSAXHandler() {
+                public void endElement(String uri, String localName, String qName)
+                        throws SAXException {
+                    if ("metadata/versioning/snapshot/timestamp".equals(getContext())) {
+                        mavenMetadata.timestamp = getText();
+                    }
+                    if ("metadata/versioning/snapshot/buildNumber".equals(getContext())) {
+                        mavenMetadata.buildNumber = getText();
+                    }
+                    if ("metadata/versioning/versions/version".equals(getContext())) {
+                        mavenMetadata.versions.add(getText().trim());
+                    }
+                    super.endElement(uri, localName, qName);
+                }
+            }, null);
+        } else {
+            LOGGER.debug("maven-metadata not available: {}", metadataResource);
+        }
+    }
+
+    public void dumpSettings() {
+        super.dumpSettings();
+        Message.debug("\t\troot: " + root);
+        Message.debug("\t\tpattern: " + pattern);
+    }
+
+    // A bunch of configuration properties that we don't (yet) support in our model via the DSL. Users can still tweak these on the resolver using mavenRepo().
+    public boolean isUsepoms() {
+        return usepoms;
+    }
+
+    public void setUsepoms(boolean usepoms) {
+        this.usepoms = usepoms;
+        updatePatterns();
+    }
+
+    private boolean shouldResolveDependencyDescriptors() {
+        return isUsepoms() && isM2compatible();
+    }
+
+    public boolean isUseMavenMetadata() {
+        return useMavenMetadata;
+    }
+
+    public void setUseMavenMetadata(boolean useMavenMetadata) {
+        this.useMavenMetadata = useMavenMetadata;
+    }
+
+    private boolean shouldUseMavenMetadata(String pattern) {
+        return isUseMavenMetadata() && isM2compatible() && pattern.endsWith(M2_PATTERN);
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        if (pattern == null) {
+            throw new NullPointerException("pattern must not be null");
+        }
+        this.pattern = pattern;
+        updatePatterns();
+    }
+
+    public String getRoot() {
+        return root;
+    }
+
+    public void setRoot(String root) {
+        throw new UnsupportedOperationException("Cannot configure root on mavenRepo. Use 'url' property instead.");
+    }
+
+    @Override
+    public void setM2compatible(boolean compatible) {
+        if (!compatible) {
+            throw new IllegalArgumentException("Cannot set m2compatible = false on mavenRepo.");
+        }
+    }
+
+    private static class MavenMetadata {
+        public String timestamp;
+        public String buildNumber;
+        public List<String> versions = new ArrayList<String>();
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PatternBasedResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PatternBasedResolver.java
new file mode 100644
index 0000000..ba67378
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PatternBasedResolver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.net.URI;
+import java.util.List;
+
+public interface PatternBasedResolver extends DependencyResolver {
+    void addArtifactLocation(URI baseUri, String pattern);
+
+    void addDescriptorLocation(URI baseUri, String pattern);
+
+    void setM2compatible(boolean b);
+
+    List getIvyPatterns();
+
+    List getArtifactPatterns();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ProgressLoggingTransferListener.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ProgressLoggingTransferListener.java
new file mode 100644
index 0000000..84f7612
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ProgressLoggingTransferListener.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.plugins.repository.TransferListener;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+
+public class ProgressLoggingTransferListener implements TransferListener {
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private final Class loggingClass;
+    private ProgressLogger logger;
+    private long total;
+
+    public ProgressLoggingTransferListener(ProgressLoggerFactory progressLoggerFactory, Class loggingClass) {
+        this.progressLoggerFactory = progressLoggerFactory;
+        this.loggingClass = loggingClass;
+    }
+
+    public void transferProgress(TransferEvent evt) {
+        if (evt.getResource().isLocal()) {
+            return;
+        }
+        if (evt.getEventType() == TransferEvent.TRANSFER_STARTED) {
+            total = 0;
+            logger = progressLoggerFactory.newOperation(loggingClass);
+            String description = String.format("%s %s", StringUtils.capitalize(getRequestType(evt)), evt.getResource().getName());
+            logger.setDescription(description);
+            logger.setLoggingHeader(description);
+            logger.started();
+        }
+        if (evt.getEventType() == TransferEvent.TRANSFER_PROGRESS) {
+            total += evt.getLength();
+            logger.progress(String.format("%s/%s %sed", getLengthText(total), getLengthText(evt), getRequestType(evt)));
+        }
+        if (evt.getEventType() == TransferEvent.TRANSFER_COMPLETED) {
+            logger.completed();
+        }
+    }
+
+    private String getRequestType(TransferEvent evt) {
+        if (evt.getRequestType() == TransferEvent.REQUEST_PUT) {
+            return "upload";
+        } else {
+            return "download";
+        }
+    }
+
+    private static String getLengthText(TransferEvent evt) {
+        return getLengthText(evt.isTotalLengthSet() ? evt.getTotalLength() : null);
+    }
+
+    private static String getLengthText(Long bytes) {
+        if (bytes == null) {
+            return "unknown size";
+        }
+        if (bytes < 1024) {
+            return bytes + " B";
+        } else if (bytes < 1048576) {
+            return (bytes / 1024) + " KB";
+        } else {
+            return String.format("%.2f MB", bytes / 1048576.0);
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
new file mode 100644
index 0000000..85268b6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.repositories.cachemanager;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.cache.ModuleDescriptorWriter;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyContextualiser;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleScopedParserSettings;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ParserRegistry;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+abstract class AbstractRepositoryCacheManager implements RepositoryCacheManager {
+    protected final String name;
+    private final ParserRegistry parserRegistry = new ParserRegistry();
+
+    public AbstractRepositoryCacheManager(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName, String artifactResolverName) {
+    }
+
+    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
+        return null;
+    }
+
+    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) {
+        return null;
+    }
+
+    public void originalToCachedModuleDescriptor(DependencyResolver resolver, ResolvedResource originalMetadataRef, Artifact requestedMetadataArtifact, ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
+    }
+
+    public void clean() {
+    }
+
+    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision) {
+    }
+
+    protected ModuleDescriptor parseModuleDescriptor(DependencyResolver resolver, Artifact moduleArtifact, CacheMetadataOptions options, File artifactFile, Resource resource) throws ParseException {
+        ModuleRevisionId moduleRevisionId = moduleArtifact.getId().getModuleRevisionId();
+        try {
+            IvySettings ivySettings = IvyContextualiser.getIvyContext().getSettings();
+            ParserSettings parserSettings = new ModuleScopedParserSettings(ivySettings, resolver, moduleRevisionId);
+            ModuleDescriptorParser parser = parserRegistry.forResource(resource);
+            return parser.parseDescriptor(parserSettings, artifactFile.toURI().toURL(), resource, options.isValidate());
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
new file mode 100644
index 0000000..21b77ae
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/DownloadingRepositoryCacheManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2011 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.repositories.cachemanager;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.cache.DownloadListener;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactOriginWithMetaData;
+import org.gradle.api.internal.artifacts.repositories.EnhancedArtifactDownloadReport;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.filestore.FileStore;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+/**
+ * A cache manager for remote repositories, that downloads files and stores them in the FileStore provided.
+ */
+public class DownloadingRepositoryCacheManager extends AbstractRepositoryCacheManager {
+    private final FileStore<ArtifactRevisionId> fileStore;
+    private final CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex;
+
+    public DownloadingRepositoryCacheManager(String name, FileStore<ArtifactRevisionId> fileStore, CachedExternalResourceIndex<String> artifactUrlCachedResolutionIndex) {
+        super(name);
+        this.fileStore = fileStore;
+        this.artifactUrlCachedResolutionIndex = artifactUrlCachedResolutionIndex;
+    }
+
+    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver,
+                                           ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+        EnhancedArtifactDownloadReport adr = new EnhancedArtifactDownloadReport(artifact);
+
+        DownloadListener listener = options.getListener();
+        if (listener != null) {
+            listener.needArtifact(this, artifact);
+        }
+
+        long start = System.currentTimeMillis();
+        try {
+            ResolvedResource artifactRef = resourceResolver.resolve(artifact);
+            if (artifactRef != null) {
+                ArtifactOrigin origin = new ArtifactOriginWithMetaData(artifact, artifactRef.getResource());
+                if (listener != null) {
+                    listener.startArtifactDownload(this, artifactRef, artifact, origin);
+                }
+
+                File artifactFile = downloadArtifactFile(artifact, resourceDownloader, artifactRef);
+
+                adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                adr.setSize(artifactFile.length());
+                adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
+                adr.setArtifactOrigin(origin);
+                adr.setLocalFile(artifactFile);
+            } else {
+                adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+                adr.setDownloadStatus(DownloadStatus.FAILED);
+                adr.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
+            }
+        } catch (Throwable throwable) {
+            adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
+            adr.failed(throwable);
+        }
+        if (listener != null) {
+            listener.endArtifactDownload(this, artifact, adr, adr.getLocalFile());
+        }
+        return adr;
+    }
+
+    private File downloadArtifactFile(Artifact artifact, ResourceDownloader resourceDownloader, ResolvedResource artifactRef) throws IOException {
+        File tempFile = fileStore.getTempFile();
+        resourceDownloader.download(artifact, artifactRef.getResource(), tempFile);
+
+        File fileInFileStore = fileStore.add(artifact.getId(), tempFile);
+
+        if (artifactRef.getResource() instanceof ExternalResource) {
+            ExternalResource resource = (ExternalResource) artifactRef.getResource();
+            ExternalResourceMetaData metaData = resource.getMetaData();
+            artifactUrlCachedResolutionIndex.store(metaData.getLocation(), fileInFileStore, metaData);
+        }
+
+        return fileInFileStore;
+    }
+
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, final ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
+        if (!moduleArtifact.isMetadata()) {
+            return null;
+        }
+
+        ArtifactResourceResolver artifactResourceResolver = new ArtifactResourceResolver() {
+            public ResolvedResource resolve(Artifact artifact) {
+                return resolvedResource;
+            }
+        };
+        ArtifactDownloadReport report = download(moduleArtifact, artifactResourceResolver, downloader, new CacheDownloadOptions().setListener(options.getListener()).setForce(true));
+
+        if (report.getDownloadStatus() == DownloadStatus.FAILED) {
+            Message.warn("problem while downloading module descriptor: " + resolvedResource.getResource()
+                    + ": " + report.getDownloadDetails()
+                    + " (" + report.getDownloadTimeMillis() + "ms)");
+            return null;
+        }
+
+        ModuleDescriptor md = parseModuleDescriptor(resolver, moduleArtifact, options, report.getLocalFile(), resolvedResource.getResource());
+        Message.debug("\t" + getName() + ": parsed downloaded md file for " + moduleArtifact.getModuleRevisionId() + "; parsed=" + md.getModuleRevisionId());
+
+        MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(md.getMetadataArtifact());
+        madr.setSearched(true);
+        madr.setDownloadStatus(report.getDownloadStatus());
+        madr.setDownloadDetails(report.getDownloadDetails());
+        madr.setArtifactOrigin(report.getArtifactOrigin());
+        madr.setDownloadTimeMillis(report.getDownloadTimeMillis());
+        madr.setOriginalLocalFile(report.getLocalFile());
+        madr.setSize(report.getSize());
+
+        return new ResolvedModuleRevision(resolver, resolver, md, madr);
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
new file mode 100644
index 0000000..9d01732
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/LocalFileRepositoryCacheManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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.repositories.cachemanager;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.CacheDownloadOptions;
+import org.apache.ivy.core.cache.CacheMetadataOptions;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
+import org.apache.ivy.plugins.repository.ResourceDownloader;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+import java.io.File;
+import java.text.ParseException;
+
+/**
+ * A cache manager for local repositories. Doesn't cache anything, and uses artifacts from their origin.
+ */
+public class LocalFileRepositoryCacheManager extends AbstractRepositoryCacheManager {
+
+    public LocalFileRepositoryCacheManager(String name) {
+        super(name);
+    }
+
+    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
+        long start = System.currentTimeMillis();
+        ArtifactDownloadReport report = new ArtifactDownloadReport(artifact);
+        ResolvedResource resolvedResource = resourceResolver.resolve(artifact);
+        if (resolvedResource == null) {
+            report.setDownloadStatus(DownloadStatus.FAILED);
+            report.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
+            report.setDownloadTimeMillis(System.currentTimeMillis() - start);
+            return report;
+        }
+        assert resolvedResource.getResource().isLocal();
+        File file = new File(resolvedResource.getResource().getName());
+        assert file.isFile();
+
+        ArtifactOrigin origin = new ArtifactOrigin(artifact, true, file.getAbsolutePath());
+        report.setDownloadStatus(DownloadStatus.NO);
+        report.setArtifactOrigin(origin);
+        report.setSize(file.length());
+        report.setLocalFile(file);
+        return report;
+    }
+
+    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, ResolvedResource resolvedResource, DependencyDescriptor dd, Artifact moduleArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
+        if (!moduleArtifact.isMetadata()) {
+            return null;
+        }
+
+        assert resolvedResource.getResource().isLocal();
+        File file = new File(resolvedResource.getResource().getName());
+        assert file.isFile();
+
+        ArtifactOrigin origin = new ArtifactOrigin(moduleArtifact, true, file.getAbsolutePath());
+        MetadataArtifactDownloadReport report = new MetadataArtifactDownloadReport(moduleArtifact);
+        report.setDownloadStatus(DownloadStatus.NO);
+        report.setArtifactOrigin(origin);
+        report.setSize(file.length());
+        report.setLocalFile(file);
+        report.setSearched(false);
+        report.setOriginalLocalFile(file);
+
+        ModuleDescriptor descriptor = parseModuleDescriptor(resolver, moduleArtifact, options, file, resolvedResource.getResource());
+        return new ResolvedModuleRevision(resolver, resolver, descriptor, report);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/GradleRepositoryLayout.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/GradleRepositoryLayout.java
new file mode 100644
index 0000000..b76f88e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/GradleRepositoryLayout.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.repositories.layout;
+
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+
+import java.net.URI;
+
+/**
+ * A Repository Layout that applies the following patterns:
+ * <ul>
+ *     <li>Artifacts: $baseUri/{@value IvyArtifactRepository#GRADLE_ARTIFACT_PATTERN}</li>
+ *     <li>Ivy: $baseUri/{@value IvyArtifactRepository#GRADLE_IVY_PATTERN}</li>
+ * </ul>
+ *
+ * Note the pattern is the same for both artifacts and ivy files.
+ */
+public class GradleRepositoryLayout extends RepositoryLayout {
+
+    public void apply(URI baseUri, PatternBasedResolver resolver) {
+        if (baseUri == null) {
+            return;
+        }
+
+        resolver.addArtifactLocation(baseUri, IvyArtifactRepository.GRADLE_ARTIFACT_PATTERN);
+        resolver.addDescriptorLocation(baseUri, IvyArtifactRepository.GRADLE_IVY_PATTERN);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/MavenRepositoryLayout.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/MavenRepositoryLayout.java
new file mode 100644
index 0000000..ac56dd1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/MavenRepositoryLayout.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.repositories.layout;
+
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+
+import java.net.URI;
+
+/**
+ * A Repository Layout that applies the following patterns:
+ * <ul>
+ *     <li>Artifacts: $baseUri/{@value IvyArtifactRepository#MAVEN_ARTIFACT_PATTERN}</li>
+ *     <li>Ivy: $baseUri/{@value IvyArtifactRepository#MAVEN_IVY_PATTERN}</li>
+ * </ul>
+ *
+ * Following the maven convention, the 'organisation' value is further processed by replacing '.' with '/'.
+ * Note that the resolver will follow the layout only, but will <em>not</em> use .pom files for meta data. Ivy metadata files are required/published.
+ */
+public class MavenRepositoryLayout extends RepositoryLayout {
+
+    public void apply(URI baseUri, PatternBasedResolver resolver) {
+        if (baseUri == null) {
+            return;
+        }
+
+        resolver.setM2compatible(true); // Replace '.' with '/' in organisation
+
+        resolver.addArtifactLocation(baseUri, IvyArtifactRepository.MAVEN_ARTIFACT_PATTERN);
+        resolver.addDescriptorLocation(baseUri, IvyArtifactRepository.MAVEN_IVY_PATTERN);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java
new file mode 100644
index 0000000..65d1ede
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/PatternRepositoryLayout.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.repositories.layout;
+
+import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+
+import java.net.URI;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A Repository Layout that uses user-supplied patterns. Each pattern will be appended to the base URI for the repository.
+ * At least one artifact pattern must be specified. If no ivy patterns are specified, then the artifact patterns will be used.
+ */
+public class PatternRepositoryLayout extends RepositoryLayout {
+    private final Set<String> artifactPatterns = new LinkedHashSet<String>();
+    private final Set<String> ivyPatterns = new LinkedHashSet<String>();
+
+    /**
+     * Adds an Ivy artifact pattern to define where artifacts are located in this repository.
+     * @param pattern The ivy pattern
+     */
+    public void artifact(String pattern) {
+        artifactPatterns.add(pattern);
+    }
+
+    /**
+     * Adds an Ivy pattern to define where ivy files are located in this repository.
+     * @param pattern The ivy pattern
+     */
+    public void ivy(String pattern) {
+        ivyPatterns.add(pattern);
+    }
+
+    @Override
+    public void apply(URI baseUri, PatternBasedResolver resolver) {
+        if (baseUri == null) {
+            return;
+        }
+
+        for (String artifactPattern : artifactPatterns) {
+            resolver.addArtifactLocation(baseUri, artifactPattern);
+        }
+
+        Set<String> usedIvyPatterns = ivyPatterns.isEmpty() ? artifactPatterns : ivyPatterns;
+        for (String ivyPattern : usedIvyPatterns) {
+            resolver.addDescriptorLocation(baseUri, ivyPattern);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/RepositoryLayout.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/RepositoryLayout.java
new file mode 100644
index 0000000..fdaf3b7
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/RepositoryLayout.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.repositories.layout;
+
+import org.gradle.api.internal.artifacts.repositories.PatternBasedResolver;
+
+import java.net.URI;
+import java.util.Set;
+
+/**
+ * Represents the directory structure for a repository.
+ */
+public abstract class RepositoryLayout {
+    /**
+     * Given the base URI, apply the patterns and other configuration for this layout to the supplied resolver.
+     *
+     * @param baseUri The base URI for the repository.
+     * @param resolver The ivy resolver that will be used to resolve this layout.
+     */
+    public abstract void apply(URI baseUri, PatternBasedResolver resolver);
+
+    /**
+     * Add any schemes registered as patterns in this layout, given the supplied base URI.
+     * These are used to determine which repository implementation can be used (local file, http, etc).
+     *
+     * @param baseUri The baseUri of the repository.
+     * @param schemes The set of schemes to add to.
+     */
+    public void addSchemes(URI baseUri, Set<String> schemes) {
+        if (baseUri != null) {
+            schemes.add(baseUri.getScheme());
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/ResolvedPattern.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/ResolvedPattern.java
new file mode 100644
index 0000000..ee7226f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/layout/ResolvedPattern.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.repositories.layout;
+
+import org.gradle.api.internal.file.FileResolver;
+
+import java.net.URI;
+
+public class ResolvedPattern {
+    public final String scheme;
+    public final URI baseUri;
+    public final String pattern;
+    public final String absolutePattern;
+
+    public ResolvedPattern(String rawPattern, FileResolver fileResolver) {
+        // get rid of the ivy [] token, as [ ] are not valid URI characters
+        int pos = rawPattern.indexOf('[');
+        String basePath = pos < 0 ? rawPattern : rawPattern.substring(0, pos);
+        this.baseUri = fileResolver.resolveUri(basePath);
+        this.pattern = pos < 0 ? "" : rawPattern.substring(pos);
+        scheme = baseUri.getScheme().toLowerCase();
+        absolutePattern = constructAbsolutePattern(baseUri, pattern);
+    }
+
+    public ResolvedPattern(URI baseUri, String pattern) {
+        this.baseUri = baseUri;
+        this.pattern = pattern;
+        scheme = baseUri.getScheme().toLowerCase();
+        absolutePattern = constructAbsolutePattern(baseUri, pattern);
+    }
+
+    private String constructAbsolutePattern(URI baseUri, String patternPart) {
+        String uriPart = baseUri.toString();
+        String join = uriPart.endsWith("/") || patternPart.length() == 0 ? "" : "/";
+        return uriPart + join + patternPart;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java
new file mode 100644
index 0000000..240dd12
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransport.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.repositories.transport;
+
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+
+import java.net.URI;
+
+public interface RepositoryTransport {
+    ExternalResourceRepository getRepository();
+
+    void configureCacheManager(AbstractResolver resolver);
+
+    String convertToPath(URI uri);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransportFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransportFactory.java
new file mode 100644
index 0000000..bb6041c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/RepositoryTransportFactory.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 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.repositories.transport;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.TransferListener;
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+import org.gradle.api.internal.artifacts.repositories.ProgressLoggingTransferListener;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.DownloadingRepositoryCacheManager;
+import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
+import org.gradle.api.internal.externalresource.transport.file.FileTransport;
+import org.gradle.api.internal.externalresource.transport.http.HttpTransport;
+import org.gradle.api.internal.filestore.FileStore;
+import org.gradle.logging.ProgressLoggerFactory;
+
+import java.net.URI;
+
+public class RepositoryTransportFactory {
+    private final TransferListener transferListener;
+    private final RepositoryCacheManager downloadingCacheManager;
+    private final RepositoryCacheManager localCacheManager;
+
+    public RepositoryTransportFactory(ProgressLoggerFactory progressLoggerFactory,
+            FileStore<ArtifactRevisionId> fileStore, CachedExternalResourceIndex<String> byUrlCachedExternalResourceIndex) {
+        this.transferListener = new ProgressLoggingTransferListener(progressLoggerFactory, RepositoryTransport.class);
+        this.downloadingCacheManager = new DownloadingRepositoryCacheManager("downloading", fileStore, byUrlCachedExternalResourceIndex);
+        this.localCacheManager = new LocalFileRepositoryCacheManager("local");
+    }
+
+    public RepositoryTransport createHttpTransport(String name, PasswordCredentials credentials) {
+        return decorate(new HttpTransport(name, credentials, downloadingCacheManager));
+    }
+
+    public RepositoryTransport createFileTransport(String name) {
+        return decorate(new FileTransport(name, localCacheManager));
+    }
+    
+    private RepositoryTransport decorate(RepositoryTransport original) {
+        return new ListeningRepositoryTransport(original);
+    }
+
+    public void attachListener(Repository repository) {
+        if (!repository.hasTransferListener(transferListener)) {
+            repository.addTransferListener(transferListener);
+        }
+    }
+
+    public RepositoryCacheManager getDownloadingCacheManager() {
+        return downloadingCacheManager;
+    }
+
+    public RepositoryCacheManager getLocalCacheManager() {
+        return localCacheManager;
+    }
+
+    public TransferListener getTransferListener() {
+        return transferListener;
+    }
+
+    private class ListeningRepositoryTransport implements RepositoryTransport {
+        private final RepositoryTransport delegate;
+
+        private ListeningRepositoryTransport(RepositoryTransport delegate) {
+            this.delegate = delegate;
+        }
+
+        public void configureCacheManager(AbstractResolver resolver) {
+            delegate.configureCacheManager(resolver);
+        }
+
+        public ExternalResourceRepository getRepository() {
+            ExternalResourceRepository repository = delegate.getRepository();
+            attachListener(repository);
+            return repository;
+        }
+
+        public String convertToPath(URI uri) {
+            return delegate.convertToPath(uri);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/file/FileExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/file/FileExternalResourceRepository.java
new file mode 100644
index 0000000..a85cd58
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/file/FileExternalResourceRepository.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.repositories.transport.file;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.file.FileRepository;
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.ExternalResourceIvyResourceAdapter;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileExternalResourceRepository extends FileRepository implements ExternalResourceRepository {
+
+    public void downloadResource(ExternalResource resource, File destination) throws IOException {
+        get(resource.getName(), destination);
+    }
+
+    public ExternalResource getResource(String source, LocallyAvailableResourceCandidates localCandidates, CachedExternalResource cached) throws IOException {
+        return getResource(source);
+    }
+
+    public ExternalResourceMetaData getResourceMetaData(String source) throws IOException {
+        Resource resource = getResource(source);
+        return resource == null || !resource.exists() ? null : new ExternalResourceIvyResourceAdapter(resource).getMetaData();
+    }
+
+    public ExternalResource getResource(String source) throws IOException {
+        return new ExternalResourceIvyResourceAdapter(super.getResource(source));
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java
new file mode 100644
index 0000000..fe7ade2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.externalresource;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.CopyProgressListener;
+import org.apache.ivy.util.FileUtil;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class AbstractExternalResource implements ExternalResource {
+    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
+        FileOutputStream output = new FileOutputStream(destination);
+        try {
+            InputStream input = openStream();
+            try {
+                FileUtil.copy(input, output, progress);
+            } finally {
+                input.close();
+            }
+        } finally {
+            output.close();
+        }
+    }
+
+    public Resource clone(String cloneName) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void close() throws IOException {
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java
new file mode 100644
index 0000000..d606a94
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.externalresource;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.CopyProgressListener;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface ExternalResource extends Resource {
+    void writeTo(File destination, CopyProgressListener progress) throws IOException;
+
+    void close() throws IOException;
+
+    @Nullable
+    ExternalResourceMetaData getMetaData();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResourceIvyResourceAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResourceIvyResourceAdapter.java
new file mode 100644
index 0000000..91fc842
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResourceIvyResourceAdapter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 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.externalresource;
+
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.util.CopyProgressListener;
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ExternalResourceIvyResourceAdapter implements ExternalResource {
+
+    private final Resource delegate;
+
+    public ExternalResourceIvyResourceAdapter(Resource delegate) {
+        this.delegate = delegate;
+    }
+
+    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
+        throwUnsupported();
+    }
+
+    public void close() throws IOException {
+        // noop
+    }
+
+    private void throwUnsupported() {
+        throw new UnsupportedOperationException("ExternalResourceIvyResourceAdapter does not support methods added by ExternalResource");
+    }
+
+    public String getName() {
+        return delegate.getName();
+    }
+
+    public long getLastModified() {
+        return delegate.getLastModified();
+    }
+
+    public long getContentLength() {
+        return delegate.getContentLength();
+    }
+
+    public boolean exists() {
+        return delegate.exists();
+    }
+
+    public boolean isLocal() {
+        return delegate.isLocal();
+    }
+
+    public Resource clone(String cloneName) {
+        return delegate.clone(cloneName);
+    }
+
+    public InputStream openStream() throws IOException {
+        return delegate.openStream();
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        return new DefaultExternalResourceMetaData(delegate.getName(), getLastModified(), getContentLength(), null, null);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
new file mode 100644
index 0000000..4ed9fdd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 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.externalresource;
+
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Used when we find a file locally that matches the checksum of some external resource.
+ *
+ * It saves us downloading the file, but we don't get any metadata for it.
+ */
+public class LocalFileStandInExternalResource extends AbstractExternalResource {
+
+    private final File localFile;
+    private final String source;
+    private HashValue sha1;
+    private ExternalResourceMetaData metaData;
+
+    public LocalFileStandInExternalResource(String source, File localFile) {
+        this(source, localFile, null);
+    }
+
+    public LocalFileStandInExternalResource(String source, File localFile, ExternalResourceMetaData metaData) {
+        this.source = source;
+        this.localFile = localFile;
+        this.metaData = metaData;
+    }
+
+    public String getName() {
+        return source;
+    }
+
+    public long getLastModified() {
+        return -1;
+    }
+
+    public long getContentLength() {
+        return localFile.length();
+    }
+
+    public boolean exists() {
+        return true;
+    }
+
+    public boolean isLocal() {
+        return true;
+    }
+
+    public InputStream openStream() throws IOException {
+        return new FileInputStream(localFile);
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        if (metaData == null) {
+            metaData = new DefaultExternalResourceMetaData(source, getLastModified(), getContentLength(), null, getLocalFileSha1());
+        }
+        return metaData;
+    }
+
+    protected File getLocalFile() {
+        return localFile;
+    }
+
+    protected HashValue getSha1(File contentFile) {
+        return HashUtil.createHash(contentFile, "SHA1");
+    }
+
+    protected HashValue getLocalFileSha1() {
+        if (sha1 == null) {
+            sha1 = getSha1(getLocalFile());
+        }
+        return sha1;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java
new file mode 100644
index 0000000..0b4cb72
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocallyAvailableExternalResource.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.externalresource;
+
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.util.hash.HashValue;
+
+public class LocallyAvailableExternalResource extends LocalFileStandInExternalResource {
+
+    private final LocallyAvailableResource locallyAvailableResource;
+
+    public LocallyAvailableExternalResource(String source, LocallyAvailableResource locallyAvailableResource) {
+        this(source, locallyAvailableResource, null);
+    }
+
+    public LocallyAvailableExternalResource(String source, LocallyAvailableResource locallyAvailableResource, ExternalResourceMetaData metaData) {
+        super(source, locallyAvailableResource.getFile(), metaData);
+        this.locallyAvailableResource = locallyAvailableResource;
+    }
+
+    @Override
+    public long getContentLength() {
+        return locallyAvailableResource.getContentLength();
+    }
+
+    @Override
+    protected HashValue getLocalFileSha1() {
+        return locallyAvailableResource.getSha1();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/MetaDataOnlyExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/MetaDataOnlyExternalResource.java
new file mode 100644
index 0000000..7a428b6
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/MetaDataOnlyExternalResource.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 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.externalresource;
+
+import org.apache.ivy.util.CopyProgressListener;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MetaDataOnlyExternalResource extends AbstractExternalResource {
+
+    private final ExternalResourceMetaData metaData;
+    private final String source;
+    private final boolean local;
+
+    public MetaDataOnlyExternalResource(String source, ExternalResourceMetaData metaData) {
+        this(source, metaData, false);
+    }
+
+    public MetaDataOnlyExternalResource(String source, ExternalResourceMetaData metaData, boolean local) {
+        this.source = source;
+        this.metaData = metaData;
+        this.local = local;
+    }
+
+    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        return metaData;
+    }
+
+    @Override
+    public String toString() {
+        return "MetaDataOnlyResource: " + getName();
+    }
+
+    public String getName() {
+        return source;
+    }
+
+    public boolean exists() {
+        return true;
+    }
+
+    public long getLastModified() {
+        return -1;
+    }
+
+    public long getContentLength() {
+        return -1;
+    }
+
+    public boolean isLocal() {
+        return local;
+    }
+
+    public InputStream openStream() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/MissingExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/MissingExternalResource.java
new file mode 100644
index 0000000..d1dcfaa
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/MissingExternalResource.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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.externalresource;
+
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Sentinel for representing that there is no resource at some source location.
+ */
+public class MissingExternalResource extends AbstractExternalResource {
+    private final String source;
+    private final ExternalResourceMetaData metaData;
+
+    public MissingExternalResource(String source) {
+        this.source = source;
+        this.metaData = new DefaultExternalResourceMetaData(source);
+    }
+
+    @Override
+    public String toString() {
+        return "MissingResource: " + getName();
+    }
+
+    public String getName() {
+        return source;
+    }
+
+    public boolean exists() {
+        return false;
+    }
+
+    public long getLastModified() {
+        return -1;
+    }
+
+    public long getContentLength() {
+        return -1;
+    }
+
+    public boolean isLocal() {
+        throw new UnsupportedOperationException();
+    }
+
+    public InputStream openStream() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        return this.metaData;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
new file mode 100644
index 0000000..c64b00c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.externalresource.cached;
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.util.TimeProvider;
+
+import java.io.File;
+
+public class ByUrlCachedExternalResourceIndex extends DefaultCachedExternalResourceIndex<String> {
+
+    public ByUrlCachedExternalResourceIndex(File persistentCacheFile, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        super(persistentCacheFile, String.class, timeProvider, cacheLockingManager);
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
new file mode 100644
index 0000000..4fc7561
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResource.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 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.externalresource.cached;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * A record of some kind of external resource that has been cached locally (typically into the filestore).
+ *
+ * Note that this is not a kind of {@link org.gradle.api.internal.externalresource.ExternalResource}. There is
+ * an adapter that can represent a cached resource as an external resource as {@link CachedExternalResourceAdapter}.
+ */
+public interface CachedExternalResource {
+
+    /**
+     * True if this cache entry represents that the resource does not exist.
+     *
+     * For a missing resource, all of the values will be null or similar “non” values.
+     *
+     * @return Whether this is a “missing” entry or not.
+     */
+    boolean isMissing();
+
+    /**
+     * The cached version of the external resource as a local file.
+     *
+     * @return The cached version of the external resource as a local file, or null if this {@link #isMissing()}.
+     */
+    @Nullable
+    File getCachedFile();
+
+    /**
+     * The timestamp of when this cache entry was created.
+     *
+     * @return The timestamp of when this cache entry was created.
+     */
+    long getCachedAt();
+
+    /**
+     * Always the actual content length of the cached file, not the external source.
+     *
+     * @return The content length of the cached file.
+     */
+    long getContentLength();
+
+    /**
+     * Always the actual checksum of the cached file, not the external source.
+     *
+     * @return The hash of the cached file.
+     */
+    HashValue getSha1();
+
+    @Nullable
+    ExternalResourceMetaData getExternalResourceMetaData();
+
+    /**
+     * Null safe shortcut for getExternalResourceMetaData().getLastModified();
+     *
+     * @return The external last modified, or null if unavailable.
+     */
+    Date getExternalLastModified();
+
+    /**
+     * Null safe shortcut for getExternalResourceMetaData().getLastModified() as a timestamp.
+     *
+     * @return The external last modified as a timestamp, or < 0 if unavailable.
+     */
+    long getExternalLastModifiedAsTimestamp();
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceAdapter.java
new file mode 100644
index 0000000..1550906
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceAdapter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.externalresource.cached;
+
+import org.apache.ivy.util.CopyProgressListener;
+import org.gradle.api.internal.externalresource.LocalFileStandInExternalResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Creates an ExternalResource from something that has been cached locally.
+ */
+public class CachedExternalResourceAdapter extends LocalFileStandInExternalResource {
+    private final CachedExternalResource cached;
+    private final ExternalResourceAccessor accessor;
+
+    public CachedExternalResourceAdapter(String source, CachedExternalResource cached, ExternalResourceAccessor accessor) {
+        this(source, cached, accessor, null);
+    }
+
+    public CachedExternalResourceAdapter(String source, CachedExternalResource cached, ExternalResourceAccessor accessor, ExternalResourceMetaData metaData) {
+        super(source, cached.getCachedFile(), metaData);
+        this.cached = cached;
+        this.accessor = accessor;
+    }
+
+    @Override
+    public String toString() {
+        return "CachedResource: " + cached.getCachedFile() + " for " + getName();
+    }
+
+    public long getLastModified() {
+        return cached.getExternalLastModifiedAsTimestamp();
+    }
+
+    public long getContentLength() {
+        return cached.getContentLength();
+    }
+
+    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
+        try {
+            super.writeTo(destination, progress);
+        } catch (IOException e) {
+            downloadResourceDirect(destination, progress);
+            return;
+        }
+
+        // If the checksum of the downloaded file does not match the cached artifact, download it directly.
+        // This may be the case if the cached artifact was changed before copying
+        if (!getSha1(destination).equals(getLocalFileSha1())) {
+            downloadResourceDirect(destination, progress);
+        }
+    }
+
+    private void downloadResourceDirect(File destination, CopyProgressListener progress) throws IOException {
+        // Perform a regular download, without considering external caches
+        accessor.getResource(getName()).writeTo(destination, progress);
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceIndex.java
new file mode 100644
index 0000000..8110de5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/CachedExternalResourceIndex.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.externalresource.cached;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+
+import java.io.File;
+
+/**
+ * Provides an indexed view into cached artifacts and a record of resolution attempts, successful or not.
+ *
+ * Maintains references to the location of files in the persistent local. Does not deal with moving files into the local.
+ * 
+ * @param <K> The type of the key to the index
+ */
+public interface CachedExternalResourceIndex<K> {
+
+    /**
+     * Adds a resolution to the index.
+     * 
+     * The incoming file is expected to be in the persistent local. This method will not move/copy the file there.
+     * <p>
+     *
+     * @param key The key to cache this resolution under in the index. Cannot be null.
+     * @param artifactFile The artifact file in the persistent file store. Cannot be null
+     * @param metaData Information about this resource at its source
+     * @see #storeMissing(Object)
+     */
+    void store(K key, File artifactFile, @Nullable ExternalResourceMetaData metaData);
+
+    /**
+     * Record that the artifact with the given key was missing.
+     *
+     * @param key The key to cache this resolution under in the index.
+     */
+    void storeMissing(K key);
+
+    /**
+     * Lookup a cached resolution.
+     *
+     * The {@link CachedExternalResource#getCachedFile()} is guaranteed
+     * to exist at the time that the entry is returned from this method.
+     *
+     * @param key The key to search the index for
+     * @return The cached artifact resolution if one exists, otherwise null.
+     */
+    @Nullable
+    CachedExternalResource lookup(K key);
+
+    /**
+     * Remove the entry for the given key if it exists.
+     *
+     * @param key The key of the item to remove.
+     */
+    void clear(K key);
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java
new file mode 100644
index 0000000..4062d03
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResource.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 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.externalresource.cached;
+
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Date;
+
+public class DefaultCachedExternalResource implements CachedExternalResource, Serializable {
+
+    private final File cachedFile;
+    private final long cachedAt;
+    private final ExternalResourceMetaData externalResourceMetaData;
+    private HashValue sha1;
+
+    public DefaultCachedExternalResource(File cachedFile, long cachedAt, ExternalResourceMetaData externalResourceMetaData) {
+        this.cachedFile = cachedFile;
+        this.cachedAt = cachedAt;
+        this.externalResourceMetaData = externalResourceMetaData;
+    }
+
+    public DefaultCachedExternalResource(long cachedAt) {
+        this.cachedAt = cachedAt;
+
+        this.cachedFile = null;
+        this.externalResourceMetaData = null;
+    }
+
+    public boolean isMissing() {
+        return cachedFile == null;
+    }
+
+    public File getCachedFile() {
+        return cachedFile;
+    }
+
+    public long getCachedAt() {
+        return cachedAt;
+    }
+
+    public ExternalResourceMetaData getExternalResourceMetaData() {
+        return externalResourceMetaData;
+    }
+
+    public Date getExternalLastModified() {
+        return externalResourceMetaData != null ? externalResourceMetaData.getLastModified() : null;
+    }
+
+    public long getExternalLastModifiedAsTimestamp() {
+        Date externalLastModified = getExternalLastModified();
+        return externalLastModified == null ? -1 : externalLastModified.getTime();
+    }
+
+    public long getContentLength() {
+        return isMissing() ? -1 : cachedFile.length();
+    }
+
+    public HashValue getSha1() {
+        if (isMissing()) {
+            return null;
+        } else {
+            if (sha1 == null) {
+                sha1 = HashUtil.createHash(cachedFile, "SHA1");
+            }
+            return sha1;
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
new file mode 100644
index 0000000..0007b6b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012 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.externalresource.cached;
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.Factory;
+import org.gradle.util.TimeProvider;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultCachedExternalResourceIndex<K extends Serializable> implements CachedExternalResourceIndex<K> {
+
+    private final File persistentCacheFile;
+    private final TimeProvider timeProvider;
+    private final CacheLockingManager cacheLockingManager;
+
+    private PersistentIndexedCache<K, DefaultCachedExternalResource> persistentCache;
+    private final Class<K> keyType;
+
+    public DefaultCachedExternalResourceIndex(File persistentCacheFile, Class<K> keyType, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        this.persistentCacheFile = persistentCacheFile;
+        this.keyType = keyType;
+        this.timeProvider = timeProvider;
+        this.cacheLockingManager = cacheLockingManager;
+    }
+
+    private PersistentIndexedCache<K, DefaultCachedExternalResource> getPersistentCache() {
+        if (persistentCache == null) {
+            persistentCache = initPersistentCache();
+        }
+        return persistentCache;
+    }
+
+    private PersistentIndexedCache<K, DefaultCachedExternalResource> initPersistentCache() {
+        return cacheLockingManager.createCache(persistentCacheFile, keyType, DefaultCachedExternalResource.class);
+    }
+
+    private DefaultCachedExternalResource createMissingEntry() {
+        return new DefaultCachedExternalResource(timeProvider.getCurrentTime());
+    }
+
+    private DefaultCachedExternalResource createEntry(File artifactFile, ExternalResourceMetaData externalResourceMetaData) {
+        return new DefaultCachedExternalResource(artifactFile, timeProvider.getCurrentTime(), externalResourceMetaData);
+    }
+
+    private String operationName(String action) {
+        return String.format("%s from artifact resolution cache '%s'", action, persistentCacheFile.getName());
+    }
+
+    public void store(final K key, final File artifactFile, ExternalResourceMetaData externalResourceMetaData) {
+        if (artifactFile == null) {
+            throw new IllegalArgumentException("artifactFile cannot be null");
+        }
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+
+        storeInternal(key, createEntry(artifactFile, externalResourceMetaData));
+    }
+
+    public void storeMissing(K key) {
+        storeInternal(key, createMissingEntry());
+    }
+
+    private void storeInternal(final K key, final DefaultCachedExternalResource entry) {
+        cacheLockingManager.useCache(operationName("store"), new Runnable() {
+            public void run() {
+                getPersistentCache().put(key, entry);
+            }
+        });
+    }
+
+    public CachedExternalResource lookup(final K key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+
+        return cacheLockingManager.useCache(operationName("store"), new Factory<DefaultCachedExternalResource>() {
+            public DefaultCachedExternalResource create() {
+                DefaultCachedExternalResource found = getPersistentCache().get(key);
+                if (found == null) {
+                    return null;
+                } else if (found.isMissing() || found.getCachedFile().exists()) {
+                    return found;
+                } else {
+                    clear(key);
+                    return null;
+                }
+            }
+        });
+    }
+
+    public void clear(final K key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+
+        cacheLockingManager.useCache(operationName("clear"), new Runnable() {
+            public void run() {
+                getPersistentCache().remove(key);
+            }
+        });
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
new file mode 100644
index 0000000..2b9dba1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.externalresource.ivy;
+
+import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
+import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex;
+import org.gradle.util.TimeProvider;
+
+import java.io.File;
+
+public class ArtifactAtRepositoryCachedExternalResourceIndex extends DefaultCachedExternalResourceIndex<ArtifactAtRepositoryKey> {
+
+    public ArtifactAtRepositoryCachedExternalResourceIndex(File persistentCacheFile, TimeProvider timeProvider, CacheLockingManager cacheLockingManager) {
+        super(persistentCacheFile, ArtifactAtRepositoryKey.class, timeProvider, cacheLockingManager);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
new file mode 100644
index 0000000..41d9760
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryKey.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 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.externalresource.ivy;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
+
+import java.io.Serializable;
+
+public class ArtifactAtRepositoryKey implements Serializable {
+    private final String resolverId;
+    private final String artifactId;
+
+    public ArtifactAtRepositoryKey(ModuleVersionRepository repository, ArtifactRevisionId artifactId) {
+        this(repository, getArtifactKey(artifactId));
+    }
+
+    private ArtifactAtRepositoryKey(ModuleVersionRepository repository, String artifactPath) {
+        this.resolverId = repository.getId();
+        this.artifactId = artifactPath;
+    }
+
+    private static String getArtifactKey(ArtifactRevisionId artifactId) {
+        String format = "[organisation]/[module](/[branch])/[revision]/[type]/[artifact](-[classifier])(.[ext])";
+        Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
+        return IvyPatternHelper.substitute(format, dummyArtifact);
+    }
+
+    @Override
+    public String toString() {
+        return resolverId + ":" + artifactId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof ArtifactAtRepositoryKey)) {
+            return false;
+        }
+        ArtifactAtRepositoryKey other = (ArtifactAtRepositoryKey) o;
+        return toString().equals(other.toString());
+    }
+
+    @Override
+    public int hashCode() {
+        return toString().hashCode();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/AbstractLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/AbstractLocallyAvailableResourceFinder.java
new file mode 100644
index 0000000..2d71841
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/AbstractLocallyAvailableResourceFinder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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.externalresource.local;
+
+import org.gradle.api.Transformer;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+import java.util.List;
+
+public class AbstractLocallyAvailableResourceFinder<C> implements LocallyAvailableResourceFinder<C> {
+
+    private final Transformer<Factory<List<File>>, C> producer;
+
+    public AbstractLocallyAvailableResourceFinder(Transformer<Factory<List<File>>, C> producer) {
+        this.producer = producer;
+    }
+
+    public LocallyAvailableResourceCandidates findCandidates(C criterion) {
+        return new LazyLocallyAvailableResourceCandidates(producer.transform(criterion));
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java
new file mode 100644
index 0000000..db6f783
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 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.externalresource.local;
+
+import org.gradle.util.hash.HashValue;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class CompositeLocallyAvailableResourceFinder<C> implements LocallyAvailableResourceFinder<C> {
+
+    private final List<LocallyAvailableResourceFinder<C>> composites;
+
+    public CompositeLocallyAvailableResourceFinder(List<LocallyAvailableResourceFinder<C>> composites) {
+        this.composites = composites;
+    }
+
+    public LocallyAvailableResourceCandidates findCandidates(C criterion) {
+        List<LocallyAvailableResourceCandidates> allCandidates = new LinkedList<LocallyAvailableResourceCandidates>();
+        for (LocallyAvailableResourceFinder<C> finder : composites) {
+            allCandidates.add(finder.findCandidates(criterion));
+        }
+
+        return new CompositeLocallyAvailableResourceCandidates(allCandidates);
+    }
+    
+    private static class CompositeLocallyAvailableResourceCandidates implements LocallyAvailableResourceCandidates {
+        private final List<LocallyAvailableResourceCandidates> allCandidates;
+
+        public CompositeLocallyAvailableResourceCandidates(List<LocallyAvailableResourceCandidates> allCandidates) {
+            this.allCandidates = allCandidates;
+        }
+
+        public boolean isNone() {
+            for (LocallyAvailableResourceCandidates candidates : allCandidates) {
+                if (!candidates.isNone()) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public LocallyAvailableResource findByHashValue(HashValue hashValue) {
+            for (LocallyAvailableResourceCandidates candidates : allCandidates) {
+                LocallyAvailableResource match = candidates.findByHashValue(hashValue);
+                if (match != null) {
+                    return match;
+                }
+            }
+
+            return null;
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResource.java
new file mode 100644
index 0000000..b6ff3d1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.externalresource.local;
+
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+
+public class DefaultLocallyAvailableResource implements LocallyAvailableResource {
+    private final File origin;
+    
+    // Calculated on demand
+    private HashValue sha1;
+    private Long contentLength;
+    private Long lastModified;
+
+    public DefaultLocallyAvailableResource(File origin) {
+        this.origin = origin;
+    }
+
+    public DefaultLocallyAvailableResource(File origin, HashValue sha1) {
+        this(origin);
+        this.sha1 = sha1;
+    }
+
+    public File getFile() {
+        return origin;
+    }
+
+    public HashValue getSha1() {
+        if (sha1 == null) {
+            this.sha1 = HashUtil.sha1(origin);
+        }
+        return sha1;
+    }
+
+    public long getContentLength() {
+        if (contentLength == null) {
+            contentLength = origin.length();
+        }
+        return contentLength;
+    }
+
+    public long getLastModified() {
+        if (lastModified == null) {
+            lastModified = origin.lastModified();
+        }
+        return lastModified;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java
new file mode 100644
index 0000000..f9a9b69
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidates.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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.externalresource.local;
+
+import org.gradle.internal.Factory;
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+import java.util.List;
+
+public class LazyLocallyAvailableResourceCandidates implements LocallyAvailableResourceCandidates {
+
+    private final Factory<List<File>> filesFactory;
+    private List<File> files;
+
+    public LazyLocallyAvailableResourceCandidates(Factory<List<File>> filesFactory) {
+        this.filesFactory = filesFactory;        
+    }
+
+    protected List<File> getFiles() {
+        if (files == null) {
+            files = filesFactory.create();
+        }
+        return files;
+    }
+    
+    public boolean isNone() {
+        return getFiles().isEmpty();
+    }
+
+    public LocallyAvailableResource findByHashValue(HashValue targetHash) {
+        HashValue thisHash;
+        for (File file : getFiles()) {
+            thisHash = HashUtil.sha1(file);
+            if (thisHash.equals(targetHash)) {
+                return new DefaultLocallyAvailableResource(file, thisHash);
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResource.java
new file mode 100644
index 0000000..5113da1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResource.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.externalresource.local;
+
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+
+public interface LocallyAvailableResource {
+
+    File getFile();
+
+    HashValue getSha1();
+
+    long getLastModified();
+
+    long getContentLength();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java
new file mode 100644
index 0000000..5694ce0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceCandidates.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.externalresource.local;
+
+import org.gradle.util.hash.HashValue;
+
+/**
+ * A set of locally available resources that were “selected” through some means.
+ */
+public interface LocallyAvailableResourceCandidates {
+
+    boolean isNone();
+
+    LocallyAvailableResource findByHashValue(HashValue hashValue);
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java
new file mode 100644
index 0000000..610c33e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinder.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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.externalresource.local;
+
+/**
+ * Can find a locally available candidates for an external resource, through some means.
+ *
+ * This is different to our caching in that we know very little about locally available resources, other than their
+ * binary content. If we can determine the sha1 value of an external resource, we can search the local system to see
+ * if a copy can be found (e.g. the local maven cache).
+ *
+ * @param <C> The type of the criterion object used to find candidates
+ */
+public interface LocallyAvailableResourceFinder<C> {
+
+    LocallyAvailableResourceCandidates findCandidates(C criterion);
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java
new file mode 100644
index 0000000..436c373
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/LocallyAvailableResourceFinderSearchableFileStoreAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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.externalresource.local;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.filestore.FileStoreEntry;
+import org.gradle.api.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.Factory;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Makes a LocallyAvailableResourceFinder out of a FileStoreSearcher.
+ * @param <C> The type of criterion the filestore can be searched for, and therefore locally available resources searched for.
+ */
+public class LocallyAvailableResourceFinderSearchableFileStoreAdapter<C> extends AbstractLocallyAvailableResourceFinder<C> {
+
+    public LocallyAvailableResourceFinderSearchableFileStoreAdapter(final FileStoreSearcher<C> fileStore) {
+        super(new Transformer<Factory<List<File>>, C>() {
+            public Factory<List<File>> transform(final C criterion) {
+                return new Factory<List<File>>() {
+                    public List<File> create() {
+                        Set<? extends FileStoreEntry> entries = fileStore.search(criterion);
+                        return CollectionUtils.collect(entries, new ArrayList<File>(entries.size()), new Transformer<File, FileStoreEntry>() {
+                            public File transform(FileStoreEntry original) {
+                                return original.getFile();
+                            }
+                        });
+                    }
+                };
+            }
+        });
+    }
+
+    
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
new file mode 100644
index 0000000..dd725f9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.externalresource.local.ivy;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
+import org.gradle.api.internal.externalresource.local.CompositeLocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinderSearchableFileStoreAdapter;
+import org.gradle.api.internal.filestore.FileStoreSearcher;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAvailableResourceFinder<ArtifactRevisionId>> {
+    private final File rootCachesDirectory;
+    private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
+    private final FileStoreSearcher<ArtifactRevisionId> fileStore;
+
+    public LocallyAvailableResourceFinderFactory(
+            ArtifactCacheMetaData artifactCacheMetaData, LocalMavenRepositoryLocator localMavenRepositoryLocator, FileStoreSearcher<ArtifactRevisionId> fileStore) {
+        this.rootCachesDirectory = artifactCacheMetaData.getCacheDir().getParentFile();
+        this.localMavenRepositoryLocator = localMavenRepositoryLocator;
+        this.fileStore = fileStore;
+    }
+
+    public LocallyAvailableResourceFinder<ArtifactRevisionId> create() {
+        List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders = new LinkedList<LocallyAvailableResourceFinder<ArtifactRevisionId>>();
+
+        // Order is important here, because they will be searched in that order
+        
+        // The current filestore
+        finders.add(new LocallyAvailableResourceFinderSearchableFileStoreAdapter<ArtifactRevisionId>(fileStore));
+        
+        // Milestone 8 and 9
+        addForPattern(finders, "artifacts-8", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+        
+        // Milestone 7
+        addForPattern(finders, "artifacts-7", "artifacts/*/[organisation]/[module](/[branch])/[revision]/[type]/[artifact]-[revision](-[classifier])(.[ext])");
+
+        // Milestone 6
+        addForPattern(finders, "artifacts-4", "[organisation]/[module](/[branch])/*/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
+        addForPattern(finders, "artifacts-4", "[organisation]/[module](/[branch])/*/pom.originals/[artifact]-[revision](-[classifier])(.[ext])");
+
+        // Milestone 3
+        addForPattern(finders, "../cache", "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
+
+        // Maven local
+        addForPattern(finders, localMavenRepositoryLocator.getLocalMavenRepository(), "[organisation-path]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])");
+
+        return new CompositeLocallyAvailableResourceFinder<ArtifactRevisionId>(finders);
+    }
+
+    private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, String path, String pattern) {
+        addForPattern(finders, new File(rootCachesDirectory, path), pattern);
+    }
+
+    private void addForPattern(List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders, File baseDir, String pattern) {
+        if (baseDir.exists()) {
+            finders.add(new PatternBasedLocallyAvailableResourceFinder(baseDir, pattern));
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
new file mode 100644
index 0000000..f79aaf1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2011 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.externalresource.local.ivy;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.Transformer;
+import org.gradle.api.file.EmptyFileVisitor;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.externalresource.local.AbstractLocallyAvailableResourceFinder;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ArtifactRevisionId> {
+
+    public PatternBasedLocallyAvailableResourceFinder(File baseDir, String pattern) {
+        super(createProducer(baseDir, pattern));
+    }
+
+    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final File baseDir, final String pattern) {
+        return new Transformer<Factory<List<File>>, ArtifactRevisionId>() {
+            DirectoryFileTree fileTree = new DirectoryFileTree(baseDir);
+
+            private String getArtifactPattern(ArtifactRevisionId artifactId) {
+                String substitute = pattern;
+                // Need to handle organisation values that have been munged for m2compatible
+                substitute = IvyPatternHelper.substituteToken(substitute, "organisation", artifactId.getModuleRevisionId().getOrganisation().replace('/', '.'));
+                substitute = IvyPatternHelper.substituteToken(substitute, "organisation-path", artifactId.getModuleRevisionId().getOrganisation().replace('.', '/'));
+
+                Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
+                substitute = IvyPatternHelper.substitute(substitute, dummyArtifact);
+                return substitute;
+            }
+
+            private DirectoryFileTree getMatchingFiles(ArtifactRevisionId artifact) {
+                String patternString = getArtifactPattern(artifact);
+                PatternFilterable pattern = new PatternSet();
+                pattern.include(patternString);
+                return fileTree.filter(pattern);
+            }
+
+            public Factory<List<File>> transform(final ArtifactRevisionId artifactId) {
+                return new Factory<List<File>>() {
+                    public List<File> create() {
+                        final List<File> files = new LinkedList<File>();
+                        if (artifactId != null) {
+                            getMatchingFiles(artifactId).visit(new EmptyFileVisitor() {
+                                public void visitFile(FileVisitDetails fileDetails) {
+                                    files.add(fileDetails.getFile());
+                                }
+                            });
+                        }
+                        return files;
+                    }
+                };
+            }
+        };
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java
new file mode 100644
index 0000000..2545c86
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaData.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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.externalresource.metadata;
+
+import org.gradle.api.Nullable;
+import org.gradle.util.hash.HashValue;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class DefaultExternalResourceMetaData implements ExternalResourceMetaData, Serializable {
+
+    private final String location;
+    private final Date lastModified;
+    private final long contentLength;
+    private final String etag;
+    private final String sha1;
+
+    public DefaultExternalResourceMetaData(String location) {
+        this(location, -1, -1, null, null);
+    }
+
+    public DefaultExternalResourceMetaData(String location, long lastModified, long contentLength, @Nullable String etag, @Nullable HashValue sha1) {
+        this(location, lastModified > 0 ? new Date(lastModified) : null, contentLength, etag, sha1);
+    }
+    
+    public DefaultExternalResourceMetaData(String location, @Nullable Date lastModified, long contentLength, @Nullable String etag, @Nullable HashValue sha1) {
+        this.location = location;
+        this.lastModified = lastModified;
+        this.contentLength = contentLength;
+        this.etag = etag;
+        this.sha1 = sha1 == null ? null : sha1.asHexString();
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    @Nullable
+    public Date getLastModified() {
+        return lastModified;
+    }
+
+    public long getContentLength() {
+        return contentLength;
+    }
+
+    @Nullable
+    public String getEtag() {
+        return etag;
+    }
+
+    public HashValue getSha1() {
+        return sha1 == null ? null : HashValue.parse(sha1);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java
new file mode 100644
index 0000000..c8386d5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaData.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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.externalresource.metadata;
+
+import org.gradle.api.Nullable;
+import org.gradle.util.hash.HashValue;
+
+import java.util.Date;
+
+public interface ExternalResourceMetaData {
+
+    String getLocation();
+
+    @Nullable
+    Date getLastModified();
+
+    long getContentLength();
+
+    /**
+     * Some kind of opaque checksum that was advertised by the remote “server”.
+     * 
+     * For HTTP this is likely the value of the ETag header but it may be any kind of opaque checksum.
+     * 
+     * @return The entity tag, or null if there was no advertised or suitable etag.
+     */
+    @Nullable
+    String getEtag();
+
+    /**
+     * The advertised sha-1 of the external resource.
+     *
+     * This should only be collected if it is very cheap to do so. For example, some HTTP servers send an
+     * “X-Checksum-Sha1” that makes the sha1 available cheaply. In this case it makes sense to advertise this as metadata here.
+     *
+     * @return The sha1, or null if it's unknown.
+     */
+    @Nullable
+    HashValue getSha1();
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java
new file mode 100644
index 0000000..b9e3f7a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 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.externalresource.metadata;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+public abstract class ExternalResourceMetaDataCompare {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceMetaDataCompare.class);
+
+    public static boolean isDefinitelyUnchanged(@Nullable ExternalResourceMetaData local, Factory<ExternalResourceMetaData> remoteFactory) {
+        if (local == null) {
+            return false;
+        }
+
+        String localEtag = local.getEtag();
+
+        Date localLastModified = local.getLastModified();
+        if (localEtag == null && localLastModified == null) {
+            return false;
+        }
+
+        long localContentLength = local.getContentLength();
+        if (localEtag == null && localContentLength < 1) {
+            return false;
+        }
+
+        // We have enough local data to make a comparison, get the remote metadata
+        ExternalResourceMetaData remote = remoteFactory.create();
+        if (remote == null) {
+            return false;
+        }
+
+        String remoteEtag = remote.getEtag();
+        if (localEtag != null && remoteEtag != null) {
+            return localEtag.equals(remoteEtag);
+        }
+
+        Date remoteLastModified = remote.getLastModified();
+        if (remoteLastModified == null) {
+            return false;
+        }
+
+        long remoteContentLength = remote.getContentLength();
+        //noinspection SimplifiableIfStatement
+        if (remoteContentLength < 1) {
+            return false;
+        }
+
+        return localContentLength == remoteContentLength && remoteLastModified.equals(localLastModified);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/CacheAwareExternalResourceAccessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/CacheAwareExternalResourceAccessor.java
new file mode 100644
index 0000000..4e7047d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/CacheAwareExternalResourceAccessor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.externalresource.transfer;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+
+import java.io.IOException;
+
+public interface CacheAwareExternalResourceAccessor {
+
+    ExternalResource getResource(String source, @Nullable LocallyAvailableResourceCandidates localCandidates, @Nullable CachedExternalResource cached) throws IOException;
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/DefaultCacheAwareExternalResourceAccessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/DefaultCacheAwareExternalResourceAccessor.java
new file mode 100644
index 0000000..a3af7e2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/DefaultCacheAwareExternalResourceAccessor.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012 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.externalresource.transfer;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceAdapter;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResource;
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaDataCompare;
+import org.gradle.internal.Factory;
+import org.gradle.util.hash.HashValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class DefaultCacheAwareExternalResourceAccessor implements CacheAwareExternalResourceAccessor {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCacheAwareExternalResourceAccessor.class);
+
+    private final ExternalResourceAccessor delegate;
+
+    public DefaultCacheAwareExternalResourceAccessor(ExternalResourceAccessor delegate) {
+        this.delegate = delegate;
+    }
+
+    public ExternalResource getResource(final String location, @Nullable LocallyAvailableResourceCandidates localCandidates, @Nullable CachedExternalResource cached) throws IOException {
+        LOGGER.debug("Constructing external resource: {}", location);
+
+        // If we have no caching options, just get the thing directly
+        if (cached == null && (localCandidates == null || localCandidates.isNone())) {
+            return delegate.getResource(location);
+        }
+
+        // We might be able to use a cached/locally available version
+
+        // Get the metadata first to see if it's there
+        final ExternalResourceMetaData remoteMetaData = delegate.getMetaData(location);
+        if (remoteMetaData == null) {
+            return null;
+        }
+
+        // Is the cached version still current?
+        if (cached != null) {
+            boolean isUnchanged = ExternalResourceMetaDataCompare.isDefinitelyUnchanged(
+                    cached.getExternalResourceMetaData(),
+                    new Factory<ExternalResourceMetaData>() {
+                        public ExternalResourceMetaData create() {
+                            return remoteMetaData;
+                        }
+                    }
+            );
+
+            if (isUnchanged) {
+                LOGGER.info("Cached resource is up-to-date (lastModified: {}). [HTTP: {}]", cached.getExternalLastModified(), location);
+                // TODO - should we use the remote metadata? It may be “better”
+                return new CachedExternalResourceAdapter(location, cached, delegate, remoteMetaData);
+            }
+        }
+
+        // Either no cached, or it's changed. See if we can find something local with the same checksum
+        boolean hasLocalCandidates = localCandidates != null && !localCandidates.isNone();
+        if (hasLocalCandidates) {
+            // The “remote” may have already given us the checksum
+            HashValue remoteChecksum = remoteMetaData.getSha1();
+
+            if (remoteChecksum == null) {
+                remoteChecksum = delegate.getResourceSha1(location);
+            }
+
+            if (remoteChecksum != null) {
+                LocallyAvailableResource local = localCandidates.findByHashValue(remoteChecksum);
+                if (local != null) {
+                    LOGGER.info("Found locally available resource with matching checksum: [{}, {}]", location, local.getFile());
+                    return new LocallyAvailableExternalResource(location, local, remoteMetaData);
+                }
+            }
+        }
+
+        // All local/cached options failed, get directly
+        return delegate.getResource(location);
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java
new file mode 100644
index 0000000..2bc60f5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceAccessor.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 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.externalresource.transfer;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.util.hash.HashValue;
+
+import java.io.IOException;
+
+public interface ExternalResourceAccessor {
+
+    /**
+     * Obtain the resource at the given location.
+     *
+     * If the resource does not exist, this method should return null.
+     *
+     * If the resource may exist but can't be accessed due to some configuration issue, the implementation
+     * may either return null or throw an {@link IOException} to indicate a fatal condition.
+     *
+     * @param location The address of the resource to obtain
+     * @return The resource if it exists, otherwise null
+     * @throws IOException If the resource may exist, but not could be obtained for some reason
+     */
+    @Nullable
+    ExternalResource getResource(String location) throws IOException;
+
+    /**
+     * Obtain the SHA-1 checksum for the resource at the given location.
+     *
+     * Implementation is optional. If it is not feasible to obtain this without reading the
+     * entire resource, implementations should return null.
+     *
+     * @param location The address of the resource to obtain the sha-1 of
+     * @return The sha-1 if it can be cheaply obtained, otherwise null.
+     */
+    @Nullable
+    HashValue getResourceSha1(String location);
+
+    /**
+     * Obtains only the metadata about the resource.
+     *
+     * If it is determined that the resource does not exist, this method should return null.
+     *
+     * If it is not possible to determine whether the resource exists or not, this method should
+     * return a metadata instance with null/non value values (e.g. -1 for content length) to indicate
+     * that the resource may indeed exist, but the metadata for it cannot be obtained.
+     *
+     * If the resource may exist but can't be accessed due to some configuration issue, the implementation
+     * may either return an empty metadata object or throw an {@link IOException} to indicate a fatal condition.
+     *
+     * @param location The location of the resource to obtain the metadata for
+     * @return The available metadata if possible, an “empty” metadata object if the
+     *         metadata can't be reliably be obtained, null if the resource doesn't exist
+     * @throws IOException If the resource may exist, but not could be obtained for some reason
+     */
+    @Nullable
+    ExternalResourceMetaData getMetaData(String location) throws IOException;
+    
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceLister.java
new file mode 100644
index 0000000..7c177c5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceLister.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.externalresource.transfer;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface ExternalResourceLister {
+
+    public List<String> list(String parent) throws IOException;
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceUploader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceUploader.java
new file mode 100644
index 0000000..eecc59a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transfer/ExternalResourceUploader.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.externalresource.transfer;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface ExternalResourceUploader {
+    
+    void upload(File source, String destination, boolean overwrite) throws IOException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
new file mode 100644
index 0000000..9dcd893
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.externalresource.transport.file;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+import org.gradle.api.internal.artifacts.repositories.transport.file.FileExternalResourceRepository;
+
+import java.io.File;
+import java.net.URI;
+
+public class FileTransport implements RepositoryTransport {
+    private final String name;
+    private final RepositoryCacheManager repositoryCacheManager;
+
+    public FileTransport(String name, RepositoryCacheManager repositoryCacheManager) {
+        this.name = name;
+        this.repositoryCacheManager = repositoryCacheManager;
+    }
+
+    public ExternalResourceRepository getRepository() {
+        FileExternalResourceRepository fileRepository = new FileExternalResourceRepository();
+        fileRepository.setName(name);
+        return fileRepository;
+    }
+
+    public void configureCacheManager(AbstractResolver resolver) {
+        resolver.setRepositoryCacheManager(repositoryCacheManager);
+    }
+
+    public String convertToPath(URI uri) {
+        return normalisePath(new File(uri).getAbsolutePath());
+    }
+
+    private String normalisePath(String path) {
+        if (path.endsWith("/")) {
+            return path;
+        }
+        return path + "/";
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/DefaultHttpSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/DefaultHttpSettings.java
new file mode 100644
index 0000000..1ab3458
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/DefaultHttpSettings.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+
+public class DefaultHttpSettings implements HttpSettings {
+    private final PasswordCredentials passwordCredentials;
+    private final HttpProxySettings proxySettings = new JavaSystemPropertiesHttpProxySettings();
+
+    public DefaultHttpSettings(PasswordCredentials passwordCredentials) {
+        this.passwordCredentials = passwordCredentials;
+    }
+
+    public PasswordCredentials getCredentials() {
+        return passwordCredentials;
+    }
+
+    public HttpProxySettings getProxySettings() {
+        return proxySettings;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java
new file mode 100644
index 0000000..12d254a
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.NTCredentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
+import org.apache.http.protocol.HttpContext;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.externalresource.transport.http.ntlm.NTLMCredentials;
+import org.gradle.api.internal.externalresource.transport.http.ntlm.NTLMSchemeFactory;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.ProxySelector;
+
+public class HttpClientConfigurer {
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfigurer.class);
+
+    private final HttpSettings httpSettings;
+    private final UsernamePasswordCredentials repositoryCredentials;
+
+    public HttpClientConfigurer(HttpSettings httpSettings) {
+        this.httpSettings = httpSettings;
+        repositoryCredentials = createRepositoryCredentials(httpSettings.getCredentials());
+    }
+
+    private UsernamePasswordCredentials createRepositoryCredentials(PasswordCredentials credentials) {
+        if (GUtil.isTrue(credentials.getUsername())) {
+            return new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword());
+        }
+        return null;
+    }
+
+    public void configure(DefaultHttpClient httpClient) {
+        NTLMSchemeFactory.register(httpClient);
+        configureCredentials(httpClient, httpSettings.getCredentials());
+        configureProxy(httpClient, httpSettings.getProxySettings());
+        configureRetryHandler(httpClient);
+    }
+
+    private void configureCredentials(DefaultHttpClient httpClient, PasswordCredentials credentials) {
+        if (GUtil.isTrue(credentials.getUsername())) {
+            useCredentials(httpClient, credentials, AuthScope.ANY_HOST, AuthScope.ANY_PORT);
+        }
+    }
+
+    private void configureProxy(DefaultHttpClient httpClient, HttpProxySettings proxySettings) {
+        // Use standard JVM proxy settings
+        ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(httpClient.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault());
+        httpClient.setRoutePlanner(routePlanner);
+
+        HttpProxySettings.HttpProxy proxy = proxySettings.getProxy();
+        if (proxy != null && proxy.credentials != null) {
+            useCredentials(httpClient, proxy.credentials, proxy.host, proxy.port);
+        }
+    }
+
+    private void useCredentials(DefaultHttpClient httpClient, PasswordCredentials credentials, String host, int port) {
+        Credentials basicCredentials = new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword());
+        httpClient.getCredentialsProvider().setCredentials(new AuthScope(host, port), basicCredentials);
+
+        NTLMCredentials ntlmCredentials = new NTLMCredentials(credentials);
+        Credentials ntCredentials = new NTCredentials(ntlmCredentials.getUsername(), ntlmCredentials.getPassword(), ntlmCredentials.getWorkstation(), ntlmCredentials.getDomain());
+        httpClient.getCredentialsProvider().setCredentials(new AuthScope(host, port, AuthScope.ANY_REALM, AuthPolicy.NTLM), ntCredentials);
+        
+        LOGGER.debug("Using {} and {} for authenticating against '{}:{}'", new Object[]{credentials, ntlmCredentials, host, port});
+    }
+
+    private void configureRetryHandler(DefaultHttpClient httpClient) {
+        httpClient.setHttpRequestRetryHandler(new HttpRequestRetryHandler() {
+            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
+                return false;
+            }
+        });
+    }
+
+    public void configureMethod(HttpRequest method) {
+        method.addHeader("User-Agent", "Gradle/" + GradleVersion.current().getVersion());
+
+        // Do preemptive authentication for basic auth
+        if (repositoryCredentials != null) {
+            try {
+                method.addHeader(new BasicScheme().authenticate(repositoryCredentials, method));
+            } catch (AuthenticationException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
new file mode 100644
index 0000000..eb81212
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2012 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.externalresource.transport.http;
+
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.ContentEncodingHttpClient;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.util.EntityUtils;
+import org.gradle.api.UncheckedIOException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * Provides some convenience and unified logging.
+ */
+public class HttpClientHelper {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientHelper.class);
+    private final DefaultHttpClient client = new ContentEncodingHttpClient();
+    private final BasicHttpContext httpContext = new BasicHttpContext();
+
+    private final HttpClientConfigurer configurer;
+
+    public HttpClientHelper(HttpSettings settings) {
+        configurer = new HttpClientConfigurer(settings);
+        configurer.configure(client);
+    }
+
+    public HttpResponse performRawHead(String source) {
+        return performRequest(new HttpHead(source));        
+    }
+    
+    public HttpResponse performHead(String source) {
+        return performHead(source, false);
+    }
+    
+    public HttpResponse performHead(String source, boolean ignoreError) {
+        return processResponse(source, "HEAD", performRawHead(source), ignoreError);
+    }
+
+    public HttpResponse performRawGet(String source) {
+        return performRequest(new HttpGet(source));
+    }
+
+    public HttpResponse performGet(String source) {
+        return performGet(source, false);
+    }
+    
+    public HttpResponse performGet(String source, boolean ignoreError) {
+        return processResponse(source, "GET", performRawGet(source), ignoreError);
+    }
+
+    public HttpRequest configureRequest(HttpRequest request) {
+        configurer.configureMethod(request);
+        return request;
+    }
+
+    public HttpResponse performRequest(HttpRequestBase request) {
+        String method = request.getMethod();
+
+        HttpResponse response;
+        try {
+            response = executeGetOrHead(request);
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Could not %s '%s'.", method, request.getURI()), e);
+        }
+
+        return response;
+    }
+
+    private HttpResponse executeGetOrHead(HttpRequestBase method) throws IOException {
+        HttpResponse httpResponse = performHttpRequest(method);
+        // Consume content for non-successful, responses. This avoids the connection being left open.
+        if (!wasSuccessful(httpResponse)) {
+            EntityUtils.consume(httpResponse.getEntity());
+            return httpResponse;
+        }
+        return httpResponse;
+    }
+
+    public boolean wasMissing(HttpResponse response) {
+        int statusCode = response.getStatusLine().getStatusCode();
+        return statusCode == 404;
+    }
+
+    public boolean wasSuccessful(HttpResponse response) {
+        int statusCode = response.getStatusLine().getStatusCode();
+        return statusCode >= 200 && statusCode < 300;
+    }
+
+    public HttpResponse performHttpRequest(HttpRequestBase request) throws IOException {
+        configureRequest(request);
+
+        // Without this, HTTP Client prohibits multiple redirects to the same location within the same context
+        httpContext.removeAttribute(DefaultRedirectStrategy.REDIRECT_LOCATIONS);
+
+        LOGGER.debug("Performing HTTP {}: {}", request.getMethod(), request.getURI());
+        return client.execute(request, httpContext);
+    }
+
+    private HttpResponse processResponse(String source, String method, HttpResponse response, boolean ignoreError) {
+        if (wasMissing(response)) {
+            LOGGER.info("Resource missing. [HTTP {}: {}]", method, source);
+            return null;
+        }
+        if (!wasSuccessful(response)) {
+            LOGGER.info("Failed to get resource: {}. [HTTP {}: {}]", new Object[]{method, response.getStatusLine(), source});
+            throw new UncheckedIOException(String.format("Could not %s '%s'. Received status code %s from server: %s",
+                    method, source, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
+        }
+
+        return response;
+    }
+
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpProxySettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpProxySettings.java
new file mode 100644
index 0000000..25b6aaf
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpProxySettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials;
+
+public interface HttpProxySettings {
+    
+    HttpProxy getProxy();
+    
+    HttpProxy getProxy(String host);
+
+    public class HttpProxy {
+        public final String host;
+        public final int port;
+        public final PasswordCredentials credentials;
+
+        public HttpProxy(String host, int port, String username, String password) {
+            this.host = host;
+            this.port = port;
+            if (username == null || username.length() == 0) {
+                credentials = null;
+            } else {
+                credentials = new DefaultPasswordCredentials(username, password);
+            }
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceAccessor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceAccessor.java
new file mode 100644
index 0000000..79396cd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceAccessor.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012 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.externalresource.transport.http;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.util.EntityUtils;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor;
+import org.gradle.util.hash.HashValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HttpResourceAccessor implements ExternalResourceAccessor {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpResourceAccessor.class);
+    private final HttpClientHelper http;
+
+    private final List<ExternalResource> openResources = new ArrayList<ExternalResource>();
+
+    public HttpResourceAccessor(HttpClientHelper http) {
+        this.http = http;
+    }
+
+    public ExternalResource getResource(String location) throws IOException {
+        abortOpenResources();
+        LOGGER.debug("Constructing external resource: {}", location);
+        HttpResponse response = http.performGet(location);
+        if (response != null) {
+            ExternalResource resource = new HttpResponseResource("GET", location, response) {
+                @Override
+                public void close() throws IOException {
+                    super.close();
+                    HttpResourceAccessor.this.openResources.remove(this);
+                }
+            };
+
+            return recordOpenGetResource(resource);
+        } else {
+            return null;
+        }
+    }
+
+    public ExternalResourceMetaData getMetaData(String location) {
+        abortOpenResources();
+        LOGGER.debug("Constructing external resource metadata: {}", location);
+        HttpResponse response = http.performHead(location);
+        return response == null ? null : new HttpResponseResource("HEAD", location, response).getMetaData();
+    }
+
+    private ExternalResource recordOpenGetResource(ExternalResource httpResource) {
+        if (httpResource instanceof HttpResponseResource) {
+            openResources.add(httpResource);
+        }
+        return httpResource;
+    }
+
+    private void abortOpenResources() {
+        for (ExternalResource openResource : openResources) {
+            LOGGER.warn("Forcing close on abandoned resource: " + openResource);
+            try {
+                openResource.close();
+            } catch (IOException e) {
+                LOGGER.warn("Failed to close abandoned resource", e);
+            }
+        }
+        openResources.clear();
+    }
+
+    public HashValue getResourceSha1(String location) {
+        String checksumUrl = location + ".sha1";
+        return downloadSha1(checksumUrl);
+    }
+
+    private HashValue downloadSha1(String checksumUrl) {
+        try {
+            HttpResponse httpResponse = http.performRawGet(checksumUrl);
+            if (http.wasSuccessful(httpResponse)) {
+                String checksumValue = EntityUtils.toString(httpResponse.getEntity());
+                return HashValue.parse(checksumValue);
+            }
+            if (!http.wasMissing(httpResponse)) {
+                LOGGER.info("Request for checksum at {} failed: {}", checksumUrl, httpResponse.getStatusLine());
+            }
+            return null;
+        } catch (Exception e) {
+            LOGGER.warn("Checksum missing at {} due to: {}", checksumUrl, e.getMessage());
+            return null;
+        }
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java
new file mode 100644
index 0000000..24f590f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.externalresource.transport.http;
+
+import org.apache.ivy.util.url.ApacheURLLister;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HttpResourceLister implements ExternalResourceLister {
+
+    public List<String> list(String parent) throws IOException {
+        // Parse standard directory listing pages served up by Apache
+        ApacheURLLister urlLister = new ApacheURLLister();
+        List<URL> urls = urlLister.listAll(new URL(parent));
+        if (urls != null) {
+            List<String> ret = new ArrayList<String>(urls.size());
+            for (URL url : urls) {
+                ret.add(url.toExternalForm());
+            }
+            return ret;
+        }
+        return null;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceUploader.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceUploader.java
new file mode 100644
index 0000000..0fd3f92
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceUploader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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.externalresource.transport.http;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.FileEntity;
+import org.apache.http.util.EntityUtils;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceUploader;
+
+import java.io.File;
+import java.io.IOException;
+
+public class HttpResourceUploader implements ExternalResourceUploader {
+
+    private final HttpClientHelper http;
+
+    public HttpResourceUploader(HttpClientHelper http) {
+        this.http = http;
+    }
+
+    public void upload(File source, String destination, boolean overwrite) throws IOException {
+        HttpPut method = new HttpPut(destination);
+        method.setEntity(new FileEntity(source, "application/octet-stream"));
+        HttpResponse response = http.performHttpRequest(method);
+        EntityUtils.consume(response.getEntity());
+        if (!http.wasSuccessful(response)) {
+            throw new IOException(String.format("Could not PUT '%s'. Received status code %s from server: %s",
+                    destination, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()));
+        }
+
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
new file mode 100644
index 0000000..0489198
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.util.EntityUtils;
+import org.gradle.api.internal.externalresource.AbstractExternalResource;
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.util.hash.HashValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+class HttpResponseResource extends AbstractExternalResource {
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpResponseResource.class);
+
+    private final String method;
+    private final String source;
+    private final HttpResponse response;
+    private final ExternalResourceMetaData metaData;
+
+    public HttpResponseResource(String method, String source, HttpResponse response) {
+        this.method = method;
+        this.source = source;
+        this.response = response;
+
+        String etag = getEtag(response);
+        this.metaData = new DefaultExternalResourceMetaData(source, getLastModified(), getContentLength(), etag, getSha1(response, etag));
+    }
+
+    public String getName() {
+        return source;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Http %s Resource: %s", method, source);
+    }
+
+    public ExternalResourceMetaData getMetaData() {
+        return metaData;
+    }
+
+    public long getLastModified() {
+        Header responseHeader = response.getFirstHeader("last-modified");
+        if (responseHeader == null) {
+            return 0;
+        }
+        try {
+            return DateUtils.parseDate(responseHeader.getValue()).getTime();
+        } catch (Exception e) {
+            return 0;
+        }
+    }
+
+    public long getContentLength() {
+        Header header = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
+        if (header == null) {
+            return -1;            
+        }
+
+        String value = header.getValue();
+        if (value == null) {
+            return -1;
+        }
+
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    public boolean exists() {
+        return true;
+    }
+
+    public boolean isLocal() {
+        return false;
+    }
+
+    public InputStream openStream() throws IOException {
+        LOGGER.debug("Attempting to download resource {}.", source);
+        return response.getEntity().getContent();
+    }
+
+    @Override
+    public void close() throws IOException {
+        EntityUtils.consume(response.getEntity());
+    }
+
+    private static String getEtag(HttpResponse response) {
+        Header etagHeader = response.getFirstHeader(HttpHeaders.ETAG);
+        return etagHeader == null ? null : etagHeader.getValue();
+    }
+    
+    private static HashValue getSha1(HttpResponse response, String etag) {
+        Header sha1Header = response.getFirstHeader("X-Checksum-Sha1");
+        if (sha1Header != null) {
+            return new HashValue(sha1Header.getValue());    
+        }
+
+        // Nexus uses sha1 etags, with a constant prefix
+        // e.g {SHA1{b8ad5573a5e9eba7d48ed77a48ad098e3ec2590b}}
+        if (etag != null && etag.startsWith("{SHA1{")) {
+            String hash = etag.substring(6, etag.length() - 2);
+            return new HashValue(hash);
+        }
+
+        return null;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpSettings.java
new file mode 100644
index 0000000..a51dbdf
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpSettings.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+
+public interface HttpSettings {
+    PasswordCredentials getCredentials();
+
+    HttpProxySettings getProxySettings();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
new file mode 100644
index 0000000..432a430
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.repositories.DefaultExternalResourceRepository;
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
+
+import java.net.URI;
+
+public class HttpTransport implements RepositoryTransport {
+    private final String name;
+    private final PasswordCredentials credentials;
+    private final RepositoryCacheManager repositoryCacheManager;
+
+    public HttpTransport(String name, PasswordCredentials credentials, RepositoryCacheManager repositoryCacheManager) {
+        this.name = name;
+        this.credentials = credentials;
+        this.repositoryCacheManager = repositoryCacheManager;
+    }
+
+    public ExternalResourceRepository getRepository() {
+        HttpClientHelper http = new HttpClientHelper(new DefaultHttpSettings(credentials));
+        return new DefaultExternalResourceRepository(
+                name, new HttpResourceAccessor(http), new HttpResourceUploader(http), new HttpResourceLister()
+        );
+    }
+
+    public void configureCacheManager(AbstractResolver resolver) {
+        resolver.setRepositoryCacheManager(repositoryCacheManager);
+    }
+
+    public String convertToPath(URI uri) {
+        return normalisePath(uri.toString());
+    }
+
+    private String normalisePath(String path) {
+        if (path.endsWith("/")) {
+            return path;
+        }
+        return path + "/";
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java
new file mode 100644
index 0000000..5387e22
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettings.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class JavaSystemPropertiesHttpProxySettings implements HttpProxySettings {
+    private static final Logger LOGGER = LoggerFactory.getLogger(JavaSystemPropertiesHttpProxySettings.class);
+    private static final int DEFAULT_PROXY_PORT = 80;
+
+    private final HttpProxy proxy;
+    private final List<Pattern> nonProxyHosts;
+
+    public JavaSystemPropertiesHttpProxySettings() {
+        this(System.getProperty("http.proxyHost"), System.getProperty("http.proxyPort"), 
+                System.getProperty("http.proxyUser"), System.getProperty("http.proxyPassword"), 
+                System.getProperty("http.nonProxyHosts"));
+    }
+
+    JavaSystemPropertiesHttpProxySettings(String proxyHost, String proxyPortString, String proxyUser, String proxyPassword, String nonProxyHostsString) {
+        if (proxyHost == null) {
+            this.proxy = null;
+        } else {
+            this.proxy = new HttpProxy(proxyHost, initProxyPort(proxyPortString), proxyUser, proxyPassword);
+        }
+        this.nonProxyHosts = initNonProxyHosts(nonProxyHostsString);
+    }
+
+    private int initProxyPort(String proxyPortString) {
+        if (proxyPortString == null) {
+            return DEFAULT_PROXY_PORT;
+        }
+        
+        try {
+            return Integer.parseInt(proxyPortString);
+        } catch (NumberFormatException e) {
+            LOGGER.warn("Invalid value for java system property 'http.proxyPort': {}. Default port '{}' will be used.", System.getProperty("http.proxyPort"), DEFAULT_PROXY_PORT);
+            return DEFAULT_PROXY_PORT;
+        }
+    }
+
+    private List<Pattern> initNonProxyHosts(String nonProxyHostsString) {
+        if (nonProxyHostsString == null) {
+            return Collections.emptyList();
+        }
+
+        LOGGER.debug("Found java system property 'http.nonProxyHosts': {}. Will ignore proxy settings for these hosts.", nonProxyHostsString);
+        List<Pattern> patterns = new ArrayList<Pattern>();
+        for (String nonProxyHost : nonProxyHostsString.split("\\|")) {
+            patterns.add(createHostMatcher(nonProxyHost));
+        }
+        return patterns;
+    }
+
+    private Pattern createHostMatcher(String nonProxyHost) {
+        if (nonProxyHost.startsWith("*")) {
+            return Pattern.compile(".*" + Pattern.quote(nonProxyHost.substring(1)));
+        }
+        if (nonProxyHost.endsWith("*")) {
+            return Pattern.compile(Pattern.quote(nonProxyHost.substring(0, nonProxyHost.length() - 1)) + ".*");
+        }
+        return Pattern.compile(Pattern.quote(nonProxyHost));
+    }
+
+    public HttpProxy getProxy() {
+        return proxy;
+    }
+
+    public HttpProxy getProxy(String host) {
+        if (proxy == null || isNonProxyHost(host)) {
+            return null;
+        }
+        return proxy;
+    }
+
+    private boolean isNonProxyHost(String host) {        
+        for (Pattern nonProxyHost : nonProxyHosts) {
+            if (nonProxyHost.matcher(host).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentials.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentials.java
new file mode 100644
index 0000000..7adaacc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentials.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http.ntlm;
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class NTLMCredentials {
+    private static final String DEFAULT_DOMAIN = "";
+    private static final String DEFAULT_WORKSTATION = "";
+    private final String domain;
+    private final String username;
+    private final String password;
+    private final String workstation;
+
+    public NTLMCredentials(PasswordCredentials credentials) {
+        String domain;
+        String username = credentials.getUsername();
+        int slashPos = username.indexOf('\\');
+        slashPos = slashPos >= 0 ? slashPos : username.indexOf('/');
+        if (slashPos >= 0) {
+            domain = username.substring(0, slashPos);
+            username = username.substring(slashPos + 1);
+        } else {
+            domain = System.getProperty("http.auth.ntlm.domain", DEFAULT_DOMAIN);
+        }
+        this.domain = domain == null ? null : domain.toUpperCase();
+        this.username = username;
+        this.password = credentials.getPassword();
+        this.workstation = determineWorkstationName();
+    }
+
+    private String determineWorkstationName() {
+        // TODO:DAZ This is a temporary (hidden) property that may be useful to track down issues. Remove when NTLM Auth is solid.
+        String sysPropWorkstation = System.getProperty("http.auth.ntlm.workstation");
+        if (sysPropWorkstation != null) {
+            return sysPropWorkstation;
+        }
+
+        try {
+            return removeDotSuffix(getHostName()).toUpperCase();
+        } catch (UnknownHostException e) {
+            return DEFAULT_WORKSTATION;
+        }
+    }
+
+    protected String getHostName() throws UnknownHostException {
+        return InetAddress.getLocalHost().getHostName();
+    }
+
+    private String removeDotSuffix(String val) {
+        int dotPos = val.indexOf('.');
+        return dotPos == -1 ? val : val.substring(0, dotPos);
+    }
+
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public String getWorkstation() {
+        return workstation;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("NTLM Credentials [user: %s, domain: %s, workstation: %s]", username, domain, workstation);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMSchemeFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMSchemeFactory.java
new file mode 100644
index 0000000..29f6739
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMSchemeFactory.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http.ntlm;
+
+import jcifs.ntlmssp.Type1Message;
+import jcifs.ntlmssp.Type2Message;
+import jcifs.ntlmssp.Type3Message;
+import jcifs.util.Base64;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeFactory;
+import org.apache.http.impl.auth.NTLMEngine;
+import org.apache.http.impl.auth.NTLMEngineException;
+import org.apache.http.impl.auth.NTLMScheme;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.HttpParams;
+
+import java.io.IOException;
+
+// Copied from http://hc.apache.org/httpcomponents-client-ga/ntlm.html
+public class NTLMSchemeFactory implements AuthSchemeFactory {
+
+    public static void register(DefaultHttpClient httpClient) {
+        httpClient.getAuthSchemes().register("ntlm", new NTLMSchemeFactory());
+    }
+
+    public AuthScheme newInstance(HttpParams params) {
+        return new NTLMScheme(new JCIFSEngine());
+    }
+
+    private static class JCIFSEngine implements NTLMEngine {
+
+        public String generateType1Msg(String domain, String workstation) throws NTLMEngineException {
+            Type1Message type1Message = new Type1Message(Type1Message.getDefaultFlags(), domain, workstation);
+            return Base64.encode(type1Message.toByteArray());
+        }
+
+        public String generateType3Msg(String username, String password, String domain, String workstation, String challenge) throws NTLMEngineException {
+            Type2Message type2Message = decodeType2Message(challenge);
+            Type3Message type3Message = new Type3Message(type2Message, password, domain, username, workstation, Type3Message.getDefaultFlags());
+            return Base64.encode(type3Message.toByteArray());
+        }
+
+        private Type2Message decodeType2Message(String challenge) throws NTLMEngineException {
+            try {
+                return new Type2Message(Base64.decode(challenge));
+            } catch (final IOException exception) {
+                throw new NTLMEngineException("Invalid Type2 message", exception);
+            }
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/DefaultFileStoreEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/DefaultFileStoreEntry.java
new file mode 100644
index 0000000..883dc56
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/DefaultFileStoreEntry.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.filestore;
+
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+
+public class DefaultFileStoreEntry implements FileStoreEntry {
+
+    private File file;
+
+    public DefaultFileStoreEntry(File file) {
+        this.file = file;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public HashValue getSha1() {
+        return HashUtil.createHash(file, "SHA1");
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java
new file mode 100644
index 0000000..e7e8771
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStore.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.filestore;
+
+import java.io.File;
+
+public interface FileStore<K> {
+    File add(K key, File contentFile);
+
+    File getTempFile();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
new file mode 100644
index 0000000..76298a5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreEntry.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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.filestore;
+
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+
+public interface FileStoreEntry {
+
+    File getFile();
+
+    HashValue getSha1();
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java
new file mode 100644
index 0000000..b23bdfd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/FileStoreSearcher.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.filestore;
+
+import java.util.Set;
+
+public interface FileStoreSearcher<S> {
+
+    Set<? extends FileStoreEntry> search(S key);
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java
new file mode 100644
index 0000000..e67c2e9
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/GroupedAndNamedUniqueFileStore.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.filestore;
+
+import org.gradle.api.Transformer;
+import org.gradle.util.hash.HashUtil;
+
+import java.io.File;
+import java.util.Set;
+
+public class GroupedAndNamedUniqueFileStore<K> implements FileStore<K>, FileStoreSearcher<K> {
+
+    private UniquePathFileStore delegate;
+    private final Transformer<String, K> grouper;
+    private final Transformer<String, K> namer;
+
+    public GroupedAndNamedUniqueFileStore(UniquePathFileStore delegate, Transformer<String, K> grouper, Transformer<String, K> namer) {
+        this.delegate = delegate;
+        this.grouper = grouper;
+        this.namer = namer;
+    }
+
+    public File add(K key, File contentFile) {
+        return delegate.add(toPath(key, getChecksum(contentFile)), contentFile);
+    }
+
+    public Set<? extends FileStoreEntry> search(K key) {
+        return delegate.search(toPath(key, "*"));
+    }
+
+    protected String toPath(K key, String checksumPart) {
+        String group = grouper.transform(key);
+        String name = namer.transform(key);
+
+        return String.format("%s/%s/%s", group, checksumPart, name);
+    }
+    
+    private String getChecksum(File contentFile) {
+        return HashUtil.createHash(contentFile, "SHA1").asHexString();
+    }
+
+    public File getTempFile() {
+        return delegate.getTempFile();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java
new file mode 100644
index 0000000..13ea982
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 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.filestore;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.EmptyFileVisitor;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * File store that stores items under a given path within a base directory.
+ *
+ * Paths are expected to be unique. If a request is given to store a file at a particular path
+ * where a file exists already then it will not be copied. That is, it is expected to be equal.
+ *
+ * This file store also provides searching via relative ant path patterns.
+ */
+public class UniquePathFileStore implements FileStore<String>, FileStoreSearcher<String> {
+
+    private final Random generator = new Random(System.currentTimeMillis());
+
+    private final File baseDir;
+
+    public UniquePathFileStore(File baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    public File getBaseDir() {
+        return baseDir;
+    }
+
+    public File add(String path, File contentFile) {
+        File destination = getFile(path);
+        if (!destination.exists()) {
+            saveIntoFileStore(contentFile, destination);
+        }
+        return destination;
+    }
+
+    private File getFile(String path) {
+        return new File(baseDir, path);
+    }
+
+    public File getTempFile() {
+        long tempLong = generator.nextLong();
+        tempLong = tempLong < 0 ? -tempLong : tempLong;
+        return new File(baseDir, "temp/" + tempLong);
+    }
+
+    private void saveIntoFileStore(File contentFile, File storageFile) {
+        File parentDir = storageFile.getParentFile();
+        if (!parentDir.mkdirs() && !parentDir.exists()) {
+            throw new GradleException(String.format("Unabled to create filestore directory %s", parentDir));
+        }
+        if (!contentFile.renameTo(storageFile)) {
+            throw new GradleException(String.format("Failed to copy downloaded content into storage file: %s", storageFile));
+        }
+    }
+
+    public Set<? extends FileStoreEntry> search(String pattern) {
+        final Set<DefaultFileStoreEntry> entries = new HashSet<DefaultFileStoreEntry>();
+        findFiles(pattern).visit(new EmptyFileVisitor() {
+            public void visitFile(FileVisitDetails fileDetails) {
+                entries.add(new DefaultFileStoreEntry(fileDetails.getFile()));
+            }
+        });
+
+        return entries;
+    }
+    
+    private DirectoryFileTree findFiles(String pattern) {
+        DirectoryFileTree fileTree = new DirectoryFileTree(baseDir);
+        PatternFilterable patternSet = new PatternSet();
+        patternSet.include(pattern);
+        return fileTree.filter(patternSet);
+    } 
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactRevisionIdFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactRevisionIdFileStore.java
new file mode 100644
index 0000000..aee4dc1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/ivy/ArtifactRevisionIdFileStore.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.filestore.ivy;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.filestore.GroupedAndNamedUniqueFileStore;
+import org.gradle.api.internal.filestore.UniquePathFileStore;
+
+public class ArtifactRevisionIdFileStore extends GroupedAndNamedUniqueFileStore<ArtifactRevisionId> {
+
+    private static final String GROUP_PATTERN = "[organisation]/[module](/[branch])/[revision]/[type]";
+    private static final String NAME_PATTERN = "[artifact]-[revision](-[classifier])(.[ext])";
+
+    public ArtifactRevisionIdFileStore(UniquePathFileStore delegate) {
+        super(delegate, toTransformer(GROUP_PATTERN), toTransformer(NAME_PATTERN));
+    }
+
+    private static Transformer<String, ArtifactRevisionId> toTransformer(final String pattern) {
+        return new Transformer<String, ArtifactRevisionId>() {
+            public String transform(ArtifactRevisionId id) {
+                String substitute = pattern;
+                substitute = IvyPatternHelper.substituteToken(substitute, "organisation", id.getModuleRevisionId().getOrganisation().replace('/', '.'));
+                return IvyPatternHelper.substitute(substitute, new DefaultArtifact(id, null, null, false));
+            }
+        };
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
new file mode 100644
index 0000000..37e8476
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
@@ -0,0 +1,48 @@
+/*
+ * 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.notations;
+
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.TopLevelNotationParser;
+
+import java.util.Collection;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleNotationParser implements TopLevelNotationParser, NotationParser<ClientModule> {
+
+    private final NotationParser<ClientModule> delegate;
+
+    public ClientModuleNotationParser(Instantiator instantiator) {
+        delegate = new NotationParserBuilder<ClientModule>()
+                .resultingType(ClientModule.class)
+                .parser(new DependencyStringNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
+                .parser(new DependencyMapNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
+                .toComposite();
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    public ClientModule parseNotation(Object notation) {
+        return delegate.parseNotation(notation);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
new file mode 100755
index 0000000..e5af72b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.notations;
+
+import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.io.File;
+import java.util.Collection;
+
+public class DependencyClassPathNotationParser
+        extends TypedNotationParser<DependencyFactory.ClassPathNotation, SelfResolvingDependency>
+        implements NotationParser<SelfResolvingDependency> {
+
+    private final ClassPathRegistry classPathRegistry;
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+
+    public DependencyClassPathNotationParser(Instantiator instantiator, ClassPathRegistry classPathRegistry,
+                                             FileResolver fileResolver) {
+        super(DependencyFactory.ClassPathNotation.class);
+
+        this.instantiator = instantiator;
+        this.classPathRegistry = classPathRegistry;
+        this.fileResolver = fileResolver;
+    }
+
+    @Override
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("ClassPathNotation, e.g. gradleApi().");
+    }
+
+    public SelfResolvingDependency parseType(DependencyFactory.ClassPathNotation notation) {
+        Collection<File> classpath = classPathRegistry.getClassPath(notation.name()).getAsFiles();
+        FileCollection files = fileResolver.resolveFiles(classpath);
+        return instantiator.newInstance(DefaultSelfResolvingDependency.class, files);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
new file mode 100644
index 0000000..c8c5cf3
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
@@ -0,0 +1,46 @@
+/*
+ * 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.notations;
+
+import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.util.Collection;
+
+public class DependencyFilesNotationParser
+        extends TypedNotationParser<FileCollection, SelfResolvingDependency>
+        implements NotationParser<SelfResolvingDependency> {
+
+    private final Instantiator instantiator;
+
+    public DependencyFilesNotationParser(Instantiator instantiator) {
+        super(FileCollection.class);
+        this.instantiator = instantiator;
+    }
+
+    @Override
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("FileCollections, e.g. files('some.jar', 'someOther.jar').");
+    }
+
+    public SelfResolvingDependency parseType(FileCollection notation) {
+        return instantiator.newInstance(DefaultSelfResolvingDependency.class, notation);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
new file mode 100644
index 0000000..56898db
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
@@ -0,0 +1,56 @@
+/*
+ * 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.notations;
+
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.tasks.Optional;
+
+import java.util.Collection;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencyMapNotationParser<T extends ExternalDependency> extends MapNotationParser<T> {
+
+    private final Instantiator instantiator;
+    private final Class<T> resultingType;
+
+    public DependencyMapNotationParser(Instantiator instantiator, Class<T> resultingType) {
+        this.instantiator = instantiator;
+        this.resultingType = resultingType;
+    }
+
+    @Override
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Maps, e.g. [group: 'org.gradle', name: 'gradle-core', version: '1.0'].");
+    }
+
+    protected T parseMap(@MapKey("group") @Optional String group,
+                         @MapKey("name") @Optional String name,
+                         @MapKey("version") @Optional String version,
+                         @MapKey("configuration") @Optional String configuration,
+                         @MapKey("ext") @Optional String ext,
+                         @MapKey("classifier") @Optional String classifier) {
+        T dependency = instantiator.newInstance(resultingType, group, name, version, configuration);
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency, ext, classifier);
+        return dependency;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
new file mode 100644
index 0000000..591d8f8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.notations;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.TopLevelNotationParser;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 11/8/11
+ */
+public class DependencyNotationParser implements TopLevelNotationParser, NotationParser<Dependency> {
+
+    private final NotationParser<Dependency> delegate;
+
+    public DependencyNotationParser(Iterable<NotationParser<? extends Dependency>> compositeParsers) {
+        delegate = new NotationParserBuilder<Dependency>()
+                .resultingType(Dependency.class)
+                .parsers(compositeParsers)
+                .invalidNotationMessage("Comprehensive documentation on dependency notations is available in DSL reference for DependencyHandler type.")
+                .toComposite();
+    }
+
+    DependencyNotationParser(NotationParser<Dependency> delegate) {
+        this.delegate = delegate;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    public Dependency parseNotation(Object dependencyNotation) {
+        try {
+            return delegate.parseNotation(dependencyNotation);
+        } catch (GradleException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not create a dependency using notation: %s", dependencyNotation), e);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
new file mode 100644
index 0000000..4c55545
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.notations;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 11/10/11
+ */
+public class DependencyProjectNotationParser extends TypedNotationParser<Project, ProjectDependency> {
+
+    private final ProjectDependenciesBuildInstruction instruction;
+    private final Instantiator instantiator;
+
+    public DependencyProjectNotationParser(ProjectDependenciesBuildInstruction instruction, Instantiator instantiator) {
+        super(Project.class);
+        this.instruction = instruction;
+        this.instantiator = instantiator;
+    }
+
+    @Override
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Projects, e.g. project(':some:project:path').");
+    }
+
+    public ProjectDependency parseType(Project notation) {
+        return instantiator.newInstance(DefaultProjectDependency.class, notation, instruction);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
new file mode 100644
index 0000000..3d601f4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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.notations;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ParsedModuleStringNotation;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * by Szczepan Faber, created at: 11/10/11
+ */
+public class DependencyStringNotationParser<T extends ExternalDependency> extends TypedNotationParser<CharSequence, T> {
+
+    private final Instantiator instantiator;
+    private final Class<T> wantedType;
+
+    public DependencyStringNotationParser(Instantiator instantiator, Class<T> wantedType) {
+        super(CharSequence.class);
+        this.instantiator = instantiator;
+        this.wantedType = wantedType;
+    }
+
+    @Override
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Strings/CharSequences, e.g. 'org.gradle:gradle-core:1.0'.");
+    }
+
+    protected T parseType(CharSequence notation) {
+        return createDependencyFromString(notation.toString());
+    }
+
+    public static final Pattern EXTENSION_SPLITTER = Pattern.compile("^(.+)\\@([^:]+$)");
+
+    private T createDependencyFromString(String notation) {
+
+        ParsedModuleStringNotation parsedNotation = splitModuleFromExtension(notation);
+        T moduleDependency = instantiator.newInstance(wantedType,
+                parsedNotation.getGroup(), parsedNotation.getName(), parsedNotation.getVersion());
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(moduleDependency, parsedNotation.getArtifactType(), parsedNotation.getClassifier());
+
+        return moduleDependency;
+    }
+
+    private ParsedModuleStringNotation splitModuleFromExtension(String notation) {
+        Matcher matcher = EXTENSION_SPLITTER.matcher(notation);
+        boolean hasArtifactType = matcher.matches();
+        if (hasArtifactType && !ClientModule.class.isAssignableFrom(wantedType)) {
+            if (matcher.groupCount() != 2) {
+                throw new InvalidUserDataException("The dependency notation " + notation + " is invalid");
+            }
+            return new ParsedModuleStringNotation(matcher.group(1), matcher.group(2));
+        }
+        return new ParsedModuleStringNotation(notation, null);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
new file mode 100644
index 0000000..45d01d5
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.notations;
+
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencyFactory {
+    private final ProjectDependenciesBuildInstruction instruction;
+    private final Instantiator instantiator;
+
+    public ProjectDependencyFactory(ProjectDependenciesBuildInstruction instruction, Instantiator instantiator) {
+        this.instruction = instruction;
+        this.instantiator = instantiator;
+    }
+
+    public ProjectDependency createFromMap(ProjectFinder projectFinder,
+                                           Map<? extends String, ? extends Object> map) {
+        Map<String, Object> args = new HashMap<String, Object>(map);
+        String path = getAndRemove(args, "path");
+        String configuration = getAndRemove(args, "configuration");
+        ProjectDependency dependency = instantiator.newInstance(DefaultProjectDependency.class, projectFinder.getProject(path), configuration, instruction);
+        ConfigureUtil.configureByMap(args, dependency);
+        return dependency;
+    }
+
+    private String getAndRemove(Map<String, Object> args, String key) {
+        Object value = args.get(key);
+        args.remove(key);
+        return value != null ? value.toString() : null;
+    }
+}
diff --git a/subprojects/core-impl/src/main/resources/META-INF/services/org.gradle.api.internal.artifacts.DependencyManagementServices b/subprojects/core-impl/src/main/resources/META-INF/services/org.gradle.api.internal.artifacts.DependencyManagementServices
new file mode 100644
index 0000000..5da1fb1
--- /dev/null
+++ b/subprojects/core-impl/src/main/resources/META-INF/services/org.gradle.api.internal.artifacts.DependencyManagementServices
@@ -0,0 +1 @@
+org.gradle.api.internal.artifacts.DefaultDependencyManagementServices
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java
new file mode 100644
index 0000000..6a19a5e
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.file.FileSource;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+
+import java.io.File;
+import java.util.Collections;
+
+public class ArtifactsTestUtils {
+    
+    public static DefaultResolvedArtifact createResolvedArtifact(final Mockery context, final String name, final String type, final String extension, final File file) {
+        final Artifact artifactStub = context.mock(Artifact.class, "artifact" + name);
+        context.checking(new Expectations() {{
+            allowing(artifactStub).getName();
+            will(returnValue(name));
+            allowing(artifactStub).getType();
+            will(returnValue(type));
+            allowing(artifactStub).getExt();
+            will(returnValue(extension));
+            allowing(artifactStub).getExtraAttributes();
+            will(returnValue(Collections.emptyMap()));
+            allowing(artifactStub).getExtraAttribute(with(org.hamcrest.Matchers.notNullValue(String.class)));
+            will(returnValue(null));
+        }});
+        final FileSource artifactSource = context.mock(FileSource.class);
+        context.checking(new Expectations() {{
+            allowing(artifactSource).get();
+            will(returnValue(file));
+        }});
+        final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
+        final ResolvedModuleVersion version = context.mock(ResolvedModuleVersion.class);
+        context.checking(new Expectations() {{
+            allowing(resolvedDependency).getModule();
+            will(returnValue(version));
+            allowing(version).getId();
+            will(returnValue(new DefaultModuleVersionIdentifier("group", name, "1.2")));
+        }});
+        return new DefaultResolvedArtifact(resolvedDependency, artifactStub, artifactSource);
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
new file mode 100644
index 0000000..ed0698a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.api.internal.DomainObjectContext
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
+import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.FileLockManager
+import org.gradle.internal.Factory
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.listener.ListenerManager
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.util.TimeProvider
+import spock.lang.Specification
+
+class DefaultDependencyManagementServicesTest extends Specification {
+    final ServiceRegistry parent = Mock()
+    final FileResolver fileResolver = Mock()
+    final DependencyMetaDataProvider dependencyMetaDataProvider = Mock()
+    final ProjectFinder projectFinder = Mock()
+    final Instantiator instantiator = Mock()
+    final DomainObjectContext domainObjectContext = Mock()
+    final DefaultRepositoryHandler repositoryHandler = Mock()
+    final ConfigurationContainerInternal configurationContainer = Mock()
+    final StartParameter startParameter = Mock()
+    final ListenerManager listenerManager = Mock()
+    final DefaultDependencyManagementServices services = new DefaultDependencyManagementServices(parent)
+
+    def setup() {
+        Factory<LoggingManagerInternal> loggingFactory = Mock()
+        _ * parent.getFactory(LoggingManagerInternal) >> loggingFactory
+        ProgressLoggerFactory progressLoggerFactory = Mock()
+        _ * parent.get(ProgressLoggerFactory) >> progressLoggerFactory
+        CacheRepository cacheRepository = initCacheRepository()
+        _ * parent.get(CacheRepository) >> cacheRepository
+        ClassPathRegistry classPathRegistry = Mock()
+        _ * parent.get(ClassPathRegistry) >> classPathRegistry
+        _ * parent.get(ListenerManager) >> listenerManager
+        _ * parent.get(FileLockManager) >> Mock(FileLockManager)
+        _ * parent.get(TimeProvider) >> Mock(TimeProvider)
+    }
+
+    private CacheRepository initCacheRepository() {
+        CacheRepository cacheRepository = Mock()
+        DirectoryCacheBuilder cacheBuilder = Mock()
+        _ * cacheRepository.store(_) >> cacheBuilder
+        _ * cacheBuilder.withVersionStrategy(_) >> cacheBuilder
+        _ * cacheBuilder.withLockMode(_) >> cacheBuilder
+        _ * cacheBuilder.withDisplayName(_) >> cacheBuilder
+        PersistentCache cache = Mock()
+        _ * cacheBuilder.open() >> cache
+        cache.baseDir >> new File("cache")
+        return cacheRepository
+    }
+
+    def "can create dependency resolution services"() {
+        given:
+        _ * parent.get(Instantiator.class) >> instantiator
+        _ * parent.get(StartParameter.class) >> startParameter
+        1 * instantiator.newInstance(DefaultRepositoryHandler.class, _, _) >> repositoryHandler
+        1 * instantiator.newInstance(DefaultConfigurationContainer.class, !null, instantiator,
+                domainObjectContext, listenerManager, dependencyMetaDataProvider) >> configurationContainer
+
+        when:
+        def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
+
+        then:
+        resolutionServices.resolveRepositoryHandler != null
+        resolutionServices.configurationContainer != null
+        resolutionServices.dependencyHandler != null
+        resolutionServices.artifactHandler != null
+        resolutionServices.publishServicesFactory != null
+    }
+
+    def "publish services provide a repository handler"() {
+        DefaultRepositoryHandler publishRepositoryHandler = Mock()
+
+        given:
+        _ * parent.get(StartParameter.class) >> startParameter
+        _ * parent.get(Instantiator.class) >> instantiator
+        _ * instantiator.newInstance(DefaultRepositoryHandler.class, _, _) >> publishRepositoryHandler
+
+        when:
+        def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
+        def publishResolverHandler = resolutionServices.publishServicesFactory.create().repositoryHandler
+
+        then:
+        publishResolverHandler == publishRepositoryHandler
+    }
+
+    def "publish services provide an ArtifactPublisher"() {
+        given:
+        _ * parent.get(StartParameter.class) >> startParameter
+        _ * parent.get(Instantiator.class) >> instantiator
+
+        when:
+        def resolutionServices = services.create(fileResolver, dependencyMetaDataProvider, projectFinder, domainObjectContext)
+        def ivyService = resolutionServices.publishServicesFactory.create().artifactPublisher
+
+        then:
+        ivyService != null
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
new file mode 100644
index 0000000..20e9204
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.gradle.api.artifacts.ResolvedDependency
+import org.gradle.api.internal.file.FileSource
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+import org.gradle.api.artifacts.ResolvedModuleVersion
+
+class DefaultResolvedArtifactTest extends Specification {
+    final FileSource artifactSource = Mock()
+
+    def "uses extended attributes to determine classifier"() {
+        Artifact ivyArtifact = Mock()
+        ResolvedDependency dependency = Mock()
+        def artifact = new DefaultResolvedArtifact(dependency, ivyArtifact, artifactSource)
+
+        given:
+        _ * ivyArtifact.getExtraAttribute("m:classifier") >> "classifier"
+
+        expect:
+        artifact.classifier == 'classifier'
+    }
+
+    def "attributes are equal when module, name, type, extension and extended attributes are equal"() {
+        ResolvedDependency dependency = dep("group", "module1", "1.2")
+        ResolvedDependency dependencySameModule = dep("group", "module1", "1.2")
+        ResolvedDependency dependency2 = dep("group", "module2", "1-beta")
+        Artifact ivyArt = ivyArtifact("name", "type", "ext", [attr: "value"])
+        Artifact ivyArtifactWithDifferentName = ivyArtifact("name2", "type", "ext", [attr: "value"])
+        Artifact ivyArtifactWithDifferentType = ivyArtifact("name", "type2", "ext", [attr: "value"])
+        Artifact ivyArtifactWithDifferentExt = ivyArtifact("name", "type", "ext2", [attr: "value"])
+        Artifact ivyArtWithDifferentAttributes = ivyArtifact("name", "type", "ext", [attr: "value2"])
+        def artifact = new DefaultResolvedArtifact(dependency, ivyArt, artifactSource)
+        def equalArtifact = new DefaultResolvedArtifact(dependencySameModule, ivyArt, artifactSource)
+        def differentModule = new DefaultResolvedArtifact(dependency2, ivyArt, artifactSource)
+        def differentName = new DefaultResolvedArtifact(dependency, ivyArtifactWithDifferentName, artifactSource)
+        def differentType = new DefaultResolvedArtifact(dependency, ivyArtifactWithDifferentType, artifactSource)
+        def differentExtension = new DefaultResolvedArtifact(dependency, ivyArtifactWithDifferentExt, artifactSource)
+        def differentAttributes = new DefaultResolvedArtifact(dependency, ivyArtWithDifferentAttributes, artifactSource)
+
+        expect:
+        artifact Matchers.strictlyEqual(equalArtifact)
+        artifact != differentModule
+        artifact != differentName
+        artifact != differentType
+        artifact != differentExtension
+        artifact != differentAttributes
+    }
+
+    def ivyArtifact(String name, String type, String extension, Map attributes) {
+        Artifact artifact = Mock()
+        _ * artifact.name >> name
+        _ * artifact.type >> type
+        _ * artifact.ext >> extension
+        _ * artifact.extraAttributes >> attributes
+        return artifact
+    }
+
+    def dep(String group, String moduleName, String version) {
+        ResolvedDependency dependency = Mock()
+        ResolvedModuleVersion module = Mock()
+        _ * dependency.module >> module
+        _ * module.id >> new DefaultModuleVersionIdentifier(group, moduleName, version)
+        return dependency
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy
new file mode 100644
index 0000000..35836cf
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencySpec.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.api.artifacts.ResolvedArtifact
+import spock.lang.Specification
+
+class DefaultResolvedDependencySpec extends Specification {
+    final DefaultResolvedDependency dependency = new DefaultResolvedDependency("name", "group", "module", "version", "config")
+
+    def "provides meta-data about the module"() {
+        expect:
+        dependency.module.id.group == "group"
+        dependency.module.id.name == "module"
+        dependency.module.id.version == "version"
+    }
+
+    def "artifacts are ordered by name then classifier then extension then type"() {
+        ResolvedArtifact artifact1 = artifact("a", null, "jar", "jar")
+        ResolvedArtifact artifact2 = artifact("b", null, "jar", "jar")
+        ResolvedArtifact artifact3 = artifact("b", "a-classifier", "jar", "jar")
+        ResolvedArtifact artifact4 = artifact("b", "b-classifier", "b-type", "a-ext")
+        ResolvedArtifact artifact5 = artifact("b", "b-classifier", "a-type", "b-ext")
+        ResolvedArtifact artifact6 = artifact("b", "b-classifier", "b-type", "b-ext")
+        ResolvedArtifact artifact7 = artifact("c", "a-classifier", "jar", "jar")
+
+        given:
+        dependency.addModuleArtifact(artifact6)
+        dependency.addModuleArtifact(artifact1)
+        dependency.addModuleArtifact(artifact3)
+        dependency.addModuleArtifact(artifact5)
+        dependency.addModuleArtifact(artifact2)
+        dependency.addModuleArtifact(artifact7)
+        dependency.addModuleArtifact(artifact4)
+
+        expect:
+        dependency.moduleArtifacts as List == [artifact1, artifact2, artifact3, artifact4, artifact5, artifact6, artifact7]
+    }
+
+    def "does not discard artifacts with the same name and classifier and extension and type"() {
+        ResolvedArtifact artifact1 = artifact("a", null, "jar", "jar")
+        ResolvedArtifact artifact2 = artifact("a", null, "jar", "jar")
+
+        given:
+        dependency.addModuleArtifact(artifact1)
+        dependency.addModuleArtifact(artifact2)
+
+        expect:
+        dependency.moduleArtifacts == [artifact1, artifact2] as Set
+    }
+
+    def "parent specific artifacts are ordered by name then classifier then extension then type"() {
+        ResolvedArtifact artifact1 = artifact("a", null, "jar", "jar")
+        ResolvedArtifact artifact2 = artifact("b", null, "jar", "jar")
+        ResolvedArtifact artifact3 = artifact("b", "a-classifier", "jar", "jar")
+        ResolvedArtifact artifact4 = artifact("b", "b-classifier", "b-type", "a-ext")
+        ResolvedArtifact artifact5 = artifact("b", "b-classifier", "a-type", "b-ext")
+        ResolvedArtifact artifact6 = artifact("b", "b-classifier", "b-type", "b-ext")
+        ResolvedArtifact artifact7 = artifact("c", "a-classifier", "jar", "jar")
+        DefaultResolvedDependency parent = Mock()
+
+        given:
+        dependency.parents.add(parent)
+        dependency.addParentSpecificArtifacts(parent, [artifact6, artifact1, artifact7, artifact5, artifact2, artifact3, artifact4] as Set)
+
+        expect:
+        dependency.getParentArtifacts(parent) as List == [artifact1, artifact2, artifact3, artifact4, artifact5, artifact6, artifact7]
+    }
+
+    def artifact(String name, String classifier, String type, String extension) {
+        ResolvedArtifact artifact = Mock()
+        _ * artifact.toString() >> "$name-$classifier-$type.$extension"
+        _ * artifact.name >> name
+        _ * artifact.classifier >> classifier
+        _ * artifact.type >> type
+        _ * artifact.extension >> extension
+        return artifact
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
new file mode 100644
index 0000000..1d110a5
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.gradle.api.artifacts.ArtifactsTestUtils.createResolvedArtifact;
+import static org.gradle.util.Matchers.strictlyEqual;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedDependencyTest {
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+
+    @Test
+    public void init() {
+        String someGroup = "someGroup";
+        String someName = "someName";
+        String someVersion = "someVersion";
+        String someConfiguration = "someConfiguration";
+        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency(someGroup, someName, someVersion, someConfiguration);
+        assertThat(resolvedDependency.getName(), equalTo(someGroup + ":" + someName + ":" + someVersion));
+        assertThat(resolvedDependency.getModuleGroup(), equalTo(someGroup));
+        assertThat(resolvedDependency.getModuleName(), equalTo(someName));
+        assertThat(resolvedDependency.getModuleVersion(), equalTo(someVersion));
+        assertThat(resolvedDependency.getConfiguration(), equalTo(someConfiguration));
+        assertThat(resolvedDependency.getModuleArtifacts(), equalTo(Collections.<ResolvedArtifact>emptySet()));
+        assertThat(resolvedDependency.getChildren(), equalTo(Collections.<ResolvedDependency>emptySet()));
+        assertThat(resolvedDependency.getParents(), equalTo(Collections.<ResolvedDependency>emptySet()));
+    }
+
+    @Test
+    public void getAllModuleArtifacts() {
+        ResolvedArtifact moduleArtifact = createArtifact("moduleArtifact");
+        ResolvedArtifact childModuleArtifact = createArtifact("childModuleArtifact");
+        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration");
+        resolvedDependency.addModuleArtifact(moduleArtifact);
+        DefaultResolvedDependency childDependency = new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration");
+        childDependency.addModuleArtifact(childModuleArtifact);
+        resolvedDependency.getChildren().add(childDependency);
+        assertThat(resolvedDependency.getAllModuleArtifacts(), equalTo(toSet(moduleArtifact, childModuleArtifact)));
+    }
+
+    @Test
+    public void getParentArtifacts() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+
+        Set<ResolvedArtifact> parent1SpecificArtifacts = toSet(createArtifact("parent1Specific"));
+        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
+
+        Set<ResolvedArtifact> parent2SpecificArtifacts = toSet(createArtifact("parent2Specific"));
+        DefaultResolvedDependency parentResolvedDependency2 = createAndAddParent("parent2", resolvedDependency, parent2SpecificArtifacts);
+
+        assertThat(resolvedDependency.getParentArtifacts(parentResolvedDependency1), equalTo(parent1SpecificArtifacts));
+        assertThat(resolvedDependency.getParentArtifacts(parentResolvedDependency2), equalTo(parent2SpecificArtifacts));
+    }
+
+    private ResolvedArtifact createArtifact(String name) {
+        return createResolvedArtifact(context, name, "someType", "someExt", new File("pathTo" + name));
+    }
+
+    private DefaultResolvedDependency createResolvedDependency() {
+        return new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration");
+    }
+
+    @Test
+    public void getArtifacts() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+
+        Set<ResolvedArtifact> parent1SpecificArtifacts = toSet(createArtifact("parent1Specific"));
+        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
+
+        assertThat(resolvedDependency.getArtifacts(parentResolvedDependency1), equalTo(parent1SpecificArtifacts));
+    }
+
+    @Test
+    public void getArtifactsWithParentWithoutParentArtifacts() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+
+        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration");
+        resolvedDependency.getParents().add(parent);
+        assertThat(resolvedDependency.getArtifacts(parent), equalTo(Collections.<ResolvedArtifact>emptySet()));
+    }
+
+    @Test
+    public void getParentArtifactsWithParentWithoutParentArtifacts() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+
+        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration");
+        resolvedDependency.getParents().add(parent);
+        assertThat(resolvedDependency.getParentArtifacts(parent), equalTo(Collections.<ResolvedArtifact>emptySet()));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getParentArtifactsWithUnknownParent() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration");
+        assertThat(resolvedDependency.getParentArtifacts(unknownParent),
+                equalTo(Collections.<ResolvedArtifact>emptySet()));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getArtifactsWithUnknownParent() {
+        Set<ResolvedArtifact> someModuleArtifacts = toSet(createArtifact("someModuleResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+
+        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration");
+        assertThat(resolvedDependency.getParentArtifacts(unknownParent),
+                equalTo(someModuleArtifacts));
+    }
+
+    @Test
+    public void getAllArtifacts() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency();
+
+        Set<ResolvedArtifact> parent1SpecificArtifacts = newHashSet(createArtifact("parent1Specific"));
+        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
+
+        createAndAddParent("parent2", resolvedDependency, newHashSet(createArtifact("parent2Specific")));
+
+        DefaultResolvedDependency child = new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration");
+        resolvedDependency.getChildren().add(child);
+
+        Set<ResolvedArtifact> childParent1SpecificArtifacts = newHashSet(createArtifact("childParent1Specific"));
+        createAndAddParent("childParent1", child, childParent1SpecificArtifacts);
+
+        Set<ResolvedArtifact> childParent2SpecificArtifacts = newHashSet(createArtifact("childParent2Specific"));
+        createAndAddParent("childParent2", child, childParent2SpecificArtifacts);
+
+        Iterable<ResolvedArtifact> allArtifacts = newHashSet(concat(parent1SpecificArtifacts, childParent1SpecificArtifacts, childParent2SpecificArtifacts));
+        assertThat(resolvedDependency.getAllArtifacts(parentResolvedDependency1), equalTo(allArtifacts));
+    }
+
+    @Test
+    public void equalsAndHashCode() {
+        DefaultResolvedDependency dependency = new DefaultResolvedDependency("group", "name", "version", "config");
+        DefaultResolvedDependency same = new DefaultResolvedDependency("group", "name", "version", "config");
+        DefaultResolvedDependency differentGroup = new DefaultResolvedDependency("other", "name", "version", "config");
+        DefaultResolvedDependency differentName = new DefaultResolvedDependency("group", "other", "version", "config");
+        DefaultResolvedDependency differentVersion = new DefaultResolvedDependency("group", "name", "other", "config");
+        DefaultResolvedDependency differentConfiguration = new DefaultResolvedDependency("group", "name", "version", "other");
+
+        assertThat(dependency, strictlyEqual(same));
+        assertThat(dependency, not(equalTo(differentGroup)));
+        assertThat(dependency, not(equalTo(differentName)));
+        assertThat(dependency, not(equalTo(differentVersion)));
+        assertThat(dependency, not(equalTo(differentConfiguration)));
+    }
+
+    private DefaultResolvedDependency createAndAddParent(String parentName, DefaultResolvedDependency resolvedDependency, Set<ResolvedArtifact> parentSpecificArtifacts) {
+        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", parentName, "someVersion", "someConfiguration");
+        resolvedDependency.getParents().add(parent);
+        resolvedDependency.addParentSpecificArtifacts(parent, parentSpecificArtifacts);
+        return parent;
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementServicesTest.groovy
deleted file mode 100644
index e884e73..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DependencyManagementServicesTest.groovy
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2011 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
-
-import org.gradle.api.artifacts.maven.MavenFactory
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory
-import spock.lang.Specification
-import org.gradle.api.internal.project.ServiceRegistry
-import org.gradle.logging.LoggingManagerInternal
-
-class DependencyManagementServicesTest extends Specification {
-    final ServiceRegistry parent = Mock()
-    final DefaultDependencyManagementServices services = new DefaultDependencyManagementServices(parent)
-
-    def providesAResolverFactory() {
-        given:
-        _ * parent.getFactory(LoggingManagerInternal.class)
-
-        expect:
-        services.get(ResolverFactory.class) != null
-    }
-
-    def providesAMavenFactory() {
-        expect:
-        services.get(MavenFactory.class) != null
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
new file mode 100644
index 0000000..a31f49b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * 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.dsl
+
+import org.gradle.api.artifacts.PublishArtifactSet
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.util.JUnit4GroovyMockery
+import spock.lang.Specification
+import org.gradle.api.internal.notations.api.NotationParser
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultArtifactHandlerTest extends Specification {
+
+    private static final String TEST_CONF_NAME = "someConf"
+
+    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    private ConfigurationContainer configurationContainerStub = Mock()
+    private NotationParser<PublishArtifact> artifactFactoryStub = Mock()
+    private Configuration configurationMock = Mock()
+    private PublishArtifactSet artifactsMock = Mock()
+
+    private DefaultArtifactHandler artifactHandler = new DefaultArtifactHandler(configurationContainerStub, artifactFactoryStub)
+
+    void setup() {
+        configurationContainerStub.findByName(TEST_CONF_NAME) >> configurationMock
+        configurationContainerStub.getByName(TEST_CONF_NAME) >> configurationMock
+        configurationMock.artifacts >> artifactsMock
+    }
+
+    void pushOneDependency() {
+        PublishArtifact artifactDummy = Mock()
+
+        when:
+        artifactHandler.someConf("someNotation")
+
+        then:
+        1 * artifactFactoryStub.parseNotation("someNotation") >> artifactDummy
+        1 * artifactsMock.add(artifactDummy)
+    }
+
+    void pushOneDependencyWithClosure() {
+        PublishArtifact artifact = new DefaultPublishArtifact("name", "ext", "jar", "classifier", null, new File(""))
+
+        when:
+        artifactHandler.someConf("someNotation") { type = 'source' }
+
+        then:
+        artifact.type == 'source'
+
+        and:
+        1 * artifactFactoryStub.parseNotation("someNotation") >> artifact
+        1 * artifactsMock.add(artifact)
+    }
+
+    void pushMultipleDependencies() {
+        PublishArtifact artifactDummy1 = Mock()
+        PublishArtifact artifactDummy2 = Mock()
+
+        when:
+        artifactHandler.someConf("someNotation", "someNotation2")
+
+        then:
+        1 * artifactFactoryStub.parseNotation("someNotation") >> artifactDummy1
+        1 * artifactFactoryStub.parseNotation("someNotation2") >> artifactDummy2
+        1 * artifactsMock.add(artifactDummy1)
+        1 * artifactsMock.add(artifactDummy2)
+
+    }
+
+    void addOneDependency() {
+        PublishArtifact artifactDummy = Mock()
+
+        when:
+        artifactHandler.add('someConf', "someNotation")
+
+        then:
+        1 * artifactFactoryStub.parseNotation("someNotation") >> artifactDummy
+        1 * artifactsMock.add(artifactDummy)
+    }
+
+    void addOneDependencyWithClosure() {
+        PublishArtifact artifact = new DefaultPublishArtifact("name", "ext", "jar", "classifier", null, new File(""))
+
+        when:
+        artifactHandler.add('someConf', "someNotation") { type = 'source' }
+
+        then:
+        artifact.type == 'source'
+
+        and:
+        1 * artifactFactoryStub.parseNotation("someNotation") >> artifact
+        1 * artifactsMock.add(artifact)
+    }
+
+    void pushToUnknownConfiguration() {
+        String unknownConf = TEST_CONF_NAME + "delta"
+
+        given:
+        configurationContainerStub.findByName(unknownConf) >> null
+
+        when:
+        artifactHandler."$unknownConf"("someNotation")
+
+        then:
+        thrown(MissingMethodException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
new file mode 100644
index 0000000..72846a9
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
@@ -0,0 +1,251 @@
+/*
+ * 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.dsl;
+
+
+import java.awt.Point
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Module
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPublishArtifactFactoryTest extends Specification {
+    final DependencyMetaDataProvider provider = Mock()
+    final Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
+    final DefaultPublishArtifactFactory publishArtifactFactory = new DefaultPublishArtifactFactory(instantiator, provider)
+
+    def setup() {
+        Module module = Mock()
+        _ * provider.module >> module
+        _ * module.version >> '1.2'
+    }
+
+    def createArtifactFromPublishArtifactInstance() {
+        PublishArtifact original = Mock()
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(original)
+
+        then:
+        publishArtifact == original
+    }
+
+    def createArtifactFromArchiveTask() {
+        AbstractArchiveTask archiveTask = Mock()
+        archiveTask.getArchivePath() >> new File("")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(archiveTask)
+
+        then:
+        publishArtifact instanceof ArchivePublishArtifact
+        publishArtifact.archiveTask == archiveTask
+    }
+
+    def createArtifactFromFile() {
+        def file = new File("some.zip")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(file)
+
+        then:
+        publishArtifact instanceof DefaultPublishArtifact
+        publishArtifact.file == file
+    }
+
+    def createArtifactFromFileInMap() {
+        Task task = Mock()
+        def file = new File("some.zip")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(file: file, type: 'someType', builtBy: task)
+
+        then:
+        publishArtifact instanceof DefaultPublishArtifact
+        publishArtifact.file == file
+        publishArtifact.type == 'someType'
+        publishArtifact.buildDependencies.getDependencies(null) == [task] as Set
+    }
+
+    def determinesArtifactPropertiesFromFileName() {
+        def file1 = new File("some.zip")
+        def file2 = new File("some.zip.zip")
+        def file3 = new File(".zip")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(file1)
+
+        then:
+        publishArtifact.name == 'some'
+        publishArtifact.type == 'zip'
+        publishArtifact.extension == 'zip'
+        publishArtifact.classifier == null
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file2)
+
+        then:
+        publishArtifact.name == 'some.zip'
+        publishArtifact.type == 'zip'
+        publishArtifact.extension == 'zip'
+        publishArtifact.classifier == null
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file3)
+
+        then:
+        publishArtifact.name == ''
+        publishArtifact.type == 'zip'
+        publishArtifact.extension == 'zip'
+        publishArtifact.classifier == null
+    }
+
+    def handlesFileWithNoExtension() {
+        def file = new File("some-file")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(file)
+
+        then:
+        publishArtifact.file == file
+        publishArtifact.name == 'some-file'
+        publishArtifact.type == null
+        publishArtifact.extension == null
+        publishArtifact.classifier == null
+    }
+
+    def removesProjectVersionFromFileName() {
+        def file1 = new File("some-file-1.2.jar")
+        def file2 = new File("some-file-1.2-1.2.jar")
+        def file3 = new File("some-file-1.22.jar")
+        def file4 = new File("some-file-1.2.jar.jar")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(file1)
+
+        then:
+        publishArtifact.name == 'some-file'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == null
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file2)
+
+        then:
+        publishArtifact.name == 'some-file-1.2'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == null
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file3)
+
+        then:
+        publishArtifact.name == 'some-file-1.22'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == null
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file4)
+
+        then:
+        publishArtifact.name == 'some-file-1.2.jar'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == null
+    }
+
+    def determinesClassifierFromFileName() {
+        def file1 = new File("some-file-1.2-classifier.jar")
+        def file2 = new File("some-file-1.2-classifier-1.2.jar")
+        def file3 = new File("-1.2-classifier.jar")
+        def file4 = new File("some-file-1.2-classifier")
+        def file5 = new File("some-file-1.2-.jar")
+
+        when:
+        def publishArtifact = publishArtifactFactory.parseNotation(file1)
+
+        then:
+        publishArtifact.name == 'some-file'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == 'classifier'
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file2)
+
+        then:
+        publishArtifact.name == 'some-file-1.2-classifier'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == null
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file3)
+
+        then:
+        publishArtifact.name == ''
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == 'classifier'
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file4)
+
+        then:
+        publishArtifact.name == 'some-file'
+        publishArtifact.type == null
+        publishArtifact.extension == null
+        publishArtifact.classifier == 'classifier'
+
+        when:
+        publishArtifact = publishArtifactFactory.parseNotation(file5)
+
+        then:
+        publishArtifact.name == 'some-file'
+        publishArtifact.type == 'jar'
+        publishArtifact.extension == 'jar'
+        publishArtifact.classifier == null
+    }
+
+    public void createArtifactWithNullNotationShouldThrowInvalidUserDataEx() {
+        when:
+        publishArtifactFactory.parseNotation(null)
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    public void createArtifactWithUnknownNotationShouldThrowInvalidUserDataEx() {
+        when:
+        publishArtifactFactory.parseNotation(new Point(1, 2))
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy
new file mode 100644
index 0000000..a3fa933
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.ivyservice
+
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.artifacts.ResolvedConfiguration
+
+class CacheLockingArtifactDependencyResolverTest extends Specification {
+    final CacheLockingManager lockingManager = Mock()
+    final ArtifactDependencyResolver target = Mock()
+    final CacheLockingArtifactDependencyResolver resolver = new CacheLockingArtifactDependencyResolver(lockingManager, target)
+
+    def "resolves while holding a lock on the cache"() {
+        ConfigurationInternal configuration = Mock()
+        ResolvedConfiguration resolvedConfiguration = Mock()
+
+        when:
+        def result = resolver.resolve(configuration)
+
+        then:
+        result == resolvedConfiguration
+
+        and:
+        1 * lockingManager.useCache("resolve $configuration", !null) >> {
+            it[1].create()
+        }
+        1 * target.resolve(configuration) >> resolvedConfiguration
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.groovy
new file mode 100644
index 0000000..521ee61
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.ivyservice
+
+import spock.lang.Specification
+import org.apache.ivy.core.settings.IvySettings
+
+class DefaultIvyFactoryTest extends Specification {
+    final DefaultIvyFactory factory = new DefaultIvyFactory()
+
+    def "creates Ivy instance for IvySettings"() {
+        def ivySettings = new IvySettings()
+
+        expect:
+        def ivy = factory.createIvy(ivySettings)
+        ivy.settings == ivySettings
+    }
+
+    def "caches Ivy instance for given IvySettings"() {
+        def ivySettings = new IvySettings()
+
+        expect:
+        def ivy1 = factory.createIvy(ivySettings)
+        def ivy2 = factory.createIvy(ivySettings)
+        ivy1.is(ivy2)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
new file mode 100644
index 0000000..2a93d19
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * 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.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.settings.IvySettings
+import org.apache.ivy.plugins.resolver.DependencyResolver
+import org.apache.ivy.plugins.resolver.IBiblioResolver
+import org.gradle.internal.Factory
+import spock.lang.Specification
+
+class DefaultSettingsConverterTest extends Specification {
+    final DependencyResolver defaultResolver = Mock()
+    final IBiblioResolver testResolver = new IBiblioResolver()
+    final IBiblioResolver testResolver2 = new IBiblioResolver()
+
+    File testGradleUserHome = new File('gradleUserHome')
+
+    final Factory<IvySettings> ivySettingsFactory = Mock()
+    final IvySettings ivySettings = new IvySettings()
+
+    DefaultSettingsConverter converter = new DefaultSettingsConverter(ivySettingsFactory)
+
+    public void setup() {
+        testResolver.name = 'resolver'
+    }
+
+    public void testConvertForResolve() {
+        when:
+        IvySettings settings = converter.convertForResolve(defaultResolver, [testResolver, testResolver2])
+
+        then:
+        1 * ivySettingsFactory.create() >> ivySettings
+        1 * defaultResolver.setSettings(ivySettings)
+        _ * defaultResolver.getName() >> 'default'
+        0 * _._
+
+        assert settings.is(ivySettings)
+
+        assert settings.defaultResolver == defaultResolver
+        assert settings.resolvers.size() == 3
+        [testResolver, testResolver2].each { resolver ->
+            assert settings.resolvers.any { it == resolver }
+            assert settings.getResolver(resolver.name) == resolver
+            assert settings == resolver.settings
+            assert settings == resolver.repositoryCacheManager.settings
+        }
+    }
+
+    public void shouldReuseResolveSettings() {
+        when:
+        IvySettings settings = converter.convertForResolve(defaultResolver, [testResolver, testResolver2])
+
+        then:
+        1 * ivySettingsFactory.create() >> ivySettings
+        1 * defaultResolver.setSettings(ivySettings)
+        _ * defaultResolver.getName() >> 'default'
+        0 * _._
+
+        assert settings.is(ivySettings)
+
+        [testResolver, testResolver2].each { resolver ->
+            assert settings.resolvers.any { it == resolver }
+        }
+
+        when:
+        settings = converter.convertForResolve(defaultResolver, [testResolver])
+
+        then:
+        assert settings.is(ivySettings)
+
+        assert settings.defaultResolver == defaultResolver
+        assert settings.resolvers.size() == 2
+        [testResolver].each { resolver ->
+             assert settings.resolvers.any { it == resolver }
+             assert settings.getResolver(resolver.name) == resolver
+             assert settings == resolver.settings
+             assert settings == resolver.repositoryCacheManager.settings
+         }
+    }
+
+    public void testConvertForPublish() {
+        when:
+        IvySettings settings = converter.convertForPublish([testResolver, testResolver2])
+
+        then:
+        settings.is(ivySettings)
+
+        and:
+        [testResolver, testResolver2].each {
+            it.settings == settings
+            it.repositoryCacheManager.settings == settings
+        }
+
+        and:
+        1 * ivySettingsFactory.create() >> ivySettings
+        0 * _._
+    }
+
+    public void reusesPublishSettings() {
+        when:
+        IvySettings settings = converter.convertForPublish([testResolver])
+
+        then:
+        settings.is(ivySettings)
+
+        and:
+        1 * ivySettingsFactory.create() >> ivySettings
+        0 * _._
+
+        when:
+        settings = converter.convertForPublish([testResolver, testResolver2])
+
+        then:
+        settings.is(ivySettings)
+
+        and:
+            [testResolver, testResolver2].each {
+            it.settings == settings
+            it.repositoryCacheManager.settings == settings
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
new file mode 100644
index 0000000..9864151
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
@@ -0,0 +1,133 @@
+/*
+ * 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.ivyservice;
+
+
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolveException
+import org.gradle.api.artifacts.ResolvedConfiguration
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.specs.Spec
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
+
+ at RunWith(JMock.class)
+public class ErrorHandlingArtifactDependencyResolverTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final ArtifactDependencyResolver resolverMock = context.mock(ArtifactDependencyResolver.class);
+    private final ResolvedConfiguration resolvedConfigurationMock = context.mock(ResolvedConfiguration.class);
+    private final ConfigurationInternal configurationMock = context.mock(ConfigurationInternal.class, "<config display name>");
+    private final Spec<Dependency> specDummy = context.mock(Spec.class);
+    private final RuntimeException failure = new RuntimeException();
+    private final ErrorHandlingArtifactDependencyResolver dependencyResolver = new ErrorHandlingArtifactDependencyResolver(resolverMock);
+
+    @Test
+    public void resolveDelegatesToBackingService() {
+        context.checking {
+            one(resolverMock).resolve(configurationMock);
+            will(returnValue(resolvedConfigurationMock));
+        }
+
+        ResolvedConfiguration resolvedConfiguration = dependencyResolver.resolve(configurationMock);
+
+        context.checking {
+            one(resolvedConfigurationMock).hasError();
+            one(resolvedConfigurationMock).rethrowFailure();
+            one(resolvedConfigurationMock).getFiles(specDummy);
+        }
+
+        resolvedConfiguration.hasError();
+        resolvedConfiguration.rethrowFailure();
+        resolvedConfiguration.getFiles(specDummy);
+    }
+
+    @Test
+    public void returnsAnExceptionThrowingConfigurationWhenResolveFails() {
+
+        context.checking {
+            one(resolverMock).resolve(configurationMock);
+            will(throwException(failure));
+        }
+
+        ResolvedConfiguration resolvedConfiguration = dependencyResolver.resolve(configurationMock);
+
+        assertThat(resolvedConfiguration.hasError(), equalTo(true));
+
+        assertFailsWithResolveException {
+            resolvedConfiguration.rethrowFailure();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFiles(specDummy);
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFirstLevelModuleDependencies();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getResolvedArtifacts();
+        }
+    }
+
+    @Test
+    public void wrapsExceptionsThrownByResolvedConfiguration() {
+        context.checking {
+            one(resolverMock).resolve(configurationMock);
+            will(returnValue(resolvedConfigurationMock));
+        }
+
+        ResolvedConfiguration resolvedConfiguration = dependencyResolver.resolve(configurationMock);
+
+        context.checking {
+            allowing(resolvedConfigurationMock).rethrowFailure()
+            will(throwException(failure))
+            allowing(resolvedConfigurationMock).getFiles(specDummy)
+            will(throwException(failure))
+            allowing(resolvedConfigurationMock).getFirstLevelModuleDependencies()
+            will(throwException(failure))
+            allowing(resolvedConfigurationMock).getResolvedArtifacts()
+            will(throwException(failure))
+        }
+
+        assertFailsWithResolveException {
+            resolvedConfiguration.rethrowFailure();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFiles(specDummy);
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFirstLevelModuleDependencies();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getResolvedArtifacts();
+        }
+    }
+
+    def assertFailsWithResolveException(Closure cl) {
+        try {
+            cl();
+            fail();
+        } catch (ResolveException e) {
+            assertThat(e.message, equalTo("Could not resolve all dependencies for <config display name>."));
+            assertThat(e.cause, sameInstance((Throwable) failure));
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisherTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisherTest.groovy
new file mode 100644
index 0000000..3244cdf
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactPublisherTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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.ivyservice;
+
+
+import org.gradle.api.artifacts.PublishException
+import org.gradle.api.artifacts.ResolveException
+
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
+import org.gradle.api.internal.artifacts.ArtifactPublisher
+
+ at RunWith(JMock.class)
+public class ErrorHandlingArtifactPublisherTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final ArtifactPublisher artifactPublisherMock = context.mock(ArtifactPublisher.class);
+    private final ConfigurationInternal configurationMock = context.mock(ConfigurationInternal.class, "<config display name>");
+    private final RuntimeException failure = new RuntimeException();
+    private final ErrorHandlingArtifactPublisher ivyService = new ErrorHandlingArtifactPublisher(artifactPublisherMock);
+
+    @Test
+    public void publishDelegatesToBackingService() {
+        context.checking {
+            one(artifactPublisherMock).publish(configurationMock, null)
+        }
+
+        ivyService.publish(configurationMock, null)
+    }
+
+    @Test
+    public void wrapsPublishException() {
+        context.checking {
+            one(artifactPublisherMock).publish(configurationMock, null)
+            will(throwException(failure))
+        }
+
+        try {
+            ivyService.publish(configurationMock, null)
+            fail()
+        }
+        catch(PublishException e) {
+            assertThat e.message, equalTo("Could not publish <config display name>.")
+            assertThat(e.cause, sameInstance((Throwable) failure));
+        }
+    }
+
+    def assertFailsWithResolveException(Closure cl) {
+        try {
+            cl();
+            fail();
+        } catch (ResolveException e) {
+            assertThat(e.message, equalTo("Could not resolve all dependencies for <config display name>."));
+            assertThat(e.cause, sameInstance((Throwable) failure));
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
new file mode 100644
index 0000000..a0c808a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.publish.PublishEngine;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.configurations.*;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class IvyBackedArtifactPublisherTest {
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+
+    private ModuleDescriptor publishModuleDescriptorDummy = context.mock(ModuleDescriptor.class);
+    private ModuleDescriptor fileModuleDescriptorMock = context.mock(ModuleDescriptor.class);
+    private PublishEngine publishEngineDummy = context.mock(PublishEngine.class);
+    private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
+    private ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
+    private IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
+    private SettingsConverter settingsConverterStub = context.mock(SettingsConverter.class);
+    private IvyDependencyPublisher ivyDependencyPublisherMock = context.mock(IvyDependencyPublisher.class);
+    private ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publishConverter");
+    private ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "fileConverter");
+
+    @Test
+    public void testPublish() throws IOException, ParseException {
+        final IvySettings ivySettingsDummy = new IvySettings();
+        final ConfigurationInternal configuration = context.mock(ConfigurationInternal.class);
+        final Set<Configuration> configurations = createConfiguration();
+        final File someDescriptorDestination = new File("somePath");
+        final List<DependencyResolver> publishResolversDummy = createPublishResolversDummy();
+        final Module moduleDummy = context.mock(Module.class, "moduleForResolve");
+        final IvyBackedArtifactPublisher ivyService = createIvyService();
+
+        setUpForPublish(configurations, publishResolversDummy, moduleDummy,
+                ivySettingsDummy);
+
+        final Set<String> expectedConfigurations = Configurations.getNames(configurations, true);
+        context.checking(new Expectations() {{
+            allowing(configuration).getHierarchy();
+            will(returnValue(configurations));
+            allowing(configuration).getModule();
+            will(returnValue(moduleDummy));
+            allowing(resolverProvider).getResolvers();
+            will(returnValue(publishResolversDummy));
+            allowing(configuration).getResolutionStrategy();
+            will(returnValue(new DefaultResolutionStrategy()));
+            one(fileModuleDescriptorMock).toIvyFile(someDescriptorDestination);
+            one(ivyDependencyPublisherMock).publish(expectedConfigurations,
+                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, publishEngineDummy);
+        }});
+
+        ivyService.publish(configuration, someDescriptorDestination);
+    }
+
+    private IvyBackedArtifactPublisher createIvyService() {
+        return new IvyBackedArtifactPublisher(resolverProvider,
+                settingsConverterStub,
+                publishModuleDescriptorConverter,
+                fileModuleDescriptorConverter,
+                ivyFactoryStub,
+                ivyDependencyPublisherMock);
+    }
+
+    private List<DependencyResolver> createPublishResolversDummy() {
+        return WrapUtil.toList(context.mock(DependencyResolver.class, "publish"));
+    }
+
+    private Set<Configuration> createConfiguration() {
+        final Configuration configurationStub1 = context.mock(Configuration.class, "confStub1");
+        final Configuration configurationStub2 = context.mock(Configuration.class, "confStub2");
+        context.checking(new Expectations() {{
+            allowing(configurationStub1).getName();
+            will(returnValue("conf1"));
+
+            allowing(configurationStub1).getHierarchy();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub1)));
+
+            allowing(configurationStub1).getAll();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub1, configurationStub2)));
+
+            allowing(configurationStub2).getName();
+            will(returnValue("conf2"));
+
+            allowing(configurationStub2).getHierarchy();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub2)));
+
+            allowing(configurationStub2).getAll();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub1, configurationStub2)));
+        }});
+        return WrapUtil.toSet(configurationStub1, configurationStub2);
+    }
+
+    private void setUpForPublish(final Set<Configuration> configurations,
+                                 final List<DependencyResolver> publishResolversDummy, final Module moduleDummy,
+                                 final IvySettings ivySettingsDummy) {
+        context.checking(new Expectations() {{
+            allowing(dependencyMetaDataProviderMock).getModule();
+            will(returnValue(moduleDummy));
+
+            allowing(settingsConverterStub).convertForPublish(publishResolversDummy
+            );
+            will(returnValue(ivySettingsDummy));
+
+            allowing(setUpIvyFactory(ivySettingsDummy)).getPublishEngine();
+            will(returnValue(publishEngineDummy));
+
+            allowing(publishModuleDescriptorConverter).convert(with(equalTo(configurations)),
+                    with(equalTo(moduleDummy)));
+            will(returnValue(publishModuleDescriptorDummy));
+
+            allowing(fileModuleDescriptorConverter).convert(with(equalTo(configurations)),
+                    with(equalTo(moduleDummy)));
+            will(returnValue(fileModuleDescriptorMock));
+        }});
+    }
+
+    private Ivy setUpIvyFactory(final IvySettings ivySettingsDummy) {
+        final Ivy ivyStub = context.mock(Ivy.class);
+        context.checking(new Expectations() {{
+            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
+            will(returnValue(ivyStub));
+
+            allowing(ivyStub).getSettings();
+            will(returnValue(ivySettingsDummy));
+        }});
+        return ivyStub;
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactoryTest.groovy
new file mode 100644
index 0000000..b0cbbdc
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvySettingsFactoryTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.ivyservice
+
+import org.gradle.api.artifacts.ArtifactRepositoryContainer
+import spock.lang.Specification
+
+class IvySettingsFactoryTest extends Specification {
+    final File cacheDir = new File('user-dir')
+    final ArtifactCacheMetaData cacheMetaData = Mock()
+    final IvySettingsFactory factory = new IvySettingsFactory(cacheMetaData)
+
+    def "creates and configures an IvySettings instance"() {
+        given:
+        _ * cacheMetaData.cacheDir >> cacheDir
+
+        when:
+        def settings = factory.create()
+
+        then:
+        settings.defaultCache == new File(cacheDir, 'ivy')
+        settings.defaultCacheArtifactPattern == ArtifactRepositoryContainer.DEFAULT_CACHE_ARTIFACT_PATTERN
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
new file mode 100644
index 0000000..ad8a76f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveExceptionTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.ivyservice
+
+import spock.lang.Specification
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import static org.gradle.util.TextUtil.*
+
+class ModuleVersionResolveExceptionTest extends Specification {
+    def "can add incoming paths to exception"() {
+        def a = ModuleRevisionId.newInstance("org", "a", "1.2")
+        def b = ModuleRevisionId.newInstance("org", "b", "5")
+        def c = ModuleRevisionId.newInstance("org", "c", "1.0")
+
+        def cause = new RuntimeException()
+        def exception = new ModuleVersionResolveException("broken", cause)
+        def onePath = exception.withIncomingPaths([[a, b, c]])
+        def twoPaths = exception.withIncomingPaths([[a, b, c], [a, c]])
+
+        expect:
+        exception.message == 'broken'
+
+        onePath.message == toPlatformLineSeparators('''broken
+Required by:
+    org:a:1.2 > org:b:5 > org:c:1.0''')
+        onePath.stackTrace == exception.stackTrace
+        onePath.cause == cause
+
+        twoPaths.message == toPlatformLineSeparators('''broken
+Required by:
+    org:a:1.2 > org:b:5 > org:c:1.0
+    org:a:1.2 > org:c:1.0''')
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy
new file mode 100644
index 0000000..3c8f169
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactoryTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.ivyservice
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.gradle.api.artifacts.ResolvedArtifact
+import org.gradle.api.artifacts.ResolvedDependency
+import org.gradle.internal.Factory
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
+import spock.lang.Specification
+
+class ResolvedArtifactFactoryTest extends Specification {
+    final CacheLockingManager lockingManager = Mock()
+    final ResolvedArtifactFactory factory = new ResolvedArtifactFactory(lockingManager)
+
+    def "creates an artifact backed by module resolve result"() {
+        Artifact artifact = Mock()
+        ArtifactResolver artifactResolver = Mock()
+        ArtifactResolveResult artifactResolveResult = Mock()
+        ResolvedDependency resolvedDependency = Mock()
+        File file = new File("something.jar")
+
+        when:
+        ResolvedArtifact resolvedArtifact = factory.create(resolvedDependency, artifact, artifactResolver)
+
+        then:
+        resolvedArtifact instanceof DefaultResolvedArtifact
+
+        when:
+        resolvedArtifact.file
+
+        then:
+        1 * lockingManager.useCache(!null, !null) >> {String displayName, Factory<?> action ->
+            return action.create()
+        }
+        1 * artifactResolver.resolve(artifact) >> artifactResolveResult
+        _ * artifactResolveResult.file >> file
+        0 * _._
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
new file mode 100644
index 0000000..f4be57d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.dependencies.AbstractDependency;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+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.io.IOException;
+import java.text.ParseException;
+
+import static java.util.Collections.emptyList;
+import static org.gradle.util.WrapUtil.toLinkedSet;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class SelfResolvingDependencyResolverTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ArtifactDependencyResolver delegate = context.mock(ArtifactDependencyResolver.class);
+    private final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+    private final ConfigurationInternal configuration = context.mock(ConfigurationInternal.class);
+    private final Ivy ivy = Ivy.newInstance();
+    private final DependencySet dependencies = context.mock(DependencySet.class);
+
+    private final SelfResolvingDependencyResolver resolver = new SelfResolvingDependencyResolver(delegate);
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations() {{
+            allowing(configuration).getAllDependencies();
+            will(returnValue(dependencies));
+        }});
+    }
+
+    @Test
+    public void wrapsResolvedConfigurationProvidedByDelegate() {
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration);
+            will(returnValue(resolvedConfiguration));
+            allowing(dependencies).iterator();
+            will(returnIterator(emptyList()));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        ResolvedConfiguration configuration = resolver.resolve(this.configuration);
+        assertThat(configuration, not(sameInstance(resolvedConfiguration)));
+
+        final File file = new File("file");
+
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
+            will(returnValue(toSet(file)));
+        }});
+
+        assertThat(configuration.getFiles(Specs.SATISFIES_ALL), equalTo(toLinkedSet(file)));
+    }
+    
+    @Test
+    public void addsFilesFromSelfResolvingDependenciesBeforeFilesFromResolvedConfiguration() {
+        final AbstractDependency dependency = context.mock(AbstractDependency.class);
+
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration);
+            will(returnValue(resolvedConfiguration));
+            allowing(dependencies).iterator();
+            will(returnIterator(dependency));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        ResolvedConfiguration actualResolvedConfiguration = resolver.resolve(this.configuration);
+        assertThat(actualResolvedConfiguration, not(sameInstance(resolvedConfiguration)));
+
+        final File configFile = new File("from config");
+        final File depFile = new File("from dep");
+        final FileCollection depFiles = context.mock(FileCollection.class);
+
+        final boolean transitive = true;
+        context.checking(new Expectations() {{
+            allowing(configuration);
+            will(returnValue(transitive));
+            one(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
+            will(returnValue(toSet(configFile)));
+            one(dependency).resolve(with(notNullValue(DependencyResolveContext.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("add files to context");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    ((DependencyResolveContext) invocation.getParameter(0)).add(depFiles);
+                    return null;
+                }
+            });
+            allowing(depFiles).getFiles();
+            will(returnValue(toSet(depFile)));
+        }});
+
+        assertThat(actualResolvedConfiguration.getFiles(Specs.SATISFIES_ALL), equalTo(toLinkedSet(depFile, configFile)));
+    }
+
+    @Test
+    public void testGetModuleDependencies() throws IOException, ParseException {
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration);
+            will(returnValue(resolvedConfiguration));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
+
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getFirstLevelModuleDependencies();
+            will(returnValue(toSet(resolvedDependency)));
+        }});
+
+        assertThat(resolver.resolve(this.configuration).getFirstLevelModuleDependencies(),
+                equalTo(toSet(resolvedDependency)));
+    }
+
+    @Test
+    public void testGetResolvedArtifacts() {
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration);
+            will(returnValue(resolvedConfiguration));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        final ResolvedArtifact resolvedArtifact = context.mock(ResolvedArtifact.class);
+
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getResolvedArtifacts();
+            will(returnValue(toSet(resolvedArtifact)));
+        }});
+
+        assertThat(resolver.resolve(this.configuration).getResolvedArtifacts(),
+                equalTo(toSet(resolvedArtifact)));
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverTest.java
new file mode 100644
index 0000000..cc17793
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.specs.Specs;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.gradle.util.Matchers.isEmpty;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class ShortcircuitEmptyConfigsArtifactDependencyResolverTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ArtifactDependencyResolver delegate = context.mock(ArtifactDependencyResolver.class);
+    private final ConfigurationInternal configuration = context.mock(ConfigurationInternal.class);
+    private final DependencySet dependencies = context.mock(DependencySet.class);
+    private final ShortcircuitEmptyConfigsArtifactDependencyResolver dependencyResolver = new ShortcircuitEmptyConfigsArtifactDependencyResolver(delegate);
+
+    @Test
+    public void resolveReturnsEmptyResolvedConfigWhenConfigHasNoDependencies() {
+        context.checking(new Expectations(){{
+            allowing(configuration).getAllDependencies();
+            will(returnValue(dependencies));
+
+            allowing(dependencies).isEmpty();
+            will(returnValue(true));
+        }});
+
+        ResolvedConfiguration resolvedConfig = dependencyResolver.resolve(configuration);
+
+        assertFalse(resolvedConfig.hasError());
+        resolvedConfig.rethrowFailure();
+        assertThat(resolvedConfig.getFiles(Specs.<Dependency>satisfyAll()), isEmpty());
+        assertThat(resolvedConfig.getFirstLevelModuleDependencies(), isEmpty());
+        assertThat(resolvedConfig.getResolvedArtifacts(), isEmpty());
+    }
+
+    @Test
+    public void resolveDelegatesToBackingServiceWhenConfigHasDependencies() {
+        final ResolvedConfiguration resolvedConfigDummy = context.mock(ResolvedConfiguration.class);
+
+        context.checking(new Expectations() {{
+            allowing(configuration).getAllDependencies();
+            will(returnValue(dependencies));
+
+            allowing(dependencies).isEmpty();
+            will(returnValue(false));
+
+            one(delegate).resolve(configuration);
+            will(returnValue(resolvedConfigDummy));
+        }});
+
+        assertThat(dependencyResolver.resolve(configuration), sameInstance(resolvedConfigDummy));
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverTest.groovy
new file mode 100644
index 0000000..5d0e3b3
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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.ivyservice
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import spock.lang.Specification
+
+class VersionForcingDependencyToModuleResolverTest extends Specification {
+    final DependencyToModuleVersionIdResolver target = Mock()
+    final ModuleRevisionId forced = new ModuleRevisionId(new ModuleId('group', 'module'), 'forced')
+    final VersionForcingDependencyToModuleResolver resolver = new VersionForcingDependencyToModuleResolver(target, [new DefaultModuleVersionSelector('group', 'module', 'forced')])
+
+    def "passes through dependency when it does not match any forced group and module"() {
+        ModuleVersionIdResolveResult resolvedVersion = Mock()
+        def differentGroup = dependency('other', 'module')
+
+        when:
+        def result = resolver.resolve(differentGroup)
+
+        then:
+        result == resolvedVersion
+
+        and:
+        1 * target.resolve(differentGroup) >> resolvedVersion
+    }
+
+    def "replaces dependency when it matches a forced group and module"() {
+        ModuleVersionIdResolveResult resolvedVersion = Mock()
+        DependencyDescriptor modified = Mock()
+        def dep = dependency('group', 'module')
+
+        when:
+        def result = resolver.resolve(dep)
+
+        then:
+        result == resolvedVersion
+
+        and:
+        1 * dep.clone(forced) >> modified
+        1 * target.resolve(modified) >> resolvedVersion
+    }
+
+    def dependency(String group, String module) {
+        DependencyDescriptor descriptor = Mock()
+        descriptor.dependencyId >> new ModuleId(group, module)
+        return descriptor
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
new file mode 100644
index 0000000..f364677
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2007-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.ivyservice.clientmodule
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.resolve.ResolveData
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveResult
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+
+/**
+ * @author Hans Dockter
+ */
+class ClientModuleResolverTest extends Specification {
+    final ModuleDescriptor module = Mock()
+    final ResolveData resolveData = Mock()
+    final ModuleRevisionId moduleId = new ModuleRevisionId(new ModuleId("org", "name"), "1.0")
+    final DependencyToModuleResolver target = Mock()
+    final ModuleVersionResolveResult resolvedModule = Mock()
+    final ClientModuleResolver resolver = new ClientModuleResolver(target)
+
+    def "replaces meta-data for a client module dependency"() {
+        ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
+
+        when:
+        def resolveResult = resolver.resolve(dependencyDescriptor)
+
+        then:
+        1 * target.resolve(dependencyDescriptor) >> resolvedModule
+        _ * dependencyDescriptor.targetModule >> module
+
+        and:
+        resolveResult.descriptor == module
+        resolveResult.failure == null
+        resolveResult.id == module.moduleRevisionId
+    }
+
+    def "does not replace meta-data for unknown module version"() {
+        DependencyDescriptor dependencyDescriptor = Mock()
+        
+        when:
+        def resolveResult = resolver.resolve(dependencyDescriptor)
+
+        then:
+        1 * target.resolve(dependencyDescriptor) >> resolvedModule
+
+        and:
+        resolveResult == resolvedModule
+    }
+
+    def "does not replace meta-data for broken module version"() {
+        ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
+
+        given:
+        resolvedModule.failure >> new ModuleVersionResolveException("broken")
+
+        when:
+        def resolveResult = resolver.resolve(dependencyDescriptor)
+
+        then:
+        1 * target.resolve(dependencyDescriptor) >> resolvedModule
+
+        and:
+        resolveResult == resolvedModule
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
new file mode 100644
index 0000000..17adb69
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 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.ivyservice.ivyresolve
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy
+
+import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache
+import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ArtifactId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
+import org.gradle.util.TrueTimeProvider
+
+class CachingModuleVersionRepositoryTest extends Specification {
+
+    ModuleVersionRepository realRepo = Mock()
+    ModuleResolutionCache moduleResolutionCache = Mock()
+    ModuleDescriptorCache moduleDescriptorCache = Mock()
+    CachedExternalResourceIndex artifactAtRepositoryCache = Mock()
+    CachePolicy cachePolicy = Mock()
+
+    CachingModuleVersionRepository repo = new CachingModuleVersionRepository(realRepo, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCache, cachePolicy, new TrueTimeProvider())
+    
+    @Unroll "last modified date is cached - lastModified = #lastModified"(Date lastModified) {
+        given:
+        ExternalResourceMetaData externalResourceMetaData = new DefaultExternalResourceMetaData("remote url", lastModified, -1, null, null)
+        DownloadedArtifact downloadedArtifact = new DownloadedArtifact(new File("artifact"), externalResourceMetaData)
+        Artifact artifact = Mock()
+        ArtifactRevisionId id = arid()
+        ArtifactAtRepositoryKey atRepositoryKey = new ArtifactAtRepositoryKey(realRepo, id)
+        
+
+        and:
+        _ * realRepo.isLocal() >> false
+        _ * artifactAtRepositoryCache.lookup(atRepositoryKey) >> null
+        _ * realRepo.download(artifact) >> downloadedArtifact
+        _ * artifact.getId() >> id
+
+        when:
+        repo.download(artifact)
+        
+        then:
+        1 * artifactAtRepositoryCache.store(atRepositoryKey, downloadedArtifact.localFile, externalResourceMetaData)
+        
+        where:
+        lastModified << [new Date(), null]
+    }
+
+    ArtifactRevisionId arid(Map attrs = [:]) {
+        Map defaults = [
+                org: "org", name: "name", revision: "1.0",
+                type: "type", ext: "ext"
+        ]
+
+        attrs = defaults + attrs
+
+        ModuleId mid = new ModuleId(attrs.org, attrs.name)
+        new ArtifactRevisionId(
+                new ArtifactId(mid, mid.name, attrs.type, attrs.ext),
+                new ModuleRevisionId(mid, attrs.revision)
+        )
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
new file mode 100644
index 0000000..ad10db6
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve;
+
+
+import org.apache.ivy.plugins.resolver.DependencyResolver
+import spock.lang.Specification
+import org.apache.ivy.plugins.resolver.AbstractPatternsBasedResolver
+
+import org.gradle.api.internal.artifacts.repositories.ExternalResourceResolver
+
+public class DependencyResolverIdentifierTest extends Specification {
+    def "uses dependency resolver name"() {
+        when:
+        DependencyResolver resolver = Mock()
+        resolver.name >> "resolver-name"
+
+        then:
+        new DependencyResolverIdentifier(resolver).name == "resolver-name"
+    }
+
+    def "dependency resolvers of unknown type are identified by their name"() {
+        given:
+        DependencyResolver resolver1 = Mock()
+        DependencyResolver resolver1a = Mock()
+        DependencyResolver resolver2 = Mock()
+
+        when:
+        resolver1.name >> 'name1'
+        resolver1a.name >> 'name1'
+        resolver2.name >> 'name2'
+
+        then:
+        id(resolver1) == id(resolver1a)
+        id(resolver1) != id(resolver2)
+    }
+
+    def "dependency resolvers of type AbstractPatternBasedResolver are differentiated by their patterns"() {
+        given:
+        AbstractPatternsBasedResolver resolver1 = Mock()
+        AbstractPatternsBasedResolver resolver1a = Mock()
+        AbstractPatternsBasedResolver resolver2 = Mock()
+        AbstractPatternsBasedResolver resolver2a = Mock()
+
+        when:
+        resolver1.ivyPatterns >> ['ivy1', 'ivy2']
+        resolver1.artifactPatterns >> ['artifact1', 'artifact2']
+        resolver1a.ivyPatterns >> ['ivy1', 'ivy2']
+        resolver1a.artifactPatterns >> ['artifact1', 'artifact2']
+        resolver2.ivyPatterns >> ['ivy1', 'different']
+        resolver2.artifactPatterns >> ['artifact1', 'artifact2']
+        resolver2a.ivyPatterns >> ['ivy1', 'ivy2']
+        resolver2a.artifactPatterns >> ['artifact1', 'different']
+
+        then:
+        id(resolver1) == id(resolver1a)
+        id(resolver1) != id(resolver2)
+        id(resolver1) != id(resolver2a)
+        id(resolver2) != id(resolver2a)
+    }
+
+    def "dependency resolvers of type ResourceCollectionResolver are differentiated by their patterns"() {
+        given:
+        ExternalResourceResolver resolver1 = Mock()
+        ExternalResourceResolver resolver1a = Mock()
+        ExternalResourceResolver resolver2 = Mock()
+        ExternalResourceResolver resolver2a = Mock()
+
+        when:
+        resolver1.ivyPatterns >> ['ivy1', 'ivy2']
+        resolver1.artifactPatterns >> ['artifact1', 'artifact2']
+        resolver1a.ivyPatterns >> ['ivy1', 'ivy2']
+        resolver1a.artifactPatterns >> ['artifact1', 'artifact2']
+        resolver2.ivyPatterns >> ['ivy1', 'different']
+        resolver2.artifactPatterns >> ['artifact1', 'artifact2']
+        resolver2a.ivyPatterns >> ['ivy1', 'ivy2']
+        resolver2a.artifactPatterns >> ['artifact1', 'different']
+
+        then:
+        id(resolver1) == id(resolver1a)
+        id(resolver1) != id(resolver2)
+        id(resolver1) != id(resolver2a)
+        id(resolver2) != id(resolver2a)
+    }
+
+    def "dependency resolvers of type AbstractPatternBasedResolver are differentiated by m2compatible flag"() {
+        given:
+        AbstractPatternsBasedResolver resolver1 = Mock()
+        AbstractPatternsBasedResolver resolver2 = Mock()
+
+        when:
+        resolver1.ivyPatterns >> ['ivy1']
+        resolver1.artifactPatterns >> ['artifact1']
+        resolver1.m2compatible >> false
+        resolver2.ivyPatterns >> ['ivy1']
+        resolver2.artifactPatterns >> ['artifact1']
+        resolver2.m2compatible >> true
+
+        then:
+        id(resolver1) != id(resolver2)
+    }
+
+    def "dependency resolvers of type ResourceCollectionResolver are differentiated by m2compatible flag"() {
+        given:
+        ExternalResourceResolver resolver1 = Mock()
+        ExternalResourceResolver resolver2 = Mock()
+
+        when:
+        resolver1.ivyPatterns >> ['ivy1']
+        resolver1.artifactPatterns >> ['artifact1']
+        resolver2.ivyPatterns >> ['ivy1']
+        resolver2.artifactPatterns >> ['artifact1']
+        resolver2.m2compatible >> true
+
+        then:
+        id(resolver1) != id(resolver2)
+    }
+
+    def id(DependencyResolver resolver) {
+        return new DependencyResolverIdentifier(resolver).uniqueId
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
new file mode 100644
index 0000000..4fbbd89
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2012 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.ivyservice.ivyresolve
+
+import org.apache.ivy.core.module.descriptor.Artifact
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.plugins.version.VersionMatcher
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.ivyservice.*
+
+class LazyDependencyToModuleResolverTest extends Specification {
+    final DependencyToModuleResolver target = Mock()
+    final VersionMatcher matcher = Mock()
+    final ModuleVersionResolveResult resolvedModule = Mock()
+    final LazyDependencyToModuleResolver resolver = new LazyDependencyToModuleResolver(target, matcher)
+
+    def "does not resolve module for static version dependency until requested"() {
+        def dependency = dependency()
+
+        when:
+        def idResolveResult = resolver.resolve(dependency)
+
+        then:
+        idResolveResult.id == dependency.dependencyRevisionId
+
+        and:
+        0 * target._
+
+        when:
+        def moduleResolveResult = idResolveResult.resolve()
+
+        then:
+        1 * target.resolve(dependency) >> resolvedModule
+        1 * resolvedModule.descriptor >> module()
+        0 * target._
+    }
+
+    def "resolves module for dynamic version dependency immediately"() {
+        def dependency = dependency()
+        def module = module()
+
+        given:
+        matcher.isDynamic(_) >> true
+
+        when:
+        def idResolveResult = resolver.resolve(dependency)
+        def id = idResolveResult.id
+
+        then:
+        id == module.moduleRevisionId
+
+        and:
+        1 * target.resolve(dependency) >> resolvedModule
+        _ * resolvedModule.id >> module.moduleRevisionId
+        _ * resolvedModule.descriptor >> module
+        0 * target._
+
+        when:
+        def moduleResolveResult = idResolveResult.resolve()
+
+        then:
+        0 * target._
+    }
+
+    def "does not resolve module more than once"() {
+        def dependency = dependency()
+
+        when:
+        def idResolveResult = resolver.resolve(dependency)
+        idResolveResult.resolve()
+
+        then:
+        1 * target.resolve(dependency) >> resolvedModule
+        1 * resolvedModule.descriptor >> module()
+        0 * target._
+
+        when:
+        def moduleResolveResult = idResolveResult.resolve()
+
+        then:
+        0 * target._
+    }
+
+    def "collects failure to resolve module"() {
+        def dependency = dependency()
+        def failure = new ModuleVersionResolveException("broken")
+
+        when:
+        def idFailureResult = resolver.resolve(dependency)
+
+        then:
+        idFailureResult.failure == null;
+
+        and:
+        0 * target._
+
+        when:
+        def resolveResult = idFailureResult.resolve()
+
+        then:
+        resolveResult.failure.is(failure)
+
+        and:
+        1 * target.resolve(dependency) >> resolvedModule
+        _ * resolvedModule.failure >> failure
+        0 * target._
+
+        when:
+        resolveResult.descriptor
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e.is(resolveResult.failure)
+
+        and:
+        0 * target._
+    }
+
+    def "collects and wraps module not found"() {
+        def dependency = dependency()
+
+        when:
+        def resolveResult = resolver.resolve(dependency).resolve()
+
+        then:
+        resolveResult.failure instanceof ModuleVersionNotFoundException
+        resolveResult.failure.message == "Could not find group:group, module:module, version:1.0."
+
+        and:
+        1 * target.resolve(dependency) >> resolvedModule
+        _ * resolvedModule.failure >> new ModuleVersionNotFoundException("broken")
+    }
+
+    def "collects and wraps unexpected module resolve failure"() {
+        def dependency = dependency()
+        def failure = new RuntimeException("broken")
+
+        when:
+        def resolveResult = resolver.resolve(dependency).resolve()
+
+        then:
+        resolveResult.failure instanceof ModuleVersionResolveException
+        resolveResult.failure.message == "Could not resolve group:group, module:module, version:1.0."
+
+        and:
+        1 * target.resolve(dependency) >> { throw failure }
+    }
+
+    def "collects and wraps module not found for missing dynamic version"() {
+        def dependency = dependency()
+
+        given:
+        matcher.isDynamic(_) >> true
+
+        when:
+        def idResolveResult = resolver.resolve(dependency)
+
+        then:
+        idResolveResult.failure instanceof ModuleVersionNotFoundException
+        idResolveResult.failure.message == "Could not find any version that matches group:group, module:module, version:1.0."
+
+        and:
+        1 * target.resolve(dependency) >> resolvedModule
+        _ * resolvedModule.failure >> new ModuleVersionNotFoundException("missing")
+
+        when:
+        idResolveResult.id
+
+        then:
+        ModuleVersionNotFoundException e = thrown()
+        e.is(idResolveResult.failure)
+
+        and:
+        0 * target._
+
+        when:
+        def resolveResult = idResolveResult.resolve()
+        resolveResult.descriptor
+
+        then:
+        e = thrown()
+        e.is(idResolveResult.failure)
+    }
+
+    def "can resolve artifact for a module version"() {
+        def dependency = dependency()
+        def artifact = artifact()
+        ArtifactResolver targetResolver = Mock()
+        ArtifactResolveResult resolvedArtifact = Mock()
+
+        when:
+        def resolveResult = resolver.resolve(dependency).resolve()
+
+        then:
+        1 * target.resolve(dependency) >> resolvedModule
+        _ * resolvedModule.descriptor >> module()
+
+        when:
+        def artifactResult = resolveResult.artifactResolver.resolve(artifact)
+
+        then:
+        artifactResult == resolvedArtifact
+
+        and:
+        _ * resolvedModule.artifactResolver >> targetResolver
+        1 * targetResolver.resolve(artifact) >> resolvedArtifact
+    }
+    
+    def "wraps unexpected failure to resolve artifact"() {
+        def dependency = dependency()
+        def artifact = artifact()
+        ArtifactResolver targetResolver = Mock()
+        def failure = new RuntimeException("broken")
+
+        when:
+        def resolveResult = resolver.resolve(dependency).resolve()
+
+        then:
+        1 * target.resolve(dependency) >> resolvedModule
+        _ * resolvedModule.descriptor >> module()
+
+        when:
+        def artifactResult = resolveResult.artifactResolver.resolve(artifact)
+
+        then:
+        artifactResult.failure instanceof ArtifactResolveException
+        artifactResult.failure.message == "Could not download artifact 'group:module:1.0 at zip'"
+        artifactResult.failure.cause == failure
+
+        and:
+        _ * resolvedModule.artifactResolver >> targetResolver
+        _ * targetResolver.resolve(artifact) >> { throw failure }
+
+        when:
+        artifactResult.file
+
+        then:
+        ArtifactResolveException e = thrown()
+        e.is(artifactResult.failure)
+    }
+
+    def module() {
+        ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
+        return new DefaultModuleDescriptor(id, "release", new Date())
+    }
+
+    def dependency() {
+        DependencyDescriptor descriptor = Mock()
+        ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
+        _ * descriptor.dependencyRevisionId >> id
+        return descriptor
+    }
+
+    def artifact() {
+        Artifact artifact = Mock()
+        ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
+        _ * artifact.moduleRevisionId >> id
+        _ * artifact.name >> 'artifact'
+        _ * artifact.ext >> 'zip'
+        return artifact
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParserTest.groovy
new file mode 100644
index 0000000..2cce972
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParserTest.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2011 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.ivyservice.ivyresolve.parser
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.plugins.parser.ParserSettings
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Issue
+
+class PomParserTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    
+    final ParserSettings ivySettings = Mock()
+    TestFile pomFile
+
+    def "setup"() {
+        pomFile = tmpDir.file('foo')
+    }
+
+    def "parses simple pom"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <description>The first test artifact</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    def "pom with dependency with classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+            <classifier>classifier-two</classifier>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDependencyArtifact(descriptor.dependencies.first(), 'artifact-two', 'jar', 'jar', 'classifier-two')
+    }
+
+    @Issue("GRADLE-2068")
+    def "pom with dependency with empty classifier is treated like dependency without classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+            <classifier></classifier>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    @Issue("GRADLE-2076")
+    def "pom with packaging of type eclipse-plugin creates jar artifact"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <packaging>eclipse-plugin</packaging>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'eclipse-plugin', 'jar')
+        descriptor.dependencies.length == 0
+    }
+
+    private ModuleDescriptor parsePom() {
+        GradlePomModuleDescriptorParser.getInstance().parseDescriptor(ivySettings, pomFile.toURI().toURL(), false)
+    }
+
+    private void hasArtifact(ModuleDescriptor descriptor, String name, String type, String ext, String classifier = null) {
+        descriptor.allArtifacts.length == 1
+        def artifact = descriptor.allArtifacts.first()
+        assert artifact.id == artifactId(descriptor.moduleRevisionId, name, type, ext)
+        assert artifact.extraAttributes['classifier'] == classifier
+    }
+    
+    private void hasDefaultDependencyArtifact(DependencyDescriptor descriptor) {
+        descriptor.allDependencyArtifacts.length == 0
+    }
+
+    private void hasDependencyArtifact(DependencyDescriptor descriptor, String name, String type, String ext, String classifier = null) {
+        descriptor.allDependencyArtifacts.length == 1
+        def artifact = descriptor.allDependencyArtifacts.first()
+        assert artifact.name == name
+        assert artifact.type == type
+        assert artifact.ext == ext
+        assert artifact.extraAttributes['classifier'] == classifier
+    }
+
+    private ModuleRevisionId moduleId(String group, String name, String version) {
+        ModuleRevisionId.newInstance(group, name, version)
+    }
+
+    private ArtifactRevisionId artifactId(ModuleRevisionId moduleId, String name, String type, String ext) {
+        ArtifactRevisionId.newInstance(moduleId, name, type, ext)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..d97f70f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.PublishArtifactSet;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultArtifactsToModuleDescriptorConverterTest {
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+
+    @Test
+    public void testAddArtifacts() {
+        final PublishArtifact publishArtifactConf1 = createNamedPublishArtifact("conf1");
+        Configuration configurationStub1 = createConfigurationStub(publishArtifactConf1);
+        final PublishArtifact publishArtifactConf2 = createNamedPublishArtifact("conf2");
+        Configuration configurationStub2 = createConfigurationStub(publishArtifactConf2);
+        final ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategyMock = context.mock(ArtifactsExtraAttributesStrategy.class);
+        final Map<String, String> extraAttributesArtifact1 = WrapUtil.toMap("name", publishArtifactConf1.getName());
+        final Map<String, String> extraAttributesArtifact2 = WrapUtil.toMap("name", publishArtifactConf2.getName());
+        context.checking(new Expectations() {{
+            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf1);
+            will(returnValue(extraAttributesArtifact1));
+            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf2);
+            will(returnValue(extraAttributesArtifact2));
+        }});
+        DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(configurationStub1.getName(),
+                configurationStub2.getName()));
+
+        DefaultArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter =
+                new DefaultArtifactsToModuleDescriptorConverter(artifactsExtraAttributesStrategyMock);
+
+        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, WrapUtil.toSet(configurationStub1, configurationStub2));
+
+        assertArtifactIsAdded(configurationStub1, moduleDescriptor, extraAttributesArtifact1);
+        assertArtifactIsAdded(configurationStub2, moduleDescriptor, extraAttributesArtifact2);
+        assertThat(moduleDescriptor.getAllArtifacts().length, equalTo(2));
+    }
+
+    @Test
+    public void testIvyFileStrategy() {
+        assertThat(
+                DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY.createExtraAttributes(context.mock(PublishArtifact.class)),
+                equalTo((Map) new HashMap<String, String>()));
+    }
+
+    @Test
+    public void testResolveStrategy() {
+        PublishArtifact publishArtifact = createNamedPublishArtifact("someName");
+        Map<String, String> expectedExtraAttributes = WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
+        assertThat(
+                DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY.createExtraAttributes(publishArtifact),
+                equalTo(expectedExtraAttributes));
+    }
+
+    private void assertArtifactIsAdded(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, Map<String, String> extraAttributes) {
+        assertThat(moduleDescriptor.getArtifacts(configuration.getName()),
+                equalTo(WrapUtil.toArray(expectedIvyArtifact(configuration, moduleDescriptor, extraAttributes))));
+    }
+
+    private Artifact expectedIvyArtifact(Configuration configuration, ModuleDescriptor moduleDescriptor, Map<String, String> additionalExtraAttributes) {
+        PublishArtifact publishArtifact = configuration.getArtifacts().iterator().next();
+        Map<String, String> extraAttributes = WrapUtil.toMap(Dependency.CLASSIFIER, publishArtifact.getClassifier());
+        extraAttributes.putAll(additionalExtraAttributes);
+        return new DefaultArtifact(moduleDescriptor.getModuleRevisionId(),
+                publishArtifact.getDate(),
+                publishArtifact.getName(),
+                publishArtifact.getType(),
+                publishArtifact.getExtension(),
+                extraAttributes);
+    }
+
+    private Configuration createConfigurationStub(final PublishArtifact publishArtifact) {
+        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(publishArtifact.getName(), context);
+        final PublishArtifactSet artifacts = context.mock(PublishArtifactSet.class);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getArtifacts();
+            will(returnValue(artifacts));
+            allowing(artifacts).iterator();
+            will(returnIterator(publishArtifact));
+        }});
+        return configurationStub;
+    }
+
+    private PublishArtifact createNamedPublishArtifact(String name) {
+        return new DefaultPublishArtifact(name, "ext", "type", "classifier", new Date(), new File("somePath"));
+    }
+
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
new file mode 100644
index 0000000..6af0160
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverterTest {
+
+    @Test
+    public void testCreateExcludeRule() {
+        String configurationName = "someConf";
+        final String someOrg = "someOrg";
+        final String someModule = "someModule";
+        org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule =
+                new DefaultExcludeRuleConverter().createExcludeRule(configurationName, new DefaultExcludeRule(someOrg, someModule));
+        assertThat(ivyExcludeRule.getId().getModuleId().getOrganisation(),
+                Matchers.equalTo(someOrg));
+        assertThat(ivyExcludeRule.getId().getName(),
+                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
+        assertThat(ivyExcludeRule.getId().getExt(),
+                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
+        assertThat(ivyExcludeRule.getId().getType(),
+                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
+        assertThat((ExactPatternMatcher) ivyExcludeRule.getMatcher(),
+                Matchers.equalTo(ExactPatternMatcher.INSTANCE));
+        assertThat(ivyExcludeRule.getConfigurations(),
+                Matchers.equalTo(WrapUtil.toArray(configurationName)));
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy
new file mode 100644
index 0000000..808f5cc
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2007-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.ivyservice.moduleconverter;
+
+
+import org.apache.ivy.Ivy
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.status.StatusManager
+import org.apache.ivy.core.settings.IvySettings
+import org.gradle.api.artifacts.Module
+import org.gradle.api.internal.artifacts.DefaultModule
+import org.gradle.api.internal.artifacts.ivyservice.IvyFactory
+import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactoryTest extends Specification {
+    final IvyFactory ivyFactory = Mock()
+    final SettingsConverter settingsConverter = Mock()
+    final DefaultModuleDescriptorFactory factory = new DefaultModuleDescriptorFactory(ivyFactory, settingsConverter)
+
+    public void testCreateModuleDescriptor() {
+        given:
+        IvySettings ivySettings = Mock()
+        Ivy ivy = Mock()
+        StatusManager statusManager = Mock()
+        Module module = new DefaultModule("org", "name", "version", "status");
+
+        when:
+        DefaultModuleDescriptor moduleDescriptor = factory.createModuleDescriptor(module);
+
+        then:
+        1 * settingsConverter.getForResolve() >> ivySettings
+        1 * ivyFactory.createIvy(ivySettings) >> ivy
+        _ * ivy.settings >> ivySettings
+        1 * ivySettings.statusManager >> statusManager
+        1 * statusManager.defaultStatus >> "default status"
+        1 * ivySettings.getDefaultBranch(ModuleId.newInstance("org", "name")) >> "default branch"
+        0 * _._
+        
+        and:
+        moduleDescriptor.moduleRevisionId.organisation == module.group;
+        moduleDescriptor.moduleRevisionId.name == module.name;
+        moduleDescriptor.moduleRevisionId.revision == module.version;
+        moduleDescriptor.status == module.getStatus();
+        moduleDescriptor.publicationDate == null;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.groovy
new file mode 100644
index 0000000..1c0e2ff
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Module
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter, Szczepan
+ */
+public class PublishModuleDescriptorConverterTest extends Specification {
+
+    def "converts"() {
+        given:
+        def configurationsDummy = [Mock(Configuration)] as Set
+        def moduleDummy = Mock(Module)
+        def moduleDescriptorDummy = Mock(DefaultModuleDescriptor)
+        def artifactsToModuleDescriptorConverter = Mock(ArtifactsToModuleDescriptorConverter)
+        def resolveModuleDescriptorConverter = Mock(ModuleDescriptorConverter)
+
+        def publishModuleDescriptorConverter = new PublishModuleDescriptorConverter(
+                resolveModuleDescriptorConverter,
+                artifactsToModuleDescriptorConverter);
+
+        resolveModuleDescriptorConverter.convert(configurationsDummy, moduleDummy) >> moduleDescriptorDummy
+
+        when:
+        def actualModuleDescriptor = publishModuleDescriptorConverter.convert(configurationsDummy, moduleDummy);
+
+        then:
+        1 * moduleDescriptorDummy.addExtraAttributeNamespace(PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE_PREFIX,
+                    PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE);
+        1 * artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptorDummy, configurationsDummy)
+        actualModuleDescriptor == moduleDescriptorDummy
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
new file mode 100644
index 0000000..e1dd1c9
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Module
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter, Szczepan
+ */
+public class ResolveModuleDescriptorConverterTest extends Specification {
+
+    def "converts"() {
+        given:
+        def configurations = [Mock(Configuration), Mock(Configuration)] as Set
+        def module = Mock(Module)
+        def moduleDescriptor = Mock(DefaultModuleDescriptor)
+        def moduleDescriptorFactory = Mock(ModuleDescriptorFactory)
+        def configurationsConverter = Mock(ConfigurationsToModuleDescriptorConverter)
+        def dependenciesConverter = Mock(DependenciesToModuleDescriptorConverter)
+
+        ResolveModuleDescriptorConverter resolveModuleDescriptorConverter = new ResolveModuleDescriptorConverter(
+                moduleDescriptorFactory,
+                configurationsConverter,
+                dependenciesConverter);
+
+        moduleDescriptorFactory.createModuleDescriptor(module) >> moduleDescriptor
+
+        when:
+        def actualDescriptor = resolveModuleDescriptorConverter.convert(configurations, module);
+
+        then:
+        1 * configurationsConverter.addConfigurations(moduleDescriptor, configurations)
+        1 * dependenciesConverter.addDependencyDescriptors(moduleDescriptor, configurations)
+
+        actualDescriptor == moduleDescriptor
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
new file mode 100644
index 0000000..29c16cb
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.util.HelperUtil;
+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.runner.RunWith;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public abstract class AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    protected static final String TEST_CONF = "conf";
+    protected static final String TEST_DEP_CONF = "depconf1";
+
+    protected static final ExcludeRule TEST_EXCLUDE_RULE = new org.gradle.api.internal.artifacts.DefaultExcludeRule("testOrg", null);
+    protected static final org.apache.ivy.core.module.descriptor.ExcludeRule TEST_IVY_EXCLUDE_RULE = HelperUtil.getTestExcludeRule("testOrg");
+    protected ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
+    protected final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(TEST_CONF));
+    private DefaultDependencyArtifact artifact = new DefaultDependencyArtifact("name", "type", null, null, null);
+    private DefaultDependencyArtifact artifactWithClassifiers = new DefaultDependencyArtifact("name2", "type2", "ext2", "classifier2", "http://www.url2.com");
+
+    @Before
+    public void setUp() {
+        expectExcludeRuleConversion(TEST_EXCLUDE_RULE, TEST_IVY_EXCLUDE_RULE);
+    }
+
+    protected void expectExcludeRuleConversion(final ExcludeRule excludeRule, final org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule) {
+        context.checking(new Expectations() {{
+            allowing(excludeRuleConverterStub).createExcludeRule(TEST_CONF, excludeRule);
+            will(returnValue(ivyExcludeRule));
+        }});
+    }
+
+    protected Dependency setUpDependency(ModuleDependency dependency) {
+        return dependency.addArtifact(artifact).
+                addArtifact(artifactWithClassifiers).
+                exclude(WrapUtil.toMap("group", TEST_EXCLUDE_RULE.getGroup())).
+                setTransitive(true);
+    }
+
+    protected void assertDependencyDescriptorHasCommonFixtureValues(DefaultDependencyDescriptor dependencyDescriptor) {
+        assertThat(dependencyDescriptor.getParentRevisionId(), equalTo(moduleDescriptor.getModuleRevisionId()));
+        assertEquals(TEST_IVY_EXCLUDE_RULE, dependencyDescriptor.getExcludeRules(TEST_CONF)[0]);
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), equalTo(WrapUtil.toArray(TEST_DEP_CONF)));
+        assertThat(dependencyDescriptor.isTransitive(), equalTo(true));
+        assertDependencyDescriptorHasArtifacts(dependencyDescriptor);
+    }
+
+    private void assertDependencyDescriptorHasArtifacts(DefaultDependencyDescriptor dependencyDescriptor) {
+        List<DependencyArtifactDescriptor> artifactDescriptors = WrapUtil.toList(dependencyDescriptor.getDependencyArtifacts(TEST_CONF));
+        assertThat(artifactDescriptors.size(), equalTo(2));
+
+        
+        DependencyArtifactDescriptor artifactDescriptorWithoutClassifier = findDescriptor(artifactDescriptors, artifact);
+        assertEquals(new HashMap(), artifactDescriptorWithoutClassifier.getExtraAttributes());
+        assertEquals(null, artifactDescriptorWithoutClassifier.getUrl());
+        compareArtifacts(artifact, artifactDescriptorWithoutClassifier);
+        assertEquals(artifact.getType(), artifactDescriptorWithoutClassifier.getExt());
+
+        DependencyArtifactDescriptor artifactDescriptorWithClassifierAndConfs = findDescriptor(artifactDescriptors, artifactWithClassifiers);
+        assertEquals(WrapUtil.toMap(Dependency.CLASSIFIER, artifactWithClassifiers.getClassifier()), artifactDescriptorWithClassifierAndConfs.getQualifiedExtraAttributes());
+        compareArtifacts(artifactWithClassifiers, artifactDescriptorWithClassifierAndConfs);
+        assertEquals(artifactWithClassifiers.getExtension(), artifactDescriptorWithClassifierAndConfs.getExt());
+        try {
+            assertEquals(new URL(artifactWithClassifiers.getUrl()), artifactDescriptorWithClassifierAndConfs.getUrl());
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private DependencyArtifactDescriptor findDescriptor(List<DependencyArtifactDescriptor> artifactDescriptors, DefaultDependencyArtifact dependencyArtifact) {
+        for (DependencyArtifactDescriptor artifactDescriptor : artifactDescriptors) {
+            if (artifactDescriptor.getName().equals(dependencyArtifact.getName())) {
+                return artifactDescriptor;
+            }
+        }
+        throw new RuntimeException("Descriptor could not be found");
+    }
+
+    private void compareArtifacts(DependencyArtifact artifact, DependencyArtifactDescriptor artifactDescriptor) {
+        assertEquals(artifact.getName(), artifactDescriptor.getName());
+        assertEquals(artifact.getType(), artifactDescriptor.getType());
+    }
+}
+
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..a7730d6
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule = context.mock(ModuleDescriptorFactoryForClientModule.class);
+    private ClientModuleDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleDependencyDescriptorFactory(
+            excludeRuleConverterStub,
+            moduleDescriptorFactoryForClientModule
+    );
+
+    @Test
+    public void canConvert() {
+        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
+        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ClientModule.class)), Matchers.equalTo(true));
+    }
+
+    @Test
+    public void testAddDependencyDescriptorForClientModule() {
+        final ModuleDependency dependencyDependency = context.mock(ModuleDependency.class, "dependencyDependency");
+        final DefaultClientModule clientModule = new DefaultClientModule("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
+        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(clientModule);
+
+        setUpDependency(clientModule);
+        clientModule.addDependency(dependencyDependency);
+        final ModuleDescriptor moduleDescriptorForClientModule = context.mock(ModuleDescriptor.class);
+        context.checking(new Expectations() {{
+            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
+                    testModuleRevisionId,
+                    WrapUtil.toSet(dependencyDependency)
+            );
+            will(returnValue(moduleDescriptorForClientModule));
+        }});
+
+        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertEquals(testModuleRevisionId,
+                dependencyDescriptor.getDependencyRevisionId());
+        assertFalse(dependencyDescriptor.isChanging());
+    }
+
+    @Test
+    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
+        final ClientModule clientModule = new DefaultClientModule(null, "gradle-core", null, TEST_DEP_CONF);
+        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(clientModule);
+        final ModuleDescriptor moduleDescriptorForClientModule = context.mock(ModuleDescriptor.class);
+        context.checking(new Expectations() {{
+            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
+                    testModuleRevisionId,
+                    WrapUtil.<ModuleDependency>toSet()
+            );
+            will(returnValue(moduleDescriptorForClientModule));
+        }});
+
+        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(testModuleRevisionId));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..0c2406c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.IvyConverterTestUtil;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultDependenciesToModuleDescriptorConverterTest {
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+
+    private static final ExcludeRule GRADLE_EXCLUDE_RULE_DUMMY_1 = new DefaultExcludeRule("testOrg", null);
+    private static final ExcludeRule GRADLE_EXCLUDE_RULE_DUMMY_2 = new DefaultExcludeRule(null, "testModule");
+
+    private ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
+    private ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
+    private ModuleDependency similarDependency1 = HelperUtil.createDependency("group", "name", "version");
+    private ModuleDependency similarDependency2 = HelperUtil.createDependency("group", "name", "version");
+    private ModuleDependency similarDependency3 = HelperUtil.createDependency("group", "name", "version");
+    private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub1 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule1");
+    private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub2 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule2");
+
+    private DependencyDescriptorFactory dependencyDescriptorFactoryStub = context.mock(DependencyDescriptorFactory.class);
+    private ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
+
+    @Test
+    public void testAddDependencyDescriptors() {
+        DefaultDependenciesToModuleDescriptorConverter converter = new DefaultDependenciesToModuleDescriptorConverter(
+                dependencyDescriptorFactoryStub, excludeRuleConverterStub);
+        Configuration configurationStub1 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf1", GRADLE_EXCLUDE_RULE_DUMMY_1, dependencyDummy1, similarDependency1);
+        Configuration configurationStub2 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf2", GRADLE_EXCLUDE_RULE_DUMMY_2, dependencyDummy2, similarDependency2);
+        Configuration configurationStub3 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf3", null, similarDependency3);
+        final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(toSet(configurationStub1.getName(),
+                configurationStub2.getName()));
+        associateDependencyWithDescriptor(dependencyDummy1, moduleDescriptor, configurationStub1);
+        associateDependencyWithDescriptor(dependencyDummy2, moduleDescriptor, configurationStub2);
+        associateDependencyWithDescriptor(similarDependency1, moduleDescriptor, configurationStub1);
+        associateDependencyWithDescriptor(similarDependency2, moduleDescriptor, configurationStub2);
+        associateDependencyWithDescriptor(similarDependency3, moduleDescriptor, configurationStub3);
+        associateGradleExcludeRuleWithIvyExcludeRule(GRADLE_EXCLUDE_RULE_DUMMY_1, ivyExcludeRuleStub1, configurationStub1);
+        associateGradleExcludeRuleWithIvyExcludeRule(GRADLE_EXCLUDE_RULE_DUMMY_2, ivyExcludeRuleStub2, configurationStub2);
+
+        converter.addDependencyDescriptors(moduleDescriptor, toSet(configurationStub1, configurationStub2, configurationStub3));
+
+        assertThat(moduleDescriptor.getExcludeRules(toArray(configurationStub1.getName())), equalTo(toArray(
+                ivyExcludeRuleStub1)));
+        assertThat(moduleDescriptor.getExcludeRules(toArray(configurationStub2.getName())), equalTo(toArray(
+                ivyExcludeRuleStub2)));
+
+    }
+
+    private void associateGradleExcludeRuleWithIvyExcludeRule(final ExcludeRule gradleExcludeRule,
+                                                              final org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule,
+                                                              final Configuration configuration) {
+        final String expectedConfigurationName = configuration.getName();
+        context.checking(new Expectations() {{
+            allowing(excludeRuleConverterStub).createExcludeRule(expectedConfigurationName, gradleExcludeRule);
+            will(returnValue(ivyExcludeRule));
+
+            allowing(ivyExcludeRule).getConfigurations();
+            will(returnValue(WrapUtil.toArray(configuration.getName())));
+        }});
+    }
+
+    private void associateDependencyWithDescriptor(final ModuleDependency dependency, final DefaultModuleDescriptor parent,
+                                                   final Configuration configuration) {
+        context.checking(new Expectations() {{
+            allowing(dependencyDescriptorFactoryStub).addDependencyDescriptor(with(sameInstance(configuration)),
+                    with(equal(parent)), with(sameInstance(dependency)));
+        }});
+    }
+    
+    private Configuration createNamedConfigurationStubWithDependenciesAndExcludeRules(final String name, final ExcludeRule excludeRule,
+                                                                                      final ModuleDependency... dependencies) {
+        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(name, context);
+        final DependencySet dependencySet = context.mock(DependencySet.class);
+
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getDependencies();
+            will(returnValue(dependencySet));
+
+            allowing(dependencySet).withType(ModuleDependency.class);
+            will(returnValue(toDomainObjectSet(ModuleDependency.class, dependencies)));
+
+            allowing(configurationStub).getExcludeRules();
+            will(returnValue(excludeRule == null ? Collections.emptySet() : toSet(excludeRule)));
+        }});
+        return configurationStub;
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
new file mode 100644
index 0000000..14ec924
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.util.WrapUtil;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactoryForClientModuleTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testCreateModuleDescriptor() {
+        DependencyDescriptor dependencyDescriptorDummy = context.mock(DependencyDescriptor.class);
+        DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy = new DependencyDescriptorFactorySpy(dependencyDescriptorDummy);
+        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory =
+                new DefaultModuleDescriptorFactoryForClientModule();
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactorySpy);
+        ModuleDependency dependencyMock = context.mock(ModuleDependency.class);
+        final ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance("org", "name", "version");
+
+        ModuleDescriptor moduleDescriptor = clientModuleDescriptorFactory.createModuleDescriptor(
+                moduleRevisionId,
+                WrapUtil.toSet(dependencyMock));
+
+        assertThat(moduleDescriptor.getModuleRevisionId(), equalTo(moduleRevisionId));
+        assertThatDescriptorHasOnlyDefaultConfiguration(moduleDescriptor);
+        assertCorrectCallToDependencyDescriptorFactory(dependencyDescriptorFactorySpy, Dependency.DEFAULT_CONFIGURATION, moduleDescriptor, dependencyMock);
+    }
+
+    private void assertThatDescriptorHasOnlyDefaultConfiguration(ModuleDescriptor moduleDescriptor) {
+        assertThat(moduleDescriptor.getConfigurations().length, equalTo(1));
+        assertThat(moduleDescriptor.getConfigurationsNames()[0], equalTo(Dependency.DEFAULT_CONFIGURATION));
+    }
+
+    private void assertCorrectCallToDependencyDescriptorFactory(DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy,
+                                                                String configuration,
+                                                                ModuleDescriptor parent,
+                                                                Dependency dependency) {
+        assertThat(dependencyDescriptorFactorySpy.configuration, equalTo(configuration));
+        assertThat(dependencyDescriptorFactorySpy.parent, equalTo(parent));
+        assertThat(dependencyDescriptorFactorySpy.dependency, equalTo(dependency));
+    }
+
+    private static class DependencyDescriptorFactorySpy implements DependencyDescriptorFactory {
+        DependencyDescriptor dependencyDescriptor;
+
+        String configuration;
+        ModuleDescriptor parent;
+        Dependency dependency;
+
+        private DependencyDescriptorFactorySpy(DependencyDescriptor dependencyDescriptor) {
+            this.dependencyDescriptor = dependencyDescriptor;
+        }
+
+        public DefaultDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                            ModuleDependency dependency) {
+            this.configuration = configuration;
+            this.parent = moduleDescriptor;
+            this.dependency = dependency;
+            return null;
+        }
+
+        public DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+            this.configuration = configuration.getName();
+            this.parent = moduleDescriptor;
+            this.dependency = dependency;
+            return null;
+        }
+
+        public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+            // do nothing
+            return null;
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
rename to subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..c71bf50
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.hamcrest.Matchers;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    ExternalModuleDependencyDescriptorFactory externalModuleDependencyDescriptorFactory =
+            new ExternalModuleDependencyDescriptorFactory(excludeRuleConverterStub);
+    
+    @Test
+    public void canConvert() {
+        assertThat(externalModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
+        assertThat(externalModuleDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), Matchers.equalTo(true));
+    }
+
+    @Test
+    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
+        ModuleDependency dependency = new DefaultExternalModuleDependency(null, "gradle-core", null, TEST_DEP_CONF);
+        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(IvyUtil.createModuleRevisionId(dependency)));
+    }
+
+    @Test
+    public void testCreateFromModuleDependency() {
+        DefaultExternalModuleDependency moduleDependency = new DefaultExternalModuleDependency("org.gradle",
+                "gradle-core", "1.0", TEST_DEP_CONF);
+        setUpDependency(moduleDependency);
+
+        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor,
+                moduleDependency);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor
+                .getDependencies()[0];
+
+        assertEquals(moduleDependency.isChanging(), dependencyDescriptor.isChanging());
+        assertEquals(dependencyDescriptor.isForce(), moduleDependency.isForce());
+        assertEquals(IvyUtil.createModuleRevisionId(moduleDependency), dependencyDescriptor.getDependencyRevisionId());
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..d443f3a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.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.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.util.HelperUtil;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ProjectDependencyDescriptorFactory projectDependencyDescriptorFactory =
+            new ProjectDependencyDescriptorFactory(excludeRuleConverterStub);
+
+    @Test
+    public void canConvert() {
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
+    }
+
+    @Test
+    public void testCreateFromProjectDependency() {
+        ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
+        setUpDependency(projectDependency);
+        projectDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, projectDependency);
+        ProjectDependencyDescriptor dependencyDescriptor = (ProjectDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertFalse(dependencyDescriptor.isChanging());
+        assertFalse(dependencyDescriptor.isForce());
+        assertEquals(ModuleRevisionId.newInstance("someGroup", "test", "someVersion"), dependencyDescriptor.getDependencyRevisionId());
+        assertSame(projectDependency.getDependencyProject(), dependencyDescriptor.getTargetProject());
+    }
+
+    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
+        AbstractProject dependencyProject = HelperUtil.createRootProject();
+        dependencyProject.setGroup("someGroup");
+        dependencyProject.setVersion("someVersion");
+        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, new ProjectDependenciesBuildInstruction(true));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
new file mode 100644
index 0000000..a27ac73
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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.ivyservice.projectmodule
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveResult
+import spock.lang.Specification
+
+class ProjectDependencyResolverTest extends Specification {
+    final ProjectModuleRegistry registry = Mock()
+    final DependencyDescriptor dependencyDescriptor = Mock()
+    final DependencyToModuleResolver target = Mock()
+    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, target)
+    
+    def "resolves project dependency"() {
+        ModuleDescriptor moduleDescriptor = Mock()
+
+        when:
+        def moduleResolver = resolver.resolve(dependencyDescriptor)
+
+        then:
+        moduleResolver.descriptor == moduleDescriptor
+
+        and:
+        1 * registry.findProject(dependencyDescriptor) >> moduleDescriptor
+    }
+
+    def "delegates to backing resolver for non-project dependency"() {
+        ModuleVersionResolveResult resolvedModule = Mock()
+
+        when:
+        def moduleResolver = resolver.resolve(dependencyDescriptor)
+
+        then:
+        moduleResolver == resolvedModule
+
+        and:
+        1 * registry.findProject(dependencyDescriptor) >> null
+        1 * target.resolve(dependencyDescriptor) >> resolvedModule
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
new file mode 100644
index 0000000..161661e
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
@@ -0,0 +1,893 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine
+
+import org.apache.ivy.core.module.id.ArtifactId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.resolve.ResolveData
+import org.apache.ivy.core.resolve.ResolveEngine
+import org.apache.ivy.core.resolve.ResolveOptions
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.apache.ivy.plugins.version.VersionMatcher
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor
+import org.gradle.api.specs.Spec
+import spock.lang.Specification
+import org.apache.ivy.core.module.descriptor.*
+import org.gradle.api.artifacts.*
+import org.gradle.api.internal.artifacts.ivyservice.*
+
+class DependencyGraphBuilderTest extends Specification {
+    final ModuleDescriptorConverter moduleDescriptorConverter = Mock()
+    final ResolvedArtifactFactory resolvedArtifactFactory = Mock()
+    final ConfigurationInternal configuration = Mock()
+    final ResolveEngine resolveEngine = Mock()
+    final ResolveData resolveData = new ResolveData(resolveEngine, new ResolveOptions())
+    final ModuleConflictResolver conflictResolver = Mock()
+    final DependencyToModuleVersionIdResolver dependencyResolver = Mock()
+    final VersionMatcher versionMatcher = Mock()
+    final DefaultModuleDescriptor root = revision('root')
+    final DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, dependencyResolver, conflictResolver)
+
+    def setup() {
+        config(root, 'root', 'default')
+        _ * configuration.name >> 'root'
+        _ * moduleDescriptorConverter.convert(_, _) >> root
+        _ * resolvedArtifactFactory.create(_, _, _) >> { owner, artifact, resolver ->
+            return new DefaultResolvedArtifact(owner, artifact, null)
+        }
+    }
+
+    def "does not resolve a given module selector more than once"() {
+        given:
+        def a = revision("a")
+        def b = revision("b")
+        def c = revision("c")
+        traverses root, a
+        traverses root, b
+        traverses a, c
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, c)
+    }
+
+    def "does not resolve a given dynamic module selector more than once"() {
+        given:
+        def a = revision("a")
+        def b = revision("b")
+        def c = revision("c")
+        def d = revision("d")
+        traverses root, a
+        traverses root, b
+        traverses root, c
+        traverses a, d, revision: 'latest'
+        doesNotResolve b, d, revision: 'latest'
+        doesNotResolve c, d
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, c, d)
+    }
+
+    def "does not include evicted module when selected module already traversed before conflict detected"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, selected
+        traverses selected, c
+        traverses root, b
+        traverses b, d
+        doesNotResolve d, evicted // Conflict is deeper than all dependencies of selected module
+        doesNotResolve evicted, e
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            assert candidates*.revision == ['1.2', '1.1']
+            return candidates.find { it.revision == '1.2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(selected, b, c, d)
+    }
+
+    def "does not include evicted module when evicted module already traversed before conflict detected"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, evicted
+        traverses evicted, c
+        traverses root, b
+        traverses b, d
+        traverses d, selected // Conflict is deeper than all dependencies of other module
+        traverses selected, e
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            assert candidates*.revision == ['1.1', '1.2']
+            return candidates.find { it.revision == '1.2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(selected, b, d, e)
+    }
+
+    def "does not include evicted module when path through evicted module is queued for traversal when conflict detected"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, evicted
+        traverses evicted, c
+        doesNotResolve c, d
+        traverses root, b
+        traverses b, selected
+        traverses selected, e
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            assert candidates*.revision == ['1.1', '1.2']
+            return candidates.find { it.revision == '1.2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(selected, b, e)
+    }
+
+    def "resolves when path through selected module is queued for traversal when conflict detected"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, selected
+        traverses selected, b
+        doesNotResolve root, evicted
+        doesNotResolve evicted, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            assert candidates*.revision == ['1.2', '1.1']
+            return candidates.find { it.revision == '1.2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(selected, b)
+    }
+
+    def "does not include evicted module when another path through evicted module traversed after conflict detected"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        traverses root, evicted
+        doesNotResolve evicted, d
+        traverses root, selected
+        traverses selected, c
+        traverses root, b
+        doesNotResolve b, evicted
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            assert candidates*.revision == ['1.1', '1.2']
+            return candidates.find { it.revision == '1.2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(selected, b, c)
+    }
+
+    def "restarts conflict resolution when later conflict on same module discovered"() {
+        given:
+        def selectedA = revision('a', '1.2')
+        def evictedA1 = revision('a', '1.1')
+        def evictedA2 = revision('a', '1.0')
+        def selectedB = revision('b', '2.2')
+        def evictedB = revision('b', '2.1')
+        def c = revision('c')
+        traverses root, evictedA1
+        traverses root, selectedA
+        traverses selectedA, c
+        traverses root, evictedB
+        traverses root, selectedB
+        doesNotResolve selectedB, evictedA2
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select({it*.revision == ['1.1', '1.2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+        1 * conflictResolver.select({it*.revision == ['2.1', '2.2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '2.2' }
+        }
+        1 * conflictResolver.select({it*.revision == ['1.1', '1.2', '1.0']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(selectedA, c, selectedB)
+    }
+
+    def "does not include module version that is excluded after conflict resolution has been applied"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def evicted = revision('c', '1')
+        def selected = revision('c', '2')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, evicted
+        traverses root, a, exclude: b
+        doesNotResolve evicted, a
+        traverses a, b
+        traverses root, d
+        traverses d, e
+        traverses e, selected // conflict is deeper than 'b', to ensure 'b' has been visited
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select({it*.revision == ['1', '2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(a, selected, d, e)
+    }
+
+    def "does not include dependencies of module version that is no longer transitive after conflict resolution has been applied"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def evicted = revision('c', '1')
+        def selected = revision('c', '2')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, evicted
+        traverses root, a, transitive: false
+        doesNotResolve evicted, a
+        traverses a, b
+        traverses root, d
+        traverses d, e
+        traverses e, selected // conflict is deeper than 'b', to ensure 'b' has been visited
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select({it*.revision == ['1', '2']}, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '2' }
+        }
+        0 * conflictResolver._
+
+        and:
+        modules(result) == ids(a, selected, d, e)
+    }
+
+    def "does not attempt to resolve a dependency whose target module is excluded earlier in the path"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses a, b, exclude: c
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b)
+    }
+
+    def "does not include the artifacts of evicted modules"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        traverses root, selected
+        doesNotResolve root, evicted
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+
+        and:
+        artifacts(result) == ids(selected)
+    }
+
+    def "does not include the artifacts of excluded modules when excluded by all paths"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        traverses root, a
+        traverses a, b, exclude: c
+        doesNotResolve b, c
+        traverses root, d, exclude: c
+        doesNotResolve d, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, d)
+        artifacts(result) == ids(a, b, d)
+    }
+
+    def "includes a module version when there is a path to the version that does not exclude it"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        traverses root, a
+        traverses a, b, exclude: c
+        doesNotResolve b, c
+        traverses root, d
+        traverses d, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, c, d)
+        artifacts(result) == ids(a, b, c, d)
+    }
+
+    def "ignores a new incoming path that includes a subset of those already included"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses a, b
+        traverses root, c, exclude: b
+        doesNotResolve c, a
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, c)
+        artifacts(result) == ids(a, b, c)
+    }
+
+    def "ignores a new incoming path that includes the same set of module versions"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, a, exclude: e
+        traverses a, b
+        traverses a, c
+        traverses b, d
+        doesNotResolve c, d
+        doesNotResolve d, e
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, c, d)
+        artifacts(result) == ids(a, b, c, d)
+    }
+
+    def "restarts traversal when new incoming path excludes fewer module versions"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a, exclude: b
+        traverses root, c
+        doesNotResolve c, a
+        traverses a, b
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b, c)
+        artifacts(result) == ids(a, b, c)
+    }
+
+    def "does not traverse outgoing paths of a non-transitive dependency"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses a, b, transitive: false
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(a, b)
+        artifacts(result) == ids(a, b)
+    }
+
+    def "reports shortest incoming paths for a failed dependency"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses root, b
+        doesNotResolve b, a
+        traversesBroken a, c
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        result.unresolvedModuleDependencies.size() == 1
+        def unresolved = result.unresolvedModuleDependencies.iterator().next()
+        unresolved.id == 'group#c;1.0'
+        unresolved.problem instanceof ModuleVersionResolveException
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionResolveException
+        e.cause.message.contains "group:root:1.0 > group:a:1.0"
+        e.cause.message.contains "group:root:1.0 > group:b:1.0"
+        !e.cause.message.contains("group:root:1.0 > group:b:1.0 > group:a:1.0")
+    }
+
+    def "reports failure to resolve version selector to module version"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        traverses root, a
+        traverses root, b
+        doesNotResolve b, a
+        brokenSelector b, 'unknown'
+        brokenSelector a, 'unknown'
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        result.unresolvedModuleDependencies.size() == 1
+        def unresolved = result.unresolvedModuleDependencies.iterator().next()
+        unresolved.id == 'group#unknown;1.0'
+        unresolved.problem instanceof ModuleVersionResolveException
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionResolveException
+        e.cause.message.contains "group:root:1.0 > group:a:1.0"
+        e.cause.message.contains "group:root:1.0 > group:b:1.0"
+    }
+
+    def "merges all failures for all dependencies with a given module version selector"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses root, b
+        traversesBroken a, c
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        result.unresolvedModuleDependencies.size() == 1
+        def unresolved = result.unresolvedModuleDependencies.iterator().next()
+        unresolved.id == 'group#c;1.0'
+        unresolved.problem instanceof ModuleVersionResolveException
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionResolveException
+        e.cause.message.contains "group:root:1.0 > group:a:1.0"
+        e.cause.message.contains "group:root:1.0 > group:b:1.0"
+    }
+
+    def "reports shortest incoming paths for a missing module version"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses root, b
+        doesNotResolve b, a
+        traversesMissing a, c
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        result.unresolvedModuleDependencies.size() == 1
+        def unresolved = result.unresolvedModuleDependencies.iterator().next()
+        unresolved.id == 'group#c;1.0'
+        unresolved.problem instanceof ModuleVersionNotFoundException
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionNotFoundException
+        e.cause.message.contains "group:root:1.0 > group:a:1.0"
+        e.cause.message.contains "group:root:1.0 > group:b:1.0"
+        !e.cause.message.contains("group:root:1.0 > group:b:1.0 > group:a:1.0")
+    }
+
+    def "merges all dependencies with a given module version selector when reporting missing version"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses root, b
+        traversesMissing a, c
+        doesNotResolve b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        result.unresolvedModuleDependencies.size() == 1
+        def unresolved = result.unresolvedModuleDependencies.iterator().next()
+        unresolved.id == 'group#c;1.0'
+        unresolved.problem instanceof ModuleVersionResolveException
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionNotFoundException
+        e.cause.message.contains "group:root:1.0 > group:a:1.0"
+        e.cause.message.contains "group:root:1.0 > group:b:1.0"
+    }
+    
+    def "can handle a cycle in the incoming paths of a broken module"() {
+        given:
+        def a = revision('a')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, a
+        traverses a, b
+        doesNotResolve b, a
+        traversesMissing b, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        result.unresolvedModuleDependencies.size() == 1
+        def unresolved = result.unresolvedModuleDependencies.iterator().next()
+        unresolved.id == 'group#c;1.0'
+        unresolved.problem instanceof ModuleVersionResolveException
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionNotFoundException
+        e.cause.message.contains "group:root:1.0 > group:a:1.0 > group:b:1.0"
+    }
+
+    def "does not report a path through an evicted version"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        def d = revision('d')
+        def e = revision('e')
+        traverses root, evicted
+        traverses evicted, b
+        traversesMissing b, c
+        traverses root, d
+        traverses d, e
+        traverses e, selected
+        doesNotResolve selected, c
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+
+        when:
+        result.rethrowFailure()
+
+        then:
+        ResolveException ex = thrown()
+        ex.cause instanceof ModuleVersionNotFoundException
+        !ex.cause.message.contains("group:a:1.1")
+        ex.cause.message.contains "group:root:1.0 > group:a:1.2"
+
+        and:
+        modules(result) == ids(selected, d, e)
+    }
+
+    def "fails when conflict resolution selects a version that does not exist"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        traverses root, evicted
+        traverses root, b
+        traversesMissing b, selected
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+
+        and:
+        ResolveException e = thrown()
+        e.cause instanceof ModuleVersionNotFoundException
+        e.cause.message.contains("group:root:1.0 > group:b:1.0")
+    }
+
+    def "does not fail when conflict resolution evicts a version that does not exist"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        traversesMissing root, evicted
+        traverses root, b
+        traverses b, selected
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+
+        and:
+        modules(result) == ids(selected, b)
+    }
+
+    def "does not fail when a broken version is evicted"() {
+        given:
+        def selected = revision('a', '1.2')
+        def evicted = revision('a', '1.1')
+        def b = revision('b')
+        def c = revision('c')
+        traverses root, evicted
+        traversesBroken evicted, b
+        traverses root, c
+        traverses c, selected
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        1 * conflictResolver.select(!null, !null) >> { Collection<ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root ->
+            return candidates.find { it.revision == '1.2' }
+        }
+
+        and:
+        modules(result) == ids(selected, c)
+    }
+
+    def "direct dependency can force a particular version"() {
+        given:
+        def forced = revision("a", "1")
+        def evicted = revision("a", "2")
+        def b = revision("b")
+        traverses root, b
+        traverses root, forced, force: true
+        doesNotResolve b, evicted
+
+        when:
+        def result = builder.resolve(configuration, resolveData)
+        result.rethrowFailure()
+
+        then:
+        modules(result) == ids(forced, b)
+    }
+
+    def revision(String name, String revision = '1.0') {
+        DefaultModuleDescriptor descriptor = new DefaultModuleDescriptor(new ModuleRevisionId(new ModuleId("group", name), revision), "release", new Date())
+        config(descriptor, 'default')
+        descriptor.addArtifact('default', new DefaultArtifact(descriptor.moduleRevisionId, new Date(), "art1", "art", "zip"))
+        return descriptor
+    }
+
+    def config(DefaultModuleDescriptor descriptor, String name, String... extendsFrom) {
+        def configuration = new Configuration(name, Configuration.Visibility.PUBLIC, null, extendsFrom, true, null)
+        descriptor.addConfiguration(configuration)
+        return configuration
+    }
+
+    def traverses(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
+        def descriptor = dependsOn(args, from, to.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, to.moduleRevisionId)
+        ModuleVersionResolveResult resolveResult = Mock()
+        1 * idResolveResult.resolve() >> resolveResult
+        1 * resolveResult.id >> to.moduleRevisionId
+        1 * resolveResult.descriptor >> { println "RESOLVE $from.moduleRevisionId -> $to.moduleRevisionId"; return to }
+    }
+
+    def doesNotResolve(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
+        def descriptor = dependsOn(args, from, to.moduleRevisionId)
+        ModuleVersionIdResolveResult result = Mock()
+        (0..1) * dependencyResolver.resolve(descriptor) >> result
+        (0..1) * result.id >> to.moduleRevisionId
+    }
+
+    def traversesMissing(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
+        def descriptor = dependsOn(args, from, to.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, to.moduleRevisionId)
+        ModuleVersionResolveResult resolveResult = Mock()
+        1 * idResolveResult.resolve() >> resolveResult
+        1 * resolveResult.id >> { throw new ModuleVersionNotFoundException("missing") }
+    }
+
+    def traversesBroken(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
+        def descriptor = dependsOn(args, from, to.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, to.moduleRevisionId)
+        ModuleVersionResolveResult resolveResult = Mock()
+        1 * idResolveResult.resolve() >> resolveResult
+        1 * resolveResult.id >> { throw new ModuleVersionResolveException("broken") }
+    }
+
+    def brokenSelector(Map<String, ?> args = [:], DefaultModuleDescriptor from, String to) {
+        def descriptor = dependsOn(args, from, ModuleRevisionId.newInstance("group", to, "1.0"))
+        ModuleVersionIdResolveResult result = Mock()
+        (0..1) * dependencyResolver.resolve(descriptor) >> result
+        _ * result.failure >> new ModuleVersionResolveException("broken")
+        0 * result._
+    }
+
+    def dependsOn(Map<String, ?> args = [:], DefaultModuleDescriptor from, ModuleRevisionId to) {
+        ModuleDependency moduleDependency = Mock()
+        def dependencyId = args.revision ? new ModuleRevisionId(to.moduleId, args.revision) : to
+        boolean transitive = args.transitive == null || args.transitive
+        boolean force = args.force
+        def descriptor = new EnhancedDependencyDescriptor(moduleDependency, from, dependencyId, force, false, transitive)
+        descriptor.addDependencyConfiguration("default", "default")
+        if (args.exclude) {
+            descriptor.addExcludeRule("default", new DefaultExcludeRule(new ArtifactId(
+                    args.exclude.moduleRevisionId.moduleId, PatternMatcher.ANY_EXPRESSION,
+                    PatternMatcher.ANY_EXPRESSION,
+                    PatternMatcher.ANY_EXPRESSION),
+                    ExactPatternMatcher.INSTANCE, null))
+        }
+        from.addDependency(descriptor)
+        return descriptor
+    }
+
+    def selectorResolvesTo(DependencyDescriptor descriptor, ModuleRevisionId to) {
+        ModuleVersionIdResolveResult result = Mock()
+        1 * dependencyResolver.resolve(descriptor) >> result
+        1 * result.id >> to
+        return result
+    }
+
+    def ids(ModuleDescriptor... descriptors) {
+        return descriptors.collect { new DefaultModuleVersionIdentifier(it.moduleRevisionId.organisation, it.moduleRevisionId.name, it.moduleRevisionId.revision)} as Set
+    }
+
+    def modules(LenientConfiguration config) {
+        Set<ModuleVersionIdentifier> result = new LinkedHashSet<ModuleVersionIdentifier>()
+        List<ResolvedDependency> queue = []
+        queue.addAll(config.getFirstLevelModuleDependencies({true} as Spec))
+        while (!queue.empty) {
+            def node = queue.remove(0)
+            result.add(node.module.id)
+            queue.addAll(0, node.children)
+        }
+        return result
+    }
+
+    def artifacts(LenientConfiguration config) {
+        return config.resolvedArtifacts.collect { it.moduleVersion.id } as Set
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy
new file mode 100644
index 0000000..f41d1a5
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpecTest.groovy
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2011 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.ivyservice.resolveengine
+
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
+import org.apache.ivy.core.module.id.ArtifactId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import spock.lang.Specification
+import org.apache.ivy.plugins.matcher.RegexpPatternMatcher
+
+class ModuleVersionSpecTest extends Specification {
+    def "accepts all module by default"() {
+        def spec = ModuleVersionSpec.forExcludes()
+
+        expect:
+        spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+    }
+
+    def "default specs accept the same modules as each other"() {
+        expect:
+        ModuleVersionSpec.forExcludes().acceptsSameModulesAs(ModuleVersionSpec.forExcludes())
+    }
+
+    def "does not accept module version that matches any exclude rule"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def rule3 = excludeRule("org2", "*")
+        def rule4 = excludeRule("*", "module4")
+        def rule5 = regExpExcludeRule("regexp-\\d+", "module\\d+")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4, rule5)
+
+        expect:
+        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+        !spec.isSatisfiedBy(ModuleId.newInstance("org2", "anything"))
+        !spec.isSatisfiedBy(ModuleId.newInstance("other", "module4"))
+        !spec.isSatisfiedBy(ModuleId.newInstance("regexp-72", "module12"))
+        spec.isSatisfiedBy(ModuleId.newInstance("org", "other"))
+        spec.isSatisfiedBy(ModuleId.newInstance("regexp-72", "other"))
+        spec.isSatisfiedBy(ModuleId.newInstance("regexp", "module2"))
+    }
+
+    def "specs with the same set of exclude rules accept the same modules as each other"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def rule3 = excludeRule("org2", "*")
+        def rule4 = excludeRule("*", "module4")
+        def rule5 = regExpExcludeRule("pattern1", "pattern2")
+        def exactMatchSpec = ModuleVersionSpec.forExcludes(rule1)
+        def moduleWildcard = ModuleVersionSpec.forExcludes(rule3)
+        def groupWildcard = ModuleVersionSpec.forExcludes(rule4)
+        def regexp = ModuleVersionSpec.forExcludes(rule5)
+        def manyRules = ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4, rule5)
+
+        expect:
+        exactMatchSpec.acceptsSameModulesAs(exactMatchSpec)
+        exactMatchSpec.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1))
+
+        !exactMatchSpec.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule2))
+        !exactMatchSpec.acceptsSameModulesAs(ModuleVersionSpec.forExcludes())
+        !exactMatchSpec.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2))
+
+        moduleWildcard.acceptsSameModulesAs(moduleWildcard)
+        moduleWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule3))
+
+        !moduleWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1))
+        !moduleWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule3))
+        !moduleWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes())
+        !moduleWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(excludeRule("org3", "*")))
+
+        groupWildcard.acceptsSameModulesAs(groupWildcard)
+        groupWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule4))
+
+        !groupWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1))
+        !groupWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule4))
+        !groupWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes())
+        !groupWildcard.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(excludeRule("*", "module5")))
+
+        regexp.acceptsSameModulesAs(regexp)
+        regexp.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule5))
+
+        !regexp.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1))
+        !regexp.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule5))
+        !regexp.acceptsSameModulesAs(ModuleVersionSpec.forExcludes())
+        !regexp.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(regExpExcludeRule("pattern", "other")))
+
+        manyRules.acceptsSameModulesAs(manyRules)
+        manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4, rule5))
+
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule3, rule4, rule5))
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, rule4, rule5))
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule5))
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4))
+
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, excludeRule("org", "module3"), rule3, rule4, rule5))
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, excludeRule("org3", "*"), rule4, rule5))
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, rule3, excludeRule("*", "module5"), rule5))
+        !manyRules.acceptsSameModulesAs(ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4, regExpExcludeRule("other", "other")))
+    }
+
+    def "union accepts all modules when one spec has empty set of exclude rules"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec2 = ModuleVersionSpec.forExcludes()
+
+        expect:
+        spec.union(spec2) == spec2
+        spec2.union(spec) == spec2
+    }
+
+    def "union of a spec with itself returns the original spec"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+
+        expect:
+        spec.union(spec) == spec
+    }
+
+    def "union of two specs with the same exclude rule instances returns one of the original specs"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = regExpExcludeRule("org", "module2")
+        def rule3 = excludeRule("org", "*")
+        def rule4 = excludeRule("*", "module")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2, rule3, rule4)
+        def spec2 = ModuleVersionSpec.forExcludes(rule2, rule3, rule1, rule4)
+
+        expect:
+        spec.union(spec2) == spec
+    }
+
+    def "union of two specs with exact matching exclude rules uses the intersection of the exclude rules"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def rule3 = excludeRule("org", "module3")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec2 = ModuleVersionSpec.forExcludes(rule1, rule3)
+
+        expect:
+        def union = spec.union(spec2)
+        union instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        union.excludeSpecs.size() == 1
+        union.excludeSpecs.any { it.moduleId == rule1.id.moduleId }
+    }
+
+    def "union of spec with module wildcard uses the most specific matching exclude rules"() {
+        def rule1 = excludeRule("org", "*")
+        def rule2 = excludeRule("org", "module")
+        def rule3 = excludeRule("org", "module2")
+        def rule4 = excludeRule("other", "module")
+        def rule5 = excludeRule("*", "module3")
+        def rule6 = excludeRule("org2", "*")
+        def spec = ModuleVersionSpec.forExcludes(rule1)
+
+        expect:
+        def union = spec.union(ModuleVersionSpec.forExcludes(rule2, rule3, rule4))
+        union instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        union.excludeSpecs.size() == 2
+        union.excludeSpecs.any { it.moduleId == rule2.id.moduleId }
+        union.excludeSpecs.any { it.moduleId == rule3.id.moduleId }
+        
+        def union2 = spec.union(ModuleVersionSpec.forExcludes(rule5))
+        union2 instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        union2.excludeSpecs.size() == 1
+        union2.excludeSpecs.any { it.moduleId.organisation == 'org' && it.moduleId.name == 'module3' }
+
+        def union3 = spec.union(ModuleVersionSpec.forExcludes(rule6, rule2))
+        union3 instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        union3.excludeSpecs.size() == 1
+        union3.excludeSpecs.any { it.moduleId == rule2.id.moduleId }
+    }
+
+    def "union of spec with group wildcard uses the most specific matching exclude rules"() {
+        def rule1 = excludeRule("*", "module")
+        def rule2 = excludeRule("org", "module")
+        def rule3 = excludeRule("org", "module2")
+        def rule4 = excludeRule("other", "module")
+        def rule5 = excludeRule("org", "*")
+        def rule6 = excludeRule("*", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1)
+
+        expect:
+        def union = spec.union(ModuleVersionSpec.forExcludes(rule2, rule3, rule4))
+        union instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        union.excludeSpecs.size() == 2
+        union.excludeSpecs.any { it.moduleId == rule2.id.moduleId }
+        union.excludeSpecs.any { it.moduleId == rule4.id.moduleId }
+
+        def union2 = spec.union(ModuleVersionSpec.forExcludes(rule5))
+        union2 instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        union2.excludeSpecs.size() == 1
+        union2.excludeSpecs.any { it.moduleId.organisation == 'org' && it.moduleId.name == 'module' }
+
+        def union3 = spec.union(ModuleVersionSpec.forExcludes(rule6))
+        union3 == ModuleVersionSpec.forExcludes()
+    }
+
+    def "union of two specs with disjoint exact matching exclude rules matches all modules"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1)
+        def spec2 = ModuleVersionSpec.forExcludes(rule2)
+
+        expect:
+        def union = spec.union(spec2)
+        union == ModuleVersionSpec.forExcludes()
+    }
+
+    def "union of two specs with non-exact matching exclude rules is a union spec"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = regExpExcludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1)
+        def spec2 = ModuleVersionSpec.forExcludes(rule2)
+
+        expect:
+        def union = spec.union(spec2)
+        union instanceof ModuleVersionSpec.UnionSpec
+        union.specs.size() == 2
+        union.specs[0] == spec
+        union.specs[1] == spec2
+    }
+
+    def "union of union specs is the union of the original specs"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def rule3 = regExpExcludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1)
+        def spec2 = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec3 = ModuleVersionSpec.forExcludes(rule3)
+
+        expect:
+        def union = spec.union(spec3).union(spec2)
+
+        union instanceof ModuleVersionSpec.UnionSpec
+        union.specs.size() == 2
+        union.specs.any {
+            it instanceof ModuleVersionSpec.ExcludeRuleBackedSpec && it.excludeSpecs == spec.excludeSpecs
+        }
+        union.specs.contains(spec3)
+    }
+
+    def "union accept module that is accepted by any merged exclude rule"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec2 = ModuleVersionSpec.forExcludes(rule1)
+
+        expect:
+        def union = spec.union(spec2)
+
+        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+        !union.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+
+        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+        union.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+    }
+
+    def "unions accepts same modules when original specs accept same modules"() {
+        def rule1 = regExpExcludeRule("org", "module")
+        def rule2 = regExpExcludeRule("org", "module2")
+        def rule3 = regExpExcludeRule("org", "module3")
+        def spec1 = ModuleVersionSpec.forExcludes(rule1)
+        def spec2 = ModuleVersionSpec.forExcludes(rule2)
+        def spec3 = ModuleVersionSpec.forExcludes(rule3)
+
+        expect:
+        spec1.union(spec2).acceptsSameModulesAs(spec2.union(spec1))
+
+        !spec1.union(spec2).acceptsSameModulesAs(spec2)
+        !spec1.union(spec2).acceptsSameModulesAs(spec1)
+        !spec1.union(spec2).acceptsSameModulesAs(spec1.union(spec3))
+    }
+
+    def "intersection accepts those modules accepted by other spec when one spec has empty set of exclude rules"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec2 = ModuleVersionSpec.forExcludes()
+
+        expect:
+        spec.intersect(spec2) == spec
+        spec2.intersect(spec) == spec
+    }
+
+    def "intersection does not accept module that is not accepted by any merged exclude rules"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec2 = ModuleVersionSpec.forExcludes(rule1)
+
+        expect:
+        def intersect = spec.intersect(spec2)
+
+        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+        !intersect.isSatisfiedBy(ModuleId.newInstance("org", "module"))
+
+        !spec.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+        !intersect.isSatisfiedBy(ModuleId.newInstance("org", "module2"))
+
+        spec.isSatisfiedBy(ModuleId.newInstance("org", "module3"))
+        spec2.isSatisfiedBy(ModuleId.newInstance("org", "module3"))
+        intersect.isSatisfiedBy(ModuleId.newInstance("org", "module3"))
+    }
+
+    def "intersection of a spec with itself returns the original spec"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+
+        expect:
+        spec.intersect(spec) == spec
+    }
+
+    def "intersection of two specs with exclude rules is the union of the exclude rules"() {
+        def rule1 = excludeRule("org", "module")
+        def rule2 = excludeRule("org", "module2")
+        def spec = ModuleVersionSpec.forExcludes(rule1, rule2)
+        def spec2 = ModuleVersionSpec.forExcludes(rule1)
+
+        expect:
+        def intersection = spec.intersect(spec2)
+        intersection instanceof ModuleVersionSpec.ExcludeRuleBackedSpec
+        intersection.excludeSpecs.size() == 2
+        intersection.excludeSpecs.any { it.moduleId == rule1.id.moduleId }
+        intersection.excludeSpecs.any { it.moduleId == rule2.id.moduleId }
+    }
+
+    def "intersections accepts same modules when original specs accept same modules"() {
+        def rule1 = regExpExcludeRule("org", "module")
+        def rule2 = regExpExcludeRule("org", "module2")
+        def rule3 = regExpExcludeRule("org", "module3")
+        def spec1 = ModuleVersionSpec.forExcludes(rule1).union(ModuleVersionSpec.forExcludes(rule2))
+        def spec2 = ModuleVersionSpec.forExcludes(rule2).union(ModuleVersionSpec.forExcludes(rule1))
+        def spec3 = ModuleVersionSpec.forExcludes(rule3)
+        assert spec1.acceptsSameModulesAs(spec2)
+
+        expect:
+        spec1.intersect(spec2).acceptsSameModulesAs(spec2.intersect(spec1))
+
+        !spec1.intersect(spec2).acceptsSameModulesAs(spec1)
+        !spec1.intersect(spec2).acceptsSameModulesAs(spec2)
+        !spec1.intersect(spec2).acceptsSameModulesAs(spec1.intersect(spec3))
+    }
+
+    def excludeRule(String org, String module) {
+        return new DefaultExcludeRule(new ArtifactId(ModuleId.newInstance(org, module), "ivy", "ivy", "ivy"), ExactPatternMatcher.INSTANCE, [:])
+    }
+
+    def regExpExcludeRule(String org, String module) {
+        return new DefaultExcludeRule(new ArtifactId(ModuleId.newInstance(org, module), "ivy", "ivy", "ivy"), RegexpPatternMatcher.INSTANCE, [:])
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
new file mode 100644
index 0000000..1ef8549
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * 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.mvnsettings
+
+import org.gradle.util.TemporaryFolder
+
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultLocalMavenRepositoryLocatorTest extends Specification {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+
+    SimpleMavenFileLocations locations
+    DefaultLocalMavenRepositoryLocator locator
+
+    Map systemProperties = ["sys.prop": "sys/prop/value"]
+    Map environmentVariables = [ENV_VAR: "env/var/value"]
+
+    File repo1 = tmpDir.file("repo1")
+    File repo2 = tmpDir.file("repo2")
+
+    def setup() {
+        locations = new SimpleMavenFileLocations()
+        locator = new DefaultLocalMavenRepositoryLocator(locations, systemProperties, environmentVariables)
+    }
+
+    def "returns default location if no settings file exists"() {
+        expect:
+        // this default comes from DefaultMavenSettingsBuilder which uses System.getProperty() directly
+        locator.localMavenRepository == new File("${System.getProperty("user.home")}/.m2/repository")
+    }
+
+    def "honors location specified in user settings file"() {
+        writeSettingsFile(locations.userSettingsFile, repo1)
+
+        expect:
+        locator.localMavenRepository == repo1
+    }
+
+    def "honors location specified in global settings file"() {
+        writeSettingsFile(locations.globalSettingsFile, repo1)
+
+        expect:
+        locator.localMavenRepository == repo1
+    }
+
+    def "prefers location specified in user settings file over that in global settings file"() {
+        writeSettingsFile(locations.userSettingsFile, repo1)
+        writeSettingsFile(locations.globalSettingsFile, repo2)
+
+        expect:
+        locator.localMavenRepository == repo1
+    }
+
+    def "handles the case where (potential) location of global settings file cannot be determined"() {
+        locations.globalSettingsFile = null
+
+        expect:
+        locator.localMavenRepository == new File("${System.getProperty("user.home")}/.m2/repository")
+
+        when:
+        writeSettingsFile(locations.userSettingsFile, repo1)
+
+        then:
+        locator.localMavenRepository == repo1
+    }
+
+    def "replaces placeholders for system properties and environment variables"() {
+        writeSettingsFile(locations.userSettingsFile, tmpDir.file('${sys.prop}/${env.ENV_VAR}'))
+
+        expect:
+        locator.localMavenRepository == tmpDir.file("sys/prop/value/env/var/value")
+    }
+
+    private void writeSettingsFile(File settings, File repo) {
+        writeSettingsFile(settings, repo.absolutePath)
+    }
+
+    private void writeSettingsFile(File settings, String repo) {
+        settings << """
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <localRepository>$repo</localRepository>
+</settings>"""
+    }
+
+    private class SimpleMavenFileLocations implements MavenFileLocations {
+        File userMavenDir
+        File globalMavenDir
+        File userSettingsFile = tmpDir.file("userSettingsFile")
+        File globalSettingsFile = tmpDir.file("globalSettingsFile")
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultDeployTaskFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultDeployTaskFactoryTest.java
deleted file mode 100644
index bc7133f..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultDeployTaskFactoryTest.java
+++ /dev/null
@@ -1,31 +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;
-
-import static org.junit.Assert.assertTrue;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.CustomDeployTask;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.DefaultDeployTaskFactory;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultDeployTaskFactoryTest {
-    @Test
-    public void create() {
-        assertTrue(new DefaultDeployTaskFactory().create() instanceof CustomDeployTask);
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultLocalMavenCacheLocatorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultLocalMavenCacheLocatorTest.groovy
deleted file mode 100644
index f8db7b0..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultLocalMavenCacheLocatorTest.groovy
+++ /dev/null
@@ -1,58 +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.api.internal.artifacts.publish.maven
-
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.SetSystemProperties
-import org.gradle.util.TemporaryFolder
-
-class DefaultLocalMavenCacheLocatorTest extends Specification {
-    @Rule public final SetSystemProperties systemProperties = new SetSystemProperties()
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
-    final DefaultLocalMavenCacheLocator locator = new DefaultLocalMavenCacheLocator()
-
-    def setup() {
-        System.setProperty('user.home', tmpDir.dir.absolutePath)
-    }
-
-    def usesDefaultWhenNoSettingsXmlFile() {
-        expect:
-        locator.localMavenCache == new File(tmpDir.dir, '.m2/repository')
-    }
-
-    def usesValueFromSettingsXmlFile() {
-        tmpDir.file('.m2/settings.xml') << """
-<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-  <localRepository>${tmpDir.file('.m2/custom').absolutePath}</localRepository>
-</settings>"""
-
-        expect:
-        locator.localMavenCache == new File(tmpDir.dir, '.m2/custom')
-    }
-
-    def usesValueWithPlaceholderFromSettingsXmlFile() {
-        tmpDir.file('.m2/settings.xml') << '''
-<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-  <localRepository>${user.home}/.m2/custom</localRepository>
-</settings>'''
-
-        expect:
-        locator.localMavenCache == new File(tmpDir.dir, '.m2/custom')
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactoryTest.groovy
deleted file mode 100644
index f756036..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactoryTest.groovy
+++ /dev/null
@@ -1,46 +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;
-
-
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer
-
-import org.gradle.api.internal.file.FileResolver
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter
- */
-public class DefaultMavenPomFactoryTest extends Specification {
-    def createMavenPom() {
-        DefaultConf2ScopeMappingContainer scopeMappings = new DefaultConf2ScopeMappingContainer();
-        PomDependenciesConverter pomDependenciesConverter = Mock(PomDependenciesConverter); 
-        ConfigurationContainer configurationContainer = Mock(ConfigurationContainer); 
-        FileResolver fileResolver = Mock(FileResolver); 
-        DefaultMavenPomFactory mavenPomFactory = new DefaultMavenPomFactory(configurationContainer, scopeMappings,
-                pomDependenciesConverter, fileResolver);
-        DefaultMavenPom mavenPom = (DefaultMavenPom) mavenPomFactory.create();
-
-        expect:
-        !scopeMappings.is(mavenPom.scopeMappings)
-        scopeMappings == mavenPom.scopeMappings
-        mavenPom.mavenProject != null
-        mavenPom.pomDependenciesConverter.is(pomDependenciesConverter)
-        mavenPom.configurations.is(configurationContainer)
-        mavenPom.fileResolver == fileResolver
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomTest.groovy
deleted file mode 100644
index 9bb7948..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomTest.groovy
+++ /dev/null
@@ -1,191 +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
-
-import org.apache.commons.lang.builder.EqualsBuilder
-import org.apache.maven.model.Dependency
-import org.apache.maven.model.Model
-import org.gradle.api.Action
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
-import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer
-
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
-import org.gradle.util.TextUtil
-import org.junit.Rule
-import spock.lang.Specification
-
-class DefaultMavenPomTest extends Specification {
-    static final String EXPECTED_PACKAGING = "something";
-    static final String EXPECTED_GROUP_ID = "someGroup";
-    static final String EXPECTED_ARTIFACT_ID = "artifactId";
-    static final String EXPECTED_VERSION = "version";
-
-    @Rule
-    TemporaryFolder tmpDir = new TemporaryFolder()
-
-    Conf2ScopeMappingContainer conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer()
-    PomDependenciesConverter pomDependenciesConverterStub = Mock()
-    ConfigurationContainer configurationContainerStub = Mock()
-    FileResolver fileResolver = Mock()
-    DefaultMavenPom mavenPom = new DefaultMavenPom(configurationContainerStub, conf2ScopeMappingContainer, pomDependenciesConverterStub,
-            fileResolver)
-
-    void setup() {
-        mavenPom.packaging = EXPECTED_PACKAGING
-        mavenPom.groupId = EXPECTED_GROUP_ID
-        mavenPom.artifactId = EXPECTED_ARTIFACT_ID
-        mavenPom.version = EXPECTED_VERSION
-    }
-
-    def init() {
-        expect:
-        mavenPom.scopeMappings.is(conf2ScopeMappingContainer)
-        mavenPom.configurations.is(configurationContainerStub)
-        mavenPom.fileResolver.is(fileResolver)
-        mavenPom.mavenProject.modelVersion == "4.0.0"
-    }
-
-    def setModel() {
-        def newModel = new Model()
-
-        when:
-        mavenPom.model = newModel
-
-        then:
-        mavenPom.model.is(newModel)
-    }
-
-    def effectivePomShouldHaveGeneratedDependencies() {
-        Set configurations = [Mock(Configuration)]
-        configurationContainerStub.getAll() >> configurations
-        List generatedDependencies = [new Dependency(groupId: 'someGroup')]
-        List manuallyAddedDependencies = [new Dependency()]
-        pomDependenciesConverterStub.convert(conf2ScopeMappingContainer, configurations) >> generatedDependencies
-
-        when:
-        mavenPom.dependencies = manuallyAddedDependencies.clone()
-
-        then:
-        EqualsBuilder.reflectionEquals(mavenPom.getEffectivePom().getMavenProject().getDependencies(), manuallyAddedDependencies + generatedDependencies)
-
-        when:
-        mavenPom.dependencies = []
-
-        then:
-        mavenPom.getEffectivePom().getMavenProject().getDependencies() == generatedDependencies
-    }
-
-    def configureActionsShouldBeAppliedAgainstEffectivePom() {
-        mavenPom.configurations = null
-        when:
-        mavenPom.whenConfigured(new Action() {
-            void execute(def mavenPom) {
-                mavenPom.mavenProject.inceptionYear = '1999'
-            }
-        })
-
-        then:
-        mavenPom.effectivePom.mavenProject.inceptionYear == '1999'
-        mavenPom.mavenProject.inceptionYear == null
-    }
-
-
-    def writeShouldUseEffectivePom() {
-        Set configurations = [Mock(Configuration)]
-        configurationContainerStub.getAll() >> configurations
-        List generatedDependencies = [new Dependency(groupId: 'someGroup')]
-        pomDependenciesConverterStub.convert(conf2ScopeMappingContainer, configurations) >> generatedDependencies
-
-        when:
-        StringWriter pomWriter = new StringWriter()
-        mavenPom.writeTo pomWriter
-
-        then:
-        pomWriter.toString().contains('someGroup')
-    }
-
-    def effectivePomWithNullConfigurationsShouldWork() {
-        when:
-        mavenPom.configurations = null
-
-        then:
-        mavenPom.getEffectivePom().getMavenProject().getDependencies() == []
-    }
-
-    void projectBuilder() {
-        mavenPom.mavenProject.inceptionYear = '2007'
-        mavenPom.mavenProject.description = 'some description'
-        mavenPom.project {
-            inceptionYear '2008'
-            licenses {
-                license {
-                    name 'The Apache Software License, Version 2.0'
-                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
-                    distribution 'repo'
-                }
-            }
-        }
-
-        expect:
-        mavenPom.mavenProject.modelVersion == "4.0.0"
-        mavenPom.version == EXPECTED_VERSION
-        mavenPom.mavenProject.description == 'some description'
-        mavenPom.mavenProject.inceptionYear == '2008'
-        mavenPom.mavenProject.licenses.size() == 1
-        mavenPom.mavenProject.licenses[0].name == 'The Apache Software License, Version 2.0'
-        mavenPom.mavenProject.licenses[0].url == 'http://www.apache.org/licenses/LICENSE-2.0.txt'
-        mavenPom.mavenProject.licenses[0].distribution == 'repo'
-    }
-
-    void writeToShouldApplyXmlActions() {
-        mavenPom.configurations = null
-        StringWriter pomWriter = new StringWriter()
-
-        when:
-        mavenPom.withXml {xmlProvider ->
-            xmlProvider.asString().append('someAppendix')
-        }
-        mavenPom.writeTo(pomWriter);
-
-        then:
-        pomWriter.toString().endsWith("someAppendix")
-    }
-
-    void writeToWritesCorrectPom() {
-        mavenPom.configurations = null
-        TestFile pomFile = tmpDir.file('someNonexistingDir').file('someFile')
-        fileResolver.resolve('file') >> pomFile
-
-        when:
-        mavenPom.writeTo('file');
-
-        then:
-        pomFile.text == TextUtil.toPlatformLineSeparators('''<?xml version="1.0" encoding="UTF-8"?>
-<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>someGroup</groupId>
-  <artifactId>artifactId</artifactId>
-  <version>version</version>
-  <packaging>something</packaging>
-</project>
-''')
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainerTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainerTest.java
deleted file mode 100644
index 2fe834b..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainerTest.java
+++ /dev/null
@@ -1,123 +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.api.internal.artifacts.publish.maven.dependencies;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
-import org.gradle.util.HelperUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static java.util.Arrays.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultConf2ScopeMappingContainerTest {
-    private DefaultConf2ScopeMappingContainer conf2ScopeMappingContainer;
-    private static final Configuration TEST_CONF_1 = HelperUtil.createConfiguration("testCompile");
-    private static final Configuration TEST_CONF_2 = HelperUtil.createConfiguration("testCompile2");
-    private static final Configuration TEST_CONF_3 = HelperUtil.createConfiguration("testCompile3");
-    private static final String TEST_SCOPE_1 = "test";
-    private static final String TEST_SCOPE_2 = "test2";
-    private static final int TEST_PRIORITY_1 = 10;
-    private static final int TEST_PRIORITY_2 = 20;
-
-    @Before
-    public void setUp() {
-        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer();
-        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1);
-    }
-
-    @Test
-    public void init() {
-        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer();
-        assertTrue(conf2ScopeMappingContainer.isSkipUnmappedConfs());
-        assertEquals(0, conf2ScopeMappingContainer.getMappings().size());
-        Map<Configuration, Conf2ScopeMapping> testMappings = createTestMappings();
-        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer(testMappings);
-        assertNotSame(testMappings, conf2ScopeMappingContainer.getMappings());
-        assertEquals(testMappings, conf2ScopeMappingContainer.getMappings());
-    }
-
-    @Test
-    public void equalsAndHashCode() {
-        Map<Configuration, Conf2ScopeMapping> testMappings = createTestMappings();
-        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer(testMappings);
-        assertTrue(conf2ScopeMappingContainer.equals(new DefaultConf2ScopeMappingContainer(testMappings)));
-        assertEquals(conf2ScopeMappingContainer.hashCode(), new DefaultConf2ScopeMappingContainer(testMappings).hashCode());
-        conf2ScopeMappingContainer.addMapping(10, HelperUtil.createConfiguration("conf2"), "scope");
-        assertFalse(conf2ScopeMappingContainer.equals(new DefaultConf2ScopeMappingContainer(testMappings)));
-    }
-
-    private Map<Configuration, Conf2ScopeMapping> createTestMappings() {
-        Map<Configuration, Conf2ScopeMapping> testMappings = new HashMap<Configuration, Conf2ScopeMapping>() {{
-            Configuration configuration = HelperUtil.createConfiguration("conf");
-            put(configuration, new Conf2ScopeMapping(10, configuration, "scope"));
-        }};
-        return testMappings;
-    }
-
-    @Test
-    public void addGetMapping() {
-        assertEquals(new Conf2ScopeMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1),
-                conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1)));
-    }
-
-    @Test
-    public void singleMappedConfiguration() {
-        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1)), equalTo(
-                new Conf2ScopeMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1)));
-    }
-
-    @Test
-    public void unmappedConfiguration() {
-        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_2)), equalTo(
-                new Conf2ScopeMapping(null, TEST_CONF_2, null)));
-    }
-
-    @Test
-    public void mappedConfigurationAndUnmappedConfiguration() {
-        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2)), equalTo(
-                new Conf2ScopeMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1)));
-    }
-
-    @Test
-    public void mappingWithDifferentPrioritiesDifferentConfsDifferentScopes() {
-        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_2, TEST_CONF_2, TEST_SCOPE_2);
-        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2)), equalTo(
-                new Conf2ScopeMapping(TEST_PRIORITY_2, TEST_CONF_2, TEST_SCOPE_2)));
-    }
-    
-    @Test(expected = InvalidUserDataException.class)
-    public void mappingWithSamePrioritiesDifferentConfsSameScope() {
-        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_2, TEST_SCOPE_1);
-        conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void mappingWithSamePrioritiesDifferentConfsDifferentScopes() {
-        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_2, TEST_SCOPE_1);
-        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_3, TEST_SCOPE_2);
-        conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2, TEST_CONF_3));
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverterTest.java
deleted file mode 100644
index 0bd43b0..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverterTest.java
+++ /dev/null
@@ -1,60 +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.dependencies;
-
-import org.apache.maven.model.Exclusion;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.util.GUtil;
-import org.gradle.util.WrapUtil;
-import org.junit.Before;
-import org.junit.Test;
-
-import static junit.framework.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultExcludeRuleConverterTest {
-    private static final String TEST_ORG = "org";
-    private static final String TEST_MODULE = "module";
-
-    private DefaultExcludeRuleConverter excludeRuleConverter;
-
-    @Before
-    public void setUp() {
-        excludeRuleConverter = new DefaultExcludeRuleConverter();   
-    }
-    
-    @Test
-    public void convertableRule() {
-        DefaultExcludeRule excludeRule = new DefaultExcludeRule(GUtil.map(ExcludeRule.GROUP_KEY, TEST_ORG, ExcludeRule.MODULE_KEY, TEST_MODULE));
-        Exclusion mavenExclude = excludeRuleConverter.convert(excludeRule);
-        assertEquals(TEST_ORG, mavenExclude.getGroupId());
-        assertEquals(TEST_MODULE, mavenExclude.getArtifactId());
-    }
-    
-    @Test
-    public void unconvertableRules() {
-        checkForNull(new DefaultExcludeRule(WrapUtil.toMap(ExcludeRule.GROUP_KEY, TEST_ORG)));
-        checkForNull(new DefaultExcludeRule(WrapUtil.toMap(ExcludeRule.MODULE_KEY, TEST_MODULE)));
-    }
-
-    private void checkForNull(DefaultExcludeRule excludeRule) {
-        assertNull(excludeRuleConverter.convert(excludeRule));
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
deleted file mode 100644
index 4dc012f..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
+++ /dev/null
@@ -1,254 +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.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.api.internal.artifacts.publish.maven.ExcludeRuleConverter;
-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;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultPomDependenciesConverterTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-    
-    private DefaultPomDependenciesConverter dependenciesConverter;
-    private Conf2ScopeMappingContainer conf2ScopeMappingContainerMock = context.mock(Conf2ScopeMappingContainer.class);
-    private ExcludeRuleConverter excludeRuleConverterMock = context.mock(ExcludeRuleConverter.class);
-
-    private ModuleDependency dependency1;
-    private ModuleDependency dependency2;
-    private ModuleDependency dependency31;
-    private ModuleDependency dependency32;
-    private Configuration compileConfStub;
-    private Configuration testCompileConfStub;
-
-    @Before
-    public void setUp() {
-        setUpCommonDependenciesAndConfigurations();
-        dependenciesConverter = new DefaultPomDependenciesConverter(excludeRuleConverterMock);
-    }
-
-    private void setUpCommonDependenciesAndConfigurations() {
-        dependency1 = createDependency("org1", "name1", "rev1");
-        dependency2 = createDependency("org2", "name2", "rev2");
-        dependency2.addArtifact(new DefaultDependencyArtifact("name2", null, null, null, null));
-        dependency31 = createDependency("org3", "name3", "rev3");
-        dependency32 = createDependency("org3", "name3", "rev3");
-        dependency32.addArtifact(new DefaultDependencyArtifact("artifactName32", "type32", "ext", "classifier32", null));
-        compileConfStub = createNamedConfigurationStubWithDependencies("compile", dependency1, dependency31);
-        testCompileConfStub = createNamedConfigurationStubWithDependencies("testCompile", dependency2, dependency32);
-        context.checking(new Expectations() {{
-            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(testCompileConfStub, compileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
-            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(compileConfStub, testCompileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
-            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(testCompileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
-            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(compileConfStub)); will(returnValue(createMapping(compileConfStub, "compile")));
-        }});
-    }
-
-    private Conf2ScopeMapping createMapping(Configuration configuration, String scope) {
-        return new Conf2ScopeMapping(10, configuration, scope);
-    }
-
-    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;
-    }
-
-    private ModuleDependency createDependency(final String group, final String name, final String version) {
-        return new DefaultExternalModuleDependency(group, name, version);
-    }
-
-    @Test
-    public void init() {
-        assertSame(excludeRuleConverterMock, dependenciesConverter.getExcludeRuleConverter());
-    }
-
-    @Test
-    public void convert() {
-        Set<Configuration> configurations = toSet(compileConfStub, testCompileConfStub);
-        context.checking(new Expectations() {{
-            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(false));
-        }});
-        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, configurations);
-        assertEquals(3, actualMavenDependencies.size());
-        checkCommonMavenDependencies(actualMavenDependencies);
-    }
-
-    @Test
-    public void convertWithUnMappedConfAndSkipTrue() {
-        final Dependency dependency4 = createDependency("org4", "name4", "rev4");
-        final Configuration unmappedConfigurationStub = createNamedConfigurationStubWithDependencies("unmappedConf");
-        context.checking(new Expectations() {{
-            allowing(unmappedConfigurationStub).getDependencies();
-            will(returnValue(toSet(dependency4)));
-        }});
-        context.checking(new Expectations() {{
-            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(true));
-            allowing(conf2ScopeMappingContainerMock).getMapping(asList(unmappedConfigurationStub)); will(returnValue(null));
-        }});
-        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(
-                compileConfStub, testCompileConfStub, unmappedConfigurationStub));
-        assertEquals(3, actualMavenDependencies.size());
-        checkCommonMavenDependencies(actualMavenDependencies);
-    }
-
-    @Test
-    public void convertWithUnMappedConfAndSkipFalse() {
-        final ModuleDependency dependency4 = createDependency("org4", "name4", "rev4");
-        final Configuration unmappedConfigurationStub = createNamedConfigurationStubWithDependencies("unmappedConf", dependency4);
-        context.checking(new Expectations() {{
-            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(false));
-            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(unmappedConfigurationStub)); will(returnValue(new Conf2ScopeMapping(null, unmappedConfigurationStub, null)));
-        }});
-        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(
-                compileConfStub, testCompileConfStub, unmappedConfigurationStub));
-        assertEquals(4, actualMavenDependencies.size());
-        checkCommonMavenDependencies(actualMavenDependencies);
-        assertTrue(hasDependency(actualMavenDependencies, "org4", "name4", "rev4", null, null, null, false));
-    }
-
-    private void checkCommonMavenDependencies(List<org.apache.maven.model.Dependency> actualMavenDependencies) {
-        assertTrue(hasDependency(actualMavenDependencies, "org1", "name1", "rev1", null, "compile", null, false));
-        assertTrue(hasDependency(actualMavenDependencies, "org2", "name2", "rev2", null, "test", null, false));
-        assertTrue(hasDependency(actualMavenDependencies, "org3", "artifactName32", "rev3", "type32", "test", "classifier32", false));
-    }
-
-    private boolean hasDependency(List<org.apache.maven.model.Dependency> mavenDependencies,
-                                  String group, String artifactId, String version, String type, String scope,
-                                  String classifier, boolean optional) {
-        org.apache.maven.model.Dependency expectedDependency = new org.apache.maven.model.Dependency();
-        expectedDependency.setGroupId(group);
-        expectedDependency.setArtifactId(artifactId);
-        expectedDependency.setVersion(version);
-        expectedDependency.setType(type);
-        expectedDependency.setScope(scope);
-        expectedDependency.setClassifier(classifier);
-        expectedDependency.setOptional(optional);
-        for (org.apache.maven.model.Dependency mavenDependency : mavenDependencies) {
-            if (equals(mavenDependency, expectedDependency)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean equals(org.apache.maven.model.Dependency lhs, org.apache.maven.model.Dependency rhs) {
-        if (!lhs.getGroupId().equals(lhs.getGroupId())) {
-            return false;
-        }
-        if (!lhs.getArtifactId().equals(lhs.getArtifactId())) {
-            return false;
-        }
-        if (!lhs.getVersion().equals(lhs.getVersion())) {
-            return false;
-        }
-        if (lhs.getType() != null ? !lhs.getType().equals(lhs.getType()) : rhs.getType() != null) {
-            return false;
-        }
-        if (lhs.getScope() != null ? !lhs.getScope().equals(lhs.getScope()) : rhs.getScope() != null) {
-            return false;
-        }
-        if (!lhs.isOptional() == lhs.isOptional()) {
-            return false;
-        }
-        if (lhs.getClassifier() != null ? !lhs.getClassifier().equals(rhs.getClassifier()) : rhs.getClassifier() != null) {
-            return false;
-        }
-        return true;
-    }
-
-    @Test
-    public void convertWithConvertableDependencyExcludes() {
-        final Configuration someConfigurationStub = createNamedConfigurationStubWithDependencies("someConfiguration", dependency1);
-        final Exclusion mavenExclude = new Exclusion();
-        mavenExclude.setGroupId("a");
-        mavenExclude.setArtifactId("b");
-        dependency1.exclude(toMap("key", "value"));
-        context.checking(new Expectations() {{
-           allowing(conf2ScopeMappingContainerMock).getMapping(toSet(someConfigurationStub)); will(returnValue(createMapping(compileConfStub, "compile")));
-           allowing(excludeRuleConverterMock).convert(dependency1.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()));
-    }
-    
-    @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/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
deleted file mode 100644
index 477c8e4..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
+++ /dev/null
@@ -1,239 +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.apache.maven.artifact.ant.AttachedArtifact;
-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.codehaus.plexus.PlexusContainerException;
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.artifacts.maven.MavenDeployment;
-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.DefaultPublishArtifact;
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomContainer;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.mvnsettings.MavenSettingsSupplier;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.util.AntUtil;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.jmock.Expectations;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Set;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractMavenResolverTest {
-    public static final String TEST_NAME = "name";
-    private static final Artifact TEST_IVY_ARTIFACT = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null);
-    private static final File TEST_IVY_FILE = new File("somepom.xml");
-    private static final File TEST_JAR_FILE = new File("somejar.jar");
-    private static final Artifact TEST_ARTIFACT = new DefaultArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null, TEST_NAME, "jar", "jar");
-    protected ArtifactPomContainer artifactPomContainerMock;
-    protected PomFilterContainer pomFilterContainerMock;
-    protected LoggingManagerInternal loggingManagerMock;
-
-    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {
-        {
-            setImposteriser(ClassImposteriser.INSTANCE);
-        }
-    };
-    protected MavenPom pomMock;
-
-    protected Settings mavenSettingsMock;
-
-    protected abstract AbstractMavenResolver getMavenResolver();
-
-    protected abstract InstallDeployTaskSupport getInstallDeployTask();
-
-    protected abstract PomFilterContainer createPomFilterContainerMock();
-
-    @Before
-    public void setUp() {
-        pomFilterContainerMock = createPomFilterContainerMock();
-        artifactPomContainerMock = context.mock(ArtifactPomContainer.class);
-        pomMock = context.mock(MavenPom.class);
-        mavenSettingsMock = context.mock(Settings.class);
-        loggingManagerMock = context.mock(LoggingManagerInternal.class);
-    }
-
-    @Test
-    public void deployOrInstall() throws IOException, PlexusContainerException {
-        getMavenResolver().mavenSettingsSupplier = context.mock(MavenSettingsSupplier.class);
-
-        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();
-                will(returnValue(mavenSettingsMock));
-                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getProject();
-                will(returnValue(AntUtil.createProject()));
-                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).createAttach();
-                will(returnValue(attachedArtifact));
-                one(artifactPomContainerMock).addArtifact(TEST_ARTIFACT, TEST_JAR_FILE);
-                allowing(artifactPomContainerMock).createDeployableFilesInfos();
-                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(testDefaultMavenDeployments, attachedArtifact, classifierArtifact);
-        assertSame(mavenSettingsMock, getMavenResolver().getSettings());
-    }
-
-    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 {
-        context.checking(new Expectations() {
-            {
-                one(getInstallDeployTask()).setProject(with(any(Project.class)));
-                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();
-                    one(getInstallDeployTask()).execute();
-                    one(loggingManagerMock).stop();
-                    will(returnValue(loggingManagerMock));
-                }
-                one(getMavenResolver().mavenSettingsSupplier).supply(getInstallDeployTask());
-                one(getMavenResolver().mavenSettingsSupplier).done();
-            }
-        });
-        getMavenResolver().commitPublishTransaction();
-        assertThat(attachedArtifact.getFile(), equalTo(classifierArtifact.getFile()));
-        assertThat(attachedArtifact.getType(), equalTo(classifierArtifact.getType()));
-        assertThat(attachedArtifact.getClassifier(), equalTo(classifierArtifact.getClassifier()));
-    }
-
-    private static Matcher<Pom> pomMatcher(final File expectedPomFile, final Project expectedAntProject) {
-        return new BaseMatcher<Pom>() {
-            public void describeTo(Description description) {
-                description.appendText("matching pom");
-            }
-
-            public boolean matches(Object actual) {
-                Pom actualPom = (Pom) actual;
-                return actualPom.getFile().equals(expectedPomFile) && actualPom.getProject().equals(expectedAntProject);
-            }
-        };
-    }
-
-    @Test
-    public void setFilter() {
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).setFilter(publishFilterMock);
-        }});
-        getMavenResolver().setFilter(publishFilterMock);
-    }
-
-    @Test
-    public void getFilter() {
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            allowing(pomFilterContainerMock).getFilter();
-            will(returnValue(publishFilterMock));
-        }});
-        assertSame(publishFilterMock, getMavenResolver().getFilter());
-    }
-
-    @Test
-    public void setPom() {
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).setPom(pomMock);
-        }});
-        getMavenResolver().setPom(pomMock);
-    }
-
-    @Test
-    public void getPom() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterContainerMock).getPom();
-            will(returnValue(pomMock));
-        }});
-        assertSame(pomMock, getMavenResolver().getPom());
-    }
-
-    @Test
-    public void addFilter() {
-        final String testName = "somename";
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).addFilter(testName, publishFilterMock);
-        }});
-        getMavenResolver().addFilter(testName, publishFilterMock);
-    }
-
-    @Test
-    public void filter() {
-        final String testName = "somename";
-        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).filter(testName);
-            will(returnValue(publishFilterMock));
-        }});
-        assertSame(publishFilterMock, getMavenResolver().filter(testName));
-    }
-
-    @Test
-    public void pom() {
-        final String testName = "somename";
-        context.checking(new Expectations() {{
-            one(pomFilterContainerMock).pom(testName);
-            will(returnValue(pomMock));
-        }});
-        assertSame(pomMock, getMavenResolver().pom(testName));
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
deleted file mode 100644
index bb0e53f..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
+++ /dev/null
@@ -1,106 +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.maven.artifact.ant.AttachedArtifact;
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
-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.PomFilterContainer;
-import org.gradle.api.internal.Factory;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Set;
-
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class BaseMavenDeployerTest extends AbstractMavenResolverTest {
-
-    private BaseMavenDeployer mavenDeployer = createMavenDeployer();
-
-    private Factory<CustomDeployTask> deployTaskFactoryMock = context.mock(Factory.class);
-    private CustomDeployTask deployTaskMock = context.mock(CustomDeployTask.class);
-
-    private PlexusContainer plexusContainerMock = context.mock(PlexusContainer.class);
-    private RemoteRepository testRepository = new RemoteRepository();
-    private RemoteRepository testSnapshotRepository = new RemoteRepository();
-
-    private Configuration configurationStub = context.mock(Configuration.class);
-
-    protected BaseMavenDeployer createMavenDeployer() {
-        return new BaseMavenDeployer(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
-    }
-
-    protected AbstractMavenResolver getMavenResolver() {
-        return mavenDeployer;
-    }
-
-    protected InstallDeployTaskSupport getInstallDeployTask() {
-        return deployTaskMock;
-    }
-
-    protected PomFilterContainer createPomFilterContainerMock() {
-        return context.mock(PomFilterContainer.class);
-    }
-
-    public void setUp() {
-        super.setUp();
-        mavenDeployer = createMavenDeployer();
-        mavenDeployer.setDeployTaskFactory(deployTaskFactoryMock);
-        mavenDeployer.setRepository(testRepository);
-        mavenDeployer.setSnapshotRepository(testSnapshotRepository);
-        mavenDeployer.setConfiguration(configurationStub);
-        mavenDeployer.setUniqueVersion(false);
-    }
-
-    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();
-                will(returnValue(protocolJars));
-                allowing(deployTaskFactoryMock).create();
-                will(returnValue(getInstallDeployTask()));
-                allowing(deployTaskMock).getContainer();
-                will(returnValue(plexusContainerMock));
-                for (File protocolProviderJar : protocolJars) {
-                    one(plexusContainerMock).addJarResource(protocolProviderJar);
-                }
-                one(deployTaskMock).setUniqueVersion(mavenDeployer.isUniqueVersion());
-                one(deployTaskMock).addRemoteRepository(testRepository);
-                one(deployTaskMock).addRemoteSnapshotRepository(testSnapshotRepository);
-        }});
-        super.checkTransaction(defaultMavenDeployments, attachedArtifact, classifierArtifact);
-    }
-
-    @Test
-    public void init() {
-        mavenDeployer = new BaseMavenDeployer(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
-        assertTrue(mavenDeployer.isUniqueVersion());
-    }
-
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
deleted file mode 100644
index 02bf3d1..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
+++ /dev/null
@@ -1,71 +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.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.PomFilterContainer;
-import org.gradle.api.internal.Factory;
-import org.jmock.Expectations;
-
-import java.io.IOException;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class BaseMavenInstallerTest extends AbstractMavenResolverTest {
-    private BaseMavenInstaller mavenInstaller;
-
-    private Factory<CustomInstallTask> installTaskFactoryMock;
-    private CustomInstallTask installTaskMock;
-
-    protected BaseMavenInstaller createMavenInstaller() {
-        return new BaseMavenInstaller(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
-    }
-
-    protected PomFilterContainer createPomFilterContainerMock() {
-        return context.mock(PomFilterContainer.class);
-    }
-
-    protected AbstractMavenResolver getMavenResolver() {
-        return mavenInstaller;
-    }
-
-    protected InstallDeployTaskSupport getInstallDeployTask() {
-        return installTaskMock;
-    }
-
-    public void setUp() {
-        super.setUp();
-        installTaskFactoryMock = context.mock(Factory.class);
-        installTaskMock = context.mock(CustomInstallTask.class);
-        mavenInstaller = createMavenInstaller();
-        mavenInstaller.setInstallTaskFactory(installTaskFactoryMock);
-    }
-
-    protected void checkTransaction(final Set<DefaultMavenDeployment> deployableUnits, AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
-        context.checking(new Expectations() {
-            {
-                allowing(installTaskFactoryMock).create();
-                will(returnValue(getInstallDeployTask()));
-            }
-        });
-        super.checkTransaction(deployableUnits, attachedArtifact, classifierArtifact);
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainerTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainerTest.java
deleted file mode 100644
index f488350..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainerTest.java
+++ /dev/null
@@ -1,187 +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.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.PublishFilter;
-import org.gradle.api.internal.Factory;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class BasePomFilterContainerTest {
-    private static final String TEST_NAME = "testName";
-    
-    private BasePomFilterContainer pomFilterContainer;
-    protected Factory<MavenPom> mavenPomFactoryMock;
-    protected MavenPom pomMock;
-    protected PomFilter pomFilterMock;
-    protected PublishFilter publishFilterMock;
-
-
-    protected BasePomFilterContainer createPomFilterContainer() {
-        return new BasePomFilterContainer(mavenPomFactoryMock);
-    }
-
-    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
-
-    @Before
-    public void setUp() {
-        pomFilterMock = context.mock(PomFilter.class);
-        mavenPomFactoryMock = context.mock(Factory.class);
-        pomMock = context.mock(MavenPom.class);
-        publishFilterMock = context.mock(PublishFilter.class);
-        context.checking(new Expectations() {
-            {
-                allowing(mavenPomFactoryMock).create();
-                will(returnValue(pomMock));
-            }
-        });
-        pomFilterContainer = createPomFilterContainer();
-        pomFilterContainer.setDefaultPomFilter(pomFilterMock);
-    }
-
-    @Test
-    public void init() {
-        pomFilterContainer = new BasePomFilterContainer(mavenPomFactoryMock);
-        assertNotNull(pomFilterContainer.getPom());
-        assertSame(PublishFilter.ALWAYS_ACCEPT, pomFilterContainer.getFilter());
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void getFilterWithNullName() {
-        pomFilterContainer.filter((String) null);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void getPomWithNullName() {
-        pomFilterContainer.pom((String) null);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void addFilterWithNullName() {
-        pomFilterContainer.addFilter(null, PublishFilter.ALWAYS_ACCEPT);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void addFilterWithNullFilter() {
-        pomFilterContainer.addFilter("somename", (PublishFilter) null);
-    }
-
-    @Test
-    public void getFilter() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
-        }});
-        assertSame(publishFilterMock, pomFilterContainer.getFilter());
-    }
-
-    @Test
-    public void setFilter() {
-        context.checking(new Expectations() {{
-            one(pomFilterMock).setFilter(publishFilterMock);
-        }});
-        pomFilterContainer.setFilter(publishFilterMock);
-    }
-
-    @Test
-    public void getPom() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterMock).getPomTemplate(); will(returnValue(pomMock));
-        }});
-        assertSame(pomMock, pomFilterContainer.getPom());
-    }
-
-    @Test
-    public void setPom() {
-        context.checking(new Expectations() {{
-            allowing(pomFilterMock).setPomTemplate(pomMock);
-        }});
-        pomFilterContainer.setPom(pomMock);
-    }
-
-
-    @Test
-    public void addFilter() {
-        MavenPom pom = pomFilterContainer.addFilter(TEST_NAME, publishFilterMock);
-        assertSame(pom, pomMock);
-        assertSame(pomMock, pomFilterContainer.pom(TEST_NAME));
-        assertSame(publishFilterMock, pomFilterContainer.filter(TEST_NAME));
-    }
-
-    @Test
-    public void getActivePomFiltersWithDefault() {
-        Iterator<PomFilter> pomFilterIterator = pomFilterContainer.getActivePomFilters().iterator();
-        assertSame(pomFilterMock, pomFilterIterator.next());
-        assertFalse(pomFilterIterator.hasNext());
-    }
-
-    @Test
-    public void getActivePomFiltersWithAdditionalFilters() {
-        PublishFilter filter1 = context.mock(PublishFilter.class, "filter1");
-        PublishFilter filter2 = context.mock(PublishFilter.class, "filter2");
-        String testName1 = "name1";
-        String testName2 = "name2";
-        pomFilterContainer.addFilter(testName1, filter1);
-        pomFilterContainer.addFilter(testName2, filter2);
-        Set actualActiveFilters = getSetFromIterator(pomFilterContainer.getActivePomFilters());
-        assertEquals(2, actualActiveFilters.size());
-        checkIfInSet(testName1, filter1, actualActiveFilters);
-        checkIfInSet(testName2, filter2, actualActiveFilters);
-    }
-
-    private void checkIfInSet(String expectedName, PublishFilter expectedPublishFilter, Set<PomFilter> filters) {
-        for (PomFilter pomFilter : filters) {
-            if (areEqualPomFilter(expectedName, expectedPublishFilter, pomFilter)) {
-                return;
-            }
-        }
-        fail("Not in Set");
-    }
-
-    private Set getSetFromIterator(Iterable<PomFilter> pomFilterIterable) {
-        HashSet<PomFilter> filters = new HashSet<PomFilter>();
-        for (PomFilter pomFilter : pomFilterIterable) {
-            filters.add(pomFilter);
-        }
-        return filters;
-    }
-
-
-    private boolean areEqualPomFilter(String expectedName, PublishFilter expectedPublishFilter, PomFilter pomFilter) {
-        if (!expectedName.equals(pomFilter.getName())) {
-            return false;
-        }
-        if (!(expectedPublishFilter == pomFilter.getFilter())) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.groovy
deleted file mode 100644
index 4dc30af..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.groovy
+++ /dev/null
@@ -1,100 +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.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
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPomFactory
-import org.gradle.api.internal.artifacts.publish.maven.ArtifactPom
-
-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/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
deleted file mode 100644
index 576c8be..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
+++ /dev/null
@@ -1,272 +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.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.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.PomDependenciesConverter;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.util.GUtil;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultArtifactPomTest {
-    private DefaultArtifactPom artifactPom;
-    private MavenPom testPom;
-
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-
-    Mockery context = new JUnit4Mockery();
-
-    @Before
-    public void setUp() {
-        testPom = new DefaultMavenPom(context.mock(ConfigurationContainer.class), new DefaultConf2ScopeMappingContainer(),
-                context.mock(PomDependenciesConverter.class), context.mock(FileResolver.class));
-        artifactPom = new DefaultArtifactPom(testPom);
-    }
-
-    @Test
-    public void pomWithMainArtifact() {
-        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
-        File mainFile = new File("someFile");
-
-        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");
-
-        artifactPom.addArtifact(mainArtifact, mainFile);
-        artifactPom.addArtifact(classifierArtifact, classifierFile);
-
-        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));
-
-        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 pomWithClassifierArtifactsOnly() {
-        File classifierFile = new File("someClassifierFile");
-        Artifact classifierArtifact = createTestArtifact("someName", "javadoc", "zip");
-
-        artifactPom.addArtifact(classifierArtifact, classifierFile);
-
-        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("someName", "javadoc");
-        artifactPom.addArtifact(classifierArtifact, classifierFile);
-        artifactPom.addArtifact(classifierArtifact, classifierFile);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void addMainArtifactTwiceShouldThrowInvalidUserDataEx() {
-        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
-        File mainFile = new File("someFile");
-        artifactPom.addArtifact(mainArtifact, mainFile);
-        artifactPom.addArtifact(mainArtifact, mainFile);
-    }
-
-    @Test
-    public void cannotAddMultipleArtifactsWithTheSameTypeAndClassifier() {
-
-        // No classifier
-        Artifact mainArtifact = createTestArtifact("someName", null);
-        artifactPom.addArtifact(mainArtifact, new File("someFile"));
-
-        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"));
-    }
-
-    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."));
-        }
-    }
-
-    @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) {
-        return createTestArtifact(name, classifier, "jar");
-    }
-
-    private Artifact createTestArtifact(String name, String classifier, String type) {
-        Map<String, String> extraAttributes = new HashMap<String, String>();
-        if (classifier != null) {
-            extraAttributes.put(Dependency.CLASSIFIER, classifier);
-        }
-        return new DefaultArtifact(ModuleRevisionId.newInstance("org", name, "1.0"), null, name, type, type, extraAttributes);
-    }
-
-    @Test
-    public void writePom() {
-        final MavenPom mavenPomMock = context.mock(MavenPom.class);
-        DefaultArtifactPom artifactPom = new DefaultArtifactPom(mavenPomMock);
-        final File somePomFile = new File(tmpDir.getDir(), "someDir/somePath");
-        context.checking(new Expectations() {{
-            allowing(mavenPomMock).getArtifactId();
-            will(returnValue("artifactId"));
-            one(mavenPomMock).writeTo(with(any(FileOutputStream.class)));
-        }});
-
-        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));
-    }
-
-    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/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilterTest.java
deleted file mode 100644
index 308f067..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilterTest.java
+++ /dev/null
@@ -1,55 +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.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.PublishFilter;
-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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultPomFilterTest {
-    private static final String TEST_NAME = "TEST_NAME";
-
-    private DefaultPomFilter pomFilter;
-    private MavenPom mavenPomMock;
-
-    private PublishFilter publishFilterMock;
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    @Before
-    public void setUp() {
-        mavenPomMock = context.mock(MavenPom.class);
-        publishFilterMock = context.mock(PublishFilter.class);
-        pomFilter = new DefaultPomFilter(TEST_NAME, mavenPomMock, publishFilterMock);
-    }
-
-    @Test
-    public void testGetName() {
-        assertEquals(TEST_NAME, pomFilter.getName());
-        assertSame(mavenPomMock, pomFilter.getPomTemplate());
-        assertSame(publishFilterMock, pomFilter.getFilter());
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployerTest.groovy
deleted file mode 100644
index 2d8b610..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployerTest.groovy
+++ /dev/null
@@ -1,117 +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.groovy
-
-import org.gradle.api.artifacts.maven.PomFilterContainer
-import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenDeployer
-import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenDeployerTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.assertEquals
-
-/**
- * @author Hans Dockter
- */
- at RunWith (org.jmock.integration.junit4.JMock.class)
-class DefaultGroovyMavenDeployerTest extends BaseMavenDeployerTest {
-    private DefaultGroovyMavenDeployer groovyMavenDeployer;
-
-    protected PomFilterContainer createPomFilterContainerMock() {
-        context.mock(PomFilterContainer.class);
-    }
-
-    protected BaseMavenDeployer createMavenDeployer() {
-        groovyMavenDeployer = new DefaultGroovyMavenDeployer(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock)
-    }
-
-    @Before
-    void setUp() {
-        super.setUp();
-    }
-
-    @Test
-    void repositoryBuilder() {
-        checkRepositoryBuilder(DefaultGroovyMavenDeployer.REPOSITORY_BUILDER)
-    }
-
-    @Test
-    void snapshotRepositoryBuilder() {
-        checkRepositoryBuilder(DefaultGroovyMavenDeployer.SNAPSHOT_REPOSITORY_BUILDER)
-    }
-
-
-    void checkRepositoryBuilder(String repositoryName) {
-        String testUrl = 'testUrl'
-        String testProxyHost = 'hans'
-        String testUserName = 'userId'
-        String testSnapshotUpdatePolicy = 'always'
-        String testReleaseUpdatePolicy = 'never'
-        groovyMavenDeployer."$repositoryName"(url: testUrl) {
-            authentication(userName: testUserName)
-            proxy(host: testProxyHost)
-            releases(updatePolicy: testReleaseUpdatePolicy)
-            snapshots(updatePolicy: testSnapshotUpdatePolicy)
-        }
-        assertEquals(testUrl, groovyMavenDeployer."$repositoryName".url)
-        assertEquals(testUserName, groovyMavenDeployer."$repositoryName".authentication.userName)
-        assertEquals(testProxyHost, groovyMavenDeployer."$repositoryName".proxy.host)
-        assertEquals(testReleaseUpdatePolicy, groovyMavenDeployer."$repositoryName".releases.updatePolicy)
-        assertEquals(testSnapshotUpdatePolicy, groovyMavenDeployer."$repositoryName".snapshots.updatePolicy)
-    }
-
-    @Test
-    void filter() {
-        Closure testClosure = {}
-        context.checking {
-            one(pomFilterContainerMock).filter(testClosure)
-        }
-        groovyMavenDeployer.filter(testClosure)
-    }
-
-    @Test
-    void pom() {
-        Closure testClosure = {}
-        context.checking {
-            one(pomFilterContainerMock).pom(testClosure)
-        }
-        groovyMavenDeployer.pom(testClosure)
-    }
-
-    @Test
-    void pomWithName() {
-        Closure testClosure = {}
-        String testName = 'somename'
-        context.checking {
-            one(pomFilterContainerMock).pom(testName, testClosure)
-        }
-        groovyMavenDeployer.pom(testName, testClosure)
-    }
-
-    @Test
-    void addFilter() {
-        Closure testClosure = {}
-        String testName = 'somename'
-        context.checking {
-            one(pomFilterContainerMock).addFilter(testName, testClosure)
-        }
-        groovyMavenDeployer.addFilter(testName, testClosure)
-    }
-}
-
-
-
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyPomFilterContainerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyPomFilterContainerTest.groovy
deleted file mode 100644
index a40fa70..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyPomFilterContainerTest.groovy
+++ /dev/null
@@ -1,122 +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.
- */
-
-
-
-import java.lang.reflect.Proxy
-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.deploy.BasePomFilterContainer
-import org.gradle.api.internal.artifacts.publish.maven.deploy.BasePomFilterContainerTest
-import org.hamcrest.BaseMatcher
-import org.hamcrest.Description
-import org.hamcrest.Factory
-import org.hamcrest.Matcher
-import org.jmock.integration.junit4.JMock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.assertSame
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock)
-class DefaultGroovyPomFilterContainerTest extends BasePomFilterContainerTest {
-    static final String TEST_NAME = "somename"
-    PomFilterContainer groovyPomFilterContainer
-
-    @Before
-    public void setUp() {
-        super.setUp()
-    }
-
-    protected BasePomFilterContainer createPomFilterContainer() {
-        return groovyPomFilterContainer = new BasePomFilterContainer(mavenPomFactoryMock);
-    }
-
-    @Test
-    public void addFilterWithClosure() {
-        Closure closureFilter = {}
-        MavenPom pom = groovyPomFilterContainer.addFilter(TEST_NAME, closureFilter)
-        assertSame(pomMock, pom);
-        assertSame(pomMock, groovyPomFilterContainer.pom(TEST_NAME));
-        assertSame(closureFilter, getClosureFromProxy(groovyPomFilterContainer.filter(TEST_NAME)));
-    }
-
-    private Closure getClosureFromProxy(PublishFilter filter) {
-        Proxy.getInvocationHandler(filter).delegate
-    }
-
-    @Test
-    public void filterWithClosure() {
-        Closure closureFilter = {}
-        context.checking {
-            one(pomFilterMock).setFilter(withParam(FilterMatcher.equalsFilter(closureFilter)))
-        }
-        groovyPomFilterContainer.filter(closureFilter)
-    }
-
-    @Test
-    public void defaultPomWithClosure() {
-        String testGroup = "testGroup"
-        context.checking {
-            one(pomFilterMock).getPomTemplate(); will(returnValue(pomMock))
-            one(pomMock).setGroupId(testGroup);
-        }
-        groovyPomFilterContainer.pom {
-            groupId = testGroup
-        }
-    }
-
-    @Test
-    public void pomWithClosure() {
-        groovyPomFilterContainer.addFilter(TEST_NAME, {})
-        String testGroup = "testGroup"
-        context.checking {
-            one(pomMock).setGroupId(testGroup);
-        }
-        groovyPomFilterContainer.pom(TEST_NAME) {
-            groupId = testGroup
-        }
-    }
-}
-
-public class FilterMatcher extends BaseMatcher {
-    Closure filter
-
-    public void describeTo(Description description) {
-        description.appendText("matching filter");
-    }
-
-    public boolean matches(Object actual) {
-        return getClosureFromProxy(actual) == filter;
-    }
-
-    private Closure getClosureFromProxy(PublishFilter filter) {
-        Proxy.getInvocationHandler(filter).delegate
-    }
-
-
-    @Factory
-    public static Matcher<PublishFilter> equalsFilter(Closure filter) {
-        return new FilterMatcher(filter: filter);
-    }
-
-}
-
-
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/EmptyMavenSettingsSupplierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/EmptyMavenSettingsSupplierTest.groovy
deleted file mode 100644
index 9494388..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/EmptyMavenSettingsSupplierTest.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 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.mvnsettings
-
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport
-import spock.lang.Specification
-
-/**
- * Author: Szczepan Faber, created at: 3/29/11
- */
-class EmptyMavenSettingsSupplierTest extends Specification {
-
-    def EmptyMavenSettingsSupplier supplier = new EmptyMavenSettingsSupplier()
-    InstallDeployTaskSupport support = Mock()
-
-    def "supplies empty settings"() {
-        when:
-        supplier.supply(support)
-
-        then:
-        supplier.settingsXml.text == '<settings/>'
-//        1 * support.setSettingsFile(supplier.settingsXml) //not sure why it doesn't work
-    }
-
-    def "deletes file when done"() {
-        when:
-        supplier.supply(support)
-        supplier.done()
-
-        then:
-        !supplier.settingsXml.exists()
-    }
-
-    def "done operation must be safe"() {
-        when:
-        supplier.done()
-
-        then:
-        noExceptionThrown()
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MaybeUserMavenSettingsSupplierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MaybeUserMavenSettingsSupplierTest.groovy
deleted file mode 100644
index 5a447c1..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/mvnsettings/MaybeUserMavenSettingsSupplierTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2011 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.mvnsettings
-
-import org.apache.maven.artifact.ant.InstallDeployTaskSupport
-import spock.lang.Specification
-
-/**
- * Author: Szczepan Faber, created at: 3/29/11
- */
-class MaybeUserMavenSettingsSupplierTest extends Specification {
-
-    InstallDeployTaskSupport support = Mock()
-    def supplier = new MaybeUserMavenSettingsSupplier()
-
-    def "supplies empty settings when user settings not found"() {
-        given:
-        supplier.emptySettingsSupplier = Mock(EmptyMavenSettingsSupplier)
-        supplier.mavenSettingsProvider = Mock(MavenSettingsProvider)
-
-        supplier.mavenSettingsProvider.getUserSettingsFile() >> { new File('does not exist') }
-
-        when:
-        supplier.supply(support)
-        supplier.done()
-
-        then:
-        1 * supplier.emptySettingsSupplier.supply(support)
-        1 * supplier.emptySettingsSupplier.done()
-    }
-
-    def "supplies user settings when file exists"() {
-        given:
-        supplier.emptySettingsSupplier = Mock(EmptyMavenSettingsSupplier)
-        supplier.mavenSettingsProvider = Mock(MavenSettingsProvider)
-
-        def concreteFile = File.createTempFile('I exist', ', really')
-        concreteFile.deleteOnExit()
-        supplier.mavenSettingsProvider.getUserSettingsFile() >> { concreteFile }
-
-        when:
-        supplier.supply(support)
-        supplier.done()
-
-        then:
-        1 * support.setSettingsFile(concreteFile)
-        0 * supplier.emptySettingsSupplier.supply(support)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy
new file mode 100644
index 0000000..c43917c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.repositories
+
+import org.apache.ivy.core.cache.RepositoryCacheManager
+import org.apache.ivy.plugins.resolver.FileSystemResolver
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import spock.lang.Specification
+
+class DefaultFlatDirArtifactRepositoryTest extends Specification {
+    final FileResolver fileResolver = Mock()
+    final RepositoryTransportFactory transportFactory = Mock()
+    final DefaultFlatDirArtifactRepository repository = new DefaultFlatDirArtifactRepository(fileResolver, transportFactory)
+
+    def "creates a repository with multiple root directories"() {
+        given:
+        def dir1 = new File('a')
+        def dir2 = new File('b')
+        _ * fileResolver.resolveFiles(['a', 'b']) >> new SimpleFileCollection(dir1, dir2)
+        1 * transportFactory.localCacheManager >> Mock(RepositoryCacheManager)
+
+        and:
+        repository.dirs('a', 'b')
+
+        when:
+        def repo = repository.createResolver()
+
+        then:
+        repo instanceof FileSystemResolver
+        def expectedPatterns = [
+                "$dir1.absolutePath/[artifact]-[revision](-[classifier]).[ext]",
+                "$dir1.absolutePath/[artifact](-[classifier]).[ext]",
+                "$dir2.absolutePath/[artifact]-[revision](-[classifier]).[ext]",
+                "$dir2.absolutePath/[artifact](-[classifier]).[ext]"
+        ]
+        repo.ivyPatterns == []
+        repo.artifactPatterns == expectedPatterns
+        repo.allownomd
+    }
+
+    def "fails when no directories specified"() {
+        given:
+        _ * fileResolver.resolveFiles(_) >> new SimpleFileCollection()
+
+        when:
+        repository.createResolver()
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == 'You must specify at least one directory for a flat directory repository.'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
index 0906c77..4fbcc72 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
@@ -15,43 +15,225 @@
  */
 package org.gradle.api.internal.artifacts.repositories
 
+import org.apache.ivy.core.cache.RepositoryCacheManager
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.artifacts.repositories.transport.file.FileExternalResourceRepository
+import org.gradle.api.internal.externalresource.transport.file.FileTransport
+import org.gradle.api.internal.externalresource.transport.http.HttpTransport
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.file.FileResolver
 import spock.lang.Specification
-import org.apache.ivy.plugins.resolver.URLResolver
-import org.apache.ivy.plugins.resolver.RepositoryResolver
 
 class DefaultIvyArtifactRepositoryTest extends Specification {
-    final DefaultIvyArtifactRepository repository = new DefaultIvyArtifactRepository()
+    final FileResolver fileResolver = Mock()
+    final PasswordCredentials credentials = Mock()
+    final RepositoryTransportFactory transportFactory = Mock()
+    final RepositoryCacheManager cacheManager = Mock()
+    final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
+    final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
+    final DefaultIvyArtifactRepository repository = new DefaultIvyArtifactRepository(
+            fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, cachedExternalResourceIndex
+    )
 
-    def createsAUrlResolver() {
+    def "cannot create a resolver for url with unknown scheme"() {
         repository.name = 'name'
-        repository.artifactPattern 'pattern'
+        repository.artifactPattern 'pattern1'
+
+        given:
+        fileResolver.resolveUri('pattern1') >> new URI('scheme:resource1')
 
         when:
-        def resolvers = []
-        repository.createResolvers(resolvers)
+        repository.createResolver()
 
         then:
-        resolvers.size() == 1
-        def resolver = resolvers[0]
-        resolver instanceof URLResolver
-        resolver.name == 'name'
-        resolver.artifactPatterns == ['pattern'] as List
+        InvalidUserDataException e = thrown()
+        e.message == "You may only specify 'file', 'http' and 'https' urls for an ivy repository."
+    }
+
+    def "cannot creates a resolver for mixed url scheme"() {
+        repository.name = 'name'
+        repository.artifactPattern 'pattern1'
+        repository.artifactPattern 'pattern2'
+
+        given:
+        fileResolver.resolveUri('pattern1') >> new URI('http:resource1')
+        fileResolver.resolveUri('pattern2') >> new URI('file:resource2')
+
+        when:
+        repository.createResolver()
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "You cannot mix file and http(s) urls for a single ivy repository. Please declare 2 separate repositories."
     }
 
-    def createsARepositoryResolverForHttpPatterns() {
+    def "creates a resolver for HTTP patterns"() {
         repository.name = 'name'
         repository.artifactPattern 'http://host/[organisation]/[artifact]-[revision].[ext]'
+        repository.artifactPattern 'http://other/[module]/[artifact]-[revision].[ext]'
+        repository.ivyPattern 'http://host/[module]/ivy-[revision].xml'
+
+        given:
+        fileResolver.resolveUri('http://host/') >> new URI('http://host/')
+        fileResolver.resolveUri('http://other/') >> new URI('http://other/')
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolvers = []
-        repository.createResolvers(resolvers)
+        def resolver = repository.createResolver()
 
         then:
-        resolvers.size() == 1
-        def resolver = resolvers[0]
-        resolver instanceof RepositoryResolver
-        resolver.repository instanceof CommonsHttpClientBackedRepository
+        resolver instanceof ExternalResourceResolver
+        resolver.repository instanceof ExternalResourceRepository
         resolver.name == 'name'
-        resolver.artifactPatterns == ['http://host/[organisation]/[artifact]-[revision].[ext]'] as List
+        resolver.artifactPatterns == ['http://host/[organisation]/[artifact]-[revision].[ext]', 'http://other/[module]/[artifact]-[revision].[ext]'] as List
+        resolver.ivyPatterns == ['http://host/[module]/ivy-[revision].xml'] as List
     }
+
+    def "creates a resolver for file patterns"() {
+        repository.name = 'name'
+        repository.artifactPattern 'repo/[organisation]/[artifact]-[revision].[ext]'
+        repository.artifactPattern 'repo/[organisation]/[module]/[artifact]-[revision].[ext]'
+        repository.ivyPattern 'repo/[organisation]/[module]/ivy-[revision].xml'
+        def file = new File("test")
+        def fileUri = file.toURI()
+
+        given:
+        fileResolver.resolveUri('repo/') >> fileUri
+        transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager)
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        resolver instanceof ExternalResourceResolver
+        resolver.repository instanceof FileExternalResourceRepository
+        resolver.name == 'name'
+        resolver.artifactPatterns == ["${file.absolutePath}/[organisation]/[artifact]-[revision].[ext]", "${file.absolutePath}/[organisation]/[module]/[artifact]-[revision].[ext]"] as List
+        resolver.ivyPatterns == ["${file.absolutePath}/[organisation]/[module]/ivy-[revision].xml"] as List
+    }
+
+    def "uses gradle patterns with specified url and default layout"() {
+        repository.name = 'name'
+        repository.url = 'http://host'
+
+        given:
+        fileResolver.resolveUri('http://host') >> new URI('http://host/')
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        resolver instanceof ExternalResourceResolver
+        resolver.repository instanceof ExternalResourceRepository
+        resolver.name == 'name'
+        resolver.artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])'] as List
+        resolver.ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml"] as List
+    }
+
+    def "uses maven patterns with specified url and maven layout"() {
+        repository.name = 'name'
+        repository.url = 'http://host'
+        repository.layout 'maven'
+
+        given:
+        fileResolver.resolveUri('http://host') >> new URI('http://host/')
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        resolver instanceof ExternalResourceResolver
+        resolver.repository instanceof ExternalResourceRepository
+        resolver.name == 'name'
+        resolver.artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])'] as List
+        resolver.ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml"] as List
+        resolver.m2compatible
+    }
+
+    def "uses specified base url with configured pattern layout"() {
+        repository.name = 'name'
+        repository.url = 'http://host'
+        repository.layout 'pattern', {
+            artifact '[module]/[revision]/[artifact](.[ext])'
+            ivy '[module]/[revision]/ivy.xml'
+        }
+
+        given:
+        fileResolver.resolveUri('http://host') >> new URI('http://host/')
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        resolver instanceof ExternalResourceResolver
+        resolver.repository instanceof ExternalResourceRepository
+        resolver.name == 'name'
+        resolver.artifactPatterns == ['http://host/[module]/[revision]/[artifact](.[ext])'] as List
+        resolver.ivyPatterns == ["http://host/[module]/[revision]/ivy.xml"] as List
+    }
+
+    def "combines layout patterns with additionally specified patterns"() {
+        repository.name = 'name'
+        repository.url = 'http://host/'
+        repository.artifactPattern 'http://host/[other]/artifact'
+        repository.ivyPattern 'http://host/[other]/ivy'
+
+        given:
+        fileResolver.resolveUri('http://host/') >> new URI('http://host/')
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        resolver instanceof ExternalResourceResolver
+        resolver.repository instanceof ExternalResourceRepository
+        resolver.name == 'name'
+        resolver.artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])', 'http://host/[other]/artifact'] as List
+        resolver.ivyPatterns == ["http://host/[organisation]/[module]/[revision]/ivy-[revision].xml", 'http://host/[other]/ivy'] as List
+    }
+
+    def "uses artifact pattern for ivy files when no ivy pattern provided"() {
+        repository.name = 'name'
+        repository.url = 'http://host'
+        repository.layout 'pattern', {
+            artifact '[layoutPattern]'
+        }
+        repository.artifactPattern 'http://other/[additionalPattern]'
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        given:
+        fileResolver.resolveUri('http://host') >> new URI('http://host')
+        fileResolver.resolveUri('http://other/') >> new URI('http://other/')
+
+        when:
+        def resolver = repository.createResolver()
+
+        then:
+        resolver.artifactPatterns == ['http://host/[layoutPattern]', 'http://other/[additionalPattern]'] as List
+        resolver.ivyPatterns == resolver.artifactPatterns
+    }
+
+    def "fails when no artifact patterns specified"() {
+        given:
+        transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
+
+        when:
+        repository.createResolver()
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == 'You must specify a base url or at least one artifact pattern for an Ivy repository.'
+    }
+
+    private HttpTransport createHttpTransport(String name, PasswordCredentials credentials) {
+        return new HttpTransport(name, credentials, cacheManager)
+    }
+
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy
new file mode 100644
index 0000000..576846d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 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.repositories
+
+import org.apache.ivy.core.cache.RepositoryCacheManager
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.externalresource.transport.file.FileTransport
+import org.gradle.api.internal.externalresource.transport.http.HttpTransport
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
+
+class DefaultMavenArtifactRepositoryTest extends Specification {
+    final FileResolver resolver = Mock()
+    final PasswordCredentials credentials = Mock()
+    final RepositoryTransportFactory transportFactory = Mock()
+    final RepositoryCacheManager cacheManager = Mock()
+    final LocallyAvailableResourceFinder locallyAvailableResourceFinder = Mock()
+    final CachedExternalResourceIndex cachedExternalResourceIndex = Mock()
+
+    final DefaultMavenArtifactRepository repository = new DefaultMavenArtifactRepository(resolver, credentials, transportFactory, locallyAvailableResourceFinder, cachedExternalResourceIndex)
+
+    def "creates local repository"() {
+        given:
+        def file = new File('repo')
+        def uri = file.toURI()
+        _ * resolver.resolveUri('repo-dir') >> uri
+        transportFactory.createFileTransport('repo') >> new FileTransport('repo', cacheManager)
+
+        and:
+        repository.name = 'repo'
+        repository.url = 'repo-dir'
+
+        when:
+        def repo = repository.createResolver()
+
+        then:
+        repo instanceof MavenResolver
+        repo.root == "${file.absolutePath}/"
+    }
+
+    def "creates http repository"() {
+        given:
+        def uri = new URI("http://localhost:9090/repo")
+        _ * resolver.resolveUri('repo-dir') >> uri
+        _ * credentials.getUsername() >> 'username'
+        _ * credentials.getPassword() >> 'password'
+        transportFactory.createHttpTransport('repo', credentials) >> createHttpTransport("repo", credentials)
+        cacheManager.name >> 'cache'
+        0 * _._
+
+        and:
+        repository.name = 'repo'
+        repository.url = 'repo-dir'
+
+        when:
+        def repo = repository.createResolver()
+
+        then:
+        repo instanceof MavenResolver
+        repo.root == "${uri}/"
+    }
+
+    def "creates repository with additional artifact URLs"() {
+        given:
+        def uri = new URI("http://localhost:9090/repo")
+        def uri1 = new URI("http://localhost:9090/repo1")
+        def uri2 = new URI("http://localhost:9090/repo2")
+        _ * resolver.resolveUri('repo-dir') >> uri
+        _ * resolver.resolveUri('repo1') >> uri1
+        _ * resolver.resolveUri('repo2') >> uri2
+        transportFactory.createHttpTransport('repo', credentials) >> createHttpTransport("repo", credentials)
+
+        and:
+        repository.name = 'repo'
+        repository.url = 'repo-dir'
+        repository.artifactUrls('repo1', 'repo2')
+
+        when:
+        def repo = repository.createResolver()
+
+        then:
+        repo instanceof MavenResolver
+        repo.root == "${uri}/"
+        repo.artifactPatterns.size() == 3
+        repo.artifactPatterns.any { it.startsWith uri.toString() }
+        repo.artifactPatterns.any { it.startsWith uri1.toString() }
+        repo.artifactPatterns.any { it.startsWith uri2.toString() }
+    }
+
+    private HttpTransport createHttpTransport(String repo, PasswordCredentials credentials) {
+        return new HttpTransport(repo, credentials, cacheManager)
+    }
+
+    def "fails when no root url specified"() {
+        when:
+        repository.createResolver()
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == 'You must specify a URL for a Maven repository.'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
index efead5d..7e4cb92 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
@@ -16,112 +16,137 @@
 
 package org.gradle.api.internal.artifacts.repositories
 
+import org.apache.ivy.plugins.resolver.DependencyResolver
 import org.gradle.api.InvalidUserDataException
-import org.gradle.api.artifacts.ResolverContainer
-import org.gradle.api.artifacts.maven.MavenFactory
-import org.gradle.api.internal.Factory
-import org.gradle.api.internal.artifacts.ivyservice.GradleIBiblioResolver
-import org.gradle.api.internal.artifacts.publish.maven.LocalMavenCacheLocator
+import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.api.artifacts.repositories.ArtifactRepository
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator
+import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
+import org.gradle.api.internal.file.FileResolver
 import org.gradle.util.JUnit4GroovyMockery
+import org.hamcrest.Matchers
 import org.jmock.integration.junit4.JMock
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.apache.ivy.plugins.resolver.*
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
 
 /**
  * @author Hans Dockter
  */
 @RunWith(JMock.class)
 class DefaultResolverFactoryTest {
-    static final String RESOLVER_URL = 'http://a.b.c/'
-    static final Map RESOLVER_MAP = [name: 'mapresolver', url: 'http://x.y.z/']
-    static final IBiblioResolver TEST_RESOLVER = new IBiblioResolver()
-    static {
-        TEST_RESOLVER.name = 'ivyResolver'
-    }
-
+    static final URI RESOLVER_URL = new URI('http://a.b.c/')
     static final String TEST_REPO_NAME = 'reponame'
-    static final String TEST_REPO_URL = 'http://www.gradle.org'
-    static final File TEST_CACHE_DIR = 'somepath' as File
+    static final String TEST_REPO = 'http://www.gradle.org'
+    static final URI TEST_REPO_URL = new URI('http://www.gradle.org/')
+    static final URI TEST_REPO2_URL = new URI('http://www.gradleware.com/')
 
     final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    final LocalMavenCacheLocator localMavenCacheLocator = context.mock(LocalMavenCacheLocator.class)
-    final DefaultResolverFactory factory = new DefaultResolverFactory(context.mock(Factory.class), context.mock(MavenFactory.class), localMavenCacheLocator)
-
-    @Test(expected = InvalidUserDataException) public void testCreateResolver() {
-        checkMavenResolver(factory.createResolver(RESOLVER_URL), RESOLVER_URL, RESOLVER_URL)
-        checkMavenResolver(factory.createResolver(RESOLVER_MAP), RESOLVER_MAP.name, RESOLVER_MAP.url)
-        DependencyResolver resolver = factory.createResolver(TEST_RESOLVER)
-        assert resolver.is(TEST_RESOLVER)
-        def someIllegalDescription = new NullPointerException()
-        factory.createResolver(someIllegalDescription)
+    final LocalMavenRepositoryLocator localMavenRepoLocator = context.mock(LocalMavenRepositoryLocator.class)
+    final FileResolver fileResolver = context.mock(FileResolver.class)
+    final RepositoryTransportFactory transportFactory = context.mock(RepositoryTransportFactory.class)
+    final LocallyAvailableResourceFinder locallyAvailableResourceFinder = context.mock(LocallyAvailableResourceFinder.class)
+    final CachedExternalResourceIndex cachedExternalResourceIndex = context.mock(CachedExternalResourceIndex);
+
+
+    final DefaultResolverFactory factory = new DefaultResolverFactory(
+            localMavenRepoLocator, fileResolver, new DirectInstantiator(), transportFactory, locallyAvailableResourceFinder, cachedExternalResourceIndex
+    )
+
+    @Before public void setup() {
+        context.checking {
+            allowing(fileResolver).resolveUri('uri');
+            will(returnValue(RESOLVER_URL))
+            allowing(fileResolver).resolveUri(TEST_REPO);
+            will(returnValue(TEST_REPO_URL))
+            allowing(fileResolver).resolveUri('uri2');
+            will(returnValue(TEST_REPO2_URL))
+            allowing(fileResolver).resolveUri(withParam(Matchers.instanceOf(URI)));
+            will { uri -> return uri }
+        }
     }
 
-    private void checkMavenResolver(IBiblioResolver resolver, String name, String url) {
-        assert url == resolver.root
-        assert name == resolver.name
-        assert resolver.allownomd
+    @Test public void testCreateResolverWithStringDescription() {
+        def repository = factory.createRepository('uri')
+
+        assert repository instanceof DefaultMavenArtifactRepository
+        assert repository.url == RESOLVER_URL
+        assert repository.name == null
+        assert repository.artifactUrls.isEmpty()
     }
 
-    @Test
-    public void testCreateMavenRepoWithAdditionalJarUrls() {
-        String testUrl2 = 'http://www.gradle2.org'
-        DualResolver dualResolver = factory.createMavenRepoResolver(TEST_REPO_NAME, TEST_REPO_URL, testUrl2)
-        assert dualResolver.allownomd
-        checkIBiblio(dualResolver.ivyResolver, "_poms")
-        URLResolver urlResolver = dualResolver.artifactResolver
-        assert urlResolver.m2compatible
-        assert urlResolver.artifactPatterns.contains("$TEST_REPO_URL/$ResolverContainer.MAVEN_REPO_PATTERN" as String)
-        assert urlResolver.artifactPatterns.contains("$testUrl2/$ResolverContainer.MAVEN_REPO_PATTERN" as String)
-        assert "${TEST_REPO_NAME}_jars" == urlResolver.name
+    @Test public void testCreateResolverWithMapDescription() {
+        def repository = factory.createRepository([name: 'name', url: 'uri'])
+
+        assert repository instanceof DefaultMavenArtifactRepository
+        assert repository.url == RESOLVER_URL
+        assert repository.name == 'name'
+        assert repository.artifactUrls.isEmpty()
     }
 
-    @Test
-    public void testCreateMavenRepoWithNoAdditionalJarUrls() {
-        checkIBiblio(factory.createMavenRepoResolver(TEST_REPO_NAME, TEST_REPO_URL), "")
+    @Test public void testCreateResolverWithResolverDescription() {
+        DependencyResolver resolver = context.mock(DependencyResolver)
+        
+        ArtifactRepository repository = factory.createRepository(resolver)
+
+        assert repository instanceof FixedResolverArtifactRepository
+        assert repository.resolver == resolver
     }
 
-    private void checkIBiblio(IBiblioResolver iBiblioResolver, String expectedNameSuffix) {
-        assert iBiblioResolver.usepoms
-        assert iBiblioResolver.m2compatible
-        assert iBiblioResolver.allownomd
-        assert TEST_REPO_URL + '/' == iBiblioResolver.root
-        assert ResolverContainer.MAVEN_REPO_PATTERN == iBiblioResolver.pattern
-        assert "${TEST_REPO_NAME}$expectedNameSuffix" == iBiblioResolver.name
+    @Test public void testCreateResolverWithArtifactRepositoryDescription() {
+        ArtifactRepository repo = context.mock(ArtifactRepository)
+
+        assert factory.createRepository(repo) == repo
+    }
+
+    @Test(expected = InvalidUserDataException) public void testCreateResolverForUnknownDescription() {
+        def someIllegalDescription = new NullPointerException()
+        factory.createRepository(someIllegalDescription)
     }
 
     @Test public void testCreateFlatDirResolver() {
-        File dir1 = new File('/rootFolder')
-        File dir2 = new File('/rootFolder2')
-        String expectedName = 'libs'
-        FileSystemResolver resolver = factory.createFlatDirResolver(expectedName, [dir1, dir2] as File[])
-        def expectedPatterns = [
-                "$dir1.absolutePath/[artifact]-[revision](-[classifier]).[ext]",
-                "$dir1.absolutePath/[artifact](-[classifier]).[ext]",
-                "$dir2.absolutePath/[artifact]-[revision](-[classifier]).[ext]",
-                "$dir2.absolutePath/[artifact](-[classifier]).[ext]"
-        ]
-        assert expectedName == resolver.name
-        assert [] == resolver.ivyPatterns
-        assert expectedPatterns == resolver.artifactPatterns
-        assert resolver.allownomd
+        def repo =factory.createFlatDirRepository()
+        assert repo instanceof DefaultFlatDirArtifactRepository
     }
 
     @Test public void testCreateLocalMavenRepo() {
         File repoDir = new File(".m2/repository")
 
         context.checking {
-            one(localMavenCacheLocator).getLocalMavenCache()
+            one(localMavenRepoLocator).getLocalMavenRepository()
             will(returnValue(repoDir))
+            allowing(fileResolver).resolveUri(repoDir)
+            will(returnValue(repoDir.toURI()))
         }
 
-        def repo = factory.createMavenLocalResolver('name')
-        assert repo instanceof GradleIBiblioResolver
-        assert repo.root == repoDir.toURI().toString() + '/'
+        def repo = factory.createMavenLocalRepository()
+        assert repo instanceof DefaultMavenArtifactRepository
+        assert repo.url == repoDir.toURI()
+    }
+
+    @Test public void testCreateMavenCentralRepo() {
+        def centralUrl = new URI(RepositoryHandler.MAVEN_CENTRAL_URL)
+
+        context.checking {
+            allowing(fileResolver).resolveUri(RepositoryHandler.MAVEN_CENTRAL_URL)
+            will(returnValue(centralUrl))
+        }
+
+        def repo = factory.createMavenCentralRepository()
+        assert repo instanceof DefaultMavenArtifactRepository
+        assert repo.url == centralUrl
     }
 
     @Test public void createIvyRepository() {
         def repo = factory.createIvyRepository()
         assert repo instanceof DefaultIvyArtifactRepository
     }
+
+    @Test public void createMavenRepository() {
+        def repo = factory.createMavenRepository()
+        assert repo instanceof DefaultMavenArtifactRepository
+    }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
new file mode 100644
index 0000000..a0fa209
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 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.resolutioncache
+
+import org.gradle.CacheUsage
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+import org.gradle.cache.internal.CacheFactory
+import org.gradle.cache.internal.DefaultCacheRepository
+import org.gradle.testfixtures.internal.InMemoryCacheFactory
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TimeProvider
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
+
+class DefaultArtifactResolutionCacheTest extends Specification {
+
+    @Rule TemporaryFolder tmp = new TemporaryFolder()
+
+    Date time = new Date(0)
+    TimeProvider timeProvider = new TimeProvider() {
+        long getCurrentTime() { time.getTime() }
+    }
+
+    CacheFactory cacheFactory = new InMemoryCacheFactory()
+    DefaultCacheRepository cacheRepository
+    DefaultCacheLockingManager cacheLockingManager
+    
+    DefaultCachedExternalResourceIndex<String> index
+
+    def setup() {
+        cacheRepository = new DefaultCacheRepository(tmp.createDir('user-home'), tmp.createDir('project-cache'), CacheUsage.ON, cacheFactory)
+        cacheLockingManager = new DefaultCacheLockingManager(cacheRepository)
+        index = new DefaultCachedExternalResourceIndex(tmp.createFile("index"), String, timeProvider, cacheLockingManager)
+    }
+
+    @Unroll "stores entry - lastModified = #lastModified, artifactUrl = #artifactUrl"() {
+        given:
+        def key = "key"
+        def artifactFile = tmp.createFile("artifact") << "content"
+        
+        when:
+        index.store(key, artifactFile, new DefaultExternalResourceMetaData(artifactUrl, lastModified, 100, null, null))
+        
+        then:
+        def cached = index.lookup(key)
+        
+        and:
+        cached != null
+        cached.cachedFile == artifactFile
+        cached.externalResourceMetaData != null
+        cached.externalResourceMetaData.lastModified == lastModified
+        cached.externalResourceMetaData.location == artifactUrl
+
+
+        where:
+        lastModified | artifactUrl
+        new Date()   | null
+        null         | "abc"
+        null         | null
+        new Date()   | "abc"
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy
new file mode 100644
index 0000000..178a326
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/CachedExternalResourceAdapterTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.externalresource;
+
+import org.apache.ivy.util.CopyProgressListener
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.hash.HashUtil
+import org.gradle.util.hash.HashValue
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.internal.externalresource.cached.CachedExternalResourceAdapter
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource
+import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
+
+public class CachedExternalResourceAdapterTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    ExternalResourceAccessor accessor = Mock()
+    CachedExternalResource cachedExternalResource = Mock()
+    CopyProgressListener progress = Mock()
+    CachedExternalResourceAdapter cachedResource
+    def origin = tmpDir.file('origin')
+    def destination = tmpDir.file('destination')
+    def download = tmpDir.file('download')
+
+    def setup() {
+        cachedExternalResource.cachedFile >> origin
+        cachedExternalResource.sha1 >> { HashUtil.createHash(origin, "SHA1") }
+        cachedResource = new CachedExternalResourceAdapter("resource-source", cachedExternalResource, accessor)
+    }
+
+    def "delegates to cached artifact"() {
+        given:
+        cachedExternalResource.contentLength >> 22
+        cachedExternalResource.externalResourceMetaData >> new DefaultExternalResourceMetaData("url")
+        cachedExternalResource.externalLastModifiedAsTimestamp >> 33
+
+        expect:
+        cachedResource.contentLength == 22
+        cachedResource.lastModified == 33
+    }
+
+    def "will copy cache file to destination"() {
+        given:
+        origin << "some content"
+
+        when:
+        cachedResource.writeTo(destination, progress)
+
+        then:
+        destination.assertIsCopyOf(origin)
+    }
+
+    def "will copy download resource if destination does not match original sha1 after copy"() {
+        given:
+        origin << "some content"
+        download << "some other content"
+        ExternalResource resource = Mock()
+
+        when:
+        cachedResource.writeTo(destination, progress)
+
+        then:
+        cachedExternalResource.cachedFile >> origin
+        cachedExternalResource.sha1 >> new HashValue("1234")
+
+        and:
+        accessor.getResource("resource-source") >> resource
+        resource.writeTo(destination, progress)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy
new file mode 100644
index 0000000..cb6f7c1
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/CompositeLocallyAvailableResourceFinderTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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.externalresource.local
+
+import spock.lang.Specification
+import org.gradle.util.hash.HashUtil
+
+class CompositeLocallyAvailableResourceFinderTest extends Specification {
+    
+    def "interrogates composites in turn as needed"() {
+        given:
+        def f1 = Mock(LocallyAvailableResourceFinder)
+        def c1 = Mock(LocallyAvailableResourceCandidates)
+        def f2 = Mock(LocallyAvailableResourceFinder)
+        def c2 = Mock(LocallyAvailableResourceCandidates)
+        def f3 = Mock(LocallyAvailableResourceFinder)
+        def c3 = Mock(LocallyAvailableResourceCandidates)
+        def hash = HashUtil.sha1("abc".bytes)
+
+        def composite = new CompositeLocallyAvailableResourceFinder<String>([f1, f2, f3])
+        def criterion = "abc"
+
+        when:
+        def candidates = composite.findCandidates(criterion)
+
+        then:
+        1 * f1.findCandidates(criterion) >> c1
+
+        then:
+        1 * f2.findCandidates(criterion) >> c2
+
+        then:
+        1 * f3.findCandidates(criterion) >> c3
+
+        and:
+        0 * c1._(*_)
+        0 * c2._(*_)
+        0 * c3._(*_)
+
+        when:
+        def isNone = candidates.isNone()
+
+        then:
+        1 * c1.isNone() >> true
+
+        and:
+        1 * c2.isNone() >> false
+        0 * c3.isNone()
+
+        when:
+        def match = candidates.findByHashValue(hash)
+
+        then:
+        1 * c1.findByHashValue(hash) >> null
+
+        and:
+        1 * c2.findByHashValue(hash) >> Mock(LocallyAvailableResource)
+        0 * c3.findByHashValue(_)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
new file mode 100644
index 0000000..0dfabf3
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/DefaultLocallyAvailableResourceTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.externalresource.local
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.util.hash.HashUtil
+
+public class DefaultLocallyAvailableResourceTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def "uses value from origin file"() {
+        given:
+        def origin = tmpDir.file("origin")
+        origin << "some text"
+
+        when:
+        def DefaultLocallyAvailableResource resource = new DefaultLocallyAvailableResource(origin)
+
+        then:
+        resource.sha1 == HashUtil.createHash(origin, 'SHA1')
+        resource.contentLength == origin.length()
+        resource.lastModified == origin.lastModified()
+    }
+
+    def "sha1 content length and last modified do not change when file is subsequently modified"() {
+        given:
+        def origin = tmpDir.file("origin")
+        origin << "some text"
+
+
+        when:
+        def DefaultLocallyAvailableResource resource = new DefaultLocallyAvailableResource(origin)
+        def originalSha1 = resource.sha1
+        def originalContentLength = resource.contentLength
+        def originalLastModified = resource.lastModified
+
+        and:
+        origin << "some more text"
+        origin.setLastModified(11)
+
+        then:
+        resource.sha1 != HashUtil.createHash(origin, 'SHA1')
+        resource.contentLength != origin.length()
+        resource.lastModified != origin.lastModified()
+
+        and:
+        resource.sha1 == originalSha1
+        resource.contentLength == originalContentLength
+        resource.lastModified == originalLastModified
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
new file mode 100644
index 0000000..3ad9df9
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/local/LazyLocallyAvailableResourceCandidatesTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 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.externalresource.local
+
+import org.gradle.internal.Factory
+import spock.lang.Specification
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.gradle.util.hash.HashUtil
+
+class LazyLocallyAvailableResourceCandidatesTest extends Specification {
+
+    @Rule TemporaryFolder tmp
+    
+    def "does not query factory until necessary"() {
+        given:
+        def factory = Mock(Factory)
+
+        when:
+        def candidates = new LazyLocallyAvailableResourceCandidates(factory)
+
+        then:
+        0 * factory.create()
+        
+        when:
+        def isNone = candidates.isNone()
+        
+        then:
+        !isNone
+        1 * factory.create() >> [file("abc"), file("def")]
+        
+        when:
+        def candidate = candidates.findByHashValue(HashUtil.sha1("def".bytes))
+
+        then:
+        candidate.file.name == "def"
+        0 * factory.create()
+    }
+    
+    File file(path) {
+        tmp.createFile(path) << path
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy
new file mode 100644
index 0000000..206ea2f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/DefaultExternalResourceMetaDataTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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.externalresource.metadata
+
+import spock.lang.Specification
+import org.gradle.util.hash.HashValue
+
+class DefaultExternalResourceMetaDataTest extends Specification {
+
+    def "hash value is preserved"() {
+        given:
+        def sha1 = "abc"
+        def md = new DefaultExternalResourceMetaData("somewhere", -1, -1, null, new HashValue(sha1))
+
+        expect:
+        md.sha1.equals(new HashValue(sha1))
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompareTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompareTest.groovy
new file mode 100644
index 0000000..124b6e2
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompareTest.groovy
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012 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.externalresource.metadata
+
+import org.gradle.internal.Factory
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class ExternalResourceMetaDataCompareTest extends Specification {
+
+    @Shared now = new Date()
+
+    def local = Mock(ExternalResourceMetaData)
+    def remote = Mock(ExternalResourceMetaData)
+    def factory = Mock(Factory)
+
+    def unchanged = false
+
+    def "always changed with no local metadata"() {
+        when:
+        compare(null, factory)
+
+        then:
+        !unchanged
+        0 * factory.create()
+    }
+
+    @Unroll
+    "always unchanged with incomplete local metadata"() {
+        given:
+        configureMetadata(local, etag, lastModified, contentLength)
+
+        when:
+        compare(local, factory)
+
+        then:
+        !unchanged
+        0 * factory.create()
+
+        where:
+        etag | lastModified | contentLength
+        null | null         | -1
+        null | now          | -1
+        null | null         | -1
+    }
+
+    @Unroll
+    "always unchanged with incomplete remote metadata"() {
+        given:
+        configureMetadata(local)
+        configureMetadata(remote, null, lastModified, contentLength)
+
+        when:
+        compare(local, factory)
+
+        then:
+        !unchanged
+        1 * factory.create() >> remote
+
+        where:
+        lastModified | contentLength
+        null         | -1
+        now          | -1
+        null         | -1
+    }
+
+    def "always changed with no remote metadata"() {
+        given:
+        configureMetadata(local)
+
+        when:
+        compare(local, factory)
+
+        then:
+        !unchanged
+        1 * factory.create() >> null
+    }
+
+    def "is unchanged if everything is equal"() {
+        given:
+        configureMetadata(local)
+        configureMetadataForEtagMatch(remote)
+
+        when:
+        compare(local, remote)
+
+        then:
+        unchanged
+    }
+
+    def "matching etags are enough to be considered equal"() {
+        given:
+        configureMetadata(local, "abc", null, -1)
+        configureMetadataForEtagMatch(remote, "abc")
+
+        when:
+        compare(local, remote)
+
+        then:
+        unchanged
+    }
+
+    def "non matching etags, no mod date, but matching content length does not match"() {
+        given:
+        configureMetadata(local, "abc", null, 10)
+        configureMetadataForEtagMatch(remote, "cde")
+
+        when:
+        compare(local, remote)
+
+        then:
+        !unchanged
+    }
+
+    def "non matching etags, matching mod date, but different content length does not match"() {
+        given:
+        configureMetadata(local, "abc", now, -1)
+        configureMetadataForEtagMatch(remote, "cde")
+
+        when:
+        compare(local, remote)
+
+        then:
+        !unchanged
+    }
+
+    def configureMetadata(ExternalResourceMetaData metaData, String etag = "abc", Date lastModified = now, long contentLength = 100) {
+        interaction {
+            1 * metaData.getEtag() >> etag
+
+            1 * metaData.getLastModified() >> lastModified
+            if (lastModified != null || etag != null) {
+                1 * metaData.getContentLength() >> contentLength
+            } else {
+                0 * metaData.getContentLength()
+            }
+        }
+    }
+
+    def configureMetadataForEtagMatch(ExternalResourceMetaData metaData, String etag = "abc") {
+        interaction {
+            1 * metaData.getEtag() >> etag
+            0 * metaData.getLastModified()
+            0 * metaData.getContentLength()
+        }
+    }
+    boolean compare(ExternalResourceMetaData local, ExternalResourceMetaData remote) {
+        compare(local, new Factory() {
+            def create() { remote }
+        })
+    }
+
+    boolean compare(ExternalResourceMetaData local, Factory<ExternalResourceMetaData> remoteFactory) {
+        unchanged = ExternalResourceMetaDataCompare.isDefinitelyUnchanged(local, remoteFactory)
+        unchanged
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/DefaultCacheAwareExternalResourceAccessorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/DefaultCacheAwareExternalResourceAccessorTest.groovy
new file mode 100644
index 0000000..468d827
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transfer/DefaultCacheAwareExternalResourceAccessorTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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.externalresource.transfer
+
+import spock.lang.Specification
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource
+import org.gradle.api.internal.externalresource.ExternalResource
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
+import org.gradle.util.hash.HashValue
+import org.gradle.api.internal.externalresource.local.LocallyAvailableResource
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource
+
+class DefaultCacheAwareExternalResourceAccessorTest extends Specification {
+
+    def "will use sha1 from metadata for finding candidates if available"() {
+        given:
+        def accessor = Mock(ExternalResourceAccessor)
+        def cache = new DefaultCacheAwareExternalResourceAccessor(accessor)
+        
+        and:
+        def location = "location"
+        def localCandidates = Mock(LocallyAvailableResourceCandidates)
+        def cached = Mock(CachedExternalResource)
+        def resource = Mock(ExternalResource)
+        def sha1 = HashValue.parse("abc")
+        def cachedMetaData = Mock(ExternalResourceMetaData)
+        def remoteMetaData = Mock(ExternalResourceMetaData)
+        def localCandidate = Mock(LocallyAvailableResource)
+        
+        and:
+        cached.getExternalResourceMetaData() >> cachedMetaData
+        accessor.getMetaData(location) >> remoteMetaData
+        localCandidates.isNone() >> false
+        remoteMetaData.sha1 >> sha1
+        
+        when:
+        def foundResource = cache.getResource(location, localCandidates, cached)
+
+        then:
+        0 * accessor.getResourceSha1(_)
+        1 * localCandidates.findByHashValue(sha1) >> localCandidate
+
+        and:
+        foundResource instanceof LocallyAvailableExternalResource
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy
new file mode 100644
index 0000000..4101343
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http
+
+import org.apache.http.auth.AuthScope
+import org.apache.http.impl.client.DefaultHttpClient
+import org.apache.http.impl.conn.ProxySelectorRoutePlanner
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import spock.lang.Specification
+
+public class HttpClientConfigurerTest extends Specification {
+    DefaultHttpClient httpClient = new DefaultHttpClient()
+    PasswordCredentials credentials = Mock()
+    HttpSettings httpSettings = Mock()
+    HttpProxySettings proxySettings = Mock()
+    
+    def "configures http client with no credentials or proxy"() {
+        when:
+        httpSettings.credentials >> credentials
+        httpSettings.proxySettings >> proxySettings
+
+        and:
+        def configurer = new HttpClientConfigurer(httpSettings)
+        configurer.configure(httpClient)
+        
+        then:
+        httpClient.getRoutePlanner() instanceof ProxySelectorRoutePlanner
+        httpClient.getHttpRequestRetryHandler().retryRequest(new IOException(), 1, null) == false
+    }
+    
+    def "configures http client with proxy credentials"() {
+        when:
+        httpSettings.credentials >> credentials
+        httpSettings.proxySettings >> proxySettings
+        proxySettings.proxy >> new HttpProxySettings.HttpProxy("host", 1111, "domain/proxyUser", "proxyPass")
+
+        and:
+        def configurer = new HttpClientConfigurer(httpSettings)
+        configurer.configure(httpClient)
+
+        then:
+        def proxyCredentials = httpClient.getCredentialsProvider().getCredentials(new AuthScope("host", 1111))
+        proxyCredentials.userPrincipal.name == "domain/proxyUser"
+        proxyCredentials.password == "proxyPass"
+
+        and:
+        def ntlmCredentials = httpClient.getCredentialsProvider().getCredentials(new AuthScope("host", 1111, AuthScope.ANY_REALM, "ntlm"))
+        ntlmCredentials.userPrincipal.name == 'DOMAIN/proxyUser'
+        ntlmCredentials.domain == 'DOMAIN'
+        ntlmCredentials.userName == 'proxyUser'
+        ntlmCredentials.password == 'proxyPass'
+        ntlmCredentials.workstation != ''
+    }
+
+    def "configures http client with credentials"() {
+        when:
+        httpSettings.credentials >> credentials
+        credentials.username >> "domain/user"
+        credentials.password >> "pass"
+        httpSettings.proxySettings >> proxySettings
+
+        and:
+        def configurer = new HttpClientConfigurer(httpSettings)
+        configurer.configure(httpClient)
+
+        then:
+        def basicCredentials = httpClient.getCredentialsProvider().getCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT))
+        basicCredentials.userPrincipal.name == "domain/user"
+        basicCredentials.password == "pass"
+
+        and:
+        def ntlmCredentials = httpClient.getCredentialsProvider().getCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM, "ntlm"))
+        ntlmCredentials.userPrincipal.name == 'DOMAIN/user'
+        ntlmCredentials.domain == 'DOMAIN'
+        ntlmCredentials.userName == 'user'
+        ntlmCredentials.password == 'pass'
+        ntlmCredentials.workstation != ''
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy
new file mode 100644
index 0000000..384f945
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 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.externalresource.transport.http
+
+import org.apache.http.Header
+import org.apache.http.HttpHeaders
+import org.apache.http.HttpResponse
+import org.apache.http.message.BasicHeader
+import org.gradle.api.internal.externalresource.ExternalResource
+import spock.lang.Specification
+
+class HttpResponseResourceTest extends Specification {
+    
+    def sourceUrl = "http://gradle.org"
+    def method = "GET"
+    def response = Mock(HttpResponse)
+    
+    def "extracts etag"() {
+        given:
+        addHeader(HttpHeaders.ETAG, "abc")
+
+        expect:
+        resource().metaData.etag == "abc"
+    }
+
+    def "handles no etag"() {
+        expect:
+        resource().metaData.etag == null
+    }
+
+    ExternalResource resource() {
+        new HttpResponseResource(method, sourceUrl, response)
+    }
+
+    void addHeader(String name, String value) {
+        interaction {
+            1 * response.getFirstHeader(name) >> header(name, value)
+        }
+    }
+    Header header(String name, String value) {
+        new BasicHeader(name, value)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy
new file mode 100644
index 0000000..13ec285
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/JavaSystemPropertiesHttpProxySettingsTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http;
+
+
+import spock.lang.Specification
+
+class JavaSystemPropertiesHttpProxySettingsTest extends Specification {
+    def "proxy is not configured when proxyHost property not set"() {
+        expect:
+        def settings = settings(null, proxyPort, nonProxyHosts)
+        settings.getProxy(requestHost) == null
+
+        where:
+        proxyPort | nonProxyHosts | requestHost
+        null      | null          | null
+        null      | null          | "foo"
+        "111"     | null          | "foo"
+        null      | "foo|bar|baz" | "foo"
+    }
+
+    private JavaSystemPropertiesHttpProxySettings settings(host, proxyPort, nonProxyHosts) {
+        return new JavaSystemPropertiesHttpProxySettings(host, proxyPort, null, null, nonProxyHosts)
+    }
+
+    def "proxy is not configured when host is in list of nonproxy hosts"() {
+        expect:
+        settings("proxyHost", "111", nonProxyHosts).getProxy(host)?.host == proxyHost
+
+        where:
+        nonProxyHosts | host     | proxyHost
+        null          | "foo"    | "proxyHost"
+        ""            | "foo"    | "proxyHost"
+        "bar"         | "foo"    | "proxyHost"
+        "foo"         | "foo"    | null
+        "fo"          | "foo"    | "proxyHost"
+        "foo|bar|baz" | "foo"    | null
+        "foo.*"       | "foo.bar"| null
+        "*.bar"       | "foo.bar"| null
+        "*.ba"        | "foo.bar"| "proxyHost"
+        "*"           | "foo"    | null
+        "*"           | "foo"    | null
+        "foo.*|baz"   | "foo.bar"| null
+    }
+
+    def "uses specified port property and default port when port property not set or invalid"() {
+        expect:
+       settings("proxyHost", prop, null).getProxy("host").port == value
+
+        where:
+        prop     | value
+        null     | 80
+        ""       | 80
+        "notInt" | 80
+        "0"      | 0
+        "111"    | 111
+    }
+
+    def "uses specified proxy user and password"() {
+        expect:
+        def proxy = new JavaSystemPropertiesHttpProxySettings("proxyHost", null, user, password, null).getProxy("host")
+        proxy.credentials?.username == proxyUser
+        proxy.credentials?.password == proxyPassword
+
+        where:
+        user     | password  | proxyUser | proxyPassword
+        "user"   | "password"| "user"    | "password"
+        "user"   | ""        | "user"    | ""
+        ""       | "password"| null      | null
+        null     | "anything"| null      | null
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy
new file mode 100644
index 0000000..de4a59b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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.externalresource.transport.http.ntlm;
+
+
+import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+public class NTLMCredentialsTest extends Specification {
+    final PasswordCredentials credentials = Mock()
+
+    @Rule
+    public SetSystemProperties systemProperties = new SetSystemProperties()
+
+    def "uses domain when encoded in username"() {
+        when:
+        credentials.username >> "domain\\username"
+        credentials.password >> "password"
+        def ntlmCredentials = new NTLMCredentials(credentials)
+
+        then:
+        ntlmCredentials.domain == 'DOMAIN'
+        ntlmCredentials.username == 'username'
+        ntlmCredentials.password == 'password'
+    }
+
+    def "uses domain when encoded in username with forward slash"() {
+        when:
+        credentials.username >> "domain/username"
+        credentials.password >> "password"
+        def ntlmCredentials = new NTLMCredentials(credentials)
+
+        then:
+        ntlmCredentials.domain == 'DOMAIN'
+        ntlmCredentials.username == 'username'
+        ntlmCredentials.password == 'password'
+    }
+
+    def "uses default domain when not encoded in username"() {
+        when:
+        credentials.username >> "username"
+        credentials.password >> "password"
+        def ntlmCredentials = new NTLMCredentials(credentials)
+
+        then:
+        ntlmCredentials.domain == ''
+        ntlmCredentials.username == 'username'
+        ntlmCredentials.password == 'password'
+    }
+
+    def "uses system property for domain when not encoded in username"() {
+        when:
+        System.setProperty("http.auth.ntlm.domain", "domain")
+        credentials.username >> "username"
+        credentials.password >> "password"
+        def ntlmCredentials = new NTLMCredentials(credentials)
+
+        then:
+        ntlmCredentials.domain == 'DOMAIN'
+        ntlmCredentials.username == 'username'
+        ntlmCredentials.password == 'password'
+    }
+
+    def "uses truncated hostname for workstation"() {
+        when:
+        credentials.username >> "username"
+        credentials.password >> "password"
+        def ntlmCredentials = new NTLMCredentials(credentials) {
+            protected String getHostName() {
+                return "hostname.domain.org"
+            }
+        }
+
+        then:
+        ntlmCredentials.workstation == 'HOSTNAME'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy
new file mode 100755
index 0000000..236b2b0
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.notations
+
+import org.gradle.api.artifacts.SelfResolvingDependency
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+import org.gradle.util.ClassPath
+
+public class DependencyClassPathNotationParserTest extends Specification {
+    def instantiator = Mock(Instantiator.class)
+    def classPathRegistry = Mock(ClassPathRegistry.class)
+    def fileResolver = Mock(FileResolver.class)
+    def DependencyClassPathNotationParser factory = new DependencyClassPathNotationParser(instantiator, classPathRegistry, fileResolver)
+
+    def "parses classpath literals"() {
+        given:
+        def dependency = Mock(SelfResolvingDependency.class)
+        def fileCollection = Mock(FileCollection.class)
+        def classpath = Mock(ClassPath.class)
+        def files = [new File('foo')]
+
+        and:
+        classPathRegistry.getClassPath('GRADLE_API') >> classpath
+        classpath.asFiles >> files
+        fileResolver.resolveFiles(files) >> fileCollection
+        instantiator.newInstance(DefaultSelfResolvingDependency.class, fileCollection as Object) >> dependency
+
+        when:
+        def out = factory.parseType(DependencyFactory.ClassPathNotation.GRADLE_API)
+
+        then:
+        out.is dependency
+    }
+}
+
+
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
new file mode 100644
index 0000000..cf58b1f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2011 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.notations;
+
+
+import org.gradle.api.artifacts.DependencyArtifact
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/10/11
+ */
+public class DependencyMapNotationParserTest extends Specification {
+
+    def parser = new DependencyMapNotationParser<DefaultExternalModuleDependency>(new DirectInstantiator(), DefaultExternalModuleDependency.class);
+
+    def "with artifact"() {
+        when:
+        def d = parser.parseNotation([group: 'org.gradle', name: 'gradle-core', version: '4.4-beta2', ext: 'mytype'])
+
+        then:
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '4.4-beta2'
+
+        !d.force
+        !d.transitive
+        !d.changing
+
+        d.artifacts.size() == 1
+        d.artifacts.find { it.name == 'gradle-core' && it.classifier == null && it.type == 'mytype' }
+    }
+
+    def "with classified artifact"() {
+        when:
+        def d = parser.parseNotation([group: 'org.gradle', name: 'gradle-core', version: '10', ext: 'zip', classifier: 'jdk-1.4'])
+
+        then:
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '10'
+
+        !d.force
+        !d.transitive
+        !d.changing
+
+        d.artifacts.size() == 1
+        d.artifacts.find { it.name == 'gradle-core' && it.classifier == 'jdk-1.4' && it.type == 'zip' }
+    }
+
+    def "with classifier"() {
+        when:
+        def d = parser.parseNotation([group: 'org.gradle', name:'gradle-core', version:'10', classifier:'jdk-1.4']);
+
+        then:
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '10'
+        d.transitive
+
+        !d.force
+        !d.changing
+
+        d.artifacts.size() == 1
+        d.artifacts.find { it.name == 'gradle-core' && it.classifier == 'jdk-1.4' &&
+                it.type == DependencyArtifact.DEFAULT_TYPE && it.extension == DependencyArtifact.DEFAULT_TYPE }
+    }
+
+    def "with 3-element map"() {
+        when:
+        def d = parser.parseNotation([group: 'org.gradle', name:'gradle-core', version:'1.0']);
+
+        then:
+        d.group == 'org.gradle'
+        d.name == 'gradle-core'
+        d.version == '1.0'
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "with 3-element map and configuration"() {
+        when:
+        def d = parser.parseNotation([group: 'org.gradle', name:'gradle-core', version:'1.0', configuration:'compile']);
+
+        then:
+        d.group == 'org.gradle'
+        d.name == 'gradle-core'
+        d.version == '1.0'
+        d.configuration == 'compile'
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "with 3-element map and property"() {
+        when:
+        def d = parser.parseNotation([group: 'org.gradle', name:'gradle-core', version:'1.0', transitive:false]);
+
+        then:
+        d.group == 'org.gradle'
+        d.name == 'gradle-core'
+        d.version == '1.0'
+
+        !d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "with no group and no version"() {
+        when:
+        def d = parser.parseNotation([name:'foo'])
+
+        then:
+        d.group == null
+        d.name == 'foo'
+        d.version == null
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
new file mode 100644
index 0000000..32a5814
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.notations;
+
+
+import org.gradle.api.GradleException
+import org.gradle.api.internal.notations.api.NotationParser
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/9/11
+ */
+public class DependencyNotationParserTest extends Specification {
+
+    def notationParser = Mock(NotationParser)
+    def parser = new DependencyNotationParser(notationParser);
+
+    def "consumes notation and forwards gradle exception"() {
+        given:
+        def exc = new GradleException("Hey!")
+        notationParser.parseNotation("foo") >> { throw exc }
+
+        when:
+        parser.parseNotation("foo")
+
+        then:
+        def e = thrown(Exception)
+        exc == e
+    }
+
+    def "wraps programmer error with higher level exception"() {
+        given:
+        def exc = new RuntimeException("ka-boom!")
+        notationParser.parseNotation("foo") >> { throw exc }
+
+        when:
+        parser.parseNotation("foo")
+
+        then:
+        def e = thrown(GradleException)
+        exc == e.cause
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
new file mode 100644
index 0000000..c4b6167
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2011 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.notations;
+
+
+import org.gradle.api.artifacts.DependencyArtifact
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/10/11
+ */
+public class DependencyStringNotationParserTest extends Specification {
+
+    def parser = new DependencyStringNotationParser(new DirectInstantiator(), DefaultExternalModuleDependency.class);
+
+    def "with artifact"() {
+        when:
+        def d = parser.parseNotation('org.gradle:gradle-core:4.4-beta2 at mytype');
+
+        then:
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '4.4-beta2'
+
+        !d.force
+        !d.transitive
+        !d.changing
+
+        d.artifacts.size() == 1
+        d.artifacts.find { it.name == 'gradle-core' && it.classifier == null && it.type == 'mytype' }
+    }
+
+    def "with classified artifact"() {
+        when:
+        def d = parser.parseNotation('org.gradle:gradle-core:10:jdk-1.4 at zip');
+
+        then:
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '10'
+
+        !d.force
+        !d.transitive
+        !d.changing
+
+        d.artifacts.size() == 1
+        d.artifacts.find { it.name == 'gradle-core' && it.classifier == 'jdk-1.4' && it.type == 'zip' }
+    }
+
+    def "with classifier"() {
+        when:
+        def d = parser.parseNotation('org.gradle:gradle-core:10:jdk-1.4');
+
+        then:
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '10'
+        d.transitive
+
+        !d.force
+        !d.changing
+
+        d.artifacts.size() == 1
+        d.artifacts.find { it.name == 'gradle-core' && it.classifier == 'jdk-1.4' &&
+                it.type == DependencyArtifact.DEFAULT_TYPE && it.extension == DependencyArtifact.DEFAULT_TYPE }
+    }
+
+    def "with 3-element GString"() {
+        when:
+        def gstring = HelperUtil.createScript("descriptor = 'org.gradle:gradle-core:1.0'; \"\$descriptor\"").run()
+        def d = parser.parseNotation(gstring);
+
+        then:
+        d.group == 'org.gradle'
+        d.name == 'gradle-core'
+        d.version == '1.0'
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "with no group"() {
+        when:
+        def d = parser.parseNotation(":foo:1.0");
+
+        then:
+        d.group == null
+        d.name == 'foo'
+        d.version == '1.0'
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "with no version"() {
+        when:
+        def d = parser.parseNotation("hey:foo:");
+
+        then:
+        d.group == 'hey'
+        d.name == 'foo'
+        d.version == null
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "with no version and no group"() {
+        when:
+        def d = parser.parseNotation(":foo:");
+
+        then:
+        d.group == null
+        d.name == 'foo'
+        d.version == null
+        d.transitive
+
+        !d.force
+        !d.changing
+    }
+
+    def "can create client module"() {
+        parser = new DependencyStringNotationParser(new DirectInstantiator(), DefaultClientModule);
+
+        when:
+        def d = parser.parseNotation('org.gradle:gradle-core:10')
+
+        then:
+        d instanceof DefaultClientModule
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '10'
+        d.transitive
+
+        !d.force
+    }
+
+    def "client module ignores the artifact only notation"() {
+        parser = new DependencyStringNotationParser(new DirectInstantiator(), DefaultClientModule);
+
+        when:
+        def d = parser.parseNotation('org.gradle:gradle-core:10 at jar')
+
+        then:
+        d instanceof DefaultClientModule
+        d.name == 'gradle-core'
+        d.group == 'org.gradle'
+        d.version == '10 at jar'
+        d.transitive
+
+        !d.force
+        d.artifacts.size() == 0
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
new file mode 100644
index 0000000..5cf25bc
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.notations;
+
+
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.util.GUtil
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencyFactoryTest extends Specification {
+
+    def ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(false);
+    def ProjectDependencyFactory factory = new ProjectDependencyFactory(projectDependenciesBuildInstruction, new DirectInstantiator());
+    def ProjectFinder projectFinder = Mock(ProjectFinder.class);
+    def ProjectInternal projectDummy = Mock(ProjectInternal.class);
+
+    def testCreateProjectDependencyWithMapNotation() {
+        given:
+        boolean expectedTransitive = false;
+        final Map<String, Object> mapNotation = GUtil.map("path", ":foo:bar", "configuration", "compile", "transitive", expectedTransitive);
+
+        and:
+        projectFinder.getProject(':foo:bar') >> projectDummy
+
+        when:
+        def projectDependency = factory.createFromMap(projectFinder, mapNotation);
+
+        then:
+        projectDependency.getDependencyProject() == projectDummy
+        projectDependency.getConfiguration() == "compile"
+        projectDependency.isTransitive() == expectedTransitive
+    }
+}
diff --git a/subprojects/core/core.gradle b/subprojects/core/core.gradle
old mode 100644
new mode 100755
index caba001..15b2e45
--- a/subprojects/core/core.gradle
+++ b/subprojects/core/core.gradle
@@ -14,114 +14,84 @@
  * limitations under the License.
  */
 
-import java.text.DateFormat
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
-
-apply plugin: 'groovy'
-apply from: "$rootDir/gradle/integTest.gradle"
+import org.gradle.build.GenerateReleasesXml
 
 configurations {
-    testFixtures
-    testFixturesRuntime {
-        extendsFrom testFixtures, testRuntime
-    }
-    integTestFixtures {
-        extendsFrom testFixtures
-    }
-    integTestFixturesRuntime {
-        extendsFrom integTestFixtures, integTestRuntime
-    }
+    reports
 }
 
 dependencies {
-    groovy libraries.groovy_depends
-
-    compile "commons-httpclient:commons-httpclient:3.0 at jar", "commons-codec:commons-codec:1.2 at jar", libraries.jcl_to_slf4j
-
-    compile libraries.ivy, "com.jcraft:jsch:0.1.42 at jar", 'com.jcraft:jzlib:1.0.7 at jar'
+    groovy libraries.groovy
 
     publishCompile libraries.slf4j_api
+    publishCompile project(":baseServices")
 
-    compile libraries.ant,
-            libraries.logback_classic,
-            libraries.logback_core,
-            libraries.jul_to_slf4j,
-            libraries.commons_io,
-            libraries.commons_lang,
-            "commons-codec:commons-codec:1.2 at jar",
-            libraries.guava,
-            "commons-collections:commons-collections:3.2.1 at jar",
-            "slide:webdavlib:2.0 at jar",
-            libraries.asm_all,
-            'org.fusesource.jansi:jansi:1.2.1',
-            'org.jruby.ext.posix:jna-posix:1.0.3'
+    compile libraries.asm
+    compile libraries.ant
+    compile libraries.commons_collections
+    compile libraries.commons_io
+    compile libraries.commons_lang
+    compile libraries.ivy
+    compile libraries.logback_core
+    compile libraries.logback_classic
+    compile libraries.guava
+    compile libraries.jcip
+    compile libraries.jul_to_slf4j
 
-    runtime 'net.java.dev.jna:jna:3.2.2'
+    compile project(":cli")
+    compile project(":native")
 
-    runtime libraries.log4j_to_slf4j, libraries.jcl_to_slf4j
+    runtime libraries.log4j_to_slf4j
+    runtime libraries.jcl_to_slf4j
 
-    // tried with scope 'runtime' but this gives a cyclic task dependency
-    testRuntime project(":coreImpl")
+    testCompile libraries.log4j_to_slf4j
+    testCompile libraries.jcl_to_slf4j
 
-    testCompile libraries.xmlunit
+    testRuntime "xerces:xercesImpl:2.9.1"
 
-    compile libraries.ant_launcher
+    testFixturesCompile project(":internalTesting")
+    testFixturesRuntime project(':coreImpl')
 
-    integTestCompile libraries.jetty_depends
+    integTestCompile project(":internalIntegTesting")
 
-    testFixtures sourceSets.test.classes, sourceSets.main.classes
-    integTestFixtures sourceSets.integTest.classes
+    reports 'css3-pie:css3-pie:1.0beta3'
 }
 
-task versionProperties(type: WriteVersionProperties) {
-    propertiesFile = new File(sourceSets.main.classesDir, GradleVersion.FILE_NAME)
-}
-
-classes.dependsOn(versionProperties)
-
-task ideVersionProperties(type: WriteVersionProperties) {
-    propertiesFile = new File(ideDir, "resources/test/$GradleVersion.FILE_NAME")
+test {
+    jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
 }
 
-ide.dependsOn ideVersionProperties
+[compileGroovy, compileTestGroovy]*.groovyOptions*.fork(memoryInitialSize: '128M', memoryMaximumSize: '1G')
 
-ideaModule {
-    dependsOn ideVersionProperties
-    scopes.COMPILE.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(new File(ideDir, "resources/test/")))))
+task releasesResource(type: GenerateReleasesXml) {
+    destFile = new File(generatedResourcesDir, "org/gradle/releases.xml")
 }
+sourceSets.main.output.dir generatedResourcesDir, builtBy: releasesResource
 
-eclipseClasspath {
-    dependsOn ideVersionProperties
-    plusConfigurations.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(new File(ideDir, "resources/test/")))))
+task reportResources(type: Copy) {
+    from configurations.reports
+    into "$generatedResourcesDir/org/gradle/reporting"
 }
+sourceSets.main.output.dir generatedResourcesDir, builtBy: reportResources
 
-[compileGroovy, compileTestGroovy]*.groovyOptions*.fork(memoryInitialSize: '128M', memoryMaximumSize: '1G')
+task pluginsManifest(type: PluginsManifest)
+sourceSets.main.output.dir generatedResourcesDir, builtBy: pluginsManifest
 
-test {
-    jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
-}
-
-class WriteVersionProperties extends DefaultTask {
-    @Input
-    String getVersion() { return project.version.toString() }
+class PluginsManifest extends DefaultTask {
+    @OutputFile
+    File getPropertiesFile() {
+        return new File(project.generatedResourcesDir, "gradle-plugins.properties")
+    }
 
     @Input
-    Date getBuildTime() { return project.version.buildTime }
-
-    @OutputFile
-    File propertiesFile
+    Properties getPluginProperties() {
+        def properties = new Properties()
+        properties.plugins = project.pluginProjects().collect { it.archivesBaseName }.join(',')
+        return properties
+    }
 
     @TaskAction
-    def void generate() {
-        logger.info('Write version properties to: {}', propertiesFile)
-        Properties versionProperties = new Properties()
-        versionProperties.putAll([
-                (GradleVersion.VERSION): version,
-                (GradleVersion.BUILD_TIME): DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(buildTime)
-        ])
-        propertiesFile.withOutputStream {
-            versionProperties.store(it, '')
-        }
-
+    def generate() {
+        propertiesFile.withOutputStream { pluginProperties.save(it, 'plugin definitions') }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/artifacts/DependencyResolutionEventsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/artifacts/DependencyResolutionEventsIntegrationTest.groovy
new file mode 100644
index 0000000..4b824fa
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/artifacts/DependencyResolutionEventsIntegrationTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.integtests.fixtures.*
+import org.gradle.integtests.fixtures.internal.*
+import spock.lang.Issue
+
+class DependencyResolutionEventsIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2047")
+    def "can access resolved files from afterResolve hook"() {
+        given:
+        file("thing.txt") << "stuff"
+        buildFile << """
+            configurations {
+                things.incoming.afterResolve { incoming ->
+                    incoming.files.files
+                    println "accessed files"
+                }
+            }
+            dependencies {
+                things files("thing.txt")
+            }
+
+            task resolveIt(type: Copy, dependsOn: configurations.things) {
+                from configurations.things
+                into buildDir
+            }
+        """
+
+        when:
+        run "resolveIt"
+
+        then:
+        output.contains "accessed files"
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/file/FileCollectionSymlinkIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/file/FileCollectionSymlinkIntegrationTest.groovy
new file mode 100644
index 0000000..a167d66
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/file/FileCollectionSymlinkIntegrationTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.file
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import static org.gradle.util.TextUtil.escapeString
+
+import spock.lang.Unroll
+
+class FileCollectionSymlinkIntegrationTest extends AbstractIntegrationSpec {
+    @Unroll("#desc can handle symlinks")
+    def "file collection can handle symlinks"() {
+        def buildScript = file("build.gradle")
+        def baseDir = getTestFile("symlinks")
+
+        buildScript << """
+def baseDir = new File("${escapeString(baseDir)}")
+def file = new File(baseDir, "file")
+def symlink = new File(baseDir, "symlink")
+def symlinked = new File(baseDir, "symlinked")
+def fileCollection = $code
+
+assert fileCollection.contains(file)
+assert fileCollection.contains(symlink)
+assert fileCollection.contains(symlinked)
+assert fileCollection.files == [file, symlink, symlinked] as Set
+assert (fileCollection - project.files(symlink)).files == [file, symlinked] as Set
+        """
+
+        when:
+        executer.usingBuildScript(buildScript).run()
+
+        then:
+        noExceptionThrown()
+
+        where:
+        desc                 | code
+        "project.files()"    | "project.files(file, symlink, symlinked)"
+        "project.fileTree()" | "project.fileTree(baseDir)"
+    }
+
+    private File getTestFile(String name) {
+        new File(getClass().getResource(name).toURI().path)
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
new file mode 100644
index 0000000..e4ee2f4
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.gradle.util.TextUtil
+
+class ArchiveTaskPermissionsIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def "permissions are preserved, overridden by type, and overridden by copy action"() {
+        def referenceArchive = createReferenceArchiveWithPermissions(0666)
+
+        def buildScript = file("build.gradle") << """
+            $assertModesFunction
+
+            task copy(type: Tar) {
+                from tarTree('${TextUtil.escapeString(referenceArchive.absolutePath)}')
+                dirMode = 0777
+                eachFile {
+                    if (it.name == 'script') {
+                        it.mode = 0123;
+                    }
+                }
+            }
+
+            task test(dependsOn: copy) << {
+                assertModes(tarTree(copy.archivePath), [
+                    file: '666',    // preserved
+                    script: '123',  // overridden by dirMode
+                    folder: '777'   // overridden by copy action
+                ])
+            }
+        """
+
+        when:
+        executer.usingBuildScript(buildScript)
+            .withTasks('test')
+            .run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "expected permissions are exposed to copy action"() {
+        def referenceArchive = createReferenceArchiveWithPermissions(0666)
+
+        def buildScript = file("build.gradle") << """
+            import static java.lang.Integer.toOctalString
+            $assertModesFunction
+
+            def preservedModes = [:]
+            task copyPreserved(type: Tar) {
+                from tarTree('${TextUtil.escapeString(referenceArchive.absolutePath)}')
+                eachFile {
+                    preservedModes[it.name] = toOctalString(it.mode)
+                }
+            }
+
+            def overriddenModes = [:]
+            task copyOverridden(type: Tar) {
+                from tarTree('${TextUtil.escapeString(referenceArchive.absolutePath)}')
+                fileMode = 0123
+                eachFile {
+                    overriddenModes[it.name] = toOctalString(it.mode)
+                }
+            }
+
+            task test(dependsOn: [copyPreserved, copyOverridden]) << {
+                assert preservedModes == [file: "666", script: "666"]
+                assert overriddenModes == [file: "123", script: "123"]
+            }
+        """
+
+        when:
+        executer.usingBuildScript(buildScript)
+            .withTasks('test')
+            .run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    /*
+     * Creates a TAR archive with three files, 'file', 'script' and 'folder'. These files
+     * are used as reference data for the tests. The TAR archive is used to make the tests
+     * independent of the file system, which may not support Unix permissions.
+     */
+    private File createReferenceArchiveWithPermissions(int mode) {
+        def archive = tmpDir.file("reference.tar")
+        def archiveTmp = tmpDir.createDir('reference')
+
+        archiveTmp.createFile("file")
+        archiveTmp.createFile("script")
+        archiveTmp.createDir("folder")
+
+        // create the archive, with correct permissions using a build script
+        def script = file("create-reference-archive.gradle") << """
+            import static java.lang.Integer.toOctalString
+            $assertModesFunction
+
+            task createReference(type: Tar) { 
+                destinationDir = file('${TextUtil.escapeString(tmpDir.dir)}')
+                archiveName = '${TextUtil.escapeString(archive.name)}'
+                from file('${TextUtil.escapeString(archiveTmp.absolutePath)}')
+                fileMode = $mode
+                dirMode = $mode
+            }
+
+            task verifyReference(dependsOn: createReference) << {
+                assertModes(tarTree(file('${TextUtil.escapeString(archive.absolutePath)}')), [
+                    file: toOctalString($mode),
+                    script: toOctalString($mode),
+                    folder: toOctalString($mode)
+                ])
+            }
+        """
+
+        executer.usingBuildScript(script)
+            .withTasks('verifyReference')
+            .run()
+
+        archive
+    }
+
+    // script fragment for extracting & checking files/modes from a FileTree
+    def assertModesFunction = """
+        def assertModes = { files, expectedModes ->
+            def actualModes = [:]
+            files.visit {
+                actualModes[it.name] = Integer.toOctalString(it.mode)
+            }
+            assert expectedModes == actualModes
+        }
+    """
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.groovy
new file mode 100644
index 0000000..102fadb
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyErrorIntegrationTest.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.api.tasks
+
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.util.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.PreconditionVerifier
+import org.junit.Assert
+import org.junit.Test
+import org.junit.Rule
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+
+class CopyErrorIntegrationTest extends AbstractIntegrationTest {
+    @Rule public PreconditionVerifier verifier = new PreconditionVerifier()
+
+    @Test
+    @Requires(TestPrecondition.SYMLINKS)
+    public void reportsSymLinkWhichPointsToNothing() {
+        TestFile link = testFile('src/file')
+        FileSystems.default.createSymbolicLink(link, testFile('missing'))
+
+        Assert.assertFalse(link.isDirectory())
+        Assert.assertFalse(link.isFile())
+        Assert.assertFalse(link.exists())
+
+        testFile('build.gradle') << '''
+            task copy(type: Copy) {
+                from 'src'
+                into 'dest'
+            }
+'''
+
+        ExecutionFailure failure = inTestDirectory().withTasks('copy').runWithFailure()
+        failure.assertHasDescription("Could not list contents of '${link}'.")
+    }
+
+    @Test
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    public void reportsUnreadableSourceDir() {
+        TestFile dir = testFile('src').createDir()
+        def oldPermissions = dir.permissions
+        dir.permissions = '-w-r--r--'
+
+        try {
+            Assert.assertTrue(dir.isDirectory())
+            Assert.assertTrue(dir.exists())
+            Assert.assertFalse(dir.canRead())
+            Assert.assertTrue(dir.canWrite())
+
+            testFile('build.gradle') << '''
+                task copy(type: Copy) {
+                    from 'src'
+                    into 'dest'
+                }
+    '''
+
+            ExecutionFailure failure = inTestDirectory().withTasks('copy').runWithFailure()
+            failure.assertHasDescription("Could not list contents of directory '${dir}' as it is not readable.")
+        } finally {
+            dir.permissions = oldPermissions
+        }
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
new file mode 100644
index 0000000..a31546d
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+ at Requires(TestPrecondition.FILE_PERMISSIONS)
+class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
+
+
+    def "file permissions of a file are preserved in copy action"() {
+        given:
+        def testSourceFile = file("reference.txt") << 'test file"'
+        testSourceFile.permissions = mode
+        and:
+        buildFile << """
+        import static java.lang.Integer.toOctalString
+        task copy(type: Copy) {
+            from "reference.txt"
+            into ("build/tmp")
+        }
+        """
+
+        when:
+        run "copy"
+        then:
+        file("build/tmp/reference.txt").permissions == mode
+        where:
+        mode << ['rwxr--r-x']
+    }
+
+    def "fileMode can be modified in copy task"() {
+        given:
+
+        file("reference.txt") << 'test file"'
+        and:
+        buildFile << """
+             import static java.lang.Integer.toOctalString
+             task copy(type: Copy) {
+                 from "reference.txt"
+                 into ("build/tmp")
+                 fileMode = $mode
+             }
+
+            ${verifyPermissionsTask(mode)}
+            """
+
+        when:
+        run "verifyPermissions"
+
+        then:
+        noExceptionThrown()
+
+        where:
+        mode << [0755, 0777]
+
+    }
+
+
+
+
+
+    def "fileMode can be modified in copy action"() {
+        given:
+        file("reference.txt") << 'test file"'
+
+        and:
+        buildFile << """
+            import static java.lang.Integer.toOctalString
+            task copy << {
+                copy {
+                    from 'reference.txt'
+                    into 'build/tmp'
+                    fileMode = $mode
+                }
+            }
+
+            ${verifyPermissionsTask(mode)}
+            """
+
+        when:
+        run "verifyPermissions"
+
+        then:
+        noExceptionThrown()
+
+        where:
+        mode << [0755, 0777]
+
+    }
+
+    String verifyPermissionsTask(int mode) {
+        """task verifyPermissions(dependsOn: copy) << {
+                fileTree("build/tmp").visit{
+                    assert toOctalString($mode) == toOctalString(it.mode)
+                }
+           }
+        """
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
new file mode 100644
index 0000000..194eca7
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
@@ -0,0 +1,436 @@
+/*
+ * 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 org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources resources = new TestResources("copyTestResources")
+
+    @Test
+    public void testSingleSourceWithIncludeAndExclude() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               from 'src'
+               into 'dest'
+               include '**/sub/**'
+               exclude '**/ignore/**'
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'one/sub/onesub.a',
+                'one/sub/onesub.b'
+        )
+    }
+
+    @Test
+    public void testSingleSourceWithSpecClosures() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                "task (copy, type:Copy) {",
+                "   from 'src'",
+                "   into 'dest'",
+                "   include { fte -> !fte.file.name.endsWith('b') }",
+                "   exclude { fte -> fte.file.name == 'bad.file' }",
+                "}"
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'one/one.a',
+                'two/two.a',
+        )
+    }
+
+    @Test
+    public void testMultipleSourceWithInheritedPatterns() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               into 'dest'
+               from('src/one') {
+                  into '1'
+                  include '**/*.a'
+               }
+               from('src/two') {
+                  into '2'
+                  include '**/*.b'
+               }
+               exclude '**/ignore/**'
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                '1/one.a',
+                '1/sub/onesub.a',
+                '2/two.b',
+        )
+    }
+
+    @Test
+    public void testMultipleSourcesWithInheritedDestination() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               into 'dest'
+               into('common') {
+                  from('src/one') {
+                     into 'a/one'
+                     include '*.a'
+                  }
+                  into('b') {
+                     from('src/two') {
+                        into 'two'
+                        include '**/*.b'
+                     }
+                  }
+               }
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'common/a/one/one.a',
+                'common/b/two/two.b',
+        )
+    }
+
+    @Test
+    void testRename() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               from 'src'
+               into 'dest'
+               exclude '**/ignore/**'
+               rename '(.*).a', '\$1.renamed'
+               rename { it.startsWith('one.') ? "renamed_$it" : it }
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.renamed',
+                'root.b',
+                'one/renamed_one.renamed',
+                'one/renamed_one.b',
+                'one/sub/onesub.renamed',
+                'one/sub/onesub.b',
+                'two/two.renamed',
+                'two/two.b'
+        )
+    }
+
+    @Test
+    public void testCopyAction() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                "task copyIt << {",
+                "   copy {",
+                "      from 'src'",
+                "      into 'dest'",
+                "      exclude '**/ignore/**'",
+                "   }",
+                "}"
+        )
+        usingBuildFile(buildFile).withTasks("copyIt").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'one/sub/onesub.a',
+                'one/sub/onesub.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void copySingleFiles() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                "task copyIt << {",
+                "   copy {",
+                "      from 'src/one/one.a', 'src/two/two.a'",
+                "      into 'dest/two'",
+                "   }",
+                "}"
+        )
+        usingBuildFile(buildFile).withTasks("copyIt").run()
+        testFile('dest').assertHasDescendants(
+                'two/one.a',
+                'two/two.a',
+        )
+    }
+
+    /*
+     * two.a starts off with "$one\n${one+1}\n${one+1+1}\n"
+     * If these filters are chained in the correct order, you should get 6, 11, and 16
+     */
+
+    @Test public void copyMultipleFilterTest() {
+        TestFile buildFile = testFile('build.gradle').writelns(
+                """task (copy, type:Copy) {
+                   into 'dest'
+                   expand(one: 1)
+                   filter { (Integer.parseInt(it) * 10) as String }
+                   filter { (Integer.parseInt(it) + 2) as String }
+                   from('src/two/two.a') {
+                     filter { (Integer.parseInt(it) / 2) as String }
+                   }
+                }
+                """
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        Iterator<String> it = testFile('dest/two.a').readLines().iterator()
+        assertThat(it.next(), startsWith('6'))
+        assertThat(it.next(), startsWith('11'))
+        assertThat(it.next(), startsWith('16'))
+    }
+
+    @Test public void chainedTransformations() {
+        def buildFile = testFile('build.gradle') << '''
+            task copy(type: Copy) {
+                into 'dest'
+                rename '(.*).a', '\$1.renamed'
+                eachFile { fcd -> if (fcd.path.contains('/ignore/')) { fcd.exclude() } }
+                eachFile { fcd -> if (fcd.relativePath.segments.length > 1) { fcd.relativePath = fcd.relativePath.prepend('prefix') }}
+                filter(org.apache.tools.ant.filters.PrefixLines, prefix: 'line: ')
+                eachFile { fcd -> fcd.filter { it.replaceAll('^line:', 'prefix:') } }
+                from ('src') {
+                    rename '(.*).renamed', '\$1.renamed_twice'
+                    eachFile { fcd -> fcd.path = fcd.path.replaceAll('/one/sub/', '/one_sub/') }
+                    eachFile { fcd -> if (fcd.path.contains('/two/')) { fcd.exclude() } }
+                    eachFile { fcd -> fcd.filter { "[$it]" } }
+                }
+            }
+'''
+        usingBuildFile(buildFile).withTasks('copy').run()
+        testFile('dest').assertHasDescendants(
+                'root.renamed_twice',
+                'root.b',
+                'prefix/one/one.renamed_twice',
+                'prefix/one/one.b',
+                'prefix/one_sub/onesub.renamed_twice',
+                'prefix/one_sub/onesub.b'
+        )
+
+        Iterator<String> it = testFile('dest/root.renamed_twice').readLines().iterator()
+        assertThat(it.next(), equalTo('[prefix: line 1]'))
+        assertThat(it.next(), equalTo('[prefix: line 2]'))
+    }
+
+    @Test public void testCopyFromFileTree() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   copy {
+                        from fileTree(dir: 'src', excludes: ['**/ignore/**'], includes: ['*', '*/*'])
+                        into 'dest'
+                    }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyFromFileCollection() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task copy << {
+                   copy {
+                        from files('src')
+                        into 'dest'
+                        exclude '**/ignore/**'
+                        exclude '*/*/*/**'
+                    }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyFromCompositeFileCollection() {
+        testFile('a.jar').touch()
+
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """
+                configurations { compile }
+                dependencies { compile files('a.jar') }
+                task copy << {
+                   copy {
+                        from files('src2') + fileTree('src') { exclude '**/ignore/**' } + configurations.compile
+                        into 'dest'
+                        include { fte -> fte.relativePath.segments.length < 3 && (fte.file.directory || fte.file.name.contains('a')) }
+                    }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'one/one.a',
+                'two/two.a',
+                'three/three.a',
+                'a.jar'
+        )
+    }
+
+    @Test public void testCopyFromTask() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """
+                    configurations { compile }
+                    dependencies { compile files('a.jar') }
+                    task fileProducer {
+                        outputs.file 'build/out.txt'
+                        doLast {
+                            file('build/out.txt').text = 'some content'
+                        }
+                    }
+                    task dirProducer {
+                        outputs.dir 'build/outdir'
+                        doLast {
+                            file('build/outdir').mkdirs()
+                            file('build/outdir/file1.txt').text = 'some content'
+                            file('build/outdir/sub').mkdirs()
+                            file('build/outdir/sub/file2.txt').text = 'some content'
+                        }
+                    }
+                    task copy(type: Copy) {
+                        from fileProducer, dirProducer
+                        into 'dest'
+                    }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'out.txt',
+                'file1.txt',
+                'sub/file2.txt'
+        )
+    }
+
+    @Test public void testCopyFromTaskOutputs() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """
+                        configurations { compile }
+                        dependencies { compile files('a.jar') }
+                        task fileProducer {
+                            outputs.file 'build/out.txt'
+                            doLast {
+                                file('build/out.txt').text = 'some content'
+                            }
+                        }
+                        task dirProducer {
+                            outputs.dir 'build/outdir'
+                            doLast {
+                                file('build/outdir').mkdirs()
+                                file('build/outdir/file1.txt').text = 'some content'
+                                file('build/outdir/sub').mkdirs()
+                                file('build/outdir/sub/file2.txt').text = 'some content'
+                            }
+                        }
+                        task copy(type: Copy) {
+                            from fileProducer.outputs, dirProducer.outputs
+                            into 'dest'
+                        }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'out.txt',
+                'file1.txt',
+                'sub/file2.txt'
+        )
+    }
+
+    @Test public void testCopyWithCopyspec() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """
+                def spec = copySpec {
+                    from 'src'
+                    exclude '**/ignore/**'
+                    include '*/*.a'
+                    into 'subdir'
+                }
+                task copy(type: Copy) {
+                    into 'dest'
+                    with spec
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'subdir/one/one.a',
+                'subdir/two/two.a'
+        )
+    }
+
+    // can't use TestResources here because Git doesn't support committing empty directories
+    @Test
+    void emptyDirsAreCopiedByDefault() {
+        file("src999", "emptyDir").createDir()
+        file("src999", "yet", "another", "veryEmptyDir").createDir()
+
+        // need to include a file in the copy, otherwise copy task says "no source files"
+        file("src999", "dummy").createFile()
+
+        def buildFile = testFile("build.gradle") <<
+                """
+                task copy(type: Copy) {
+                    from 'src999'
+                    into 'dest'
+                }
+                """
+        usingBuildFile(buildFile).withTasks("copy").run()
+
+        assert file("dest", "emptyDir").isDirectory()
+        assert file("dest", "emptyDir").list().size() == 0
+        assert file("dest", "yet", "another", "veryEmptyDir").isDirectory()
+        assert file("dest", "yet", "another", "veryEmptyDir").list().size() == 0
+    }
+
+    @Test
+    void emptyDirsAreNotCopiedIfCorrespondingOptionIsSetToFalse() {
+        file("src999", "emptyDir").createDir()
+        file("src999", "yet", "another", "veryEmptyDir").createDir()
+
+        // need to include a file in the copy, otherwise copy task says "no source files"
+        file("src999", "dummy").createFile()
+
+        def buildFile = testFile("build.gradle") <<
+                """
+                task copy(type: Copy) {
+                    from 'src999'
+                    into 'dest'
+
+                    includeEmptyDirs = false
+                }
+                """
+        usingBuildFile(buildFile).withTasks("copy").run()
+
+        assert !file("dest", "emptyDir").exists()
+        assert !file("dest", "yet", "another", "veryEmptyDir").exists()
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
new file mode 100644
index 0000000..4ff2896
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * 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.tasks
+
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+
+public class FileTreeCopyIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources resources = new TestResources("copyTestResources")
+
+    @Test public void testCopyWithClosure() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree('src') {
+                      exclude '**/ignore/**'
+                   }.copy { into 'dest'}
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'one/sub/onesub.a',
+                'one/sub/onesub.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyWithClosureBaseDir() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree((Object){ 'src' }).exclude('**/ignore/**').copy { into 'dest'}
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'one/sub/onesub.a',
+                'one/sub/onesub.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyWithMap() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree(dir:'src', excludes:['**/ignore/**', '**/sub/**']).copy { into 'dest'}
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyFluent() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree(dir:'src').exclude('**/ignore/**', '**/sub/*.?').copy { into 'dest' }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractExecutionResult.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractExecutionResult.java
deleted file mode 100644
index fef20d5..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractExecutionResult.java
+++ /dev/null
@@ -1,35 +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.fixtures;
-
-import org.gradle.util.Matchers;
-
-public abstract class AbstractExecutionResult implements ExecutionResult {
-    public void assertOutputHasNoStackTraces() {
-        assertNoStackTraces(getOutput(), "Standard output");
-    }
-
-    public void assertErrorHasNoStackTraces() {
-        assertNoStackTraces(getError(), "Standard error");
-    }
-
-    private void assertNoStackTraces(String output, String displayName) {
-        if (Matchers.containsLine(Matchers.matchesRegexp("\\s+at [\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
-            throw new RuntimeException(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
-        }
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
deleted file mode 100644
index b6dd865..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
+++ /dev/null
@@ -1,262 +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.fixtures;
-
-import java.io.File;
-import java.util.*;
-
-public abstract class AbstractGradleExecuter implements GradleExecuter {
-    private final List<String> args = new ArrayList<String>();
-    private final List<String> tasks = new ArrayList<String>();
-    private File workingDir;
-    private boolean quiet;
-    private boolean taskList;
-    private boolean dependencyList;
-    private boolean searchUpwards;
-    private Map<String, String> environmentVars = new HashMap<String, String>();
-    private List<File> initScripts = new ArrayList<File>();
-    private String executable;
-    private File userHomeDir;
-    private File buildScript;
-    private File projectDir;
-    private String buildScriptText;
-    private File settingsFile;
-
-    public GradleExecuter reset() {
-        args.clear();
-        tasks.clear();
-        initScripts.clear();
-        workingDir = null;
-        projectDir = null;
-        buildScript = null;
-        buildScriptText = null;
-        settingsFile = null;
-        quiet = false;
-        taskList = false;
-        dependencyList = false;
-        searchUpwards = false;
-        executable = null;
-        userHomeDir = null;
-        environmentVars.clear();
-        return this;
-    }
-
-    public GradleExecuter inDirectory(File directory) {
-        workingDir = directory;
-        return this;
-    }
-
-    public File getWorkingDir() {
-        return workingDir;
-    }
-
-    protected void copyTo(GradleExecuter executer) {
-        if (workingDir != null) {
-            executer.inDirectory(workingDir);
-        }
-        if (projectDir != null) {
-            executer.usingProjectDirectory(projectDir);
-        }
-        if (buildScript != null) {
-            executer.usingBuildScript(buildScript);
-        }
-        if (buildScriptText != null) {
-            executer.usingBuildScript(buildScriptText);
-        }
-        if (settingsFile != null) {
-            executer.usingSettingsFile(settingsFile);
-        }
-        for (File initScript : initScripts) {
-            executer.usingInitScript(initScript);
-        }
-        executer.withTasks(tasks);
-        executer.withArguments(args);
-        executer.withEnvironmentVars(environmentVars);
-        executer.usingExecutable(executable);
-        if (quiet) {
-            executer.withQuietLogging();
-        }
-        if (taskList) {
-            executer.withTaskList();
-        }
-        if (dependencyList) {
-            executer.withDependencyList();
-        }
-        executer.withUserHomeDir(userHomeDir);
-    }
-
-    public GradleExecuter usingBuildScript(File buildScript) {
-        this.buildScript = buildScript;
-        return this;
-    }
-
-    public GradleExecuter usingBuildScript(String scriptText) {
-        this.buildScriptText = scriptText;
-        return this;
-    }
-
-    public GradleExecuter usingProjectDirectory(File projectDir) {
-        this.projectDir = projectDir;
-        return this;
-    }
-
-    public GradleExecuter usingSettingsFile(File settingsFile) {
-        this.settingsFile = settingsFile;
-        return this;
-    }
-
-    public GradleExecuter usingInitScript(File initScript) {
-        initScripts.add(initScript);
-        return this;
-    }
-
-    public File getUserHomeDir() {
-        return userHomeDir;
-    }
-
-    public GradleExecuter withUserHomeDir(File userHomeDir) {
-        this.userHomeDir = userHomeDir;
-        return this;
-    }
-
-    public GradleExecuter usingExecutable(String script) {
-        this.executable = script;
-        return this;
-    }
-
-    public String getExecutable() {
-        return executable;
-    }
-
-    public GradleExecuter withSearchUpwards() {
-        searchUpwards = true;
-        return this;
-    }
-
-    public boolean isQuiet() {
-        return quiet;
-    }
-
-    public GradleExecuter withQuietLogging() {
-        quiet = true;
-        return this;
-    }
-
-    public GradleExecuter withTaskList() {
-        taskList = true;
-        return this;
-    }
-
-    public GradleExecuter withDependencyList() {
-        dependencyList = true;
-        return this;
-    }
-
-    public GradleExecuter withArguments(String... args) {
-        return withArguments(Arrays.asList(args));
-    }
-
-    public GradleExecuter withArguments(List<String> args) {
-        this.args.clear();
-        this.args.addAll(args);
-        return this;
-    }
-
-    public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
-        environmentVars.clear();
-        for (Map.Entry<String, ?> entry : environment.entrySet()) {
-            environmentVars.put(entry.getKey(), entry.getValue().toString());
-        }
-        return this;
-    }
-
-    public Map<String, String> getEnvironmentVars() {
-        return environmentVars;
-    }
-
-    public GradleExecuter withTasks(String... names) {
-        return withTasks(Arrays.asList(names));
-    }
-
-    public GradleExecuter withTasks(List<String> names) {
-        tasks.clear();
-        tasks.addAll(names);
-        return this;
-    }
-
-    protected List<String> getAllArgs() {
-        List<String> allArgs = new ArrayList<String>();
-        if (buildScript != null) {
-            allArgs.add("--build-file");
-            allArgs.add(buildScript.getAbsolutePath());
-        }
-        if (buildScriptText != null) {
-            allArgs.add("--embedded");
-            allArgs.add(buildScriptText);
-        }
-        if (projectDir != null) {
-            allArgs.add("--project-dir");
-            allArgs.add(projectDir.getAbsolutePath());
-        }
-        for (File initScript : initScripts) {
-            allArgs.add("--init-script");
-            allArgs.add(initScript.getAbsolutePath());
-        }
-        if (settingsFile != null) {
-            allArgs.add("--settings-file");
-            allArgs.add(settingsFile.getAbsolutePath());
-        }
-        if (quiet) {
-            allArgs.add("--quiet");
-        }
-        if (taskList) {
-            allArgs.add("tasks");
-        }
-        if (dependencyList) {
-            allArgs.add("dependencies");
-        }
-        if (!searchUpwards) {
-            allArgs.add("--no-search-upward");
-        }
-        if (userHomeDir != null) {
-            args.add("--gradle-user-home");
-            args.add(userHomeDir.getAbsolutePath());
-        }
-        allArgs.addAll(args);
-        allArgs.addAll(tasks);
-        return allArgs;
-    }
-
-    public final ExecutionResult run() {
-        try {
-            return doRun();
-        } finally {
-            reset();
-        }
-    }
-
-    public final ExecutionFailure runWithFailure() {
-        try {
-            return doRunWithFailure();
-        } finally {
-            reset();
-        }
-    }
-
-    protected abstract ExecutionResult doRun();
-
-    protected abstract ExecutionFailure doRunWithFailure();
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
deleted file mode 100644
index f2e043e..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.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.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/core/src/integTest/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
deleted file mode 100644
index 004867b..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
+++ /dev/null
@@ -1,65 +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.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) {
-        registerDaemon(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;
-    }
-
-    public static void registerDaemon(final File userHomeDir) {
-        assert userHomeDir != null;
-        if (!DAEMONS.add(userHomeDir)) {
-            return;
-        }
-        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-            public void run() {
-//                ExecHandleBuilder builder = new ExecHandleBuilder();
-//                builder.workingDir(new File(".").getAbsolutePath());
-//                builder.executable(Jvm.current().getJpsExecutable());
-//                builder.args("-lm");
-//                builder.setStandardOutput(new ByteArrayOutputStream());
-//                builder.build().start().waitForFinish();
-            }
-        }));
-    }
-
-    @Override
-    protected List<String> getAllArgs() {
-        List<String> args = new ArrayList<String>();
-        args.add("--daemon");
-        args.addAll(super.getAllArgs());
-        return args;
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionResult.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
deleted file mode 100644
index d28d51a..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
+++ /dev/null
@@ -1,51 +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.integtests.fixtures;
-
-public interface ExecutionResult {
-    String getOutput();
-
-    String getError();
-
-    void assertOutputHasNoStackTraces();
-
-    void assertErrorHasNoStackTraces();
-
-    /**
-     * Asserts that exactly the given set of tasks have been executed in the given order. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTasksExecuted(String... taskPaths);
-
-    /**
-     * Asserts that exactly the given set of tasks have been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTasksSkipped(String... taskPaths);
-
-    /**
-     * Asserts the given task has been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTaskSkipped(String taskPath);
-
-    /**
-     * Asserts that exactly the given set of tasks have not been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTasksNotSkipped(String... taskPaths);
-
-    /**
-     * Asserts that the given task has not been skipped. Note: ignores buildSrc tasks.
-     */
-    ExecutionResult assertTaskNotSkipped(String taskPath);
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
deleted file mode 100644
index 21e0c41..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
+++ /dev/null
@@ -1,299 +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.fixtures;
-
-import org.gradle.api.Action;
-import org.gradle.process.internal.ExecHandle;
-import org.gradle.process.internal.ExecHandleBuilder;
-import org.gradle.util.GUtil;
-import org.gradle.util.Jvm;
-import org.gradle.util.OperatingSystem;
-import org.gradle.util.TestFile;
-import org.hamcrest.Matcher;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.util.*;
-import java.util.regex.Pattern;
-
-import static org.gradle.util.Matchers.containsLine;
-import static org.gradle.util.Matchers.matchesRegexp;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-public class ForkingGradleExecuter extends AbstractGradleExecuter {
-    private static final Logger LOG = LoggerFactory.getLogger(ForkingGradleExecuter.class);
-    private final TestFile gradleHomeDir;
-    private final List<String> gradleOpts = new ArrayList<String>();
-
-    public ForkingGradleExecuter(TestFile gradleHomeDir) {
-        this.gradleHomeDir = gradleHomeDir;
-        gradleOpts.add("-ea");
-    }
-
-    public TestFile getGradleHomeDir() {
-        return gradleHomeDir;
-    }
-
-    @Override
-    protected ExecutionResult doRun() {
-        Map result = doRun(false);
-        return new ForkedExecutionResult(result);
-    }
-
-    @Override
-    protected ExecutionFailure doRunWithFailure() {
-        Map result = doRun(true);
-        return new ForkedExecutionFailure(result);
-    }
-
-    /**
-     * Adds some options to the GRADLE_OPTS environment variable to use.
-     */
-    public void addGradleOpts(String... opts) {
-        gradleOpts.addAll(Arrays.asList(opts));
-    }
-
-    protected Map doRun(boolean expectFailure) {
-        gradleHomeDir.assertIsDir();
-
-        CommandBuilder commandBuilder = OperatingSystem.current().isWindows() ? new WindowsCommandBuilder()
-                : new UnixCommandBuilder();
-
-        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream errStream = new ByteArrayOutputStream();
-
-        ExecHandleBuilder builder = new ExecHandleBuilder() {
-            @Override
-            public File getWorkingDir() {
-                // Override this, so that the working directory is not canonicalised. Some int tests require that
-                // the working directory is not canonicalised
-                return ForkingGradleExecuter.this.getWorkingDir();
-            }
-        };
-        builder.setStandardOutput(outStream);
-        builder.setErrorOutput(errStream);
-        builder.environment("GRADLE_HOME", "");
-        builder.environment("JAVA_HOME", Jvm.current().getJavaHome());
-        builder.environment("GRADLE_OPTS", GUtil.join(gradleOpts, " "));
-        builder.environment(getEnvironmentVars());
-        builder.workingDir(getWorkingDir());
-
-        commandBuilder.build(builder);
-
-        builder.args(getAllArgs());
-
-        LOG.info(String.format("Execute in %s with: %s %s", builder.getWorkingDir(), builder.getExecutable(),
-                builder.getArgs()));
-
-        ExecHandle proc = builder.build();
-        int exitValue = proc.start().waitForFinish().getExitValue();
-
-        String output = outStream.toString();
-        String error = errStream.toString();
-        boolean failed = exitValue != 0;
-
-        LOG.info("OUTPUT: " + output);
-        LOG.info("ERROR: " + error);
-
-        if (failed != expectFailure) {
-            String message = String.format("Gradle execution %s in %s with: %s %s%nOutput:%n%s%nError:%n%s%n-----%n",
-                    expectFailure ? "did not fail" : "failed", builder.getWorkingDir(), builder.getExecutable(),
-                    builder.getArgs(), output, error);
-            System.out.println(message);
-            throw new RuntimeException(message);
-        }
-        return GUtil.map("output", output, "error", error);
-    }
-
-    private interface CommandBuilder {
-        void build(ExecHandleBuilder builder);
-    }
-
-    private class WindowsCommandBuilder implements CommandBuilder {
-        public void build(ExecHandleBuilder builder) {
-            String cmd;
-            if (getExecutable() != null) {
-                cmd = getExecutable().replace('/', File.separatorChar);
-            } else {
-                cmd = "gradle";
-            }
-            builder.executable("cmd");
-            builder.args("/c", cmd);
-            String gradleHome = gradleHomeDir.getAbsolutePath();
-            builder.environment("Path", String.format("%s\\bin;%s", gradleHome, System.getenv("Path")));
-            builder.environment("GRADLE_EXIT_CONSOLE", "true");
-        }
-    }
-
-    private class UnixCommandBuilder implements CommandBuilder {
-        public void build(ExecHandleBuilder builder) {
-            if (getExecutable() != null) {
-                builder.executable(String.format("%s/%s", getWorkingDir().getAbsolutePath(), getExecutable()));
-            } else {
-                builder.executable(String.format("%s/bin/gradle", gradleHomeDir.getAbsolutePath()));
-            }
-        }
-    }
-
-    private static class ForkedExecutionResult extends AbstractExecutionResult {
-        private final Map result;
-        private final Pattern skippedTaskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)\\s+((SKIPPED)|(UP-TO-DATE))");
-        private final Pattern notSkippedTaskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)");
-        private final Pattern taskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)(\\s+.+)?");
-
-        public ForkedExecutionResult(Map result) {
-            this.result = result;
-        }
-
-        public String getOutput() {
-            return result.get("output").toString();
-        }
-
-        public String getError() {
-            return result.get("error").toString();
-        }
-
-        public ExecutionResult assertTasksExecuted(String... taskPaths) {
-            List<String> tasks = grepTasks(taskPattern);
-            List<String> expectedTasks = Arrays.asList(taskPaths);
-            assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
-            return this;
-        }
-
-        public ExecutionResult assertTasksSkipped(String... taskPaths) {
-            Set<String> tasks = new HashSet<String>(grepTasks(skippedTaskPattern));
-            Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
-            assertThat(String.format("Expected skipped tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
-            return this;
-        }
-
-        public ExecutionResult assertTaskSkipped(String taskPath) {
-            Set<String> tasks = new HashSet<String>(grepTasks(skippedTaskPattern));
-            assertThat(String.format("Expected skipped task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
-            return this;
-        }
-
-        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
-            Set<String> tasks = new HashSet<String>(grepTasks(notSkippedTaskPattern));
-            Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
-            assertThat(String.format("Expected executed tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
-            return this;
-        }
-
-        public ExecutionResult assertTaskNotSkipped(String taskPath) {
-            Set<String> tasks = new HashSet<String>(grepTasks(notSkippedTaskPattern));
-            assertThat(String.format("Expected executed task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
-            return this;
-        }
-
-        private List<String> grepTasks(final Pattern pattern) {
-            final List<String> tasks = new ArrayList<String>();
-
-            eachLine(new Action<String>() {
-                public void execute(String s) {
-                    java.util.regex.Matcher matcher = pattern.matcher(s);
-                    if (matcher.matches()) {
-                        String taskName = matcher.group(1);
-                        if (!taskName.startsWith(":buildSrc:")) {
-                            tasks.add(taskName);
-                        }
-                    }
-                }
-            });
-
-            return tasks;
-        }
-
-        private void eachLine(Action<String> action) {
-            BufferedReader reader = new BufferedReader(new StringReader(getOutput()));
-            String line;
-            try {
-                while ((line = reader.readLine()) != null) {
-                    action.execute(line);
-                }
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    private static class ForkedExecutionFailure extends ForkedExecutionResult implements ExecutionFailure {
-        private final Pattern causePattern = Pattern.compile("(?m)^Cause: ");
-
-        public ForkedExecutionFailure(Map result) {
-            super(result);
-        }
-
-        public ExecutionFailure assertHasLineNumber(int lineNumber) {
-            assertThat(getError(), containsString(String.format(" line: %d", lineNumber)));
-            return this;
-        }
-
-        public ExecutionFailure assertHasFileName(String filename) {
-            assertThat(getError(), containsLine(startsWith(filename)));
-            return this;
-        }
-
-        public ExecutionFailure assertHasCause(String description) {
-            assertThatCause(startsWith(description));
-            return this;
-        }
-
-        public ExecutionFailure assertThatCause(Matcher<String> matcher) {
-            String error = getError();
-            java.util.regex.Matcher regExpMatcher = causePattern.matcher(error);
-            int pos = 0;
-            while (pos < error.length()) {
-                if (!regExpMatcher.find(pos)) {
-                    break;
-                }
-                int start = regExpMatcher.end();
-                String cause;
-                if (regExpMatcher.find(start)) {
-                    cause = error.substring(start, regExpMatcher.start());
-                    pos = regExpMatcher.start();
-                } else {
-                    cause = error.substring(start);
-                    pos = error.length();
-                }
-                if (matcher.matches(cause)) {
-                    return this;
-                }
-            }
-            fail(String.format("No matching cause found in '%s'", error));
-            return this;
-        }
-
-        public ExecutionFailure assertHasNoCause() {
-            assertThat(getError(), not(matchesRegexp(causePattern)));
-            return this;
-        }
-
-        public ExecutionFailure assertHasDescription(String context) {
-            assertThatDescription(startsWith(context));
-            return this;
-        }
-
-        public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
-            assertThat(getError(), containsLine(matcher));
-            return this;
-        }
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
deleted file mode 100644
index 0e31620..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
+++ /dev/null
@@ -1,179 +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.integtests.fixtures;
-
-import org.gradle.util.*;
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-
-/**
- * Provides access to a Gradle distribution for integration testing.
- */
-public class GradleDistribution implements MethodRule, TestFileContext, BasicGradleDistribution {
-    private static final TestFile USER_HOME_DIR;
-    private static final TestFile GRADLE_HOME_DIR;
-    private static final TestFile SAMPLES_DIR;
-    private static final TestFile USER_GUIDE_OUTPUT_DIR;
-    private static final TestFile USER_GUIDE_INFO_DIR;
-    private static final TestFile DISTS_DIR;
-    private static final TestFile LIBS_REPO;
-    private final TemporaryFolder temporaryFolder = new TemporaryFolder();
-    private TestFile userHome;
-
-    static {
-        String workerId = System.getProperty("org.gradle.test.worker", "1");
-        USER_HOME_DIR = file("integTest.gradleUserHomeDir", "intTestHomeDir").file(String.format("worker-%s", workerId));
-        GRADLE_HOME_DIR = file("integTest.gradleHomeDir", null);
-        SAMPLES_DIR = file("integTest.samplesdir", new File(GRADLE_HOME_DIR, "samples").getAbsolutePath());
-        USER_GUIDE_OUTPUT_DIR = file("integTest.userGuideOutputDir",
-                "subprojects/docs/src/samples/userguideOutput");
-        USER_GUIDE_INFO_DIR = file("integTest.userGuideInfoDir", "subprojects/docs/build/src");
-        DISTS_DIR = file("integTest.distsDir", "build/distributions");
-        LIBS_REPO = file("integTest.libsRepo", "build/repo");
-    }
-
-    public GradleDistribution() {
-        this.userHome = USER_HOME_DIR;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Gradle %s", GradleVersion.current().getVersion());
-    }
-
-    public boolean worksWith(Jvm jvm) {
-        return jvm.isJava5Compatible();
-    }
-
-    public void requireOwnUserHomeDir() {
-        userHome = getTestDir().file("user-home");
-    }
-
-    public Statement apply(Statement base, FrameworkMethod method, Object target) {
-        return temporaryFolder.apply(base, method, target);
-    }
-
-    private static TestFile file(String propertyName, String defaultFile) {
-        String path = System.getProperty(propertyName, defaultFile);
-        if (path == null) {
-            throw new RuntimeException(String.format("You must set the '%s' property to run the integration tests.",
-                    propertyName));
-        }
-        return new TestFile(new File(path));
-    }
-
-    /**
-     * The user home dir used for the current test. This is usually shared with other tests unless
-     * {@link #requireOwnUserHomeDir()} is called.
-     */
-    public TestFile getUserHomeDir() {
-        return userHome;
-    }
-
-    /**
-     * The distribution for the current test. This is usually shared with other tests.
-     */
-    public TestFile getGradleHomeDir() {
-        return GRADLE_HOME_DIR;
-    }
-
-    public String getVersion() {
-        return GradleVersion.current().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.
-     */
-    public TestFile getSamplesDir() {
-        return SAMPLES_DIR;
-    }
-
-    public TestFile getUserGuideInfoDir() {
-        return USER_GUIDE_INFO_DIR;
-    }
-
-    public TestFile getUserGuideOutputDir() {
-        return USER_GUIDE_OUTPUT_DIR;
-    }
-
-    /**
-     * The directory containing the distribution Zips
-     */
-    public TestFile getDistributionsDir() {
-        return DISTS_DIR;
-    }
-
-    public TestFile getLibsRepo() {
-        return LIBS_REPO;
-    }
-
-    /**
-     * Returns true if the given file is either part of the distributions, samples, or test files.
-     */
-    public boolean isFileUnderTest(File file) {
-        return GRADLE_HOME_DIR.isSelfOrDescendent(file)
-                || SAMPLES_DIR.isSelfOrDescendent(file)
-                || getTestDir().isSelfOrDescendent(file)
-                || getUserHomeDir().isSelfOrDescendent(file);
-    }
-
-    /**
-     * Returns a scratch-pad directory for the current test. This directory is not shared with any other tests.
-     */
-    public TestFile getTestDir() {
-        return temporaryFolder.getDir();
-    }
-
-    public TemporaryFolder getTemporaryFolder() {
-        return temporaryFolder;
-    }
-
-    /**
-     * Returns a previous version of Gradle.
-     *
-     * @param version The Gradle version
-     * @return An executer
-     */
-    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)
-     */
-    public TestFile file(Object... path) {
-        return getTestDir().file(path);
-    }
-
-    /**
-     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
-     */
-    public TestFile testFile(Object... path) {
-        return getTestDir().file(path);
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
deleted file mode 100644
index 998c727..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
+++ /dev/null
@@ -1,175 +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.fixtures;
-
-import org.gradle.StartParameter;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.util.TestFile;
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-
-/**
- * A Junit rule which provides a {@link GradleExecuter} implementation that executes Gradle using a given {@link
- * 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.
- */
-public class GradleDistributionExecuter extends AbstractGradleExecuter implements MethodRule {
-    private static final String IGNORE_SYS_PROP = "org.gradle.integtest.ignore";
-    private static final String EXECUTER_SYS_PROP = "org.gradle.integtest.executer";
-    private static final Executer EXECUTER;
-    private GradleDistribution dist;
-    private boolean workingDirSet;
-    private boolean userHomeSet;
-
-    public enum Executer {
-        forking, embedded, daemon
-    }
-
-    static {
-        EXECUTER = Executer.valueOf(System.getProperty(EXECUTER_SYS_PROP, Executer.forking.toString()).toLowerCase());
-    }
-
-    public static Executer getExecuter() {
-        return EXECUTER;
-    }
-
-    public GradleDistributionExecuter(GradleDistribution dist) {
-        this.dist = dist;
-        reset();
-    }
-
-    public GradleDistributionExecuter() {
-    }
-
-    public Statement apply(Statement base, final FrameworkMethod method, Object target) {
-        if (System.getProperty(IGNORE_SYS_PROP) != null) {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    System.out.println(String.format("Skipping test '%s'", method.getName()));
-                }
-            };
-        }
-
-        if (dist == null) {
-            dist = RuleHelper.getField(target, GradleDistribution.class);
-        }
-        reset();
-        return base;
-    }
-
-    @Override
-    public GradleExecuter reset() {
-        super.reset();
-        workingDirSet = false;
-        userHomeSet = false;
-        return this;
-    }
-
-    @Override
-    public GradleExecuter inDirectory(File directory) {
-        super.inDirectory(directory);
-        workingDirSet = true;
-        return this;
-    }
-
-    @Override
-    public GradleExecuter withUserHomeDir(File userHomeDir) {
-        super.withUserHomeDir(userHomeDir);
-        userHomeSet = true;
-        return this;
-    }
-
-    @Override
-    protected ExecutionResult doRun() {
-        return checkResult(configureExecuter().run());
-    }
-
-    @Override
-    protected ExecutionFailure doRunWithFailure() {
-        return checkResult(configureExecuter().runWithFailure());
-    }
-
-    private <T extends ExecutionResult> T checkResult(T result) {
-        // Assert that nothing unexpected was logged
-        result.assertOutputHasNoStackTraces();
-        result.assertErrorHasNoStackTraces();
-        if (getExecutable() == null) {
-            // Assert that no temp files are left lying around
-            // Note: don't do this if a custom executable is used, as we don't know (and probably don't care) whether the executable cleans up or not
-            getTmpDir().assertIsEmptyDir();
-        }
-        return result;
-    }
-
-    private GradleExecuter configureExecuter() {
-        if (!workingDirSet) {
-            inDirectory(dist.getTestDir());
-        }
-        if (!userHomeSet) {
-            withUserHomeDir(dist.getUserHomeDir());
-        }
-
-        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);
-
-        InProcessGradleExecuter inProcessGradleExecuter = new InProcessGradleExecuter(parameter);
-        copyTo(inProcessGradleExecuter);
-
-        GradleExecuter returnedExecuter = inProcessGradleExecuter;
-
-        if (EXECUTER != Executer.embedded || !inProcessGradleExecuter.canExecute()) {
-            boolean useDaemon = EXECUTER == Executer.daemon && getExecutable() == null;
-            ForkingGradleExecuter forkingGradleExecuter = useDaemon ? new DaemonGradleExecuter(dist.getGradleHomeDir()) : new ForkingGradleExecuter(dist.getGradleHomeDir());
-            copyTo(forkingGradleExecuter);
-            TestFile tmpDir = getTmpDir();
-            if (tmpDir != null) {
-                tmpDir.deleteDir().createDir();
-                forkingGradleExecuter.addGradleOpts(String.format("-Djava.io.tmpdir=%s", tmpDir));
-            }
-            returnedExecuter = forkingGradleExecuter;
-        }
-
-        boolean settingsFound = false;
-        for (
-                TestFile dir = new TestFile(getWorkingDir()); dir != null && dist.isFileUnderTest(dir) && !settingsFound;
-                dir = dir.getParentFile()) {
-            if (dir.file("settings.gradle").isFile()) {
-                settingsFound = true;
-            }
-        }
-        if (settingsFound) {
-            returnedExecuter.withSearchUpwards();
-        }
-
-        return returnedExecuter;
-    }
-
-    private TestFile getTmpDir() {
-        return new TestFile(dist.getUserHomeDir(), "tmp");
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
deleted file mode 100644
index 6826368..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
+++ /dev/null
@@ -1,105 +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.integtests.fixtures;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-public interface GradleExecuter {
-    GradleExecuter inDirectory(File directory);
-
-    /**
-     * Enables search upwards. Defaults to false.
-     */
-    GradleExecuter withSearchUpwards();
-
-    /**
-     * Sets the task names to execute. Defaults to an empty list.
-     */
-    GradleExecuter withTasks(String... names);
-
-    /**
-     * Sets the task names to execute. Defaults to an empty list.
-     */
-    GradleExecuter withTasks(List<String> names);
-
-    GradleExecuter withTaskList();
-
-    GradleExecuter withDependencyList();
-
-    GradleExecuter withQuietLogging();
-
-    /**
-     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
-     */
-    GradleExecuter withArguments(String... args);
-
-    /**
-     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
-     */
-    GradleExecuter withArguments(List<String> args);
-
-    /**
-     * Sets the environment variables to use when executing the build. Defaults to the environment of this process.
-     */
-    GradleExecuter withEnvironmentVars(Map<String, ?> environment);
-
-    GradleExecuter usingSettingsFile(File settingsFile);
-
-    GradleExecuter usingInitScript(File initScript);
-
-    /**
-     * Uses the given project directory
-     */
-    GradleExecuter usingProjectDirectory(File projectDir);
-
-    /**
-     * Uses the given build script
-     */
-    GradleExecuter usingBuildScript(File buildScript);
-
-    /**
-     * Uses the given build script
-     *
-     * @param scriptText The script text.
-     */
-    GradleExecuter usingBuildScript(String scriptText);
-
-    /**
-     * Sets the user home dir. Set to null to use the default user home dir.
-     */
-    GradleExecuter withUserHomeDir(File userHomeDir);
-
-    /**
-     * Sets the executable to use. Set to null to use the default executable (if any)
-     */
-    GradleExecuter usingExecutable(String script);
-
-    /**
-     * Executes the requested build, asserting that the build succeeds. Resets the configuration of this executer.
-     *
-     * @return The result.
-     */
-    ExecutionResult run();
-
-    /**
-     * Executes the requested build, asserting that the build fails. Resets the configuration of this executer.
-     *
-     * @return The result.
-     */
-    ExecutionFailure runWithFailure();
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/HttpServer.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
deleted file mode 100644
index 286a16b..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
+++ /dev/null
@@ -1,243 +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.integtests.fixtures
-
-import java.security.Principal
-import javax.servlet.http.HttpServletRequest
-import javax.servlet.http.HttpServletResponse
-import org.junit.rules.MethodRule
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
-import org.mortbay.jetty.Handler
-import org.mortbay.jetty.Request
-import org.mortbay.jetty.Server
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.mortbay.jetty.handler.*
-import org.mortbay.jetty.security.*
-import org.mortbay.jetty.HttpHeaders
-import org.mortbay.jetty.MimeTypes
-
-class HttpServer implements MethodRule {
-    private Logger logger = LoggerFactory.getLogger(HttpServer.class)
-    private final Server server = new Server(0)
-    private final HandlerCollection collection = new HandlerCollection()
-
-    def HttpServer() {
-        HandlerCollection handlers = new HandlerCollection()
-        handlers.addHandler(collection)
-        handlers.addHandler(new DefaultHandler())
-        server.setHandler(handlers)
-    }
-
-    def start() {
-        server.start()
-    }
-
-    def stop() {
-        server.stop()
-    }
-
-    Statement apply(Statement base, FrameworkMethod method, Object target) {
-        return new Statement() {
-            @Override
-            void evaluate() {
-                try {
-                    base.evaluate()
-                } finally {
-                    stop()
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds a given file at the given URL. The source file can be either a file or a directory.
-     */
-    def allowGet(String path, File srcFile) {
-        allow(path, true, fileHandler(path, srcFile))
-    }
-
-    private AbstractHandler fileHandler(String path, File srcFile) {
-        return new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                def file
-                if (request.pathInfo == path) {
-                    file = srcFile
-                } else {
-                    def relativePath = request.pathInfo.substring(path.length() + 1)
-                    file = new File(srcFile, relativePath)
-                }
-                if (!file.isFile()) {
-                    response.sendError(404, "'$target' does not exist")
-                    return
-                }
-                response.setDateHeader(HttpHeaders.LAST_MODIFIED, file.lastModified())
-                response.setContentLength((int)file.length())
-                response.setContentType(new MimeTypes().getMimeByExtension(file.name).toString())
-                response.outputStream.bytes = file.bytes
-            }
-        }
-    }
-
-    /**
-     * Adds a broken resource at the given URL.
-     */
-    def addBroken(String path) {
-        allow(path, true, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                response.sendError(500, "broken")
-            }
-        })
-    }
-
-    /**
-     * Allow one GET request for the given URL. Reads the request content from the given file.
-     */
-    def expectGet(String path, File srcFile) {
-        expect(path, true, fileHandler(path, srcFile))
-    }
-
-    /**
-     * Allow one PUT request for the given URL. Writes the request content to the given file.
-     */
-    def expectPut(String path, File destFile) {
-        expect(path, false, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                destFile.bytes = request.inputStream.bytes
-            }
-        })
-    }
-
-    /**
-     * Allow one PUT request for the given URL, with the given credentials. Writes the request content to the given file.
-     */
-    def expectPut(String path, String username, String password, File destFile) {
-        def realm = new TestUserRealm()
-        realm.username = username
-        realm.password = password
-        def constraint = new Constraint()
-        constraint.name = Constraint.__BASIC_AUTH
-        constraint.authenticate = true
-        constraint.roles = ['*'] as String[]
-        def constraintMapping = new ConstraintMapping()
-        constraintMapping.pathSpec = path
-        constraintMapping.constraint = constraint
-        def securityHandler = new SecurityHandler()
-        securityHandler.userRealm = realm
-        securityHandler.constraintMappings = [constraintMapping] as ConstraintMapping[]
-        securityHandler.authenticator = new BasicAuthenticator()
-        collection.addHandler(securityHandler)
-
-        expect(path, false, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                if (request.remoteUser != username) {
-                    response.sendError(500, 'unexpected username')
-                    return
-                }
-                destFile.bytes = request.inputStream.bytes
-            }
-        })
-    }
-
-    def expect(String path, boolean recursive, Handler handler) {
-        boolean run
-        add(path, recursive, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                if (run) {
-                    return
-                }
-                run = true
-                handler.handle(target, request, response, dispatch)
-                request.handled = true
-            }
-        })
-    }
-
-    def allow(String path, boolean recursive, Handler handler) {
-        add(path, recursive, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                handler.handle(target, request, response, dispatch)
-                request.handled = true
-            }
-        })
-    }
-
-    def add(String path, boolean recursive, Handler handler) {
-        assert path.startsWith('/')
-        assert path == '/' || !path.endsWith('/')
-        def prefix = path == '/' ? '/' : path + '/'
-        collection.addHandler(new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                boolean match = request.pathInfo == path || (recursive && request.pathInfo.startsWith(prefix))
-                if (match && !request.handled) {
-                    handler.handle(target, request, response, dispatch)
-                }
-            }
-        })
-    }
-
-    def int getPort() {
-        return server.connectors[0].localPort
-    }
-
-    static class TestUserRealm implements UserRealm {
-        String username
-        String password
-
-        Principal authenticate(String username, Object credentials, Request request) {
-            if (username == this.username && password == credentials) {
-                return getPrincipal(username)
-            }
-            return null
-        }
-
-        String getName() {
-            return "test"
-        }
-
-        Principal getPrincipal(String username) {
-            return new Principal() {
-                String getName() {
-                    return username
-                }
-            }
-        }
-
-        boolean reauthenticate(Principal user) {
-            return false
-        }
-
-        boolean isUserInRole(Principal user, String role) {
-            return false
-        }
-
-        void disassociate(Principal user) {
-        }
-
-        Principal pushRole(Principal user, String role) {
-            return user
-        }
-
-        Principal popRole(Principal user) {
-            return user
-        }
-
-        void logout(Principal user) {
-        }
-
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
deleted file mode 100644
index 5d83109..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
+++ /dev/null
@@ -1,378 +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.fixtures;
-
-import junit.framework.AssertionFailedError;
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.gradle.api.GradleException;
-import org.gradle.api.LocationAwareException;
-import org.gradle.api.Task;
-import org.gradle.api.execution.TaskExecutionGraph;
-import org.gradle.api.execution.TaskExecutionGraphListener;
-import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.StandardOutputListener;
-import org.gradle.api.tasks.TaskState;
-import org.gradle.initialization.CommandLineParser;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.hamcrest.Matcher;
-
-import java.io.File;
-import java.io.StringWriter;
-import java.util.*;
-
-import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-public class InProcessGradleExecuter extends AbstractGradleExecuter {
-    private StartParameter parameter;
-
-    public InProcessGradleExecuter(StartParameter parameter) {
-        this.parameter = parameter;
-    }
-
-    @Override
-    public GradleExecuter reset() {
-        super.reset();
-        parameter = new StartParameter();
-        return this;
-    }
-
-    public StartParameter getParameter() {
-        return parameter;
-    }
-
-    @Override
-    public GradleExecuter inDirectory(File directory) {
-        parameter.setCurrentDir(directory);
-        return this;
-    }
-
-    @Override
-    public InProcessGradleExecuter withSearchUpwards() {
-        parameter.setSearchUpwards(true);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter withTasks(List<String> names) {
-        parameter.setTaskNames(names);
-        return this;
-    }
-
-    @Override
-    public InProcessGradleExecuter withTaskList() {
-        parameter.setTaskNames(toList("tasks"));
-        return this;
-    }
-
-    @Override
-    public InProcessGradleExecuter withDependencyList() {
-        parameter.setTaskNames(toList("dependencies"));
-        return this;
-    }
-
-    @Override
-    public InProcessGradleExecuter usingSettingsFile(File settingsFile) {
-        parameter.setSettingsFile(settingsFile);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter usingInitScript(File initScript) {
-        parameter.addInitScript(initScript);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter usingProjectDirectory(File projectDir) {
-        parameter.setProjectDir(projectDir);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter usingBuildScript(File buildScript) {
-        parameter.setBuildFile(buildScript);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter usingBuildScript(String scriptText) {
-        parameter.useEmbeddedBuildFile(scriptText);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter withArguments(List<String> args) {
-        CommandLineParser parser = new CommandLineParser();
-        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
-        converter.configure(parser);
-        converter.convert(parser.parse(args), parameter);
-        return this;
-    }
-
-    @Override
-    public GradleExecuter withUserHomeDir(File userHomeDir) {
-        parameter.setGradleUserHomeDir(userHomeDir);
-        return this;
-    }
-
-    @Override
-    protected ExecutionResult doRun() {
-        OutputListenerImpl outputListener = new OutputListenerImpl();
-        OutputListenerImpl errorListener = new OutputListenerImpl();
-        BuildListenerImpl buildListener = new BuildListenerImpl();
-        BuildResult result = doRun(outputListener, errorListener, buildListener);
-        result.rethrowFailure();
-        return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
-                outputListener.toString(), errorListener.toString());
-    }
-
-    @Override
-    protected ExecutionFailure doRunWithFailure() {
-        OutputListenerImpl outputListener = new OutputListenerImpl();
-        OutputListenerImpl errorListener = new OutputListenerImpl();
-        BuildListenerImpl buildListener = new BuildListenerImpl();
-        try {
-            doRun(outputListener, errorListener, buildListener).rethrowFailure();
-            throw new AssertionFailedError("expected build to fail but it did not.");
-        } catch (GradleException e) {
-            return new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
-                    outputListener.writer.toString(), errorListener.writer.toString(), e);
-        }
-    }
-
-    private BuildResult doRun(final OutputListenerImpl outputListener, OutputListenerImpl errorListener,
-                              BuildListenerImpl listener) {
-        assertCanExecute();
-        if (isQuiet()) {
-            parameter.setLogLevel(LogLevel.QUIET);
-        }
-        DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
-        factory.addListener(listener);
-        GradleLauncher gradleLauncher = GradleLauncher.newInstance(parameter);
-        gradleLauncher.addStandardOutputListener(outputListener);
-        gradleLauncher.addStandardErrorListener(errorListener);
-        try {
-            return gradleLauncher.run();
-        } finally {
-            System.clearProperty("test.single");
-            factory.removeListener(listener);
-        }
-    }
-
-    public void assertCanExecute() {
-        assertNull(getExecutable());
-        assertTrue(getEnvironmentVars().isEmpty());
-    }
-
-    public boolean canExecute() {
-        try {
-            assertCanExecute();
-        } catch (AssertionError e) {
-            return false;
-        }
-        return true;
-    }
-
-    private static class BuildListenerImpl implements TaskExecutionGraphListener {
-        private final List<String> executedTasks = new ArrayList<String>();
-        private final Set<String> skippedTasks = new HashSet<String>();
-
-        public void graphPopulated(TaskExecutionGraph graph) {
-            List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
-            graph.addTaskExecutionListener(new TaskListenerImpl(planned, executedTasks, skippedTasks));
-        }
-    }
-
-    private static class OutputListenerImpl implements StandardOutputListener {
-        private StringWriter writer = new StringWriter();
-
-        @Override
-        public String toString() {
-            return writer.toString();
-        }
-
-        public void onOutput(CharSequence output) {
-            writer.append(output);
-        }
-    }
-
-    private static class TaskListenerImpl implements TaskExecutionListener {
-        private final List<Task> planned;
-        private final List<String> executedTasks;
-        private final Set<String> skippedTasks;
-        private Task current;
-
-        public TaskListenerImpl(List<Task> planned, List<String> executedTasks, Set<String> skippedTasks) {
-            this.planned = planned;
-            this.executedTasks = executedTasks;
-            this.skippedTasks = skippedTasks;
-        }
-
-        public void beforeExecute(Task task) {
-            assertThat(current, nullValue());
-            assertTrue(planned.contains(task));
-            current = task;
-
-            String taskPath = path(task);
-            if (taskPath.startsWith(":buildSrc:")) {
-                return;
-            }
-
-            executedTasks.add(taskPath);
-        }
-
-        public void afterExecute(Task task, TaskState state) {
-            assertThat(task, sameInstance(current));
-            current = null;
-
-            String taskPath = path(task);
-            if (taskPath.startsWith(":buildSrc:")) {
-                return;
-            }
-
-            if (state.getSkipped()) {
-                skippedTasks.add(taskPath);
-            }
-        }
-
-        private String path(Task task) {
-            return task.getProject().getGradle().getParent() == null ? task.getPath() : ":" + task.getProject().getRootProject().getName() + task.getPath();
-        }
-    }
-
-    public static class InProcessExecutionResult extends AbstractExecutionResult {
-        private final List<String> plannedTasks;
-        private final Set<String> skippedTasks;
-        private final String output;
-        private final String error;
-
-        public InProcessExecutionResult(List<String> plannedTasks, Set<String> skippedTasks, String output,
-                                        String error) {
-            this.plannedTasks = plannedTasks;
-            this.skippedTasks = skippedTasks;
-            this.output = output;
-            this.error = error;
-        }
-
-        public String getOutput() {
-            return output;
-        }
-
-        public String getError() {
-            return error;
-        }
-
-        public ExecutionResult assertTasksExecuted(String... taskPaths) {
-            List<String> expected = Arrays.asList(taskPaths);
-            assertThat(plannedTasks, equalTo(expected));
-            return this;
-        }
-
-        public ExecutionResult assertTasksSkipped(String... taskPaths) {
-            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
-            assertThat(skippedTasks, equalTo(expected));
-            return this;
-        }
-
-        public ExecutionResult assertTaskSkipped(String taskPath) {
-            assertThat(skippedTasks, hasItem(taskPath));
-            return this;
-        }
-
-        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
-            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
-            Set<String> notSkipped = getExecutedTasks();
-            assertThat(notSkipped, equalTo(expected));
-            return this;
-        }
-
-        public ExecutionResult assertTaskNotSkipped(String taskPath) {
-            assertThat(getExecutedTasks(), hasItem(taskPath));
-            return this;
-        }
-
-        private Set<String> getExecutedTasks() {
-            Set<String> notSkipped = new HashSet<String>(plannedTasks);
-            notSkipped.removeAll(skippedTasks);
-            return notSkipped;
-        }
-    }
-
-    private static class InProcessExecutionFailure extends InProcessExecutionResult implements ExecutionFailure {
-        private final GradleException failure;
-
-        public InProcessExecutionFailure(List<String> tasks, Set<String> skippedTasks, String output, String error,
-                                         GradleException failure) {
-            super(tasks, skippedTasks, output, error);
-            this.failure = failure;
-        }
-
-        public ExecutionFailure assertHasLineNumber(int lineNumber) {
-            assertThat(failure.getMessage(), containsString(String.format(" line: %d", lineNumber)));
-            return this;
-
-        }
-
-        public ExecutionFailure assertHasFileName(String filename) {
-            assertThat(failure.getMessage(), startsWith(String.format("%s", filename)));
-            return this;
-        }
-
-        public ExecutionFailure assertHasCause(String description) {
-            assertThatCause(startsWith(description));
-            return this;
-        }
-
-        public ExecutionFailure assertThatCause(final Matcher<String> matcher) {
-            if (failure instanceof LocationAwareException) {
-                LocationAwareException exception = (LocationAwareException) failure;
-                assertThat(exception.getReportableCauses(), hasItem(hasMessage(matcher)));
-            } else {
-                assertThat(failure.getCause(), notNullValue());
-                assertThat(failure.getCause().getMessage(), matcher);
-            }
-            return this;
-        }
-
-        public ExecutionFailure assertHasNoCause() {
-            if (failure instanceof LocationAwareException) {
-                LocationAwareException exception = (LocationAwareException) failure;
-                assertThat(exception.getReportableCauses(), isEmpty());
-            } else {
-                assertThat(failure.getCause(), nullValue());
-            }
-            return this;
-        }
-
-        public ExecutionFailure assertHasDescription(String context) {
-            assertThatDescription(startsWith(context));
-            return this;
-        }
-
-        public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
-            assertThat(failure.getMessage(), containsLine(matcher));
-            return this;
-        }
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
deleted file mode 100644
index 7797ba8..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2011 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
-
-class IvyRepository {
-    final TestFile rootDir
-
-    IvyRepository(TestFile rootDir) {
-        this.rootDir = rootDir
-    }
-
-    String getPattern() {
-        return "[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-
-    IvyModule module(String organisation, String module, Object revision = '1.0') {
-        def moduleDir = rootDir.file("$organisation/$module/$revision")
-        return new IvyModule(moduleDir, organisation, module, revision as String)
-    }
-}
-
-class IvyModule {
-    final TestFile moduleDir
-    final String organisation
-    final String module
-    final String revision
-
-    IvyModule(TestFile moduleDir, String organisation, String module, String revision) {
-        this.moduleDir = moduleDir
-        this.organisation = organisation
-        this.module = module
-        this.revision = revision
-    }
-
-    File getIvyFile() {
-        return moduleDir.file("ivy-${revision}.xml")
-    }
-
-    File getJarFile() {
-        return moduleDir.file("$module-${revision}.jar")
-    }
-
-    File publishArtifact() {
-        moduleDir.createDir()
-
-        ivyFile << """<?xml version="1.0" encoding="UTF-8"?>
-<ivy-module version="1.0">
-	<info organisation="${organisation}"
-		module="${module}"
-		revision="${revision}"
-	/>
-	<configurations>
-		<conf name="runtime" visibility="public"/>
-		<conf name="default" visibility="public" extends="runtime"/>
-	</configurations>
-	<publications>
-		<artifact name="${module}" type="jar" ext="jar" conf="*"/>
-	</publications>
-</ivy-module>
-        """
-
-        jarFile << "add some content so that file size isn't zero"
-
-        return jarFile
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
deleted file mode 100644
index 086ec07..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
+++ /dev/null
@@ -1,91 +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.fixtures
-
-import org.gradle.util.TestFile
-
-class MavenRepository {
-    private final TestFile rootDir
-
-    MavenRepository(TestFile rootDir) {
-        this.rootDir = rootDir
-    }
-
-    MavenModule module(String groupId, String artifactId, Object version = '1.0') {
-        def artifactDir = rootDir.file("$groupId/$artifactId/$version")
-        return new MavenModule(artifactDir, groupId, artifactId, version as String)
-    }
-}
-
-class MavenModule {
-    final TestFile moduleDir
-    final String groupId
-    final String artifactId
-    final String version
-    private final List<String> dependencies = []
-
-    MavenModule(TestFile moduleDir, String groupId, String artifactId, String version) {
-        this.moduleDir = moduleDir
-        this.groupId = groupId
-        this.artifactId = artifactId
-        this.version = version
-    }
-
-    MavenModule dependsOn(String dependencyArtifactId) {
-        this.dependencies << dependencyArtifactId
-        return this
-    }
-
-    void assertArtifactsDeployed(String... names) {
-        for (name in names) {
-            moduleDir.file(name).assertIsFile()
-            moduleDir.file("${name}.md5").assertIsFile()
-            moduleDir.file("${name}.sha1").assertIsFile()
-        }
-    }
-
-    File publishArtifact() {
-        moduleDir.createDir()
-
-        def pomFile = new File(moduleDir, "$artifactId-${version}.pom")
-        pomFile << """
-<project xmlns="http://maven.apache.org/POM/4.0.0">
-  <modelVersion>4.0.0</modelVersion>
-  <groupId>$groupId</groupId>
-  <artifactId>$artifactId</artifactId>
-  <packaging>jar</packaging>
-  <version>$version</version>"""
-
-        dependencies.each { dependency ->
-            pomFile << """
-  <dependencies>
-    <dependency>
-      <groupId>$groupId</groupId>
-      <artifactId>$dependency</artifactId>
-      <version>1.0</version>
-    </dependency>
-  </dependencies>"""
-        }
-
-        pomFile << "\n</project>"
-
-        def jarFile = new File("$moduleDir/$artifactId-${version}.jar")
-        jarFile << "add some content so that file size isn't zero"
-
-        return jarFile
-    }
-
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
deleted file mode 100644
index 4451ec5..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
+++ /dev/null
@@ -1,94 +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.integtests.fixtures
-
-import org.gradle.util.Jvm
-import org.gradle.util.TestFile
-import org.gradle.util.GradleVersion
-import org.gradle.util.DistributionLocator
-
-public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implements BasicGradleDistribution {
-    private final GradleDistribution dist
-    def final GradleVersion version
-
-    PreviousGradleVersionExecuter(GradleDistribution dist, String version) {
-        this.dist = dist
-        this.version = GradleVersion.version(version)
-    }
-
-    def String toString() {
-        version.toString()
-    }
-
-    String getVersion() {
-        return version.version
-    }
-
-    boolean worksWith(Jvm jvm) {
-        return version == GradleVersion.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.version-bin.zip")
-        if (!zipFile.isFile()) {
-            try {
-                URL url = binDistributionUrl
-                System.out.println("downloading $url");
-                zipFile.copyFrom(url)
-            } catch (Throwable t) {
-                zipFile.delete()
-                throw t
-            }
-        }
-        return zipFile
-    }
-
-    private URL getBinDistributionUrl() {
-        return new URL(new DistributionLocator().getDistributionFor(version))
-    }
-
-    def TestFile getGradleHomeDir() {
-        return findGradleHome()
-    }
-
-    private TestFile findGradleHome() {
-        // maybe download and unzip distribution
-        TestFile versionsDir = dist.distributionsDir.parentFile.file('previousVersions')
-        TestFile gradleHome = versionsDir.file("gradle-$version.version")
-        TestFile markerFile = gradleHome.file('ok.txt')
-        if (!markerFile.isFile()) {
-            TestFile zipFile = binDistribution
-            zipFile.usingNativeTools().unzipTo(versionsDir)
-            markerFile.touch()
-        }
-        return gradleHome
-    }
-
-    protected ExecutionFailure doRunWithFailure() {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
deleted file mode 100644
index 2daa475..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2011 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.process.internal.ExecHandleBuilder
-import org.gradle.process.internal.ExecHandle
-import org.gradle.process.ExecResult
-import org.gradle.util.OperatingSystem
-
-class ScriptExecuter extends ExecHandleBuilder {
-    @Override
-    ExecHandle build() {
-        if (OperatingSystem.current().isWindows()) {
-            args = ['/c', executable.replace('/', File.separator)] + args
-            executable = 'cmd'
-        } else {
-            executable = "${workingDir}/${executable}"
-        }
-        return super.build()
-    }
-
-    ExecResult run() {
-        return build().start().waitForFinish()
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AbstractAutoTestedSamplesTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AbstractAutoTestedSamplesTest.groovy
deleted file mode 100644
index 02acc4c..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AbstractAutoTestedSamplesTest.groovy
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2011 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.internal
-
-/**
- * Author: Szczepan Faber, created at: 4/2/11
- */
-class AbstractAutoTestedSamplesTest extends AbstractIntegrationTest{
-
-     void runSamplesFrom(String dir) {
-        def util = new AutoTestedSamplesUtil()
-        util.findSamples(dir) { file, sample ->
-            println "Found sample in $file"
-            def testFile = testFile('build.gradle')
-            testFile.text = sample
-            usingBuildFile(testFile).withTasks('tasks').withArguments("-s").run()
-        }
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AbstractIntegrationTest.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AbstractIntegrationTest.java
deleted file mode 100644
index 9a1bcae..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AbstractIntegrationTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2011 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.internal;
-
-import org.gradle.CacheUsage;
-import org.gradle.StartParameter;
-import org.gradle.integtests.fixtures.*;
-import org.gradle.util.TestFile;
-import org.gradle.util.TestFileContext;
-import org.junit.Rule;
-
-import java.io.File;
-
-public abstract class AbstractIntegrationTest implements TestFileContext {
-    @Rule public final GradleDistribution distribution = new GradleDistribution();
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
-
-    public TestFile getTestDir() {
-        return distribution.getTestDir();
-    }
-
-    public TestFile file(Object... path) {
-        return getTestDir().file(path);
-    }
-
-    public TestFile testFile(String name) {
-        return file(name);
-    }
-
-    private StartParameter startParameter() {
-        StartParameter parameter = new StartParameter();
-        parameter.setGradleUserHomeDir(distribution.getUserHomeDir());
-
-        parameter.setSearchUpwards(false);
-        parameter.setCacheUsage(CacheUsage.ON);
-        parameter.setCurrentDir(getTestDir());
-
-        return parameter;
-    }
-
-    protected GradleExecuter inTestDirectory() {
-        return inDirectory(getTestDir());
-    }
-
-    protected GradleExecuter inDirectory(File directory) {
-        return executer.inDirectory(directory);
-    }
-
-    protected GradleExecuter usingBuildFile(File file) {
-        return executer.usingBuildScript(file);
-    }
-
-    protected GradleExecuter usingBuildScript(String script) {
-        return executer.usingBuildScript(script);
-    }
-
-    protected GradleExecuter usingProjectDir(File projectDir) {
-        return executer.usingProjectDirectory(projectDir);
-    }
-
-    protected ArtifactBuilder artifactBuilder() {
-        return new GradleBackedArtifactBuilder(new InProcessGradleExecuter(startParameter()), getTestDir().file("artifacts"));
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AutoTestedSamplesUtil.groovy b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AutoTestedSamplesUtil.groovy
deleted file mode 100644
index 07d4648..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/AutoTestedSamplesUtil.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2011 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.internal
-
-/**
- * Author: Szczepan Faber, created at: 3/28/11
- */
-class AutoTestedSamplesUtil {
-
-    void findSamples(String dir, Closure runner) {
-        def sources = findDir(dir)
-        def ant = new AntBuilder()
-
-        def list = ant.fileScanner {
-            fileset(dir: sources, includes: '**/*.groovy **/*.java')
-        }
-
-        list.each() { runSamplesFromFile(it, runner) }
-    }
-
-    String findDir(String dir) {
-        def workDir = System.getProperty("user.dir")
-        def candidates = [
-            "$workDir/$dir",        //when ran from IDEA
-            "$workDir/../../$dir"  //when ran from command line
-        ]
-        for (c in candidates) {
-            if (new File(c).exists()) {
-                return c
-            }
-        }
-        throw new RuntimeException("""Couldn't find the root folder :-( Please update the logic so that it detects the root folder correctly.
-I tried looking for a root folder here: $candidates
-""")
-    }
-
-    void runSamplesFromFile(File file, Closure runner) {
-        file.text.eachMatch(/(?ms).*?<pre autoTested.*?>(.*?)<\/pre>(.*?)/) {
-            def sample = it[1]
-            sample = sample.replaceAll(/(?m)^\s*?\*/, '')
-            try {
-                runner.call(file, sample)
-            } catch (Exception e) {
-                throw new RuntimeException("""
-*****
-Failed to execute sample:
--File: $file
--Sample:
-$sample
--Problem: see the full stactrace below.
-*****
-""", e);
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/IntegrationTestHint.java b/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/IntegrationTestHint.java
deleted file mode 100644
index 01f5ebf..0000000
--- a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/internal/IntegrationTestHint.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 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.internal;
-
-/**
- * @author Szczepan Faber, @date: 25.03.11
- */
-public class IntegrationTestHint extends RuntimeException {
-
-    public IntegrationTestHint(Throwable cause) {
-        super("****\n"
-+"This test is one of the integration tests that requires specific tasks to be ran first.\n"
-+"Please run: gradle ideVersionProperties binZip intTestImage publishLocalArchives\n"
-+"If the problem persists after running tasks then it probably means it's a genuine test failure.\n"
-+"****\n", cause);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/ignore/bad.file b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/ignore/bad.file
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/ignore/bad.file
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/ignore/bad.file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.a b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/one.a
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.a
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/one.a
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.b b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/one.b
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.b
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/one.b
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/ignore/bad.file b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/sub/ignore/bad.file
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/ignore/bad.file
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/sub/ignore/bad.file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.a b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/sub/onesub.a
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.a
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/sub/onesub.a
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.b b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/sub/onesub.b
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.b
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/one/sub/onesub.b
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.a b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/root.a
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.a
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/root.a
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.b b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/root.b
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.b
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/root.b
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/ignore/bad.file b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/two/ignore/bad.file
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/ignore/bad.file
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/two/ignore/bad.file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.a b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/two/two.a
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.a
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/two/two.a
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.b b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/two/two.b
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.b
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src/two/two.b
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.a b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src2/three/three.a
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.a
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src2/three/three.a
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.b b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src2/three/three.b
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.b
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/copyTestResources/src2/three/three.b
diff --git a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
index db95921..d16ac6a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/BuildExceptionReporter.java
@@ -18,39 +18,38 @@ package org.gradle;
 import org.codehaus.groovy.runtime.StackTraceUtils;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
-import org.gradle.api.LocationAwareException;
+import org.gradle.api.internal.LocationAwareException;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.configuration.ImplicitTasksConfigurer;
 import org.gradle.execution.TaskSelectionException;
 import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.logging.LoggingConfiguration;
+import org.gradle.logging.ShowStacktrace;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.logging.internal.AbstractStyledTextOutput;
+import org.gradle.logging.internal.LinePrefixingStyledTextOutput;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
+import org.gradle.logging.internal.BufferingStyledTextOutput;
 import org.gradle.util.GUtil;
+import org.gradle.util.TreeVisitor;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.gradle.logging.StyledTextOutput.Style.Failure;
-import static org.gradle.logging.StyledTextOutput.Style.UserInput;
+import static org.gradle.logging.StyledTextOutput.Style.*;
 
 /**
  * A {@link BuildListener} which reports the build exception, if any.
  */
-public class BuildExceptionReporter extends BuildAdapter {
+public class BuildExceptionReporter extends BuildAdapter implements Action<Throwable> {
     private enum ExceptionStyle {
         NONE, SANITIZED, FULL
     }
 
     private final StyledTextOutputFactory textOutputFactory;
-    private final StartParameter startParameter;
+    private final LoggingConfiguration loggingConfiguration;
     private final BuildClientMetaData clientMetaData;
 
-    public BuildExceptionReporter(StyledTextOutputFactory textOutputFactory, StartParameter startParameter, BuildClientMetaData clientMetaData) {
+    public BuildExceptionReporter(StyledTextOutputFactory textOutputFactory, LoggingConfiguration loggingConfiguration, BuildClientMetaData clientMetaData) {
         this.textOutputFactory = textOutputFactory;
-        this.startParameter = startParameter;
+        this.loggingConfiguration = loggingConfiguration;
         this.clientMetaData = clientMetaData;
     }
 
@@ -60,10 +59,10 @@ public class BuildExceptionReporter extends BuildAdapter {
             return;
         }
 
-        reportException(failure);
+        execute(failure);
     }
 
-    public void reportException(Throwable failure) {
+    public void execute(Throwable failure) {
         FailureDetails details = new FailureDetails(failure);
         if (failure instanceof GradleException) {
             reportBuildFailure((GradleException) failure, details);
@@ -79,24 +78,24 @@ public class BuildExceptionReporter extends BuildAdapter {
 
         output.println();
         output.withStyle(Failure).text("FAILURE: ");
-        details.summary.replay(output.withStyle(Failure));
+        details.summary.writeTo(output.withStyle(Failure));
 
-        if (details.location.hasContent) {
+        if (details.location.getHasContent()) {
             output.println().println();
             output.println("* Where:");
-            details.location.replay(output);
+            details.location.writeTo(output);
         }
 
-        if (details.details.hasContent) {
+        if (details.details.getHasContent()) {
             output.println().println();
             output.println("* What went wrong:");
-            details.details.replay(output);
+            details.details.writeTo(output);
         }
 
-        if (details.resolution.hasContent) {
+        if (details.resolution.getHasContent()) {
             output.println().println();
             output.println("* Try:");
-            details.resolution.replay(output);
+            details.resolution.writeTo(output);
         }
 
         Throwable exception = null;
@@ -122,9 +121,9 @@ public class BuildExceptionReporter extends BuildAdapter {
 
     public void reportInternalError(FailureDetails details) {
         details.summary.text("Build aborted because of an internal error.");
-        details.details.text("Build aborted because of an unexpected internal error. Please file an issue at: http://www.gradle.org.");
+        details.details.text("Build aborted because of an unexpected internal error. Please file an issue at: http://forums.gradle.org.");
 
-        if (startParameter.getLogLevel() != LogLevel.DEBUG) {
+        if (loggingConfiguration.getLogLevel() != LogLevel.DEBUG) {
             details.resolution.text("Run with ");
             details.resolution.withStyle(UserInput).format("--%s", LoggingCommandLineConverter.DEBUG_LONG);
             details.resolution.text(" option to get additional debug info.");
@@ -133,10 +132,10 @@ public class BuildExceptionReporter extends BuildAdapter {
     }
 
     private void reportBuildFailure(GradleException failure, FailureDetails details) {
-        if (startParameter.getShowStacktrace() == StartParameter.ShowStacktrace.ALWAYS || startParameter.getLogLevel() == LogLevel.DEBUG) {
+        if (loggingConfiguration.getShowStacktrace() == ShowStacktrace.ALWAYS || loggingConfiguration.getLogLevel() == LogLevel.DEBUG) {
             details.exceptionStyle = ExceptionStyle.SANITIZED;
         }
-        if (startParameter.getShowStacktrace() == StartParameter.ShowStacktrace.ALWAYS_FULL) {
+        if (loggingConfiguration.getShowStacktrace() == ShowStacktrace.ALWAYS_FULL) {
             details.exceptionStyle = ExceptionStyle.FULL;
         }
 
@@ -156,21 +155,49 @@ public class BuildExceptionReporter extends BuildAdapter {
         details.resolution.text(" to get a list of available tasks.");
     }
 
-    private void formatGenericFailure(GradleException failure, FailureDetails details) {
+    private void formatGenericFailure(GradleException failure, final FailureDetails details) {
         details.summary.text("Build failed with an exception.");
 
         fillInFailureResolution(details);
 
         if (failure instanceof LocationAwareException) {
-            LocationAwareException scriptException = (LocationAwareException) failure;
+            final LocationAwareException scriptException = (LocationAwareException) failure;
             details.failure = scriptException.getCause();
             if (scriptException.getLocation() != null) {
                 details.location.text(scriptException.getLocation());
             }
-            details.details.text(scriptException.getOriginalMessage());
-            for (Throwable cause : scriptException.getReportableCauses()) {
-                details.details.format("%nCause: %s", getMessage(cause));
-            }
+            scriptException.visitReportableCauses(new TreeVisitor<Throwable>() {
+                int depth;
+
+                @Override
+                public void node(final Throwable node) {
+                    if (node == scriptException) {
+                        details.details.text(scriptException.getOriginalMessage());
+                    } else {
+                        details.details.format("%n");
+                        StringBuilder prefix = new StringBuilder();
+                        for (int i = 1; i < depth; i++) {
+                            prefix.append("   ");
+                        }
+                        details.details.text(prefix);
+                        prefix.append("  ");
+                        details.details.style(Info).text("> ").style(Normal);
+                        
+                        final LinePrefixingStyledTextOutput output = new LinePrefixingStyledTextOutput(details.details, prefix);
+                        output.text(getMessage(node));
+                    }
+                }
+
+                @Override
+                public void startChildren() {
+                    depth++;
+                }
+
+                @Override
+                public void endChildren() {
+                    depth--;
+                }
+            });
         } else {
             details.details.text(getMessage(failure));
         }
@@ -179,13 +206,13 @@ public class BuildExceptionReporter extends BuildAdapter {
     private void fillInFailureResolution(FailureDetails details) {
         if (details.exceptionStyle == ExceptionStyle.NONE) {
             details.resolution.text("Run with ");
-            details.resolution.withStyle(UserInput).format("--%s", DefaultCommandLineConverter.STACKTRACE_LONG);
+            details.resolution.withStyle(UserInput).format("--%s", LoggingCommandLineConverter.STACKTRACE_LONG);
             details.resolution.text(" option to get the stack trace. ");
         }
 
-        if (startParameter.getLogLevel() != LogLevel.DEBUG) {
+        if (loggingConfiguration.getLogLevel() != LogLevel.DEBUG) {
             details.resolution.text("Run with ");
-            if (startParameter.getLogLevel() != LogLevel.INFO) {
+            if (loggingConfiguration.getLogLevel() != LogLevel.INFO) {
                 details.resolution.withStyle(UserInput).format("--%s", LoggingCommandLineConverter.INFO_LONG);
                 details.resolution.text(" or ");
             }
@@ -204,10 +231,10 @@ public class BuildExceptionReporter extends BuildAdapter {
 
     private static class FailureDetails {
         Throwable failure;
-        final RecordingStyledTextOutput summary = new RecordingStyledTextOutput();
-        final RecordingStyledTextOutput details = new RecordingStyledTextOutput();
-        final RecordingStyledTextOutput location = new RecordingStyledTextOutput();
-        final RecordingStyledTextOutput resolution = new RecordingStyledTextOutput();
+        final BufferingStyledTextOutput summary = new BufferingStyledTextOutput();
+        final BufferingStyledTextOutput details = new BufferingStyledTextOutput();
+        final BufferingStyledTextOutput location = new BufferingStyledTextOutput();
+        final BufferingStyledTextOutput resolution = new BufferingStyledTextOutput();
 
         ExceptionStyle exceptionStyle = ExceptionStyle.NONE;
 
@@ -215,49 +242,4 @@ public class BuildExceptionReporter extends BuildAdapter {
             this.failure = failure;
         }
     }
-
-    private static class RecordingStyledTextOutput extends AbstractStyledTextOutput {
-        private final List<Action<StyledTextOutput>> events = new ArrayList<Action<StyledTextOutput>>();
-        private boolean hasContent;
-
-        void replay(StyledTextOutput output) {
-            for (Action<StyledTextOutput> event : events) {
-                event.execute(output);
-            }
-            events.clear();
-        }
-
-        @Override
-        protected void doStyleChange(final Style style) {
-            if (!events.isEmpty() && (events.get(events.size() - 1) instanceof ChangeStyleAction)) {
-                events.remove(events.size() - 1);
-            }
-            events.add(new ChangeStyleAction(style));
-        }
-
-        @Override
-        protected void doAppend(final String text) {
-            if (text.length() == 0) {
-                return;
-            }
-            hasContent = true;
-            events.add(new Action<StyledTextOutput>() {
-                public void execute(StyledTextOutput styledTextOutput) {
-                    styledTextOutput.text(text);
-                }
-            });
-        }
-
-        private static class ChangeStyleAction implements Action<StyledTextOutput> {
-            private final Style style;
-
-            public ChangeStyleAction(Style style) {
-                this.style = style;
-            }
-
-            public void execute(StyledTextOutput styledTextOutput) {
-                styledTextOutput.style(style);
-            }
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/BuildLogger.java b/subprojects/core/src/main/groovy/org/gradle/BuildLogger.java
index 075d84d..254485e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/BuildLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/BuildLogger.java
@@ -46,9 +46,8 @@ public class BuildLogger implements BuildListener, TaskExecutionGraphListener {
         logger.info("Starting Build");
         logger.debug("Gradle user home: " + startParameter.getGradleUserHomeDir());
         logger.debug("Current dir: " + startParameter.getCurrentDir());
-        logger.debug("Settings file: " + startParameter.getSettingsScriptSource());
+        logger.debug("Settings file: " + startParameter.getSettingsFile());
         logger.debug("Build file: " + startParameter.getBuildFile());
-        logger.debug("Select default project: " + startParameter.getDefaultProjectSelector().getDisplayName());
     }
 
     public void settingsEvaluated(Settings settings) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java b/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
index bcde614..874edd1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
+++ b/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
@@ -19,9 +19,11 @@ import org.gradle.api.InvalidUserDataException;
 
 /**
  * <p>{@code CacheUsage} specifies how compiled scripts should be cached.</p>
- * 
+ *
+ * @deprecated This enum has been deprecated. Use StartParameter#isRerunTasks() and StartParameter#isRecompileScripts() instead.
  * @author Hans Dockter
  */
+ at Deprecated
 public enum CacheUsage {
     ON, REBUILD;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/CommandLineArgumentException.java b/subprojects/core/src/main/groovy/org/gradle/CommandLineArgumentException.java
deleted file mode 100644
index 6138a51..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/CommandLineArgumentException.java
+++ /dev/null
@@ -1,33 +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;
-
-import org.gradle.api.GradleException;
-
-/**
- * A {@code CommandLineArgumentException} is thrown when command-line arguments cannot be parsed.
- * 
- * @author Hans Dockter
- */
-public class CommandLineArgumentException extends GradleException {
-    public CommandLineArgumentException(String message) {
-        super(message);
-    }
-
-    public CommandLineArgumentException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java b/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java
index 631444d..7d58db8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/GradleLauncher.java
@@ -20,8 +20,12 @@ 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,
- * as follows:</p>
+ * <p>{@code GradleLauncher} is mildly deprecated. It is being replaced by the Tooling API.
+ * If you're interested in embedding Gradle you should read the new user guide chapter on embedding Gradle.
+ * The main entry point to the Tooling API (and embedding Gradle) is {@code org.gradle.tooling.GradleConnector}.
+ *
+ * <p>You should try using the Tooling API ({@code GradleConnector}) instead of {@code GradleLauncher}.
+ * However, if you need some capability that isn't yet implemented in the Tooling API here is how you use {@code GradleLauncher}:
  *
  * <ol>
  *
diff --git a/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java b/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java
new file mode 100644
index 0000000..9df6749
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.cli.CommandLineArgumentException;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The options supplied by a user to refresh dependencies and other external resources.
+ * @deprecated Use Commandline Option '--refresh-dependencies' instead.
+ */
+ at Deprecated
+public class RefreshOptions implements Serializable {
+    public static final RefreshOptions NONE = new RefreshOptions(Collections.<Option>emptyList());
+
+    private final List<Option> options;
+
+    public RefreshOptions(List<Option> options) {
+        this.options = options;
+    }
+
+    public static RefreshOptions fromCommandLineOptions(List<String> optionNames) {
+        if (optionNames.size() == 0) {
+            return new RefreshOptions(Arrays.asList(Option.values()));
+        } 
+        
+        List<Option> options = new ArrayList<Option>();
+        for (String optionName : optionNames) {
+            try {
+                options.add(Option.valueOf(optionName.toUpperCase()));
+            } catch (IllegalArgumentException e) {
+                throw new CommandLineArgumentException(String.format("Unknown refresh option '%s' specified.", optionName));
+            }
+        }
+        return new RefreshOptions(options);
+    }
+
+    public boolean refreshDependencies() {
+        return options.contains(Option.DEPENDENCIES);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        RefreshOptions that = (RefreshOptions) o;
+        return options.equals(that.options);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return options.hashCode();
+    }
+
+    /**
+     * The set of allowable options.
+     */
+    public enum Option {
+        DEPENDENCIES
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index d11f83e..79c5591 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -16,73 +16,75 @@
 
 package org.gradle;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import org.apache.commons.lang.builder.EqualsBuilder;
 import org.apache.commons.lang.builder.HashCodeBuilder;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.execution.*;
-import org.gradle.groovy.scripts.UriScriptSource;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.StringScriptSource;
-import org.gradle.initialization.*;
+import org.gradle.internal.SystemProperties;
+import org.gradle.logging.LoggingConfiguration;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.*;
 
 /**
- * <p>{@code StartParameter} defines the configuration used by a {@link GradleLauncher} instance to execute a build. The
- * properties of {@code StartParameter} generally correspond to the command-line options of Gradle. You pass a {@code
- * StartParameter} instance to {@link GradleLauncher#newInstance(StartParameter)} when you create a new {@code Gradle}
- * instance.</p>
+ * <p>{@code StartParameter} defines the configuration used by a {@link GradleLauncher} instance to execute a build. The properties of {@code StartParameter} generally correspond to the command-line
+ * options of Gradle. You pass a {@code StartParameter} instance to {@link GradleLauncher#newInstance(StartParameter)} when you create a new {@code Gradle} instance.</p>
  *
- * <p>You can obtain an instance of a {@code StartParameter} by either creating a new one, or duplicating an existing
- * one using {@link #newInstance} or {@link #newBuild}.</p>
+ * <p>You can obtain an instance of a {@code StartParameter} by either creating a new one, or duplicating an existing one using {@link #newInstance} or {@link #newBuild}.</p>
  *
  * @author Hans Dockter
  * @see GradleLauncher
  */
-public class StartParameter {
+public class StartParameter extends LoggingConfiguration implements Serializable {
     public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home";
     /**
      * The default user home directory.
      */
-    public static final File DEFAULT_GRADLE_USER_HOME = new File(System.getProperty("user.home") + "/.gradle");
-
-    /**
-     * Specifies the detail to include in stacktraces.
-     */
-    public enum ShowStacktrace {
-        INTERNAL_EXCEPTIONS, ALWAYS, ALWAYS_FULL
-    }
+    public static final File DEFAULT_GRADLE_USER_HOME = new File(SystemProperties.getUserHome() + "/.gradle");
 
     private List<String> taskNames = new ArrayList<String>();
     private Set<String> excludedTaskNames = new HashSet<String>();
-    private ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction
-            = new ProjectDependenciesBuildInstruction(true);
+    private boolean buildProjectDependencies = true;
     private File currentDir;
+    private File projectDir;
     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;
     private CacheUsage cacheUsage = CacheUsage.ON;
-    private ScriptSource buildScriptSource;
-    private ScriptSource settingsScriptSource;
-    private BuildExecuter buildExecuter;
-    private ProjectSpec defaultProjectSelector;
-    private LogLevel logLevel = LogLevel.LIFECYCLE;
-    private ShowStacktrace showStacktrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
+    private File settingsFile;
+    private boolean useEmptySettings;
     private File buildFile;
     private List<File> initScripts = new ArrayList<File>();
     private boolean dryRun;
-    private boolean noOpt;
-    private boolean colorOutput = true;
+    private boolean rerunTasks;
     private boolean profile;
+    private boolean continueOnFailure;
+    private boolean offline;
+    private File projectCacheDir;
+    private boolean refreshDependencies;
+    private boolean recompileScripts;
+
+    /**
+     * Sets the project's cache location. Set to null to use the default location.
+     */
+    public void setProjectCacheDir(File projectCacheDir) {
+        this.projectCacheDir = projectCacheDir;
+    }
 
     /**
-     * Creates a {@code StartParameter} with default values. This is roughly equivalent to running Gradle on the
-     * command-line with no arguments.
+     * Returns the project's cache dir.
+     *
+     * @return project's cache dir, or null if the default location is to be used.
+     */
+    public File getProjectCacheDir() {
+        return projectCacheDir;
+    }
+
+    /**
+     * Creates a {@code StartParameter} with default values. This is roughly equivalent to running Gradle on the command-line with no arguments.
      */
     public StartParameter() {
         String gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY);
@@ -105,32 +107,35 @@ public class StartParameter {
     public StartParameter newInstance() {
         StartParameter startParameter = new StartParameter();
         startParameter.buildFile = buildFile;
+        startParameter.projectDir = projectDir;
+        startParameter.settingsFile = settingsFile;
+        startParameter.useEmptySettings = useEmptySettings;
         startParameter.taskNames = taskNames;
-        startParameter.projectDependenciesBuildInstruction = projectDependenciesBuildInstruction;
+        startParameter.buildProjectDependencies = buildProjectDependencies;
         startParameter.currentDir = currentDir;
         startParameter.searchUpwards = searchUpwards;
         startParameter.projectProperties = projectProperties;
         startParameter.systemPropertiesArgs = systemPropertiesArgs;
         startParameter.gradleUserHomeDir = gradleUserHomeDir;
         startParameter.cacheUsage = cacheUsage;
-        startParameter.buildScriptSource = buildScriptSource;
-        startParameter.settingsScriptSource = settingsScriptSource;
-        startParameter.initScripts = new ArrayList<File>(initScripts); 
-        startParameter.buildExecuter = buildExecuter;
-        startParameter.defaultProjectSelector = defaultProjectSelector;
-        startParameter.logLevel = logLevel;
-        startParameter.colorOutput = colorOutput;
-        startParameter.showStacktrace = showStacktrace;
+        startParameter.initScripts = new ArrayList<File>(initScripts);
+        startParameter.setLogLevel(getLogLevel());
+        startParameter.setColorOutput(isColorOutput());
+        startParameter.setShowStacktrace(getShowStacktrace());
         startParameter.dryRun = dryRun;
-        startParameter.noOpt = noOpt;
+        startParameter.rerunTasks = rerunTasks;
+        startParameter.recompileScripts = recompileScripts;
         startParameter.profile = profile;
+        startParameter.projectCacheDir = projectCacheDir;
+        startParameter.continueOnFailure = continueOnFailure;
+        startParameter.offline = offline;
+        startParameter.refreshDependencies = refreshDependencies;
         return startParameter;
     }
 
     /**
-     * <p>Creates the parameters for a new build, using these parameters as a template. Copies the environmental
-     * properties from this parameter (eg gradle user home dir, etc), but does not copy the build specific properties
-     * (eg task names).</p>
+     * <p>Creates the parameters for a new build, using these parameters as a template. Copies the environmental properties from this parameter (eg gradle user home dir, etc), but does not copy the
+     * build specific properties (eg task names).</p>
      *
      * @return The new parameters.
      */
@@ -138,9 +143,15 @@ public class StartParameter {
         StartParameter startParameter = new StartParameter();
         startParameter.gradleUserHomeDir = gradleUserHomeDir;
         startParameter.cacheUsage = cacheUsage;
-        startParameter.logLevel = logLevel;
-        startParameter.colorOutput = colorOutput;
+        startParameter.setLogLevel(getLogLevel());
+        startParameter.setColorOutput(isColorOutput());
+        startParameter.setShowStacktrace(getShowStacktrace());
         startParameter.profile = profile;
+        startParameter.continueOnFailure = continueOnFailure;
+        startParameter.offline = offline;
+        startParameter.rerunTasks = rerunTasks;
+        startParameter.recompileScripts = recompileScripts;
+        startParameter.refreshDependencies = refreshDependencies;
         return startParameter;
     }
 
@@ -153,8 +164,7 @@ public class StartParameter {
     }
 
     /**
-     * Returns the build file to use to select the default project. Returns null when the build file is not used to
-     * select the default project.
+     * Returns the build file to use to select the default project. Returns null when the build file is not used to select the default project.
      *
      * @return The build file. May be null.
      */
@@ -163,8 +173,7 @@ public class StartParameter {
     }
 
     /**
-     * Sets the build file to use to select the default project. Use null to disable selecting the default project using
-     * the build file.
+     * Sets the build file to use to select the default project. Use null to disable selecting the default project using the build file.
      *
      * @param buildFile The build file. May be null.
      */
@@ -174,98 +183,47 @@ public class StartParameter {
             setCurrentDir(null);
         } else {
             this.buildFile = GFileUtils.canonicalise(buildFile);
-            currentDir = this.buildFile.getParentFile();
-            defaultProjectSelector = new BuildFileProjectSpec(this.buildFile);
+            setProjectDir(this.buildFile.getParentFile());
         }
     }
 
     /**
-     * <p>Returns the {@link ScriptSource} to use for the build file for this build. Returns null when the default build
-     * file(s) are to be used. This source is used for <em>all</em> projects included in the build.</p>
-     *
-     * @return The build file source, or null to use the defaults.
-     */
-    public ScriptSource getBuildScriptSource() {
-        return buildScriptSource;
-    }
-
-    /**
-     * <p>Returns the {@link ScriptSource} to use for the settings script for this build. Returns null when the default
-     * settings script is to be used.</p>
-     *
-     * @return The settings script source, or null to use the default.
-     */
-    public ScriptSource getSettingsScriptSource() {
-        return settingsScriptSource;
-    }
-
-    /**
-     * <p>Sets the {@link ScriptSource} to use for the settings script. Set to null to use the default settings
-     * script.</p>
+     * Specifies that an empty settings script should be used.
      *
-     * @param settingsScriptSource The settings script source.
-     */
-    public void setSettingsScriptSource(ScriptSource settingsScriptSource) {
-        this.settingsScriptSource = settingsScriptSource;
-    }
-
-    /**
-     * <p>Specifies that the given script should be used as the build file for this build. Uses an empty settings file.
-     * </p>
+     * This means that even if a settings file exists in the conventional location, or has been previously specified by {@link #setSettingsFile(java.io.File)}, it will not be used.
      *
-     * @param buildScriptText The script to use as the build file.
-     * @return this
-     */
-    public StartParameter useEmbeddedBuildFile(String buildScriptText) {
-        return setBuildScriptSource(new StringScriptSource("embedded build file", buildScriptText));
-    }
-    
-    /**
-     * <p>Specifies that the given script should be used as the build file for this build. Uses an empty settings file.
-     * </p>
+     * If {@link #setSettingsFile(java.io.File)} is called after this, it will supersede calling this method.
      *
-     * @param buildScript The script to use as the build file.
      * @return this
      */
-    public StartParameter setBuildScriptSource(ScriptSource buildScript) {
-        buildScriptSource = buildScript;
-        settingsScriptSource = new StringScriptSource("empty settings file", "");
+    public StartParameter useEmptySettings() {
         searchUpwards = false;
+        useEmptySettings = true;
+        settingsFile = null;
         return this;
     }
 
     /**
-     * <p>Returns the {@link BuildExecuter} to use for the build.</p>
+     * Deprecated. Use {@link #useEmptySettings()}.
      *
-     * @return The {@link BuildExecuter}. Never returns null.
+     * @deprecated use {@link #useEmptySettings()}
      */
-    public BuildExecuter getBuildExecuter() {
-        BuildExecuter executer = buildExecuter;
-        if (executer == null) {
-            executer = new DefaultBuildExecuter(taskNames, excludedTaskNames);
-        }
-        if (dryRun) {
-            executer = new DryRunBuildExecuter(executer);
-        }
-        return executer;
+    @Deprecated
+    public StartParameter useEmptySettingsScript() {
+        return useEmptySettings();
     }
 
     /**
-     * <p>Sets the {@link BuildExecuter} to use for the build. You can use the method to change the algorithm used to
-     * execute the build, by providing your own {@code BuildExecuter} implementation.</p>
-     *
-     * <p> Set to null to use the default executer. When this property is set to a non-null value, the taskNames and
-     * mergedBuild properties are ignored.</p>
+     * Returns whether an empty settings script will be used regardless of whether one exists in the default location.
      *
-     * @param buildExecuter The executer to use, or null to use the default executer.
+     * @return Whether to use empty settings or not.
      */
-    public void setBuildExecuter(BuildExecuter buildExecuter) {
-        this.buildExecuter = buildExecuter;
+    public boolean isUseEmptySettings() {
+        return useEmptySettings;
     }
 
     /**
-     * Returns the names of the tasks to execute in this build. When empty, the default tasks for the project will be
-     * executed.
+     * Returns the names of the tasks to execute in this build. When empty, the default tasks for the project will be executed.
      *
      * @return the names of the tasks to execute in this build. Never returns null.
      */
@@ -274,14 +232,13 @@ public class StartParameter {
     }
 
     /**
-     * <p>Sets the tasks to execute in this build. Set to an empty list, or null, to execute the default tasks for the
-     * project. The tasks are executed in the order provided, subject to dependency between the tasks.</p>
+     * <p>Sets the tasks to execute in this build. Set to an empty list, or null, to execute the default tasks for the project. The tasks are executed in the order provided, subject to dependency
+     * between the tasks.</p>
      *
      * @param taskNames the names of the tasks to execute in this build.
      */
-    public void setTaskNames(Collection<String> taskNames) {
-        this.taskNames = !GUtil.isTrue(taskNames) ? new ArrayList<String>() : new ArrayList<String>(taskNames);
-        buildExecuter = null;
+    public void setTaskNames(Iterable<String> taskNames) {
+        this.taskNames = Lists.newArrayList(taskNames);
     }
 
     /**
@@ -298,8 +255,8 @@ public class StartParameter {
      *
      * @param excludedTaskNames The task names. Can be null.
      */
-    public void setExcludedTaskNames(Collection<String> excludedTaskNames) {
-        this.excludedTaskNames = !GUtil.isTrue(excludedTaskNames) ? new HashSet<String>() : new HashSet<String>(excludedTaskNames);
+    public void setExcludedTaskNames(Iterable<String> excludedTaskNames) {
+        this.excludedTaskNames = Sets.newLinkedHashSet(excludedTaskNames);
     }
 
     /**
@@ -312,8 +269,7 @@ public class StartParameter {
     }
 
     /**
-     * Sets the directory to use to select the default project, and to search for the settings file. Set to null to use
-     * the default current directory.
+     * Sets the directory to use to select the default project, and to search for the settings file. Set to null to use the default current directory.
      *
      * @param currentDir The directory. Should not be null.
      */
@@ -323,7 +279,6 @@ public class StartParameter {
         } else {
             this.currentDir = GFileUtils.canonicalise(new File(System.getProperty("user.dir")));
         }
-        defaultProjectSelector = null;
     }
 
     public boolean isSearchUpwards() {
@@ -351,6 +306,18 @@ public class StartParameter {
     }
 
     /**
+     * Returns a newly constructed map that is the JVM system properties merged with the system property args. <p> System property args take precedency overy JVM system properties.
+     *
+     * @return The merged system properties
+     */
+    public Map<String, String> getMergedSystemProperties() {
+        Map<String, String> merged = new HashMap<String, String>();
+        merged.putAll((Map) System.getProperties());
+        merged.putAll(getSystemPropertiesArgs());
+        return merged;
+    }
+
+    /**
      * Returns the directory to use as the user home directory.
      *
      * @return The home directory.
@@ -368,19 +335,37 @@ public class StartParameter {
         this.gradleUserHomeDir = gradleUserHomeDir == null ? DEFAULT_GRADLE_USER_HOME : GFileUtils.canonicalise(gradleUserHomeDir);
     }
 
-    public ProjectDependenciesBuildInstruction getProjectDependenciesBuildInstruction() {
-        return projectDependenciesBuildInstruction;
+    /**
+     * Returns true if project dependencies are to be built, false if they should not be. The default is true.
+     */
+    public boolean isBuildProjectDependencies() {
+        return buildProjectDependencies;
     }
 
-    public void setProjectDependenciesBuildInstruction(
-            ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction) {
-        this.projectDependenciesBuildInstruction = projectDependenciesBuildInstruction;
+    /**
+     * Specifies whether project dependencies should be built. Defaults to true.
+     *
+     * @return this
+     */
+    public StartParameter setBuildProjectDependencies(boolean build) {
+        this.buildProjectDependencies = build;
+        return this;
     }
 
+    /**
+     *  Returns the configured CacheUsage.
+     *  @deprecated Use #isRecompileScripts and/or #isRerunTasks instead.
+     * */
+    @Deprecated
     public CacheUsage getCacheUsage() {
         return cacheUsage;
     }
 
+    /**
+     *  Sets the Cache usage.
+     *  @deprecated Use #setRecompileScripts and/or #setRerunTasks instead.
+     * */
+    @Deprecated
     public void setCacheUsage(CacheUsage cacheUsage) {
         this.cacheUsage = cacheUsage;
     }
@@ -393,12 +378,25 @@ public class StartParameter {
         this.dryRun = dryRun;
     }
 
+    /**
+     * Returns task optimization disabled flag.
+     *
+     * @deprecated Use #isRerunTasks instead.
+     * */
+    @Deprecated
     public boolean isNoOpt() {
-        return noOpt;
+        return rerunTasks;
     }
 
+   /**
+    * Get task optimization disabled.
+    *
+    * @param noOpt The boolean value for disabling task optimsation.
+    * @deprecated Use #setRefreshDependencies(boolean) instead.
+    */
+    @Deprecated
     public void setNoOpt(boolean noOpt) {
-        this.noOpt = noOpt;
+        this.rerunTasks = noOpt;
     }
 
     /**
@@ -408,120 +406,184 @@ public class StartParameter {
      */
     public void setSettingsFile(File settingsFile) {
         if (settingsFile == null) {
-            settingsScriptSource = null;
+            this.settingsFile = null;
         } else {
-            File canonicalFile = GFileUtils.canonicalise(settingsFile);
-            currentDir = canonicalFile.getParentFile();
-            settingsScriptSource = new UriScriptSource("settings file", canonicalFile);
+            this.useEmptySettings = false;
+            this.settingsFile = GFileUtils.canonicalise(settingsFile);
+            currentDir = this.settingsFile.getParentFile();
         }
     }
 
     /**
-     * Adds the given file to the list of init scripts that are run before the build starts.  This list is in
-     * addition to the user init script located in ${user.home}/.gradle/init.gradle.
-     * @param initScriptFile The init script to be run during the Gradle invocation.
+     * Returns the explicit settings file to use for the build, or null.
+     *
+     * Will return null if the default settings file is to be used. However, if {@link #isUseEmptySettings()} returns true, then no settings file at all will be used.
+     *
+     * @return The settings file. May be null.
+     * @see #isUseEmptySettings()
+     */
+    public File getSettingsFile() {
+        return settingsFile;
+    }
+
+    /**
+     * Adds the given file to the list of init scripts that are run before the build starts.  This list is in addition to the default init scripts.
+     *
+     * @param initScriptFile The init scripts.
      */
     public void addInitScript(File initScriptFile) {
         initScripts.add(initScriptFile);
     }
 
+    /**
+     * Sets the list of init scripts to be run before the build starts. This list is in addition to the default init scripts.
+     *
+     * @param initScripts The init scripts.
+     */
     public void setInitScripts(List<File> initScripts) {
         this.initScripts = initScripts;
     }
 
     /**
-     * Returns all explicitly added init scripts that will be run before the build starts.  This list does not
-     * contain the user init script located in ${user.home}/.gradle/init.gradle, even though that init script
-     * will also be run.
+     * Returns all explicitly added init scripts that will be run before the build starts.  This list does not contain the user init script located in ${user.home}/.gradle/init.gradle, even though
+     * that init script will also be run.
+     *
      * @return list of all explicitly added init scripts.
      */
     public List<File> getInitScripts() {
         return Collections.unmodifiableList(initScripts);
     }
 
-    public LogLevel getLogLevel() {
-        return logLevel;
+    /**
+     * Sets the project directory to use to select the default project. Use null to use the default criteria for selecting the default project.
+     *
+     * @param projectDir The project directory. May be null.
+     */
+    public void setProjectDir(File projectDir) {
+        if (projectDir == null) {
+            setCurrentDir(null);
+            this.projectDir = null;
+        } else {
+            File canonicalFile = GFileUtils.canonicalise(projectDir);
+            currentDir = canonicalFile;
+            this.projectDir = canonicalFile;
+        }
+    }
+
+    /**
+     * Returns the project dir to use to select the default project.
+     *
+     * Returns null when the build file is not used to select the default project
+     *
+     * @return The project dir. May be null.
+     */
+    public File getProjectDir() {
+        return projectDir;
     }
 
-    public void setLogLevel(LogLevel logLevel) {
-        this.logLevel = logLevel;
+    /**
+     * Specifies if a profile report should be generated.
+     *
+     * @param profile true if a profile report should be generated
+     */
+    public void setProfile(boolean profile) {
+        this.profile = profile;
     }
 
-    public ShowStacktrace getShowStacktrace() {
-        return showStacktrace;
+    /**
+     * Returns true if a profile report will be generated.
+     */
+    public boolean isProfile() {
+        return profile;
     }
 
-    public void setShowStacktrace(ShowStacktrace showStacktrace) {
-        this.showStacktrace = showStacktrace;
+    /**
+     * Specifies whether the build should continue on task failure. The default is false.
+     */
+    public boolean isContinueOnFailure() {
+        return continueOnFailure;
     }
 
     /**
-     * Returns the selector used to choose the default project of the build. This is the project used as the starting
-     * point for resolving task names, and for determining the default tasks.
-     *
-     * @return The default project. Never returns null.
+     * Specifies whether the build should continue on task failure. The default is false.
      */
-    public ProjectSpec getDefaultProjectSelector() {
-        return defaultProjectSelector != null ? defaultProjectSelector : new DefaultProjectSpec(currentDir);
+    public void setContinueOnFailure(boolean continueOnFailure) {
+        this.continueOnFailure = continueOnFailure;
     }
 
     /**
-     * Sets the selector used to choose the default project of the build.
-     *
-     * @param defaultProjectSelector The selector. Should not be null.
+     * Specifies whether the build should be performed offline (ie without network access).
      */
-    public void setDefaultProjectSelector(ProjectSpec defaultProjectSelector) {
-        this.defaultProjectSelector = defaultProjectSelector;
+    public boolean isOffline() {
+        return offline;
     }
 
     /**
-     * Sets the project directory to use to select the default project. Use null to use the default criteria for
-     * selecting the default project.
-     *
-     * @param projectDir The project directory. May be null.
+     * Specifies whether the build should be performed offline (ie without network access).
      */
-    public void setProjectDir(File projectDir) {
-        if (projectDir == null) {
-            setCurrentDir(null);
-        } else {
-            File canonicalFile = GFileUtils.canonicalise(projectDir);
-            currentDir = canonicalFile;
-            defaultProjectSelector = new ProjectDirectoryProjectSpec(canonicalFile);
-        }
+    public void setOffline(boolean offline) {
+        this.offline = offline;
     }
 
     /**
-     * Returns true if logging output should be displayed in color when Gradle is running in a terminal which supports
-     * color output. The default value is true.
-     *
-     * @return true if logging output should be displayed in color.
+     * Supplies the refresh options to use for the build.
+     * @deprecated Use #setRefreshDependencies(boolean) instead.
      */
-    public boolean isColorOutput() {
-        return colorOutput;
+    @Deprecated
+    public void setRefreshOptions(RefreshOptions refreshOptions) {
+        this.refreshDependencies = refreshOptions.refreshDependencies();
     }
 
     /**
-     * Specifies whether logging output should be displayed in color.
-     *
-     * @param colorOutput true if logging output should be displayed in color.
+     * Returns the refresh options used for the build.
+     * @deprecated Use #isRefreshDependencies instead.
      */
-    public void setColorOutput(boolean colorOutput) {
-        this.colorOutput = colorOutput;
+    @Deprecated
+    public RefreshOptions getRefreshOptions() {
+        RefreshOptions options = isRefreshDependencies() ? new RefreshOptions(Arrays.asList(RefreshOptions.Option.DEPENDENCIES)) : RefreshOptions.NONE;
+        return options;
     }
 
     /**
-     * Specifies if a profile report should be generated.
-     * @param profile true if a profile report should be generated
+     * Specifies whether the depencendies should be refreshed..
      */
-    public void setProfile(boolean profile) {
-        this.profile = profile;
+    public boolean isRefreshDependencies() {
+        return refreshDependencies;
     }
 
     /**
-     * Returns true if a profile report will be generated.
+     * Specifies whether the depencendies should be refreshed..
      */
-    public boolean isProfile() {
-        return profile;
+    public void setRefreshDependencies(boolean refreshDependencies) {
+        this.refreshDependencies = refreshDependencies;
+    }
+
+    /**
+     * Specifies whether the cached task results should be ignored and each task should be forced to be executed.
+     */
+    public boolean isRerunTasks() {
+        return rerunTasks;
+    }
+
+    /**
+     * Specifies whether the cached task results should be ignored and each task should be forced to be executed.
+     */
+    public void setRerunTasks(boolean rerunTasks) {
+        this.rerunTasks = rerunTasks;
+    }
+
+    /**
+     * Specifies whether the build scripts should be recompiled.
+     */
+    public boolean isRecompileScripts() {
+        return recompileScripts;
+    }
+
+    /**
+     * Specifies whether the build scripts should be recompiled.
+     */
+    public void setRecompileScripts(boolean recompileScripts) {
+        this.recompileScripts = recompileScripts;
     }
 
     @Override
@@ -535,17 +597,15 @@ public class StartParameter {
                 + ", systemPropertiesArgs=" + systemPropertiesArgs
                 + ", gradleUserHomeDir=" + gradleUserHomeDir
                 + ", cacheUsage=" + cacheUsage
-                + ", buildScriptSource=" + buildScriptSource
-                + ", settingsScriptSource=" + settingsScriptSource
-                + ", buildExecuter=" + buildExecuter
-                + ", defaultProjectSelector=" + defaultProjectSelector
-                + ", logLevel=" + logLevel
-                + ", showStacktrace=" + showStacktrace
+                + ", logLevel=" + getLogLevel()
+                + ", showStacktrace=" + getShowStacktrace()
                 + ", buildFile=" + buildFile
                 + ", initScripts=" + initScripts
                 + ", dryRun=" + dryRun
-                + ", noOpt=" + noOpt
-                + ", profile=" + profile
+                + ", rerunTasks=" + rerunTasks
+                + ", recompileScripts=" + recompileScripts
+                + ", offline=" + offline
+                + ", refreshDependencies=" + refreshDependencies
                 + '}';
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/AntBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/AntBuilder.java
index deac473..76b657f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/AntBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/AntBuilder.java
@@ -43,4 +43,12 @@ public abstract class AntBuilder extends groovy.util.AntBuilder {
      * @param antBuildFile The build file. This is resolved as per {@link Project#file(Object)}.
      */
     public abstract void importBuild(Object antBuildFile);
+
+    /**
+     * Returns this AntBuilder. Useful when you need to pass this builder to methods from within closures.
+     * @return this
+     */
+    public AntBuilder getAnt() {
+        return this;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java b/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java
index ee8c52c..e34301e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/DefaultTask.java
@@ -22,7 +22,7 @@ import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.NoConventionMapping;
 
 /**
- * {@code DefaultTask} is the standard {@link Task} implementation. You can extend this to implement your own tasks.
+ * {@code DefaultTask} is the standard {@link Task} implementation. You can extend this to implement your own task types.
  *
  * @author Hans Dockter
  */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectCollection.java
index 98e185f..a61d202 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectCollection.java
@@ -18,31 +18,20 @@ package org.gradle.api;
 import groovy.lang.Closure;
 import org.gradle.api.specs.Spec;
 
-import java.util.Set;
+import java.util.Collection;
 
 /**
- * <p>A {@code DomainObjectCollection} represents a read-only set of domain objects of type {@code T}.</p>
+ * <p>A {@code DomainObjectCollection} is a specialised {@link Collection} that adds the ability to modification notifications and live filtered sub collections.</p>
  *
- * <p>You can use the methods of this interface to query the elements of the collection. You can also add actions
- * which are executed as elements are added to this collection.</p>
+ * <p>The filtered collections returned by the filtering methods, such as {@link #matching(Closure)}, return collections that are <em>live</em>. That is, they reflect 
+ * changes made to the source collection that they were created from. This is true for filtered collections made from filtered collections etc.</p>
+ * <p>
+ * You can also add actions which are executed as elements are added to the collection. Actions added to filtered collections will be fired if an addition/removal
+ * occurs for the source collection that matches the filter.</p>
  *
  * @param <T> The type of domain objects in this collection.
  */
-public interface DomainObjectCollection<T> extends Iterable<T> {
-    /**
-     * Returns the objects in this collection.
-     *
-     * @return The objects. Returns an empty set if this collection is empty.
-     */
-    Set<T> getAll();
-
-    /**
-     * Returns the objects in this collection which meet the given specification.
-     *
-     * @param spec The specification to use.
-     * @return The matching objects. Returns an empty set if there are no such objects in this collection.
-     */
-    Set<T> findAll(Spec<? super T> spec);
+public interface DomainObjectCollection<T> extends Collection<T> {
 
     /**
      * Returns a collection containing the objects in this collection of the given type.  The returned collection is
@@ -50,7 +39,7 @@ public interface DomainObjectCollection<T> extends Iterable<T> {
      * collection.
      *
      * @param type The type of objects to find.
-     * @return The matching objects. Returns an empty set if there are no such objects in this collection.
+     * @return The matching objects. Returns an empty collection if there are no such objects in this collection.
      */
     <S extends T> DomainObjectCollection<S> withType(Class<S> type);
 
@@ -60,7 +49,7 @@ public interface DomainObjectCollection<T> extends Iterable<T> {
      *
      * @param type The type of objects to find.
      * @param configureAction The action to execute for each object in the resulting collection.
-     * @return The matching objects. Returns an empty set if there are no such objects in this collection.
+     * @return The matching objects. Returns an empty collection if there are no such objects in this collection.
      */
     <S extends T> DomainObjectCollection<S> withType(Class<S> type, Action<? super S> configureAction);
 
@@ -70,7 +59,7 @@ public interface DomainObjectCollection<T> extends Iterable<T> {
      *
      * @param type The type of objects to find.
      * @param configureClosure The closure to execute for each object in the resulting collection.
-     * @return The matching objects. Returns an empty set if there are no such objects in this collection.
+     * @return The matching objects. Returns an empty collection if there are no such objects in this collection.
      */
     <S extends T> DomainObjectCollection<S> withType(Class<S> type, Closure configureClosure);
 
@@ -121,24 +110,12 @@ public interface DomainObjectCollection<T> extends Iterable<T> {
     Action<? super T> whenObjectRemoved(Action<? super T> action);
 
     /**
-     * Executes the given action against all objects in this collection, and any objects subsequently added to this
-     * collection.
-     *
-     * @param action The action to be executed
-     * @deprecated Use {@link #all(Action)} instead.
-     */
-    @Deprecated
-    void allObjects(Action<? super T> action);
-
-    /**
-     * Executes the given closure against all objects in this collection, and any objects subsequently added to this
-     * collection.
+     * Adds a closure to be called when an object is removed from this collection. The removed object is passed to the
+     * closure as the parameter.
      *
      * @param action The closure to be called
-     * @deprecated Use {@link #all(groovy.lang.Closure)} instead.
      */
-    @Deprecated
-    void allObjects(Closure action);
+    void whenObjectRemoved(Closure action);
 
     /**
      * Executes the given action against all objects in this collection, and any objects subsequently added to this
@@ -149,10 +126,20 @@ public interface DomainObjectCollection<T> extends Iterable<T> {
     void all(Action<? super T> action);
 
     /**
-     * Executes the given closure against all objects in this collection, and any objects subsequently added to this
-     * collection. The object is passed to the closure as the closure delegate. Alternatively, it is also passed as a parameter.
+     * Executes the given closure against all objects in this collection, and any objects subsequently added to this collection. The object is passed to the closure as the closure
+     * delegate. Alternatively, it is also passed as a parameter.
      *
      * @param action The action to be executed
      */
     void all(Closure action);
+
+    // note: this is here to override the default Groovy Collection.findAll { } method.
+    /**
+     * Returns a collection which contains the objects in this collection which meet the given closure specification.
+     *
+     * @param spec The specification to use. The closure gets a collection element as an argument.
+     * @return The collection of matching objects. Returns an empty collection if there are no such objects in this
+     *         collection.
+     */
+    public Collection<T> findAll(Closure spec);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectSet.java
new file mode 100644
index 0000000..1e8016d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/DomainObjectSet.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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 groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+
+import java.util.Set;
+
+/**
+ * <p>A {@code DomainObjectSet} is a specialisation of {@link DomainObjectCollection} that guarantees {@link Set} semantics.</p>
+ *
+ * @param <T> The type of domain objects in this set.
+ */
+public interface DomainObjectSet<T> extends DomainObjectCollection<T>, Set<T> {
+
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> DomainObjectSet<S> withType(Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    DomainObjectSet<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    DomainObjectSet<T> matching(Closure spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    Set<T> findAll(Closure spec);
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java b/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
index 481bd52..8d965a8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
@@ -16,7 +16,7 @@
 package org.gradle.api;
 
 /**
- * This exceptions is thrown, if a dependency is declared with a ilegal notation.
+ * This exceptions is thrown, if a dependency is declared with a illegal notation.
  * 
  * @author Hans Dockter
  */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java b/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java
index fb9ba60..9119ba8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api;
 
+import org.gradle.internal.SystemProperties;
+
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 
@@ -60,6 +62,15 @@ public enum JavaVersion {
         throw new IllegalArgumentException(String.format("Could not determine java version from '%s'.", name));
     }
 
+    /**
+     * Provides the JavaVersion of the current used JVM  {@code JavaVersion}.
+     *
+     * @return The version of the current JVM.
+     */
+    public static JavaVersion current() {
+        return toVersion(SystemProperties.getJavaVersion());
+    }
+
     @Override
     public String toString() {
         return name().substring("VERSION_".length()).replace('_', '.');
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/LocationAwareException.java b/subprojects/core/src/main/groovy/org/gradle/api/LocationAwareException.java
deleted file mode 100644
index cf41c26..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/LocationAwareException.java
+++ /dev/null
@@ -1,139 +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.api;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.internal.MultiCauseException;
-import org.gradle.groovy.scripts.ScriptSource;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * A {@code LocationAwareException} is an exception which can be annotated with a location in a script.
- */
-public class LocationAwareException extends GradleException {
-    private final Throwable target;
-    private final ScriptSource source;
-    private final Integer lineNumber;
-
-    public LocationAwareException(Throwable cause, Throwable target, ScriptSource source, Integer lineNumber) {
-        this.source = source;
-        this.lineNumber = lineNumber;
-        this.target = target;
-        initCause(cause);
-    }
-
-    /**
-     * Returns the target exception.
-     *
-     * @return The target exception. Not null
-     */
-    public Throwable getTarget() {
-        return target;
-    }
-
-    /**
-     * <p>Returns the undecorated message of this exception.</p>
-     *
-     * @return The undecorated message. May return null.
-     */
-    public String getOriginalMessage() {
-        return target.getMessage();
-    }
-
-    /**
-     * <p>Returns the source of the script where this exception occurred.</p>
-     *
-     * @return The source. May return null.
-     */
-    public ScriptSource getScriptSource() {
-        return source;
-    }
-
-    /**
-     * <p>Returns a description of the location of where this exception occurred.</p>
-     *
-     * @return The location description. May return null.
-     */
-    public String getLocation() {
-        if (source == null) {
-            return null;
-        }
-        String sourceMsg = StringUtils.capitalize(source.getDisplayName());
-        if (lineNumber == null) {
-            return sourceMsg;
-        }
-        return String.format("%s line: %d", sourceMsg, lineNumber);
-    }
-
-    /**
-     * Returns the line in the script where this exception occurred, if known.
-     *
-     * @return The line number, or null if not known.
-     */
-    public Integer getLineNumber() {
-        return lineNumber;
-    }
-
-    /**
-     * Returns the fully formatted error message, including the location.
-     *
-     * @return the message. May return null.
-     */
-    public String getMessage() {
-        String location = getLocation();
-        String message = target.getMessage();
-        if (location == null && message == null) {
-            return null;
-        }
-        if (location == null) {
-            return message;
-        }
-        if (message == null) {
-            return location;
-        }
-        return String.format("%s%n%s", location, message);
-    }
-
-    /**
-     * Returns the reportable causes for this failure.
-     *
-     * @return The causes. Never returns null, returns an empty list if this exception has no reportable causes.
-     */
-    public List<Throwable> getReportableCauses() {
-        List<Throwable> causes = new ArrayList<Throwable>();
-        LinkedList<Throwable> queue = new LinkedList<Throwable>();
-        addCauses(target, queue);
-        while (!queue.isEmpty()) {
-            Throwable t = queue.removeFirst();
-            causes.add(t);
-            addCauses(t, queue);
-        }
-        return causes;
-    }
-
-    private void addCauses(Throwable t, Collection<Throwable> causes) {
-        if (t instanceof MultiCauseException) {
-            MultiCauseException multiCauseException = (MultiCauseException) t;
-            causes.addAll(multiCauseException.getCauses());
-        } else if (t.getCause() != null) {
-            causes.add(t.getCause());
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Named.java b/subprojects/core/src/main/groovy/org/gradle/api/Named.java
new file mode 100644
index 0000000..f1a9820
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Named.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * Types can implement this interface and use the embedded {@link Namer} implementation, to satisfy API that calls for a namer.
+ */
+public interface Named {
+
+    /**
+     * The object's name.
+     * <p>
+     * Must be constant for the life of the object.
+     *
+     * @return The name. Never null.
+     */
+    String getName();
+
+    // -- Internal note --
+    // It would be better to only require getName() to return Object and just call toString() on it, but
+    // if you have a groovy class with a “String name” property the generated getName() method will not 
+    // satisfy the Named interface. This seems to be a bug in the Groovy compiler - LD.
+
+    /**
+     * An implementation of the namer interface for objects implementing the named interface.
+     */
+    public static class Namer implements org.gradle.api.Namer<Named> {
+        public String determineName(Named object) {
+            return object.getName();
+        }
+        
+        public static <T> org.gradle.api.Namer<? super T> forType(Class<? extends T> type) {
+            if (Named.class.isAssignableFrom(type)) {
+                return (org.gradle.api.Namer<T>)new Namer();
+            } else {
+                throw new IllegalArgumentException(String.format("The '%s' cannot be used with FactoryNamedDomainObjectContainer without specifying a Namer as it does not implement the Named interface.", type));
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
index 4ee53cc..aabc1a6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
@@ -1,114 +1,182 @@
-/*
- * 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;
-
-import groovy.lang.Closure;
-import org.gradle.api.specs.Spec;
-
-import java.util.Map;
-
-/**
- * <p>A {@code NamedDomainObjectCollection} represents a read-only set of domain objects of type {@code T}. Each domain
- * object in this collection has a name, which uniquely identifies the object in this collection.</p>
- *
- * <p>Each object in a collection are accessible as read-only properties of the collection, using the name of the object
- * as the property name. For example:</p>
- *
- * <pre>
- * tasks.add('myTask')
- * tasks.myTask.dependsOn someOtherTask
- * </pre>
- *
- * <p>A dynamic method is added for each object which takes a configuration closure. This is equivalent to calling
- * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
- *
- * <pre>
- * tasks.add('myTask')
- * tasks.myTask {
- *     dependsOn someOtherTask
- * }
- * </pre>
- *
- * <p>You can also use the {@code []} operator to access the objects of a collection by name. For example:</p>
- *
- * <pre>
- * tasks.add('myTask')
- * tasks['myTask'].dependsOn someOtherTask
- * </pre>
- *
- * @param <T> The type of domain objects in this collection.
- */
-public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T> {
-    /**
-     * Returns the objects in this collection, as a map from object name to object instance.
-     *
-     * @return The objects. Returns an empty map if this collection is empty.
-     */
-    Map<String, T> getAsMap();
-
-    /**
-     * Locates an object by name, returning null if there is no such object.
-     *
-     * @param name The object name
-     * @return The object with the given name, or null if there is no such object in this collection.
-     */
-    T findByName(String name);
-
-    /**
-     * Locates an object by name, failing if there is no such object.
-     *
-     * @param name The object name
-     * @return The object with the given name. Never returns null.
-     * @throws UnknownDomainObjectException when there is no such object in this collection.
-     */
-    T getByName(String name) throws UnknownDomainObjectException;
-
-    /**
-     * Locates an object by name, failing if there is no such object. The given configure closure is executed against
-     * the object before it is returned from this method. The object is passed to the closure as it's delegate.
-     *
-     * @param name The object name
-     * @param configureClosure The closure to use to configure the object.
-     * @return The object with the given name, after the configure closure has been applied to it. Never returns null.
-     * @throws UnknownDomainObjectException when there is no such object in this collection.
-     */
-    T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException;
-
-    /**
-     * Locates an object by name, failing if there is no such task. This method is identical to {@link
-     * #getByName(String)}. You can call this method in your build script by using the groovy {@code []} operator.
-     *
-     * @param name The object name
-     * @return The object with the given name. Never returns null.
-     * @throws UnknownDomainObjectException when there is no such object in this collection.
-     */
-    T getAt(String name) throws UnknownDomainObjectException;
-
-    /**
-     * {@inheritDoc}
-     */
-    <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type);
-
-    /**
-     * {@inheritDoc}
-     */
-    NamedDomainObjectCollection<T> matching(Spec<? super T> spec);
-
-    /**
-     * {@inheritDoc}
-     */
-    NamedDomainObjectCollection<T> matching(Closure spec);
-}
+/*
+ * Copyright 2011 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 groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedMap;
+
+/**
+ * <p>A {@code NamedDomainObjectCollection} represents a collection of domain objects that have an inherent, constant, name.</p>
+ *
+ * <p>Objects to be added to a named domain object collection must implement {@code equals()} in such a way that no two objects
+ * with different names are considered equal. That is, all equality tests <strong>must</strong> consider the name as an
+ * equality key. Behavior is undefined if two objects with different names are considered equal by their {@code equals()} implementation.</p>
+ *
+ * <p>All implementations <strong>must</strong> guarantee that all elements in the collection are uniquely named. That is,
+ * an attempt to add an object with a name equal to the name of any existing object in the collection will fail.
+ * Implementations may choose to simply return false from {@code add(T)} or to throw an exception.</p>
+ *
+ * <p>Objects in the collection are accessible as read-only properties, using the name of the object
+ * as the property name. For example (assuming the 'name' property provides the object name):</p>
+ *
+ * <pre>
+ * books.add(new Book(name: "gradle", title: null))
+ * books.gradle.title = "Gradle in Action"
+ * </pre>
+ *
+ * <p>A dynamic method is added for each object which takes a configuration closure. This is equivalent to calling
+ * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
+ *
+ * <pre>
+ * books.add(new Book(name: "gradle", title: null))
+ * books.gradle {
+ *   title = "Gradle in Action"
+ * }
+ * </pre>
+ *
+ * <p>You can also use the {@code []} operator to access the objects of a collection by name. For example:</p>
+ *
+ * <pre>
+ * books.add(new Book(name: "gradle", title: null))
+ * books['gradle'].title = "Gradle in Action"
+ * </pre>
+ *
+ * <p>{@link Rule} objects can be attached to the collection in order to respond to requests for objects by name
+ * where no object with name exists in the collection. This mechanism can be used to create objects on demand. 
+ * For example: </p>
+ * 
+ * <pre>
+ * books.addRule('create any') { books.add(new Book(name: "gradle", title: null)) }
+ * books.gradle.name == "gradle"
+ * </pre>
+ * 
+ * @param <T> The type of domain objects in this collection.
+ */
+public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T> {
+
+    /**
+     * Adds an object to the collection, if there is no existing object in the collection with the same name.
+     *
+     * @param e the item to add to the collection
+     * @return {@code true} if the item was added, or {@code} false if an item with the same name already exists.
+     */
+    boolean add(T e);
+
+    /**
+     * Adds any of the given objects to the collection that do not have the same name as any existing element.
+     *
+     * @param c the items to add to the collection
+     * @return {@code true} if any item was added, or {@code} false if all items have non unique names within this collection.
+     */
+    boolean addAll(Collection<? extends T> c);
+
+    /**
+     * An object that represents the naming strategy used to name objects of this collection.
+     */
+    Namer<T> getNamer();
+
+    /**
+     * <p>Returns the objects in this collection, as a map from object name to object instance.</p>
+     *
+     * <p>The map is ordered by the <em>natural ordering</em> of the object names (i.e. keys).</p>
+     *
+     * @return The objects. Returns an empty map if this collection is empty.
+     */
+    SortedMap<String, T> getAsMap();
+
+    /**
+     * Locates an object by name, returning null if there is no such object.
+     *
+     * @param name The object name
+     * @return The object with the given name, or null if there is no such object in this collection.
+     */
+    T findByName(String name);
+
+    /**
+     * Locates an object by name, failing if there is no such object.
+     *
+     * @param name The object name
+     * @return The object with the given name. Never returns null.
+     * @throws UnknownDomainObjectException when there is no such object in this collection.
+     */
+    T getByName(String name) throws UnknownDomainObjectException;
+
+    /**
+     * Locates an object by name, failing if there is no such object. The given configure closure is executed against
+     * the object before it is returned from this method. The object is passed to the closure as it's delegate.
+     *
+     * @param name The object name
+     * @param configureClosure The closure to use to configure the object.
+     * @return The object with the given name, after the configure closure has been applied to it. Never returns null.
+     * @throws UnknownDomainObjectException when there is no such object in this collection.
+     */
+    T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException;
+
+    /**
+     * Locates an object by name, failing if there is no such task. This method is identical to {@link
+     * #getByName(String)}. You can call this method in your build script by using the groovy {@code []} operator.
+     *
+     * @param name The object name
+     * @return The object with the given name. Never returns null.
+     * @throws UnknownDomainObjectException when there is no such object in this collection.
+     */
+    T getAt(String name) throws UnknownDomainObjectException;
+
+    /**
+     * Adds a rule to this collection. The given rule is invoked when an unknown object is requested by name.
+     *
+     * @param rule The rule to add.
+     * @return The added rule.
+     */
+    Rule addRule(Rule rule);
+
+    /**
+     * Adds a rule to this collection. The given closure is executed when an unknown object is requested by name. The
+     * requested name is passed to the closure as a parameter.
+     *
+     * @param description The description of the rule.
+     * @param ruleAction The closure to execute to apply the rule.
+     * @return The added rule.
+     */
+    Rule addRule(String description, Closure ruleAction);
+
+    /**
+     * Returns the rules used by this collection.
+     *
+     * @return The rules, in the order they will be applied.
+     */
+    List<Rule> getRules();
+
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectCollection<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectCollection<T> matching(Closure spec);
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
index 885155c..5c737f3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
@@ -16,38 +16,49 @@
 package org.gradle.api;
 
 import groovy.lang.Closure;
-
-import java.util.List;
+import org.gradle.util.Configurable;
 
 /**
- * A {@code DomainObjectContainer} represents a mutable collection of domain objects of type {@code T}.
- *
+ * <p>A named domain object container is a specialisation of {@link NamedDomainObjectSet} that adds the ability to create
+ * instances of the element type.</p>
+ * 
+ * <p>Implementations may use different strategies for creating new object instances.</p>
+ * 
+ * <p>Note that a container is an implementation of {@link java.util.SortedSet}, which means that the container is guaranteed
+ * to only contain elements with unique names within this container. Furthermore, items are ordered by their name.</p>
+ * 
  * @param <T> The type of domain objects in this container.
+ * @see NamedDomainObjectSet
  */
-public interface NamedDomainObjectContainer<T> extends NamedDomainObjectCollection<T> {
+public interface NamedDomainObjectContainer<T> extends NamedDomainObjectSet<T>, Configurable<NamedDomainObjectContainer<T>> {
 
     /**
-     * Adds a rule to this container. The given rule is invoked when an unknown object is requested by name.
+     * Creates a new item with the given name, adding it to this container.
      *
-     * @param rule The rule to add.
-     * @return The added rule.
+     * @param name The name to assign to the created object
+     * @return The created object. Never null.
+     * @throws InvalidUserDataException if an object with the given name already exists in this container.
      */
-    Rule addRule(Rule rule);
+    T create(String name) throws InvalidUserDataException;
 
     /**
-     * Adds a rule to this container. The given closure is executed when an unknown object is requested by name. The
-     * requested name is passed to the closure as a parameter.
+     * Creates a new item with the given name, adding it to this container, then configuring it with the given closure.
      *
-     * @param description The description of the rule.
-     * @param ruleAction The closure to execute to apply the rule.
-     * @return The added rule.
+     * @param name The name to assign to the created object
+     * @param configureClosure The closure to configure the created object with
+     * @return The created object. Never null.
+     * @throws InvalidUserDataException if an object with the given name already exists in this container.
      */
-    Rule addRule(String description, Closure ruleAction);
+    T create(String name, Closure configureClosure) throws InvalidUserDataException;
 
     /**
-     * Returns the rules used by this container.
-     *
-     * @return The rules, in the order they will be applied.
+     * <p>Allows the container to be configured, creating missing objects as they are referenced.</p>
+     * 
+     * <p>TODO: example usage</p>
+     * 
+     * @param configureClosure The closure to configure this container with
+     * @return This.
      */
-    List<Rule> getRules();
+    NamedDomainObjectContainer<T> configure(Closure configureClosure);
+    
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectList.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectList.java
new file mode 100644
index 0000000..39e0ca5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectList.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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 groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+
+import java.util.List;
+
+/**
+ * <p>A specialisation of {@link org.gradle.api.NamedDomainObjectCollection} that also implements {@link java.util.List}.</p>
+ *
+ * <p>All object equality is determined in terms of object names. That is, calling {@code remove()} with an object that is NOT equal to
+ * an existing object in terms of {@code equals}, but IS in terms of name equality will result in the existing collection item with
+ * the equal name being removed.</p>
+ *
+ * @param <T> The type of element in the set
+ */
+public interface NamedDomainObjectList<T> extends NamedDomainObjectCollection<T>, List<T> {
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> NamedDomainObjectList<S> withType(Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectList<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectList<T> matching(Closure spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    List<T> findAll(Closure spec);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectSet.java
new file mode 100755
index 0000000..5563bf0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectSet.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+import java.util.Set;
+
+/**
+ * <p>A specialisation of {@link NamedDomainObjectCollection} that also implements {@link Set} and orders objects by their inherent name.</p>
+ * 
+ * <p>All object equality is determined in terms of object names. That is, calling {@code remove()} with an object that is NOT equal to
+ * an existing object in terms of {@code equals}, but IS in terms of name equality will result in the existing collection item with
+ * the equal name being removed.</p>
+ * 
+ * @param <T> The type of element in the set
+ */
+public interface NamedDomainObjectSet<T> extends NamedDomainObjectCollection<T>, Set<T> {
+
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> NamedDomainObjectSet<S> withType(Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectSet<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectSet<T> matching(Closure spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    Set<T> findAll(Closure spec);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Namer.java b/subprojects/core/src/main/groovy/org/gradle/api/Namer.java
new file mode 100644
index 0000000..d022a51
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Namer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * A namer is capable of providing a name based on some inherent characteristic of an object.
+ *
+ * @param <T> The type of object that the namer can name
+ */
+public interface Namer<T> {
+
+    /**
+     * Determines the name of the given object.
+     *
+     * @param object The object to determine the name of
+     * @return The object's inherent name. Never null.
+     * @throws RuntimeException If the name cannot be determined or is null
+     */
+    String determineName(T object);
+
+    /**
+     * A comparator implementation based on the names returned by the given namer.
+     */
+    static class Comparator<T> implements java.util.Comparator<T> {
+
+        private final Namer<? super T> namer;
+
+        public Comparator(Namer<? super T> namer) {
+            this.namer = namer;
+        }
+
+        public int compare(T o1, T o2) {
+            return namer.determineName(o1).compareTo(namer.determineName(o2));
+        }
+
+        public boolean equals(Object obj) {
+            return getClass().equals(obj.getClass()) && namer.equals(((Comparator)obj).namer);
+        }
+
+        public int hashCode() {
+            return 31 * namer.hashCode();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Nullable.java b/subprojects/core/src/main/groovy/org/gradle/api/Nullable.java
new file mode 100644
index 0000000..7283320
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Nullable.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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 java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that the value of an element can be null.
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface Nullable {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Project.java b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
index 7a21a64..e467507 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -18,19 +18,23 @@ package org.gradle.api;
 
 import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
-import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.dsl.ArtifactHandler;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.ConfigurableFileTree;
 import org.gradle.api.file.CopySpec;
 import org.gradle.api.file.FileTree;
-import org.gradle.api.artifacts.dsl.*;
-import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.invocation.Gradle;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.resources.ResourceHandler;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.process.ExecResult;
@@ -62,8 +66,9 @@ import java.util.Set;
  * <code>Project</code> instances.</li>
  *
  * <li>Finally, evaluate each <code>Project</code> by executing its <code>{@value #DEFAULT_BUILD_FILE}</code> file, if
- * present, against the project. The project are evaulated in breadth-wise order, such that a project is evaulated
- * before its child projects. This order can be overridden by adding an evaluation dependency.</li>
+ * present, against the project. The project are evaluated in breadth-wise order, such that a project is evaluated
+ * before its child projects. This order can be overridden by calling <code>{@link #evaluationDependsOnChildren()}</code> or by adding an
+ * explicit evaluation dependency using <code>{@link #evaluationDependsOn(String)}</code>.</li>
  *
  * </ul>
  *
@@ -90,22 +95,20 @@ import java.util.Set;
  * <p>Projects are arranged into a hierarchy of projects. A project has a name, and a fully qualified path which
  * uniquely identifies it in the hierarchy.</p>
  *
- * <h3>Build scripts</h3>
+ * <a name="properties"/> <h3>Properties</h3>
  *
  * <p>Gradle executes the project's build file against the <code>Project</code> instance to configure the project. Any
- * property or method which your script uses which is not defined in the script is delegated through to the associated
- * <code>Project</code> object.  This means, that you can use any of the methods and properties on the
- * <code>Project</code> interface directly in your script.</p><p>For example:
+ * property or method which your script uses is delegated through to the associated <code>Project</code> object.  This
+ * means, that you can use any of the methods and properties on the <code>Project</code> interface directly in your script.
+ * </p><p>For example:
  * <pre>
  * defaultTasks('some-task')  // Delegates to Project.defaultTasks()
- * reportDir = file('reports') // Delegates to Project.file() and Project.setProperty()
+ * reportsDir = file('reports') // Delegates to Project.file() and the Java Plugin
  * </pre>
  * <p>You can also access the <code>Project</code> instance using the <code>project</code> property. This can make the
  * script clearer in some cases. For example, you could use <code>project.name</code> rather than <code>name</code> to
  * access the project's name.</p>
  *
- * <a name="properties"/> <h4>Dynamic Properties</h4>
- *
  * <p>A project has 5 property 'scopes', which it searches for properties. You can access these properties by name in
  * your build file, or by calling the project's {@link #property(String)} method. The scopes are:</p>
  *
@@ -136,8 +139,33 @@ import java.util.Set;
  * scope it finds the property in.  See {@link #property(String)} for more details.</p>
  *
  * <p>When writing a property, the project searches the above scopes in order, and sets the property in the first scope
- * it finds the property in. If not found, the project adds the property to its map of additional properties. See {@link
- * #setProperty(String, Object)} for more details.</p>
+ * it finds the property in. If not found, the project adds the property to its map of additional properties.  For the
+ * next few releases a deprecation warning will be issued when trying to set a property that does not exist. Dynamic
+ * properties will eventually be removed entirely, meaning that this will be a fatal error in future versions of Gradle.
+ * See Extra Properties to learn how to add properties dynamically. </p>
+ *
+ * <a name="extraproperties"/> <h4>Extra Properties</h4>
+ *
+ * All extra properties must be created through the "ext" namespace. Once extra properties have been created,
+ * they are available on the owning object (in the below case the Project, Task, and sub-projects respectively) and can
+ * be read and changed. It's only the initial declaration that needs to be done via the namespace.
+ *
+ * <pre>
+ * project.ext.prop1 = "foo"
+ * task doStuff {
+ *     ext.prop2 = "bar"
+ * }
+ * subprojects { ext.${prop3} = false }
+ * </pre>
+ *
+ * Reading extra properties is done through the "ext" or through the owning object.
+ *
+ * <pre>
+ * ext.isSnapshot = version.endsWith("-SNAPSHOT")
+ * if (isSnapshot) {
+ *     // do snapshot stuff
+ * }
+ * </pre>
  *
  * <h4>Dynamic Methods</h4>
  *
@@ -163,7 +191,7 @@ import java.util.Set;
  *
  * @author Hans Dockter
  */
-public interface Project extends Comparable<Project> {
+public interface Project extends Comparable<Project>, ExtensionAware {
     /**
      * The default project build file name.
      */
@@ -183,8 +211,6 @@ public interface Project extends Comparable<Project> {
 
     public static final String SYSTEM_PROP_PREFIX = "systemProp";
 
-    public static final String TMP_DIR_NAME = ".gradle";
-
     public static final String DEFAULT_VERSION = "unspecified";
 
     public static final String DEFAULT_STATUS = "release";
@@ -223,24 +249,6 @@ public interface Project extends Comparable<Project> {
     void setBuildDir(Object path);
 
     /**
-     * <p>Returns the name of the build directory of this project. It is resolved relative to the project directory of
-     * this project to determine the build directory. The default value is {@value #DEFAULT_BUILD_DIR_NAME}.</p>
-     *
-     * @return The build dir name. Never returns null.
-     */
-    @Deprecated
-    String getBuildDirName();
-
-    /**
-     * <p>Sets the build directory name of this project.</p>
-     *
-     * @param buildDirName The build dir name. Should not be null.
-     * @deprecated Use {@link #setBuildDir(Object)} instead.
-     */
-    @Deprecated
-    void setBuildDirName(String buildDirName);
-
-    /**
      * <p>Returns the build file Gradle will evaluate against this project object. The default is <code> {@value
      * #DEFAULT_BUILD_FILE}</code>. If an embedded script is provided the build file will be null. </p>
      *
@@ -388,28 +396,6 @@ public interface Project extends Comparable<Project> {
     Set<Project> getSubprojects();
 
     /**
-     * <p>Applies a {@link Plugin} to this project.</p>
-     *
-     * @param pluginId The id of the plugin.
-     * @return This project.
-     * @deprecated You should use the {@link #apply(java.util.Map)} or {@link #apply(groovy.lang.Closure)} method
-     *             instead.
-     */
-    @Deprecated
-    Project usePlugin(String pluginId);
-
-    /**
-     * <p>Applies a {@link Plugin} to this project.</p>
-     *
-     * @param pluginClass The class of the plugin.  This class must implement the {@link Plugin} interface.
-     * @return This project.
-     * @deprecated You should use the {@link #apply(java.util.Map)} or {@link #apply(groovy.lang.Closure)} method
-     *             instead.
-     */
-    @Deprecated
-    Project usePlugin(Class<? extends Plugin> pluginClass);
-
-    /**
      * <p>Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to
      * calling {@link #task(java.util.Map, String)} with an empty options map.</p>
      *
@@ -499,139 +485,6 @@ public interface Project extends Comparable<Project> {
     Task task(String name, Closure configureClosure);
 
     /**
-     * <p>Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to
-     * calling {@link #createTask(java.util.Map, String)} with an empty options map.</p>
-     *
-     * <p>After the task is added to the project, it is made available as a property of the project, so that you can
-     * reference the task by name in your build file.  See <a href="#properties">here</a> for more details</p>
-     *
-     * <p>If a task with the given name already exists in this project, an exception is thrown.</p>
-     *
-     * @param name The name of the task to be created
-     * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exists in this project.
-     * @deprecated You should use {@link #task(String)} instead.
-     */
-    @Deprecated
-    Task createTask(String name) throws InvalidUserDataException;
-
-    /**
-     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
-     * action is passed to the task's {@link Task#doFirst(Action)} method. Calling this method is equivalent to calling
-     * {@link #createTask(java.util.Map, String, Action)} with an empty options map.</p>
-     *
-     * <p>After the task is added to the project, it is made available as a property of the project, so that you can
-     * reference the task by name in your build file.  See <a href="#properties">here</a> for more details</p>
-     *
-     * <p>If a task with the given name already exists in this project, an exception is thrown.</p>
-     *
-     * @param name The name of the task to be created
-     * @param action The action to be passed to the {@link Task#doFirst(Action)} method of the created task.
-     * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exists in this project.
-     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
-     */
-    @Deprecated
-    Task createTask(String name, Action<? super Task> action) throws InvalidUserDataException;
-
-    /**
-     * <p>Creates a {@link Task} with the given name and adds it to this project. A map of creation options can be
-     * passed to this method to control how the task is created. The following options are available:</p>
-     *
-     * <table>
-     *
-     * <tr><th>Option</th><th>Description</th><th>Default Value</th></tr>
-     *
-     * <tr><td><code>{@value org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to
-     * create.</td><td>{@link org.gradle.api.DefaultTask}</td></tr>
-     *
-     * <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace an existing
-     * task?</td><td><code>false</code></td></tr>
-     *
-     * <tr><td><code>{@value org.gradle.api.Task#TASK_DEPENDS_ON}</code></td><td>A task name or set of task names which
-     * this task depends on</td><td><code>[]</code></td></tr>
-     *
-     * </table>
-     *
-     * <p>After the task is added to the project, it is made available as a property of the project, so that you can
-     * reference the task by name in your build file.  See <a href="#properties">here</a> for more details</p>
-     *
-     * <p>If a task with the given name already exists in this project and the <code>override</code> option is not set
-     * to true, an exception is thrown.</p>
-     *
-     * @param args The task creation options.
-     * @param name The name of the task to be created
-     * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exists in this project.
-     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
-     */
-    @Deprecated
-    Task createTask(Map<String, ?> args, String name) throws InvalidUserDataException;
-
-    /**
-     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
-     * action is passed to the task's {@link Task#doFirst(Action)} method. A map of creation options can be passed to
-     * this method to control how the task is created. See {@link #createTask(java.util.Map, String)} for the available
-     * options.</p>
-     *
-     * <p>After the task is added to the project, it is made available as a property of the project, so that you can
-     * reference the task by name in your build file.  See <a href="#properties">here</a> for more details</p>
-     *
-     * <p>If a task with the given name already exists in this project and the <code>override</code> option is not set
-     * to true, an exception is thrown.</p>
-     *
-     * @param args The task creation options.
-     * @param name The name of the task to be created
-     * @param action The action to be passed to the {@link Task#doFirst(Action)} method of the created task.
-     * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exists in this project.
-     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
-     */
-    @Deprecated
-    Task createTask(Map<String, ?> args, String name, Action<? super Task> action) throws InvalidUserDataException;
-
-    /**
-     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
-     * action closure is passed to the task's {@link Task#doFirst(Closure)} method. Calling this method is equivalent to
-     * calling {@link #createTask(java.util.Map, String, Closure)} with an empty options map.</p>
-     *
-     * <p>After the task is added to the project, it is made available as a property of the project, so that you can
-     * reference the task by name in your build file.  See <a href="#properties">here</a> for more details</p>
-     *
-     * <p>If a task with the given name already exists in this project, an exception is thrown.</p>
-     *
-     * @param name The name of the task to be created
-     * @param action The closure to be passed to the {@link Task#doFirst(Closure)} method of the created task.
-     * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exists in this project.
-     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
-     */
-    @Deprecated
-    Task createTask(String name, Closure action);
-
-    /**
-     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
-     * action closure is passed to the task's {@link Task#doFirst(Closure)} method. A map of creation options can be
-     * passed to this method to control how the task is created. See {@link #createTask(java.util.Map, String)} for the
-     * available options.</p>
-     *
-     * <p>After the task is added to the project, it is made available as a property of the project, so that you can
-     * reference the task by name in your build file.  See <a href="#properties">here</a> for more details</p>
-     *
-     * <p>If a task with the given name already exists in this project and the <code>override</code> option is not set
-     * to true, an exception is thrown.</p>
-     *
-     * @param args The task creation options.
-     * @param name The name of the task to be created
-     * @param action The closure to be passed to the {@link Task#doFirst(Closure)} method of the created task.
-     * @return The newly created task object
-     * @throws InvalidUserDataException If a task with the given name already exists in this project.
-     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
-     */
-    @Deprecated
-    Task createTask(Map<String, ?> args, String name, Closure action);
-
-    /**
      * <p>Returns the path of this project.  The path is the fully qualified name of the project.</p>
      *
      * @return The path. Never returns null.
@@ -665,22 +518,26 @@ public interface Project extends Comparable<Project> {
     /**
      * <p>Declares that this project has an execution dependency on the project with the given path.</p>
      *
+     * @deprecated Use {@link Task#dependsOn(Object...)} instead.
      * @param path The path of the project which this project depends on.
      * @throws UnknownProjectException If no project with the given path exists.
      */
+    @Deprecated
     void dependsOn(String path) throws UnknownProjectException;
 
     /**
      * <p>Declares that this project has an execution dependency on the project with the given path.</p>
      *
+     * @deprecated Use {@link Task#dependsOn(Object...)} instead.
      * @param path The path of the project which this project depends on.
      * @param evaluateDependsOnProject If true, adds an evaluation dependency.
      * @throws UnknownProjectException If no project with the given path exists.
      */
+    @Deprecated
     void dependsOn(String path, boolean evaluateDependsOnProject) throws UnknownProjectException;
 
     /**
-     * <p>Declares that this project has an evaulation dependency on the project with the given path.</p>
+     * <p>Declares that this project has an evaluation dependency on the project with the given path.</p>
      *
      * @param path The path of the project which this project depends on.
      * @return The project which this project depends on.
@@ -689,25 +546,39 @@ public interface Project extends Comparable<Project> {
     Project evaluationDependsOn(String path) throws UnknownProjectException;
 
     /**
+     * <p>Declares that this project has an evaluation dependency on each of its child projects.</p>
+     *
+     */
+    void evaluationDependsOnChildren();
+
+    /**
      * <p>Declares that all child projects of this project have an execution dependency on this project.</p>
      *
+     * @deprecated Use {@link Task#dependsOn(Object...)} instead.
      * @return this project.
      */
+    @Deprecated
     Project childrenDependOnMe();
 
     /**
-     * <p>Declares that this project have an execution dependency on each of its child projects.</p>
+     * <p>Declares that this project has an execution dependency on each of its child projects.</p>
      *
+     * @deprecated Use {@link Task#dependsOn(Object...)} instead.
      * @return this project.
      */
+    @Deprecated
     Project dependsOnChildren();
 
     /**
-     * <p>Declares that this project have an execution dependency on each of its child projects.</p>
+     * <p>Declares that this project has an execution dependency on each of its child projects.</p>
      *
+     * @deprecated To definde task dependencies use {@link Task#dependsOn(Object...)} instead.
+     * For declaring evaluation dependencies to child projects, use evaluation dependencies
+     * use {@link #evaluationDependsOnChildren()}.
      * @param evaluateDependsOnProject If true, adds an evaluation dependency.
      * @return this project.
      */
+    @Deprecated
     Project dependsOnChildren(boolean evaluateDependsOnProject);
 
     /**
@@ -770,6 +641,9 @@ public interface Project extends Comparable<Project> {
      *
      * <ul>
      *
+     * <li>A {@link CharSequence} / {@link String}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
+     * that starts with {@code file:} is treated as a file URL.</li>
+     *
      * <li>{@link File}. If the file is an absolute file, it is returned as is. Otherwise, the file's path is
      * interpreted relative to the project directory.</li>
      *
@@ -780,10 +654,8 @@ public interface Project extends Comparable<Project> {
      *
      * <li>{@link java.util.concurrent.Callable}. The callable's return value is resolved recursively.</li>
      *
-     * <li>{@link Object}. The object's {@code toString()} value is interpreted as a path. If the path is a relative
-     * path, the project directory will be used as a base directory. A String starting with {@code file:} is treated as
-     * a file URL.</li>
-     *
+     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link #file(Object)}.
+     * This handling of custom Objects has been deprecated and will be removed in the next version of Gradle.</li>
      * </ul>
      *
      * @param path The object to resolve as a File.
@@ -825,7 +697,7 @@ public interface Project extends Comparable<Project> {
      * <p>Returns a {@link ConfigurableFileCollection} containing the given files. You can pass any of the following
      * types to this method:</p>
      *
-     * <ul> <li>A {@link String}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
+     * <ul> <li>A {@link CharSequence} / {@link String}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
      * that starts with {@code file:} is treated as a file URL.</li>
      *
      * <li>A {@link File}. Interpreted relative to the project directory, as for {@link #file(Object)}.</li>
@@ -846,9 +718,15 @@ public interface Project extends Comparable<Project> {
      * <li>A Closure. May return any of the types listed here. The return value of the closure is recursively converted
      * to files. A {@code null} return value is treated as an empty collection.</li>
      *
-     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link
-     * #file(Object)}.</li> </ul>
+     * <li>A {@link Task}. Converted to the task's output files.</li>
      *
+     * <li>A {@link org.gradle.api.tasks.TaskOutputs}. Converted to the output files the related task.</li>
+     *
+     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link #file(Object)}.
+     * Handling custom Objects has been deprecated and will be removed in the next version of Gradle.</li>
+     *
+     * <li>A Closure. May return any of the types listed here. The return value of the closure is recursively converted
+     * to files. A {@code null} return value is treated as an empty collection.</li></ul>
      * <p>The returned file collection is lazy, so that the paths are evaluated only when the contents of the file
      * collection are queried. The file collection is also live, so that it evaluates the above each time the contents
      * of the collection is queried.</p>
@@ -883,16 +761,45 @@ public interface Project extends Comparable<Project> {
      * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
      * as for {@link #file(Object)}.</p>
      *
+     * <p><b>Note:</b> to use a closure as the baseDir, you must explicitly cast the closure to {@code Object} to force
+     * the use of this method instead of {@link #fileTree(Closure)}. Example:</p>
+     *
+     * <pre>
+     * fileTree((Object){ someDir })
+     * </pre>
+     *
      * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
+     * @deprecated Use {@link #fileTree(Object,Closure)} instead.
      * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
      * @return the file tree. Never returns null.
      */
     ConfigurableFileTree fileTree(Object baseDir);
 
     /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
+     * as for {@link #file(Object)}. The closure will be used to configure the new file tree.
+     * The file tree is passed to the closure as its delegate.  Example:</p>
+     *
+     * <pre>
+     * fileTree('src') {
+     *    exclude '**/.svn/**'
+     * }.copy { into 'dest'}
+     * </pre>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @param configureClosure Closure to configure the {@code ConfigurableFileTree} object.
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure);
+
+    /**
      * <p>Creates a new {@code ConfigurableFileTree} using the provided map of arguments.  The map will be applied as
      * properties on the new file tree.  Example:</p>
      *
@@ -943,16 +850,40 @@ public interface Project extends Comparable<Project> {
      */
     FileTree zipTree(Object zipPath);
 
+
     /**
-     * <p>Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path is
-     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
-     * method to untar a TAR file.</p>
+     * Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path can be:
+     * <ul>
+     *   <li>an instance of {@link org.gradle.api.resources.Resource}</li>
+     *   <li>any other object is evaluated as for {@link #file(Object)}</li>
+     * </ul>
      *
-     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
-     * queried.</p>
+     * queried.
+     * <p>
+     * Unless custom implementation of resources is passed, the tar tree attempts to guess the compression based on the file extension.
+     * <p>
+     * You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to untar a TAR file:
+     *
+     * <pre autoTested=''>
+     * task untar(type: Copy) {
+     *   from tarTree('someCompressedTar.gzip')
+     *
+     *   //tar tree attempts to guess the compression based on the file extension
+     *   //however if you must specify the compression explicitly you can:
+     *   from tarTree(resources.gzip('someTar.ext'))
+     *
+     *   //in case you work with unconventionally compressed tars
+     *   //you can provide your own implementation of a ReadableResource:
+     *   //from tarTree(yourOwnResource as ReadableResource)
+     *
+     *   into 'dest'
+     * }
+     * </pre>
      *
-     * @param tarPath The TAR file. Evaluated as for {@link #file(Object)}.
+     * @param tarPath The TAR file or an instance of {@link org.gradle.api.resources.Resource}.
      * @return the file tree. Never returns null.
      */
     FileTree tarTree(Object tarPath);
@@ -995,16 +926,6 @@ public interface Project extends Comparable<Project> {
      *
      * @param path The path to convert.
      * @return The absolute path.
-     * @deprecated Use {@link #absoluteProjectPath(String)} instead.
-     */
-    @Deprecated
-    String absolutePath(String path);
-
-    /**
-     * <p>Converts a name to an absolute project path, resolving names relative to this project.</p>
-     *
-     * @param path The path to convert.
-     * @return The absolute path.
      */
     String absoluteProjectPath(String path);
 
@@ -1018,7 +939,51 @@ public interface Project extends Comparable<Project> {
 
     /**
      * <p>Returns the <code>AntBuilder</code> for this project. You can use this in your build file to execute ant
-     * tasks.</p>
+     * tasks. See example below.</p>
+     * <pre autoTested=''>
+     * task printChecksum {
+     *   doLast {
+     *     ant {
+     *       //using ant checksum task to store the file checksum in the checksumOut ant property
+     *       checksum(property: 'checksumOut', file: 'someFile.txt')
+     *
+     *       //we can refer to the ant property created by checksum task:
+     *       println "The checksum is: " + checksumOut
+     *     }
+     *
+     *     //we can refer to the ant property later as well:
+     *     println "I just love to print checksums: " + ant.checksumOut
+     *   }
+     * }
+     * </pre>
+     *
+     * Consider following example of ant target:
+     * <pre>
+     * <target name='printChecksum'>
+     *   <checksum property='checksumOut'>
+     *     <fileset dir='.'>
+     *       <include name='agile.txt'/>
+     *     </fileset>
+     *   </checksum>
+     *   <echo>The checksum is: ${checksumOut}</echo>
+     * </target>
+     * </pre>
+     *
+     * Here's how it would look like in gradle. Observe how the ant xml is represented in groovy by the ant builder
+     * <pre autoTested=''>
+     * task printChecksum {
+     *   doLast {
+     *     ant {
+     *       checksum(property: 'checksumOut') {
+     *         fileset(dir: '.') {
+     *           include name: 'agile1.txt'
+     *         }
+     *       }
+     *     }
+     *     logger.lifecycle("The checksum is $ant.checksumOut")
+     *   }
+     * }
+     * </pre>
      *
      * @return The <code>AntBuilder</code> for this project. Never returns null.
      */
@@ -1036,7 +1001,7 @@ public interface Project extends Comparable<Project> {
     /**
      * <p>Executes the given closure against the <code>AntBuilder</code> for this project. You can use this in your
      * build file to execute ant tasks. The <code>AntBuild</code> is passed to the closure as the closure's
-     * delegate.</p>
+     * delegate. See example in javadoc for {@link #getAnt()}</p>
      *
      * @param configureClosure The closure to execute against the <code>AntBuilder</code>.
      * @return The <code>AntBuilder</code>. Never returns null.
@@ -1046,6 +1011,8 @@ public interface Project extends Comparable<Project> {
     /**
      * Returns the configurations of this project.
      *
+     * <h3>Examples:</h3> See docs for {@link ConfigurationContainer}
+     *
      * @return The configuration of this project.
      */
     ConfigurationContainer getConfigurations();
@@ -1053,8 +1020,10 @@ public interface Project extends Comparable<Project> {
     /**
      * <p>Configures the dependency configurations for this project.
      *
-     * <p>This method executes the given closure against the {@link ConfigurationContainer} for this project. The {@link
-     * ConfigurationContainer} is passed to the closure as the closure's delegate.
+     * <p>This method executes the given closure against the {@link ConfigurationContainer}
+     * for this project. The {@link ConfigurationContainer} is passed to the closure as the closure's delegate.
+     *
+     * <h3>Examples:</h3> See docs for {@link ConfigurationContainer}
      *
      * @param configureClosure the closure to use to configure the dependency configurations.
      */
@@ -1062,6 +1031,7 @@ public interface Project extends Comparable<Project> {
 
     /**
      * Returns a handler for assigning artifacts produced by the project to configurations.
+     * <h3>Examples:</h3>See docs for {@link ArtifactHandler}
      */
     ArtifactHandler getArtifacts();
 
@@ -1071,6 +1041,24 @@ public interface Project extends Comparable<Project> {
      * <p>This method executes the given closure against the {@link ArtifactHandler} for this project. The {@link
      * ArtifactHandler} is passed to the closure as the closure's delegate.
      *
+     * <p>Example:
+     * <pre autoTested=''>
+     * configurations {
+     *   //declaring new configuration that will be used to associate with artifacts
+     *   schema
+     * }
+     *
+     * task schemaJar(type: Jar) {
+     *   //some imaginary task that creates a jar artifact with the schema
+     * }
+     *
+     * //associating the task that produces the artifact with the configuration
+     * artifacts {
+     *   //configuration name and the task:
+     *   schema schemaJar
+     * }
+     * </pre>
+     *
      * @param configureClosure the closure to use to configure the published artifacts.
      */
     void artifacts(Closure configureClosure);
@@ -1170,7 +1158,7 @@ public interface Project extends Comparable<Project> {
     /**
      * <p>Adds a closure to be called immediately after this project has been evaluated. The project is passed to the
      * closure as a parameter. Such a listener gets notified when the build file belonging to this project has been
-     * executed. A parent project may for example add such a listener to its child project. Such a listener can futher
+     * executed. A parent project may for example add such a listener to its child project. Such a listener can further
      * configure those child projects based on the state of the child projects after their build files have been
      * run.</p>
      *
@@ -1246,30 +1234,6 @@ public interface Project extends Comparable<Project> {
     LoggingManager getLogging();
 
     /**
-     * Disables redirection of standard output during project evaluation. By default redirection is enabled.
-     *
-     * @see #captureStandardOutput(org.gradle.api.logging.LogLevel)
-     */
-    @Deprecated
-    void disableStandardOutputCapture();
-
-    /**
-     * <p>Starts redirection of standard output during to the logging system during project evaluation. By default
-     * redirection is enabled and the output is redirected to the QUIET level. System.err is always redirected to the
-     * ERROR level. Redirection of output at execution time can be configured via the tasks.</p>
-     *
-     * <p>In a multi-project this is a per-project setting.</p>
-     *
-     * @param level The level standard out should be logged to.
-     * @see #disableStandardOutputCapture()
-     * @see Task#captureStandardOutput(org.gradle.api.logging.LogLevel)
-     * @see org.gradle.api.Task#disableStandardOutputCapture()
-     * @deprecated Use the {@link org.gradle.api.logging.LoggingManager} returned by {@link #getLogging()} instead
-     */
-    @Deprecated
-    void captureStandardOutput(LogLevel level);
-
-    /**
      * <p>Configures an object via a closure, with the closure's delegate set to the supplied object. This way you don't
      * have to specify the context of a configuration statement multiple times. <p/> Instead of:</p>
      * <pre>
@@ -1335,17 +1299,12 @@ public interface Project extends Comparable<Project> {
     void repositories(Closure configureClosure);
 
     /**
-     * Creates a new repository handler. <p/> Each repository handler is a factory and container for repositories. For
-     * example each instance of an upload task has its own repository handler.
-     *
-     * @return a new repository handler
-     */
-    RepositoryHandler createRepositoryHandler();
-
-    /**
      * Returns the dependency handler of this project. The returned dependency handler instance can be used for adding
      * new dependencies. For accessing already declared dependencies, the configurations can be used.
      *
+     * <h3>Examples:</h3>
+     * See docs for {@link DependencyHandler}
+     *
      * @return the dependency handler. Never returns null.
      * @see #getConfigurations()
      */
@@ -1357,6 +1316,9 @@ public interface Project extends Comparable<Project> {
      * <p>This method executes the given closure against the {@link DependencyHandler} for this project. The {@link
      * DependencyHandler} is passed to the closure as the closure's delegate.
      *
+     * <h3>Examples:</h3>
+     * See docs for {@link DependencyHandler}
+     *
      * @param configureClosure the closure to use to configure the dependencies.
      */
     void dependencies(Closure configureClosure);
@@ -1458,7 +1420,9 @@ public interface Project extends Comparable<Project> {
     ProjectState getState();
 
     /**
-     * Creates a container for managing named objects of the specified type. The specified type must have a public constructor which takes the name as a String parameter.
+     * <p>Creates a container for managing named objects of the specified type. The specified type must have a public constructor which takes the name as a String parameter.<p>
+     *
+     * <p>All objects <b>MUST</b> expose their name as a bean property named "name". The name must be constant for the life of the object.</p>
      *
      * @param type The type of objects for the container to contain.
      * @param <T> The type of objects for the container to contain.
@@ -1467,7 +1431,9 @@ public interface Project extends Comparable<Project> {
     <T> NamedDomainObjectContainer<T> container(Class<T> type);
 
     /**
-     * Creates a container for managing named objects of the specified type. The given factory is used to create object instances.
+     * <p>Creates a container for managing named objects of the specified type. The given factory is used to create object instances.</p>
+     *
+     * <p>All objects <b>MUST</b> expose their name as a bean property named "name". The name must be constant for the life of the object.</p>
      *
      * @param type The type of objects for the container to contain.
      * @param factory The factory to use to create object instances.
@@ -1477,8 +1443,10 @@ public interface Project extends Comparable<Project> {
     <T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory);
 
     /**
-     * Creates a container for managing named objects of the specified type. The given closure is used to create object instances. The name of the instance to be created is passed as a parameter to
-     * the closure.
+     * <p>Creates a container for managing named objects of the specified type. The given closure is used to create object instances. The name of the instance to be created is passed as a parameter to
+     * the closure.</p>
+     *
+     * <p>All objects <b>MUST</b> expose their name as a bean property named "name". The name must be constant for the life of the object.</p>
      *
      * @param type The type of objects for the container to contain.
      * @param factoryClosure The closure to use to create object instances.
@@ -1486,4 +1454,18 @@ public interface Project extends Comparable<Project> {
      * @return The container.
      */
     <T> NamedDomainObjectContainer<T> container(Class<T> type, Closure factoryClosure);
+
+    /**
+     * Allows adding DSL extensions to the project. Useful for plugin authors.
+     *
+     * @return Returned instance allows adding DSL extensions to the project
+     */
+    ExtensionContainer getExtensions();
+
+    /**
+     * Provides access to resource-specific utility methods, for example factory methods that create various resources.
+     *
+     * @return Returned instance contains various resource-specific utility methods.
+     */
+    ResourceHandler getResources();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Script.java b/subprojects/core/src/main/groovy/org/gradle/api/Script.java
old mode 100644
new mode 100755
index 9243a89..c03b30e
--- a/subprojects/core/src/main/groovy/org/gradle/api/Script.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Script.java
@@ -1,330 +1,369 @@
-/*
- * 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 groovy.lang.Closure;
-import org.gradle.api.file.ConfigurableFileCollection;
-import org.gradle.api.file.ConfigurableFileTree;
-import org.gradle.api.file.CopySpec;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.initialization.dsl.ScriptHandler;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.LoggingManager;
-import org.gradle.api.tasks.WorkResult;
-import org.gradle.process.ExecResult;
-
-import java.io.File;
-import java.net.URI;
-import java.util.Map;
-
-/**
- * <p>This interface is implemented by all Gradle scripts to add in some Gradle-specific methods. As your compiled
- * script class will implement this interface, you can use the methods and properties declared by this interface
- * directly in your script.</p>
- *
- * <p>Generally, a {@code Script} object will have a delegate object attached to it. For example, a build script will
- * have a {@link Project} instance attached to it, and an initialization script will have a {@link
- * org.gradle.api.invocation.Gradle} instance attached to it. Any property reference or method call which is not found
- * on this {@code Script} object is forwarded to the delegate object.</p>
- */
-public interface Script {
-    /**
-     * <p>Configures the delegate object for this script using plugins or scripts.
-     *
-     * <p>The given closure is used to configure an {@link org.gradle.api.plugins.ObjectConfigurationAction} which is
-     * then used to configure the delegate object.</p>
-     *
-     * @param closure The closure to configure the {@code ObjectConfigurationAction}.
-     */
-    void apply(Closure closure);
-
-    /**
-     * <p>Configures the delegate object for this script using plugins or scripts.
-     *
-     * <p>The following options are available:</p>
-     *
-     * <ul><li>{@code from}: A script to apply to the delegate object. Accepts any path supported by {@link
-     * #uri(Object)}.</li>
-     *
-     * <li>{@code plugin}: The id or implementation class of the plugin to apply to the delegate object.</li>
-     *
-     * <li>{@code to}: The target delegate object or objects.</li></ul> <p/> <p>For more detail, see {@link
-     * org.gradle.api.plugins.ObjectConfigurationAction}.</p>
-     *
-     * @param options The options to use to configure the {@code ObjectConfigurationAction}.
-     */
-    void apply(Map<String, ?> options);
-
-    /**
-     * Returns the script handler for this script. You can use this handler to manage the classpath used to compile and
-     * execute this script.
-     *
-     * @return the classpath handler. Never returns null.
-     */
-    ScriptHandler getBuildscript();
-
-    /**
-     * Configures the classpath for this script.
-     *
-     * <p>The given closure is executed against this script's {@link ScriptHandler}. The {@link ScriptHandler} is passed
-     * to the closure as the closure's delegate.
-     *
-     * @param configureClosure the closure to use to configure the script classpath.
-     */
-    void buildscript(Closure configureClosure);
-
-    /**
-     * <p>Resolves a file path relative to the directory containing this script. This works as described for {@link
-     * Project#file(Object)}</p>
-     *
-     * @param path The object to resolve as a File.
-     * @return The resolved file. Never returns null.
-     */
-    File file(Object path);
-
-    /**
-     * <p>Resolves a file path relative to the directory containing this script and validates it using the given scheme.
-     * See {@link PathValidation} for the list of possible validations.</p>
-     *
-     * @param path An object to resolve as a File.
-     * @param validation The validation to perform on the file.
-     * @return The resolved file. Never returns null.
-     * @throws InvalidUserDataException When the file does not meet the given validation constraint.
-     */
-    File file(Object path, PathValidation validation) throws InvalidUserDataException;
-
-    /**
-     * <p>Resolves a file path to a URI, relative to the directory containing this script. Evaluates the provided path
-     * object as described for {@link #file(Object)}, with the exception that any URI scheme is supported, not just
-     * 'file:' URIs.</p>
-     *
-     * @param path The object to resolve as a URI.
-     * @return The resolved URI. Never returns null.
-     */
-    URI uri(Object path);
-
-    /**
-     * <p>Returns a {@link ConfigurableFileCollection} containing the given files. This works as described for {@link
-     * Project#files(Object...)}. Relative paths are resolved relative to the directory containing this script.</p>
-     *
-     * @param paths The paths to the files. May be empty.
-     * @return The file collection. Never returns null.
-     */
-    ConfigurableFileCollection files(Object... paths);
-
-    /**
-     * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The file collection is configured
-     * using the given closure. This method works as described for {@link Project#files(Object, groovy.lang.Closure)}.
-     * Relative paths are resolved relative to the directory containing this script.</p>
-     *
-     * @param paths The contents of the file collection. Evaluated as for {@link #files(Object...)}.
-     * @param configureClosure The closure to use to configure the file collection.
-     * @return the configured file tree. Never returns null.
-     */
-    ConfigurableFileCollection files(Object paths, Closure configureClosure);
-
-    /**
-     * <p>Returns the relative path from the directory containing this script to the given path. The given path object
-     * is (logically) resolved as described for {@link #file(Object)}, from which a relative path is calculated.</p>
-     *
-     * @param path The path to convert to a relative path.
-     * @return The relative path. Never returns null.
-     */
-    String relativePath(Object path);
-
-    /**
-     * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
-     * as for {@link #file(Object)}.</p>
-     *
-     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
-     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
-     * queried.</p>
-     *
-     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
-     * @return the file tree. Never returns null.
-     */
-    ConfigurableFileTree fileTree(Object baseDir);
-
-    /**
-     * <p>Creates a new {@code ConfigurableFileTree} using the provided map of arguments.  The map will be applied as
-     * properties on the new file tree.  Example:</p>
-     * <pre>
-     * fileTree(dir:'src', excludes:['**/ignore/**','**/.svn/**'])
-     * </pre>
-     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
-     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
-     * queried.</p>
-     *
-     * @param args map of property assignments to {@code ConfigurableFileTree} object
-     * @return the configured file tree. Never returns null.
-     */
-    ConfigurableFileTree fileTree(Map<String, ?> args);
-
-    /**
-     * <p>Creates a new {@code ConfigurableFileTree} using the provided closure.  The closure will be used to configure
-     * the new file tree. The file tree is passed to the closure as its delegate.  Example:</p>
-     * <pre>
-     * fileTree {
-     *    from 'src'
-     *    exclude '**/.svn/**'
-     * }.copy { into 'dest'}
-     * </pre>
-     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
-     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
-     * queried.</p>
-     *
-     * @param closure Closure to configure the {@code ConfigurableFileTree} object
-     * @return the configured file tree. Never returns null.
-     */
-    ConfigurableFileTree fileTree(Closure closure);
-
-    /**
-     * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is
-     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
-     * method to unzip a ZIP file.</p>
-     *
-     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
-     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
-     * queried.</p>
-     *
-     * @param zipPath The ZIP file. Evaluated as for {@link #file(Object)}.
-     * @return the file tree. Never returns null.
-     */
-    FileTree zipTree(Object zipPath);
-
-    /**
-     * <p>Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path is
-     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
-     * method to untar a TAR file.</p>
-     *
-     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
-     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
-     * queried.</p>
-     *
-     * @param tarPath The TAR file. Evaluated as for {@link #file(Object)}.
-     * @return the file tree. Never returns null.
-     */
-    FileTree tarTree(Object tarPath);
-
-    /**
-     * Copy the specified files.  The given closure is used to configure a {@link org.gradle.api.file.CopySpec}, which
-     * is then used to copy the files. Example:
-     * <pre>
-     * copy {
-     *    from configurations.runtime
-     *    into 'build/deploy/lib'
-     * }
-     * </pre>
-     * Note that CopySpecs can be nested:
-     * <pre>
-     * copy {
-     *    into 'build/webroot'
-     *    exclude '**/.svn/**'
-     *    from('src/main/webapp') {
-     *       include '**/*.jsp'
-     *       filter(ReplaceTokens, tokens:[copyright:'2009', version:'2.3.1'])
-     *    }
-     *    from('src/main/js') {
-     *       include '**/*.js'
-     *    }
-     * }
-     * </pre>
-     *
-     * @param closure Closure to configure the CopySpec
-     * @return {@link org.gradle.api.tasks.WorkResult} that can be used to check if the copy did any work.
-     */
-    WorkResult copy(Closure closure);
-
-    /**
-     * Creates a {@link org.gradle.api.file.CopySpec} which can later be used to copy files or create an archive. The
-     * given closure is used to configure the {@link org.gradle.api.file.CopySpec} before it is returned by this
-     * method.
-     *
-     * @param closure Closure to configure the CopySpec
-     * @return The CopySpec
-     */
-    CopySpec copySpec(Closure closure);
-
-    /**
-     * Creates a directory and returns a file pointing to it.
-     *
-     * @param path The path for the directory to be created. Evaluated as for {@link #file(Object)}.
-     * @return the created directory
-     * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file.
-     */
-    File mkdir(Object path);
-
-    /**
-     * Deletes files and directories.
-     *
-     * @param paths Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
-     * @return true if anything got deleted, false otherwise
-     */
-    boolean delete(Object... paths);
-
-    /**
-     * Executes a Java main class. The closure configures a {@link org.gradle.process.JavaExecSpec}.
-     *
-     * @param closure The closure for configuring the execution.
-     * @return the result of the execution
-     */
-    ExecResult javaexec(Closure closure);
-
-    /**
-     * Executes an external command. The closure configures a {@link org.gradle.process.ExecSpec}.
-     *
-     * @param closure The closure for configuring the execution.
-     * @return the result of the execution
-     */
-    ExecResult exec(Closure closure);
-
-    /**
-     * Returns the {@link org.gradle.api.logging.LoggingManager} which can be used to control the logging level and
-     * standard output/error capture for this script. By default, System.out is redirected to the Gradle logging system
-     * at the QUIET log level, and System.err is redirected at the ERROR log level.
-     *
-     * @return the LoggingManager. Never returns null.
-     */
-    LoggingManager getLogging();
-
-    /**
-     * Disables redirection of standard output during script execution. By default redirection is enabled.
-     *
-     * @see #captureStandardOutput(org.gradle.api.logging.LogLevel)
-     */
-    @Deprecated
-    void disableStandardOutputCapture();
-
-    /**
-     * <p>Starts redirection of standard output during to the logging system during script execution. By default
-     * redirection is enabled and the output is redirected to the QUIET level. System.err is always redirected to the
-     * ERROR level.</p>
-     *
-     * @param level The level standard out should be logged to.
-     * @see #disableStandardOutputCapture()
-     * @deprecated Use the {@link org.gradle.api.logging.LoggingManager} returned by {@link #getLogging()} instead
-     */
-    @Deprecated
-    void captureStandardOutput(LogLevel level);
-
-    /**
-     * Returns the logger for this script. You can use this in your script to write log messages.
-     *
-     * @return The logger. Never returns null.
-     */
-    Logger getLogger();
-}
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.resources.ResourceHandler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.process.ExecResult;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * <p>This interface is implemented by all Gradle scripts to add in some Gradle-specific methods. As your compiled
+ * script class will implement this interface, you can use the methods and properties declared by this interface
+ * directly in your script.</p>
+ *
+ * <p>Generally, a {@code Script} object will have a delegate object attached to it. For example, a build script will
+ * have a {@link Project} instance attached to it, and an initialization script will have a {@link
+ * org.gradle.api.invocation.Gradle} instance attached to it. Any property reference or method call which is not found
+ * on this {@code Script} object is forwarded to the delegate object.</p>
+ */
+public interface Script {
+    /**
+     * <p>Configures the delegate object for this script using plugins or scripts.
+     *
+     * <p>The given closure is used to configure an {@link org.gradle.api.plugins.ObjectConfigurationAction} which is
+     * then used to configure the delegate object.</p>
+     *
+     * @param closure The closure to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Closure closure);
+
+    /**
+     * <p>Configures the delegate object for this script using plugins or scripts.
+     *
+     * <p>The following options are available:</p>
+     *
+     * <ul><li>{@code from}: A script to apply to the delegate object. Accepts any path supported by {@link
+     * #uri(Object)}.</li>
+     *
+     * <li>{@code plugin}: The id or implementation class of the plugin to apply to the delegate object.</li>
+     *
+     * <li>{@code to}: The target delegate object or objects.</li></ul> <p/> <p>For more detail, see {@link
+     * org.gradle.api.plugins.ObjectConfigurationAction}.</p>
+     *
+     * @param options The options to use to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Map<String, ?> options);
+
+    /**
+     * Returns the script handler for this script. You can use this handler to manage the classpath used to compile and
+     * execute this script.
+     *
+     * @return the classpath handler. Never returns null.
+     */
+    ScriptHandler getBuildscript();
+
+    /**
+     * Configures the classpath for this script.
+     *
+     * <p>The given closure is executed against this script's {@link ScriptHandler}. The {@link ScriptHandler} is passed
+     * to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the script classpath.
+     */
+    void buildscript(Closure configureClosure);
+
+    /**
+     * <p>Resolves a file path relative to the directory containing this script. This works as described for {@link
+     * Project#file(Object)}</p>
+     *
+     * @param path The object to resolve as a File.
+     * @return The resolved file. Never returns null.
+     */
+    File file(Object path);
+
+    /**
+     * <p>Resolves a file path relative to the directory containing this script and validates it using the given scheme.
+     * See {@link PathValidation} for the list of possible validations.</p>
+     *
+     * @param path An object to resolve as a File.
+     * @param validation The validation to perform on the file.
+     * @return The resolved file. Never returns null.
+     * @throws InvalidUserDataException When the file does not meet the given validation constraint.
+     */
+    File file(Object path, PathValidation validation) throws InvalidUserDataException;
+
+    /**
+     * <p>Resolves a file path to a URI, relative to the directory containing this script. Evaluates the provided path
+     * object as described for {@link #file(Object)}, with the exception that any URI scheme is supported, not just
+     * 'file:' URIs.</p>
+     *
+     * @param path The object to resolve as a URI.
+     * @return The resolved URI. Never returns null.
+     */
+    URI uri(Object path);
+
+    /**
+     * <p>Returns a {@link ConfigurableFileCollection} containing the given files. This works as described for {@link
+     * Project#files(Object...)}. Relative paths are resolved relative to the directory containing this script.</p>
+     *
+     * @param paths The paths to the files. May be empty.
+     * @return The file collection. Never returns null.
+     */
+    ConfigurableFileCollection files(Object... paths);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The file collection is configured
+     * using the given closure. This method works as described for {@link Project#files(Object, groovy.lang.Closure)}.
+     * Relative paths are resolved relative to the directory containing this script.</p>
+     *
+     * @param paths The contents of the file collection. Evaluated as for {@link #files(Object...)}.
+     * @param configureClosure The closure to use to configure the file collection.
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileCollection files(Object paths, Closure configureClosure);
+
+    /**
+     * <p>Returns the relative path from the directory containing this script to the given path. The given path object
+     * is (logically) resolved as described for {@link #file(Object)}, from which a relative path is calculated.</p>
+     *
+     * @param path The path to convert to a relative path.
+     * @return The relative path. Never returns null.
+     */
+    String relativePath(Object path);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
+     * as for {@link #file(Object)}.</p>
+     * 
+     * <p><b>Note:</b> to use a closure as the baseDir, you must explicitly cast the closure to {@code Object} to force
+     * the use of this method instead of {@link #fileTree(Closure)}. Example:</p>
+     *
+     * <pre>
+     * fileTree((Object){ someDir })
+     * </pre>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Object baseDir);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the provided map of arguments.  The map will be applied as
+     * properties on the new file tree.  Example:</p>
+     * <pre>
+     * fileTree(dir:'src', excludes:['**/ignore/**','**/.svn/**'])
+     * </pre>
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param args map of property assignments to {@code ConfigurableFileTree} object
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Map<String, ?> args);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the provided closure.  The closure will be used to configure
+     * the new file tree. The file tree is passed to the closure as its delegate.  Example:</p>
+     * <pre>
+     * fileTree {
+     *    from 'src'
+     *    exclude '**/.svn/**'
+     * }.copy { into 'dest'}
+     * </pre>
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @deprecated Use {@link #fileTree(Object,Closure)} instead.
+     * @param closure Closure to configure the {@code ConfigurableFileTree} object
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Closure closure);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
+     * as for {@link #file(Object)}. The closure will be used to configure the new file tree. 
+     * The file tree is passed to the closure as its delegate.  Example:</p>
+     *
+     * <pre>
+     * fileTree('src') {
+     *    exclude '**/.svn/**'
+     * }.copy { into 'dest'}
+     * </pre>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @param configureClosure Closure to configure the {@code ConfigurableFileTree} object.
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure);
+
+    /**
+     * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is
+     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to unzip a ZIP file.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param zipPath The ZIP file. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    FileTree zipTree(Object zipPath);
+
+    /**
+     * Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path can be:
+     * <ul>
+     *   <li>an instance of {@link org.gradle.api.resources.Resource}</li>
+     *   <li>any other object is evaluated as for {@link #file(Object)}</li>
+     * </ul>
+     *
+     * The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.
+     * <p>
+     * Unless custom implementation of resources is passed, the tar tree attempts to guess the compression based on the file extension.
+     * <p>
+     * You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to untar a TAR file:
+     *
+     * <pre autoTested=''>
+     * task untar(type: Copy) {
+     *   from tarTree('someCompressedTar.gzip')
+     *
+     *   //tar tree attempts to guess the compression based on the file extension
+     *   //however if you must specify the compression explicitly you can:
+     *   from tarTree(resources.gzip('someTar.ext'))
+     *
+     *   //in case you work with unconventionally compressed tars
+     *   //you can provide your own implementation of a ReadableResource:
+     *   //from tarTree(yourOwnResource as ReadableResource)
+     *
+     *   into 'dest'
+     * }
+     * </pre>
+     *
+     * @param tarPath The TAR file or an instance of {@link org.gradle.api.resources.Resource}.
+     * @return the file tree. Never returns null.
+     */
+    FileTree tarTree(Object tarPath);
+
+    /**
+     * Copy the specified files.  The given closure is used to configure a {@link org.gradle.api.file.CopySpec}, which
+     * is then used to copy the files. Example:
+     * <pre>
+     * copy {
+     *    from configurations.runtime
+     *    into 'build/deploy/lib'
+     * }
+     * </pre>
+     * Note that CopySpecs can be nested:
+     * <pre>
+     * copy {
+     *    into 'build/webroot'
+     *    exclude '**/.svn/**'
+     *    from('src/main/webapp') {
+     *       include '**/*.jsp'
+     *       filter(ReplaceTokens, tokens:[copyright:'2009', version:'2.3.1'])
+     *    }
+     *    from('src/main/js') {
+     *       include '**/*.js'
+     *    }
+     * }
+     * </pre>
+     *
+     * @param closure Closure to configure the CopySpec
+     * @return {@link org.gradle.api.tasks.WorkResult} that can be used to check if the copy did any work.
+     */
+    WorkResult copy(Closure closure);
+
+    /**
+     * Creates a {@link org.gradle.api.file.CopySpec} which can later be used to copy files or create an archive. The
+     * given closure is used to configure the {@link org.gradle.api.file.CopySpec} before it is returned by this
+     * method.
+     *
+     * @param closure Closure to configure the CopySpec
+     * @return The CopySpec
+     */
+    CopySpec copySpec(Closure closure);
+
+    /**
+     * Creates a directory and returns a file pointing to it.
+     *
+     * @param path The path for the directory to be created. Evaluated as for {@link #file(Object)}.
+     * @return the created directory
+     * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file.
+     */
+    File mkdir(Object path);
+
+    /**
+     * Deletes files and directories.
+     *
+     * @param paths Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
+     * @return true if anything got deleted, false otherwise
+     */
+    boolean delete(Object... paths);
+
+    /**
+     * Executes a Java main class. The closure configures a {@link org.gradle.process.JavaExecSpec}.
+     *
+     * @param closure The closure for configuring the execution.
+     * @return the result of the execution
+     */
+    ExecResult javaexec(Closure closure);
+
+    /**
+     * Executes an external command. The closure configures a {@link org.gradle.process.ExecSpec}.
+     *
+     * @param closure The closure for configuring the execution.
+     * @return the result of the execution
+     */
+    ExecResult exec(Closure closure);
+
+    /**
+     * Returns the {@link org.gradle.api.logging.LoggingManager} which can be used to control the logging level and
+     * standard output/error capture for this script. By default, System.out is redirected to the Gradle logging system
+     * at the QUIET log level, and System.err is redirected at the ERROR log level.
+     *
+     * @return the LoggingManager. Never returns null.
+     */
+    LoggingManager getLogging();
+
+    /**
+     * Returns the logger for this script. You can use this in your script to write log messages.
+     *
+     * @return The logger. Never returns null.
+     */
+    Logger getLogger();
+
+    /**
+     * Provides access to resource-specific utility methods, for example factory methods that create various resources.
+     *
+     * @return Returned instance contains various resource-specific utility methods.
+     */
+    ResourceHandler getResources();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/ScriptCompilationException.java b/subprojects/core/src/main/groovy/org/gradle/api/ScriptCompilationException.java
deleted file mode 100644
index 3359d60..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/ScriptCompilationException.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;
-
-import org.gradle.groovy.scripts.ScriptSource;
-
-/**
- * A {@code ScriptCompilationException} is thrown when a script cannot be compiled.
- */
-public class ScriptCompilationException extends GradleScriptException {
-    private final ScriptSource scriptSource;
-    private final Integer lineNumber;
-
-    public ScriptCompilationException(ScriptCompilationException source) {
-        super(source.getMessage(), source.getCause());
-        scriptSource = source.scriptSource;
-        lineNumber = source.lineNumber;
-    }
-
-    public ScriptCompilationException(String message, Throwable cause, ScriptSource scriptSource, Integer lineNumber) {
-        super(message, cause);
-        this.scriptSource = scriptSource;
-        this.lineNumber = lineNumber;
-    }
-
-    public ScriptSource getScriptSource() {
-        return scriptSource;
-    }
-
-    public Integer getLineNumber() {
-        return lineNumber;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Task.java b/subprojects/core/src/main/groovy/org/gradle/api/Task.java
index 6f6aecd..6d24910 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Task.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Task.java
@@ -18,10 +18,10 @@ package org.gradle.api;
 
 import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionAware;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.TaskInputs;
@@ -78,7 +78,7 @@ import java.util.Set;
  *
  * <ul>
  *
- * <li>A {@code String} task path or name. A relative path is interpreted relative to the task's {@link Project}. This
+ * <li>A {@code String}, {@code CharSequence} or {@code groovy.lang.GString} task path or name. A relative path is interpreted relative to the task's {@link Project}. This
  * allows you to refer to tasks in other projects.</li>
  *
  * <li>A {@link Task}.</li>
@@ -96,7 +96,8 @@ import java.util.Set;
  * <li>A {@code Callable}. The {@code call()} method may return any of the types listed here. Its return value is
  * recursively converted to tasks. A {@code null} return value is treated as an empty collection.</li>
  *
- * <li>An {@code Object}. The object's {@code toString()} method is interpreted as a task path or name.</li>
+ * <li>An {@code Object}. The object's {@code toString()} method is interpreted as a task path or name. The support for custom Objects
+ * has been deprecated and will be removed in the next version of Gradle.</li>
  *
  * </ul>
  *
@@ -129,7 +130,7 @@ import java.util.Set;
  *
  * @author Hans Dockter
  */
-public interface Task extends Comparable<Task> {
+public interface Task extends Comparable<Task>, ExtensionAware {
     public static final String TASK_NAME = "name";
 
     public static final String TASK_DESCRIPTION = "description";
@@ -152,6 +153,15 @@ public interface Task extends Comparable<Task> {
     String getName();
 
     /**
+     * A {@link org.gradle.api.Namer} namer for tasks that returns {@link #getName()}.
+     */
+    static class Namer implements org.gradle.api.Namer<Task> {
+        public String determineName(Task c) {
+            return c.getName();
+        }
+    }
+
+    /**
      * <p>Returns the {@link Project} which this task belongs to.</p>
      *
      * @return The project this task belongs to. Never returns null.
@@ -199,7 +209,28 @@ public interface Task extends Comparable<Task> {
      * <p>Adds the given dependencies to this task. See <a href="#dependencies">here</a> for a description of the types
      * of objects which can be used as task dependencies.</p>
      *
-     * @param paths The dependencies to add to this task.
+     * @param paths The dependencies to add to this task. The path can be defined by:
+     * <li>A {@code String}, {@code CharSequence} or {@code groovy.lang.GString} task path or name. A relative path is interpreted relative to the task's {@link Project}. This
+     * allows you to refer to tasks in other projects.</li>
+     *
+     * <li>A {@link Task}.</li>
+     *
+     * <li>A closure. The closure may take a {@code Task} as parameter. It may return any of the types listed here. Its
+     * return value is recursively converted to tasks. A {@code null} return value is treated as an empty collection.</li>
+     *
+     * <li>A {@link TaskDependency} object.</li>
+     *
+     * <li>A {@link Buildable} object.</li>
+     *
+     * <li>A {@code Iterable}, {@code Collection}, {@code Map} or array. May contain any of the types listed here. The elements of the
+     * iterable/collection/map/array are recursively converted to tasks.</li>
+     *
+     * <li>A {@code Callable}. The {@code call()} method may return any of the types listed here. Its return value is
+     * recursively converted to tasks. A {@code null} return value is treated as an empty collection.</li>
+     *
+     * <li>An {@code Object}. The object's {@code toString()} method is interpreted as a task path or name. The support for custom Objects
+     * has been deprecated and will be removed in the next version of Gradle.</li>
+     *
      * @return the task object this method is applied to
      */
     Task dependsOn(Object... paths);
@@ -388,28 +419,6 @@ public interface Task extends Comparable<Task> {
     LoggingManager getLogging();
 
     /**
-     * Disables redirection of standard output during task execution. By default redirection is enabled.
-     *
-     * @return this
-     * @see #captureStandardOutput(org.gradle.api.logging.LogLevel)
-     */
-    @Deprecated
-    Task disableStandardOutputCapture();
-
-    /**
-     * <p>Enables redirection of standard output during task execution to the logging system. By default redirection is
-     * enabled and the task output is redirected to the QUIET level. System.err is always redirected to the ERROR level.
-     * </p>
-     *
-     * @param level The level standard out should be logged to.
-     * @return this
-     * @see #disableStandardOutputCapture()
-     * @deprecated Use the {@link org.gradle.api.logging.LoggingManager} returned by {@link #getLogging()} instead.
-     */
-    @Deprecated
-    Task captureStandardOutput(LogLevel level);
-
-    /**
      * <p>Returns the value of the given property of this task.  This method locates a property as follows:</p>
      *
      * <ol>
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Transformer.java b/subprojects/core/src/main/groovy/org/gradle/api/Transformer.java
index 0956c58..aadb125 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Transformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Transformer.java
@@ -16,16 +16,19 @@
 package org.gradle.api;
 
 /**
- * <p>A {@code Transformer} transforms objects of type T.</p>
+ * <p>A {@code Transformer} transforms objects of type.</p>
+ * 
+ * <p>Implementations are free to return new objects or mutate the incoming value.</p>
  *
- * @param <T> The type of object which this transformer can transform.
+ * @param <R> The type the value is transformed to.
+ * @param <I> The type of the value to be transformed.
  */
-public interface Transformer<T> {
+public interface Transformer<R, I> {
     /**
      * Transforms the given object, and returns the transformed value.
      *
      * @param original The object to transform.
      * @return The transformed object.
      */
-    T transform(T original);
+    R transform(I original);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java b/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java
index 5bbacbf..7527bf6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/UncheckedIOException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2008 the original author or authors.
+ * Copyright 2012 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.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java
new file mode 100644
index 0000000..7f2e961
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/XmlProvider.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import groovy.util.Node;
+import org.w3c.dom.Element;
+
+/**
+ * Provides various ways to access the content of an XML document.
+ *
+ * @author Hans Dockter
+ */
+public interface XmlProvider {
+    /**
+     * Returns the XML document as a {@link StringBuilder}. Changes to the returned instance will be applied to the XML.
+     * The returned instance is only valid until one of the other methods on this interface are called.
+     *
+     * @return A {@code StringBuilder} representation of the XML.
+     */
+    StringBuilder asString();
+
+    /**
+     * Returns the XML document as a Groovy {@link groovy.util.Node}. Changes to the returned instance will be applied
+     * to the XML. The returned instance is only valid until one of the other methods on this interface are called.
+     *
+     * @return A {@code Node} representation of the XML.
+     */
+    Node asNode();
+
+    /**
+     * Returns the XML document as a DOM {@link org.w3c.dom.Element}. Changes to the returned instance will be applied
+     * to the XML. The returned instance is only valid until one of the other methods on this interface are called.
+     *
+     * @return An {@code Element} representation of the XML.
+     */
+    Element asElement();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java
new file mode 100644
index 0000000..85cc620
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactIdentifier.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * The identifier for a module artifact.
+ */
+public interface ArtifactIdentifier {
+    /**
+     * Returns the identifier of the module that owns this artifact.
+     */
+    ModuleVersionIdentifier getModuleVersionIdentifier();
+
+    /**
+     * Returns the name of the dependency artifact.
+     */
+    String getName();
+
+    /**
+     * Returns the type of the dependency artifact. Often the type is the same as the extension,
+     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * <em>ivy</em> and the extension is <em>xml</em>.
+     *
+     * @see #getExtension()
+     */
+    String getType();
+
+    /**
+     * Returns the extension of this dependency artifact. Often the extension is the same as the type,
+     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * <em>ivy</em> and the extension is <em>xml</em>.
+     *
+     * @see #getType()
+     */
+    String getExtension();
+
+    /**
+     * Returns the classifier of this dependency artifact.
+     */
+    String getClassifier();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
new file mode 100644
index 0000000..6296979
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
@@ -0,0 +1,230 @@
+/*
+ * 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.artifacts;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectList;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.util.Configurable;
+
+import java.util.List;
+
+/**
+ * <p>A {@code ResolverContainer} is responsible for managing a set of {@link ArtifactRepository} instances. Repositories are arranged in a sequence.</p>
+ *
+ * <p>You can obtain a {@code ResolverContainer} instance by calling {@link org.gradle.api.Project#getRepositories()} or
+ * using the {@code repositories} property in your build script.</p>
+ *
+ * <p>The resolvers in a container are accessible as read-only properties of the container, using the name of the
+ * resolver as the property name. For example:</p>
+ *
+ * <pre>
+ * resolvers.addLast(name: 'myResolver')
+ * resolvers.myResolver.url = 'some-url'
+ * </pre>
+ *
+ * <p>A dynamic method is added for each resolver which takes a configuration closure. This is equivalent to calling
+ * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
+ *
+ * <pre>
+ * resolvers.addLast(name: 'myResolver')
+ * resolvers.myResolver {
+ *     url 'some-url'
+ * }
+ * </pre>
+ *
+ * @author Hans Dockter
+ */
+public interface ArtifactRepositoryContainer extends NamedDomainObjectList<ArtifactRepository>, Configurable<ArtifactRepositoryContainer> {
+    String DEFAULT_MAVEN_CENTRAL_REPO_NAME = "MavenRepo";
+    String DEFAULT_MAVEN_LOCAL_REPO_NAME = "MavenLocal";
+    String MAVEN_CENTRAL_URL = "http://repo1.maven.org/maven2/";
+    String MAVEN_REPO_PATTERN = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]";
+    String DEFAULT_CACHE_ARTIFACT_PATTERN
+            = "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])";
+    String DEFAULT_CACHE_IVY_PATTERN = "[organisation]/[module](/[branch])/ivy-[revision].xml";
+    String INTERNAL_REPOSITORY_NAME = "internal-repository";
+    String RESOLVER_NAME = "name";
+    String RESOLVER_URL = "url";
+
+    /**
+     * Adds a repository to this container, at the end of the repository sequence.
+     *
+     * @param repository The repository to add.
+     */
+    boolean add(ArtifactRepository repository);
+
+    /**
+     * Adds a repository to this container, at the start of the repository sequence.
+     *
+     * @param repository The repository to add.
+     */
+    void addFirst(ArtifactRepository repository);
+
+    /**
+     * Adds a repository to this container, at the end of the repository sequence.
+     *
+     * @param repository The repository to add.
+     */
+    void addLast(ArtifactRepository repository);
+
+    /**
+     * Adds a repository to this container, at the end of the repository sequence.
+     *
+     * @param resolver The repository to add, represented as an Ivy {@link DependencyResolver}.
+     */
+    boolean add(DependencyResolver resolver);
+
+    /**
+     * Adds a repository to this container, at the end of the repository sequence.
+     *
+     * @param resolver The repository to add, represented as an Ivy {@link DependencyResolver}.
+     * @param configureClosure The closure to use to configure the repository.
+     */
+    boolean add(DependencyResolver resolver, Closure configureClosure);
+
+    /**
+     * Adds a repository to this container, at the end of the repository sequence. The given {@code userDescription} can be
+     * one of:
+     *
+     * <ul>
+     *
+     * <li>A String. This is treated as a URL, and used to create a maven repository.</li>
+     *
+     * <li>A map. This is used to create a maven maven repository. The map must contain an {@value #RESOLVER_NAME} entry and a
+     * {@value #RESOLVER_URL} entry.</li>
+     *
+     * <li>A {@link org.apache.ivy.plugins.resolver.DependencyResolver}.</li>
+     *
+     * <li>A {@link ArtifactRepository}.</li>
+     *
+     * </ul>
+     *
+     * @param userDescription The resolver definition.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @deprecated Use {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(groovy.lang.Closure)} or {@link #add(ArtifactRepository)} instead.
+     */
+    @Deprecated
+    DependencyResolver addLast(Object userDescription) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, at the end of the resolver sequence. The resolver is configured using the
+     * given configure closure.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @deprecated Use {@link org.gradle.api.artifacts.dsl.RepositoryHandler#maven(groovy.lang.Closure)} or {@link #add(ArtifactRepository)} instead.
+     */
+    @Deprecated
+    DependencyResolver addLast(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, before the given resolver.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @param nextResolver The existing resolver to add the new resolver before.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws UnknownRepositoryException when the given next resolver does not exist in this container.
+     */
+    DependencyResolver addBefore(Object userDescription, String nextResolver) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, before the given resolver. The resolver is configured using the given
+     * configure closure.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @param nextResolver The existing resolver to add the new resolver before.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws UnknownRepositoryException when the given next resolver does not exist in this container.
+     */
+    DependencyResolver addBefore(Object userDescription, String nextResolver, Closure configureClosure)
+            throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, after the given resolver.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @param previousResolver The existing resolver to add the new resolver after.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws UnknownRepositoryException when the given previous resolver does not exist in this container.
+     */
+    DependencyResolver addAfter(Object userDescription, String previousResolver) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, after the given resolver. The resolver is configured using the given configure
+     * closure.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @param previousResolver The existing resolver to add the new resolver after.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws UnknownRepositoryException when the given previous resolver does not exist in this container.
+     */
+    DependencyResolver addAfter(Object userDescription, String previousResolver, Closure configureClosure)
+            throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, at the start of the resolver sequence.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     */
+    DependencyResolver addFirst(Object userDescription) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, at the start of the resolver sequence. The resolver is configured using the
+     * given configure closure.
+     *
+     * @param userDescription The resolver definition. See {@link #addLast(Object)} for details of this parameter.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     */
+    DependencyResolver addFirst(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * {@inheritDoc}
+     */
+    ArtifactRepository getByName(String name) throws UnknownRepositoryException;
+
+    /**
+     * {@inheritDoc}
+     */
+    ArtifactRepository getByName(String name, Closure configureClosure) throws UnknownRepositoryException;
+
+    /**
+     * {@inheritDoc}
+     */
+    ArtifactRepository getAt(String name) throws UnknownRepositoryException;
+
+    /**
+     * Returns the resolvers in this container, in sequence.
+     *
+     * @return The resolvers in sequence. Returns an empty list if this container is empty.
+     */
+    List<DependencyResolver> getResolvers();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
index b91bfa5..e4869e6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
@@ -26,8 +26,6 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public interface ClientModule extends ExternalDependency {
-    String CLIENT_MODULE_KEY = "org.gradle.clientModule";
-
     /**
      * Add a dependency to the client module. Such a dependency is transitive dependency for the
      * project that has a dependency on the client module.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java
new file mode 100644
index 0000000..60bdd16
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * A {@link PublishArtifact} whose properties can be modified.
+ */
+public interface ConfigurablePublishArtifact extends PublishArtifact {
+    /**
+     * Sets the name of this artifact.
+     *
+     * @param name The name. Should not be null.
+     */
+    void setName(String name);
+
+    /**
+     * Sets the extension of this artifact.
+     *
+     * @param extension The extension. Should not be null.
+     */
+    void setExtension(String extension);
+
+    /**
+     * Sets the type of this artifact.
+     *
+     * @param type The type. Should not be null.
+     */
+    void setType(String type);
+
+    /**
+     * Sets the classifier of this artifact.
+     *
+     * @param classifier The classifier. May be null.
+     */
+    void setClassifier(String classifier);
+
+    /**
+     * Registers some tasks which build this artifact.
+     *
+     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @return this
+     */
+    ConfigurablePublishArtifact builtBy(Object... tasks);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Configuration.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Configuration.java
index f70552f..b6f6700 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Configuration.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/Configuration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 the original author or authors.
+ * Copyright 2011 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.
@@ -16,20 +16,48 @@
 package org.gradle.api.artifacts;
 
 import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 
 import java.io.File;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 /**
- * <p>A {@code Configuration} represents a group of artifacts and their dependencies.</p>
+ * A {@code Configuration} represents a group of artifacts and their dependencies.
+ * Find more information about declaring dependencies to a configuration
+ * or about managing configurations in docs for {@link ConfigurationContainer}
+ * <p>
+ * Configuration is an instance of a {@link FileCollection}
+ * that contains all dependencies (see also {@link #getAllDependencies()}) but not artifacts.
+ * If you want to refer to the artifacts declared in this configuration
+ * please use {@link #getArtifacts()} or {@link #getAllArtifacts()}.
+ * Read more about declaring artifacts in the configuration in docs for {@link org.gradle.api.artifacts.dsl.ArtifactHandler}
+ * <p>
  */
 public interface Configuration extends FileCollection {
+
+    /**
+     * Returns the resolution strategy used by this configuration.
+     * The resolution strategy provides extra details on how to resolve this configuration.
+     * See docs for {@link ResolutionStrategy} for more info and examples.
+     *
+     * @return resolution strategy
+     * @since 1.0-milestone-6
+     */
+    ResolutionStrategy getResolutionStrategy();
+
+    /**
+     * The resolution strategy provides extra details on how to resolve this configuration.
+     * See docs for {@link ResolutionStrategy} for more info and examples.
+     *
+     * @param closure closure applied to the {@link ResolutionStrategy}
+     * @return this configuration instance
+     * @since 1.0-milestone-6
+     */
+    Configuration resolutionStrategy(Closure closure);
+
     /**
      * The states a configuration can be into. A configuration is only mutable as long as it is
      * in the unresolved state.
@@ -40,6 +68,7 @@ public interface Configuration extends FileCollection {
      * Returns the state of the configuration.
      *
      * @see org.gradle.api.artifacts.Configuration.State
+     * @return The state of the configuration
      */
     State getState();
 
@@ -51,6 +80,15 @@ public interface Configuration extends FileCollection {
     String getName();
 
     /**
+     * A {@link org.gradle.api.Namer} namer for configurations that returns {@link #getName()}.
+     */
+    static class Namer implements org.gradle.api.Namer<Configuration> {
+        public String determineName(Configuration c) {
+            return c.getName();
+        }
+    }
+    
+    /**
      * Returns true if this is a visible configuration. A visible configuration is usable outside the project it belongs
      * to. The default value is true.
      *
@@ -156,7 +194,7 @@ public interface Configuration extends FileCollection {
      * @param dependencySpec The spec describing a filter applied to the all the dependencies of this configuration (including dependencies from extended configurations).
      * @return The files of a subset of dependencies of this configuration.
      */
-    Set<File> files(Spec<Dependency> dependencySpec);
+    Set<File> files(Spec<? super Dependency> dependencySpec);
 
     /**
      * Resolves this configuration. This locates and downloads the files which make up this configuration.
@@ -176,7 +214,7 @@ public interface Configuration extends FileCollection {
      * @param dependencySpec The spec describing a filter applied to the all the dependencies of this configuration (including dependencies from extended configurations).
      * @return The FileCollection with a subset of dependencies of this configuration.
      */
-    FileCollection fileCollection(Spec<Dependency> dependencySpec);
+    FileCollection fileCollection(Spec<? super Dependency> dependencySpec);
 
     /**
      * Takes a closure which gets coerced into a Spec. Behaves otherwise in the same way as
@@ -210,6 +248,7 @@ public interface Configuration extends FileCollection {
      * Returns the name of the task that upload the artifacts of this configuration to repositories
      * declared by the user.
      *
+     * @return The name of the associated upload task
      * @see org.gradle.api.tasks.Upload
      */
     String getUploadTaskName();
@@ -236,90 +275,40 @@ public interface Configuration extends FileCollection {
     TaskDependency getTaskDependencyFromProjectDependency(boolean useDependedOn, final String taskName);
 
     /**
-     * Returns a {@code TaskDependency} object containing all required dependencies to build the artifacts
-     * belonging to this configuration or to one of its super configurations.
-     *
-     * @return a task dependency object
-     */
-    TaskDependency getBuildArtifacts();
-
-    /**
-     * Publishes the artifacts of this configuration to the specified repositories. This
-     * method is usually used only internally as the users use the associated upload tasks to
-     * upload the artifacts.
-     *
-     * @param publishRepositories The repositories to publish the artifacts to.
-     * @param descriptorDestination The destination dir for the descriptor file (if null no descriptor file is written).
-     *
-     * @see org.gradle.api.tasks.Upload
-     * @see #getUploadTaskName()
-     * @throws PublishException On failure to publish this configuration.
-     */
-    void publish(List<DependencyResolver> publishRepositories, File descriptorDestination) throws PublishException;
-
-    /**
      * Gets the set of dependencies directly contained in this configuration
      * (ignoring superconfigurations).
      *
      * @return the set of dependencies
      */
-    Set<Dependency> getDependencies();
+    DependencySet getDependencies();
 
     /**
-     * Gets the complete set of dependencies including those contributed by
-     * superconfigurations.
+     * <p>Gets the complete set of dependencies including those contributed by
+     * superconfigurations.</p>
      *
-     * @return the set of dependencies
+     * @return the (read-only) set of dependencies
      */
-    Set<Dependency> getAllDependencies();
-
-    /**
-     * Gets the set of dependencies of type T directly contained in this configuration (ignoring superconfigurations).
-     *
-     * @param type the dependency type
-     * @param <T> the dependency type
-     * @return The set. Returns an empty set if there are no such dependencies.
-     */
-    <T extends Dependency> Set<T> getDependencies(Class<T> type);
-
-    /**
-     * Gets the set of dependencies of type T for this configuration including those contributed by superconfigurations.
-     *
-     * @param type the dependency type
-     * @param <T> the dependency type
-     * @return The set. Returns an empty set if there are no such dependencies.
-     */
-    <T extends Dependency> Set<T> getAllDependencies(Class<T> type);
-
-    /**
-     * Adds a dependency to this configuration.
-     *
-     * @param dependency The dependency to be added.
-     */
-    void addDependency(Dependency dependency);
+    DependencySet getAllDependencies();
 
     /**
      * Returns the artifacts of this configuration excluding the artifacts of extended configurations.
+     * 
+     * @return The set.
      */
-    Set<PublishArtifact> getArtifacts();
+    PublishArtifactSet getArtifacts();
 
     /**
      * Returns the artifacts of this configuration including the artifacts of extended configurations.
+     * 
+     * @return The (read-only) set.
      */
-    Set<PublishArtifact> getAllArtifacts();
-
-    /**
-     * Returns the artifacts of this configuration as a {@link FileCollection}, including artifacts of extended
-     * configurations.
-     *
-     * @return the artifact files.
-     */
-    FileCollection getAllArtifactFiles();
+    PublishArtifactSet getAllArtifacts();
 
     /**
      * Returns the exclude rules applied for resolving any dependency of this configuration.
      *
      * @see #exclude(java.util.Map)
+     * @return The exclude rules
      */
     Set<ExcludeRule> getExcludeRules();
 
@@ -335,24 +324,17 @@ public interface Configuration extends FileCollection {
     /**
      * Returns all the configurations belonging to the same configuration container as this
      * configuration (including this configuration).
-     */
-    Set<Configuration> getAll();
-
-    /**
-     * Adds an artifact to be published to this configuration.
      *
-     * @param artifact The artifact.
-     * @return this
+     * @return All of the configurations belong to the configuration container that this set belongs to.
      */
-    Configuration addArtifact(PublishArtifact artifact);
+    Set<Configuration> getAll();
 
     /**
-     * Removes an artifact from the artifacts to be published to this configuration.
+     * Returns the incoming dependencies of this configuration.
      *
-     * @param artifact The artifact.
-     * @return this
+     * @return The incoming dependencies of this configuration. Never null.
      */
-    Configuration removeArtifact(PublishArtifact artifact);
+    ResolvableDependencies getIncoming();
 
     /**
      * Creates a copy of this configuration that only contains the dependencies directly in this configuration
@@ -374,23 +356,21 @@ public interface Configuration extends FileCollection {
 
     /**
      * Creates a copy of this configuration ignoring superconfigurations (see {@link #copy()} but filtering
-     * the dependencies using the specified dependency spec. {@link org.gradle.api.artifacts.specs.Type}
-     * provides some predefined dependency specs.
+     * the dependencies using the specified dependency spec.
      *
      * @param dependencySpec filtering requirements
      * @return copy of this configuration
      */
-    Configuration copy(Spec<Dependency> dependencySpec);
+    Configuration copy(Spec<? super Dependency> dependencySpec);
 
     /**
      * Creates a copy of this configuration with dependencies from superconfigurations (see {@link #copyRecursive()})
-     * but filtering the dependencies using the dependencySpec. {@link org.gradle.api.artifacts.specs.Type}
-     * provides some predefined dependency specs.
+     * but filtering the dependencies using the dependencySpec.
      *
      * @param dependencySpec filtering requirements
      * @return copy of this configuration
      */
-    Configuration copyRecursive(Spec<Dependency> dependencySpec);
+    Configuration copyRecursive(Spec<? super Dependency> dependencySpec);
 
     /**
      * Takes a closure which gets coerced into a Spec. Behaves otherwise in the same way as {@link #copy(org.gradle.api.specs.Spec)}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java
index 9cc20ec..6af7586 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java
@@ -16,17 +16,16 @@
 package org.gradle.api.artifacts;
 
 import groovy.lang.Closure;
-import org.gradle.api.NamedDomainObjectContainer;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.NamedDomainObjectCollection;
+import org.gradle.api.NamedDomainObjectContainer;
 
 /**
- * <p>A {@code ConfigurationContainer} is responsible for managing a set of {@link Configuration} instances.</p>
+ * <p>A {@code ConfigurationContainer} is responsible for declaring and managing configurations. See also {@link Configuration}.</p>
  *
  * <p>You can obtain a {@code ConfigurationContainer} instance by calling {@link org.gradle.api.Project#getConfigurations()},
  * or using the {@code configurations} property in your build script.</p>
  *
- * <p>The configurations in a container are accessable as read-only properties of the container, using the name of the
+ * <p>The configurations in a container are accessible as read-only properties of the container, using the name of the
  * configuration as the property name. For example:</p>
  *
  * <pre>
@@ -44,9 +43,45 @@ import org.gradle.api.NamedDomainObjectCollection;
  * }
  * </pre>
  *
+ * <h2>Examples</h2>
+ *
+ * An example showing how to refer to a given configuration by name
+ * in order to get hold of all dependencies (e.g. jars, but only)
+ * <pre autoTested='true'>
+ *   apply plugin: 'java' //so that I can use 'compile' configuration
+ *
+ *   //copying all dependencies attached to 'compile' into a specific folder
+ *   task copyAllDependencies(type: Copy) {
+ *     //referring to the 'compile' configuration
+ *     from configurations.compile
+ *     into 'allLibs'
+ *   }
+ * </pre>
+ *
+ * An example showing how to declare and configure configurations
+ * <pre autoTested=''>
+ * apply plugin: 'java' //so that I can use 'compile', 'testCompile' configurations
+ *
+ * configurations {
+ *   //adding a configuration:
+ *   myConfiguration
+ *
+ *   //adding a configuration that extends existing configuration:
+ *   //(testCompile was added by the java plugin)
+ *   myIntegrationTestsCompile.extendsFrom(testCompile)
+ *
+ *   //configuring existing configurations not to put transitive dependencies on the compile classpath
+ *   //this way you can avoid issues with implicit dependencies to transitive libraries
+ *   compile.transitive = false
+ *   testCompile.transitive = false
+ * }
+ * </pre>
+ *
+ * Examples on configuring the <b>resolution strategy</b> - see docs for {@link ResolutionStrategy}
+ *
  * @author Hans Dockter
  */
-public interface ConfigurationContainer extends NamedDomainObjectContainer<Configuration>, NamedDomainObjectCollection<Configuration> {
+public interface ConfigurationContainer extends NamedDomainObjectContainer<Configuration> {
     /**
      * {@inheritDoc}
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConflictResolution.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConflictResolution.java
new file mode 100644
index 0000000..2e42d12
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConflictResolution.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * The conflict resolution
+ */
+public interface ConflictResolution {}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolutionListener.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolutionListener.java
new file mode 100644
index 0000000..0720806
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolutionListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * A {@code DependencyResolutionListener} is notified as dependencies are resolved.
+ */
+public interface DependencyResolutionListener {
+    /**
+     * This method is called immediately before a set of dependencies are resolved.
+     *
+     * @param dependencies The set of dependencies to be resolved.
+     */
+    void beforeResolve(ResolvableDependencies dependencies);
+
+    /**
+     * This method is called immediately after a set of dependencies are resolved.
+     *
+     * @param dependencies The set of dependencies resolved.
+     */
+    void afterResolve(ResolvableDependencies dependencies);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySet.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySet.java
new file mode 100644
index 0000000..598ea14
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencySet.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.DomainObjectSet;
+
+/**
+ * A set of artifact dependencies.
+ */
+public interface DependencySet extends DomainObjectSet<Dependency>, Buildable {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
index 91c328d..9262a7a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
@@ -28,12 +28,25 @@ public interface ExcludeRule {
     String MODULE_KEY = "module";
 
     /**
+     * The exact name of the organization or group that should be excluded.
+     * */
+    String getGroup();
+
+    /**
+     * The exact name of the module that should be excluded.
+     */
+    String getModule();
+    
+    /**
      * Returns the arguments of an exclude rule. The possible keys for the map are:
      *
      * <ul>
      * <li><code>group</code> - The exact name of the organization or group that should be excluded.
-     * <li><code>module</code> - The exact name of the module that should be excluded
+     * <li><code>module</code> - The exact name of the module that should be excluded.
      * </ul>
+     * 
+     * @deprecated Use {@link #getGroup()} or {@link #getModule()} instead.
      */
+    @Deprecated
     Map<String, String> getExcludeArgs();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
index 0e027e3..f403492 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
@@ -20,7 +20,7 @@ package org.gradle.api.artifacts;
  *
  * @author Hans Dockter
  */
-public interface ExternalDependency extends ModuleDependency {
+public interface ExternalDependency extends ModuleDependency, ModuleVersionSelector {
     /**
      * Returns whether or not the version of this dependency should be enforced in the case of version conflicts.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
new file mode 100644
index 0000000..ecc6a8e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Resolved configuration that does not fail eagerly when some dependencies are not resolved, or some artifacts do not exist.
+ */
+public interface LenientConfiguration {
+
+    /**
+     * returns successfully resolved dependencies
+     *
+     * @param dependencySpec dependency spec
+     * @return only resolved dependencies
+     */
+    public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec);
+
+    /**
+     * returns dependencies that were attempted to resolve but failed.
+     * If empty then all dependencies are neatly resolved.
+     *
+     * @return only unresolved dependencies
+     */
+    public Set<UnresolvedDependency> getUnresolvedModuleDependencies();
+
+    /**
+     * returns successfully resolved files for successfully resolved dependencies
+     *
+     * @param dependencySpec dependency spec
+     * @return resolved dependencies files
+     */
+    public Set<File> getFiles(Spec<? super Dependency> dependencySpec);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java
index d1cd977..02e31b4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java
@@ -21,15 +21,41 @@ import java.util.Map;
 import java.util.Set;
 
 /**
- * <p>A {@code ModuleDependency} is a {@link org.gradle.api.artifacts.Dependency} on a module outside the current
- * project.</p>
- *
- * <p>A module dependency is an entity. Its key consists of the fields {@code group, name, version, configuration}.</p>
+ * A {@code ModuleDependency} is a {@link org.gradle.api.artifacts.Dependency} on a module outside the current
+ * project.
+ * <p>
+ * For examples on configuring the exclude rules please refer to {@link #exclude(java.util.Map)}.
  */
 public interface ModuleDependency extends Dependency {
     /**
-     * Adds an exclude rule to exclude transitive dependencies of this dependency. You can also add exclude rules
-     * per-configuration. See {@link Configuration#getExcludeRules()}.
+     * Adds an exclude rule to exclude transitive dependencies of this dependency.
+     * <p>
+     * Excluding a particular transitive dependency does not guarantee that it does not show up
+     * in the dependencies of a given configuration.
+     * For example, some other dependency, which does not have any exclude rules,
+     * might pull in exactly the same transitive dependency.
+     * To guarantee that the transitive dependency is excluded from the entire configuration
+     * please use per-configuration exclude rules: {@link Configuration#getExcludeRules()}.
+     * In fact, in majority of cases the actual intention of configuring per-dependency exclusions
+     * is really excluding a dependency from the entire configuration (or classpath).
+     * <p>
+     * If your intention is to exclude a particular transitive dependency
+     * because you don't like the version it pulls in to the configuration
+     * then consider using forced versions' feature: {@link ResolutionStrategy#force(Object...)}.
+     *
+     * <pre autoTested=''>
+     * apply plugin: 'java' //so that I can declare 'compile' dependencies
+     *
+     * dependencies {
+     *   compile('org.hibernate:hibernate:3.1') {
+     *     //excluding a particular transitive dependency:
+     *     exclude module: 'cglib' //by artifact name
+     *     exclude group: 'org.jmock' //by group
+     *     exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
+     *   }
+     * }
+    
+     * </pre>
      *
      * @param excludeProperties the properties to define the exclude rule.
      * @return this
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java
new file mode 100644
index 0000000..b4e6710
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionIdentifier.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * The identifier of a module version.
+ */
+public interface ModuleVersionIdentifier {
+    /**
+     * The group of the module.
+     *
+     * @return module group
+     */
+    String getGroup();
+
+    /**
+     * The name of the module.
+     *
+     * @return module name
+     */
+    String getName();
+
+    /**
+     * The version of the module
+     *
+     * @return module version
+     */
+    String getVersion();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionSelector.java
new file mode 100644
index 0000000..d304428
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ModuleVersionSelector.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * Selects a module version
+ */
+public interface ModuleVersionSelector {
+
+    /**
+     * The group of the module.
+     *
+     * @return module group
+     */
+    String getGroup();
+
+    /**
+     * The name of the module.
+     *
+     * @return module name
+     */
+    String getName();
+
+    /**
+     * The version of the module
+     *
+     * @return module version
+     */
+    String getVersion();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
index 01ddef5..0bc99d4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
@@ -29,34 +29,40 @@ import java.util.Date;
 public interface PublishArtifact extends Buildable {
     /**
      * Returns the name of the artifact.
+     *
+     * @return The name. Never null.
      */
     String getName();
 
     /**
-     * Returns the extension of this published artifact. Often the extendsion is the same as the type,
-     * but sometimes this is not the case. For example for an ivy xml module decsriptor, the type is
+     * Returns the extension of this published artifact. Often the extension is the same as the type,
+     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
-     * @see #getType()
+     * @return The extension. Never null.
      */
     String getExtension();
 
     /**
      * Returns the type of the published artifact. Often the type is the same as the extension,
-     * but sometimes this is not the case. For example for an ivy xml module decsriptor, the type is
+     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
      * <em>ivy</em> and the extension is <em>xml</em>.
      *
-     * @see #getExtension()
+     * @return The type. Never null.
      */
     String getType();
 
     /**
-     * Returns the classifier of this published artifact. 
+     * Returns the classifier of this published artifact, if any.
+     *
+     * @return The classifier. May be null.
      */
     String getClassifier();
 
     /**
      * Returns the file of this artifact.
+     *
+     * @return The file. Never null.
      */
     File getFile();
 
@@ -64,7 +70,9 @@ public interface PublishArtifact extends Buildable {
      * Returns the date that should be used when publishing this artifact. This is used
      * in the module descriptor accompanying this artifact (the ivy.xml). If the date is
      * not specified, the current date is used. If this artifact
-     * is published without an module descriptor, this property has no relevance. 
+     * is published without an module descriptor, this property has no relevance.
+     *
+     * @return The date. May be null.
      */
     Date getDate();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifactSet.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifactSet.java
new file mode 100644
index 0000000..fd84d4e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishArtifactSet.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * A set of artifacts to be published.
+ */
+public interface PublishArtifactSet extends DomainObjectSet<PublishArtifact>, Buildable {
+    FileCollection getFiles();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishInstruction.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishInstruction.java
deleted file mode 100644
index fd4d0bd..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/PublishInstruction.java
+++ /dev/null
@@ -1,99 +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.api.artifacts;
-
-import org.gradle.api.InvalidUserDataException;
-
-import java.io.File;
-
-/**
- * Uploading details for artifacts produced by a project.
- *
- * @author Hans Dockter
- */
-public class PublishInstruction {
-    private boolean uploadDescriptor;
-    private File descriptorDestination;
-
-    /**
-     * Creates a publish instruction for not uploading an module descriptor file.
-     */
-    public PublishInstruction() {
-        uploadDescriptor = false;
-        descriptorDestination = null;
-    }
-
-    /**
-     * Creates a publish instruction. If <code>uploadDescriptor</code> is set to true and the target destination
-     * is an ivy repository, the ivy file destination needs to be specified. 
-     *
-     * @param uploadDescriptor
-     * @param descriptorDestination
-     */
-    public PublishInstruction(boolean uploadDescriptor, File descriptorDestination) {
-        if (uploadDescriptor && descriptorDestination == null) {
-            throw new InvalidUserDataException("You must specify a module descriptor destination, if a module descriptor should be uploaded.");
-        }
-        if (!uploadDescriptor && descriptorDestination != null) {
-            throw new InvalidUserDataException("You must not specify a module descriptor destination, if a module descriptor should not be uploaded.");
-        }
-        this.uploadDescriptor = uploadDescriptor;
-        this.descriptorDestination = descriptorDestination;
-    }
-
-    /**
-     * Returns whether an xml module descriptor file should be uploaded or not. 
-     */
-    public boolean isUploadDescriptor() {
-        return uploadDescriptor;
-    }
-
-    /**
-     * Returns the file destination where to create the ivy.xml file. Can be null.
-     */
-    public File getDescriptorDestination() {
-        return descriptorDestination;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        PublishInstruction that = (PublishInstruction) o;
-
-        if (uploadDescriptor != that.uploadDescriptor) {
-            return false;
-        }
-        if (descriptorDestination != null ? !descriptorDestination.equals(that.descriptorDestination)
-                : that.descriptorDestination != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = uploadDescriptor ? 1 : 0;
-        result = 31 * result + (descriptorDestination != null ? descriptorDestination.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
new file mode 100644
index 0000000..98bbebf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolutionStrategy.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2011 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;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Defines the strategies around dependency resolution.
+ * For example, forcing certain dependency versions, conflict resolutions or snapshot timeouts.
+ * <p>
+ * Examples:
+ * <pre autoTested=''>
+ * configurations.all {
+ *   resolutionStrategy {
+ *     // fail eagerly on version conflict (includes transitive dependencies)
+ *     // e.g. multiple different versions of the same dependency (group and name are equal)
+ *     failOnVersionConflict()
+ *
+ *     // force certain versions of dependencies (including transitive)
+ *     //  *append new forced modules:
+ *     force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4'
+ *     //  *replace existing forced modules with new ones:
+ *     forcedModules = ['asm:asm-all:3.3.1']
+ *
+ *     // cache dynamic versions for 10 minutes
+ *     cacheDynamicVersionsFor 10, 'minutes'
+ *     // don't cache changing modules at all
+ *     cacheChangingModulesFor 0, 'seconds'
+ *   }
+ * }
+ * </pre>
+ *
+ * @since 1.0-milestone-6
+ */
+public interface ResolutionStrategy {
+
+    /**
+     * In case of conflict, Gradle by default uses the newest of conflicting versions.
+     * However, you can change this behavior. Use this method to configure the resolution to fail eagerly on any version conflict, e.g.
+     * multiple different versions of the same dependency (group and name are equal) in the same {@link Configuration}.
+     * The check includes both first level and transitive dependencies. See example below:
+     *
+     * <pre autoTested=''>
+     * configurations.all {
+     *   resolutionStrategy.failOnVersionConflict()
+     * }
+     * </pre>
+     *
+     * @return this resolution strategy instance
+     * @since 1.0-milestone-6
+     */
+    ResolutionStrategy failOnVersionConflict();
+
+    /**
+     * Allows forcing certain versions of dependencies, including transitive dependencies.
+     * <b>Appends</b> new forced modules to be considered when resolving dependencies.
+     * <p>
+     * It accepts following notations:
+     * <ul>
+     *   <li>String in a format of: 'group:name:version', for example: 'org.gradle:gradle-core:1.0'</li>
+     *   <li>instance of {@link ModuleVersionSelector}</li>
+     *   <li>any collection or array of above will be automatically flattened</li>
+     * </ul>
+     * Example:
+     * <pre autoTested=''>
+     * configurations.all {
+     *   resolutionStrategy.force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4'
+     * }
+     * </pre>
+     *
+     * @param forcedModuleNotations typically group:name:version notations to append
+     * @return this ResolutionStrategy instance
+     * @since 1.0-milestone-7
+     */
+    ResolutionStrategy force(Object... forcedModuleNotations);
+
+    /**
+     * Allows forcing certain versions of dependencies, including transitive dependencies.
+     * <b>Replaces</b> existing forced modules with the input.
+     * <p>
+     * For information on notations see {@link #force(Object...)}
+     * <p>
+     * Example:
+     * <pre autoTested=''>
+     * configurations.all {
+     *   resolutionStrategy.forcedModules = ['asm:asm-all:3.3.1', 'commons-io:commons-io:1.4']
+     * }
+     * </pre>
+     *
+     * @param forcedModuleNotations typically group:name:version notations to set
+     * @return this ResolutionStrategy instance
+     * @since 1.0-milestone-7
+     */
+    ResolutionStrategy setForcedModules(Object... forcedModuleNotations);
+
+    /**
+     * Returns currently configured forced modules. For more information on forcing versions see {@link #force(Object...)}
+     *
+     * @return forced modules
+     * @since 1.0-milestone-7
+     */
+    Set<ModuleVersionSelector> getForcedModules();
+
+    /**
+     * Sets the length of time that dynamic versions will be cached, with units expressed as a String.
+     *
+     * <p>A convenience method for {@link #cacheDynamicVersionsFor(int, java.util.concurrent.TimeUnit)} with units expressed as a String.
+     * Units are resolved by calling the {@code valueOf(String)} method of {@link java.util.concurrent.TimeUnit} with the upper-cased string value.</p>
+     * @param value The number of time units
+     * @param units The units
+     * @since 1.0-milestone-6
+     */
+    void cacheDynamicVersionsFor(int value, String units);
+
+    /**
+     * Sets the length of time that dynamic versions will be cached.
+     *
+     * <p>Gradle keeps a cache of dynamic version => resolved version (ie 2.+ => 2.3). By default, these cached values are kept for 24 hours, after which the cached entry is expired
+     * and the dynamic version is resolved again.</p>
+     * <p>Use this method to provide a custom expiry time after which the cached value for any dynamic version will be expired.</p>
+     * @param value The number of time units
+     * @param units The units
+     * @since 1.0-milestone-6
+     */
+    void cacheDynamicVersionsFor(int value, TimeUnit units);
+
+    /**
+     * Sets the length of time that changing modules will be cached, with units expressed as a String.
+     *
+     * <p>A convenience method for {@link #cacheChangingModulesFor(int, java.util.concurrent.TimeUnit)} with units expressed as a String.
+     * Units are resolved by calling the {@code valueOf(String)} method of {@link java.util.concurrent.TimeUnit} with the upper-cased string value.</p>
+     * @param value The number of time units
+     * @param units The units
+     * @since 1.0-milestone-6
+     */
+    void cacheChangingModulesFor(int value, String units);
+
+    /**
+     * Sets the length of time that changing modules will be cached.
+     *
+     * <p>Gradle caches the contents and artifacts of changing modules. By default, these cached values are kept for 24 hours,
+     * after which the cached entry is expired and the module is resolved again.</p>
+     * <p>Use this method to provide a custom expiry time after which the cached entries for any changing module will be expired.</p>
+     * @param value The number of time units
+     * @param units The units
+     * @since 1.0-milestone-6
+     */
+    void cacheChangingModulesFor(int value, TimeUnit units);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
new file mode 100644
index 0000000..041a53a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvableDependencies.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * A set of {@link Dependency} objects which can be resolved to a set of {@code File} instances.
+ */
+public interface ResolvableDependencies {
+    /**
+     * Returns the name of this set.
+     *
+     * @return The name. Never null.
+     */
+    String getName();
+
+    /**
+     * Returns the path of this set. This is a unique identifier for this set.
+     *
+     * @return The path. Never null.
+     */
+    String getPath();
+
+    /**
+     * Returns a {@link FileCollection} which contains the resolved set of files. The returned value is lazy, so dependency resolution is not performed until the contents of the
+     * collection are queried.
+     *
+     * @return The collection. Never null.
+     */
+    FileCollection getFiles();
+
+    /**
+     * Returns the set of dependencies which will be resolved.
+     *
+     * @return the dependencies. Never null.
+     */
+    DependencySet getDependencies();
+
+    /**
+     * Adds an action to be executed before the dependencies in this set are resolved.
+     *
+     * @param action The action to execute.
+     */
+    void beforeResolve(Action<? super ResolvableDependencies> action);
+
+    /**
+     * Adds an action to be executed before the dependencies in this set are resolved.
+     *
+     * @param action The action to execute.
+     */
+    void beforeResolve(Closure action);
+
+    /**
+     * Adds an action to be executed after the dependencies of this set have been resolved.
+     *
+     * @param action The action to execute.
+     */
+    void afterResolve(Action<? super ResolvableDependencies> action);
+
+    /**
+     * Adds an action to be executed after the dependencies of this set have been resolved.
+     *
+     * @param action The action to execute.
+     */
+    void afterResolve(Closure action);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
index 0e3ad6d..553395e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
@@ -16,9 +16,8 @@
 
 package org.gradle.api.artifacts;
 
-import org.gradle.api.GradleException;
+import org.gradle.api.internal.AbstractMultiCauseException;
 import org.gradle.api.internal.Contextual;
-import org.gradle.util.GUtil;
 
 /**
  * <p>A <code>ResolveException</code> is thrown when a dependency configuration cannot be resolved for some reason.</p>
@@ -26,20 +25,16 @@ import org.gradle.util.GUtil;
  * @author Hans Dockter
  */
 @Contextual
-public class ResolveException extends GradleException {
-    public ResolveException(Configuration configuration, String message) {
-        super(buildMessage(configuration, message));
+public class ResolveException extends AbstractMultiCauseException {
+    public ResolveException(Configuration configuration, Throwable cause) {
+        super(buildMessage(configuration), cause);
     }
 
-    public ResolveException(Configuration configuration, Throwable cause) {
-        super(buildMessage(configuration, null), cause);
+    public ResolveException(Configuration configuration, Iterable<? extends Throwable> causes) {
+        super(buildMessage(configuration), causes);
     }
 
-    private static String buildMessage(Configuration configuration, String message) {
-        if (GUtil.isTrue(message)) {
-            return String.format("Could not resolve all dependencies for %s:%n%s", configuration, message);
-        } else {
-            return String.format("Could not resolve all dependencies for %s.", configuration);
-        }
+    private static String buildMessage(Configuration configuration) {
+        return String.format("Could not resolve all dependencies for %s.", configuration);
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
index ff03b62..486fa6f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
@@ -25,6 +25,20 @@ import java.io.File;
 public interface ResolvedArtifact {
     File getFile();
 
+    /**
+     * Returns the module which this artifact belongs to.
+     *
+     * @return The module.
+     */
+    ResolvedModuleVersion getModuleVersion();
+
+    /**
+     * Returns one of the dependencies which this artifact belongs to.
+     *
+     * @return One of the dependencies which this artifact belongs to.
+     * @deprecated An artifact can belong to multiple dependencies. Use {@link #getModuleVersion()} instead.
+     */
+    @Deprecated
     ResolvedDependency getResolvedDependency();
 
     String getName();
@@ -32,4 +46,6 @@ public interface ResolvedArtifact {
     String getType();
 
     String getExtension();
+
+    String getClassifier();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java
index 0a14e53..b45ba3a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java
@@ -31,6 +31,11 @@ public interface ResolvedConfiguration {
     boolean hasError();
 
     /**
+     * Provides configuration that does not fail eagerly when some dependencies are not resolved.
+     */
+    LenientConfiguration getLenientConfiguration();
+
+    /**
      * A resolve of a configuration that is not successful does not automatically throws an exception.
      * Such a exception is only thrown if the result of a resolve is accessed. You can force the throwing
      * of such an exception by calling this method.  
@@ -46,7 +51,7 @@ public interface ResolvedConfiguration {
      * @return The artifact files of the specified dependencies.
      * @throws ResolveException when the resolve was not successful.
      */
-    Set<File> getFiles(Spec<Dependency> dependencySpec) throws ResolveException;
+    Set<File> getFiles(Spec<? super Dependency> dependencySpec) throws ResolveException;
 
     /**
      * Returns the {@link ResolvedDependency} instances for each direct dependency of the configuration. Via those
@@ -67,7 +72,7 @@ public interface ResolvedConfiguration {
      * @return A {@code ResolvedDependency} instance for each direct dependency.
      * @throws ResolveException when the resolve was not successful.
      */
-    Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<Dependency> dependencySpec) throws ResolveException;
+    Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<? super Dependency> dependencySpec) throws ResolveException;
 
     /**
      * Returns the set of artifact meta-data for this configuration.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
index 46545f7..2fc35c4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
@@ -49,6 +49,13 @@ public interface ResolvedDependency {
     String getConfiguration();
 
     /**
+     * Returns the module which this resolved dependency belongs to.
+     *
+     * @return The module.
+     */
+    ResolvedModuleVersion getModule();
+
+    /**
      * Returns the transitive ResolvedDependency instances of this resolved dependency. Returns never null.
      */
     Set<ResolvedDependency> getChildren();
@@ -80,7 +87,7 @@ public interface ResolvedDependency {
     Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent);
 
     /**
-     * Returns a union of the module and parent artifacts of this dependency. Never returns null.
+     * Returns the parent artifacts of this dependency. Never returns null.
      *
      * @param parent A parent of the ResolvedDependency. Must not be null.
      * @throws org.gradle.api.InvalidUserDataException If the parent is unknown or null
@@ -88,7 +95,7 @@ public interface ResolvedDependency {
     Set<ResolvedArtifact> getArtifacts(ResolvedDependency parent);
 
     /**
-     * Returns a union of the module and parent artifacts of this dependency and its children. Never returns null.
+     * Returns the parent artifacts of this dependency and its children. Never returns null.
      *
      * @param parent A parent of the ResolvedDependency. Must not be null.
      * @throws org.gradle.api.InvalidUserDataException If the parent is unknown or null
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedModuleVersion.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedModuleVersion.java
new file mode 100644
index 0000000..c1d9b3b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolvedModuleVersion.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * Represents meta-data about a resolved module version.
+ */
+public interface ResolvedModuleVersion {
+    /**
+     * The identifier of this resolved module version.
+     * @return the identifier
+     */
+    ModuleVersionIdentifier getId();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolverContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolverContainer.java
deleted file mode 100644
index 0d017ad..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ResolverContainer.java
+++ /dev/null
@@ -1,197 +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.artifacts;
-
-import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.NamedDomainObjectContainer;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.NamedDomainObjectCollection;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * <p>A {@code ResolverContainer} is responsible for managing a set of {@link org.apache.ivy.plugins.resolver.DependencyResolver}
- * instances. Resolvers are arranged in a sequence.</p>
- *
- * <p>You can obtain a {@code ResolverContainer} instance by calling {@link org.gradle.api.Project#getRepositories()} or
- * using the {@code repositories} property in your build script.</p>
- *
- * <p>The resolvers in a container are accessable as read-only properties of the container, using the name of the
- * resolver as the property name. For example:</p>
- *
- * <pre>
- * resolvers.add('myResolver')
- * resolvers.myResolver.addArtifactPattern(somePattern)
- * </pre>
- *
- * <p>A dynamic method is added for each resolver which takes a configuration closure. This is equivalent to calling
- * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
- *
- * <pre>
- * resolvers.add('myResolver')
- * resolvers.myResolver {
- *     addArtifactPattern(somePattern)
- * }
- * </pre>
- *
- * @author Hans Dockter
- */
-public interface ResolverContainer extends NamedDomainObjectContainer<DependencyResolver>, NamedDomainObjectCollection<DependencyResolver> {
-    String DEFAULT_MAVEN_CENTRAL_REPO_NAME = "MavenRepo";
-    String DEFAULT_MAVEN_LOCAL_REPO_NAME = "MavenLocal";
-    String MAVEN_CENTRAL_URL = "http://repo1.maven.org/maven2/";
-    String MAVEN_REPO_PATTERN = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]";
-    String DEFAULT_CACHE_ARTIFACT_PATTERN
-            = "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])";
-    String DEFAULT_CACHE_IVY_PATTERN = "[organisation]/[module](/[branch])/ivy-[revision].xml";
-    String INTERNAL_REPOSITORY_NAME = "internal-repository";
-    String DEFAULT_CACHE_DIR_NAME = "cache";
-    String RESOLVER_NAME = "name";
-    String RESOLVER_URL = "url";
-
-    /**
-     * Adds a resolver to this container, at the end of the resolver sequence. The given {@code userDescription} can be
-     * one of:
-     *
-     * <ul>
-     *
-     * <li>A String. This is treated as a URL, and used to create a maven resolver.</li>
-     *
-     * <li>A map. This is used to create a maven resolver. The map must contain an {@value #RESOLVER_NAME} entry and a
-     * {@value #RESOLVER_URL} entry.</li>
-     *
-     * <li>A {@link org.apache.ivy.plugins.resolver.DependencyResolver}.</li>
-     *
-     * </ul>
-     *
-     * @param userDescription The resolver definition.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     */
-    DependencyResolver add(Object userDescription) throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, at the end of the resolver sequence. The resolver is configured using the
-     * given configure closure.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @param configureClosure The closure to use to configure the resolver.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     */
-    DependencyResolver add(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, before the given resolver.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @param nextResolver The existing resolver to add the new resolver before.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     * @throws InvalidUserDataException when the given next resolver does not exist in this container.
-     */
-    DependencyResolver addBefore(Object userDescription, String nextResolver) throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, before the given resolver. The resolver is configured using the given
-     * configure closure.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @param nextResolver The existing resolver to add the new resolver before.
-     * @param configureClosure The closure to use to configure the resolver.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     * @throws InvalidUserDataException when the given next resolver does not exist in this container.
-     */
-    DependencyResolver addBefore(Object userDescription, String nextResolver, Closure configureClosure)
-            throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, after the given resolver.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @param previousResolver The existing resolver to add the new resolver after.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     * @throws InvalidUserDataException when the given previous resolver does not exist in this container.
-     */
-    DependencyResolver addAfter(Object userDescription, String previousResolver) throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, after the given resolver. The resolver is configured using the given configure
-     * closure.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @param previousResolver The existing resolver to add the new resolver after.
-     * @param configureClosure The closure to use to configure the resolver.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     * @throws InvalidUserDataException when the given previous resolver does not exist in this container.
-     */
-    DependencyResolver addAfter(Object userDescription, String previousResolver, Closure configureClosure)
-            throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, at the start of the resolver sequence.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     */
-    DependencyResolver addFirst(Object userDescription) throws InvalidUserDataException;
-
-    /**
-     * Adds a resolver to this container, at the start of the resolver sequence. The resolver is configured using the
-     * given configure closure.
-     *
-     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
-     * @param configureClosure The closure to use to configure the resolver.
-     * @return The added resolver.
-     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
-     */
-    DependencyResolver addFirst(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
-
-    /**
-     * {@inheritDoc}
-     */
-    DependencyResolver getByName(String name) throws UnknownRepositoryException;
-
-    /**
-     * {@inheritDoc}
-     */
-    DependencyResolver getByName(String name, Closure configureClosure) throws UnknownRepositoryException;
-
-    /**
-     * {@inheritDoc}
-     */
-    DependencyResolver getAt(String name) throws UnknownRepositoryException;
-
-    /**
-     * Returns the resolvers in this container, in sequence.
-     *
-     * @return The resolvers in sequence. Returns an empty list if this container is empty.
-     */
-    List<DependencyResolver> getResolvers();
-
-    void setMavenPomDir(File mavenPomDir);
-
-    Conf2ScopeMappingContainer getMavenScopeMappings();
-
-    File getMavenPomDir();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java
new file mode 100644
index 0000000..17d5dc9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * Unsuccessfully resolved dependency.
+ */
+public interface UnresolvedDependency {
+
+    /**
+     * Returns the identifier of the dependency, for example group:name:version
+     */
+    String getId();
+
+    /**
+     * the exception that is the cause of unresolved state
+     */
+    Throwable getProblem();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
new file mode 100644
index 0000000..8b6b555
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.api.artifacts.ArtifactIdentifier;
+
+import java.io.File;
+
+/**
+ * Command methods for controlling artifact resolution via the DSL.
+ */
+public interface ArtifactResolutionControl extends ResolutionControl<ArtifactIdentifier, File> {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java
new file mode 100644
index 0000000..4b3374b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+/**
+ * Command methods for controlling dependency resolution via the DSL.
+ */
+public interface DependencyResolutionControl extends ResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java
new file mode 100644
index 0000000..ab2aec2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+
+/**
+ * Command methods for controlling module resolution via the DSL.
+ */
+public interface ModuleResolutionControl extends ResolutionControl<ModuleVersionIdentifier, ResolvedModuleVersion> {
+    // TODO: This should be part of the cached result?
+    /**
+     * Does the module change content over time?
+     * @return if the module is changing
+     */
+    boolean isChanging();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java
new file mode 100644
index 0000000..f95d305
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.cache;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Command methods for controlling dependency resolution via the DSL.
+ * @param <A> The type of the request object for this resolution
+ * @param <B> The type of the result of this resolution
+ */
+public interface ResolutionControl<A, B> {
+    /**
+     * Returns the query object that was requested in this resolution.
+     * @return the request object
+     */
+    A getRequest();
+
+    /**
+     * Returns the cached result file or null if the result has not been cached.
+     * @return the cached result
+     */
+    B getCachedResult();
+    
+    /**
+     * States that the cached value should be used if it is no older than the specified duration.
+     * @param value The number of units
+     * @param units The time units
+     */
+    void cacheFor(int value, TimeUnit units);
+
+    /**
+     * States that the cached value should be used regardless of age.
+     * If not cachedResult is available, resolution should fail.
+     */
+    void useCachedResult();
+
+    /**
+     * States that any cached value should be ignored, forcing a fresh resolve.
+     */
+    void refresh();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java
new file mode 100644
index 0000000..56f88e0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.api.Action;
+
+/**
+ * Represents a set of rules/actions that can be applied during dependency resolution.
+ * Currently these are restricted to controlling caching, but these could possibly be extended in the future to include other manipulations.
+ */
+public interface ResolutionRules {
+    /**
+     * Apply a rule to control resolution of dependencies.
+     * @param rule the rule to apply
+     */
+    void eachDependency(Action<? super DependencyResolutionControl> rule);
+
+    /**
+     * Apply a rule to control resolution of modules.
+     * @param rule the rule to apply
+     */
+    void eachModule(Action<? super ModuleResolutionControl> rule);
+
+    /**
+     * Apply a rule to control resolution of artifacts.
+     * @param rule the rule to apply
+     */
+    void eachArtifact(Action<? super ArtifactResolutionControl> rule);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/package-info.java
new file mode 100644
index 0000000..68e93f2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for controlling dependency resolution.
+ */
+package org.gradle.api.artifacts.cache;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
index d743451..376cb32 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
@@ -15,19 +15,78 @@
  */
 package org.gradle.api.artifacts.dsl;
 
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.PublishArtifact;
+
 /**
- * This class is for creating publish artifacts and adding them to configurations. Creating publish artifacts
+ * This class is for defining artifacts to be published and adding them to configurations. Creating publish artifacts
  * does not mean to create an archive. What is created is a domain object which represents a file to be published
- * and information on how it should be published (e.g. the name). The publish artifact, that should be created,
- * can only be described with an archive at the moment. We will add additional notations in the future.
+ * and information on how it should be published (e.g. the name).
  *
  * <p>To create an publish artifact and assign it to a configuration you can use the following syntax:</p>
  *
- * <code><ArtifactHandler>.<configurationName> <archive1>, <archive2>, ...</code>
+ * <code><configurationName> <artifact-notation>, <artifact-notation> ...</code>
+ *
+ * or
+ *
+ * <code><configurationName> <artifact-notation> { ... some code to configure the artifact }</code>
+ *
+ * <p>The notation can be one of the following types:</p>
+ *
+ * <ul>
+ *
+ * <li>{@link org.gradle.api.tasks.bundling.AbstractArchiveTask}. The information for publishing the artifact is extracted from the archive task (e.g. name, extension, ...).
+ * An archive artifact is represented using an instance of {@link PublishArtifact}.</li>
  *
- *  <p>The information for publishing the artifact is extracted from the archive (e.g. name, extension, ...).</p>
+ * <li>{@link java.io.File}. The information for publishing the artifact is extracted from the file name. You can tweak the resulting values by using
+ * a closure to configure the properties of the artifact instance. A file artifact is represented using an instance of {@link org.gradle.api.artifacts.ConfigurablePublishArtifact}
+ * </li>
+ *
+ * <li>{@link java.util.Map}. The map should contain a 'file' key. This is converted to an artifact as described above. You can also
+ * specify other properties of the artifact using entries in the map.
+ * </li>
+ *
+ * </ul>
+ *
+ * <h2>Examples</h2>
+ * <p>An example showing how to associate an archive task with a configuration via the artifact handler.
+ * This way the archive can be published or referred in other projects via the configuration.
+ * <pre autoTested=''>
+ * configurations {
+ *   //declaring new configuration that will be used to associate with artifacts
+ *   schema
+ * }
+ *
+ * task schemaJar(type: Jar) {
+ *   //some imaginary task that creates a jar artifact with some schema
+ * }
+ *
+ * //associating the task that produces the artifact with the configuration
+ * artifacts {
+ *   //configuration name and the task:
+ *   schema schemaJar
+ * }
+ * </pre>
  *
  * @author Hans Dockter
  */
 public interface ArtifactHandler {
+    /**
+     * Adds an artifact to the given configuration.
+     *
+     * @param configurationName The name of the configuration.
+     * @param artifactNotation The artifact notation, in one of the notations described above.
+     * @return The artifact.
+     */
+    PublishArtifact add(String configurationName, Object artifactNotation);
+
+    /**
+     * Adds an artifact to the given configuration.
+     *
+     * @param configurationName The name of the configuration.
+     * @param artifactNotation The artifact notation, in one of the notations described above.
+     * @param configureClosure The closure to execute to configure the artifact.
+     * @return The artifact.
+     */
+    PublishArtifact add(String configurationName, Object artifactNotation, Closure configureClosure);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactRepository.java
deleted file mode 100644
index 42e2eaa..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactRepository.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 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.dsl;
-
-/**
- * A repository for resolving and publishing artifacts.
- */
-public interface ArtifactRepository {
-    /**
-     * Returns the name for this repository.
-     *
-     * @return The name.
-     */
-    String getName();
-
-    /**
-     * Sets the name for this repository.
-     *
-     * @param name The name. Must not be null.
-     */
-    void setName(String name);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
index dcb3d56..1cef039 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
@@ -21,9 +21,8 @@ import org.gradle.api.artifacts.Dependency;
 import java.util.Map;
 
 /**
- * <p>A {@code DependencyHandler} is used to declare artifact dependencies. Artifact dependencies are grouped into
- * configurations (see {@link org.gradle.api.artifacts.Configuration}), and a given dependency declarations is always
- * attached to a single configuration.</p>
+ * <p>A {@code DependencyHandler} is used to declare dependencies. Dependencies are grouped into
+ * configurations (see {@link org.gradle.api.artifacts.Configuration}).</p>
  *
  * <p>To declare a specific dependency for a configuration you can use the following syntax:</p>
  *
@@ -33,17 +32,95 @@ import java.util.Map;
  * }
  * </pre>
  *
- * <p>or, to configure a dependency when it is declared, you can additionally pass a configuration closure:</p>
+ * <p>Example shows a basic way of declaring dependencies.
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * //so that we can use 'compile', 'testCompile' for dependencies
+ *
+ * dependencies {
+ *   //for dependencies found in artifact repositories you can use
+ *   //the group:name:version notation
+ *   compile 'commons-lang:commons-lang:2.6'
+ *   testCompile 'org.mockito:mockito:1.9.0-rc1'
+ *
+ *   //map-style notation:
+ *   compile group: 'com.google.code.guice', name: 'guice', version: '1.0'
+ *
+ *   //declaring arbitrary files as dependencies
+ *   compile files('hibernate.jar', 'libs/spring.jar')
+ *
+ *   //putting all jars from 'libs' onto compile classpath
+ *   compile fileTree('libs')
+ * }
+ * </pre>
+ *
+ * <h2>Advanced dependency configuration</h2>
+ * <p>To do some advanced configuration on a dependency when it is declared, you can additionally pass a configuration closure:</p>
  *
  * <pre>
  * dependencies {
- *     <i>configurationName</i> <i>dependencyNotation</i> {
+ *     <i>configurationName</i>(<i>dependencyNotation</i>){
  *         <i>configStatement1</i>
  *         <i>configStatement2</i>
  *     }
  * }
  * </pre>
  *
+ * Examples of advanced dependency declaration including:
+ * <ul>
+ * <li>Forcing certain dependency version in case of the conflict.</li>
+ * <li>Excluding certain dependencies by name, group or both.
+ *      More details about per-dependency exclusions can be found in
+ *      docs for {@link org.gradle.api.artifacts.ModuleDependency#exclude(java.util.Map)}.</li>
+ * <li>Avoiding transitive dependencies for certain dependency.</li>
+ * </ul>
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java' //so that I can declare 'compile' dependencies
+ *
+ * dependencies {
+ *   compile('org.hibernate:hibernate:3.1') {
+ *     //in case of versions conflict '3.1' version of hibernate wins:
+ *     force = true
+ *
+ *     //excluding a particular transitive dependency:
+ *     exclude module: 'cglib' //by artifact name
+ *     exclude group: 'org.jmock' //by group
+ *     exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
+ *
+ *     //disabling all transitive dependencies of this dependency
+ *     transitive = false
+ *   }
+ * }
+ * </pre>
+ *
+ * More examples of advanced configuration, useful when dependency module has multiple artifacts:
+ * <ul>
+ *   <li>Declaring dependency to a specific configuration of the module.</li>
+ *   <li>Explicit specification of the artifact. See also {@link org.gradle.api.artifacts.ModuleDependency#artifact(groovy.lang.Closure)}.</li>
+ * </ul>
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java' //so that I can declare 'compile' dependencies
+ *
+ * dependencies {
+ *   //configuring dependency to specific configuration of the module
+ *   compile configuration: 'someConf', group: 'org.someOrg', name: 'someModule', version: '1.0'
+ *
+ *   //configuring dependency on 'someLib' module
+ *   compile(group: 'org.myorg', name: 'someLib', version:'1.0') {
+ *     //explicitly adding the dependency artifact:
+ *     artifact {
+ *       name = 'someArtifact' //artifact name different than module name
+ *       type = 'jar'
+ *       classifier = 'someClassifier'
+ *     }
+ *   }
+ * }
+ * </pre>
+ *
+ * <h2>Dependency notations</h2>
+ *
  * <p>There are several supported dependency notations. These are described below. For each dependency declared this
  * way, a {@link Dependency} object is created. You can use this object to query or further configure the
  * dependency.</p>
@@ -53,9 +130,10 @@ import java.util.Map;
  *
  * <code><i>configurationName</i> <instance></code>
  *
- * <h2>External Modules</h2>
+ * <h3>External dependencies</h3>
  *
- * <p>There are 2 notations supported for declaring a dependency on an external module. One is a string notation:</p>
+ * <p>There are 2 notations supported for declaring a dependency on an external module.
+ * One is a String notation formatted this way: group:name:version</p>
  *
  * <code><i>configurationName</i> "<i>group</i>:<i>name</i>:<i>version</i>:<i>classifier</i>"</code>
  *
@@ -69,33 +147,95 @@ import java.util.Map;
  * <p>External dependencies are represented by a {@link
  * org.gradle.api.artifacts.ExternalModuleDependency}.</p>
  *
- * <h2>Client Modules</h2>
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * //so that we can use 'compile', 'testCompile' for dependencies
  *
- * <p>To add a client module to a configuration you can use the notation:</p>
+ * dependencies {
+ *   //for dependencies found in artifact repositories you can use
+ *   //the string notation, e.g. group:name:version
+ *   compile 'commons-lang:commons-lang:2.6'
+ *   testCompile 'org.mockito:mockito:1.9.0-rc1'
  *
- * <pre>
- * <i>configurationName</i> module(<i>moduleNotation</i>) {
- *     <i>module dependencies</i>
+ *   //map notation:
+ *   compile group: 'com.google.code.guice', name: 'guice', version: '1.0'
  * }
  * </pre>
  *
- * The module notation is the same as the dependency notations described above, except that the classifier property is
- * not available. Client modules are represented using a {@link org.gradle.api.artifacts.ClientModule}.
+ * <h3>Project dependencies</h3>
  *
- * <h2>Projects</h2>
+ * <p>To add a project dependency, you use the following notation:
+ * <p><code><i>configurationName</i> project(':someProject')</code>
  *
- * <p>To add a project dependency, you use the following notation</p>
+ * <p>The notation <code>project(':projectA')</code> is similar to the syntax you use
+ * when configuring a projectA in a multi-module gradle project.
  *
- * <code><i>configurationName</i> project(':someProject')</code>
+ * <p>By default, when you declare dependency to projectA, you actually declare dependency to the 'default' configuration of the projectA.
+ * If you need to depend on a specific configuration of projectA, use map notation for projects:
+ * <p><code><i>configurationName</i> project(path: ':projectA', configuration: 'someOtherConfiguration')</code>
  *
- * <p>Project dependencies are represented using a {@link org.gradle.api.artifacts.ProjectDependency}.</p>
+ * <p>Project dependencies are represented using a {@link org.gradle.api.artifacts.ProjectDependency}.
  *
- * <h2>Files</h2>
+ * <h3>File dependencies</h3>
  *
  * <p>You can also add a dependency using a {@link org.gradle.api.file.FileCollection}:</p>
  * <code><i>configurationName</i> files('a file')</code>
  *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * //so that we can use 'compile', 'testCompile' for dependencies
+ *
+ * dependencies {
+ *   //declaring arbitrary files as dependencies
+ *   compile files('hibernate.jar', 'libs/spring.jar')
+ *
+ *   //putting all jars from 'libs' onto compile classpath
+ *   compile fileTree('libs')
+ * }
+ * </pre>
+ *
  * <p>File dependencies are represented using a {@link org.gradle.api.artifacts.SelfResolvingDependency}.</p>
+ * 
+ * <h3>Dependencies to other configurations</h3>
+ * 
+ * <p>You can add a dependency using a {@link org.gradle.api.artifacts.Configuration}.</p>
+ *
+ * <p>When the configuration is from the same project as the target configuration, the target configuration is changed
+ * to extend from the provided configuration.</p>
+ *
+ * <p>When the configuration is from a different project, a project dependency is added.</p>
+ *
+ * <h3>Gradle distribution specific dependencies</h3>
+ *
+ * <p>It is possible to depend on certain Gradle APIs or libraries that Gradle ships with.
+ * It is particularly useful for Gradle plugin development. Example:</p>
+ *
+ * <pre autoTested=''>
+ * //Our Gradle plugin is written in groovy
+ * apply plugin: 'groovy'
+ * //now we can use 'groovy' and 'compile' configuration for declaring dependencies
+ *
+ * dependencies {
+ *   //we will use groovy that ships with Gradle:
+ *   groovy localGroovy()
+ *
+ *   //our plugin requires Gradle API interfaces and classes to compile:
+ *   compile gradleApi()
+ * }
+ * </pre>
+ *
+ * <h3>Client module dependencies</h3>
+ *
+ * <p>To add a client module to a configuration you can use the notation:</p>
+ *
+ * <pre>
+ * <i>configurationName</i> module(<i>moduleNotation</i>) {
+ *     <i>module dependencies</i>
+ * }
+ * </pre>
+ *
+ * The module notation is the same as the dependency notations described above, except that the classifier property is
+ * not available. Client modules are represented using a {@link org.gradle.api.artifacts.ClientModule}.
  *
  * @author Hans Dockter
  */
@@ -104,7 +244,9 @@ public interface DependencyHandler {
      * Adds a dependency to the given configuration.
      *
      * @param configurationName The name of the configuration.
-     * @param dependencyNotation The dependency notation, in one of the notations described above.
+     * @param dependencyNotation
+     *
+     * The dependency notation, in one of the notations described above.
      * @return The dependency.
      */
     Dependency add(String configurationName, Object dependencyNotation);
@@ -120,6 +262,24 @@ public interface DependencyHandler {
     Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure);
 
     /**
+     * Creates a dependency without adding it to a configuration.
+     *
+     * @param dependencyNotation The dependency notation, in one of the notations described above.
+     * @return The dependency.
+     */
+    Dependency create(Object dependencyNotation);
+
+    /**
+     * Creates a dependency without adding it to a configuration, and configures the dependency using
+     * the given closure.
+     *
+     * @param dependencyNotation The dependency notation, in one of the notations described above.
+     * @param configureClosure The closure to use to configure the dependency.
+     * @return The dependency.
+     */
+    Dependency create(Object dependencyNotation, Closure configureClosure);
+
+    /**
      * Creates a dependency on a client module.
      *
      * @param notation The module notation, in one of the notations described above.
@@ -143,7 +303,7 @@ public interface DependencyHandler {
      * @param notation The project notation, in one of the notations described above.
      * @return The dependency.
      */
-    Dependency project(Map notation);
+    Dependency project(Map<String, ?> notation);
     
     /**
      * Creates a dependency on the API of the current version of Gradle.
@@ -151,4 +311,11 @@ public interface DependencyHandler {
      * @return The dependency.
      */
     Dependency gradleApi();
+    
+    /**
+     * Creates a dependency on the Groovy that is distributed with the current version of Gradle.
+     * 
+     * @return The dependency.
+     */
+    Dependency localGroovy();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/IvyArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/IvyArtifactRepository.java
deleted file mode 100644
index 527e3aa..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/IvyArtifactRepository.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2011 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.dsl;
-
-/**
- * An artifact repository which uses an Ivy format to store artifacts and meta-data.
- */
-public interface IvyArtifactRepository extends ArtifactRepository {
-    /**
-     * Returns the user name to use when authenticating to this repository.
-     *
-     * @return The user name. May be null.
-     */
-    String getUserName();
-
-    /**
-     * Sets the user name to use when authenticating to this repository.
-     *
-     * @param username The user name. May be null.
-     */
-    void setUserName(String username);
-
-    /**
-     * Returns the password to use when authenticating to this repository.
-     *
-     * @return The password. May be null.
-     */
-    String getPassword();
-
-    /**
-     * Sets the password to use when authenticating to this repository.
-     *
-     * @param password The password. May be null.
-     */
-    void setPassword(String password);
-
-    /**
-     * Adds an Ivy artifact pattern to use to locate artifacts in this repository.
-     *
-     * @param pattern The artifact pattern.
-     */
-    void artifactPattern(String pattern);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
index 61315ba..a858122 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
@@ -17,12 +17,11 @@ package org.gradle.api.artifacts.dsl;
 
 import groovy.lang.Closure;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.FileSystemResolver;
 import org.gradle.api.Action;
-import org.gradle.api.artifacts.ResolverContainer;
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
-import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 
 import java.util.Map;
 
@@ -31,9 +30,7 @@ import java.util.Map;
  *
  * @author Hans Dockter
  */
-public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
-    final String DEFAULT_MAVEN_DEPLOYER_NAME = "mavenDeployer";
-    final String DEFAULT_MAVEN_INSTALLER_NAME = "mavenInstaller";
+public interface RepositoryHandler extends ArtifactRepositoryContainer {
 
     /**
      * Adds a resolver that looks into a number of directories for artifacts. The artifacts are expected to be located in the
@@ -51,7 +48,7 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      * The default is a Hash value of the rootdir paths. The name is used in the console output,
      * to point to information related to a particular repository. A name must be unique amongst a repository group.</td></tr>
      * <tr><td><code>dirs</code></td>
-     *     <td>Specifies a list of rootDirs where to look for dependencies.</td></tr>
+     *     <td>Specifies a list of rootDirs where to look for dependencies. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}</td></tr>
      * </table>
      *
      * <p>Examples:
@@ -63,16 +60,32 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      * </pre>
      * </p>
      *
-     * @param args
+     * @param args The arguments used to configure the repository.
      * @return the added resolver
      * @throws org.gradle.api.InvalidUserDataException In the case neither rootDir nor rootDirs is specified of if both
      * are specified.
      */
-    FileSystemResolver flatDir(Map<String, ?> args);
+    FlatDirectoryArtifactRepository flatDir(Map<String, ?> args);
+
+    /**
+     * Adds an configures a repository which will look for dependencies in a number of local directories.
+     *
+     * @param configureClosure The closure to execute to configure the repository.
+     * @return The repository.
+     */
+    FlatDirectoryArtifactRepository flatDir(Closure configureClosure);
+
+    /**
+     * Adds an configures a repository which will look for dependencies in a number of local directories.
+     *
+     * @param action The action to execute to configure the repository.
+     * @return The repository.
+     */
+    FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action);
 
     /**
      * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
-     * always {@link org.gradle.api.artifacts.ResolverContainer#MAVEN_CENTRAL_URL}. The behavior of this resolver
+     * always {@link org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The behavior of this resolver
      * is otherwise the same as the ones added by {@link #mavenRepo(java.util.Map)}.
      *
      * The following parameter are accepted as keys for the map:
@@ -82,34 +95,33 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      *     <th>Description of Associated Value</th></tr>
      * <tr><td><code>name</code></td>
      *     <td><em>(optional)</em> The name of the repository. The default is
-     * {@value org.gradle.api.artifacts.ResolverContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME} is used as the name. A name
+     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME} is used as the name. A name
      * must be unique amongst a repository group.
      * </td></tr>
-     * <tr><td><code>urls</code></td>
-     *     <td>A single jar repository or a collection of jar repositories. Sometimes the artifact
-     * lives in a different repository than the POM. In such a case you can specify further locations to look for an artifact.
-     * But be aware that the POM is only looked up in the root repository</td></tr>
+     * <tr><td><code>artifactUrls</code></td>
+     *     <td>A single jar repository or a collection of jar repositories containing additional artifacts not found in the maven central repository.
+     * But be aware that the POM must exist in maven central.
+     * The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}.</td></tr>
      * </table>
      *
      * <p>Examples:
      * <pre>
      * repositories {
-     *     mavenCentral urls: ["http://www.mycompany.com/repository1", "http://www.mycompany.com/repository2"]
-     *     mavenCentral name: "nonDefaultName", urls: ["http://www.mycompany.com/repository"]
+     *     mavenCentral artifactUrls: ["http://www.mycompany.com/artifacts1", "http://www.mycompany.com/artifacts2"]
+     *     mavenCentral name: "nonDefaultName", artifactUrls: ["http://www.mycompany.com/artifacts1"]
      * }
      * </pre>
      * </p>
      *
      * @param args A list of urls of repositories to look for artifacts only.
-     * @return the added resolver
-     * @see #mavenRepo(java.util.Map)
+     * @return the added repository
      */
-    DependencyResolver mavenCentral(Map<String, ?> args);
+    MavenArtifactRepository mavenCentral(Map<String, ?> args);
 
     /**
      * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
-     * {@value org.gradle.api.artifacts.ResolverContainer#MAVEN_CENTRAL_URL}. The name of the repository is
-     * {@value org.gradle.api.artifacts.ResolverContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME}.
+     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The name of the repository is
+     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME}.
      *
      * <p>Examples:
      * <pre>
@@ -120,14 +132,13 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      * </p>
      *
      * @return the added resolver
-     * @see #mavenRepo(java.util.Map)
      * @see #mavenCentral(java.util.Map)
      */
-    DependencyResolver mavenCentral();
+    MavenArtifactRepository mavenCentral();
 
     /**
      * Adds a repository which looks in the local Maven cache for dependencies. The name of the repository is
-     * {@value org.gradle.api.artifacts.ResolverContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}.
+     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}.
      *
      * <p>Examples:
      * <pre>
@@ -138,14 +149,14 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      * </p>
      *
      * @return the added resolver
-     * @see #mavenRepo(java.util.Map)
      */
-    DependencyResolver mavenLocal();
+    MavenArtifactRepository mavenLocal();
 
     /**
      * Adds a repository which is Maven compatible. The compatibility is in regard to layout, snapshot handling and
      * dealing with the pom.xml. This repository can't be used for publishing in a Maven compatible way. For publishing
-     * to a Maven repository, have a look at {@link #mavenDeployer(java.util.Map)} or {@link #mavenInstaller(java.util.Map)}.
+     * to a Maven repository, have a look at {@link org.gradle.api.plugins.MavenRepositoryHandlerConvention#mavenDeployer(java.util.Map)} or
+     * {@link org.gradle.api.plugins.MavenRepositoryHandlerConvention#mavenInstaller(java.util.Map)}.
      *
      * By default the repository accepts to resolve artifacts without a pom. The repository always looks first for the pom
      * in the root repository. It then looks for the artifact in the root repository. Sometimes the artifact
@@ -162,17 +173,21 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      * The name is used in the console output,
      * to point to information related to a particular repository. A name must be unique amongst a repository group.
      * </td></tr>
-     * <tr><td><code>urls</code></td>
-     *     <td>A single repository url or a list of urls. The first url is the the url of the root repo.
-     * Gradle always looks first for the pom in the root repository. After this it looks for the artifact in the root repository.
-     * If the artifact can't be found there, it looks for it in the other repositories.</td></tr>
+     * <tr><td><code>url</code></td>
+     *     <td>The root repository where POM files and artifacts are located.
+     * The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}.</td></tr>
+     * <tr><td><code>artifactUrls</code></td>
+     *     <td>A single jar repository or a collection of jar repositories containing additional artifacts not found in the root repository. Sometimes the artifact
+     * lives in a different repository than the POM. In such a case you can specify further locations to look for an artifact.
+     * But be aware that the POM is only looked up in the root repository.
+     * The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}.</td></tr>
      * </table>
      *
      * <p>Examples:
      * <pre>
      * repositories {
-     *     mavenRepo urls: ["http://www.mycompany.com/repository1", "http://www.mycompany.com/repository2"]
-     *     mavenRepo name: "nonDefaultName", urls: ["http://www.mycompany.com/repository"]
+     *     mavenRepo url: "http://www.mycompany.com/repository", artifactUrls: ["http://www.mycompany.com/artifacts1", "http://www.mycompany.com/artifacts2"]
+     *     mavenRepo name: "nonDefaultName", url: "http://www.mycompany.com/repository"
      * }
      * </pre>
      * </p>
@@ -182,80 +197,35 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      *
      * @param args The argument to create the repository
      * @return the added repository
-     * @see #mavenCentral(java.util.Map)
      */
+    @SuppressWarnings("JavadocReference")
     DependencyResolver mavenRepo(Map<String, ?> args);
 
-    DependencyResolver mavenRepo(Map<String, ?> args, Closure configClosure);
-
-    GroovyMavenDeployer mavenDeployer();
-
-    GroovyMavenDeployer mavenDeployer(Closure configureClosure);
-
     /**
-     * Adds a repository for publishing to a Maven repository. This repository can not be used for reading from a Maven
-     * repository.
-     *
-     * The following parameter are accepted as keys for the map:
-     *
-     * <table summary="Shows property keys and associated values">
-     * <tr><th>Key</th>
-     *     <th>Description of Associated Value</th></tr>
-     * <tr><td><code>name</code></td>
-     *     <td><em>(optional)</em> The name of the repository. The default is <em>mavenDeployer-{SOME_ID}</em>.
-     * The name is used in the console output,
-     * to point to information related to a particular repository. A name must be unique amongst a repository group.
-     * </td></tr>
-     * </table>
-     *
-     * @param args The argument to create the repository
-     * @return The added repository
-     * @see #mavenDeployer(java.util.Map, groovy.lang.Closure)
-     */
-    GroovyMavenDeployer mavenDeployer(Map<String, ?> args);
-
-    /**
-     * Behaves the same way as {@link #mavenDeployer(java.util.Map)}. Additionally a closure can be passed to
-     * further configure the added repository.
-     *
+     * Adds a repository which is Maven compatible.
+     * 
      * @param args The argument to create the repository
-     * @param configureClosure
-     * @return The added repository
+     * @param configClosure Further configuration of the dependency resolver
+     * @return The created dependency resolver
+     * @see #mavenRepo(java.util.Map)
      */
-    GroovyMavenDeployer mavenDeployer(Map<String, ?> args, Closure configureClosure);
-
-    MavenResolver mavenInstaller();
-
-    MavenResolver mavenInstaller(Closure configureClosure);
+    DependencyResolver mavenRepo(Map<String, ?> args, Closure configClosure);
 
     /**
-     * Adds a repository for installing to a local Maven cache. This repository can not be used for reading.
+     * Adds and configures a Maven repository.
      *
-     * The following parameter are accepted as keys for the map:
-     *
-     * <table summary="Shows property keys and associated values">
-     * <tr><th>Key</th>
-     *     <th>Description of Associated Value</th></tr>
-     * <tr><td><code>name</code></td>
-     *     <td><em>(optional)</em> The name of the repository. The default is <em>mavenInstaller-{SOME_ID}</em>.
-     * The name is used in the console output,
-     * to point to information related to a particular repository. A name must be unique amongst a repository group.
-     * </td></tr>
-     * </table>
-     *
-     * @param args The argument to create the repository
-     * @return The added repository
-     * @see #mavenInstaller(java.util.Map, groovy.lang.Closure) (java.util.Map, groovy.lang.Closure)
+     * @param closure The closure to use to configure the repository.
+     * @return The added repository.
      */
-    MavenResolver mavenInstaller(Map<String, ?> args);
+    MavenArtifactRepository maven(Closure closure);
 
     /**
-     * Behaves the same way as {@link #mavenInstaller(java.util.Map)}. Additionally a closure can be passed to further configure the added repository.
+     * Adds and configures a Maven repository.
      *
-     * @param args The argument to create the repository
-     * @return The added repository
+     * @param action The action to use to configure the repository.
+     * @return The added repository.
      */
-    MavenResolver mavenInstaller(Map<String, ?> args, Closure configureClosure);
+    MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action);
 
     /**
      * Adds and configures an Ivy repository.
@@ -272,4 +242,5 @@ public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
      * @return The added repository.
      */
     IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action);
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenFactory.java
deleted file mode 100644
index 07444a7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenFactory.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2011 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.Configuration;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.file.FileResolver;
-
-import java.util.Map;
-
-/**
- * Factory for various types related to Maven dependency management.
- */
-public interface MavenFactory {
-    Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Map<Configuration, Conf2ScopeMapping> mappings, FileResolver fileResolver);
-
-    Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, FileResolver fileResolver);
-
-    Conf2ScopeMappingContainer createConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings);
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
deleted file mode 100644
index 61154b9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
+++ /dev/null
@@ -1,224 +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.artifacts.maven;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.ConfigurationContainer;
-
-import java.io.Writer;
-import java.util.List;
-
-/**
- * Is used for generating a Maven pom file and customizing the generation.
- * To learn about the Maven pom see: <a href="http://maven.apache.org/pom.html">http://maven.apache.org/pom.html</a>
- *
- * @author Hans Dockter
- */
-public interface MavenPom {
-    /**
-     * Returns the scope mappings used for generating this pom.
-     */
-    Conf2ScopeMappingContainer getScopeMappings();
-
-    /**
-     * Provides a builder for the Maven pom for adding or modifying properties of the Maven {@link #getModel()}.
-     * The syntax is exactly the same as used by polyglot Maven. For example:
-     *
-     * <pre>
-     * pom.project {
-     *    inceptionYear '2008'
-     *    licenses {
-     *       license {
-     *          name 'The Apache Software License, Version 2.0'
-     *          url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
-     *          distribution 'repo'
-     *       }
-     *    }
-     * }
-     * </pre>
-     *
-     * @param pom
-     * @return this
-     */
-    MavenPom project(Closure pom);
-
-    /**
-     * Returns the group id for this POM.
-     *
-     * @see org.apache.maven.model.Model#setGroupId(String)
-     */
-    String getGroupId();
-
-    /**
-     * Sets the group id for this POM.
-     *
-     * @see org.apache.maven.model.Model#getGroupId
-     * @return this
-     */
-    MavenPom setGroupId(String groupId);
-
-    /**
-     * Returns the artifact id for this POM.
-     * 
-     * @see org.apache.maven.model.Model#getArtifactId()
-     */
-    String getArtifactId();
-
-    /**
-     * Sets the artifact id for this POM.
-     *
-     * @see org.apache.maven.model.Model#setArtifactId(String)
-     * @return this
-     */
-    MavenPom setArtifactId(String artifactId);
-
-    /**
-     * Returns the version for this POM.
-     *
-     * @see org.apache.maven.model.Model#getVersion()
-     */
-    String getVersion();
-
-    /**
-     * Sets the version for this POM.
-     *
-     * @see org.apache.maven.model.Model#setVersion(String)
-     * @return this
-     */
-    MavenPom setVersion(String version);
-
-    /**
-     * Returns the packaging for this POM.
-     *
-     * @see org.apache.maven.model.Model#getPackaging()
-     */
-    String getPackaging();
-
-    /**
-     * Sets the packaging for this POM.
-     *
-     * @see org.apache.maven.model.Model#setPackaging(String)
-     * @return this
-     */
-    MavenPom setPackaging(String packaging);
-
-    /**
-     * Sets the dependencies for this POM.
-     *
-     * @see org.apache.maven.model.Model#setDependencies(java.util.List)
-     * @return this
-     */
-    MavenPom setDependencies(List<?> dependencies);
-
-    /**
-     * Returns the dependencies for this POM.
-     * 
-     * @see org.apache.maven.model.Model#getDependencies()
-     */
-    List<?> getDependencies();
-
-    /**
-     * Returns the underlying native Maven {@link org.apache.maven.model.Model} object. The MavenPom object
-     * delegates all the configuration information to this object. There Gradle MavenPom objects provides
-     * delegation methods just for setting the groupId, artifactId, version and packaging. For all other
-     * elements, either use the model object or {@link #project(groovy.lang.Closure)}.
-     *
-     * @return the underlying native Maven object
-     */
-    Object getModel();
-
-    /**
-     * Sets the underlying native Maven {@link org.apache.maven.model.Model} object.
-     *
-     * @param model
-     * @return this
-     * @see #getModel() 
-     */
-    MavenPom setModel(Object model);
-
-    /**
-     * Writes the {@link #getEffectivePom()} xml to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions.
-     *
-     * @param writer The writer to write the pom xml.
-     * @return this
-     */
-    MavenPom writeTo(Writer writer);
-
-    /**
-     * Writes the {@link #getEffectivePom()} xml to a file while applying the {@link #withXml(org.gradle.api.Action)} actions.
-     * The path is resolved as defined by {@link org.gradle.api.Project#files(Object...)}
-     * The file will be encoded as UTF-8.
-     *
-     * @param path The path of the file to write the pom xml into.
-     * @return this
-     */
-    MavenPom writeTo(Object path);
-
-    /**
-     * <p>Adds a closure to be called when the pom has been configured. The pom is passed to the closure as a
-     * parameter.</p>
-     *
-     * @param closure The closure to execute when the pom has been configured.
-     * @return this
-     */
-    MavenPom whenConfigured(Closure closure);
-
-    /**
-     * <p>Adds an action to be called when the pom has been configured. The pom is passed to the action as a
-     * parameter.</p>
-     *
-     * @param action The action to execute when the pom has been configured.
-     * @return this
-     */
-    MavenPom whenConfigured(Action<MavenPom> action);
-
-    /**
-     * <p>Adds a closure to be called when the POM 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 action can modify the XML.</p>
-     *
-     * @param closure The closure to execute when the POM XML has been created.
-     * @return this
-     */
-    MavenPom withXml(Closure closure);
-
-    /**
-     * <p>Adds an action to be called when the POM 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.</p>
-     *
-     * @param action The action to execute when the POM XML has been created.
-     * @return this
-     */
-    MavenPom withXml(Action<XmlProvider> action);
-
-    /**
-     * Returns the configuration container used for mapping configurations to maven scopes.
-     */
-    ConfigurationContainer getConfigurations();
-
-    /**
-     * Sets the configuration container used for mapping configurations to maven scopes.
-     * @return this
-     */
-    MavenPom setConfigurations(ConfigurationContainer configurations);
-
-    /**
-     * Returns a pom with the generated dependencies and the {@link #whenConfigured(org.gradle.api.Action)} actions applied.
-     *
-     * @return the effective pom
-     */
-    MavenPom getEffectivePom();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
deleted file mode 100644
index 63f9b50..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
+++ /dev/null
@@ -1,51 +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.artifacts.maven;
-
-import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-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.
-     */
-    Object 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/core/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
deleted file mode 100644
index 91a53b4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
+++ /dev/null
@@ -1,136 +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.artifacts.maven;
-
-import groovy.lang.Closure;
-import org.gradle.api.internal.artifacts.publish.maven.deploy.PomFilter;
-
-/**
- * Manages a set of {@link MavenPom} instances and their associated {@link PublishFilter} instances.
- *
- * @author Hans Dockter
- */
-public interface PomFilterContainer {
-    String DEFAULT_ARTIFACT_POM_NAME = "default";
-
-    /**
-     * Returns the default filter being used. .
-     *
-     * @see #setFilter(org.gradle.api.artifacts.maven.PublishFilter)
-     */
-    PublishFilter getFilter();
-
-    /**
-     * <p>Sets the default filter to be used. This filter is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
-     * If at least one custom filter has been added the default filter is not used any longer.</p>
-     * <p>The default for this property is {@link PublishFilter#ALWAYS_ACCEPT}.
-     * If there is only one artifact you are fine with this filter. If there is more than one artifact, deployment will lead to
-     * an exception, if you don't specify a filter that selects the artifact to deploy. If you want to deploy more than one artiact you have
-     * to use the (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)} method.</p>
-     *
-     * @param defaultFilter
-     * @see #getFilter()
-     */
-    void setFilter(PublishFilter defaultFilter);
-
-    /**
-     * Returns the pom property of the custom filter.
-     * The pom property can be used to customize the pom generation. By default the properties of such a pom object are all null.
-     * Null means that Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
-     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use those instead.
-     *
-     * @return The Maven Pom
-     */
-    MavenPom getPom();
-
-    /**
-     * <p>Sets the default pom to be used. This pom is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
-     * If at least one custom filter has been added the default pom is not used any longer.</p>
-     * <p>Usually you don't need to set this property as the default value provides you a pom object you might use for configuration.
-     * By default the properties of such a pom object are all null.
-     * If they are null, Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
-     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use this instead.</p>
-     *
-     * @param defaultPom
-     */
-    void setPom(MavenPom defaultPom);
-
-    /**
-     * If you want to deploy more than one artifact you need to define filters to select each of those artifacts. The method
-     * returns a pom object associated with this filter, that allows you to customize the pom generation for the artifact selected
-     * by the filter.
-     *
-     * @param name The name of the filter
-     * @param publishFilter The filter to use
-     * @return The pom associated with the filter
-     */
-    MavenPom addFilter(String name, PublishFilter publishFilter);
-
-    /**
-     * Adds a publish filter.
-     *
-     * @param name   The name of the filter
-     * @param filter The filter
-     * @return The Maven pom associated with the closure
-     * @see PublishFilter
-     * @see PomFilterContainer#addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)
-     */
-    MavenPom addFilter(String name, Closure filter);
-
-    /**
-     * Returns a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
-     *
-     * @param name The name of the filter
-     */
-    PublishFilter filter(String name);
-
-    /**
-     * Sets the default publish filter.
-     *
-     * @param filter The filter to be set
-     * @see PublishFilter
-     * @see PomFilterContainer#setFilter(org.gradle.api.artifacts.maven.PublishFilter)
-     */
-    void filter(Closure filter);
-
-    /**
-     * Returns the pom associated with a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
-     *
-     * @param name The name of the filter.
-     */
-    MavenPom pom(String name);
-
-    /**
-     * Configures a pom by a closure. The closure statements are delegated to the pom object associated with the given name.
-     *
-     * @param name
-     * @param configureClosure
-     * @return The pom object associated with the given name.
-     * @see PomFilterContainer#pom(String)
-     */
-    MavenPom pom(String name, Closure configureClosure);
-
-    /**
-     * Configures the default pom by a closure. The closure statements are delegated to the default pom.
-     *
-     * @param configureClosure
-     * @return The default pom.
-     * @see PomFilterContainer#getPom()
-     */
-    MavenPom pom(Closure configureClosure);
-
-    Iterable<PomFilter> getActivePomFilters();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/XmlProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/XmlProvider.java
deleted file mode 100644
index 2f87b43..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/XmlProvider.java
+++ /dev/null
@@ -1,50 +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.artifacts.maven;
-
-import groovy.util.Node;
-import org.w3c.dom.Element;
-
-/**
- * Provides various ways to access the content of an XML document.
- *
- * @author Hans Dockter
- */
-public interface XmlProvider {
-    /**
-     * Returns the XML document as a {@link StringBuilder}. Changes to the returned instance will be applied to the XML.
-     * The returned instance is only valid until one of the other methods on this interface are called.
-     *
-     * @return A {@code StringBuilder} representation of the XML.
-     */
-    StringBuilder asString();
-
-    /**
-     * Returns the XML document as a Groovy {@link groovy.util.Node}. Changes to the returned instance will be applied
-     * to the XML. The returned instance is only valid until one of the other methods on this interface are called.
-     *
-     * @return A {@code Node} representation of the XML.
-     */
-    Node asNode();
-
-    /**
-     * Returns the XML document as a DOM {@link org.w3c.dom.Element}. Changes to the returned instance will be applied
-     * to the XML. The returned instance is only valid until one of the other methods on this interface are called.
-     *
-     * @return An {@code Element} representation of the XML.
-     */
-    Element asElement();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/ArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/ArtifactRepository.java
new file mode 100644
index 0000000..3276f1e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/ArtifactRepository.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.repositories;
+
+/**
+ * A repository for resolving and publishing artifacts.
+ */
+public interface ArtifactRepository {
+    /**
+     * Returns the name for this repository. A name must be unique amongst a repository set. A default name is provided for the repository if none
+     * is provided.
+     *
+     * <p>The name is used in logging output and error reporting to point to information related to this repository.
+     *
+     * @return The name.
+     */
+    String getName();
+
+    /**
+     * Sets the name for this repository.
+     *
+     * @param name The name. Must not be null.
+     */
+    void setName(String name);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/AuthenticationSupported.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/AuthenticationSupported.java
new file mode 100644
index 0000000..a9a5c92
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/AuthenticationSupported.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import groovy.lang.Closure;
+
+/**
+ * An artifact repository which supports username/password authentication.
+ */
+public interface AuthenticationSupported {
+
+    /**
+     * Provides the Credentials used to authenticate to this repository.
+     * @return The credentials
+     */
+    PasswordCredentials getCredentials();
+
+    /**
+     * Configure the Credentials for this repository using the supplied Closure.
+     *
+     * <pre autoTested=''>
+     * repositories {
+     *     maven {
+     *         credentials {
+     *             username = 'joe'
+     *             password = 'secret'
+     *         }
+     *     }
+     * }
+     * </pre>
+     */
+    void credentials(Closure closure);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java
new file mode 100644
index 0000000..dc2b0df
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * A repository that looks into a number of directories for artifacts. The artifacts are expected to be located in the root of the specified directories.
+ * The repository ignores any group/organization information specified in the dependency section of your build script. If you only use this kind of
+ * resolver you can specify your dependencies like <code>:junit:4.8.1</code> instead of <code>junit:junit:4.8.1</code>.
+ *
+ * <p>To resolve a dependency, this resolver looks for one of the following files. It will return the first match it finds:
+ *
+ * <ul>
+ *
+ * <li>[artifact]-[version].[ext]
+ * <li>[artifact]-[version]-[classifier].[ext]
+ * <li>[artifact].[ext]
+ * <li>[artifact]-[classifier].[ext]
+ *
+ * </ul>
+ *
+ * So, for example, to resolve <code>:junit:junit:4.8.1</code>, this repository will look for <code>junit-4.8.1.jar</code> and then <code>junit.jar</code>.
+ */
+public interface FlatDirectoryArtifactRepository extends ArtifactRepository {
+    /**
+     * Returns the directories where this repository will look for artifacts.
+     *
+     * @return The directories. Never null.
+     */
+    Set<File> getDirs();
+
+    /**
+     * Adds a directory where this repository will look for artifacts.
+     *
+     * <p>The provided value are evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     *
+     * @param dir the directory
+     */
+    void dir(Object dir);
+
+    /**
+     * Adds some directories where this repository will look for artifacts.
+     *
+     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     *
+     * @param dirs the directories.
+     */
+    void dirs(Object... dirs);
+
+    /**
+     * Sets the directories where this repository will look for artifacts.
+     *
+     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     *
+     * @param dirs the directories.
+     */
+    void setDirs(Iterable<?> dirs);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java
new file mode 100644
index 0000000..5763950
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import groovy.lang.Closure;
+
+import java.net.URI;
+
+/**
+ * An artifact repository which uses an Ivy format to store artifacts and meta-data.
+ */
+public interface IvyArtifactRepository extends ArtifactRepository, AuthenticationSupported {
+
+    String GRADLE_ARTIFACT_PATTERN = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])";
+    String GRADLE_IVY_PATTERN = "[organisation]/[module]/[revision]/ivy-[revision].xml";
+
+    String MAVEN_ARTIFACT_PATTERN = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])";
+    String MAVEN_IVY_PATTERN = "[organisation]/[module]/[revision]/ivy-[revision].xml";
+
+    /**
+     * The base URL of this repository.
+     *
+     * @return The URL.
+     */
+    URI getUrl();
+
+    /**
+     * Sets the base URL of this repository. The provided value is evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means,
+     * for example, you can pass in a File object or a relative path which is evaluated relative to the project directory.
+     *
+     * File are resolved based on the supplied URL and the configured {@link #layout(String, Closure)} for this repository.
+     *
+     * @param url The base URL.
+     */
+    void setUrl(Object url);
+
+    /**
+     * Adds an Ivy artifact pattern to use to locate artifacts in this repository. This pattern will be in addition to any layout-based patterns added via {@link #setUrl}.
+     *
+     * @param pattern The artifact pattern.
+     */
+    void artifactPattern(String pattern);
+
+    /**
+     * Adds an Ivy pattern to use to locate ivy files in this repository. This pattern will be in addition to any layout-based patterns added via {@link #setUrl}.
+     *
+     * @param pattern The ivy pattern.
+     */
+    void ivyPattern(String pattern);
+
+    /**
+     * Specifies the layout to use with this repository, based on the root url.
+     * See {@link #layout(String, Closure)}.
+     *
+     * @param layoutName The name of the layout to use.
+     */
+    void layout(String layoutName);
+
+    /**
+     * Specifies the layout to use with this repository, based on the root url. The returned layout is configured with the supplied closure.
+     * Available layouts are outlined below.
+     * <h4>'gradle'</h4>
+     * A Repository Layout that applies the following patterns:
+     * <ul>
+     *     <li>Artifacts: <code>$baseUri/{@value #GRADLE_ARTIFACT_PATTERN}</code></li>
+     *     <li>Ivy: <code>$baseUri/{@value #GRADLE_IVY_PATTERN}</code></li>
+     * </ul>
+     *
+     * <h4>'maven'</h4>
+     * A Repository Layout that applies the following patterns:
+     * <ul>
+     *     <li>Artifacts: <code>$baseUri/{@value #MAVEN_ARTIFACT_PATTERN}</code></li>
+     *     <li>Ivy: <code>$baseUri/{@value #MAVEN_IVY_PATTERN}</code></li>
+     * </ul>
+     * Following the maven convention, the 'organisation' value is further processed by replacing '.' with '/'.
+     *
+     * <h4>'pattern'</h4>
+     * A repository layout that allows custom patterns to be defined. eg:
+     * <pre autoTested="">
+     * repositories {
+     *     ivy {
+     *         layout 'pattern' , {
+     *             artifact '[module]/[revision]/[artifact](.[ext])'
+     *             ivy '[module]/[revision]/ivy.xml'
+     *         }
+     *     }
+     * }
+     * </pre>
+     *
+     * @param layoutName The name of the layout to use.
+     * @param config The closure used to configure the layout.
+     */
+    void layout(String layoutName, Closure config);
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java
new file mode 100644
index 0000000..bdff5e6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import java.net.URI;
+import java.util.Set;
+
+/**
+ * An artifact repository which uses a Maven format to store artifacts and meta-data.
+ */
+public interface MavenArtifactRepository extends ArtifactRepository, AuthenticationSupported {
+
+    /**
+     * The base URL of this repository. This URL is used to find both POMs and artifact files. You can add additional URLs to use to look for artifact files, such as jars, using {@link
+     * #setArtifactUrls(Iterable)}.
+     *
+     * @return The URL.
+     */
+    URI getUrl();
+
+    /**
+     * Sets the base URL of this repository. This URL is used to find both POMs and artifact files. You can add additional URLs to use to look for artifact files, such as jars, using {@link
+     * #setArtifactUrls(Iterable)}.
+     *
+     * <p>The provided value is evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated relative
+     * to the project directory.
+     *
+     * @param url The base URL.
+     */
+    void setUrl(Object url);
+
+    /**
+     * Returns the additional URLs to use to find artifact files. Note that these URLs are not used to find POM files.
+     *
+     * @return The additional URLs. Returns an empty list if there are no such URLs.
+     */
+    Set<URI> getArtifactUrls();
+
+    /**
+     * Adds some additional URLs to use to find artifact files. Note that these URLs are not used to find POM files.
+     *
+     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated
+     * relative to the project directory.
+     *
+     * @param urls The URLs to add.
+     */
+    void artifactUrls(Object... urls);
+
+    /**
+     * Sets the additional URLs to use to find artifact files. Note that these URLs are not used to find POM files.
+     *
+     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated
+     * relative to the project directory.
+     *
+     * @param urls The URLs.
+     */
+    void setArtifactUrls(Iterable<?> urls);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/PasswordCredentials.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/PasswordCredentials.java
new file mode 100644
index 0000000..01c93cd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/PasswordCredentials.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.repositories;
+
+/**
+ * A username/password credentials that can be used to login to password-protected remote repository.
+ */
+public interface PasswordCredentials {
+    /**
+     * Returns the user name to use when authenticating to this repository.
+     *
+     * @return The user name. May be null.
+     */
+    String getUsername();
+
+    /**
+     * Sets the user name to use when authenticating to this repository.
+     *
+     * @param userName The user name. May be null.
+     */
+    void setUsername(String userName);
+
+    /**
+     * Returns the password to use when authenticating to this repository.
+     *
+     * @return The password. May be null.
+     */
+    String getPassword();
+
+    /**
+     * Sets the password to use when authenticating to this repository.
+     *
+     * @param password The password. May be null.
+     */
+    void setPassword(String password);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/WebdavResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/WebdavResolver.java
deleted file mode 100644
index 5a20d83..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/WebdavResolver.java
+++ /dev/null
@@ -1,46 +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.api.artifacts.repositories;
-
-import org.apache.ivy.plugins.resolver.RepositoryResolver;
-import org.gradle.api.internal.artifacts.repositories.WebdavRepository;
-
-/**
- * A {@link org.apache.ivy.plugins.resolver.RepositoryResolver} for webdav based repositories.
- *
- * @author Hans Dockter
- */
-public class WebdavResolver extends RepositoryResolver {
-    public WebdavResolver() {
-         setRepository(new WebdavRepository());
-    }
-
-    private WebdavRepository getWebdavRepository() {
-        return (WebdavRepository) getRepository();
-    }
-
-    public String getTypeName() {
-        return "webdav";
-    }
-
-    public void setUserPassword(String userPassword) {
-       getWebdavRepository().setUserPassword(userPassword);
-    }
-
-    public void setUser(String user) {
-        getWebdavRepository().setUser(user);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/DependencySpecs.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/DependencySpecs.java
deleted file mode 100644
index ba80997..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/DependencySpecs.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2007-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.artifacts.specs;
-
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.specs.Spec;
-
-/**
- * Various {@link Spec} implementations for selecting {@link Dependency} instances.
- *
- * @author Hans Dockter
- */
-public class DependencySpecs {
-    /**
-     * Deprecated. Use {@link Type} directly instead (it already implements Spec<Dependency>).
-     */
-    @Deprecated
-    public static Spec<Dependency> type(Type type) {
-        return new DependencyTypeSpec<Dependency>(type);
-    }
-
-    private static class DependencyTypeSpec<T extends Dependency> implements Spec<T> {
-
-        private Type type;
-
-        public DependencyTypeSpec(Type type) {
-            this.type = type;
-        }
-
-        public boolean isSatisfiedBy(Dependency dependency) {
-            return type.isOf(dependency);
-        }
-
-        public Type getType() {
-            return type;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            DependencyTypeSpec typeSpec = (DependencyTypeSpec) o;
-
-            if (type != typeSpec.type) {
-                return false;
-            }
-
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            return type != null ? type.hashCode() : 0;
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/Type.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/Type.java
deleted file mode 100644
index d62f5e8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/Type.java
+++ /dev/null
@@ -1,51 +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.artifacts.specs;
-
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.specs.Spec;
-
-/**
- * Dependency types.
- */
-public enum Type implements Spec<Dependency> {
-    EXTERNAL {
-        public boolean isSatisfiedBy(Dependency dependency) {
-            return dependency instanceof ExternalDependency;
-        }
-        public boolean isOf(Dependency dependency) {
-            return isSatisfiedBy(dependency);
-        }
-    },
-    PROJECT {
-        public boolean isSatisfiedBy(Dependency dependency) {
-            return dependency instanceof ProjectDependency;
-        }
-        public boolean isOf(Dependency dependency) {
-            return isSatisfiedBy(dependency);
-        }
-    };
-
-
-    /**
-     * Deprecated. Use isSatisfiedBy() instead.
-     */
-    @Deprecated
-    public abstract boolean isOf(Dependency dependency);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/package-info.java
deleted file mode 100644
index 2e75726..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/specs/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 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.
- */
-
-/**
- * Artifact dependency criteria.
- */
-package org.gradle.api.artifacts.specs;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConvenienceProperty.java b/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConvenienceProperty.java
deleted file mode 100644
index c21e4b0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConvenienceProperty.java
+++ /dev/null
@@ -1,73 +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.api.dsl;
-
-/**
- * ConventionProperty can be assigned but <b>cannot</b> be mutated (even if the object is mutable!)
- * <p>
- * Understanding convention properties is important mostly for collections
- * because one might want to mutate the collection but it wouldn't work.
- * <p>
- * Consider this example:
- *
- * <pre>
- * someTask {
- *   //Convention properties cannot be mutated,
- *   //even if the object is mutable!
- *   conventionProperty.add('c')  //WRONG!
- *
- *   //However, convention properties can be assigned:
- *   conventionProperty += 'c'  //OK
- *   conventionProperty = ['a', 'b']  //OK
- *
- *   //Simple properties can be mutated or assigned:
- *   simpleProperty = ['1.5']  //OK
- *   simpleProperty.add('1.5')  //OK
- * }
- * </pre>
- *
- * You may wonder why Gradle uses convention properties.
- * The reason for that is that internally, convention properties are evaluated 'lazily'.
- * This means that Gradle can configure tasks and objects with reasonable defaults
- * without worrying about the order of statements that configure the build. Example:
- *
- * <pre>
- * apply plugin: 'java'
- *
- * test {
- *   //test task has a testClassesDir convention property
- *   //that is by default configured to 'test classes dir'
- *   //testClassesDir = sourceSets.test.classesDir
- * }
- *
- * //what if someone reconfigured the 'test classes dir'
- * //after the 'test' task was configured? Like that:
- * sourceSets.test.classesDir = new File(buildDir, 'test-classes')
- *
- * //will the already-configured test.testClassesDir property
- * //on a 'test' task point to a wrong folder?
- * </pre>
- *
- * Answer: It will all work fine!
- * <p>
- * Thanks to the 'lazy' evaluation of the convention properties
- * the user can reconfigure the sourceSets anywhere in the gradle script -
- * and still the test.testClassesDir will point to the right folder.
- *
- * Author: Szczepan Faber, created at: 4/19/11
- */
-public class ConvenienceProperty {}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java b/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java
new file mode 100644
index 0000000..12c0569
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/dsl/ConventionProperty.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dsl;
+
+/**
+ * ConventionProperty can be assigned but <b>cannot</b> be mutated (even if the object is mutable!)
+ * <p>
+ * Understanding convention properties is important mostly for collections
+ * because one might want to mutate the collection but it wouldn't work
+ * (actually mutating may work but it will be sensitive to the evaluation order).
+ * <p>
+ * Consider this example:
+ *
+ * <pre>
+ * someTask {
+ *   //Convention properties cannot be mutated,
+ *   //even if the object is mutable!
+ *   conventionProperty.add('c')  //WRONG!
+ *
+ *   //However, convention properties can be assigned:
+ *   conventionProperty = ['a', 'b']  //OK
+ *
+ *   //Following may work but depends on the order of evaluation:
+ *   conventionProperty -= 'a'  //SENSITIVE
+ *
+ *   //Simple properties can be mutated or assigned:
+ *   simpleProperty = ['1.5']  //OK
+ *   simpleProperty.add('1.5')  //OK
+ * }
+ * </pre>
+ *
+ * You may wonder why Gradle uses convention properties.
+ * The reason for that is that internally, convention properties are evaluated 'lazily'.
+ * This means that Gradle can configure tasks and objects with reasonable defaults
+ * without worrying about the order of statements that configure the build. Example:
+ *
+ * <pre>
+ * apply plugin: 'java'
+ *
+ * test {
+ *   //test task has a testClassesDir convention property
+ *   //that is by default configured to 'test classes dir'
+ *   //testClassesDir = sourceSets.test.classesDir
+ * }
+ *
+ * //what if someone reconfigured the 'test classes dir'
+ * //after the 'test' task was configured? Like that:
+ * sourceSets.test.classesDir = new File(buildDir, 'test-classes')
+ *
+ * //will the already-configured test.testClassesDir property
+ * //on a 'test' task point to a wrong folder?
+ * </pre>
+ *
+ * Answer: It will all work fine!
+ * <p>
+ * Thanks to the 'lazy' evaluation of the convention properties
+ * the user can reconfigure the sourceSets anywhere in the gradle script -
+ * and still the test.testClassesDir will point to the right folder.
+ *
+ * @author Szczepan Faber, created at: 4/19/11
+ */
+public class ConventionProperty {}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/dsl/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/dsl/package-info.java
index 3c59c30..60a9b5a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/dsl/package-info.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/dsl/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * See docs for {@link PropertyKinds}
+ * dsl related classes.
  */
 package org.gradle.api.dsl;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
index 8178ca4..46c569d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
@@ -1,125 +1,129 @@
-/*
- * 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.file;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-
-import java.util.regex.Pattern;
-
-/**
- * Specifies the destination of a copy.
- */
-public interface CopyProcessingSpec extends ContentFilterable {
-    /**
-     * Specifies the destination directory for a copy. The destination is evaluated as for {@link
-     * org.gradle.api.Project#file(Object)}.
-     *
-     * @param destPath Path to the destination directory for a Copy
-     * @return this
-     */
-    CopyProcessingSpec into(Object destPath);
-
-    /**
-     * Renames a source file to a different relative location under the target directory. The closure will be called
-     * with a single parameter, the name of the file.  The closure should return a String object with a new target name.
-     * The closure may return null, in which case the original name will be used.
-     *
-     * @param closure rename closure
-     * @return this
-     */
-    CopyProcessingSpec rename(Closure closure);
-
-    /**
-     * Renames files based on a regular expression.  Uses java.util.regex type of regular expressions.  Note that the
-     * replace string should use the '$1' syntax to refer to capture groups in the source regular expression.  Files
-     * that do not match the source regular expression will be copied with the original name.
-     *
-     * <p> Example:
-     * <pre>
-     * rename '(.*)_OEM_BLUE_(.*)', '$1$2'
-     * </pre>
-     * would map the file 'style_OEM_BLUE_.css' to 'style.css'
-     *
-     * @param sourceRegEx Source regular expression
-     * @param replaceWith Replacement string (use $ syntax for capture groups)
-     * @return this
-     */
-    CopyProcessingSpec rename(String sourceRegEx, String replaceWith);
-
-    /**
-     * Renames files based on a regular expression. See {@link #rename(String, String)}.
-     *
-     * @param sourceRegEx Source regular expression
-     * @param replaceWith Replacement string (use $ syntax for capture groups)
-     * @return this
-     */
-    CopyProcessingSpec rename(Pattern sourceRegEx, String replaceWith);
-
-    /**
-     * Returns the Unix permissions to use for the target files. It is dependent on the copy action implementation
-     * whether these permissions will actually be applied.
-     *
-     * @return The file permissions.
-     */
-    int getFileMode();
-
-    /**
-     * Sets the Unix permissions to use for the target files. It is dependent on the copy action implementation whether
-     * these permissions will actually be applied.
-     *
-     * @param mode The file permissions.
-     * @return this
-     */
-    CopyProcessingSpec setFileMode(int mode);
-
-    /**
-     * Returns the Unix permissions to use for the target directories. It is dependent on the copy action implementation
-     * whether these permissions will actually be applied.
-     *
-     * @return The directory permissions.
-     */
-    int getDirMode();
-
-    /**
-     * Sets the Unix permissions to use for the target directories. It is dependent on the copy action implementation
-     * whether these permissions will actually be applied.
-     *
-     * @param mode The directory permissions.
-     * @return this
-     */
-    CopyProcessingSpec setDirMode(int mode);
-
-    /**
-     * Adds an action to be applied to each file as it about to be copied into its destination. The action can change
-     * the destination path of the file, filter the contents of the file, or exclude the file from the result entirely.
-     * Actions are executed in the order added, and are inherited from the parent spec.
-     *
-     * @param action The action to execute.
-     * @return this
-     */
-    CopyProcessingSpec eachFile(Action<? super FileCopyDetails> action);
-
-    /**
-     * Adds an action to be applied to each file as it about to be copied into its destination. The given closure is
-     * called with a {@link org.gradle.api.file.FileCopyDetails} as its parameter. Actions are executed in the order
-     * added, and are inherited from the parent spec.
-     *
-     * @param closure The action to execute.
-     * @return this
-     */
-    CopyProcessingSpec eachFile(Closure closure);
-}
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+
+import java.util.regex.Pattern;
+
+/**
+ * Specifies the destination of a copy.
+ */
+public interface CopyProcessingSpec extends ContentFilterable {
+    /**
+     * Specifies the destination directory for a copy. The destination is evaluated as for {@link
+     * org.gradle.api.Project#file(Object)}.
+     *
+     * @param destPath Path to the destination directory for a Copy
+     * @return this
+     */
+    CopyProcessingSpec into(Object destPath);
+
+    /**
+     * Renames a source file to a different relative location under the target directory. The closure will be called
+     * with a single parameter, the name of the file.  The closure should return a String object with a new target name.
+     * The closure may return null, in which case the original name will be used.
+     *
+     * @param closure rename closure
+     * @return this
+     */
+    CopyProcessingSpec rename(Closure closure);
+
+    /**
+     * Renames files based on a regular expression.  Uses java.util.regex type of regular expressions.  Note that the
+     * replace string should use the '$1' syntax to refer to capture groups in the source regular expression.  Files
+     * that do not match the source regular expression will be copied with the original name.
+     *
+     * <p> Example:
+     * <pre>
+     * rename '(.*)_OEM_BLUE_(.*)', '$1$2'
+     * </pre>
+     * would map the file 'style_OEM_BLUE_.css' to 'style.css'
+     *
+     * @param sourceRegEx Source regular expression
+     * @param replaceWith Replacement string (use $ syntax for capture groups)
+     * @return this
+     */
+    CopyProcessingSpec rename(String sourceRegEx, String replaceWith);
+
+    /**
+     * Renames files based on a regular expression. See {@link #rename(String, String)}.
+     *
+     * @param sourceRegEx Source regular expression
+     * @param replaceWith Replacement string (use $ syntax for capture groups)
+     * @return this
+     */
+    CopyProcessingSpec rename(Pattern sourceRegEx, String replaceWith);
+
+    /**
+     * Returns the Unix permissions to use for the target files. {@code null} means that existing
+     * permissions are preserved. It is dependent on the copy action implementation whether these permissions
+     * will actually be applied.
+     *
+     * @return The file permissions, or {@code null} if existing permissions should be preserved.
+     */
+    Integer getFileMode();
+
+    /**
+     * Sets the Unix permissions to use for the target files. {@code null} means that existing
+     * permissions are preserved. It is dependent on the copy action implementation whether these permissions
+     * will actually be applied.
+     *
+     * @param mode The file permissions.
+     * @return this
+     */
+    CopyProcessingSpec setFileMode(Integer mode);
+
+    /**
+     * Returns the Unix permissions to use for the target directories. {@code null} means that existing
+     * permissions are preserved. It is dependent on the copy action implementation whether these permissions
+     * will actually be applied.
+     *
+     * @return The directory permissions, or {@code null} if existing permissions should be preserved.
+     */
+    Integer getDirMode();
+
+    /**
+     * Sets the Unix permissions to use for the target directories. {@code null} means that existing
+     * permissions are preserved. It is dependent on the copy action implementation whether these permissions
+     * will actually be applied.
+     *
+     * @param mode The directory permissions.
+     * @return this
+     */
+    CopyProcessingSpec setDirMode(Integer mode);
+
+    /**
+     * Adds an action to be applied to each file as it is about to be copied into its destination. The action can change
+     * the destination path of the file, filter the contents of the file, or exclude the file from the result entirely.
+     * Actions are executed in the order added, and are inherited from the parent spec.
+     *
+     * @param action The action to execute.
+     * @return this
+     */
+    CopyProcessingSpec eachFile(Action<? super FileCopyDetails> action);
+
+    /**
+     * Adds an action to be applied to each file as it about to be copied into its destination. The given closure is
+     * called with a {@link org.gradle.api.file.FileCopyDetails} as its parameter. Actions are executed in the order
+     * added, and are inherited from the parent spec.
+     *
+     * @param closure The action to execute.
+     * @return this
+     */
+    CopyProcessingSpec eachFile(Closure closure);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
index 8b3c497..65cf25f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
@@ -194,15 +194,8 @@ public interface CopySpec extends CopySourceSpec, CopyProcessingSpec, PatternFil
     CopySpec into(Object destPath);
 
     /**
-     * Creates and configures a child {@code CopySpec} with the given destination path. The destination path is evaluated based on its type, as follows:
-     *
-     * <ul>
-     *
-     * <li>A closure: The return value of the closure is recursively evaluated.</li>
-     *
-     * <li>Anything else: The {@code toString()} value of the given path.</li>
-     *
-     * </ul>
+     * Creates and configures a child {@code CopySpec} with the given destination path.
+     * The destination is evaluated as for {@link org.gradle.api.Project#file(Object)}.
      *
      * @param destPath Path to the destination directory for a Copy
      * @param configureClosure The closure to use to configure the child {@code CopySpec}.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
index bd794b0..59a2ab9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
@@ -48,4 +48,11 @@ public interface FileCopyDetails extends FileTreeElement, ContentFilterable {
      * @param path the new path for this file.
      */
     void setRelativePath(RelativePath path);
+
+    /**
+     * Sets the Unix permissions of this file.
+     *
+     * @param mode the Unix permissions, e.g. {@code 0644}.
+     */
+    void setMode(int mode);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/FileTreeElement.java b/subprojects/core/src/main/groovy/org/gradle/api/file/FileTreeElement.java
index 17d6af0..c433e66 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/FileTreeElement.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/FileTreeElement.java
@@ -98,4 +98,11 @@ public interface FileTreeElement {
      * @return The path. Never returns null.
      */
     RelativePath getRelativePath();
+
+    /**
+     * Returns the Unix permissions of this file, e.g. {@code 0644}.
+     *
+     * @return The Unix file permissions.
+     */
+    int getMode();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java b/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
index c53755c..e189a4d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.file;
 
+import org.gradle.api.Named;
 import org.gradle.api.tasks.util.PatternFilterable;
 
 import java.io.File;
@@ -26,8 +27,13 @@ import java.util.Set;
  *
  * TODO - configure includes/excludes for individual source dirs, and sync up with CopySpec
  * TODO - allow add FileTree
- */
-public interface SourceDirectorySet extends FileTree, PatternFilterable {
+' */
+public interface SourceDirectorySet extends FileTree, PatternFilterable, Named {
+
+    /**
+     * A concise name for the source directory set (typically used to identify it in a collection).
+     */
+    String getName();
 
     /**
      * Adds the given source directory to this set.
@@ -58,7 +64,7 @@ public interface SourceDirectorySet extends FileTree, PatternFilterable {
      * @param srcPaths The source directories. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}
      * @return this
      */
-    SourceDirectorySet setSrcDirs(Iterable<Object> srcPaths);
+    SourceDirectorySet setSrcDirs(Iterable<?> srcPaths);
 
     /**
      * Adds the given source to this set.
@@ -67,7 +73,7 @@ public interface SourceDirectorySet extends FileTree, PatternFilterable {
      * @return this
      */
     SourceDirectorySet source(SourceDirectorySet source);
-    
+
     /**
      * Returns the source directory trees which make up this set. Does not filter source directories which do not exist.
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
index 0519ca1..a4f96be 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
@@ -16,22 +16,23 @@
 
 package org.gradle.api.internal;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
 import groovy.lang.*;
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
-import org.gradle.util.ReflectionUtil;
+import org.gradle.api.plugins.ExtensionAware;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 public abstract class AbstractClassGenerator implements ClassGenerator {
     private static final Map<Class, Map<Class, Class>> GENERATED_CLASSES = new HashMap<Class, Map<Class, Class>>();
 
     public <T> T newInstance(Class<T> type, Object... parameters) {
-        return type.cast(ReflectionUtil.newInstance(generate(type), parameters));
+        Instantiator instantiator = new DirectInstantiator();
+        return instantiator.newInstance(generate(type), parameters);
     }
 
     public <T> Class<? extends T> generate(Class<T> type) {
@@ -64,6 +65,9 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
             builder.startClass(isConventionAware, isDynamicAware);
 
             if (isDynamicAware && !DynamicObjectAware.class.isAssignableFrom(type)) {
+                if (ExtensionAware.class.isAssignableFrom(type)) {
+                    throw new UnsupportedOperationException("A type that implements ExtensionAware must currently also implement DynamicObjectAware.");
+                }
                 builder.mixInDynamicAware();
             }
             if (isDynamicAware && !GroovyObject.class.isAssignableFrom(type)) {
@@ -83,7 +87,10 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
                 }
             }
 
-            Collection<String> skipProperties = Arrays.asList("metaClass", "conventionMapping", "convention", "asDynamicObject");
+            Collection<String> skipProperties = Arrays.asList("metaClass", "conventionMapping", "convention", "asDynamicObject", "extensions");
+
+            Set<MetaBeanProperty> settableProperties = new HashSet<MetaBeanProperty>();
+            Set<MetaBeanProperty> conventionProperties = new HashSet<MetaBeanProperty>();
 
             MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(type);
             for (MetaProperty property : metaClass.getProperties()) {
@@ -92,31 +99,81 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
                 }
                 if (property instanceof MetaBeanProperty) {
                     MetaBeanProperty metaBeanProperty = (MetaBeanProperty) property;
+
+                    boolean needsConventionMapping = true;
                     MetaMethod getter = metaBeanProperty.getGetter();
                     if (getter == null) {
-                        continue;
+                        needsConventionMapping = false;
+                    } else {
+                        if (Modifier.isFinal(getter.getModifiers()) || Modifier.isPrivate(getter.getModifiers())) {
+                            needsConventionMapping = false;
+                        } else {
+                            Class declaringClass = getter.getDeclaringClass().getTheClass();
+                            if (declaringClass.isAssignableFrom(noMappingClass)) {
+                                needsConventionMapping = false;
+                            }
+                        }
                     }
-                    if (Modifier.isFinal(getter.getModifiers()) || Modifier.isPrivate(getter.getModifiers())) {
-                        continue;
+
+                    if (needsConventionMapping) {
+                        conventionProperties.add(metaBeanProperty);
+                        builder.addGetter(metaBeanProperty);
                     }
-                    if (getter.getReturnType().isPrimitive()) {
+
+                    MetaMethod setter = metaBeanProperty.getSetter();
+                    if (setter == null || Modifier.isPrivate(setter.getModifiers())) {
                         continue;
                     }
-                    Class declaringClass = getter.getDeclaringClass().getTheClass();
-                    if (declaringClass.isAssignableFrom(noMappingClass)) {
-                        continue;
+
+                    if (needsConventionMapping && !Modifier.isFinal(setter.getModifiers())) {
+                        builder.addSetter(metaBeanProperty);
                     }
-                    builder.addGetter(metaBeanProperty);
 
-                    MetaMethod setter = metaBeanProperty.getSetter();
-                    if (setter == null) {
+                    if (Iterable.class.isAssignableFrom(property.getType())) {
                         continue;
                     }
-                    if (Modifier.isFinal(setter.getModifiers()) || Modifier.isPrivate(setter.getModifiers())) {
-                        continue;
+
+                    settableProperties.add(metaBeanProperty);
+                }
+            }
+
+            Multimap<String, MetaMethod> methods = HashMultimap.create();
+            Set<MetaMethod> actionMethods = new HashSet<MetaMethod>();
+
+            for (MetaMethod method : metaClass.getMethods()) {
+                if (method.isPrivate()) {
+                    continue;
+                }
+                if (method.getParameterTypes().length != 1) {
+                    continue;
+                }
+                methods.put(method.getName(), method);
+                if (method.getParameterTypes()[0].getTheClass().equals(Action.class)) {
+                    actionMethods.add(method);
+                }
+            }
+
+            for (MetaMethod method : actionMethods) {
+                boolean hasClosure = false;
+                for (MetaMethod otherMethod : methods.get(method.getName())) {
+                    if (otherMethod.getParameterTypes()[0].getTheClass().equals(Closure.class)) {
+                        hasClosure = true;
+                        break;
                     }
+                }
+                if (!hasClosure) {
+                    builder.addActionMethod(method);
+                }
+            }
 
-                    builder.addSetter(metaBeanProperty);
+            for (MetaBeanProperty property : settableProperties) {
+                Collection<MetaMethod> methodsForProperty = methods.get(property.getName());
+                if (methodsForProperty.isEmpty()) {
+                    builder.addSetMethod(property);
+                } else if (conventionProperties.contains(property)) {
+                    for (MetaMethod method : methodsForProperty) {
+                        builder.overrideSetMethod(property, method);
+                    }
                 }
             }
 
@@ -132,6 +189,7 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
         }
 
         cache.put(type, subclass);
+        cache.put(subclass, subclass);
         return subclass;
     }
 
@@ -154,6 +212,12 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
 
         void addSetter(MetaBeanProperty property) throws Exception;
 
+        void overrideSetMethod(MetaBeanProperty property, MetaMethod metaMethod) throws Exception;
+
+        void addSetMethod(MetaBeanProperty property) throws Exception;
+
         Class<? extends T> generate() throws Exception;
+
+        void addActionMethod(MetaMethod method) throws Exception;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassPathProvider.java
deleted file mode 100644
index 3873844..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassPathProvider.java
+++ /dev/null
@@ -1,165 +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.api.internal;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.util.ClasspathUtil;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.*;
-import java.util.regex.Pattern;
-
-public abstract class AbstractClassPathProvider implements ClassPathProvider, GradleDistributionLocator {
-    private final List<Pattern> all = Arrays.asList(Pattern.compile(".+"));
-    private final Map<String, List<Pattern>> classPaths = new HashMap<String, List<Pattern>>();
-    private final Scanner runtimeLibs;
-    private final Scanner pluginLibs;
-    private final Scanner coreImplLibs;
-    private final File gradleHome;
-
-    protected AbstractClassPathProvider() {
-        File codeSource = getClasspathForClass(DefaultClassPathProvider.class);
-        if (codeSource.isFile()) {
-            // Loaded from a JAR - assume we're running from the distribution
-            gradleHome = codeSource.getParentFile().getParentFile();
-            runtimeLibs = new DirScanner(new File(gradleHome + "/lib"));
-            pluginLibs = new DirScanner(new File(gradleHome + "/lib/plugins"));
-            coreImplLibs = new DirScanner(new File(gradleHome + "/lib/core-impl"));
-        } else {
-            // Loaded from a classes dir - assume we're running from the ide or tests
-            gradleHome = null;
-            runtimeLibs = new ClassPathScanner(codeSource);
-            pluginLibs = runtimeLibs;
-            coreImplLibs = runtimeLibs;
-        }
-    }
-
-    public File getGradleHome() {
-        return gradleHome;
-    }
-
-    protected void add(String name, List<Pattern> patterns) {
-        classPaths.put(name, patterns);
-    }
-
-    protected static List<Pattern> toPatterns(String... patternStrings) {
-        List<Pattern> patterns = new ArrayList<Pattern>();
-        for (String patternString : patternStrings) {
-            patterns.add(Pattern.compile(patternString + "-.+"));
-        }
-        return patterns;
-    }
-
-    public Set<File> findClassPath(String name) {
-        Set<File> matches = new LinkedHashSet<File>();
-        if (name.equals("GRADLE_RUNTIME")) {
-            runtimeLibs.find(all, matches);
-            return matches;
-        }
-        if (name.equals("GRADLE_PLUGINS")) {
-            pluginLibs.find(all, matches);
-            return matches;
-        }
-        if (name.equals("GRADLE_CORE_IMPL")) {
-            coreImplLibs.find(all, matches);
-            return matches;
-        }
-        List<Pattern> classPathPatterns = classPaths.get(name);
-        if (classPathPatterns != null) {
-            runtimeLibs.find(classPathPatterns, matches);
-            pluginLibs.find(classPathPatterns, matches);
-            coreImplLibs.find(classPathPatterns, matches);
-            return matches;
-        }
-        return null;
-    }
-
-    public static File getClasspathForClass(Class<?> targetClass) {
-        URI location;
-        try {
-            location = targetClass.getProtectionDomain().getCodeSource().getLocation().toURI();
-        } catch (URISyntaxException e) {
-            throw new UncheckedIOException(e);
-        }
-        if (!location.getScheme().equals("file")) {
-            throw new GradleException(String.format("Cannot determine Gradle home using codebase '%s'.", location));
-        }
-        return new File(location.getPath());
-    }
-
-    private static boolean matches(Iterable<Pattern> patterns, String name) {
-        for (Pattern pattern : patterns) {
-            if (pattern.matcher(name).matches()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private interface Scanner {
-        void find(Iterable<Pattern> patterns, Collection<File> into);
-    }
-
-    private static class DirScanner implements Scanner {
-        private final File dir;
-
-        private DirScanner(File dir) {
-            this.dir = dir;
-        }
-
-        public void find(Iterable<Pattern> patterns, Collection<File> into) {
-            for (File file : dir.listFiles()) {
-                if (matches(patterns, file.getName())) {
-                    into.add(file);
-                }
-            }
-        }
-    }
-
-    // This is used when running from the IDE or junit tests
-    private static class ClassPathScanner implements Scanner {
-        private final File classesDir;
-        private final Collection<URL> classpath;
-
-        private ClassPathScanner(File classesDir) {
-            this.classesDir = classesDir;
-            this.classpath = ClasspathUtil.getClasspath(getClass().getClassLoader());
-        }
-
-        public void find(Iterable<Pattern> patterns, Collection<File> into) {
-            if (matches(patterns, "gradle-core-version.jar")) {
-                into.add(classesDir);
-            }
-            for (URL url : classpath) {
-                if (url.getProtocol().equals("file")) {
-                    try {
-                        File file = new File(url.toURI());
-                        if (matches(patterns, file.getName())) {
-                            into.add(file);
-                        }
-                    } catch (URISyntaxException e) {
-                        throw new UncheckedIOException(e);
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDomainObjectCollection.java
deleted file mode 100644
index 21ebfe2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractDomainObjectCollection.java
+++ /dev/null
@@ -1,163 +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;
-
-import groovy.lang.Closure;
-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.util.ConfigureUtil;
-import org.gradle.util.DeprecationLogger;
-
-import java.util.*;
-
-public abstract class AbstractDomainObjectCollection<T> implements DomainObjectCollection<T> {
-    private final Store<T> store;
-
-    protected AbstractDomainObjectCollection(Store<T> store) {
-        this.store = store;
-    }
-
-    public Set<T> getAll() {
-        return new LinkedHashSet<T>(store.getAll());
-    }
-
-    public Set<T> findAll(Spec<? super T> spec) {
-        return Specs.filterIterable(store.getAll(), spec);
-    }
-
-    public Iterator<T> iterator() {
-        return getAll().iterator();
-    }
-
-    public void allObjects(Action<? super T> action) {
-        DeprecationLogger.nagUser("DomainObjectCollection.allObjects()", "all()");
-        all(action);
-    }
-
-    public void allObjects(Closure action) {
-        DeprecationLogger.nagUser("DomainObjectCollection.allObjects()", "all()");
-        all(action);
-    }
-
-    public void all(Action<? super T> action) {
-        whenObjectAdded(action);
-        for (T t : new ArrayList<T>(store.getAll())) {
-            action.execute(t);
-        }
-    }
-
-    public void all(Closure action) {
-        all(toAction(action));
-    }
-
-    public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Action<? super S> configureAction) {
-        DomainObjectCollection<S> result = withType(type);
-        result.all(configureAction);
-        return result;
-    }
-
-    public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Closure configureClosure) {
-        DomainObjectCollection<S> result = withType(type);
-        result.all(configureClosure);
-        return result;
-    }
-
-    public Action<? super T> whenObjectAdded(Action<? super T> action) {
-        store.objectAdded(action);
-        return action;
-    }
-
-    public Action<? super T> whenObjectRemoved(Action<? super T> action) {
-        store.objectRemoved(action);
-        return action;
-    }
-
-    public void whenObjectAdded(Closure action) {
-        whenObjectAdded(toAction(action));
-    }
-
-    private Action<? super T> toAction(final Closure action) {
-        return new Action<T>() {
-            public void execute(T t) {
-                ConfigureUtil.configure(action, t);
-            }
-        };
-    }
-
-    protected interface Store<S> {
-        Collection<? extends S> getAll();
-
-        void objectAdded(Action<? super S> action);
-
-        void objectRemoved(Action<? super S> action);
-    }
-
-    protected static class FilteredStore<S> implements Store<S> {
-        private final Store<? super S> store;
-        private final Class<S> type;
-        private final Spec<? super S> spec;
-
-        public FilteredStore(Store<? super S> store, Class<S> type, Spec<? super S> spec) {
-            this.store = store;
-            this.type = type;
-            this.spec = spec;
-        }
-
-        public Collection<? extends S> getAll() {
-            List<S> values = new ArrayList<S>();
-            for (Object s : store.getAll()) {
-                S filtered = filter(s);
-                if (filtered != null) {
-                    values.add(filtered);
-                }
-            }
-            return values;
-        }
-
-        public void objectAdded(Action<? super S> action) {
-            store.objectAdded(filter(action));
-        }
-
-        public void objectRemoved(Action<? super S> action) {
-            store.objectRemoved(filter(action));
-        }
-
-        protected S filter(Object object) {
-            if (!type.isInstance(object)) {
-                return null;
-            }
-            S s = type.cast(object);
-            if (!spec.isSatisfiedBy(s)) {
-                return null;
-            }
-            return s;
-        }
-
-        protected Action<Object> filter(final Action<? super S> action) {
-            return new Action<Object>() {
-                public void execute(Object object) {
-                    S s = filter(object);
-                    if (s != null) {
-                        action.execute(s);
-                    }
-                }
-            };
-        }
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java
index f742ebd..0100438 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractMultiCauseException.java
@@ -16,14 +16,15 @@
 package org.gradle.api.internal;
 
 import org.gradle.api.GradleException;
-import org.gradle.util.GUtil;
 
 import java.io.PrintStream;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 public class AbstractMultiCauseException extends GradleException implements MultiCauseException {
-    private List<Throwable> causes;
+    private final List<Throwable> causes = new CopyOnWriteArrayList<Throwable>();
     private final ThreadLocal<Boolean> hideCause = new ThreadLocal<Boolean>() {
         @Override
         protected Boolean initialValue() {
@@ -31,9 +32,18 @@ public class AbstractMultiCauseException extends GradleException implements Mult
         }
     };
 
+    public AbstractMultiCauseException(String message) {
+        super(message);
+    }
+
+    public AbstractMultiCauseException(String message, Throwable... causes) {
+        super(message);
+        this.causes.addAll(Arrays.asList(causes));
+    }
+
     public AbstractMultiCauseException(String message, Iterable<? extends Throwable> causes) {
         super(message);
-        this.causes = GUtil.addLists(causes);
+        initCauses(causes);
     }
 
     public List<? extends Throwable> getCauses() {
@@ -41,6 +51,20 @@ public class AbstractMultiCauseException extends GradleException implements Mult
     }
 
     @Override
+    public Throwable initCause(Throwable throwable) {
+        causes.clear();
+        causes.add(throwable);
+        return null;
+    }
+
+    public void initCauses(Iterable<? extends Throwable> causes) {
+        this.causes.clear();
+        for (Throwable cause : causes) {
+            this.causes.add(cause);
+        }
+    }
+
+    @Override
     public Throwable getCause() {
         if (hideCause.get()) {
             return null;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
new file mode 100644
index 0000000..0879440
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Named;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Namer;
+import org.gradle.util.ConfigureUtil;
+
+public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamedDomainObjectSet<T> implements NamedDomainObjectContainer<T> {
+
+    protected AbstractNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, instantiator, namer);
+    }
+
+    protected AbstractNamedDomainObjectContainer(Class<T> type, Instantiator instantiator) {
+        super(type, instantiator, Named.Namer.forType(type));
+    }
+
+    /**
+     * Subclasses need only implement this method as the creation strategy.
+     */
+    protected abstract T doCreate(String name);
+
+    public T create(String name) {
+        return create(name, null);
+    }
+
+    public T create(String name, Closure configureClosure) {
+        assertCanAdd(name);
+        T object = doCreate(name);
+        add(object);
+        ConfigureUtil.configure(configureClosure, object);
+        return object;
+    }
+
+    protected Object createConfigureDelegate(Closure configureClosure) {
+        return new NamedDomainObjectContainerConfigureDelegate(configureClosure.getOwner(), this);
+    }
+
+    public AbstractNamedDomainObjectContainer<T> configure(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, createConfigureDelegate(configureClosure));
+        return this;
+    }
+
+    public String getDisplayName() {
+        return String.format("%s container", getTypeDisplayName());
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
index 31278dd..368468d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
@@ -21,24 +21,25 @@ import groovy.lang.MissingPropertyException;
 import org.codehaus.groovy.runtime.InvokerInvocationException;
 import org.gradle.api.*;
 import org.gradle.api.internal.file.TemporaryFileProvider;
-import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ServiceRegistry;
 import org.gradle.api.internal.tasks.DefaultTaskDependency;
 import org.gradle.api.internal.tasks.TaskDependencyInternal;
 import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.specs.AndSpec;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.TaskInputs;
+import org.gradle.api.tasks.TaskInstantiationException;
 import org.gradle.api.tasks.TaskState;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.ConfigureUtil;
@@ -47,7 +48,6 @@ import org.gradle.util.DeprecationLogger;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
 
@@ -69,7 +69,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
 
     private DefaultTaskDependency dependencies;
 
-    private DynamicObjectHelper dynamicObjectHelper;
+    private ExtensibleDynamicObject extensibleDynamicObject;
 
     private String description;
 
@@ -96,21 +96,23 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     private static TaskInfo taskInfo() {
-        TaskInfo taskInfo = nextInstance.get();
-        assert taskInfo != null;
-        return taskInfo;
+        return nextInstance.get();
     }
 
     private AbstractTask(TaskInfo taskInfo) {
+        if (taskInfo == null) {
+            throw new TaskInstantiationException(String.format("Task of type '%s' has been instantiated directly which is not supported. Tasks can only be created using the DSL.", getClass().getName()));
+        }
+
         this.project = taskInfo.project;
         this.name = taskInfo.name;
         assert project != null;
         assert name != null;
         path = project.absoluteProjectPath(name);
         state = new TaskStateInternal(toString());
-        dynamicObjectHelper = new DynamicObjectHelper(this, new DefaultConvention());
         dependencies = new DefaultTaskDependency(project.getTasks());
         services = project.getServices().createFor(this);
+        extensibleDynamicObject = new ExtensibleDynamicObject(this, getServices().get(Instantiator.class));
         outputs = services.get(TaskOutputsInternal.class);
         inputs = services.get(TaskInputs.class);
         executer = services.get(TaskExecuter.class);
@@ -161,7 +163,10 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public void setActions(List<Action<? super Task>> actions) {
-        this.actions = actions;
+        deleteAllActions();
+        for (Action<? super Task> action : actions) {
+            doLast(action);
+        }
     }
 
     public TaskDependencyInternal getTaskDependencies() {
@@ -233,11 +238,15 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return this;
     }
 
-    public void execute() {
-        executer.execute(this, state);
+    public final void execute() {
+        executeWithoutThrowingTaskFailure();
         state.rethrowFailure();
     }
 
+    public void executeWithoutThrowingTaskFailure() {
+        executer.execute(this, state);
+    }
+
     public TaskExecuter getExecuter() {
         return executer;
     }
@@ -255,7 +264,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
-        actions.add(0, action);
+        actions.add(0, wrap(action));
         return this;
     }
 
@@ -263,7 +272,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
-        actions.add(action);
+        actions.add(wrap(action));
         return this;
     }
 
@@ -284,18 +293,6 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return buildLogger;
     }
 
-    public Task disableStandardOutputCapture() {
-        DeprecationLogger.nagUser("Task.disableStandardOutputCapture()");
-        loggingManager.disableStandardOutputCapture();
-        return this;
-    }
-
-    public Task captureStandardOutput(LogLevel level) {
-        DeprecationLogger.nagUser("Task.captureStandardOutput()", "getLogging().captureStandardOutput()");
-        loggingManager.captureStandardOutput(level);
-        return this;
-    }
-
     public LoggingManager getLogging() {
         return loggingManager;
     }
@@ -304,36 +301,33 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return loggingManager;
     }
 
-    public Map<String, Object> getAdditionalProperties() {
-        return dynamicObjectHelper.getAdditionalProperties();
-    }
-
     public DynamicObjectHelper getDynamicObjectHelper() {
-        return dynamicObjectHelper;
+        DeprecationLogger.nagUserOfReplacedMethod("AbstractTask.getDynamicObjectHelper()", "getAsDynamicObject()");
+        return new DynamicObjectHelper(extensibleDynamicObject);
     }
 
     public Object property(String propertyName) throws MissingPropertyException {
-        return dynamicObjectHelper.getProperty(propertyName);
+        return extensibleDynamicObject.getProperty(propertyName);
     }
 
     public boolean hasProperty(String propertyName) {
-        return dynamicObjectHelper.hasProperty(propertyName);
+        return extensibleDynamicObject.hasProperty(propertyName);
     }
 
     public void setProperty(String name, Object value) {
-        dynamicObjectHelper.setProperty(name, value);
+        extensibleDynamicObject.setProperty(name, value);
     }
 
     public Convention getConvention() {
-        return dynamicObjectHelper.getConvention();
+        return extensibleDynamicObject.getConvention();
     }
 
-    public void setConvention(Convention convention) {
-        dynamicObjectHelper.setConvention(convention);
+    public ExtensionContainer getExtensions() {
+        return getConvention();
     }
 
     public DynamicObject getAsDynamicObject() {
-        return dynamicObjectHelper;
+        return extensibleDynamicObject;
     }
 
     public String getDescription() {
@@ -378,7 +372,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
-        doFirst(convertClosureToAction(action));
+        actions.add(0, convertClosureToAction(action));
         return this;
     }
 
@@ -386,7 +380,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         if (action == null) {
             throw new InvalidUserDataException("Action must not be null!");
         }
-        doLast(convertClosureToAction(action));
+        actions.add(convertClosureToAction(action));
         return this;
     }
 
@@ -395,7 +389,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     public Task configure(Closure closure) {
-        return ConfigureUtil.configure(closure, this);
+        return ConfigureUtil.configure(closure, this, false);
     }
 
     public File getTemporaryDir() {
@@ -404,6 +398,15 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return dir;
     }
 
+    // note: this method is on TaskInternal
+    public Factory<File> getTemporaryDirFactory() {
+        return new Factory<File>() {
+            public File create() {
+                return getTemporaryDir();
+            }
+        };
+    }
+    
     public void addValidator(TaskValidator validator) {
         validators.add(validator);
     }
@@ -413,11 +416,13 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
     }
 
     private Action<Task> convertClosureToAction(Closure actionClosure) {
-        actionClosure.setDelegate(this);
-        actionClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
         return new ClosureTaskAction(actionClosure);
     }
 
+    private Action<Task> wrap(final Action<? super Task> action) {
+        return new TaskActionWrapper(action);
+    }
+
     private static class TaskInfo {
         private final ProjectInternal project;
         private final String name;
@@ -436,6 +441,10 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         }
 
         public void execute(Task task) {
+            closure.setDelegate(task);
+            closure.setResolveStrategy(Closure.DELEGATE_FIRST);
+            ClassLoader original = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(closure.getClass().getClassLoader());
             try {
                 if (closure.getMaximumNumberOfParameters() == 0) {
                     closure.call();
@@ -448,8 +457,27 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
                     throw (RuntimeException) cause;
                 }
                 throw e;
+            } finally {
+                Thread.currentThread().setContextClassLoader(original);
             }
         }
     }
 
+    private static class TaskActionWrapper implements Action<Task> {
+        private final Action<? super Task> action;
+
+        public TaskActionWrapper(Action<? super Task> action) {
+            this.action = action;
+        }
+
+        public void execute(Task task) {
+            ClassLoader original = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(action.getClass().getClassLoader());
+            try {
+                action.execute(task);
+            } finally {
+                Thread.currentThread().setContextClassLoader(original);
+            }
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
index 38d6949..bec7f16 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
@@ -16,8 +16,11 @@
 package org.gradle.api.internal;
 
 import groovy.lang.*;
+import org.gradle.api.Action;
 import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.util.ConfigureUtil;
 import org.gradle.util.ReflectionUtil;
 import org.objectweb.asm.*;
 
@@ -43,6 +46,8 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         private MethodCodeBody initMetaClass;
         private final Type conventionAwareType = Type.getType(IConventionAware.class);
         private final Type dynamicObjectAwareType = Type.getType(DynamicObjectAware.class);
+        private final Type extensionAwareType = Type.getType(ExtensionAware.class);
+        private final Type hasConventionType = Type.getType(HasConvention.class);
         private final Type dynamicObjectType = Type.getType(DynamicObject.class);
         private final Type conventionMappingType = Type.getType(ConventionMapping.class);
         private final Type groovyObjectType = Type.getType(GroovyObject.class);
@@ -67,8 +72,8 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             }
             if (isDynamicAware) {
                 interfaceTypes.add(dynamicObjectAwareType.getInternalName());
-            }
-            if (isDynamicAware) {
+                interfaceTypes.add(extensionAwareType.getInternalName());
+                interfaceTypes.add(hasConventionType.getInternalName());
                 interfaceTypes.add(groovyObjectType.getInternalName());
             }
 
@@ -87,9 +92,10 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                     new String[0]);
             methodVisitor.visitCode();
 
-            // super(p0 .. pn)
-            for (int i = 0; i <= constructor.getParameterTypes().length; i++) {
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, i);
+            // this.super(p0 .. pn)
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            for (int i = 0; i < constructor.getParameterTypes().length; i++) {
+                methodVisitor.visitVarInsn(Type.getType(constructor.getParameterTypes()[i]).getOpcode(Opcodes.ILOAD), i+1);
             }
             methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
                     methodDescriptor);
@@ -110,11 +116,11 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         }
 
         public void mixInDynamicAware() throws Exception {
-            final Type helperType = Type.getType(MixInDynamicObject.class);
+            final Type helperType = Type.getType(MixInExtensibleDynamicObject.class);
 
-            // GENERATE private MixInDynamicObject dynamicObjectHelper = new MixInDynamicObject(this, super.getAsDynamicObject())
+            // GENERATE private MixInExtensibleDynamicObject dynamicObjectHelper = new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
 
-            final String fieldSignature = "L" + MixInDynamicObject.class.getName().replaceAll("\\.", "/") + ";";
+            final String fieldSignature = "L" + MixInExtensibleDynamicObject.class.getName().replaceAll("\\.", "/") + ";";
             visitor.visitField(Opcodes.ACC_PRIVATE, "dynamicObjectHelper", fieldSignature, null, null);
             initDynamicObjectHelper = new MethodCodeBody() {
                 public void add(MethodVisitor visitor) throws Exception {
@@ -122,11 +128,11 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                             Type.getType(Object.class), dynamicObjectType
                     });
 
-                    // GENERATE dynamicObjectHelper = new MixInDynamicObject(this, super.getAsDynamicObject())
+                    // GENERATE dynamicObjectHelper = new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
 
                     visitor.visitVarInsn(Opcodes.ALOAD, 0);
 
-                    // GENERATE new DynamicObjectHelper(this, super.getAsDynamicObject())
+                    // GENERATE new MixInExtensibleDynamicObject(this, super.getAsDynamicObject())
                     visitor.visitTypeInsn(Opcodes.NEW, helperType.getInternalName());
                     visitor.visitInsn(Opcodes.DUP);
 
@@ -158,7 +164,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
 
             // GENERATE public Convention getConvention() { return dynamicObjectHelper.getConvention(); }
 
-            addGetter(DynamicObjectAware.class.getDeclaredMethod("getConvention"), new MethodCodeBody() {
+            addGetter(HasConvention.class.getDeclaredMethod("getConvention"), new MethodCodeBody() {
                 public void add(MethodVisitor visitor) throws Exception {
 
                     // GENERATE dynamicObjectHelper.getConvention()
@@ -166,7 +172,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                     visitor.visitVarInsn(Opcodes.ALOAD, 0);
                     visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
                             fieldSignature);
-                    String getterDescriptor = Type.getMethodDescriptor(DynamicObjectHelper.class.getDeclaredMethod(
+                    String getterDescriptor = Type.getMethodDescriptor(ExtensibleDynamicObject.class.getDeclaredMethod(
                             "getConvention"));
                     visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(), "getConvention",
                             getterDescriptor);
@@ -175,6 +181,23 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
 
             // END
 
+            // GENERATE public ExtensionContainer getExtensions() { return getConvention(); }
+
+            addGetter(ExtensionAware.class.getDeclaredMethod("getExtensions"), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+
+                    // GENERATE getConvention()
+
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    String getterDescriptor = Type.getMethodDescriptor(ExtensibleDynamicObject.class.getDeclaredMethod(
+                            "getConvention"));
+                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), "getConvention",
+                            getterDescriptor);
+                }
+            });
+
+            // END
+
             // GENERATE public DynamicObject.getAsDynamicObject() { return dynamicObjectHelper; }
 
             addGetter(DynamicObjectAware.class.getDeclaredMethod("getAsDynamicObject"), new MethodCodeBody() {
@@ -189,35 +212,6 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             });
 
             // END
-
-            // GENERATE public void setConvention(Convention c) { dynamicObjectHelper.setConvention(c); getConventionMapping().setConvention(c); }
-
-            addSetter(DynamicObjectAware.class.getDeclaredMethod("setConvention", Convention.class),
-                    new MethodCodeBody() {
-                        public void add(MethodVisitor visitor) {
-                            String setConventionDesc = Type.getMethodDescriptor(Type.VOID_TYPE,
-                                    new Type[]{conventionType});
-
-                            visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                            visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(),
-                                    "dynamicObjectHelper", fieldSignature);
-
-                            visitor.visitVarInsn(Opcodes.ALOAD, 1);
-                            visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(),
-                                    "setConvention", setConventionDesc);
-
-                            visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                            visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(),
-                                    "getConventionMapping", Type.getMethodDescriptor(conventionMappingType,
-                                            new Type[0]));
-
-                            visitor.visitVarInsn(Opcodes.ALOAD, 1);
-                            visitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, conventionMappingType.getInternalName(),
-                                    "setConvention", setConventionDesc);
-                        }
-                    });
-
-            // END
         }
 
         public void mixInConventionAware() throws Exception {
@@ -273,27 +267,30 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
 
             // END
 
-            // GENERATE public ConventionMapping getConventionMapping() { return mapping; }
+            // GENERATE public ConventionMapping getConventionMapping() { if (mapping != null) { return mapping; } else { return new ConventionAwareHelper(this); } }
+            // the null check is for when getConventionMapping() is called by a superclass constructor (eg when the constructor calls a getter method)
 
             addGetter(IConventionAware.class.getDeclaredMethod("getConventionMapping"), new MethodCodeBody() {
                 public void add(MethodVisitor visitor) {
+                    // GENERATE if (mapping != null) {... }
                     visitor.visitVarInsn(Opcodes.ALOAD, 0);
                     visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "mapping",
                             mappingFieldSignature);
+                    visitor.visitInsn(Opcodes.DUP);
+                    Label nullBranch = new Label();
+                    visitor.visitJumpInsn(Opcodes.IFNULL, nullBranch);
+                    visitor.visitInsn(Opcodes.ARETURN);
+                    // GENERATE else { return new ConventionAwareHelper(this); }
+                    visitor.visitLabel(nullBranch);
+                    Type conventionAwareHelperType = Type.getType(ConventionAwareHelper.class);
+                    String constructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{Type.getType(IConventionAware.class)});
+                    visitor.visitTypeInsn(Opcodes.NEW, conventionAwareHelperType.getInternalName());
+                    visitor.visitInsn(Opcodes.DUP);
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, conventionAwareHelperType.getInternalName(), "<init>", constructorDesc);
+                    visitor.visitInsn(Opcodes.ARETURN);
                 }
             });
-
-            // GENERATE public void setConventionMapping(ConventionMapping m) { mapping = m; }
-
-            addSetter(IConventionAware.class.getDeclaredMethod("setConventionMapping", ConventionMapping.class),
-                    new MethodCodeBody() {
-                        public void add(MethodVisitor visitor) {
-                            visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                            visitor.visitVarInsn(Opcodes.ALOAD, 1);
-                            visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "mapping",
-                                    mappingFieldSignature);
-                        }
-                    });
         }
 
         public void mixInGroovyObject() throws Exception {
@@ -482,7 +479,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                             // Generate args
 //                            methodVisitor.visitInsn(Opcodes.ACONST_NULL);
                             methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
-                            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,  objArrayDesc);
+                            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, objArrayDesc);
                             methodVisitor.visitJumpInsn(Opcodes.GOTO, end);
 
                             // Generate new Object[] { args }
@@ -510,11 +507,34 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             String flagName = String.format("%sSet", property.getName());
             visitor.visitField(Opcodes.ACC_PRIVATE, flagName, Type.BOOLEAN_TYPE.getDescriptor(), null, null);
 
+            addConventionGetter(getter.getName(), flagName, property);
+    
+            String getterName = getter.getName();
+            Class<?> returnType = getter.getReturnType();
+
+            // If it's a boolean property, there can be get or is type variants.
+            // If this class has both, decorate both.
+            if (returnType.equals(Boolean.TYPE)) {
+                boolean getterIsIsMethod = getterName.startsWith("is");
+                String propertyNameComponent = getterName.substring(getterIsIsMethod ? 2 : 3);
+                String alternativeGetterName = String.format("%s%s", getterIsIsMethod ? "get" : "is", propertyNameComponent);
+
+                try {
+                    type.getMethod(alternativeGetterName);
+                    addConventionGetter(alternativeGetterName, flagName, property);
+                } catch (NoSuchMethodException e) {
+                    // ignore, no method to override
+                }
+            }
+        }
+
+        private void addConventionGetter(String getterName, String flagName, MetaBeanProperty property) throws Exception {
             // GENERATE public <type> <getter>() { return (<type>)getConventionMapping().getConventionValue(super.<getter>(), '<prop>', <prop>Set); }
+            MetaMethod getter = property.getGetter();
 
             Type returnType = Type.getType(getter.getReturnType());
             String methodDescriptor = Type.getMethodDescriptor(returnType, new Type[0]);
-            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, getter.getName(), methodDescriptor,
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, getterName, methodDescriptor,
                     null, new String[0]);
             methodVisitor.visitCode();
 
@@ -523,10 +543,19 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                     "getConventionMapping", Type.getMethodDescriptor(conventionMappingType, new Type[0]));
 
             methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), getter.getName(),
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), getterName,
                     methodDescriptor);
 
+            Type boxedType = null;
+            if (getter.getReturnType().isPrimitive()) {
+                // Box value
+                boxedType = Type.getType(ReflectionUtil.getWrapperTypeForPrimitiveType(getter.getReturnType()));
+                String valueOfMethodDescriptor = Type.getMethodDescriptor(boxedType, new Type[]{returnType});
+                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, boxedType.getInternalName(), "valueOf", valueOfMethodDescriptor);
+            }
+
             methodVisitor.visitLdcInsn(property.getName());
+
             methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
             methodVisitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), flagName,
                     Type.BOOLEAN_TYPE.getDescriptor());
@@ -536,11 +565,19 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, conventionMappingType.getInternalName(),
                     "getConventionValue", getConventionValueDesc);
 
-            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,
-                    getter.getReturnType().isArray() ? "[" + returnType.getElementType().getDescriptor()
-                            : returnType.getInternalName());
+            if (getter.getReturnType().isPrimitive()) {
+                // Unbox value
+                methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, boxedType.getInternalName());
+                String valueMethodDescriptor = Type.getMethodDescriptor(returnType, new Type[0]);
+                methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxedType.getInternalName(), getter.getReturnType().getName() + "Value", valueMethodDescriptor);
+            } else {
+                // Cast to return type
+                methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,
+                        getter.getReturnType().isArray() ? "[" + returnType.getElementType().getDescriptor()
+                                : returnType.getInternalName());
+            }
 
-            methodVisitor.visitInsn(Opcodes.ARETURN);
+            methodVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
             methodVisitor.visitMaxs(0, 0);
             methodVisitor.visitEnd();
         }
@@ -552,19 +589,16 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
 
             Type paramType = Type.getType(setter.getParameterTypes()[0].getTheClass());
             Type returnType = Type.getType(setter.getReturnType());
-            boolean isVoid = setter.getReturnType().equals(Void.TYPE);
-            String methodDescriptor = Type.getMethodDescriptor(returnType, new Type[]{paramType});
-            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, setter.getName(), methodDescriptor,
-                    null, new String[0]);
+            String setterDescriptor = Type.getMethodDescriptor(returnType, new Type[]{paramType});
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, setter.getName(), setterDescriptor, null, new String[0]);
             methodVisitor.visitCode();
 
             // GENERATE super.<setter>(v)
 
             methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+            methodVisitor.visitVarInsn(paramType.getOpcode(Opcodes.ILOAD), 1);
 
-            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), setter.getName(),
-                    methodDescriptor);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), setter.getName(), setterDescriptor);
 
             // END
 
@@ -582,6 +616,89 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             methodVisitor.visitEnd();
         }
 
+        public void addSetMethod(MetaBeanProperty property) throws Exception {
+            MetaMethod setter = property.getSetter();
+            Type paramType = Type.getType(setter.getParameterTypes()[0].getTheClass());
+            Type returnType = Type.getType(setter.getReturnType());
+            String setterDescriptor = Type.getMethodDescriptor(returnType, new Type[]{paramType});
+
+            // GENERATE public void <propName>(<type> v) { <setter>(v) }
+            String setMethodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{paramType});
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, property.getName(), setMethodDescriptor, null, new String[0]);
+            methodVisitor.visitCode();
+
+            // GENERATE <setter>(v)
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitVarInsn(paramType.getOpcode(Opcodes.ILOAD), 1);
+
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), setter.getName(), setterDescriptor);
+
+            // END
+
+            methodVisitor.visitInsn(Opcodes.RETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        public void overrideSetMethod(MetaBeanProperty property, MetaMethod metaMethod) throws Exception {
+            Type paramType = Type.getType(metaMethod.getParameterTypes()[0].getTheClass());
+            Type returnType = Type.getType(metaMethod.getReturnType());
+            String methodDescriptor = Type.getMethodDescriptor(returnType, new Type[]{paramType});
+
+            // GENERATE public <returnType> <propName>(<type> v) { val = super.<propName>(v); <prop>Set = true; return val; }
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, metaMethod.getName(), methodDescriptor, null, new String[0]);
+            methodVisitor.visitCode();
+
+            // GENERATE super.<propName>(v)
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitVarInsn(paramType.getOpcode(Opcodes.ILOAD), 1);
+
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), metaMethod.getName(), methodDescriptor);
+
+            // GENERATE <prop>Set = true
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitLdcInsn(true);
+            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), String.format("%sSet",
+                    property.getName()), Type.BOOLEAN_TYPE.getDescriptor());
+
+            // END
+
+            methodVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        public void addActionMethod(MetaMethod method) throws Exception {
+            Type actionImplType = Type.getType(ClosureBackedAction.class);
+            Type closureType = Type.getType(Closure.class);
+
+            String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{closureType});
+
+            // GENERATE public void <method>(Closure v) { <method>(new ClosureBackedAction(v)); }
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor, null, new String[0]);
+            methodVisitor.visitCode();
+
+            // GENERATE <method>(new ClosureBackedAction(v));
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+            // GENERATE new ClosureBackedAction(v);
+            methodVisitor.visitTypeInsn(Opcodes.NEW, actionImplType.getInternalName());
+            methodVisitor.visitInsn(Opcodes.DUP);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+            String constuctorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{closureType});
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, actionImplType.getInternalName(), "<init>", constuctorDescriptor);
+
+            methodDescriptor = Type.getMethodDescriptor(Type.getType(method.getReturnType()), new Type[]{Type.getType(method.getParameterTypes()[0].getTheClass())});
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), method.getName(), methodDescriptor);
+
+            methodVisitor.visitInsn(Opcodes.RETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
         public Class<? extends T> generate() {
             visitor.visitEnd();
 
@@ -596,9 +713,10 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         void add(MethodVisitor visitor) throws Exception;
     }
 
-    public static class MixInDynamicObject extends DynamicObjectHelper {
-        public MixInDynamicObject(Object delegateObject, DynamicObject dynamicObject) {
-            super(wrap(delegateObject, dynamicObject), new DefaultConvention());
+    public static class MixInExtensibleDynamicObject extends ExtensibleDynamicObject {
+
+        public MixInExtensibleDynamicObject(Object delegateObject, DynamicObject dynamicObject) {
+            super(delegateObject, wrap(delegateObject, dynamicObject), ThreadGlobalInstantiator.getOrCreate());
         }
 
         private static AbstractDynamicObject wrap(Object delegateObject, DynamicObject dynamicObject) {
@@ -608,4 +726,16 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             return new BeanDynamicObject(delegateObject);
         }
     }
+
+    public static class ClosureBackedAction implements Action<Object> {
+        private final Closure cl;
+
+        public ClosureBackedAction(Closure cl) {
+            this.cl = cl;
+        }
+
+        public void execute(Object o) {
+            ConfigureUtil.configure(cl, o);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainer.java
deleted file mode 100644
index 36e407e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainer.java
+++ /dev/null
@@ -1,51 +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.api.internal;
-
-import groovy.lang.Closure;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.util.Configurable;
-import org.gradle.util.ConfigureUtil;
-
-public abstract class AutoCreateDomainObjectContainer<T> extends DefaultNamedDomainObjectContainer<T> implements
-        Configurable<AutoCreateDomainObjectContainer<T>> {
-    protected AutoCreateDomainObjectContainer(Class<T> type, ClassGenerator classGenerator) {
-        super(type, classGenerator);
-    }
-
-    protected abstract T create(String name);
-
-    public T add(String name) {
-        return add(name, null);
-    }
-
-    public T add(String name, Closure configureClosure) {
-        if (findByName(name) != null) {
-            throw new InvalidUserDataException(String.format("Cannot add %s '%s' as a %s with that name already exists.",
-                    getTypeDisplayName(), name, getTypeDisplayName()));
-        }
-        T object = create(name);
-        addObject(name, object);
-        ConfigureUtil.configure(configureClosure, object);
-        return object;
-    }
-
-    public AutoCreateDomainObjectContainer<T> configure(Closure configureClosure) {
-        ConfigureUtil.configure(configureClosure, new AutoCreateDomainObjectContainerDelegate(
-                configureClosure.getOwner(), this));
-        return this;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerDelegate.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerDelegate.groovy
deleted file mode 100644
index 81f230c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerDelegate.groovy
+++ /dev/null
@@ -1,75 +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
-
-class AutoCreateDomainObjectContainerDelegate {
-    private final Object owner;
-    private final AutoCreateDomainObjectContainer container;
-    private final DynamicObject delegate;
-    private final ThreadLocal<Boolean> configuring = new ThreadLocal<Boolean>()
-
-    public AutoCreateDomainObjectContainerDelegate(Object owner, AutoCreateDomainObjectContainer container) {
-        this.container = container
-        delegate = container.asDynamicObject
-        this.owner = owner
-    }
-
-    @SuppressWarnings("EmptyCatchBlock")
-    public Object invokeMethod(String name, Object params) {
-        boolean isTopLevelCall = !configuring.get()
-        configuring.set(true)
-        try {
-            if (delegate.hasMethod(name, params)) {
-                return delegate.invokeMethod(name, params)
-            }
-
-            // try the owner
-            try {
-                return owner.invokeMethod(name, params)
-            } catch (groovy.lang.MissingMethodException e) {
-                // ignore
-            }
-
-            boolean isConfigureMethod = params.length == 1 && params[0] instanceof Closure
-            if (isTopLevelCall && isConfigureMethod) {
-                // looks like a configure method - add the object and try the delegate again
-                container.add(name)
-            }
-
-            return delegate.invokeMethod(name, params);
-        } finally {
-            configuring.set(!isTopLevelCall)
-        }
-    }
-
-    @SuppressWarnings("EmptyCatchBlock")
-    public Object get(String name) {
-        if (delegate.hasProperty(name)) {
-            return delegate.getProperty(name)
-        }
-
-        // try the owner
-        try {
-            return owner."$name"
-        } catch (groovy.lang.MissingPropertyException e) {
-            // Ignore
-        }
-
-        // try the delegate again
-        container.add(name)
-        return delegate.getProperty(name)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java
index 3af1e82..d966399 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java
@@ -28,19 +28,29 @@ import java.util.Map;
  * A {@link DynamicObject} which uses groovy reflection to provide access to the properties and methods of a bean.
  */
 public class BeanDynamicObject extends AbstractDynamicObject {
+    
     private final Object bean;
     private final boolean includeProperties;
+    private final DynamicObject delegate;
 
     public BeanDynamicObject(Object bean) {
-        this.bean = bean;
-        includeProperties = true;
+        this(bean, true);
     }
 
     private BeanDynamicObject(Object bean, boolean includeProperties) {
         this.bean = bean;
         this.includeProperties = includeProperties;
+        this.delegate = determineDelegate(bean);
     }
 
+    public DynamicObject determineDelegate(Object bean) {
+        if (bean instanceof DynamicObject || bean instanceof DynamicObjectAware || !(bean instanceof GroovyObject)) {
+            return new MetaClassAdapter();
+        } else {
+            return new GroovyObjectAdapter();
+        }
+    }
+    
     public BeanDynamicObject withNoProperties() {
         return new BeanDynamicObject(bean, false);
     }
@@ -64,105 +74,169 @@ public class BeanDynamicObject extends AbstractDynamicObject {
 
     @Override
     public boolean hasProperty(String name) {
-        return includeProperties && getMetaClass().hasProperty(bean, name) != null;
+        return delegate.hasProperty(name);    
     }
 
     @Override
     public Object getProperty(String name) throws MissingPropertyException {
-        if (!includeProperties) {
-            throw propertyMissingException(name);
-        }
+        return delegate.getProperty(name);
+    }
 
-        MetaProperty property = getMetaClass().hasProperty(bean, name);
-        if (property == null) {
-            throw propertyMissingException(name);
-        }
-        if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getGetter() == null) {
-            throw new GroovyRuntimeException(String.format(
-                    "Cannot get the value of write-only property '%s' on %s.", name, getDisplayName()));
-        }
+    @Override
+    public void setProperty(final String name, Object value) throws MissingPropertyException {
+        delegate.setProperty(name, value); 
+    }
 
-        try {
-            return property.getProperty(bean);
-        } catch (InvokerInvocationException e) {
-            if (e.getCause() instanceof RuntimeException) {
-                throw (RuntimeException) e.getCause();
-            }
-            throw e;
-        }
+    @Override
+    public Map<String, ?> getProperties() {
+        return delegate.getProperties();        
     }
 
     @Override
-    public void setProperty(final String name, Object value) throws MissingPropertyException {
-        if (!includeProperties) {
-            throw propertyMissingException(name);
-        }
+    public boolean hasMethod(String name, Object... arguments) {
+        return delegate.hasMethod(name, arguments);                        
+    }
+
+    @Override
+    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+        return delegate.invokeMethod(name, arguments);        
+    }
+
+    private class MetaClassAdapter implements DynamicObject {
 
-        MetaClass metaClass = getMetaClass();
-        MetaProperty property = metaClass.hasProperty(bean, name);
-        if (property == null) {
-            throw propertyMissingException(name);
+        public boolean hasProperty(String name) {
+            return includeProperties && getMetaClass().hasProperty(bean, name) != null;
         }
 
-        if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getSetter() == null) {
-            throw new ReadOnlyPropertyException(name, bean.getClass()) {
-                @Override
-                public String getMessage() {
-                    return String.format("Cannot set the value of read-only property '%s' on %s.", name,
-                            getDisplayName());
+        public Object getProperty(String name) throws MissingPropertyException {
+            if (!includeProperties) {
+                throw propertyMissingException(name);
+            }
+
+            MetaProperty property = getMetaClass().hasProperty(bean, name);
+            if (property == null) {
+                return getMetaClass().invokeMissingProperty(bean, name, null, true);
+            }
+            if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getGetter() == null) {
+                throw new GroovyRuntimeException(String.format(
+                        "Cannot get the value of write-only property '%s' on %s.", name, getDisplayName()));
+            }
+
+            try {
+                return property.getProperty(bean);
+            } catch (InvokerInvocationException e) {
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
                 }
-            };
-        }
-        try {
-            metaClass.setProperty(bean, name, value);
-        } catch (InvokerInvocationException e) {
-            if (e.getCause() instanceof RuntimeException) {
-                throw (RuntimeException) e.getCause();
+                throw e;
             }
-            throw e;
         }
-    }
 
-    @Override
-    public Map<String, Object> getProperties() {
-        if (!includeProperties) {
-            return Collections.emptyMap();
+        public void setProperty(final String name, Object value) throws MissingPropertyException {
+            if (!includeProperties) {
+                throw propertyMissingException(name);
+            }
+
+            MetaClass metaClass = getMetaClass();
+            MetaProperty property = metaClass.hasProperty(bean, name);
+            if (property == null) {
+                if (property == null) {
+                    getMetaClass().invokeMissingProperty(bean, name, null, false);
+                }
+            }
+
+            if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getSetter() == null) {
+                throw new ReadOnlyPropertyException(name, bean.getClass()) {
+                    @Override
+                    public String getMessage() {
+                        return String.format("Cannot set the value of read-only property '%s' on %s.", name,
+                                getDisplayName());
+                    }
+                };
+            }
+            try {
+                metaClass.setProperty(bean, name, value);
+            } catch (InvokerInvocationException e) {
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                }
+                throw e;
+            }
         }
 
-        Map<String, Object> properties = new HashMap<String, Object>();
-        List<MetaProperty> classProperties = getMetaClass().getProperties();
-        for (MetaProperty metaProperty : classProperties) {
-            if (metaProperty.getName().equals("properties")) {
-                properties.put("properties", properties);
-                continue;
+        public Map<String, ?> getProperties() {
+            if (!includeProperties) {
+                return Collections.emptyMap();
             }
-            if (metaProperty instanceof MetaBeanProperty) {
-                MetaBeanProperty beanProperty = (MetaBeanProperty) metaProperty;
-                if (beanProperty.getGetter() == null) {
+
+            Map<String, Object> properties = new HashMap<String, Object>();
+            List<MetaProperty> classProperties = getMetaClass().getProperties();
+            for (MetaProperty metaProperty : classProperties) {
+                if (metaProperty.getName().equals("properties")) {
+                    properties.put("properties", properties);
                     continue;
                 }
+                if (metaProperty instanceof MetaBeanProperty) {
+                    MetaBeanProperty beanProperty = (MetaBeanProperty) metaProperty;
+                    if (beanProperty.getGetter() == null) {
+                        continue;
+                    }
+                }
+                properties.put(metaProperty.getName(), metaProperty.getProperty(bean));
             }
-            properties.put(metaProperty.getName(), metaProperty.getProperty(bean));
+            return properties;
         }
-        return properties;
-    }
 
-    @Override
-    public boolean hasMethod(String name, Object... arguments) {
-        return !getMetaClass().respondsTo(bean, name, arguments).isEmpty();
+        public boolean hasMethod(String name, Object... arguments) {
+            return !getMetaClass().respondsTo(bean, name, arguments).isEmpty();
+        }
+
+        public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+            try {
+                return getMetaClass().invokeMethod(bean, name, arguments);
+            } catch (InvokerInvocationException e) {
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                }
+                throw e;
+            } catch (MissingMethodException e) {
+                throw methodMissingException(name, arguments);
+            }
+        }
     }
 
-    @Override
-    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
-        try {
-            return getMetaClass().invokeMethod(bean, name, arguments);
-        } catch (InvokerInvocationException e) {
-            if (e.getCause() instanceof RuntimeException) {
-                throw (RuntimeException) e.getCause();
+    /*
+       The GroovyObject interface defines dynamic property and dynamic method methods. Implementers
+       are free to implement their own logic in  these methods which makes it invisible to the metaclass.
+
+       The most notable case of this is Closure.
+
+       So in this case we use these methods directly on the GroovyObject in case it does implement logic at this level.
+     */
+    private class GroovyObjectAdapter extends MetaClassAdapter {
+        private final GroovyObject groovyObject = (GroovyObject)bean;
+
+
+        @Override
+        public Object getProperty(String name) throws MissingPropertyException {
+            return groovyObject.getProperty(name);
+        }
+
+        @Override
+        public void setProperty(String name, Object value) throws MissingPropertyException {
+            groovyObject.setProperty(name, value);
+        }
+
+        @Override
+        public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+            try {
+                return groovyObject.invokeMethod(name, arguments);
+            } catch (InvokerInvocationException e) {
+                if (e.getCause() instanceof RuntimeException) {
+                    throw (RuntimeException) e.getCause();
+                }
+                throw e;
             }
-            throw e;
-        } catch (MissingMethodException e) {
-            throw methodMissingException(name, arguments);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java
index 676ce0c..429f6e8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java
@@ -23,8 +23,8 @@ import java.util.ArrayList;
 
 import org.gradle.api.Transformer;
 
-public class ChainingTransformer<T> implements Transformer<T> {
-    private final List<Transformer<T>> transformers = new ArrayList<Transformer<T>>();
+public class ChainingTransformer<T> implements Transformer<T, T> {
+    private final List<Transformer<T, T>> transformers = new ArrayList<Transformer<T, T>>();
     private final Class<T> type;
 
     public ChainingTransformer(Class<T> type) {
@@ -33,18 +33,18 @@ public class ChainingTransformer<T> implements Transformer<T> {
 
     public T transform(T original) {
         T value = original;
-        for (Transformer<T> transformer : transformers) {
+        for (Transformer<T, T> transformer : transformers) {
             value = type.cast(transformer.transform(value));
         }
         return value;
     }
 
-    public void add(Transformer<T> transformer) {
+    public void add(Transformer<T, T> transformer) {
         transformers.add(transformer);
     }
 
     public void add(final Closure transformer) {
-        transformers.add(new Transformer<T>() {
+        transformers.add(new Transformer<T, T>() {
             public T transform(T original) {
                 transformer.setDelegate(original);
                 transformer.setResolveStrategy(Closure.DELEGATE_FIRST);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java
index b69161b..e34add7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java
@@ -20,9 +20,4 @@ public interface ClassGenerator {
      * Generates a proxy class for the given class. May decorate the given class or may generate a subclass.
      */
     <T> Class<? extends T> generate(Class<T> type);
-
-    /**
-     * Creates an instance of the proxy class for the given class.
-     */
-    <T> T newInstance(Class<T> type, Object... parameters);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
new file mode 100644
index 0000000..cba14d1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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;
+
+public class ClassGeneratorBackedInstantiator implements Instantiator {
+    private final ClassGenerator classGenerator;
+    private final Instantiator instantiator;
+
+    public ClassGeneratorBackedInstantiator(ClassGenerator classGenerator, Instantiator instantiator) {
+        this.classGenerator = classGenerator;
+        this.instantiator = instantiator;
+    }
+
+    public <T> T newInstance(Class<T> type, Object... parameters) {
+        // During the construction of the object, it will look for this global instantiator.
+        // This is to support ExtensionContainer.add(String, Class, Object...) which facilitates
+        // making extensions ExtensionAware themselves.
+        // See: AsmBackedClassGenerator.MixInExtensibleDynamicObject#getInstantiator()
+        ThreadGlobalInstantiator.set(this);
+        try {
+            return instantiator.newInstance(classGenerator.generate(type), parameters);    
+        } finally {
+            ThreadGlobalInstantiator.set(null);
+        }
+        
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
index c4cf80c..14eaedf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
@@ -16,12 +16,11 @@
 
 package org.gradle.api.internal;
 
-import java.io.File;
-import java.util.Set;
+import org.gradle.util.ClassPath;
 
 public interface ClassPathProvider {
     /**
      * Returns the files for the given classpath, if known. Returns null for unknown classpath.
      */
-    Set<File> findClassPath(String name);
+    ClassPath findClassPath(String name);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
old mode 100644
new mode 100755
index e8965a8..99cbed6
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
@@ -1,29 +1,23 @@
-/*
- * 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;
-
-import java.io.File;
-import java.net.URL;
-import java.util.Set;
-
-public interface ClassPathRegistry {
-    Set<URL> getClassPath(String name);
-
-    URL[] getClassPathUrls(String name);
-
-    Set<File> getClassPathFiles(String name);
-}
+/*
+ * 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;
+
+import org.gradle.util.ClassPath;
+
+public interface ClassPathRegistry {
+    ClassPath getClassPath(String name);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDomainObjectSet.java
new file mode 100644
index 0000000..a6483c7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDomainObjectSet.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.specs.Spec;
+
+import org.apache.commons.collections.collection.CompositeCollection;
+import java.util.LinkedHashSet;
+import java.util.Iterator;
+import java.util.Collection;
+
+/**
+ * A domain object collection that presents a combined view of one or more collections.
+ *
+ * @param <T> The type of domain objects in the component collections of this collection.
+ */
+public class CompositeDomainObjectSet<T> extends DefaultDomainObjectSet<T> {
+
+    private Spec<T> uniqueSpec = new ItemIsUniqueInCompositeSpec();
+    private Spec<T> notInSpec = new ItemNotInCompositeSpec();
+    
+    public CompositeDomainObjectSet(Class<T> type) {
+        super(type, new CompositeCollection());
+    }
+
+    public CompositeDomainObjectSet(Class<T> type, DomainObjectCollection<? extends T>... collections) {
+        this(type);
+        for (DomainObjectCollection<? extends T> collection : collections) {
+            addCollection(collection);
+        }
+    }
+
+    public class ItemIsUniqueInCompositeSpec implements Spec<T> {
+        public boolean isSatisfiedBy(T element) {
+            int matches = 0;
+            for (Object collection : getStore().getCollections()) {
+                if (((Collection)collection).contains(element)) {
+                    if (++matches > 1) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }
+
+    public class ItemNotInCompositeSpec implements Spec<T> {
+        public boolean isSatisfiedBy(T element) {
+            return !getStore().contains(element);
+        }
+    }
+
+    protected CompositeCollection getStore() {
+        return (CompositeCollection)super.getStore();
+    }
+
+    public Action<? super T> whenObjectAdded(Action<? super T> action) {
+        return super.whenObjectAdded(new FilteredAction<T>(uniqueSpec, action));
+    }
+
+    public Action<? super T> whenObjectRemoved(Action<? super T> action) {
+        return super.whenObjectRemoved(new FilteredAction<T>(notInSpec, action));
+    }
+    
+    public void addCollection(DomainObjectCollection<? extends T> collection) {
+        getStore().addComposited(collection);
+        collection.all(getEventRegister().getAddAction());
+        collection.whenObjectRemoved(getEventRegister().getRemoveAction());
+    }
+
+    public void removeCollection(DomainObjectCollection<? extends T> collection) {
+        getStore().removeComposited(collection);
+        Action<T> action = getEventRegister().getRemoveAction();
+        for (T item : collection) {
+            action.execute(item);
+        }
+    }
+
+    public Iterator<T> iterator() {
+        return new LinkedHashSet<T>(getStore()).iterator();
+    }
+
+    public int size() {
+        return new LinkedHashSet<T>(getStore()).size();
+    }
+    
+    public void all(Action<? super T> action) {
+        whenObjectAdded(action);
+
+        for (T t : this) {
+            action.execute(t);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
old mode 100644
new mode 100755
index 39feb1e..8b68663
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
@@ -1,108 +1,114 @@
-/*
- * 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;
-
-import groovy.lang.*;
-import groovy.lang.MissingMethodException;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public abstract class CompositeDynamicObject extends AbstractDynamicObject {
-    private DynamicObject[] objects = new DynamicObject[0];
-    private DynamicObject[] updateObjects = new DynamicObject[0];
-
-    protected void setObjects(DynamicObject... objects) {
-        this.objects = objects;
-        updateObjects = objects;
-    }
-
-    protected void setObjectsForUpdate(DynamicObject... objects) {
-        this.updateObjects = objects;
-    }
-
-    @Override
-    public boolean hasProperty(String name) {
-        for (DynamicObject object : objects) {
-            if (object.hasProperty(name)) {
-                return true;
-            }
-        }
-        return super.hasProperty(name);
-    }
-
-    @Override
-    public Object getProperty(String name) throws MissingPropertyException {
-        for (DynamicObject object : objects) {
-            if (object.hasProperty(name)) {
-                return object.getProperty(name);
-            }
-        }
-        return super.getProperty(name);
-    }
-
-    @Override
-    public void setProperty(String name, Object value) throws MissingPropertyException {
-        for (DynamicObject object : updateObjects) {
-            if (object.hasProperty(name)) {
-                object.setProperty(name, value);
-                return;
-            }
-        }
-        updateObjects[updateObjects.length - 1].setProperty(name, value);
-    }
-
-    @Override
-    public Map<String, Object> getProperties() {
-        Map<String, Object> properties = new HashMap<String, Object>();
-        for (int i = objects.length - 1; i >= 0; i--) {
-            DynamicObject object = objects[i];
-            properties.putAll(object.getProperties());
-        }
-        properties.put("properties", properties);
-        return properties;
-    }
-
-    @Override
-    public boolean hasMethod(String name, Object... arguments) {
-        for (DynamicObject object : objects) {
-            if (object.hasMethod(name, arguments)) {
-                return true;
-            }
-        }
-        return super.hasMethod(name, arguments);
-    }
-
-    @Override
-    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
-        for (DynamicObject object : objects) {
-            if (object.hasMethod(name, arguments)) {
-                return object.invokeMethod(name, arguments);
-            }
-        }
-
-        if (hasProperty(name)) {
-            Object property = getProperty(name);
-            if (property instanceof Closure) {
-                Closure closure = (Closure) property;
-                closure.setResolveStrategy(Closure.DELEGATE_FIRST);
-                return closure.call(arguments);
-            }
-        }
-
-        return super.invokeMethod(name, arguments);
-    }
-}
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Presents a {@link DynamicObject} view of multiple objects at once.
+ *
+ * Can be used to provide a dynamic view of an object with enhancements.
+ */
+public abstract class CompositeDynamicObject extends AbstractDynamicObject {
+    private DynamicObject[] objects = new DynamicObject[0];
+    private DynamicObject[] updateObjects = new DynamicObject[0];
+
+    protected void setObjects(DynamicObject... objects) {
+        this.objects = objects;
+        updateObjects = objects;
+    }
+
+    protected void setObjectsForUpdate(DynamicObject... objects) {
+        this.updateObjects = objects;
+    }
+
+    @Override
+    public boolean hasProperty(String name) {
+        for (DynamicObject object : objects) {
+            if (object.hasProperty(name)) {
+                return true;
+            }
+        }
+        return super.hasProperty(name);
+    }
+
+    @Override
+    public Object getProperty(String name) throws MissingPropertyException {
+        for (DynamicObject object : objects) {
+            if (object.hasProperty(name)) {
+                return object.getProperty(name);
+            }
+        }
+        return super.getProperty(name);
+    }
+
+    @Override
+    public void setProperty(String name, Object value) throws MissingPropertyException {
+        for (DynamicObject object : updateObjects) {
+            if (object.hasProperty(name)) {
+                object.setProperty(name, value);
+                return;
+            }
+        }
+        updateObjects[updateObjects.length - 1].setProperty(name, value);
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        for (int i = objects.length - 1; i >= 0; i--) {
+            DynamicObject object = objects[i];
+            properties.putAll(object.getProperties());
+        }
+        properties.put("properties", properties);
+        return properties;
+    }
+
+    @Override
+    public boolean hasMethod(String name, Object... arguments) {
+        for (DynamicObject object : objects) {
+            if (object.hasMethod(name, arguments)) {
+                return true;
+            }
+        }
+        return super.hasMethod(name, arguments);
+    }
+
+    @Override
+    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+        for (DynamicObject object : objects) {
+            if (object.hasMethod(name, arguments)) {
+                return object.invokeMethod(name, arguments);
+            }
+        }
+
+        if (hasProperty(name)) {
+            Object property = getProperty(name);
+            if (property instanceof Closure) {
+                Closure closure = (Closure) property;
+                closure.setResolveStrategy(Closure.DELEGATE_FIRST);
+                return closure.call(arguments);
+            }
+        }
+
+        return super.invokeMethod(name, arguments);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
new file mode 100644
index 0000000..df0715d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import org.gradle.api.Action;
+
+public class ConfigureDelegate extends GroovyObjectSupport {
+    private final DynamicObject owner;
+    private final DynamicObject delegate;
+    private final Action<String> onMissing;
+    private final ThreadLocal<Boolean> configuring = new ThreadLocal<Boolean>() {
+        @Override
+        protected Boolean initialValue() {
+            return false;
+        }
+    };
+
+    private static final Action<String> NOOP_ACTION = new Action<String>() {
+        public void execute(String s) {}
+    };
+
+    public ConfigureDelegate(Object owner, Object delegate) {
+        this(owner, delegate, NOOP_ACTION);
+    }
+    public ConfigureDelegate(Object owner, Object delegate, Action<String> onMissing) {
+        this.owner = DynamicObjectUtil.asDynamicObject(owner);
+        this.delegate = DynamicObjectUtil.asDynamicObject(delegate);
+        this.onMissing = onMissing;
+    }
+
+    @SuppressWarnings("EmptyCatchBlock")
+    public Object invokeMethod(String name, Object paramsObj) {
+        Object[] params = (Object[])paramsObj;
+
+        boolean isTopLevelCall = !configuring.get();
+        configuring.set(true);
+        try {
+            if (delegate.hasMethod(name, params)) {
+                return delegate.invokeMethod(name, params);
+            }
+
+            // try the owner
+            try {
+                return owner.invokeMethod(name, params);
+            } catch (groovy.lang.MissingMethodException e) {
+                // ignore
+            }
+
+            boolean isConfigureMethod = (params.length == 1) && (params[0] instanceof Closure);
+            if (isTopLevelCall && isConfigureMethod) {
+                onMissing.execute(name);
+            }
+
+            return delegate.invokeMethod(name, params);
+        } finally {
+            configuring.set(!isTopLevelCall);
+        }
+    }
+
+    @SuppressWarnings("EmptyCatchBlock")
+    public Object get(String name) {
+        if (delegate.hasProperty(name)) {
+            return delegate.getProperty(name);
+        }
+
+        // try the owner
+        try {
+            return owner.getProperty(name);
+        } catch (groovy.lang.MissingPropertyException e) {
+            // Ignore
+        }
+
+        // try the delegate again
+        onMissing.execute(name);
+        return delegate.getProperty(name);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
index a0ebf6a..0390c0a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
@@ -19,13 +19,12 @@ package org.gradle.api.internal;
 import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.plugins.Convention;
-import org.gradle.api.tasks.ConventionValue;
+import org.gradle.internal.UncheckedException;
 import org.gradle.util.ReflectionUtil;
-import org.gradle.util.UncheckedException;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -33,26 +32,42 @@ import java.util.concurrent.Callable;
 /**
  * @author Hans Dockter
  */
-public class ConventionAwareHelper implements ConventionMapping {
-    private Convention convention;
+public class ConventionAwareHelper implements ConventionMapping, HasConvention {
 
+    private Convention convention;
     private IConventionAware source;
+    private Map<String, MappedPropertyImpl> mappings = new HashMap<String, MappedPropertyImpl>();
 
-    private Map<String, ConventionValue> conventionMapping = new HashMap<String, ConventionValue>();
+    /**
+     * @see org.gradle.api.internal.AsmBackedClassGenerator.ClassBuilderImpl#mixInConventionAware()
+     */
+    public ConventionAwareHelper(IConventionAware source) {
+        this.source = source;
+        this.convention = new DefaultConvention();
+    }
 
     public ConventionAwareHelper(IConventionAware source, Convention convention) {
         this.source = source;
         this.convention = convention;
     }
 
-    public MappedProperty map(String propertyName, ConventionValue value) {
-        MappedPropertyImpl property = new MappedPropertyImpl(value);
-        map(Collections.singletonMap(propertyName, property));
-        return property;
+    private static interface Value<T> {
+        T getValue(Convention convention, IConventionAware conventionAwareObject);
+    }
+
+    private MappedProperty map(String propertyName, Value<?> value) {
+        if (!ReflectionUtil.hasProperty(source, propertyName)) {
+            throw new InvalidUserDataException(
+                    "You can't map a property that does not exist: propertyName=" + propertyName);
+        }
+
+        MappedPropertyImpl mappedProperty = new MappedPropertyImpl(value);
+        mappings.put(propertyName, mappedProperty);
+        return mappedProperty;
     }
 
-    public MappedProperty map(String propertyName, final Closure value) {
-        return map(propertyName, new ConventionValue() {
+    public MappedProperty map(String propertyName, final Closure<?> value) {
+        return map(propertyName, new Value<Object>() {
             public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
                 switch (value.getMaximumNumberOfParameters()) {
                     case 0:
@@ -60,80 +75,56 @@ public class ConventionAwareHelper implements ConventionMapping {
                     case 1:
                         return value.call(convention);
                     default:
-                        return value.call(new Object[]{convention, conventionAwareObject});
+                        return value.call(convention, conventionAwareObject);
                 }
             }
         });
     }
 
     public MappedProperty map(String propertyName, final Callable<?> value) {
-        return map(propertyName, new ConventionValue() {
+        return map(propertyName, new Value<Object>() {
             public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
                 try {
                     return value.call();
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
             }
         });
     }
 
-    public ConventionMapping map(Map<String, ? extends ConventionValue> mapping) {
-        for (Map.Entry<String, ? extends ConventionValue> entry : mapping.entrySet()) {
-            String propertyName = entry.getKey();
-            if (!ReflectionUtil.hasProperty(source, propertyName)) {
-                throw new InvalidUserDataException(
-                        "You can't map a property that does not exist: propertyName=" + propertyName);
-            }
-            if (entry.getValue() == null) {
-                throw new IllegalArgumentException("No convention value provided: propertyName= " + propertyName);
-            }
-        }
-        this.conventionMapping.putAll(mapping);
-        return this;
-    }
-
     public void propertyMissing(String name, Object value) {
         if (value instanceof Closure) {
             map(name, (Closure) value);
-        } else if (value instanceof ConventionValue) {
-            map(name, (ConventionValue) value);
         } else {
             throw new MissingPropertyException(name, getClass());
         }
     }
 
-    public Object getConventionValue(String propertyName) {
-        Object value = ReflectionUtil.getProperty(source, propertyName);
-        return getConventionValue(value, propertyName);
-    }
-
-    public <T> T getConventionValue(T internalValue, String propertyName) {
-        Object returnValue = internalValue;
-        boolean useMapping = internalValue == null
-                || internalValue instanceof Collection && ((Collection) internalValue).isEmpty()
-                || internalValue instanceof Map && ((Map) internalValue).isEmpty();
-        if (useMapping && conventionMapping.keySet().contains(propertyName)) {
-            returnValue = conventionMapping.get(propertyName).getValue(convention, source);
-        }
-        return (T) returnValue;
-    }
-
     public <T> T getConventionValue(T actualValue, String propertyName, boolean isExplicitValue) {
         if (isExplicitValue) {
             return actualValue;
         }
-        return getConventionValue(actualValue, propertyName);
+
+        Object returnValue = actualValue;
+        if (mappings.containsKey(propertyName)) {
+            boolean useMapping = true;
+            if (actualValue instanceof Collection && !((Collection<?>) actualValue).isEmpty()) {
+                useMapping = false;
+            } else if (actualValue instanceof Map && !((Map<?, ?>) actualValue).isEmpty()) {
+                useMapping = false;
+            }
+            if (useMapping) {
+                returnValue = mappings.get(propertyName).getValue(convention, source);
+            }
+        }
+        return (T) returnValue;
     }
 
     public Convention getConvention() {
         return convention;
     }
 
-    public void setConvention(Convention convention) {
-        this.convention = convention;
-    }
-
     public IConventionAware getSource() {
         return source;
     }
@@ -142,21 +133,13 @@ public class ConventionAwareHelper implements ConventionMapping {
         this.source = source;
     }
 
-    public Map getConventionMapping() {
-        return conventionMapping;
-    }
-
-    public void setConventionMapping(Map conventionMapping) {
-        this.conventionMapping = conventionMapping;
-    }
-
-    private static class MappedPropertyImpl implements MappedProperty, ConventionValue {
-        private final ConventionValue value;
+    private static class MappedPropertyImpl implements MappedProperty {
+        private final Value<?> value;
         private boolean haveValue;
         private boolean cache;
         private Object cachedValue;
 
-        private MappedPropertyImpl(ConventionValue value) {
+        private MappedPropertyImpl(Value<?> value) {
             this.value = value;
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionMapping.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionMapping.java
index cfe5a6b..29dd4c5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionMapping.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionMapping.java
@@ -15,34 +15,21 @@
  */
 package org.gradle.api.internal;
 
-import org.gradle.api.tasks.ConventionValue;
-import org.gradle.api.plugins.Convention;
+import groovy.lang.Closure;
 
-import java.util.Map;
 import java.util.concurrent.Callable;
 
-import groovy.lang.Closure;
-
 /**
  * <p>A {@code ConventionMapping} maintains the convention mappings for the properties of a particular object.</p>
  *
  * <p>Implementations should also allow mappings to be set using dynamic properties.</p>
  */
 public interface ConventionMapping {
-    Convention getConvention();
-
-    void setConvention(Convention convention);
 
-    MappedProperty map(String propertyName, ConventionValue value);
-
-    MappedProperty map(String propertyName, Closure value);
+    MappedProperty map(String propertyName, Closure<?> value);
 
     MappedProperty map(String propertyName, Callable<?> value);
 
-    ConventionMapping map(Map<String, ? extends ConventionValue> properties);
-
-    <T> T getConventionValue(T actualValue, String propertyName);
-
     <T> T getConventionValue(T actualValue, String propertyName, boolean isExplicitValue);
 
     interface MappedProperty {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
index 2d1ae66..f79245a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
@@ -16,9 +16,11 @@
 
 package org.gradle.api.internal;
 
+import groovy.lang.Closure;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Task;
-import org.gradle.api.tasks.ConventionValue;
+
+import java.util.concurrent.Callable;
 
 /**
  * @author Hans Dockter
@@ -30,7 +32,12 @@ public abstract class ConventionTask extends DefaultTask implements IConventionA
         conventionMapping = new ConventionAwareHelper(this, getProject().getConvention());
     }
 
-    public Task conventionMapping(String property, ConventionValue mapping) {
+    public Task conventionMapping(String property, Callable<?> mapping) {
+        conventionMapping.map(property, mapping);
+        return this;
+    }
+
+    public Task conventionMapping(String property, Closure mapping) {
         conventionMapping.map(property, mapping);
         return this;
     }
@@ -39,7 +46,4 @@ public abstract class ConventionTask extends DefaultTask implements IConventionA
         return conventionMapping;
     }
 
-    public void setConventionMapping(ConventionMapping mapping) {
-        this.conventionMapping = mapping;
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultAutoCreateDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultAutoCreateDomainObjectContainer.java
deleted file mode 100644
index 301d2e4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultAutoCreateDomainObjectContainer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2011 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;
-
-import groovy.lang.Closure;
-import org.gradle.api.NamedDomainObjectFactory;
-import org.gradle.util.ReflectionUtil;
-
-public class DefaultAutoCreateDomainObjectContainer<T> extends AutoCreateDomainObjectContainer<T> {
-    private final NamedDomainObjectFactory<T> factory;
-
-    public DefaultAutoCreateDomainObjectContainer(Class<T> type, ClassGenerator classGenerator, NamedDomainObjectFactory<T> factory) {
-        super(type, classGenerator);
-        this.factory = factory;
-    }
-
-    public DefaultAutoCreateDomainObjectContainer(Class<T> type, ClassGenerator classGenerator) {
-        this(type, classGenerator, new DefaultConstructorObjectFactory<T>(type));
-    }
-    
-    public DefaultAutoCreateDomainObjectContainer(Class<T> type, ClassGenerator classGenerator, final Closure factoryClosure) {
-        this(type, classGenerator, new ClosureObjectFactory<T>(type, factoryClosure));
-    }
-
-    @Override
-    protected T create(String name) {
-        return factory.create(name);
-    }
-
-    private static class DefaultConstructorObjectFactory<T> implements NamedDomainObjectFactory<T> {
-        private final Class<T> type;
-
-        public DefaultConstructorObjectFactory(Class<T> type) {
-            this.type = type;
-        }
-
-        public T create(String name) {
-            return type.cast(ReflectionUtil.newInstance(type, new Object[]{name}));
-        }
-    }
-
-    private static class ClosureObjectFactory<T> implements NamedDomainObjectFactory<T> {
-        private final Class<T> type;
-        private final Closure factoryClosure;
-
-        public ClosureObjectFactory(Class<T> type, Closure factoryClosure) {
-            this.type = type;
-            this.factoryClosure = factoryClosure;
-        }
-
-        public T create(String name) {
-            return type.cast(factoryClosure.call(name));
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
index 565480a..e783987 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
@@ -13,24 +13,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.gradle.api.internal;
 
-import java.util.List;
-import java.util.regex.Pattern;
+import org.gradle.api.internal.classpath.Module;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.util.ClassPath;
+import org.gradle.util.DefaultClassPath;
+
+public class DefaultClassPathProvider implements ClassPathProvider {
+    private final ModuleRegistry moduleRegistry;
+
+    public DefaultClassPathProvider(ModuleRegistry moduleRegistry) {
+        this.moduleRegistry = moduleRegistry;
+    }
 
-public class DefaultClassPathProvider extends AbstractClassPathProvider {
-    public DefaultClassPathProvider() {
-        List<Pattern> groovyPatterns = toPatterns("groovy-all");
+    public ClassPath findClassPath(String name) {
+        if (name.equals("GRADLE_RUNTIME")) {
+            ClassPath classpath = new DefaultClassPath();
+            for (Module module : moduleRegistry.getModule("gradle-launcher").getAllRequiredModules()) {
+                classpath = classpath.plus(module.getClasspath());
+            }
+            return classpath;
+        }
+        if (name.equals("GRADLE_CORE")) {
+            return moduleRegistry.getModule("gradle-core").getImplementationClasspath();
+        }
+        if (name.equals("COMMONS_CLI")) {
+            return moduleRegistry.getExternalModule("commons-cli").getClasspath();
+        }
+        if (name.equals("ANT")) {
+            ClassPath classpath = new DefaultClassPath();
+            classpath = classpath.plus(moduleRegistry.getExternalModule("ant").getClasspath());
+            classpath = classpath.plus(moduleRegistry.getExternalModule("ant-launcher").getClasspath());
+            return classpath;
+        }
+        if (name.equals("GROOVY")) {
+            return moduleRegistry.getExternalModule("groovy-all").getClasspath();
+        }
 
-        add("LOCAL_GROOVY", groovyPatterns);
-        List<Pattern> gradleApiPatterns = toPatterns("gradle-\\w+", "ivy", "slf4j", "ant");
-        gradleApiPatterns.addAll(groovyPatterns);
-        // Add the test fixture runtime, too
-        gradleApiPatterns.addAll(toPatterns("commons-io", "asm", "commons-lang", "commons-collections", "maven-ant-tasks"));
-        add("GRADLE_API", gradleApiPatterns);
-        add("GRADLE_CORE", toPatterns("gradle-core"));
-        add("ANT", toPatterns("ant", "ant-launcher"));
-        add("COMMONS_CLI", toPatterns("commons-cli"));
+        return null;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
old mode 100644
new mode 100755
index 7b72fcb..4d37c3d
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
@@ -1,75 +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.api.internal;
-
-import org.gradle.api.UncheckedIOException;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-
-public class DefaultClassPathRegistry implements ClassPathRegistry {
-    private final List<ClassPathProvider> providers = new ArrayList<ClassPathProvider>();
-
-    public DefaultClassPathRegistry(ClassPathProvider... providers) {
-        this.providers.addAll(Arrays.asList(providers));
-        this.providers.add(new DefaultClassPathProvider());
-    }
-
-    public URL[] getClassPathUrls(String name) {
-        return toURLArray(getClassPathFiles(name));
-    }
-
-    public Set<URL> getClassPath(String name) {
-        return toUrlSet(getClassPathFiles(name));
-    }
-
-    public Set<File> getClassPathFiles(String name) {
-        for (ClassPathProvider provider : providers) {
-            Set<File> classpath = provider.findClassPath(name);
-            if (classpath != null) {
-                return classpath;
-            }
-        }
-        throw new IllegalArgumentException(String.format("unknown classpath '%s' requested.", name));
-    }
-
-    private Set<URL> toUrlSet(Set<File> classPathFiles) {
-        Set<URL> urls = new LinkedHashSet<URL>();
-        for (File file : classPathFiles) {
-            try {
-                urls.add(file.toURI().toURL());
-            } catch (MalformedURLException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-        return urls;
-    }
-
-    private URL[] toURLArray(Collection<File> files) {
-        List<URL> urls = new ArrayList<URL>(files.size());
-        for (File file : files) {
-            try {
-                urls.add(file.toURI().toURL());
-            } catch (MalformedURLException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-        return urls.toArray(new URL[urls.size()]);
-    }
-}
+/*
+ * 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;
+
+import org.gradle.util.ClassPath;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class DefaultClassPathRegistry implements ClassPathRegistry {
+    private final List<ClassPathProvider> providers = new ArrayList<ClassPathProvider>();
+
+    public DefaultClassPathRegistry(ClassPathProvider... providers) {
+        this.providers.addAll(Arrays.asList(providers));
+    }
+
+    public ClassPath getClassPath(String name) {
+        for (ClassPathProvider provider : providers) {
+            ClassPath classpath = provider.findClassPath(name);
+            if (classpath != null) {
+                return classpath;
+            }
+        }
+        throw new IllegalArgumentException(String.format("unknown classpath '%s' requested.", name));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectCollection.java
new file mode 100755
index 0000000..59dde19
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectCollection.java
@@ -0,0 +1,291 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.internal.collections.CollectionEventRegister;
+import org.gradle.api.internal.collections.CollectionFilter;
+import org.gradle.api.internal.collections.FilteredCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.*;
+
+public class DefaultDomainObjectCollection<T> extends AbstractCollection<T> implements DomainObjectCollection<T> {
+
+    private final Class<? extends T> type;
+    private final CollectionEventRegister<T> eventRegister;
+    private final Collection<T> store;
+    private final Set<Runnable> mutateActions = new LinkedHashSet<Runnable>();
+
+    public DefaultDomainObjectCollection(Class<? extends T> type, Collection<T> store) {
+        this(type, store, new CollectionEventRegister<T>());
+    }
+
+    protected DefaultDomainObjectCollection(Class<? extends T> type, Collection<T> store, CollectionEventRegister<T> eventRegister) {
+        this.type = type;
+        this.store = store;
+        this.eventRegister = eventRegister;
+    }
+
+    protected DefaultDomainObjectCollection(DefaultDomainObjectCollection<? super T> collection, CollectionFilter<T> filter) {
+        this(filter.getType(), collection.filteredStore(filter), collection.filteredEvents(filter));
+    }
+
+    public Class<? extends T> getType() {
+        return type;
+    }
+
+    protected Collection<T> getStore() {
+        return store;
+    }
+
+    protected CollectionEventRegister<T> getEventRegister() {
+        return eventRegister;
+    }
+
+    protected CollectionFilter<T> createFilter(Spec<? super T> filter) {
+        return createFilter(getType(), filter);
+    }
+
+    protected <S extends T> CollectionFilter<S> createFilter(Class<S> type) {
+        return new CollectionFilter<S>(type);
+    }
+
+    protected <S extends T> CollectionFilter<S> createFilter(Class<? extends S> type, Spec<? super S> spec) {
+        return new CollectionFilter<S>(type, spec);
+    }
+
+    protected <S extends T> DefaultDomainObjectCollection<S> filtered(CollectionFilter<S> filter) {
+        return new DefaultDomainObjectCollection<S>(this, filter);
+    }
+
+    protected <S extends T> Collection<S> filteredStore(CollectionFilter<S> filter) {
+        return new FilteredCollection<T, S>(this, filter);
+    }
+
+    protected <S extends T> CollectionEventRegister<S> filteredEvents(CollectionFilter<S> filter) {
+        return getEventRegister().filtered(filter);
+    }
+
+    public DomainObjectCollection<T> matching(final Spec<? super T> spec) {
+        return filtered(createFilter(spec));
+    }
+
+    public DomainObjectCollection<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
+    }
+
+    public <S extends T> DomainObjectCollection<S> withType(final Class<S> type) {
+        return filtered(createFilter(type));
+    }
+
+    public Iterator<T> iterator() {
+        return new IteratorImpl(getStore().iterator());
+    }
+
+    public void all(Action<? super T> action) {
+        action = whenObjectAdded(action);
+
+        // copy in case any actions mutate the store
+        // linked list because the underlying store may preserve order
+        Collection<T> copied = new LinkedList<T>(this);
+
+        for (T t : copied) {
+            action.execute(t);
+        }
+    }
+
+    public void all(Closure action) {
+        all(toAction(action));
+    }
+
+    public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Action<? super S> configureAction) {
+        DomainObjectCollection<S> result = withType(type);
+        result.all(configureAction);
+        return result;
+    }
+
+    public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Closure configureClosure) {
+        DomainObjectCollection<S> result = withType(type);
+        result.all(configureClosure);
+        return result;
+    }
+
+    public Action<? super T> whenObjectAdded(Action<? super T> action) {
+        return eventRegister.registerAddAction(action);
+    }
+
+    public Action<? super T> whenObjectRemoved(Action<? super T> action) {
+        return eventRegister.registerRemoveAction(action);
+    }
+
+    public void whenObjectAdded(Closure action) {
+        whenObjectAdded(toAction(action));
+    }
+
+    public void whenObjectRemoved(Closure action) {
+        whenObjectRemoved(toAction(action));
+    }
+
+    /**
+     * Adds an action which is executed before this collection is mutated. Any exception thrown by the action will veto the mutation.
+     */
+    public void beforeChange(Runnable action) {
+        mutateActions.add(action);
+    }
+
+    private Action<? super T> toAction(final Closure action) {
+        return new Action<T>() {
+            public void execute(T t) {
+                ConfigureUtil.configure(action, t);
+            }
+        };
+    }
+
+    public boolean add(T toAdd) {
+        assertMutable();
+        return doAdd(toAdd);
+    }
+
+    private boolean doAdd(T toAdd) {
+        if (getStore().add(toAdd)) {
+            eventRegister.getAddAction().execute(toAdd);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean addAll(Collection<? extends T> c) {
+        assertMutable();
+        boolean changed = false;
+        for (T o : c) {
+            if (doAdd(o)) {
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    public void clear() {
+        assertMutable();
+        Object[] c = toArray();
+        getStore().clear();
+        for (Object o : c) {
+            eventRegister.getRemoveAction().execute((T)o);
+        }
+    }
+
+    public boolean contains(Object o) {
+        return getStore().contains(o);
+    }
+
+    public boolean containsAll(Collection<?> c) {
+        return getStore().containsAll(c);
+    }
+
+    public boolean isEmpty() {
+        return getStore().isEmpty();
+    }
+
+    public boolean remove(Object o) {
+        assertMutable();
+        return doRemove(o);
+    }
+
+    private boolean doRemove(Object o) {
+        if (getStore().remove(o)) {
+            eventRegister.getRemoveAction().execute((T)o);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeAll(Collection<?> c) {
+        assertMutable();
+        boolean changed = false;
+        for (Object o : c) {
+            if (doRemove(o)) {
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    public boolean retainAll(Collection<?> target) {
+        assertMutable();
+        Object[] existingItems = toArray();
+        boolean changed = false;
+        for (Object existingItem : existingItems) {
+            if (!target.contains(existingItem)) {
+                doRemove(existingItem);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    public int size() {
+        return getStore().size();
+    }
+
+    public Collection<T> findAll(Closure cl) {
+        return findAll(cl, new ArrayList<T>());
+    }
+
+    protected <S extends Collection<? super T>> S findAll(Closure cl, S matches) {
+        for (T t : filteredStore(createFilter(Specs.<Object>convertClosureToSpec(cl)))) {
+            matches.add(t);
+        }
+        return matches;
+    }
+
+    protected void assertMutable() {
+        for (Runnable action : mutateActions) {
+            action.run();
+        }
+    }
+
+    private class IteratorImpl implements Iterator<T> {
+        private final Iterator<T> iterator;
+        private T currentElement;
+
+        public IteratorImpl(Iterator<T> iterator) {
+            this.iterator = iterator;
+        }
+
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        public T next() {
+            currentElement = iterator.next();
+            return currentElement;
+        }
+
+        public void remove() {
+            assertMutable();
+            iterator.remove();
+            getEventRegister().getRemoveAction().execute(currentElement);
+            currentElement = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
deleted file mode 100644
index ed7bf2d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
+++ /dev/null
@@ -1,110 +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;
-
-import groovy.lang.Closure;
-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.ActionBroadcast;
-
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class DefaultDomainObjectContainer<T> extends AbstractDomainObjectCollection<T> {
-    private final Class<T> type;
-    private final ObjectStore<T> store;
-
-    public DefaultDomainObjectContainer(Class<T> type) {
-        this(type, new SetStore<T>());
-    }
-
-    protected DefaultDomainObjectContainer(Class<T> type, ObjectStore<T> store) {
-        super(store);
-        this.type = type;
-        this.store = store;
-    }
-
-    public Class<T> getType() {
-        return type;
-    }
-
-    public DomainObjectCollection<T> matching(final Spec<? super T> spec) {
-        return new DefaultDomainObjectContainer<T>(type, storeWithSpec(spec));
-    }
-
-    public DomainObjectCollection<T> matching(Closure spec) {
-        return matching(Specs.<T>convertClosureToSpec(spec));
-    }
-
-    public <S extends T> DomainObjectCollection<S> withType(final Class<S> type) {
-        return new DefaultDomainObjectContainer<S>(type, storeWithType(type));
-    }
-
-    protected ObjectStore<T> storeWithSpec(Spec<? super T> spec) {
-        return new FilteredObjectStore<T>(store, type, spec);
-    }
-
-    protected <S extends T> ObjectStore<S> storeWithType(Class<S> type) {
-        return new FilteredObjectStore<S>(store, type, Specs.satisfyAll());
-    }
-
-    public void addObject(T value) {
-        store.add(value);
-    }
-
-    protected interface ObjectStore<S> extends Store<S> {
-        void add(S object);
-    }
-
-    private static class SetStore<S> implements ObjectStore<S> {
-        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.execute(oldValue);
-            }
-            addActions.execute(object);
-        }
-
-        public Collection<? extends S> getAll() {
-            return objects.values();
-        }
-
-        public void objectAdded(Action<? super S> action) {
-            addActions.add(action);
-        }
-
-        public void objectRemoved(Action<? super S> action) {
-            removeActions.add(action);
-        }
-    }
-
-    private static class FilteredObjectStore<S> extends FilteredStore<S> implements ObjectStore<S> {
-        public FilteredObjectStore(ObjectStore<? super S> store, Class<S> type, Spec<? super S> spec) {
-            super(store, type, spec);
-        }
-
-        public void add(S object) {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectSet.java
new file mode 100755
index 0000000..eddd4f2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectSet.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.internal.collections.CollectionEventRegister;
+import org.gradle.api.internal.collections.CollectionFilter;
+import org.gradle.api.internal.collections.FilteredSet;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultDomainObjectSet<T> extends DefaultDomainObjectCollection<T> implements DomainObjectSet<T> {
+
+    public DefaultDomainObjectSet(Class<? extends T> type) {
+        this(type, new LinkedHashSet<T>());
+    }
+
+    public DefaultDomainObjectSet(Class<? extends T> type, Collection<T> store) {
+        super(type, store);
+    }
+
+    protected DefaultDomainObjectSet(DefaultDomainObjectSet<? super T> store, CollectionFilter<T> filter) {
+        this(filter.getType(), store.filteredStore(filter), store.filteredEvents(filter));
+    }
+
+    protected DefaultDomainObjectSet(Class<? extends T> type, Collection<T> store, CollectionEventRegister<T> eventRegister) {
+        super(type, store, eventRegister);
+    }
+
+    @Override
+    protected <S extends T> DefaultDomainObjectSet<S> filtered(CollectionFilter<S> filter) {
+        return new DefaultDomainObjectSet<S>(this, filter);
+    }
+
+    @Override
+    protected <S extends T> Set<S> filteredStore(CollectionFilter<S> filter) {
+        return new FilteredSet<T, S>(this, filter);
+    }
+
+    @Override
+    public <S extends T> DomainObjectSet<S> withType(Class<S> type) {
+        return filtered(createFilter(type));
+    }
+
+    @Override
+    public DomainObjectSet<T> matching(Spec<? super T> spec) {
+        return filtered(createFilter(spec));
+    }
+
+    @Override
+    public DomainObjectSet<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
+    }
+
+    @Override
+    public Set<T> findAll(Closure cl) {
+        return findAll(cl, new LinkedHashSet<T>());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
new file mode 100644
index 0000000..273c635
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
@@ -0,0 +1,325 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.*;
+import org.gradle.api.internal.collections.CollectionEventRegister;
+import org.gradle.api.internal.collections.CollectionFilter;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionContainer;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.*;
+
+public class DefaultNamedDomainObjectCollection<T> extends DefaultDomainObjectCollection<T> implements NamedDomainObjectCollection<T>, DynamicObjectAware {
+
+    private final Instantiator instantiator;
+    private final Namer<? super T> namer;
+
+    private final ContainerElementsDynamicObject elementsDynamicObject = new ContainerElementsDynamicObject();
+    private final Convention convention;
+    private final DynamicObject dynamicObject;
+
+    private final List<Rule> rules = new ArrayList<Rule>();
+    private Set<String> applyingRulesFor = new HashSet<String>();
+
+    public DefaultNamedDomainObjectCollection(Class<? extends T> type, Collection<T> store, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, store);
+        this.instantiator = instantiator;
+        this.convention = new DefaultConvention(instantiator);
+        this.dynamicObject = new ExtensibleDynamicObject(this, new ContainerDynamicObject(elementsDynamicObject), convention);
+        this.namer = namer;
+    }
+
+    protected DefaultNamedDomainObjectCollection(Class<? extends T> type, Collection<T> store, CollectionEventRegister<T> eventRegister, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, store, eventRegister);
+        this.instantiator = instantiator;
+        this.convention = new DefaultConvention(instantiator);
+        this.dynamicObject = new ExtensibleDynamicObject(this, new ContainerDynamicObject(elementsDynamicObject), convention);
+        this.namer = namer;
+    }
+
+    // should be protected, but use of the class generator forces it to be public
+    public DefaultNamedDomainObjectCollection(DefaultNamedDomainObjectCollection<? super T> collection, CollectionFilter<T> filter, Instantiator instantiator, Namer<? super T> namer) {
+        this(filter.getType(), collection.filteredStore(filter), collection.filteredEvents(filter), instantiator, namer);
+    }
+
+    /**
+     * Subclasses that can guarantee that the backing store enforces name uniqueness should override this to simply call super.add(T) (avoiding an unnecessary lookup)
+     */
+    public boolean add(T o) {
+        if (!hasWithName(namer.determineName(o))) {
+            return super.add(o);
+        } else {
+            handleAttemptToAddItemWithNonUniqueName(o);
+            return false;
+        }
+    }
+
+    /**
+     * <p>Subclass hook for implementations wanting to throw an exception when an attempt is made to add
+     * an item with the same name as an existing item.</p>
+     * 
+     * <p>This implementation does not thrown an exception, meaning that {@code add(T)} will simply return {@code false}.
+     * 
+     * @param o The item that is being attempted to add.
+     */
+    protected void handleAttemptToAddItemWithNonUniqueName(T o) {
+        // do nothing
+    }
+
+    /**
+     * Asserts that an item with the given name can be added to this collection.
+     */
+    protected void assertCanAdd(String name) {
+        if (hasWithName(name)) {
+            throw new InvalidUserDataException(String.format("Cannot add a %s with name '%s' as a %s with that name already exists.", getTypeDisplayName(), name, getTypeDisplayName()));
+        }
+    }
+
+    /**
+     * Asserts that the given item can be added to this collection.
+     */
+    protected void assertCanAdd(T t) {
+        assertCanAdd(getNamer().determineName(t));
+    }
+
+    public Namer<T> getNamer() {
+        return (Namer)this.namer;
+    }
+    
+    protected Instantiator getInstantiator() {
+        return instantiator;
+    }
+
+    /**
+     * Creates a filtered version of this collection.
+     */
+    protected <S extends T> DefaultNamedDomainObjectCollection<S> filtered(CollectionFilter<S> filter) {
+        return instantiator.newInstance(DefaultNamedDomainObjectCollection.class, this, filter, instantiator, namer);
+    }
+
+    public String getDisplayName() {
+        return String.format("%s container", getTypeDisplayName());
+    }
+
+    public SortedMap<String, T> getAsMap() {
+        SortedMap<String, T> map = new TreeMap<String, T>();
+        for (T o : getStore()) {
+            map.put(namer.determineName(o), o);
+        }
+        return map;
+    }
+
+    public <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type) {
+        return filtered(createFilter(type));
+    }
+
+    public NamedDomainObjectCollection<T> matching(Spec<? super T> spec) {
+        return filtered(createFilter(spec));
+    }
+
+    public NamedDomainObjectCollection<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
+    }
+
+    public T findByName(String name) {
+        T value = findByNameWithoutRules(name);
+        if (value != null) {
+            return value;
+        }
+        applyRules(name);
+        return findByNameWithoutRules(name);
+    }
+
+    protected boolean hasWithName(String name) {
+        return findByNameWithoutRules(name) != null;
+    }
+
+    protected T findByNameWithoutRules(String name) {
+        for (T o : getStore()) {
+            if (name.equals(namer.determineName(o))) {
+                return o;
+            }
+        }
+        return null;
+    }
+
+    protected T removeByName(String name) {
+        T it = getByName(name);
+        if (it != null) {
+            if (remove(it)) {
+                return it;
+            } else {
+                // unclear what the best thing to do here would be
+                throw new IllegalStateException(String.format("found '%s' with name '%s' but remove() returned false", it, name));
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public T getByName(String name) throws UnknownDomainObjectException {
+        T t = findByName(name);
+        if (t == null) {
+            throw createNotFoundException(name);
+        }
+        return t;
+    }
+
+    public T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException {
+        T t = getByName(name);
+        ConfigureUtil.configure(configureClosure, t);
+        return t;
+    }
+
+    public T getAt(String name) throws UnknownDomainObjectException {
+        return getByName(name);
+    }
+
+    /**
+     * Returns a {@link DynamicObject} which can be used to access the domain objects as dynamic properties and
+     * methods.
+     *
+     * @return The dynamic object
+     */
+    public DynamicObject getAsDynamicObject() {
+        return dynamicObject;
+    }
+
+    public Convention getConvention() {
+        return convention;
+    }
+
+    public ExtensionContainer getExtensions() {
+        return convention;
+    }
+
+    protected DynamicObject getElementsAsDynamicObject() {
+        return elementsDynamicObject;
+    }
+
+    private void applyRules(String name) {
+        if (applyingRulesFor.contains(name)) {
+            return;
+        }
+        applyingRulesFor.add(name);
+        try {
+            for (Rule rule : rules) {
+                rule.apply(name);
+            }
+        } finally {
+            applyingRulesFor.remove(name);
+        }
+    }
+
+    public Rule addRule(Rule rule) {
+        rules.add(rule);
+        return rule;
+    }
+
+    public Rule addRule(final String description, final Closure ruleAction) {
+        Rule rule = new Rule() {
+            public String getDescription() {
+                return description;
+            }
+
+            public void apply(String taskName) {
+                ruleAction.call(taskName);
+            }
+
+            @Override
+            public String toString() {
+                return "Rule: " + description;
+            }
+        };
+        rules.add(rule);
+        return rule;
+    }
+
+    public List<Rule> getRules() {
+        return Collections.unmodifiableList(rules);
+    }
+
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownDomainObjectException(String.format("%s with name '%s' not found.", getTypeDisplayName(),
+                name));
+    }
+
+    protected String getTypeDisplayName() {
+        return getType().getSimpleName();
+    }
+
+    private class ContainerDynamicObject extends CompositeDynamicObject {
+        private ContainerDynamicObject(ContainerElementsDynamicObject elementsDynamicObject) {
+            setObjects(new BeanDynamicObject(DefaultNamedDomainObjectCollection.this), elementsDynamicObject, convention.getExtensionsAsDynamicObject());
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return DefaultNamedDomainObjectCollection.this.getDisplayName();
+        }
+    }
+
+    private class ContainerElementsDynamicObject extends AbstractDynamicObject {
+        @Override
+        protected String getDisplayName() {
+            return DefaultNamedDomainObjectCollection.this.getDisplayName();
+        }
+
+        @Override
+        public boolean hasProperty(String name) {
+            return findByName(name) != null;
+        }
+
+        @Override
+        public T getProperty(String name) throws MissingPropertyException {
+            T t = findByName(name);
+            if (t == null) {
+                return (T) super.getProperty(name);
+            }
+            return t;
+        }
+
+        @Override
+        public Map<String, T> getProperties() {
+            return getAsMap();
+        }
+
+        @Override
+        public boolean hasMethod(String name, Object... arguments) {
+            return isConfigureMethod(name, arguments);
+        }
+
+        @Override
+        public Object invokeMethod(String name, Object... arguments) throws groovy.lang.MissingMethodException {
+            if (isConfigureMethod(name, arguments)) {
+                return ConfigureUtil.configure((Closure) arguments[0], getByName(name));
+            } else {
+                return super.invokeMethod(name, arguments);
+            }
+        }
+
+        private boolean isConfigureMethod(String name, Object... arguments) {
+            return (arguments.length == 1 && arguments[0] instanceof Closure) && hasProperty(name);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
deleted file mode 100644
index ef2ee24..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
+++ /dev/null
@@ -1,317 +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.api.internal;
-
-import groovy.lang.Closure;
-import groovy.lang.MissingPropertyException;
-import org.gradle.api.*;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.listener.ActionBroadcast;
-import org.gradle.util.ConfigureUtil;
-
-import java.util.*;
-
-public class DefaultNamedDomainObjectContainer<T> extends AbstractDomainObjectCollection<T>
-        implements NamedDomainObjectContainer<T> {
-    private final DynamicObject dynamicObject = new ContainerDynamicObject();
-    private final List<Rule> rules = new ArrayList<Rule>();
-    private final ClassGenerator classGenerator;
-    private final NamedObjectStore<T> store;
-    private final Class<T> type;
-    private Set<String> applyingRulesFor = new HashSet<String>();
-
-    public DefaultNamedDomainObjectContainer(Class<T> type, ClassGenerator classGenerator) {
-        this(type, classGenerator, new MapStore<T>());
-    }
-                                         
-    public DefaultNamedDomainObjectContainer(Class<T> type, ClassGenerator classGenerator, NamedObjectStore<T> store) {
-        super(store);
-        this.type = type;
-        this.classGenerator = classGenerator;
-        this.store = store;
-    }
-
-    protected Class<T> getType() {
-        return type;
-    }
-
-    protected ClassGenerator getClassGenerator() {
-        return classGenerator;
-    }
-
-    /**
-     * Adds a domain object to this container.
-     *
-     * @param name The name of the domain object.
-     * @param object The object to add
-     */
-    protected void addObject(String name, T object) {
-        assert object != null && name != null;
-        store.put(name, object);
-    }
-
-    public String getDisplayName() {
-        return String.format("%s container", getTypeDisplayName());
-    }
-
-    public Map<String, T> getAsMap() {
-        return Collections.unmodifiableMap(store.getAsMap());
-    }
-
-    public T findByName(String name) {
-        T value = store.find(name);
-        if (value != null) {
-            return value;
-        }
-        applyRules(name);
-        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));
-    }
-
-    public NamedDomainObjectCollection<T> matching(Closure spec) {
-        return matching(Specs.convertClosureToSpec(spec));
-    }
-
-    public <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type) {
-        return classGenerator.newInstance(DefaultNamedDomainObjectContainer.class, type, classGenerator, storeWithType(type));
-    }
-
-    protected NamedObjectStore<T> storeWithSpec(Spec<? super T> spec) {
-        return new FilteredObjectStore<T>(store, type, spec);
-    }
-
-    protected <S extends T> NamedObjectStore<S> storeWithType(Class<S> type) {
-        return new FilteredObjectStore<S>(store, type, Specs.satisfyAll());
-    }
-
-    public T getByName(String name) throws UnknownDomainObjectException {
-        T t = findByName(name);
-        if (t == null) {
-            throw createNotFoundException(name);
-        }
-        return t;
-    }
-
-    public T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException {
-        T t = getByName(name);
-        ConfigureUtil.configure(configureClosure, t);
-        return t;
-    }
-
-    public T getAt(String name) throws UnknownDomainObjectException {
-        return getByName(name);
-    }
-
-    /**
-     * Returns a {@link DynamicObject} which can be used to access the domain objects as dynamic properties and
-     * methods.
-     *
-     * @return The dynamic object
-     */
-    public DynamicObject getAsDynamicObject() {
-        return dynamicObject;
-    }
-
-    private void applyRules(String name) {
-        if (applyingRulesFor.contains(name)) {
-            return;
-        }
-        applyingRulesFor.add(name);
-        try {
-            for (Rule rule : rules) {
-                rule.apply(name);
-            }
-        } finally {
-            applyingRulesFor.remove(name);
-        }
-    }
-
-    public Rule addRule(Rule rule) {
-        rules.add(rule);
-        return rule;
-    }
-
-    public Rule addRule(final String description, final Closure ruleAction) {
-        Rule rule = new Rule() {
-            public String getDescription() {
-                return description;
-            }
-
-            public void apply(String taskName) {
-                ruleAction.call(taskName);
-            }
-
-            @Override
-            public String toString() {
-                return "Rule: " + description;
-            }
-        };
-        rules.add(rule);
-        return rule;
-    }
-
-    public List<Rule> getRules() {
-        return Collections.unmodifiableList(rules);
-    }
-
-    protected UnknownDomainObjectException createNotFoundException(String name) {
-        return new UnknownDomainObjectException(String.format("%s with name '%s' not found.", getTypeDisplayName(),
-                name));
-    }
-
-    protected String getTypeDisplayName() {
-        return type.getSimpleName();
-    }
-
-    protected interface NamedObjectStore<S> extends Store<S> {
-        S put(String name, S value);
-
-        S find(String name);
-
-        Map<String, S> getAsMap();
-    }
-
-    private static class MapStore<S> implements NamedObjectStore<S> {
-        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.execute(oldValue);
-            }
-            addActions.execute(value);
-            return oldValue;
-        }
-
-        public S find(String name) {
-            return objects.get(name);
-        }
-
-        public Collection<? extends S> getAll() {
-            return getAsMap().values();
-        }
-
-        public Map<String, S> getAsMap() {
-            return objects;
-        }
-
-        public void objectAdded(Action<? super S> action) {
-            addActions.add(action);
-        }
-
-        public void objectRemoved(Action<? super S> action) {
-            removeActions.add(action);
-        }
-    }
-
-    private static class FilteredObjectStore<S> extends FilteredStore<S> implements NamedObjectStore<S> {
-        private final NamedObjectStore<? super S> store;
-
-        public FilteredObjectStore(NamedObjectStore<? super S> store, Class<S> type, Spec<? super S> spec) {
-            super(store, type, spec);
-            this.store = store;
-        }
-
-        public S put(String name, S value) {
-            throw new UnsupportedOperationException();
-        }
-
-        public S find(String name) {
-            return filter(store.find(name));
-        }
-
-        public Collection<? extends S> getAll() {
-            return getAsMap().values();
-        }
-
-        public Map<String, S> getAsMap() {
-            Map<String, S> filteredMap = new LinkedHashMap<String, S>();
-            for (Map.Entry<String, ? super S> entry : store.getAsMap().entrySet()) {
-                S s = filter(entry.getValue());
-                if (s != null) {
-                    filteredMap.put(entry.getKey(), s);
-                }
-            }
-            return filteredMap;
-        }
-    }
-
-    private class ContainerDynamicObject extends CompositeDynamicObject {
-        private ContainerDynamicObject() {
-            setObjects(new BeanDynamicObject(DefaultNamedDomainObjectContainer.this),
-                    new ContainerElementsDynamicObject());
-        }
-
-        @Override
-        protected String getDisplayName() {
-            return DefaultNamedDomainObjectContainer.this.getDisplayName();
-        }
-    }
-
-    private class ContainerElementsDynamicObject extends AbstractDynamicObject {
-        @Override
-        protected String getDisplayName() {
-            return DefaultNamedDomainObjectContainer.this.getDisplayName();
-        }
-
-        @Override
-        public boolean hasProperty(String name) {
-            return findByName(name) != null;
-        }
-
-        @Override
-        public T getProperty(String name) throws MissingPropertyException {
-            T t = findByName(name);
-            if (t == null) {
-                return (T) super.getProperty(name);
-            }
-            return t;
-        }
-
-        @Override
-        public Map<String, T> getProperties() {
-            return getAsMap();
-        }
-
-        @Override
-        public boolean hasMethod(String name, Object... arguments) {
-            return isConfigureMethod(name, arguments);
-        }
-
-        @Override
-        public Object invokeMethod(String name, Object... arguments) throws groovy.lang.MissingMethodException {
-            if (isConfigureMethod(name, arguments)) {
-                return ConfigureUtil.configure((Closure) arguments[0], getByName(name));
-            } else {
-                return super.invokeMethod(name, arguments);
-            }
-        }
-
-        private boolean isConfigureMethod(String name, Object... arguments) {
-            return (arguments.length == 1 && arguments[0] instanceof Closure) && hasProperty(name);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java
new file mode 100644
index 0000000..f96ecc0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.NamedDomainObjectList;
+import org.gradle.api.Namer;
+import org.gradle.api.internal.collections.CollectionEventRegister;
+import org.gradle.api.internal.collections.CollectionFilter;
+import org.gradle.api.internal.collections.FilteredList;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.*;
+
+public class DefaultNamedDomainObjectList<T> extends DefaultNamedDomainObjectCollection<T> implements NamedDomainObjectList<T> {
+    public DefaultNamedDomainObjectList(DefaultNamedDomainObjectList<? super T> objects, CollectionFilter<T> filter, Instantiator instantiator, Namer<? super T> namer) {
+        super(objects, filter, instantiator, namer);
+    }
+
+    public DefaultNamedDomainObjectList(Class<T> type, CollectionEventRegister<T> collectionEventRegister, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, new ArrayList<T>(), collectionEventRegister, instantiator, namer);
+    }
+
+    public DefaultNamedDomainObjectList(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, new ArrayList<T>(), instantiator, namer);
+    }
+
+    public void add(int index, T element) {
+        assertMutable();
+        assertCanAdd(element);
+        getStore().add(index, element);
+        getEventRegister().getAddAction().execute(element);
+    }
+
+    public boolean addAll(int index, Collection<? extends T> c) {
+        assertMutable();
+        boolean changed = false;
+        int current = index;
+        for (T t : c) {
+            if (!hasWithName(getNamer().determineName(t))) {
+                getStore().add(current, t);
+                getEventRegister().getAddAction().execute(t);
+                changed = true;
+                current++;
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    protected List<T> getStore() {
+        return (List<T>) super.getStore();
+    }
+
+    public T get(int index) {
+        return getStore().get(index);
+    }
+
+    public T set(int index, T element) {
+        assertMutable();
+        assertCanAdd(element);
+        T oldElement = getStore().set(index, element);
+        getEventRegister().getRemoveAction().execute(oldElement);
+        getEventRegister().getAddAction().execute(element);
+        return oldElement;
+    }
+
+    public T remove(int index) {
+        assertMutable();
+        T element = getStore().remove(index);
+        getEventRegister().getRemoveAction().execute(element);
+        return element;
+    }
+
+    public int indexOf(Object o) {
+        return getStore().indexOf(o);
+    }
+
+    public int lastIndexOf(Object o) {
+        return getStore().lastIndexOf(o);
+    }
+
+    public ListIterator<T> listIterator() {
+        return new ListIteratorImpl(getStore().listIterator());
+    }
+
+    public ListIterator<T> listIterator(int index) {
+        return new ListIteratorImpl(getStore().listIterator(index));
+    }
+
+    public List<T> subList(int fromIndex, int toIndex) {
+        return Collections.unmodifiableList(getStore().subList(fromIndex, toIndex));
+    }
+
+    @Override
+    protected <S extends T> Collection<S> filteredStore(CollectionFilter<S> filter) {
+        return new FilteredList<T, S>(this, filter);
+    }
+
+    @Override
+    public NamedDomainObjectList<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
+    }
+
+    @Override
+    public NamedDomainObjectList<T> matching(Spec<? super T> spec) {
+        return new DefaultNamedDomainObjectList<T>(this, createFilter(spec), getInstantiator(), getNamer());
+    }
+
+    @Override
+    public <S extends T> NamedDomainObjectList<S> withType(Class<S> type) {
+        return new DefaultNamedDomainObjectList<S>(this, createFilter(type), getInstantiator(), getNamer());
+    }
+
+    @Override
+    public List<T> findAll(Closure cl) {
+        return findAll(cl, new ArrayList<T>());
+    }
+
+    private class ListIteratorImpl implements ListIterator<T> {
+        private final ListIterator<T> iterator;
+        private T lastElement;
+
+        public ListIteratorImpl(ListIterator<T> iterator) {
+            this.iterator = iterator;
+        }
+        
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        public boolean hasPrevious() {
+            return iterator.hasPrevious();
+        }
+
+        public T next() {
+            lastElement = iterator.next();
+            return lastElement;
+        }
+
+        public T previous() {
+            lastElement = iterator.previous();
+            return lastElement;
+        }
+
+        public int nextIndex() {
+            return iterator.nextIndex();
+        }
+
+        public int previousIndex() {
+            return iterator.previousIndex();
+        }
+
+        public void add(T t) {
+            assertMutable();
+            assertCanAdd(t);
+            iterator.add(t);
+            getEventRegister().getAddAction().execute(t);
+        }
+
+        public void remove() {
+            assertMutable();
+            iterator.remove();
+            getEventRegister().getRemoveAction().execute(lastElement);
+            lastElement = null;
+        }
+
+        public void set(T t) {
+            assertMutable();
+            assertCanAdd(t);
+            iterator.set(t);
+            getEventRegister().getRemoveAction().execute(lastElement);
+            getEventRegister().getAddAction().execute(t);
+            lastElement = null;
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java
new file mode 100644
index 0000000..008f1e9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Named;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Namer;
+import org.gradle.api.internal.collections.CollectionEventRegister;
+import org.gradle.api.internal.collections.CollectionFilter;
+import org.gradle.api.internal.collections.FilteredSet;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class DefaultNamedDomainObjectSet<T> extends DefaultNamedDomainObjectCollection<T> implements NamedDomainObjectSet<T> {
+
+    public DefaultNamedDomainObjectSet(Class<? extends T> type, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, new TreeSet(new Namer.Comparator(namer)), instantiator, namer);
+    }
+
+    public DefaultNamedDomainObjectSet(Class<? extends T> type, Instantiator instantiator) {
+        this(type, instantiator, Named.Namer.forType(type));
+    }
+
+    /**
+     * Subclasses using this constructor must ensure that the {@code store} uses a name based equality strategy as per the contract on NamedDomainObjectContainer.
+     */
+    protected DefaultNamedDomainObjectSet(Class<? extends T> type, Set<T> store, CollectionEventRegister<T> eventRegister, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, store, eventRegister, instantiator, namer);
+    }
+
+    // should be protected, but use of the class generator forces it to be public
+    public DefaultNamedDomainObjectSet(DefaultNamedDomainObjectSet<? super T> collection, CollectionFilter<T> filter, Instantiator instantiator, Namer<? super T> namer) {
+        this(filter.getType(), collection.filteredStore(filter), collection.filteredEvents(filter), instantiator, namer);
+    }
+
+    @Override
+    protected <S extends T> DefaultNamedDomainObjectSet<S> filtered(CollectionFilter<S> filter) {
+        return getInstantiator().newInstance(DefaultNamedDomainObjectSet.class, this, filter, getInstantiator(), getNamer());
+    }
+
+    protected <S extends T> Set<S> filteredStore(CollectionFilter<S> filter) {
+        return new FilteredSet<T, S>(this, filter);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s set", getTypeDisplayName());
+    }
+
+    @Override
+    public <S extends T> NamedDomainObjectSet<S> withType(Class<S> type) {
+        return filtered(createFilter(type));
+    }
+
+    @Override
+    public NamedDomainObjectSet<T> matching(Spec<? super T> spec) {
+        return filtered(createFilter(spec));
+    }
+
+    @Override
+    public NamedDomainObjectSet<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
+    }
+
+    @Override
+    public Set<T> findAll(Closure cl) {
+        return findAll(cl, new LinkedHashSet<T>());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DelegatingDomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DelegatingDomainObjectSet.java
new file mode 100644
index 0000000..11b9295
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DelegatingDomainObjectSet.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.specs.Spec;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+public class DelegatingDomainObjectSet<T> implements DomainObjectSet<T> {
+    private final DomainObjectSet<T> backingSet;
+
+    public DelegatingDomainObjectSet(DomainObjectSet<T> backingSet) {
+        this.backingSet = backingSet;
+    }
+
+    public DomainObjectSet<T> matching(Closure spec) {
+        return backingSet.matching(spec);
+    }
+
+    public DomainObjectSet<T> matching(Spec<? super T> spec) {
+        return backingSet.matching(spec);
+    }
+
+    public <S extends T> DomainObjectSet<S> withType(Class<S> type) {
+        return backingSet.withType(type);
+    }
+
+    public void all(Action<? super T> action) {
+        backingSet.all(action);
+    }
+
+    public void all(Closure action) {
+        backingSet.all(action);
+    }
+
+    public Action<? super T> whenObjectAdded(Action<? super T> action) {
+        return backingSet.whenObjectAdded(action);
+    }
+
+    public void whenObjectAdded(Closure action) {
+        backingSet.whenObjectAdded(action);
+    }
+
+    public Action<? super T> whenObjectRemoved(Action<? super T> action) {
+        return backingSet.whenObjectRemoved(action);
+    }
+
+    public void whenObjectRemoved(Closure action) {
+        backingSet.whenObjectRemoved(action);
+    }
+
+    public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Action<? super S> configureAction) {
+        return backingSet.withType(type, configureAction);
+    }
+
+    public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Closure configureClosure) {
+        return backingSet.withType(type, configureClosure);
+    }
+
+    public boolean add(T o) {
+        return backingSet.add(o);
+    }
+
+    public boolean addAll(Collection<? extends T> c) {
+        return backingSet.addAll(c);
+    }
+
+    public void clear() {
+        backingSet.clear();
+    }
+
+    public boolean contains(Object o) {
+        return backingSet.contains(o);
+    }
+
+    public boolean containsAll(Collection<?> c) {
+        return backingSet.containsAll(c);
+    }
+
+    public boolean isEmpty() {
+        return backingSet.isEmpty();
+    }
+
+    public Iterator<T> iterator() {
+        return backingSet.iterator();
+    }
+
+    public boolean remove(Object o) {
+        return backingSet.remove(o);
+    }
+
+    public boolean removeAll(Collection<?> c) {
+        return backingSet.removeAll(c);
+    }
+
+    public boolean retainAll(Collection<?> c) {
+        return backingSet.retainAll(c);
+    }
+
+    public int size() {
+        return backingSet.size();
+    }
+
+    public Object[] toArray() {
+        return backingSet.toArray();
+    }
+
+    public <T> T[] toArray(T[] a) {
+        return backingSet.toArray(a);
+    }
+
+    public Set<T> findAll(Closure spec) {
+        return backingSet.findAll(spec);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.java
new file mode 100644
index 0000000..ca7f7fa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.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;
+
+import org.gradle.api.internal.classpath.Module;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.api.internal.classpath.UnknownModuleException;
+import org.gradle.util.ClassPath;
+import org.gradle.util.DefaultClassPath;
+
+import static org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory.ClassPathNotation.GRADLE_API;
+import static org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory.ClassPathNotation.LOCAL_GROOVY;
+
+public class DependencyClassPathProvider implements ClassPathProvider {
+    private final ModuleRegistry moduleRegistry;
+    private final PluginModuleRegistry pluginModuleRegistry;
+
+    public DependencyClassPathProvider(ModuleRegistry moduleRegistry, PluginModuleRegistry pluginModuleRegistry) {
+        this.moduleRegistry = moduleRegistry;
+        this.pluginModuleRegistry = pluginModuleRegistry;
+    }
+
+    public ClassPath findClassPath(String name) {
+        if (name.equals(GRADLE_API.name())) {
+            ClassPath classpath = new DefaultClassPath();
+            Module core = moduleRegistry.getModule("gradle-core");
+            for (Module module : core.getAllRequiredModules()) {
+                classpath = classpath.plus(module.getClasspath());
+            }
+            classpath = classpath.plus(moduleRegistry.getModule("gradle-core-impl").getClasspath());
+            try {
+                classpath = classpath.plus(moduleRegistry.getModule("gradle-tooling-api").getImplementationClasspath());
+            } catch (UnknownModuleException e) {
+                // Ignore
+            }
+            for (Module pluginModule : pluginModuleRegistry.getPluginModules()) {
+                classpath = classpath.plus(pluginModule.getClasspath());
+            }
+            return classpath;
+        }
+        if (name.equals(LOCAL_GROOVY.name())) {
+            return moduleRegistry.getExternalModule("groovy-all").getClasspath();
+        }
+
+        return null;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectInstantiator.java
new file mode 100644
index 0000000..6f7b4dc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectInstantiator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.ReflectionUtil;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
+public class DirectInstantiator implements Instantiator {
+    public <T> T newInstance(Class<T> type, Object... params) {
+        try {
+            List<Constructor<?>> matches = new ArrayList<Constructor<?>>();
+            for (Constructor<?> constructor : type.getConstructors()) {
+                if (isMatch(constructor, params)) {
+                    matches.add(constructor);
+                }
+            }
+            if (matches.isEmpty()) {
+                throw new IllegalArgumentException(String.format("Could not find any public constructor for %s which accepts parameters %s.", type, Arrays.toString(params)));
+            }
+            if (matches.size() > 1) {
+                throw new IllegalArgumentException(String.format("Found multiple public constructors for %s which accept parameters %s.", type, Arrays.toString(params)));
+            }
+            return type.cast(matches.get(0).newInstance(params));
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private boolean isMatch(Constructor<?> constructor, Object... params) {
+        if (constructor.getParameterTypes().length != params.length) {
+            return false;
+        }
+        for (int i = 0; i < params.length; i++) {
+            Object param = params[i];
+            Class<?> parameterType = constructor.getParameterTypes()[i];
+            if (parameterType.isPrimitive()) {
+                if (!ReflectionUtil.getWrapperTypeForPrimitiveType(parameterType).isInstance(param)) {
+                    return false;
+                }
+            } else {
+                if (param != null && !parameterType.isInstance(param)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DocumentationRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DocumentationRegistry.java
new file mode 100644
index 0000000..db52f8f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DocumentationRegistry.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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;
+
+import java.io.File;
+
+/**
+ * Locates documentation for various features.
+ */
+public class DocumentationRegistry {
+    private final GradleDistributionLocator locator;
+
+    public DocumentationRegistry(GradleDistributionLocator locator) {
+        this.locator = locator;
+    }
+
+    /**
+     * Returns the location the documentation for the given feature, referenced by id. The location may be local or remote.
+     */
+    public String getDocumentationFor(String id) {
+        if (locator.getGradleHome() != null) {
+            File pageLocation = new File(locator.getGradleHome(), String.format("docs/userguide/%s.html", id));
+            File userGuideLocation = new File(locator.getGradleHome(), "docs/userguide/userguide.html");
+            if (pageLocation.isFile() && userGuideLocation.isFile()) {
+                return pageLocation.getAbsolutePath();
+            }
+            if (!pageLocation.isFile() && userGuideLocation.isFile()) {
+                throw new IllegalArgumentException(String.format("User guide page '%s' not found.", pageLocation));
+            }
+            if (pageLocation.isFile() && !userGuideLocation.isFile()) {
+                throw new IllegalArgumentException(String.format("User guide page '%s' not found.", userGuideLocation));
+            }
+        }
+        return String.format("http://gradle.org/docs/current/userguide/%s.html", id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DomNode.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DomNode.java
new file mode 100644
index 0000000..cb06d96
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DomNode.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.GroovySystem;
+import groovy.util.Node;
+
+/**
+ * A node which represents the root of an XML document.
+ */
+public class DomNode extends Node {
+    private String publicId;
+    private String systemId;
+
+    static {
+        setMetaClass(GroovySystem.getMetaClassRegistry().getMetaClass(DomNode.class), DomNode.class);
+    }
+    
+    public String getPublicId() {
+        return publicId;
+    }
+
+    public void setPublicId(String publicId) {
+        this.publicId = publicId;
+    }
+
+    public String getSystemId() {
+        return systemId;
+    }
+
+    public void setSystemId(String systemId) {
+        this.systemId = systemId;
+    }
+
+    public DomNode(Object name) {
+        super(null, name);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java
new file mode 100644
index 0000000..83cec61
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.internal.classpath.Module;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.util.ClassPath;
+import org.gradle.util.DefaultClassPath;
+
+public class DynamicModulesClassPathProvider implements ClassPathProvider {
+    private final ModuleRegistry moduleRegistry;
+    private final PluginModuleRegistry pluginModuleRegistry;
+
+    public DynamicModulesClassPathProvider(ModuleRegistry moduleRegistry, PluginModuleRegistry pluginModuleRegistry) {
+        this.moduleRegistry = moduleRegistry;
+        this.pluginModuleRegistry = pluginModuleRegistry;
+    }
+
+    public ClassPath findClassPath(String name) {
+        if (name.equals("GRADLE_PLUGINS")) {
+            ClassPath classpath = new DefaultClassPath();
+            for (Module pluginModule : pluginModuleRegistry.getPluginModules()) {
+                classpath = classpath.plus(pluginModule.getClasspath());
+            }
+            return classpath;
+        }
+        if (name.equals("GRADLE_CORE_IMPL")) {
+            return moduleRegistry.getModule("gradle-core-impl").getClasspath();
+        }
+
+        return null;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObject.java
index 0cc2a14..10306e0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObject.java
@@ -20,6 +20,13 @@ import groovy.lang.MissingPropertyException;
 
 import java.util.Map;
 
+/**
+ * An object that can be worked with in a dynamic fashion.
+ *
+ * The semantics of each method is completely up to the implementation. For example, {@link BeanDynamicObject}
+ * provides a dynamic view of the functionality of an object and does not provide any decoration or extra functionality.
+ * The {@link ExtensibleDynamicObject} implementation on the other hand does provide extra functionality.
+ */
 public interface DynamicObject {
     boolean hasProperty(String name);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java
index 644c379..ff49f2b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java
@@ -15,35 +15,19 @@
  */
 package org.gradle.api.internal;
 
-import org.gradle.api.plugins.Convention;
-
 /**
- * <p>Allows properties and method to be added to an object at run-time. Most implementations are generated at run-time
- * from existing classes, by a {@link org.gradle.api.internal.ClassGenerator} implementation.</p>
+ * An object that can present a dynamic view of itself.
  *
- * <p>A {@code DynamicObjectAware} implementation should add meta-object methods which provide the properties and
- * methods returned by {@link #getAsDynamicObject()}.</p>
+ * The exposed dynamic object <i>may</i> provide functionality over and above what the type implementing
+ * this interface can do. For example, the {@link DynamicObject} may provide the ability to register new
+ * properties or implement methods that this object does not provide in a concrete way.
  */
 public interface DynamicObjectAware {
-    /**
-     * Returns the convention object used by this dynamic object.
-     *
-     * @return The convention object. Never returns null.
-     */
-    Convention getConvention();
-
-    /**
-     * Sets the convention object used by this dynamic object.
-     *
-     * @param convention The convention object. Should not be null.
-     */
-    void setConvention(Convention convention);
 
     /**
-     * Returns a {@link DynamicObject} which describes all the static and dynamic properties and methods of this
-     * object.
+     * Returns a {@link DynamicObject} for this object.
      *
-     * @return The meta-info for this object.
+     * @return The dynamic object.
      */
     DynamicObject getAsDynamicObject();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
index 2df2a06..3bb87f7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2012 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.
@@ -13,155 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal;
-
-import groovy.lang.MissingPropertyException;
-import org.gradle.api.plugins.Convention;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-public class DynamicObjectHelper extends CompositeDynamicObject {
-    public enum Location {
-        BeforeConvention, AfterConvention
-    }
-
-    private final AbstractDynamicObject delegateObject;
-    private DynamicObject parent;
-    private Convention convention;
-    private DynamicObject beforeConvention;
-    private DynamicObject afterConvention;
-    private MapBackedDynamicObject additionalProperties;
-
-    public DynamicObjectHelper(Object delegateObject) {
-        this(new BeanDynamicObject(delegateObject), null);
-    }
-
-    public DynamicObjectHelper(Object delegateObject, Convention convention) {
-        this(new BeanDynamicObject(delegateObject), convention);
-    }
-
-    public DynamicObjectHelper(AbstractDynamicObject delegateObject, Convention convention) {
-        this.delegateObject = delegateObject;
-        additionalProperties = new MapBackedDynamicObject(delegateObject);
-        setConvention(convention);
-    }
-
-    private void updateDelegates() {
-        List<DynamicObject> delegates = new ArrayList<DynamicObject>();
-        delegates.add(delegateObject);
-        delegates.add(additionalProperties);
-        if (beforeConvention != null) {
-            delegates.add(beforeConvention);
-        }
-        if (convention != null) {
-            delegates.add(convention);
-        }
-        if (afterConvention != null) {
-            delegates.add(afterConvention);
-        }
-        if (parent != null) {
-            delegates.add(parent);
-        }
-        setObjects(delegates.toArray(new DynamicObject[delegates.size()]));
-
-        delegates.remove(parent);
-        delegates.add(additionalProperties);
-        setObjectsForUpdate(delegates.toArray(new DynamicObject[delegates.size()]));
-    }
-
-    protected String getDisplayName() {
-        return delegateObject.getDisplayName();
-    }
-
-    public Map<String, Object> getAdditionalProperties() {
-        return additionalProperties.getProperties();
-    }
-
-    public DynamicObject getParent() {
-        return parent;
-    }
 
-    public void setParent(DynamicObject parent) {
-        this.parent = parent;
-        updateDelegates();
-    }
-
-    public Convention getConvention() {
-        return convention;
-    }
-
-    public void setConvention(Convention convention) {
-        this.convention = convention;
-        updateDelegates();
-    }
-
-    public void addObject(DynamicObject object, Location location) {
-        switch (location) {
-            case BeforeConvention:
-                beforeConvention = object;
-                break;
-            case AfterConvention:
-                afterConvention = object;
-        }
-        updateDelegates();
-    }
-
-    /**
-     * Returns the inheritable properties and methods of this object.
-     *
-     * @return an object containing the inheritable properties and methods of this object.
-     */
-    public DynamicObject getInheritable() {
-        return new InheritedDynamicObject();
-    }
-
-    private DynamicObjectHelper snapshotInheritable() {
-        AbstractDynamicObject emptyBean = new AbstractDynamicObject() {
-            @Override
-            protected String getDisplayName() {
-                return delegateObject.getDisplayName();
-            }
-        };
+package org.gradle.api.internal;
 
-        DynamicObjectHelper helper = new DynamicObjectHelper(emptyBean);
+/**
+ * This is necessary because DynamicObjectHelper was renamed to ExtensibleDynamicObject in 1.0-milestone-9.
+ *
+ * AbstractTask leaked DynamicObjectHelper by having a public method that returned this type. This method
+ * has been deprecated but we need to keep a class around with the same name for backwards compatibility.
+ *
+ * This will probably have to stay until we remove task inheritance.
+ */
+public class DynamicObjectHelper extends BeanDynamicObject {
 
-        helper.parent = parent;
-        helper.convention = convention;
-        helper.additionalProperties = additionalProperties;
-        if (beforeConvention != null) {
-            helper.beforeConvention = beforeConvention;
-        }
-        helper.updateDelegates();
-        return helper;
+    public DynamicObjectHelper(ExtensibleDynamicObject delegate) {
+        super(delegate);
     }
 
-    private class InheritedDynamicObject implements DynamicObject {
-        public void setProperty(String name, Object value) {
-            throw new MissingPropertyException(String.format("Could not find property '%s' inherited from %s.", name,
-                    delegateObject.getDisplayName()));
-        }
-
-        public boolean hasProperty(String name) {
-            return snapshotInheritable().hasProperty(name);
-        }
-
-        public Object getProperty(String name) {
-            return snapshotInheritable().getProperty(name);
-        }
-
-        public Map<String, Object> getProperties() {
-            return snapshotInheritable().getProperties();
-        }
-
-        public boolean hasMethod(String name, Object... arguments) {
-            return snapshotInheritable().hasMethod(name, arguments);
-        }
-
-        public Object invokeMethod(String name, Object... arguments) {
-            return snapshotInheritable().invokeMethod(name, arguments);
-        }
-    }
 }
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectUtil.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectUtil.java
new file mode 100644
index 0000000..dc04fdc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectUtil.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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;
+
+public abstract class DynamicObjectUtil {
+
+    public static DynamicObject asDynamicObject(Object object) {
+        if (object instanceof DynamicObject) {
+            return (DynamicObject)object;
+        } else if (object instanceof DynamicObjectAware) {
+            return ((DynamicObjectAware) object).getAsDynamicObject();
+        } else {
+            return new BeanDynamicObject(object);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicPropertyNamer.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicPropertyNamer.groovy
new file mode 100644
index 0000000..b20c4da
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicPropertyNamer.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.api.Namer
+
+class DynamicPropertyNamer implements Namer<Object> {
+
+    final String propertyName
+
+    DynamicPropertyNamer() {
+        this("name")
+    }
+
+    DynamicPropertyNamer(String propertyName) {
+        this.propertyName = propertyName
+    }
+
+    String determineName(thing) {
+        def name
+
+        try {
+            name = thing."$propertyName"
+        } catch (MissingPropertyException e) {
+            throw new NoNamingPropertyException(thing, propertyName)
+        }
+        
+        if (name == null) {
+            throw new NullNamingPropertyException(thing, propertyName)
+        }
+        
+        name.toString()
+    }
+
+    private static class NoNamingPropertyException extends RuntimeException {
+        NoNamingPropertyException(Object thing, String property) {
+            super("Unable to determine the name of '$thing' because it does not have a '$property' property")
+        }
+    }
+    
+    private static class NullNamingPropertyException extends RuntimeException {
+        NullNamingPropertyException(Object thing, String property) {
+            super("Unable to determine the name of '$thing' because its value for the naming property '$property' is null")
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ExceptionAnalyser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExceptionAnalyser.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
new file mode 100644
index 0000000..66af052
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
@@ -0,0 +1,196 @@
+/*
+ * 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;
+
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.internal.plugins.ExtraPropertiesDynamicObjectAdapter;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link DynamicObject} implementation that provides extensibility.
+ *
+ * This is the dynamic object implementation that “enhanced” objects expose.
+ *
+ * @see org.gradle.api.internal.AsmBackedClassGenerator.MixInExtensibleDynamicObject
+ */
+public class ExtensibleDynamicObject extends CompositeDynamicObject implements HasConvention {
+
+    public enum Location {
+        BeforeConvention, AfterConvention
+    }
+
+    private final Object delegate;
+    private final AbstractDynamicObject dynamicDelegate;
+    private DynamicObject parent;
+    private Convention convention;
+    private DynamicObject beforeConvention;
+    private DynamicObject afterConvention;
+    private DynamicObject extraPropertiesDynamicObject;
+
+    /**
+     * This variant will internally create a convention that is not fully featured, so should be avoided.
+     *
+     * Use one of the other variants wherever possible.
+     *
+     * @param delegate The delegate
+     * @see org.gradle.api.internal.plugins.DefaultConvention#DefaultConvention()
+     */
+    public ExtensibleDynamicObject(Object delegate) {
+        this(delegate, new BeanDynamicObject(delegate), new DefaultConvention());
+    }
+
+    public ExtensibleDynamicObject(Object delegate, Instantiator instantiator) {
+        this(delegate, new BeanDynamicObject(delegate), new DefaultConvention(instantiator));
+    }
+
+    public ExtensibleDynamicObject(Object delegate, AbstractDynamicObject dynamicDelegate, Instantiator instantiator) {
+        this(delegate, dynamicDelegate, new DefaultConvention(instantiator));
+    }
+
+    public ExtensibleDynamicObject(Object delegate, AbstractDynamicObject dynamicDelegate, Convention convention) {
+        this.delegate = delegate;
+        this.dynamicDelegate = dynamicDelegate;
+        this.convention = convention;
+        this.extraPropertiesDynamicObject = new ExtraPropertiesDynamicObjectAdapter(delegate, this, convention.getExtraProperties());
+
+        updateDelegates();
+    }
+
+    private void updateDelegates() {
+        List<DynamicObject> delegates = new ArrayList<DynamicObject>();
+        delegates.add(dynamicDelegate);
+        delegates.add(extraPropertiesDynamicObject);
+        if (beforeConvention != null) {
+            delegates.add(beforeConvention);
+        }
+        if (convention != null) {
+            delegates.add(convention.getExtensionsAsDynamicObject());
+        }
+        if (afterConvention != null) {
+            delegates.add(afterConvention);
+        }
+        if (parent != null) {
+            delegates.add(parent);
+        }
+        setObjects(delegates.toArray(new DynamicObject[delegates.size()]));
+
+        delegates.remove(parent);
+        delegates.add(extraPropertiesDynamicObject);
+        setObjectsForUpdate(delegates.toArray(new DynamicObject[delegates.size()]));
+    }
+
+    protected String getDisplayName() {
+        return dynamicDelegate.getDisplayName();
+    }
+
+    public ExtraPropertiesExtension getDynamicProperties() {
+        return convention.getExtraProperties();
+    }
+
+    public void addProperties(Map<String, ?> properties) {
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            getDynamicProperties().set(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public DynamicObject getParent() {
+        return parent;
+    }
+
+    public void setParent(DynamicObject parent) {
+        this.parent = parent;
+        updateDelegates();
+    }
+
+    public Convention getConvention() {
+        return convention;
+    }
+
+    public void addObject(DynamicObject object, Location location) {
+        switch (location) {
+            case BeforeConvention:
+                beforeConvention = object;
+                break;
+            case AfterConvention:
+                afterConvention = object;
+        }
+        updateDelegates();
+    }
+
+    /**
+     * Returns the inheritable properties and methods of this object.
+     *
+     * @return an object containing the inheritable properties and methods of this object.
+     */
+    public DynamicObject getInheritable() {
+        return new InheritedDynamicObject();
+    }
+
+    private ExtensibleDynamicObject snapshotInheritable() {
+        AbstractDynamicObject emptyBean = new AbstractDynamicObject() {
+            @Override
+            protected String getDisplayName() {
+                return dynamicDelegate.getDisplayName();
+            }
+        };
+
+        ExtensibleDynamicObject extensibleDynamicObject = new ExtensibleDynamicObject(emptyBean);
+
+        extensibleDynamicObject.parent = parent;
+        extensibleDynamicObject.convention = convention;
+        extensibleDynamicObject.extraPropertiesDynamicObject = extraPropertiesDynamicObject;
+        if (beforeConvention != null) {
+            extensibleDynamicObject.beforeConvention = beforeConvention;
+        }
+        extensibleDynamicObject.updateDelegates();
+        return extensibleDynamicObject;
+    }
+
+    private class InheritedDynamicObject implements DynamicObject {
+        public void setProperty(String name, Object value) {
+            throw new MissingPropertyException(String.format("Could not find property '%s' inherited from %s.", name,
+                    dynamicDelegate.getDisplayName()));
+        }
+
+        public boolean hasProperty(String name) {
+            return snapshotInheritable().hasProperty(name);
+        }
+
+        public Object getProperty(String name) {
+            return snapshotInheritable().getProperty(name);
+        }
+
+        public Map<String, Object> getProperties() {
+            return snapshotInheritable().getProperties();
+        }
+
+        public boolean hasMethod(String name, Object... arguments) {
+            return snapshotInheritable().hasMethod(name, arguments);
+        }
+
+        public Object invokeMethod(String name, Object... arguments) {
+            return snapshotInheritable().invokeMethod(name, arguments);
+        }
+    }
+
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/Factory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/Factory.java
deleted file mode 100644
index 8a01b10..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/Factory.java
+++ /dev/null
@@ -1,29 +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.api.internal;
-
-/**
- * A generic factory which creates instances of type T.
- *
- * @param <T> The type of object created.
- */
-public interface Factory<T> {
-    /**
-     * Creates a new instance of type T.
-     * @return The instance. Never returns null.
-     */
-    T create();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java
new file mode 100644
index 0000000..095d516
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Namer;
+import org.gradle.api.Named;
+import org.gradle.api.NamedDomainObjectFactory;
+
+public class FactoryNamedDomainObjectContainer<T> extends AbstractNamedDomainObjectContainer<T> {
+
+    private final NamedDomainObjectFactory<T> factory;
+
+    /**
+     * <p>Creates a container that instantiates reflectively, expecting a 1 arg constructor taking the name.<p>
+     *
+     * <p>The type must implement the {@link Named} interface as a {@link Namer} will be created based on this type.</p>
+     *
+     * @param type The concrete type of element in the container (must implement {@link Named})
+     * @param instantiator The instantiator to use to create any other collections based on this one
+     */
+    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator) {
+        this(type, instantiator, Named.Namer.forType(type));
+    }
+
+    /**
+     * <p>Creates a container that instantiates reflectively, expecting a 1 arg constructor taking the name.<p>
+     *
+     * @param type The concrete type of element in the container (must implement {@link Named})
+     * @param instantiator The instantiator to use to create any other collections based on this one
+     * @param namer The naming strategy to use
+     */
+    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
+        this(type, instantiator, namer, new ReflectiveNamedDomainObjectFactory<T>(type));
+    }
+
+    /**
+     * <p>Creates a container that instantiates using the given factory.<p>
+     *
+     * @param type The concrete type of element in the container (must implement {@link Named})
+     * @param instantiator The instantiator to use to create any other collections based on this one
+     * @param factory The factory responsible for creating new instances on demand
+     */
+    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, NamedDomainObjectFactory<T> factory) {
+        this(type, instantiator, Named.Namer.forType(type), factory);
+    }
+
+    /**
+     * <p>Creates a container that instantiates using the given factory.<p>
+     *
+     * @param type The concrete type of element in the container
+     * @param instantiator The instantiator to use to create any other collections based on this one
+     * @param namer The naming strategy to use
+     * @param factory The factory responsible for creating new instances on demand
+     */
+    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer, NamedDomainObjectFactory<T> factory) {
+        super(type, instantiator, namer);
+        this.factory = factory;
+    }
+
+    /**
+     * <p>Creates a container that instantiates using the given factory.<p>
+     *
+     * @param type The concrete type of element in the container (must implement {@link Named})
+     * @param instantiator The instantiator to use to create any other collections based on this one
+     * @param factoryClosure The closure responsible for creating new instances on demand
+     */
+    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, final Closure factoryClosure) {
+        this(type, instantiator, Named.Namer.forType(type), factoryClosure);
+    }
+
+    /**
+     * <p>Creates a container that instantiates using the given factory.<p>
+     *
+     * @param type The concrete type of element in the container
+     * @param instantiator The instantiator to use to create any other collections based on this one
+     * @param namer The naming strategy to use
+     * @param factoryClosure The factory responsible for creating new instances on demand
+     */
+    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer, final Closure factoryClosure) {
+        this(type, instantiator, namer, new ClosureObjectFactory<T>(type, factoryClosure));
+    }
+
+    @Override
+    protected T doCreate(String name) {
+        return factory.create(name);
+    }
+
+    private static class ClosureObjectFactory<T> implements NamedDomainObjectFactory<T> {
+        private final Class<T> type;
+        private final Closure factoryClosure;
+
+        public ClosureObjectFactory(Class<T> type, Closure factoryClosure) {
+            this.type = type;
+            this.factoryClosure = factoryClosure;
+        }
+
+        public T create(String name) {
+            return type.cast(factoryClosure.call(name));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/FilteredAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/FilteredAction.java
new file mode 100644
index 0000000..54bf4bd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/FilteredAction.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.specs.Spec;
+
+public class FilteredAction<T> implements Action<T> {
+
+    private final Spec<? super T> filter;
+    private final Action<? super T> action;
+
+    public FilteredAction(Spec<? super T> filter, Action<? super T> action) {
+        this.filter = filter;
+        this.action = action;
+    }
+
+    public void execute(T t) {
+        if (filter.isSatisfiedBy(t)) {
+            action.execute(t);
+        }
+    }
+
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.java
index 5659828..9a219fe 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.java
@@ -18,5 +18,8 @@ package org.gradle.api.internal;
 import java.io.File;
 
 public interface GradleDistributionLocator {
+    /**
+     * Returns the directory containing the Gradle distribution of the current Gradle version. May be null.
+     */
     File getGradleHome();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
deleted file mode 100644
index baa7244..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
+++ /dev/null
@@ -1,134 +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.api.internal;
-
-import groovy.lang.GroovyClassLoader;
-import groovy.lang.MetaBeanProperty;
-import groovy.lang.MetaMethod;
-
-import java.lang.reflect.Constructor;
-import java.util.Formatter;
-
-public class GroovySourceGenerationBackedClassGenerator extends AbstractClassGenerator {
-
-    protected <T> ClassBuilder<T> start(Class<T> type) {
-        return new ClassBuilderImpl<T>(type);
-    }
-
-    private static class ClassBuilderImpl<T> implements ClassBuilder<T> {
-        private final Formatter src;
-        private final Class<T> type;
-        private final String className;
-        private boolean dynamicAware;
-
-        private ClassBuilderImpl(Class<T> type) {
-            className = type.getSimpleName() + "_Generated";
-            src = new Formatter();
-            this.type = type;
-        }
-
-        public void startClass(boolean isConventionAware, boolean isDynamicAware) {
-            dynamicAware = isDynamicAware;
-            if (type.getPackage() != null) {
-                src.format("package %s;%n", type.getPackage().getName());
-            }
-            src.format("public class %s extends %s ", className, type.getName().replaceAll("\\$", "."));
-            if (isConventionAware) {
-                src.format("implements org.gradle.api.internal.IConventionAware ");
-            }
-            if (isDynamicAware) {
-                src.format(isConventionAware ? ", " : "implements ");
-                src.format("org.gradle.api.internal.DynamicObjectAware ");
-            }
-            src.format("{%n");
-        }
-
-        public void addConstructor(Constructor<?> constructor) {
-            src.format("public %s(", className);
-            for (int i = 0; i < constructor.getParameterTypes().length; i++) {
-                Class<?> paramType = constructor.getParameterTypes()[i];
-                if (i > 0) {
-                    src.format(",");
-                }
-                src.format("%s p%d", paramType.getCanonicalName(), i);
-            }
-            src.format(") { super(");
-            for (int i = 0; i < constructor.getParameterTypes().length; i++) {
-                if (i > 0) {
-                    src.format(",");
-                }
-                src.format("p%d", i);
-            }
-            src.format("); }%n");
-        }
-
-        public void mixInDynamicAware() {
-            src.format("private org.gradle.api.internal.DynamicObjectHelper dynamicObject = new org.gradle.api.internal.DynamicObjectHelper(this, new org.gradle.api.internal.plugins.DefaultConvention())%n");
-            src.format("public void setConvention(org.gradle.api.plugins.Convention convention) { dynamicObject.setConvention(convention); getConventionMapping().setConvention(convention) }%n");
-            src.format("public org.gradle.api.plugins.Convention getConvention() { return dynamicObject.getConvention() }%n");
-            src.format("public org.gradle.api.internal.DynamicObject getAsDynamicObject() { return dynamicObject }%n");
-        }
-
-        public void mixInConventionAware() {
-            if (dynamicAware) {
-                src.format("private org.gradle.api.internal.ConventionMapping mapping = new org.gradle.api.internal.ConventionAwareHelper(this, getConvention())%n");
-            } else {
-                src.format("private org.gradle.api.internal.ConventionMapping mapping = new org.gradle.api.internal.ConventionAwareHelper(this, new org.gradle.api.internal.plugins.DefaultConvention())%n");
-            }
-            src.format("public void setConventionMapping(org.gradle.api.internal.ConventionMapping conventionMapping) { this.mapping = conventionMapping }%n");
-            src.format("public org.gradle.api.internal.ConventionMapping getConventionMapping() { return mapping }%n");
-        }
-
-        public void mixInGroovyObject() {
-        }
-
-        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");
-        }
-
-        public void addGetter(MetaBeanProperty property) {
-            MetaMethod getter = property.getGetter();
-            String returnTypeName = getter.getReturnType().getCanonicalName();
-            src.format("private boolean %sSet;%n", property.getName());
-            src.format("public %s %s() { getConventionMapping().getConventionValue(super.%s(), '%s', %sSet); }%n",
-                    returnTypeName, getter.getName(), getter.getName(), property.getName(), property.getName());
-        }
-
-        public void addSetter(MetaBeanProperty property) throws Exception {
-            MetaMethod setter = property.getSetter();
-            if (setter.getReturnType().equals(Void.TYPE)) {
-                src.format("public void %s(%s v) { super.%s(v); %sSet = true; }%n", setter.getName(),
-                        setter.getParameterTypes()[0].getTheClass().getCanonicalName(), setter.getName(),
-                        property.getName());
-            } else {
-                String returnTypeName = setter.getReturnType().getCanonicalName();
-                src.format("public %s %s(%s v) { %s r = super.%s(v); %sSet = true; return r; }%n", returnTypeName,
-                        setter.getName(), setter.getParameterTypes()[0].getTheClass().getCanonicalName(),
-                        returnTypeName, setter.getName(), property.getName());
-            }
-        }
-
-        public Class<? extends T> generate() {
-            src.format("}");
-
-            GroovyClassLoader classLoader = new GroovyClassLoader(type.getClassLoader());
-            return classLoader.parseClass(src.toString());
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/HasConvention.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/HasConvention.java
new file mode 100644
index 0000000..3802a61
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/HasConvention.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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;
+
+import org.gradle.api.plugins.Convention;
+
+/**
+ * Demarcates objects that expose a convention.
+ *
+ * Convention objects aren't going to be around forever, so this is a temporary interface.
+ */
+public interface HasConvention {
+
+    Convention getConvention();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
index 46233ac..a4139a7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
@@ -26,12 +26,6 @@ package org.gradle.api.internal;
  * @author Hans Dockter
  */
 public interface IConventionAware {
-    /**
-     * Sets the convention mapping for the properties of this object.
-     *
-     * @param mapping The mapping.
-     */
-    void setConventionMapping(ConventionMapping mapping);
 
     /**
      * Returns the convention mapping for the properties of this object. The returned mapping object can be used to
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/Instantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/Instantiator.java
new file mode 100644
index 0000000..ee91ad7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/Instantiator.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * An object that can create new instances of a given type, which may be decorated in some fashion.
+ */
+public interface Instantiator {
+
+    /**
+     * Create a new instance of T, using {@code parameters} as the construction parameters.
+     */
+    <T> T newInstance(Class<T> type, Object... parameters);
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
new file mode 100755
index 0000000..9ac1544
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/LocationAwareException.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.GradleException;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.TreeVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@code LocationAwareException} is an exception which can be annotated with a location in a script.
+ */
+public class LocationAwareException extends GradleException {
+    private final Throwable target;
+    private final ScriptSource source;
+    private final Integer lineNumber;
+
+    public LocationAwareException(Throwable cause, Throwable target, ScriptSource source, Integer lineNumber) {
+        this.source = source;
+        this.lineNumber = lineNumber;
+        this.target = target;
+        initCause(cause);
+    }
+
+    /**
+     * Returns the target exception.
+     *
+     * @return The target exception. Not null
+     */
+    public Throwable getTarget() {
+        return target;
+    }
+
+    /**
+     * <p>Returns the undecorated message of this exception.</p>
+     *
+     * @return The undecorated message. May return null.
+     */
+    public String getOriginalMessage() {
+        return target.getMessage();
+    }
+
+    /**
+     * <p>Returns the source of the script where this exception occurred.</p>
+     *
+     * @return The source. May return null.
+     */
+    public ScriptSource getScriptSource() {
+        return source;
+    }
+
+    /**
+     * <p>Returns a description of the location of where this exception occurred.</p>
+     *
+     * @return The location description. May return null.
+     */
+    public String getLocation() {
+        if (source == null) {
+            return null;
+        }
+        String sourceMsg = StringUtils.capitalize(source.getDisplayName());
+        if (lineNumber == null) {
+            return sourceMsg;
+        }
+        return String.format("%s line: %d", sourceMsg, lineNumber);
+    }
+
+    /**
+     * Returns the line in the script where this exception occurred, if known.
+     *
+     * @return The line number, or null if not known.
+     */
+    public Integer getLineNumber() {
+        return lineNumber;
+    }
+
+    /**
+     * Returns the fully formatted error message, including the location.
+     *
+     * @return the message. May return null.
+     */
+    public String getMessage() {
+        String location = getLocation();
+        String message = target.getMessage();
+        if (location == null && message == null) {
+            return null;
+        }
+        if (location == null) {
+            return message;
+        }
+        if (message == null) {
+            return location;
+        }
+        return String.format("%s%n%s", location, message);
+    }
+
+    /**
+     * Returns the reportable causes for this failure.
+     *
+     * @return The causes. Never returns null, returns an empty list if this exception has no reportable causes.
+     */
+    public List<Throwable> getReportableCauses() {
+        final List<Throwable> causes = new ArrayList<Throwable>();
+        visitCauses(target, new TreeVisitor<Throwable>(){
+            @Override
+            public void node(Throwable node) {
+                causes.add(node);
+            }
+        });
+        return causes;
+    }
+
+    /**
+     * Visits the reportable causes for this failure.
+     */
+    public void visitReportableCauses(TreeVisitor<? super Throwable> visitor) {
+        visitor.node(this);
+        visitCauses(target, visitor);
+    }
+
+    private void visitCauses(Throwable t, TreeVisitor<? super Throwable> visitor) {
+        if (t instanceof MultiCauseException) {
+            MultiCauseException multiCauseException = (MultiCauseException) t;
+            List<? extends Throwable> causes = multiCauseException.getCauses();
+            if (!causes.isEmpty()) {
+                visitor.startChildren();
+                for (Throwable cause : causes) {
+                    visitor.node(cause);
+                    if (cause.getClass().getAnnotation(Contextual.class) != null) {
+                        visitCauses(cause, visitor);
+                    }
+                }
+                visitor.endChildren();
+            }
+        } else if (t.getCause() != null) {
+            visitor.startChildren();
+            Throwable cause = t.getCause();
+            visitor.node(cause);
+            if (cause.getClass().getAnnotation(Contextual.class) != null) {
+                visitCauses(cause, visitor);
+            }
+            visitor.endChildren();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/MapBackedDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/MapBackedDynamicObject.java
deleted file mode 100644
index 3ab0f90..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/MapBackedDynamicObject.java
+++ /dev/null
@@ -1,58 +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;
-
-import groovy.lang.MissingPropertyException;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class MapBackedDynamicObject extends AbstractDynamicObject {
-    private final Map<String, Object> properties = new HashMap<String, Object>();
-    private final AbstractDynamicObject owner;
-
-    public MapBackedDynamicObject(AbstractDynamicObject owner) {
-        this.owner = owner;
-    }
-
-    @Override
-    protected String getDisplayName() {
-        return owner.getDisplayName();
-    }
-
-    @Override
-    public Map<String, Object> getProperties() {
-        return properties;
-    }
-
-    @Override
-    public boolean hasProperty(String name) {
-        return properties.containsKey(name);
-    }
-
-    @Override
-    public Object getProperty(String name) throws MissingPropertyException {
-        if (hasProperty(name)) {
-            return properties.get(name);
-        }
-        return super.getProperty(name);
-    }
-
-    @Override
-    public void setProperty(String name, Object value) {
-        properties.put(name, value);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
new file mode 100644
index 0000000..b999fc7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+
+public class NamedDomainObjectContainerConfigureDelegate extends ConfigureDelegate {
+
+    public NamedDomainObjectContainerConfigureDelegate(Object owner, final NamedDomainObjectContainer container) {
+        super(owner, container, new Action<String>() {
+            public void execute(String name) {
+                container.create(name);
+            }
+        });
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/Operation.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/Operation.java
new file mode 100644
index 0000000..5caafa6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/Operation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * Generic, parameter-less and void returning operation of some kind.
+ * <p>
+ * by Szczepan Faber, created at: 12/16/11
+ */
+public interface Operation {
+    void execute();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ProcessOperations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ProcessOperations.java
new file mode 100644
index 0000000..9202d25
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ProcessOperations.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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;
+
+import groovy.lang.Closure;
+import org.gradle.process.ExecResult;
+
+public interface ProcessOperations {
+    ExecResult javaexec(Closure cl);
+
+    ExecResult exec(Closure cl);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java
new file mode 100644
index 0000000..194b8cf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/PropertiesTransformer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.listener.ActionBroadcast;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Properties;
+
+/**
+ * Transformer implementation to support modification of Properties objects.
+ */
+public class PropertiesTransformer implements Transformer<Properties, Properties> {
+    private final ActionBroadcast<Properties> actions = new ActionBroadcast<Properties>();
+    
+    /**
+     * Adds an action to be executed when properties are transformed.
+     * @param action the action to add
+     */
+    public void addAction(Action<? super Properties> action) {
+        actions.add(action);
+    }
+
+    /**
+     * Adds an action to be executed when properties are transformed.
+     * @param closure the closure to add
+     */
+    public void addAction(Closure closure) {
+        actions.add(closure);
+    }
+    
+    /**
+     * Transforms a properties object.  This will modify the
+     * original.
+     * @param original the properties to transform
+     * @return the transformed properties
+     */
+    public Properties transform(Properties original) {
+        return doTransform(original);
+    }
+    
+    /**
+     * Transforms a properties object and write them out to a stream.
+     * This will modify the original properties.
+     * @param original the properties to transform
+     * @param destination the stream to write the properties to
+     */
+    public void transform(Properties original, OutputStream destination) {
+        try {
+            doTransform(original).store(destination, "");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Transforms a properties object.  This will modify the
+     * original.
+     * @param original the properties to transform
+     * @return the transformed properties
+     */
+    private Properties doTransform(Properties original) {
+        actions.execute(original);
+        return original;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java
new file mode 100644
index 0000000..c956d78
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.NamedDomainObjectFactory;
+
+public class ReflectiveNamedDomainObjectFactory<T> implements NamedDomainObjectFactory<T> {
+    private final Class<? extends T> type;
+    private final Object[] extraArgs;
+    private final Instantiator instantiator;
+
+    public ReflectiveNamedDomainObjectFactory(Class<? extends T> type, Object... extraArgs) {
+        this(type, new DirectInstantiator(), extraArgs);
+    }
+    
+    public ReflectiveNamedDomainObjectFactory(Class<? extends T> type, Instantiator instantiator, Object... extraArgs) {
+        this.type = type;
+        this.instantiator = instantiator;
+        this.extraArgs = extraArgs;
+    }
+
+    public T create(String name) {
+        return instantiator.newInstance(type, combineInstantiationArgs(name));
+    }
+
+    protected Object[] combineInstantiationArgs(String name) {
+        Object[] combinedArgs;
+        if (extraArgs.length == 0) {
+            Object[] nameArg = {name};
+            combinedArgs = nameArg;
+        } else {
+            combinedArgs = new Object[extraArgs.length + 1];
+            combinedArgs[0] = name;
+            int i = 1;
+            for (Object e : extraArgs) {
+                combinedArgs[i++] = e;
+            }
+        }
+
+        return combinedArgs;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
index 6763825..e62a9c1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
@@ -20,19 +20,20 @@ import org.gradle.api.Task;
 import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
 import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factory;
 import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.Configurable;
 
 import java.util.List;
+import java.io.File;
 
 public interface TaskInternal extends Task, Configurable<Task> {
     Spec<? super TaskInternal> getOnlyIf();
 
-    /**
-     * Executes this task.
-     */
     void execute();
 
+    void executeWithoutThrowingTaskFailure();
+
     StandardOutputCapture getStandardOutputCapture();
 
     TaskExecuter getExecuter();
@@ -44,4 +45,11 @@ public interface TaskInternal extends Task, Configurable<Task> {
     List<TaskValidator> getValidators();
 
     void addValidator(TaskValidator validator);
+
+    /**
+     * The returned factory is expected to return the same file each time.
+     * <p>
+     * The getTemporaryDir() method creates the directory which can be problematic. Use this to delay that creation.
+     */
+    Factory<File> getTemporaryDirFactory();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java
new file mode 100644
index 0000000..c6f9f2c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.util.Stack;
+
+/**
+ * @see org.gradle.api.internal.AsmBackedClassGenerator.MixInExtensibleDynamicObject#MixInExtensibleDynamicObject(Object, DynamicObject)
+ * @see ClassGeneratorBackedInstantiator#newInstance(Class, Object...)
+ */
+public abstract class ThreadGlobalInstantiator {
+
+    private static final ThreadLocal<Stack<Instantiator>> STORAGE = new ThreadLocal<Stack<Instantiator>>() {
+        @Override
+        protected Stack<Instantiator> initialValue() {
+            return new Stack<Instantiator>();
+        }
+    };
+
+    private static Stack<Instantiator> getStack() {
+        return STORAGE.get();
+    } 
+    
+    public static Instantiator get() {
+        Stack<Instantiator> stack = getStack();
+        return stack.empty() ? null : stack.peek(); 
+    }
+
+    public static void set(Instantiator instantiator) {
+        Stack<Instantiator> stack = getStack();
+        if (instantiator != null) {
+            stack.push(instantiator);
+        } else if (!stack.empty()) {
+            stack.pop();
+        }
+    }
+
+    public static Instantiator getOrCreate() {
+        Instantiator instantiator = get();
+        if (instantiator != null) {
+            return instantiator;
+        } else {
+            return new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java
index 52f8aa2..7bd0bd1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/XmlTransformer.java
@@ -24,10 +24,11 @@ import org.apache.commons.lang.StringUtils;
 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.gradle.api.Action;
 import org.gradle.api.Transformer;
-import org.gradle.api.artifacts.maven.XmlProvider;
-import org.gradle.util.SystemProperties;
+import org.gradle.api.XmlProvider;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.GUtil;
 import org.gradle.util.TextUtil;
-import org.gradle.util.UncheckedException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.InputSource;
@@ -42,7 +43,7 @@ import java.io.*;
 import java.util.ArrayList;
 import java.util.List;
 
-public class XmlTransformer implements Transformer<String> {
+public class XmlTransformer implements Transformer<String, String> {
     private final List<Action<? super XmlProvider>> actions = new ArrayList<Action<? super XmlProvider>>();
     private String indentation = "  ";
 
@@ -78,14 +79,27 @@ public class XmlTransformer implements Transformer<String> {
         doTransform(original).writeTo(destination);
     }
 
+    public void transform(DomNode original, Writer destination) {
+        doTransform(original).writeTo(destination);
+    }
+
+    public void transform(DomNode original, OutputStream destination) {
+        doTransform(original).writeTo(destination);
+    }
+
     private XmlProviderImpl doTransform(String original) {
-        XmlProviderImpl provider = new XmlProviderImpl(original);
-        provider.apply(actions);
-        return provider;
+        return doTransform(new XmlProviderImpl(original));
     }
 
     private XmlProviderImpl doTransform(Node original) {
-        XmlProviderImpl provider = new XmlProviderImpl(original);
+        return doTransform(new XmlProviderImpl(original));
+    }
+
+    private XmlProviderImpl doTransform(DomNode original) {
+        return doTransform(new XmlProviderImpl(original));
+    }
+
+    private XmlProviderImpl doTransform(XmlProviderImpl provider) {
         provider.apply(actions);
         return provider;
     }
@@ -95,6 +109,8 @@ public class XmlTransformer implements Transformer<String> {
         private Node node;
         private String stringValue;
         private Element element;
+        private String publicId;
+        private String systemId;
 
         public XmlProviderImpl(String original) {
             this.stringValue = original;
@@ -104,6 +120,12 @@ public class XmlTransformer implements Transformer<String> {
             this.node = original;
         }
 
+        public XmlProviderImpl(DomNode original) {
+            this.node = original;
+            publicId = original.getPublicId();
+            systemId = original.getSystemId();
+        }
+
         public void apply(Iterable<Action<? super XmlProvider>> actions) {
             for (Action<? super XmlProvider> action : actions) {
                 action.execute(this);
@@ -127,7 +149,7 @@ public class XmlTransformer implements Transformer<String> {
                 doWriteTo(writer, "UTF-8");
                 writer.flush();
             } catch (IOException e) {
-                throw UncheckedException.asUncheckedException(e);
+                throw UncheckedException.throwAsUncheckedException(e);
             }
         }
 
@@ -145,7 +167,7 @@ public class XmlTransformer implements Transformer<String> {
                 try {
                     node = new XmlParser().parseText(toString());
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
                 builder = null;
                 element = null;
@@ -159,7 +181,7 @@ public class XmlTransformer implements Transformer<String> {
                 try {
                     document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(toString())));
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
                 element = document.getDocumentElement();
                 builder = null;
@@ -173,30 +195,37 @@ public class XmlTransformer implements Transformer<String> {
 
             try {
                 if (node != null) {
-                    final PrintWriter printWriter = new PrintWriter(writer);
-                    IndentPrinter indentPrinter = new IndentPrinter(printWriter, indentation) {
-                        @Override
-                        public void println() {
-                            printWriter.println();
-                        }
-                    };
-                    XmlNodePrinter nodePrinter = new XmlNodePrinter(indentPrinter);
-                    nodePrinter.setPreserveWhitespace(true);
-                    nodePrinter.print(node);
-                    printWriter.flush();
+                    printNode(node, writer);
                 } else if (element != null) {
-                    printNode(element, writer);
+                    printDomNode(element, writer);
                 } else if (builder != null) {
                     writer.append(TextUtil.toPlatformLineSeparators(stripXmlDeclaration(builder)));
                 } else {
                     writer.append(TextUtil.toPlatformLineSeparators(stripXmlDeclaration(stringValue)));
                 }
             } catch (IOException e) {
-                throw UncheckedException.asUncheckedException(e);
+                throw UncheckedException.throwAsUncheckedException(e);
             }
         }
 
-        private void printNode(org.w3c.dom.Node node, Writer destination) {
+        private void printNode(Node node, Writer writer) {
+            final PrintWriter printWriter = new PrintWriter(writer);
+            if (GUtil.isTrue(publicId)) {
+                printWriter.format("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">%n", node.name(), publicId, systemId);
+            }
+            IndentPrinter indentPrinter = new IndentPrinter(printWriter, indentation) {
+                @Override
+                public void println() {
+                    printWriter.println();
+                }
+            };
+            XmlNodePrinter nodePrinter = new XmlNodePrinter(indentPrinter);
+            nodePrinter.setPreserveWhitespace(true);
+            nodePrinter.print(node);
+            printWriter.flush();
+        }
+
+        private void printDomNode(org.w3c.dom.Node node, Writer destination) {
             removeEmptyTextNodes(node); // empty text nodes hinder subsequent formatting
             int indentAmount = determineIndentAmount();
 
@@ -212,6 +241,10 @@ public class XmlTransformer implements Transformer<String> {
                 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+                if (GUtil.isTrue(publicId)) {
+                    transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, publicId);
+                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, systemId);
+                }
                 try {
                     // some impls support this but not factory.setAttribute("indent-number")
                     transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indentAmount));
@@ -221,7 +254,7 @@ public class XmlTransformer implements Transformer<String> {
 
                 transformer.transform(new DOMSource(node), new StreamResult(destination));
             } catch (TransformerException e) {
-                throw UncheckedException.asUncheckedException(e);
+                throw UncheckedException.throwAsUncheckedException(e);
             }
         }
 
@@ -256,7 +289,7 @@ public class XmlTransformer implements Transformer<String> {
                 writer.write("?>");
                 writer.write(SystemProperties.getLineSeparator());
             } catch (IOException e) {
-                throw UncheckedException.asUncheckedException(e);
+                throw UncheckedException.throwAsUncheckedException(e);
             }
         }
         private boolean hasXmlDeclaration(String xml) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactContainer.java
deleted file mode 100644
index 2a9e0b9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactContainer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2007-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;
-
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.specs.Spec;
-
-import java.util.Collections;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactContainer {
-    static final ArtifactContainer EMPTY_CONTAINER = new ArtifactContainer() {
-        public void addArtifacts(PublishArtifact... publishArtifacts) {
-            throw new UnsupportedOperationException("You can add elements to EMPTY container");
-        }
-
-        public Set<PublishArtifact> getArtifacts() {
-            return Collections.emptySet();
-        }
-
-        public Set<PublishArtifact> getArtifacts(Spec<PublishArtifact> spec) {
-            return Collections.emptySet();
-        }
-    };
-
-
-    void addArtifacts(PublishArtifact... publishArtifacts);
-
-    Set<PublishArtifact> getArtifacts();
-
-    Set<PublishArtifact> getArtifacts(Spec<PublishArtifact> spec);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
new file mode 100644
index 0000000..dce7c55
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2007-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;
+
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactDependencyResolver {
+    ResolvedConfiguration resolve(ConfigurationInternal configuration) throws ResolveException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
new file mode 100644
index 0000000..dcfbbc8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublicationServices.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+
+public interface ArtifactPublicationServices {
+    ArtifactPublisher getArtifactPublisher();
+
+    RepositoryHandler getRepositoryHandler();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
new file mode 100644
index 0000000..4d98a2e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPublisher {
+    void publish(ConfigurationInternal configuration, File descriptorDestination) throws PublishException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
index 6047752..660bca5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
@@ -56,10 +56,10 @@ public class CachingDependencyResolveContext implements DependencyResolveContext
             if (node instanceof FileCollection) {
                 FileCollection fileCollection = (FileCollection) node;
                 values.add(fileCollection);
-            } else if (node instanceof DependencyInternal) {
-                DependencyInternal dependencyInternal = (DependencyInternal) node;
+            } else if (node instanceof ResolvableDependency) {
+                ResolvableDependency resolvableDependency = (ResolvableDependency) node;
                 queue.clear();
-                dependencyInternal.resolve(CachingDependencyResolveContext.this);
+                resolvableDependency.resolve(CachingDependencyResolveContext.this);
                 connectedNodes.addAll(queue);
                 queue.clear();
             } else {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationContainerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationContainerFactory.java
deleted file mode 100644
index e4c8ad4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationContainerFactory.java
+++ /dev/null
@@ -1,31 +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.api.internal.artifacts;
-
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-
-/**
- * @author Hans Dockter
- */
-public interface ConfigurationContainerFactory {
-    ConfigurationContainer createConfigurationContainer(ResolverProvider resolverProvider,
-                                                        DependencyMetaDataProvider dependencyMetaDataProvider,
-                                                        DomainObjectContext domainObjectContext);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java
new file mode 100644
index 0000000..b9cbe72
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactIdentifier.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+public class DefaultArtifactIdentifier implements ArtifactIdentifier {
+    private final ModuleVersionIdentifier moduleVersionIdentifier;
+    private final String name;
+    private final String type;
+    private final String extension;
+    private final String classifier;
+
+    public DefaultArtifactIdentifier(ModuleVersionIdentifier moduleVersionIdentifier, String name, String type, String extension, String classifier) {
+        this.moduleVersionIdentifier = moduleVersionIdentifier;
+        this.name = name;
+        this.type = type;
+        this.extension = extension;
+        this.classifier = classifier;
+    }
+
+    public ModuleVersionIdentifier getModuleVersionIdentifier() {
+        return moduleVersionIdentifier;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
new file mode 100644
index 0000000..608e634
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Namer;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.UnknownRepositoryException;
+import org.gradle.api.internal.DefaultNamedDomainObjectList;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.DeprecationLogger;
+import org.gradle.util.GUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObjectList<ArtifactRepository>
+        implements ArtifactRepositoryContainer {
+    private final ResolverFactory resolverFactory;
+
+    private final Action<ArtifactRepository> addLastAction = new Action<ArtifactRepository>() {
+        public void execute(ArtifactRepository repository) {
+            DefaultArtifactRepositoryContainer.super.add(repository);
+        }
+    };
+    private final Action<ArtifactRepository> addFirstAction = new Action<ArtifactRepository>() {
+        public void execute(ArtifactRepository repository) {
+            DefaultArtifactRepositoryContainer.super.add(0, repository);
+        }
+    };
+
+    public DefaultArtifactRepositoryContainer(ResolverFactory resolverFactory, Instantiator instantiator) {
+        super(ArtifactRepository.class, instantiator, new RepositoryNamer());
+        this.resolverFactory = resolverFactory;
+    }
+
+    private static class RepositoryNamer implements Namer<ArtifactRepository> {
+        public String determineName(ArtifactRepository r) {
+            return r.getName();
+        }
+    }
+
+    @Override
+    public String getTypeDisplayName() {
+        return "repository";
+    }
+
+    public DefaultArtifactRepositoryContainer configure(Closure closure) {
+        return ConfigureUtil.configure(closure, this, false);
+    }
+
+    public void addFirst(ArtifactRepository repository) {
+        add(0, repository);
+    }
+
+    public void addLast(ArtifactRepository repository) {
+        add(repository);
+    }
+
+    public boolean add(DependencyResolver resolver, Closure configureClosure) {
+        addCustomDependencyResolver(resolver, configureClosure, addLastAction);
+        return true;
+    }
+
+    public boolean add(DependencyResolver resolver) {
+        addCustomDependencyResolver(resolver, null, addLastAction);
+        return true;
+    }
+
+    public DependencyResolver addFirst(Object userDescription) {
+        return addFirst(userDescription, null);
+    }
+
+    public DependencyResolver addFirst(Object userDescription, Closure configureClosure) {
+        return addCustomDependencyResolver(userDescription, configureClosure, addFirstAction);
+    }
+
+    @Deprecated
+    public DependencyResolver addLast(Object userDescription) {
+        DeprecationLogger.nagUserOfReplacedMethod("ArtifactRepositoryContainer.addLast()", "maven() or add()");
+        return addCustomDependencyResolver(userDescription, null, addLastAction);
+    }
+
+    @Deprecated
+    public DependencyResolver addLast(Object userDescription, Closure configureClosure) {
+        DeprecationLogger.nagUserOfReplacedMethod("ArtifactRepositoryContainer.addLast()", "maven() or add()");
+        return addCustomDependencyResolver(userDescription, configureClosure, addLastAction);
+    }
+
+    public DependencyResolver addBefore(Object userDescription, String afterResolverName) {
+        return addBefore(userDescription, afterResolverName, null);
+    }
+
+    public DependencyResolver addBefore(Object userDescription, final String afterResolverName, Closure configureClosure) {
+        if (!GUtil.isTrue(afterResolverName)) {
+            throw new InvalidUserDataException("You must specify afterResolverName");
+        }
+        final ArtifactRepository after = getByName(afterResolverName);
+        return addCustomDependencyResolver(userDescription, configureClosure, new Action<ArtifactRepository>() {
+            public void execute(ArtifactRepository repository) {
+                DefaultArtifactRepositoryContainer.super.add(indexOf(after), repository);
+            }
+        });
+    }
+
+    public DependencyResolver addAfter(Object userDescription, final String beforeResolverName) {
+        return addAfter(userDescription, beforeResolverName, null);
+    }
+
+    public DependencyResolver addAfter(Object userDescription, final String beforeResolverName, Closure configureClosure) {
+        if (!GUtil.isTrue(beforeResolverName)) {
+            throw new InvalidUserDataException("You must specify beforeResolverName");
+        }
+        final ArtifactRepository before = getByName(beforeResolverName);
+
+        return addCustomDependencyResolver(userDescription, configureClosure, new Action<ArtifactRepository>() {
+            public void execute(ArtifactRepository repository) {
+                int insertPos = indexOf(before) + 1;
+                if (insertPos == size()) {
+                    DefaultArtifactRepositoryContainer.super.add(repository);
+                } else {
+                    DefaultArtifactRepositoryContainer.this.add(insertPos, repository);
+                }
+            }
+        });
+    }
+
+    private DependencyResolver addCustomDependencyResolver(Object userDescription, Closure configureClosure, Action<ArtifactRepository> orderAction) {
+        ArtifactRepository repository = resolverFactory.createRepository(userDescription);
+        DependencyResolver resolver = toResolver(DependencyResolver.class, repository);
+        ConfigureUtil.configure(configureClosure, resolver);
+        addRepository(new FixedResolverArtifactRepository(resolver), "repository", orderAction);
+        return resolver;
+    }
+
+    @Override
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownRepositoryException(String.format("Repository with name '%s' not found.", name));
+    }
+
+    public List<DependencyResolver> getResolvers() {
+        List<DependencyResolver> returnedResolvers = new ArrayList<DependencyResolver>();
+        for (ArtifactRepository repository : this) {
+            returnedResolvers.add(((ArtifactRepositoryInternal) repository).createResolver());
+        }
+        return returnedResolvers;
+    }
+
+    public ResolverFactory getResolverFactory() {
+        return resolverFactory;
+    }
+
+    protected <T extends ArtifactRepository> T addRepository(T repository, Action<? super T> action, String defaultName) {
+        action.execute(repository);
+        return addRepository(repository, defaultName);
+    }
+
+    public <T extends ArtifactRepository> T addRepository(T repository, Closure closure, String defaultName) {
+        return addRepository(repository, closure, defaultName, addLastAction);
+    }
+
+    public <T extends ArtifactRepository> T addRepository(T repository, Map<String, ?> args, Closure closure, String defaultName) {
+        ConfigureUtil.configureByMap(args, repository);
+        return addRepository(repository, closure, defaultName);
+    }
+
+    protected <T extends ArtifactRepository> T addRepository(T repository, Closure closure, String defaultName, Action<ArtifactRepository> action) {
+        ConfigureUtil.configure(closure, repository);
+        return addRepository(repository, defaultName, action);
+    }
+
+    public <T extends ArtifactRepository> T addRepository(T repository, Map<String, ?> args, String defaultName) {
+        ConfigureUtil.configureByMap(args, repository);
+        addRepository(repository, defaultName);
+        return repository;
+    }
+
+    public <T extends ArtifactRepository> T addRepository(T repository, String defaultName) {
+        return addRepository(repository, defaultName, addLastAction);
+    }
+
+    protected <T extends ArtifactRepository> T addRepository(T repository, String defaultName, Action<ArtifactRepository> action) {
+        String repositoryName = repository.getName();
+        if (!GUtil.isTrue(repositoryName)) {
+            repositoryName = findName(defaultName);
+            repository.setName(repositoryName);
+        }
+        assertCanAdd(repositoryName);
+        action.execute(repository);
+
+        return repository;
+    }
+
+    protected String findName(String defaultName) {
+        if (findByName(defaultName) == null) {
+            return defaultName;
+        }
+        for (int index = 2; true; index++) {
+            String candidate = String.format("%s%d", defaultName, index);
+            if (findByName(candidate) == null) {
+                return candidate;
+            }
+        }
+    }
+
+    protected <T extends DependencyResolver> T toResolver(Class<T> type, ArtifactRepository repository) {
+        DependencyResolver resolver = ((ArtifactRepositoryInternal) repository).createResolver();
+        return type.cast(resolver);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactory.java
deleted file mode 100644
index 84089b1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactory.java
+++ /dev/null
@@ -1,79 +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.api.internal.artifacts;
-
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
-import org.gradle.api.internal.artifacts.ivyservice.*;
-
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultConfigurationContainerFactory implements ConfigurationContainerFactory {
-    private Map clientModuleRegistry;
-    private SettingsConverter settingsConverter;
-    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
-    private ModuleDescriptorConverter publishModuleDescriptorConverter;
-    private ModuleDescriptorConverter fileModuleDescriptorConverter;
-    private IvyFactory ivyFactory;
-    private IvyDependencyResolver dependencyResolver;
-    private IvyDependencyPublisher dependencyPublisher;
-    private ClassGenerator classGenerator;
-
-    public DefaultConfigurationContainerFactory(Map clientModuleRegistry, SettingsConverter settingsConverter,
-                                                ModuleDescriptorConverter resolveModuleDescriptorConverter,
-                                                ModuleDescriptorConverter publishModuleDescriptorConverter,
-                                                ModuleDescriptorConverter fileModuleDescriptorConverter,
-                                                IvyFactory ivyFactory,
-                                                IvyDependencyResolver dependencyResolver, IvyDependencyPublisher dependencyPublisher,
-                                                ClassGenerator classGenerator) {
-        this.clientModuleRegistry = clientModuleRegistry;
-        this.settingsConverter = settingsConverter;
-        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
-        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
-        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
-        this.ivyFactory = ivyFactory;
-        this.dependencyResolver = dependencyResolver;
-        this.dependencyPublisher = dependencyPublisher;
-        this.classGenerator = classGenerator;
-    }
-
-    public ConfigurationContainer createConfigurationContainer(ResolverProvider resolverProvider,
-                                                               DependencyMetaDataProvider dependencyMetaDataProvider,
-                                                               DomainObjectContext domainObjectContext) {
-        IvyService ivyService = new ErrorHandlingIvyService(
-                new ShortcircuitEmptyConfigsIvyService(
-                        new DefaultIvyService(
-                                dependencyMetaDataProvider,
-                                resolverProvider,
-                                settingsConverter,
-                                resolveModuleDescriptorConverter,
-                                publishModuleDescriptorConverter,
-                                fileModuleDescriptorConverter,
-                                ivyFactory,
-                                dependencyResolver,
-                                dependencyPublisher,
-                                clientModuleRegistry)));
-        return classGenerator.newInstance(DefaultConfigurationContainer.class, ivyService, classGenerator, domainObjectContext);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencySet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencySet.java
new file mode 100644
index 0000000..e1decbe
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencySet.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencySet;
+import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.internal.DelegatingDomainObjectSet;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.tasks.TaskDependency;
+
+public class DefaultDependencySet extends DelegatingDomainObjectSet<Dependency> implements DependencySet {
+    private final TaskDependency builtBy = new DependencySetTaskDependency();
+    private final String displayName;
+
+    public DefaultDependencySet(String displayName, DomainObjectSet<Dependency> backingSet) {
+        super(backingSet);
+        this.displayName = displayName;
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return builtBy;
+    }
+
+    private class DependencySetTaskDependency extends AbstractTaskDependency {
+        @Override
+        public String toString() {
+            return String.format("build dependencies %s", DefaultDependencySet.this);
+        }
+
+        public void resolve(TaskDependencyResolveContext context) {
+            for (SelfResolvingDependency dependency : DefaultDependencySet.this.withType(SelfResolvingDependency.class)) {
+                context.add(dependency);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java
index db65a45..3c6fd6f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java
@@ -16,7 +16,9 @@
 package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.util.DeprecationLogger;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -25,14 +27,39 @@ import java.util.Map;
  *         DefaultExcludeRule is a value object
  */
 public class DefaultExcludeRule implements ExcludeRule {
-    private Map<String, String> excludeArgs;
+    private String group;
+    private String module;
 
-    public DefaultExcludeRule(Map<String, String> excludeArgs) {
-        this.excludeArgs = excludeArgs;
+    public DefaultExcludeRule(){
+    }
+
+    public DefaultExcludeRule(String group, String module) {
+        this.group = group;
+        this.module = module;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String groupValue) {
+        this.group = groupValue;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String moduleValue) {
+        this.module = moduleValue;
     }
 
     public Map<String, String> getExcludeArgs() {
-        return excludeArgs;
+        DeprecationLogger.nagUserWith("The getExcludeArgs method has been deprecated and will be removed in the next version of Gradle. Please use the getGroup() method or the getModule() method instead.");
+        Map excludeArgsAsMap = new HashMap();
+        excludeArgsAsMap.put(ExcludeRule.GROUP_KEY, group);
+        excludeArgsAsMap.put(ExcludeRule.MODULE_KEY, module);
+        return excludeArgsAsMap;
     }
 
     @Override
@@ -46,15 +73,19 @@ public class DefaultExcludeRule implements ExcludeRule {
 
         DefaultExcludeRule that = (DefaultExcludeRule) o;
 
-        if (excludeArgs != null ? !excludeArgs.equals(that.excludeArgs) : that.excludeArgs != null) {
+        if (group != null ? !group.equals(that.group) : that.group != null) {
+            return false;
+        }
+        if (module != null ? !module.equals(that.module) : that.module != null) {
             return false;
         }
-
         return true;
     }
 
     @Override
     public int hashCode() {
-        return excludeArgs != null ? excludeArgs.hashCode() : 0;
+        int result = group != null ? group.hashCode() : 0;
+        result = 31 * result + (module != null ? module.hashCode() : 0);
+        return result;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
index a79ad0d..ebae94d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.artifacts;
 
 import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.artifacts.ExcludeRuleContainer;
+import org.gradle.api.internal.notations.api.NotationParser;
 
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -28,6 +29,8 @@ import java.util.Set;
  */
 public class DefaultExcludeRuleContainer implements ExcludeRuleContainer {
     private Set<ExcludeRule> addedRules = new LinkedHashSet<ExcludeRule>();
+    private NotationParser<ExcludeRule> notationParser = new ExcludeRuleNotationParser<ExcludeRule>();
+    //TODO has usage of NotationParserBuilder here any advantage?
 
     public DefaultExcludeRuleContainer() {
     }
@@ -37,7 +40,7 @@ public class DefaultExcludeRuleContainer implements ExcludeRuleContainer {
     }
 
     public void add(Map<String, String> args) {
-        addedRules.add(new DefaultExcludeRule(args));
+        addedRules.add(notationParser.parseNotation(args));
     }
 
     public Set<ExcludeRule> getRules() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
new file mode 100755
index 0000000..138ab8a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifier.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+public class DefaultModuleVersionIdentifier implements ModuleVersionIdentifier, ModuleVersionSelector {
+    private final String group;
+    private final String name;
+    private final String version;
+
+    public DefaultModuleVersionIdentifier(String group, String name, String version) {
+        assert group != null : "group cannot be null";
+        assert name != null : "name cannot be null";
+        assert version != null : "version cannot be null";
+        this.group = group;
+        this.name = name;
+        this.version = version;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{group: %s, module: %s, version: %s}", group, name, version);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultModuleVersionIdentifier other = (DefaultModuleVersionIdentifier) obj;
+        if (!group.equals(other.group)) {
+            return false;
+        }
+        if (!name.equals(other.name)) {
+            return false;
+        }
+        if (!version.equals(other.version)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return group.hashCode() ^ name.hashCode() ^ version.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
new file mode 100644
index 0000000..e161e8a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+/**
+ * by Szczepan Faber, created at: 11/13/11
+ */
+public class DefaultModuleVersionSelector implements ModuleVersionSelector {
+
+    private String group;
+    private String name;
+    private String version;
+
+    public DefaultModuleVersionSelector(String group, String name, String version) {
+        this.group = group;
+        this.name = name;
+        this.version = version;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public DefaultModuleVersionSelector setGroup(String group) {
+        this.group = group;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultModuleVersionSelector setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public DefaultModuleVersionSelector setVersion(String version) {
+        this.version = version;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ModuleVersionSelector{group: %s, module: %s, version: %s}", group, name, version);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultModuleVersionSelector)) {
+            return false;
+        }
+
+        DefaultModuleVersionSelector that = (DefaultModuleVersionSelector) o;
+
+        if (group != null ? !group.equals(that.group) : that.group != null) {
+            return false;
+        }
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (version != null ? !version.equals(that.version) : that.version != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = group != null ? group.hashCode() : 0;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (version != null ? version.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultPublishArtifactSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultPublishArtifactSet.java
new file mode 100644
index 0000000..cae3292
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultPublishArtifactSet.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.artifacts.PublishArtifactSet;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.DelegatingDomainObjectSet;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyInternal;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultPublishArtifactSet extends DelegatingDomainObjectSet<PublishArtifact> implements PublishArtifactSet {
+    private final TaskDependencyInternal builtBy = new ArtifactsTaskDependency();
+    private final ArtifactsFileCollection files = new ArtifactsFileCollection();
+    private final String displayName;
+
+    public DefaultPublishArtifactSet(String displayName, DomainObjectSet<PublishArtifact> backingSet) {
+        super(backingSet);
+        this.displayName = displayName;
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public FileCollection getFiles() {
+        return files;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return builtBy;
+    }
+
+    private class ArtifactsFileCollection extends AbstractFileCollection {
+
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return builtBy;
+        }
+
+        public Set<File> getFiles() {
+            Set<File> files = new LinkedHashSet<File>();
+            for (PublishArtifact artifact : DefaultPublishArtifactSet.this) {
+                files.add(artifact.getFile());
+            }
+            return files;
+        }
+    }
+
+    private class ArtifactsTaskDependency extends AbstractTaskDependency {
+        public void resolve(TaskDependencyResolveContext context) {
+            for (PublishArtifact publishArtifact : DefaultPublishArtifactSet.this) {
+                context.add(publishArtifact);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
deleted file mode 100644
index 835132c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
+++ /dev/null
@@ -1,79 +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.api.internal.artifacts;
-
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.resolve.ResolveEngine;
-import org.apache.ivy.core.resolve.DownloadOptions;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultResolvedArtifact implements ResolvedArtifact {
-    private ResolvedDependency resolvedDependency;
-    private Artifact artifact;
-    private ResolveEngine resolvedEngine;
-    private File file;
-
-    public DefaultResolvedArtifact(Artifact artifact, ResolveEngine resolvedEngine) {
-        this.artifact = artifact;
-        this.resolvedEngine = resolvedEngine;
-    }
-
-    public ResolvedDependency getResolvedDependency() {
-        return resolvedDependency;
-    }
-
-    public void setResolvedDependency(ResolvedDependency resolvedDependency) {
-        this.resolvedDependency = resolvedDependency;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s;%s", resolvedDependency, artifact.getName());
-    }
-
-    public String getName() {
-        return artifact.getName();
-    }
-
-    public String getType() {
-        return artifact.getType();
-    }
-
-    public String getExtension() {
-        return artifact.getExt();
-    }
-
-    public String getVersion() {
-        return getResolvedDependency() == null ? null : getResolvedDependency().getModuleVersion();
-    }
-
-    public String getDependencyName() {
-        return getResolvedDependency() == null ? null : getResolvedDependency().getModuleName();
-    }
-
-    public File getFile() {
-        if (file == null) {
-            file = resolvedEngine.download(artifact, new DownloadOptions()).getLocalFile();
-        }
-        return file;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
deleted file mode 100644
index 9745a41..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
+++ /dev/null
@@ -1,160 +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.api.internal.artifacts;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.util.GUtil;
-
-import java.util.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultResolvedDependency implements ResolvedDependency {
-    private Set<ResolvedDependency> children = new LinkedHashSet<ResolvedDependency>();
-    private Set<ResolvedDependency> parents = new LinkedHashSet<ResolvedDependency>();
-    private Map<ResolvedDependency, Set<ResolvedArtifact>> parentArtifacts
-            = new LinkedHashMap<ResolvedDependency, Set<ResolvedArtifact>>();
-    private String name;
-    private final ResolvedConfigurationIdentifier id;
-    private Set<ResolvedArtifact> moduleArtifacts = new LinkedHashSet<ResolvedArtifact>();
-    private Map<ResolvedDependency, Set<ResolvedArtifact>> allArtifactsCache = new HashMap<ResolvedDependency, Set<ResolvedArtifact>>();
-    private Set<ResolvedArtifact> allModuleArtifactsCache;
-
-    public DefaultResolvedDependency(String name, String moduleGroup, String moduleName, String moduleVersion,
-                                     String configuration, Set<ResolvedArtifact> moduleArtifacts) {
-        assert name != null;
-        assert moduleArtifacts != null;
-
-        this.name = name;
-        id = new ResolvedConfigurationIdentifier(moduleGroup, moduleName, moduleVersion, configuration);
-        this.moduleArtifacts = moduleArtifacts;
-    }
-
-    public DefaultResolvedDependency(String moduleGroup, String moduleName, String moduleVersion, String configuration,
-                                     Set<ResolvedArtifact> moduleArtifacts) {
-        this(moduleGroup + ":" + moduleName + ":" + moduleVersion, moduleGroup, moduleName, moduleVersion,
-                configuration, moduleArtifacts);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public ResolvedConfigurationIdentifier getId() {
-        return id;
-    }
-
-    public String getModuleGroup() {
-        return id.getModuleGroup();
-    }
-
-    public String getModuleName() {
-        return id.getModuleName();
-    }
-
-    public String getModuleVersion() {
-        return id.getModuleVersion();
-    }
-
-    public String getConfiguration() {
-        return id.getConfiguration();
-    }
-
-    public Set<ResolvedDependency> getChildren() {
-        return children;
-    }
-
-    public Set<ResolvedArtifact> getModuleArtifacts() {
-        return moduleArtifacts;
-    }
-
-    public Set<ResolvedArtifact> getAllModuleArtifacts() {
-        if (allModuleArtifactsCache == null) {
-            Set<ResolvedArtifact> allArtifacts = new LinkedHashSet<ResolvedArtifact>();
-            allArtifacts.addAll(getModuleArtifacts());
-            for (ResolvedDependency childResolvedDependency : getChildren()) {
-                allArtifacts.addAll(childResolvedDependency.getAllModuleArtifacts());
-            }
-            allModuleArtifactsCache = allArtifacts;
-        }
-        return allModuleArtifactsCache;
-    }
-
-    public Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent) {
-        if (!parents.contains(parent)) {
-            throw new InvalidUserDataException("Unknown Parent");
-        }
-        Set<ResolvedArtifact> artifacts = parentArtifacts.get(parent);
-        return artifacts == null ? Collections.<ResolvedArtifact>emptySet() : artifacts;
-    }
-
-    public Set<ResolvedArtifact> getArtifacts(ResolvedDependency parent) {
-        return GUtil.addSets(getParentArtifacts(parent), getModuleArtifacts());
-    }
-
-    public Set<ResolvedArtifact> getAllArtifacts(ResolvedDependency parent) {
-        if (allArtifactsCache.get(parent) == null) {
-            Set<ResolvedArtifact> allArtifacts = new LinkedHashSet<ResolvedArtifact>();
-            allArtifacts.addAll(getArtifacts(parent));
-            for (ResolvedDependency childResolvedDependency : getChildren()) {
-                for (ResolvedDependency childParent : childResolvedDependency.getParents()) {
-                    allArtifacts.addAll(childResolvedDependency.getAllArtifacts(childParent));
-                }
-            }
-            allArtifactsCache.put(parent, allArtifacts);
-        }
-        return allArtifactsCache.get(parent);
-    }
-
-    public Set<ResolvedDependency> getParents() {
-        return parents;
-    }
-
-    public String toString() {
-        return name + ";" + getConfiguration();
-    }
-
-    public void addParentSpecificArtifacts(ResolvedDependency parent, Set<ResolvedArtifact> artifacts) {
-        parentArtifacts.put(parent, artifacts);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        DefaultResolvedDependency that = (DefaultResolvedDependency) o;
-        return id.equals(that.id);
-    }
-
-    @Override
-    public int hashCode() {
-        return id.hashCode();
-    }
-
-    public void addChild(DefaultResolvedDependency child) {
-        children.add(child);
-        child.parents.add(this);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainer.java
deleted file mode 100644
index 4fc7931..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainer.java
+++ /dev/null
@@ -1,231 +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.api.internal.artifacts;
-
-import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.UnknownDomainObjectException;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.ResolverContainer;
-import org.gradle.api.artifacts.UnknownRepositoryException;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
-import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.DefaultNamedDomainObjectContainer;
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
-import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.util.ConfigureUtil;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultResolverContainer extends DefaultNamedDomainObjectContainer<DependencyResolver>
-        implements ResolverContainer, MavenPomMetaInfoProvider {
-    private ResolverFactory resolverFactory;
-
-    private List<String> resolverNames = new ArrayList<String>();
-
-    private File mavenPomDir;
-
-    private FileResolver fileResolver;
-
-    private Conf2ScopeMappingContainer mavenScopeMappings;
-
-    private ConfigurationContainer configurationContainer;
-
-    public DefaultResolverContainer(ResolverFactory resolverFactory, ClassGenerator classGenerator) {
-        super(DependencyResolver.class, classGenerator);
-        this.resolverFactory = resolverFactory;
-    }
-
-    @Override
-    public String getTypeDisplayName() {
-        return "resolver";
-    }
-
-    public DependencyResolver add(Object userDescription) {
-        return add(userDescription, null);
-    }
-
-    public DependencyResolver add(Object userDescription, Closure configureClosure) {
-        return addInternal(userDescription, configureClosure, new OrderAction() {
-            public void apply(String resolverName) {
-                resolverNames.add(resolverName);
-            }
-        });
-    }
-
-    public DependencyResolver addBefore(Object userDescription, String afterResolverName) {
-        return addBefore(userDescription, afterResolverName, null);
-    }
-
-    public DependencyResolver addBefore(Object userDescription, final String afterResolverName, Closure configureClosure) {
-        if (!GUtil.isTrue(afterResolverName)) {
-            throw new InvalidUserDataException("You must specify userDescription and afterResolverName");
-        }
-        if (findByName(afterResolverName) == null) {
-            throw new InvalidUserDataException("Resolver $afterResolverName does not exists!");
-        }
-        return addInternal(userDescription, configureClosure, new OrderAction() {
-            public void apply(String resolverName) {
-                resolverNames.add(resolverNames.indexOf(afterResolverName), resolverName);
-            }
-        });
-    }
-
-    public DependencyResolver addAfter(Object userDescription, final String beforeResolverName) {
-        return addAfter(userDescription, beforeResolverName, null);
-    }
-
-    public DependencyResolver addAfter(Object userDescription, final String beforeResolverName, Closure configureClosure) {
-        if (!GUtil.isTrue(beforeResolverName)) {
-            throw new InvalidUserDataException("You must specify userDescription and beforeResolverName");
-        }
-        if (findByName(beforeResolverName) == null) {
-            throw new InvalidUserDataException("Resolver $beforeResolverName does not exists!");
-        }
-        return addInternal(userDescription, configureClosure, new OrderAction() {
-            public void apply(String resolverName) {
-                int insertPos = resolverNames.indexOf(beforeResolverName) + 1;
-                if (insertPos == resolverNames.size()) {
-                    resolverNames.add(resolverName);
-                } else {
-                    resolverNames.add(insertPos, resolverName);
-                }
-            }
-        });
-    }
-
-    public DependencyResolver addFirst(Object userDescription) {
-        return addFirst(userDescription, null);
-    }
-
-    public DependencyResolver addFirst(Object userDescription, Closure configureClosure) {
-        if (!GUtil.isTrue(userDescription)) {
-            throw new InvalidUserDataException("You must specify userDescription");
-        }
-        return addInternal(userDescription, configureClosure, new OrderAction() {
-            public void apply(String resolverName) {
-                if (resolverNames.size() == 0) {
-                    resolverNames.add(resolverName);
-                } else {
-                    resolverNames.add(0, resolverName);
-                }
-            }
-        });
-    }
-
-    private DependencyResolver addInternal(Object userDescription, Closure configureClosure, OrderAction orderAction) {
-        if (!GUtil.isTrue(userDescription)) {
-            throw new InvalidUserDataException("You must specify userDescription");
-        }
-        DependencyResolver resolver = resolverFactory.createResolver(userDescription);
-        ConfigureUtil.configure(configureClosure, resolver);
-        if (!GUtil.isTrue(resolver.getName())) {
-            throw new InvalidUserDataException("You must specify a name for the resolver. Resolver=" + userDescription);
-        }
-        if (findByName(resolver.getName()) != null) {
-            throw new InvalidUserDataException(String.format(
-                    "Cannot add a resolver with name '%s' as a resolver with that name already exists.", resolver.getName()));
-        }
-        addObject(resolver.getName(), resolver);
-        orderAction.apply(resolver.getName());
-        return resolver;
-    }
-
-    @Override
-    protected UnknownDomainObjectException createNotFoundException(String name) {
-        return new UnknownRepositoryException(String.format("Repository with name '%s' not found.", name));
-    }
-
-    public List<DependencyResolver> getResolvers() {
-        List<DependencyResolver> returnedResolvers = new ArrayList<DependencyResolver>();
-        for (String resolverName : resolverNames) {
-            returnedResolvers.add(getByName(resolverName));
-        }
-        return returnedResolvers;
-    }
-
-    private static interface OrderAction {
-        
-        void apply(String resolverName);
-    }
-
-    public ResolverFactory getResolverFactory() {
-        return resolverFactory;
-    }
-
-    public void setResolverFactory(ResolverFactory resolverFactory) {
-        this.resolverFactory = resolverFactory;
-    }
-
-    public List<String> getResolverNames() {
-        return resolverNames;
-    }
-
-    public void setResolverNames(List<String> resolverNames) {
-        this.resolverNames = resolverNames;
-    }
-
-    public FileResolver getFileResolver() {
-        return fileResolver;
-    }
-
-    public void setFileResolver(FileResolver fileResolver) {
-        this.fileResolver = fileResolver;
-    }
-
-    public ConfigurationContainer getConfigurationContainer() {
-        return configurationContainer;
-    }
-
-    public void setConfigurationContainer(ConfigurationContainer configurationContainer) {
-        this.configurationContainer = configurationContainer;
-    }
-
-    public GroovyMavenDeployer createMavenDeployer(String name) {
-        return resolverFactory.createMavenDeployer(name, this, getConfigurationContainer(), getMavenScopeMappings(), getFileResolver());
-    }
-
-    public MavenResolver createMavenInstaller(String name) {
-        return resolverFactory.createMavenInstaller(name, this, getConfigurationContainer(), getMavenScopeMappings(), getFileResolver());
-    }
-
-    public Conf2ScopeMappingContainer getMavenScopeMappings() {
-        return mavenScopeMappings;
-    }
-
-    public void setMavenScopeMappings(Conf2ScopeMappingContainer mavenScopeMappings) {
-        this.mavenScopeMappings = mavenScopeMappings;
-    }
-
-    public File getMavenPomDir() {
-        return mavenPomDir;
-    }
-
-    public void setMavenPomDir(File mavenPomDir) {
-        this.mavenPomDir = mavenPomDir;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyInternal.java
deleted file mode 100644
index 2f0d67c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyInternal.java
+++ /dev/null
@@ -1,23 +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.api.internal.artifacts;
-
-import org.gradle.api.artifacts.Dependency;
-
-public interface DependencyInternal extends Dependency {
-    void resolve(DependencyResolveContext context);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java
index 2c1a777..9b71f59 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyManagementServices.java
@@ -15,15 +15,21 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.service.ServiceRegistry;
 
 /**
  * Factory for various types related to dependency management.
  *
  * <p>The motivation for having this factory is to allow implementation
  * types, and more importantly their dependencies, to be loaded from a
- * different (coreImpl) class loader. This helps to prevent version conflicts,
+ * different class loader. This helps to prevent version conflicts,
  * for example between Maven 2 and Maven 3 libraries.
  */
 public interface DependencyManagementServices extends ServiceRegistry {
+    DependencyResolutionServices create(FileResolver resolver, DependencyMetaDataProvider dependencyMetaDataProvider,
+                                        ProjectFinder projectFinder, DomainObjectContext domainObjectContext);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
new file mode 100644
index 0000000..1e6da83
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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;
+
+import org.gradle.api.artifacts.dsl.ArtifactHandler;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
+
+public interface DependencyResolutionServices {
+    RepositoryHandler getResolveRepositoryHandler();
+
+    ConfigurationContainerInternal getConfigurationContainer();
+
+    DependencyHandler getDependencyHandler();
+
+    ArtifactHandler getArtifactHandler();
+
+    Factory<ArtifactPublicationServices> getPublishServicesFactory();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java
new file mode 100644
index 0000000..a135639
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParser.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.tasks.Optional;
+
+import java.util.Collection;
+
+/**
+ * @author Rene Groeschke
+ */
+public class ExcludeRuleNotationParser<T extends ExcludeRule> extends MapNotationParser<T> {
+
+    @Override
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Maps, e.g. [group: 'org.gradle', module:'gradle-core'].");
+    }
+
+    protected T parseMap(@MapKey(ExcludeRule.GROUP_KEY) @Optional String group,
+                         @MapKey(ExcludeRule.MODULE_KEY) @Optional String module) {
+        if (group == null && module == null) {
+            throw new InvalidUserDataException("Either a group or module must be specified. For example: [group:'org.gradle']");
+        }
+        DefaultExcludeRule excluderule = new DefaultExcludeRule();
+        excluderule.setGroup(group);
+        excluderule.setModule(module);
+        return (T) excluderule;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/IvyService.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/IvyService.java
deleted file mode 100644
index 729cb0e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/IvyService.java
+++ /dev/null
@@ -1,36 +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;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.PublishException;
-import org.gradle.api.artifacts.ResolveException;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface IvyService {
-    ResolvedConfiguration resolve(Configuration configuration) throws ResolveException;
-
-    void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
-                 List<DependencyResolver> publishResolvers) throws PublishException;
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvableDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvableDependency.java
new file mode 100644
index 0000000..05170f8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvableDependency.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface ResolvableDependency {
+    void resolve(DependencyResolveContext context);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
index a86abde..46bb2dd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
@@ -16,8 +16,6 @@
 
 package org.gradle.api.internal.artifacts;
 
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-
 public class ResolvedConfigurationIdentifier {
     private final String moduleGroup;
     private final String moduleName;
@@ -32,11 +30,6 @@ public class ResolvedConfigurationIdentifier {
         this.configuration = configuration;
     }
 
-    public ResolvedConfigurationIdentifier(ModuleRevisionId moduleRevisionId, String configuration) {
-        this(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision(),
-                configuration);
-    }
-
     public String getConfiguration() {
         return configuration;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverFactory.java
new file mode 100644
index 0000000..4ebd9e8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ResolverFactory.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ResolverFactory {
+    ArtifactRepository createRepository(Object userDescription);
+
+    FlatDirectoryArtifactRepository createFlatDirRepository();
+
+    MavenArtifactRepository createMavenLocalRepository();
+
+    MavenArtifactRepository createMavenCentralRepository();
+
+    IvyArtifactRepository createIvyRepository();
+
+    MavenArtifactRepository createMavenRepository();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java
new file mode 100644
index 0000000..d27f2f9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationContainerInternal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.configurations;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.UnknownConfigurationException;
+
+public interface ConfigurationContainerInternal extends ConfigurationContainer {
+    ConfigurationInternal getByName(String name) throws UnknownConfigurationException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java
new file mode 100644
index 0000000..f67ccc6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.configurations;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.DependencyResolutionListener;
+
+public interface ConfigurationInternal extends Configuration, DependencyMetaDataProvider {
+    DependencyResolutionListener getDependencyResolutionBroadcast();
+
+    ResolutionStrategyInternal getResolutionStrategy();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
index d77abe7..b1ef068 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
@@ -16,11 +16,10 @@
 package org.gradle.api.internal.artifacts.configurations;
 
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.specs.Spec;
 
-import java.util.*;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * @author Hans Dockter
@@ -62,28 +61,4 @@ public class Configurations {
     private static String getCapitalName(String configurationName) {
         return configurationName.substring(0, 1).toUpperCase() + configurationName.substring(1);
     }
-
-    public static Set<Dependency> getDependencies(Set<Configuration> configurations, Spec<Dependency> dependencySpec) {
-        Set<Dependency> dependencies = new LinkedHashSet<Dependency>();
-        for (Configuration configuration : configurations) {
-            for (Dependency dependency : configuration.getDependencies()) {
-                if (dependencySpec.isSatisfiedBy(dependency)) {
-                    dependencies.add(dependency);
-                }
-            }
-        }
-        return dependencies;
-    }
-
-    public static Set<PublishArtifact> getArtifacts(Set<Configuration> configurations, Spec<PublishArtifact> artifactSpec) {
-        Set<PublishArtifact> artifacts = new HashSet<PublishArtifact>();
-        for (Configuration configuration : configurations) {
-            for (PublishArtifact artifact : configuration.getArtifacts()) {
-                if (artifactSpec.isSatisfiedBy(artifact)) {
-                    artifacts.add(artifact);
-                }
-            }
-        }
-        return artifacts;
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
index 5f1b382..f3f6597 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -17,23 +17,25 @@
 package org.gradle.api.internal.artifacts.configurations;
 
 import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.Action;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
+import org.gradle.api.*;
 import org.gradle.api.artifacts.*;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.DefaultDomainObjectContainer;
+import org.gradle.api.internal.CompositeDomainObjectSet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.DefaultDependencySet;
 import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet;
 import org.gradle.api.internal.file.AbstractFileCollection;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
-import org.gradle.api.internal.tasks.TaskDependencyInternal;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.TaskDependency;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.ConfigureUtil;
 import org.gradle.util.WrapUtil;
 
 import java.io.File;
@@ -41,7 +43,7 @@ import java.util.*;
 
 import static org.apache.ivy.core.module.descriptor.Configuration.Visibility;
 
-public class DefaultConfiguration extends AbstractFileCollection implements Configuration {
+public class DefaultConfiguration extends AbstractFileCollection implements ConfigurationInternal {
     private final String path;
     private final String name;
 
@@ -50,29 +52,51 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
     private String description;
     private ConfigurationsProvider configurationsProvider;
-
-    private IvyService ivyService;
-
-    private DefaultDomainObjectContainer<Dependency> dependencies =
-            new DefaultDomainObjectContainer<Dependency>(Dependency.class);
-
-    private Set<PublishArtifact> artifacts = new LinkedHashSet<PublishArtifact>();
-
+    private final ArtifactDependencyResolver dependencyResolver;
+    private final ListenerManager listenerManager;
+    private final DependencyMetaDataProvider metaDataProvider;
+    private final DefaultDependencySet dependencies;
+    private final CompositeDomainObjectSet<Dependency> inheritedDependencies;
+    private final DefaultDependencySet allDependencies;
+    private final DefaultPublishArtifactSet artifacts;
+    private final CompositeDomainObjectSet<PublishArtifact> inheritedArtifacts;
+    private final DefaultPublishArtifactSet allArtifacts;
+    private final ConfigurationResolvableDependencies resolvableDependencies = new ConfigurationResolvableDependencies();
+    private final ListenerBroadcast<DependencyResolutionListener> resolutionListenerBroadcast;
     private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
 
-    private final ConfigurationTaskDependency taskDependency = new ConfigurationTaskDependency();
-
     // This lock only protects the following fields
     private final Object lock = new Object();
     private State state = State.UNRESOLVED;
     private ResolvedConfiguration cachedResolvedConfiguration;
+    private final DefaultResolutionStrategy resolutionStrategy;
 
     public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
-                                IvyService ivyService) {
+                                ArtifactDependencyResolver dependencyResolver, ListenerManager listenerManager,
+                                DependencyMetaDataProvider metaDataProvider, DefaultResolutionStrategy resolutionStrategy) {
         this.path = path;
         this.name = name;
         this.configurationsProvider = configurationsProvider;
-        this.ivyService = ivyService;
+        this.dependencyResolver = dependencyResolver;
+        this.listenerManager = listenerManager;
+        this.metaDataProvider = metaDataProvider;
+        assert resolutionStrategy != null : "Cannot create configuration with null resolutionStrategy";
+        this.resolutionStrategy = resolutionStrategy;
+
+        resolutionListenerBroadcast = listenerManager.createAnonymousBroadcaster(DependencyResolutionListener.class);
+
+        DefaultDomainObjectSet<Dependency> ownDependencies = new DefaultDomainObjectSet<Dependency>(Dependency.class);
+        ownDependencies.beforeChange(new VetoContainerChangeAction());
+
+        dependencies = new DefaultDependencySet(String.format("%s dependencies", getDisplayName()), ownDependencies);
+        inheritedDependencies = new CompositeDomainObjectSet<Dependency>(Dependency.class, ownDependencies);
+        allDependencies = new DefaultDependencySet(String.format("%s all dependencies", getDisplayName()), inheritedDependencies);
+
+        DefaultDomainObjectSet<PublishArtifact> ownArtifacts = new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact.class);
+        ownArtifacts.beforeChange(new VetoContainerChangeAction());
+        artifacts = new DefaultPublishArtifactSet(String.format("%s artifacts", getDisplayName()), ownArtifacts);
+        inheritedArtifacts = new CompositeDomainObjectSet<PublishArtifact>(PublishArtifact.class, ownArtifacts);
+        allArtifacts = new DefaultPublishArtifactSet(String.format("%s all artifacts", getDisplayName()), inheritedArtifacts);
     }
 
     public String getName() {
@@ -85,6 +109,10 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
     }
 
+    public Module getModule() {
+        return metaDataProvider.getModule();
+    }
+
     public boolean isVisible() {
         return visibility == Visibility.PUBLIC;
     }
@@ -101,6 +129,10 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
 
     public Configuration setExtendsFrom(Set<Configuration> extendsFrom) {
         throwExceptionIfNotInUnresolvedState();
+        for (Configuration configuration : this.extendsFrom) {
+            inheritedArtifacts.removeCollection(configuration.getAllArtifacts());
+            inheritedDependencies.removeCollection(configuration.getAllDependencies());
+        }
         this.extendsFrom = new HashSet<Configuration>();
         for (Configuration configuration : extendsFrom) {
             extendsFrom(configuration);
@@ -117,6 +149,8 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
                         configuration, configuration.getHierarchy()));
             }
             this.extendsFrom.add(configuration);
+            inheritedArtifacts.addCollection(configuration.getAllArtifacts());
+            inheritedDependencies.addCollection(configuration.getAllDependencies());
         }
         return this;
     }
@@ -177,11 +211,11 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return fileCollection(dependencySpecClosure).getFiles();
     }
 
-    public Set<File> files(Spec<Dependency> dependencySpec) {
+    public Set<File> files(Spec<? super Dependency> dependencySpec) {
         return fileCollection(dependencySpec).getFiles();
     }
 
-    public FileCollection fileCollection(Spec<Dependency> dependencySpec) {
+    public FileCollection fileCollection(Spec<? super Dependency> dependencySpec) {
         return new ConfigurationFileCollection(dependencySpec);
     }
 
@@ -196,23 +230,23 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     public ResolvedConfiguration getResolvedConfiguration() {
         synchronized (lock) {
             if (state == State.UNRESOLVED) {
-                cachedResolvedConfiguration = ivyService.resolve(this);
+                DependencyResolutionListener broadcast = getDependencyResolutionBroadcast();
+                ResolvableDependencies incoming = getIncoming();
+                broadcast.beforeResolve(incoming);
+                cachedResolvedConfiguration = dependencyResolver.resolve(this);
                 if (cachedResolvedConfiguration.hasError()) {
                     state = State.RESOLVED_WITH_FAILURES;
                 } else {
                     state = State.RESOLVED;
                 }
+                broadcast.afterResolve(incoming);
             }
             return cachedResolvedConfiguration;
         }
     }
 
-    public void publish(List<DependencyResolver> publishResolvers, File descriptorDestination) {
-        ivyService.publish(getHierarchy(), descriptorDestination, publishResolvers);
-    }
-
     public TaskDependency getBuildDependencies() {
-        return taskDependency;
+        return allDependencies.getBuildDependencies();
     }
 
     /**
@@ -231,7 +265,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
 
             private void addTaskDependenciesFromProjectsIDependOn(final String taskName,
                                                                   final TaskDependencyResolveContext context) {
-                Set<ProjectDependency> projectDependencies = getAllDependencies(ProjectDependency.class);
+                Set<ProjectDependency> projectDependencies = getAllDependencies().withType(ProjectDependency.class);
                 for (ProjectDependency projectDependency : projectDependencies) {
                     Task nextTask = projectDependency.getDependencyProject().getTasks().findByName(taskName);
                     if (nextTask != null) {
@@ -254,7 +288,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     }
 
     private static boolean doesConfigurationDependOnProject(Configuration configuration, Project project) {
-        Set<ProjectDependency> projectDependencies = configuration.getAllDependencies(ProjectDependency.class);
+        Set<ProjectDependency> projectDependencies = configuration.getAllDependencies().withType(ProjectDependency.class);
         for (ProjectDependency projectDependency : projectDependencies) {
             if (projectDependency.getDependencyProject().equals(project)) {
                 return true;
@@ -263,63 +297,20 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return false;
     }
 
-    public TaskDependency getBuildArtifacts() {
-        return getAllArtifactFiles().getBuildDependencies();
-    }
-
-    public Set<Dependency> getDependencies() {
-        return dependencies.getAll();
-    }
-
-    public Set<Dependency> getAllDependencies() {
-        return Configurations.getDependencies(getHierarchy(), Specs.<Dependency>satisfyAll());
-    }
-
-    public <T extends Dependency> Set<T> getDependencies(Class<T> type) {
-        return filter(type, getDependencies());
-    }
-
-    private <T extends Dependency> Set<T> filter(Class<T> type, Set<Dependency> dependencySet) {
-        Set<T> matches = new LinkedHashSet<T>();
-        for (Dependency dependency : dependencySet) {
-            if (type.isInstance(dependency)) {
-                matches.add(type.cast(dependency));
-            }
-        }
-        return matches;
-    }
-
-    public <T extends Dependency> Set<T> getAllDependencies(Class<T> type) {
-        return filter(type, getAllDependencies());
+    public DependencySet getDependencies() {
+        return dependencies;
     }
 
-    public void addDependency(Dependency dependency) {
-        throwExceptionIfNotInUnresolvedState();
-        dependencies.addObject(dependency);
+    public DependencySet getAllDependencies() {
+        return allDependencies;
     }
 
-    public Configuration addArtifact(PublishArtifact artifact) {
-        throwExceptionIfNotInUnresolvedState();
-        artifacts.add(artifact);
-        return this;
-    }
-
-    public Configuration removeArtifact(PublishArtifact artifact) {
-        throwExceptionIfNotInUnresolvedState();
-        artifacts.remove(artifact);
-        return this;
+    public PublishArtifactSet getArtifacts() {
+        return artifacts;
     }
 
-    public Set<PublishArtifact> getArtifacts() {
-        return Collections.unmodifiableSet(artifacts);
-    }
-
-    public Set<PublishArtifact> getAllArtifacts() {
-        return Configurations.getArtifacts(this.getHierarchy(), Specs.SATISFIES_ALL);
-    }
-
-    public FileCollection getAllArtifactFiles() {
-        return new ArtifactsFileCollection();
+    public PublishArtifactSet getAllArtifacts() {
+        return allArtifacts;
     }
 
     public Set<ExcludeRule> getExcludeRules() {
@@ -333,7 +324,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
 
     public DefaultConfiguration exclude(Map<String, String> excludeRuleArgs) {
         throwExceptionIfNotInUnresolvedState();
-        excludeRules.add(new DefaultExcludeRule(excludeRuleArgs));
+        excludeRules.add(new DefaultExcludeRule(excludeRuleArgs.get(ExcludeRule.GROUP_KEY), excludeRuleArgs.get(ExcludeRule.MODULE_KEY)));
         return this;
     }
 
@@ -345,35 +336,30 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return String.format("configuration '%s'", path);
     }
 
-    public Configuration getConfiguration(Dependency dependency) {
-        for (Configuration configuration : getHierarchy()) {
-            if (configuration.getDependencies().contains(dependency)) {
-                return configuration;
-            }
-        }
-        return null;
+    public ResolvableDependencies getIncoming() {
+        return resolvableDependencies;
     }
 
     public Configuration copy() {
-        return createCopy(getDependencies());
+        return createCopy(getDependencies(), false);
     }
 
     public Configuration copyRecursive() {
-        return createCopy(getAllDependencies());
+        return createCopy(getAllDependencies(), true);
     }
 
-    public Configuration copy(Spec<Dependency> dependencySpec) {
-        return createCopy(Specs.filterIterable(getDependencies(), dependencySpec));
+    public Configuration copy(Spec<? super Dependency> dependencySpec) {
+        return createCopy(CollectionUtils.filter(getDependencies(), dependencySpec), false);
     }
 
-    public Configuration copyRecursive(Spec<Dependency> dependencySpec) {
-        return createCopy(Specs.filterIterable(getAllDependencies(), dependencySpec));
+    public Configuration copyRecursive(Spec<? super Dependency> dependencySpec) {
+        return createCopy(CollectionUtils.filter(getAllDependencies(), dependencySpec), true);
     }
 
-    private DefaultConfiguration createCopy(Set<Dependency> dependencies) {
+    private DefaultConfiguration createCopy(Set<Dependency> dependencies, boolean recursive) {
         DetachedConfigurationsProvider configurationsProvider = new DetachedConfigurationsProvider();
         DefaultConfiguration copiedConfiguration = new DefaultConfiguration(path + "Copy", name + "Copy",
-                configurationsProvider, ivyService);
+                configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy);
         configurationsProvider.setTheOnlyConfiguration(copiedConfiguration);
         // state, cachedResolvedConfiguration, and extendsFrom intentionally not copied - must re-resolve copy
         // copying extendsFrom could mess up dependencies when copy was re-resolved
@@ -382,19 +368,26 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         copiedConfiguration.transitive = transitive;
         copiedConfiguration.description = description;
 
-        for (PublishArtifact artifact : getAllArtifacts()) {
-            copiedConfiguration.addArtifact(artifact);
-        }
+        copiedConfiguration.getArtifacts().addAll(getAllArtifacts());
 
         // todo An ExcludeRule is a value object but we don't enforce immutability for DefaultExcludeRule as strong as we
         // should (we expose the Map). We should provide a better API for ExcludeRule (I don't want to use unmodifiable Map).
-        // As soon as DefaultExcludeRule is truly immutable, we don't need to create a new instance of DefaultExcludeRule. 
-        for (ExcludeRule excludeRule : getExcludeRules()) {
-            copiedConfiguration.excludeRules.add(new DefaultExcludeRule(excludeRule.getExcludeArgs()));
+        // As soon as DefaultExcludeRule is truly immutable, we don't need to create a new instance of DefaultExcludeRule.
+        Set<Configuration> excludeRuleSources = new LinkedHashSet<Configuration>();
+        excludeRuleSources.add(this);
+        if (recursive) {
+            excludeRuleSources.addAll(getHierarchy());
+        }
+
+        for (Configuration excludeRuleSource : excludeRuleSources) {
+            for (ExcludeRule excludeRule : excludeRuleSource.getExcludeRules()) {
+                copiedConfiguration.excludeRules.add(new DefaultExcludeRule(excludeRule.getGroup(), excludeRule.getModule()));
+            }
         }
 
+        DomainObjectSet<Dependency> copiedDependencies = copiedConfiguration.getDependencies();
         for (Dependency dependency : dependencies) {
-            copiedConfiguration.addDependency(dependency.copy());
+            copiedDependencies.add(dependency.copy());
         }
         return copiedConfiguration;
     }
@@ -407,46 +400,29 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         return copyRecursive(Specs.<Dependency>convertClosureToSpec(dependencySpec));
     }
 
-    private void throwExceptionIfNotInUnresolvedState() {
-        if (getState() != State.UNRESOLVED) {
-            throw new InvalidUserDataException("You can't change a configuration which is not in unresolved state!");
-        }
+    public DependencyResolutionListener getDependencyResolutionBroadcast() {
+        return resolutionListenerBroadcast.getSource();
     }
 
-    class ArtifactsFileCollection extends AbstractFileCollection {
-        private final TaskDependencyInternal taskDependency = new AbstractTaskDependency() {
-            public void resolve(TaskDependencyResolveContext context) {
-                for (Configuration configuration : getExtendsFrom()) {
-                    context.add(configuration.getBuildArtifacts());
-                }
-                for (PublishArtifact publishArtifact : getArtifacts()) {
-                    context.add(publishArtifact);
-                }
-            }
-        };
-
-        public String getDisplayName() {
-            return String.format("%s artifacts", DefaultConfiguration.this);
-        }
+    public DefaultResolutionStrategy getResolutionStrategy() {
+        return resolutionStrategy;
+    }
 
-        @Override
-        public TaskDependency getBuildDependencies() {
-            return taskDependency;
-        }
+    public Configuration resolutionStrategy(Closure closure) {
+        ConfigureUtil.configure(closure, resolutionStrategy);
+        return this;
+    }
 
-        public Set<File> getFiles() {
-            Set<File> files = new LinkedHashSet<File>();
-            for (PublishArtifact artifact : getAllArtifacts()) {
-                files.add(artifact.getFile());
-            }
-            return files;
+    private void throwExceptionIfNotInUnresolvedState() {
+        if (getState() != State.UNRESOLVED) {
+            throw new InvalidUserDataException("You can't change a configuration which is not in unresolved state!");
         }
     }
 
     class ConfigurationFileCollection extends AbstractFileCollection {
-        private Spec<Dependency> dependencySpec;
+        private Spec<? super Dependency> dependencySpec;
 
-        private ConfigurationFileCollection(Spec<Dependency> dependencySpec) {
+        private ConfigurationFileCollection(Spec<? super Dependency> dependencySpec) {
             this.dependencySpec = dependencySpec;
         }
 
@@ -462,7 +438,12 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
             };
         }
 
-        public Spec<Dependency> getDependencySpec() {
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return DefaultConfiguration.this.getBuildDependencies();
+        }
+
+        public Spec<? super Dependency> getDependencySpec() {
             return dependencySpec;
         }
 
@@ -481,37 +462,99 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
     }
 
-    public Action<? super Dependency> whenDependencyAdded(Action<? super Dependency> action) {
-        return dependencies.whenObjectAdded(action);
-    }
+    /**
+     * Print a formatted representation of a Configuration
+     */
+    public String dump() {
+        StringBuilder reply = new StringBuilder();
+
+        reply.append("\nConfiguration:");
+        reply.append("  class='" + this.getClass() + "'");
+        reply.append("  name='" + this.getName() + "'");
+        reply.append("  hashcode='" + this.hashCode() + "'");
+
+        reply.append("\nLocal Dependencies:");
+        if (getDependencies().size() > 0) {
+            for (Dependency d : getDependencies()) {
+                reply.append("\n   " + d);
+            }
+        } else {
+            reply.append("\n   none");
+        }
 
-    public void whenDependencyAdded(Closure closure) {
-        dependencies.whenObjectAdded(closure);
-    }
+        reply.append("\nLocal Artifacts:");
+        if (getArtifacts().size() > 0) {
+            for (PublishArtifact a : getArtifacts()) {
+                reply.append("\n   " + a);
+            }
+        } else {
+            reply.append("\n   none");
+        }
 
-    public void allDependencies(Action<? super Dependency> action) {
-        dependencies.all(action);
+        reply.append("\nAll Dependencies:");
+        if (getAllDependencies().size() > 0) {
+            for (Dependency d : getAllDependencies()) {
+                reply.append("\n   " + d);
+            }
+        } else {
+            reply.append("\n   none");
+        }
+
+
+        reply.append("\nAll Artifacts:");
+        if (getAllArtifacts().size() > 0) {
+            for (PublishArtifact a : getAllArtifacts()) {
+                reply.append("\n   " + a);
+            }
+        } else {
+            reply.append("\n   none");
+        }
+
+        return reply.toString();
     }
 
-    public void allDependencies(Closure action) {
-        dependencies.all(action);
+    private class VetoContainerChangeAction implements Runnable {
+        public void run() {
+            throwExceptionIfNotInUnresolvedState();
+        }
     }
 
-    private class ConfigurationTaskDependency extends AbstractTaskDependency {
+    private class ConfigurationResolvableDependencies implements ResolvableDependencies {
+        public String getName() {
+            return name;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
         @Override
         public String toString() {
-            return String.format("build dependencies %s", DefaultConfiguration.this);
+            return String.format("dependencies '%s'", path);
         }
 
-        public void resolve(TaskDependencyResolveContext context) {
-            for (Configuration configuration : getExtendsFrom()) {
-                context.add(configuration);
-            }
-            for (SelfResolvingDependency dependency : DefaultConfiguration.this.getDependencies(
-                    SelfResolvingDependency.class)) {
-                context.add(dependency);
-            }
+        public FileCollection getFiles() {
+            return DefaultConfiguration.this.fileCollection(Specs.<Dependency>satisfyAll());
+        }
+
+        public DependencySet getDependencies() {
+            return getAllDependencies();
+        }
+
+        public void beforeResolve(Action<? super ResolvableDependencies> action) {
+            resolutionListenerBroadcast.add("beforeResolve", action);
+        }
+
+        public void beforeResolve(Closure action) {
+            resolutionListenerBroadcast.add("beforeResolve", action);
+        }
+
+        public void afterResolve(Action<? super ResolvableDependencies> action) {
+            resolutionListenerBroadcast.add("afterResolve", action);
+        }
+
+        public void afterResolve(Closure action) {
+            resolutionListenerBroadcast.add("afterResolve", action);
         }
     }
 }
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
index 41831b6..90d1543 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -15,36 +15,69 @@
  */
 package org.gradle.api.internal.artifacts.configurations;
 
+import groovy.lang.Closure;
+import org.gradle.api.DomainObjectSet;
 import org.gradle.api.UnknownDomainObjectException;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.AutoCreateDomainObjectContainer;
-import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.UnknownConfigurationException;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
 import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.listener.ListenerManager;
+
+import java.util.Collection;
+import java.util.Set;
 
 /**
  * @author Hans Dockter
  */
-public class DefaultConfigurationContainer extends AutoCreateDomainObjectContainer<Configuration> 
-        implements ConfigurationContainer, ConfigurationsProvider {
+public class DefaultConfigurationContainer extends AbstractNamedDomainObjectContainer<Configuration> 
+        implements ConfigurationContainerInternal, ConfigurationsProvider {
     public static final String DETACHED_CONFIGURATION_DEFAULT_NAME = "detachedConfiguration";
     
-    private final IvyService ivyService;
-    private final ClassGenerator classGenerator;
+    private final ArtifactDependencyResolver dependencyResolver;
+    private final Instantiator instantiator;
     private final DomainObjectContext context;
+    private final ListenerManager listenerManager;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
 
     private int detachedConfigurationDefaultNameCounter = 1;
 
-    public DefaultConfigurationContainer(IvyService ivyService, ClassGenerator classGenerator, DomainObjectContext context) {
-        super(Configuration.class, classGenerator);
-        this.ivyService = ivyService;
-        this.classGenerator = classGenerator;
+    public DefaultConfigurationContainer(ArtifactDependencyResolver dependencyResolver,
+                                         Instantiator instantiator, DomainObjectContext context, ListenerManager listenerManager,
+                                         DependencyMetaDataProvider dependencyMetaDataProvider) {
+        super(Configuration.class, instantiator, new Configuration.Namer());
+        this.dependencyResolver = dependencyResolver;
+        this.instantiator = instantiator;
         this.context = context;
+        this.listenerManager = listenerManager;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
     }
 
     @Override
-    protected Configuration create(String name) {
-        return classGenerator.newInstance(DefaultConfiguration.class, context.absoluteProjectPath(name), name, this, ivyService);
+    protected Configuration doCreate(String name) {
+        return instantiator.newInstance(DefaultConfiguration.class, context.absoluteProjectPath(name),
+                name, this, dependencyResolver, listenerManager,
+                dependencyMetaDataProvider, new DefaultResolutionStrategy());
+    }
+
+    public Set<Configuration> getAll() {
+        return this;
+    }
+
+    public Configuration add(String name) {
+        return create(name);
+    }
+
+    public Configuration add(String name, Closure closure) {
+        return create(name, closure);
+    }
+
+    @Override
+    public ConfigurationInternal getByName(String name) {
+        return (ConfigurationInternal) super.getByName(name);
     }
 
     @Override
@@ -57,19 +90,33 @@ public class DefaultConfigurationContainer extends AutoCreateDomainObjectContain
         return new UnknownConfigurationException(String.format("Configuration with name '%s' not found.", name));
     }
 
-    public IvyService getIvyService() {
-        return ivyService;
-    }
-
     public Configuration detachedConfiguration(Dependency... dependencies) {
         DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
         String name = DETACHED_CONFIGURATION_DEFAULT_NAME + detachedConfigurationDefaultNameCounter++;
-        DefaultConfiguration detachedConfiguration = new DefaultConfiguration(name, name,
-                detachedConfigurationsProvider, ivyService);
+        DefaultConfiguration detachedConfiguration = new DefaultConfiguration(
+                name, name, detachedConfigurationsProvider, dependencyResolver,
+                listenerManager, dependencyMetaDataProvider, new DefaultResolutionStrategy());
+        DomainObjectSet<Dependency> detachedDependencies = detachedConfiguration.getDependencies();
         for (Dependency dependency : dependencies) {
-            detachedConfiguration.addDependency(dependency.copy());
+            detachedDependencies.add(dependency.copy());
         }
         detachedConfigurationsProvider.setTheOnlyConfiguration(detachedConfiguration);
         return detachedConfiguration;
     }
+    
+    /**
+     * Build a formatted representation of all Configurations in this ConfigurationContainer.
+     * Configuration(s) being toStringed are likely derivations of DefaultConfiguration.
+     */
+    public String dump() {
+        StringBuilder reply = new StringBuilder();
+        
+        reply.append("Configuration of type: " + getTypeDisplayName());
+        Collection<Configuration> configs = getAll();
+        for (Configuration c : configs) {
+            reply.append("\n  " + c.toString());
+        }
+        
+        return reply.toString();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategy.java
new file mode 100644
index 0000000..1b4ce95
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategy.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.configurations;
+
+import org.gradle.api.artifacts.ConflictResolution;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.ResolutionStrategy;
+import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.configurations.conflicts.LatestConflictResolution;
+import org.gradle.api.internal.artifacts.configurations.conflicts.StrictConflictResolution;
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.DefaultCachePolicy;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * by Szczepan Faber, created at: 10/7/11
+ */
+public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
+
+    private Set<ModuleVersionSelector> forcedModules = new LinkedHashSet<ModuleVersionSelector>();
+    private ConflictResolution conflictResolution = new LatestConflictResolution();
+    private final DefaultCachePolicy cachePolicy = new DefaultCachePolicy();
+
+    public Set<ModuleVersionSelector> getForcedModules() {
+        return forcedModules;
+    }
+
+    public ResolutionStrategy failOnVersionConflict() {
+        this.conflictResolution = new StrictConflictResolution();
+        return this;
+    }
+
+    public ConflictResolution getConflictResolution() {
+        return this.conflictResolution;
+    }
+
+    public ResolutionRules getResolutionRules() {
+        return cachePolicy;
+    }
+
+    public DefaultResolutionStrategy force(Object... forcedModuleNotations) {
+        assert forcedModuleNotations != null : "forcedModuleNotations cannot be null";
+        this.forcedModules.addAll(new ForcedModuleNotationParser().parseNotation(forcedModuleNotations));
+        return this;
+    }
+
+    public DefaultResolutionStrategy setForcedModules(Object ... forcedModuleNotations) {
+        this.forcedModules = new ForcedModuleNotationParser().parseNotation(forcedModuleNotations);
+        return this;
+    }
+
+    public CachePolicy getCachePolicy() {
+        return cachePolicy;
+    }
+
+    public void cacheDynamicVersionsFor(int value, String units) {
+        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
+        cacheDynamicVersionsFor(value, timeUnit);
+    }
+
+    public void cacheDynamicVersionsFor(int value, TimeUnit units) {
+        this.cachePolicy.cacheDynamicVersionsFor(value, units);
+    }
+
+    public void cacheChangingModulesFor(int value, String units) {
+        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
+        cacheChangingModulesFor(value, timeUnit);
+    }
+
+    public void cacheChangingModulesFor(int value, TimeUnit units) {
+        this.cachePolicy.cacheChangingModulesFor(value, units);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
index 45e55ca..a5b6ef0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
@@ -16,17 +16,10 @@
 package org.gradle.api.internal.artifacts.configurations;
 
 import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
-
-import java.io.File;
 
 /**
  * @author Hans Dockter
  */
 public interface DependencyMetaDataProvider {
-    File getGradleUserHomeDir();
-
-    InternalRepository getInternalRepository();
-
     Module getModule();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParser.java
new file mode 100644
index 0000000..4db406c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParser.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 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.configurations;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ParsedModuleStringNotation;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.TopLevelNotationParser;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 10/11/11
+ */
+public class ForcedModuleNotationParser implements TopLevelNotationParser, NotationParser<Set<ModuleVersionSelector>> {
+
+    private NotationParser<Set<ModuleVersionSelector>> delegate = new NotationParserBuilder<ModuleVersionSelector>()
+            .resultingType(ModuleVersionSelector.class)
+            .parser(new ForcedModuleStringParser())
+            .parser(new ForcedModuleMapParser())
+            .toFlatteningComposite();
+
+    public Set<ModuleVersionSelector> parseNotation(Object notation) {
+        assert notation != null : "notation cannot be null";
+        return delegate.parseNotation(notation);
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    static class ForcedModuleMapParser extends MapNotationParser<ModuleVersionSelector> {
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Maps, e.g. [group: 'org.gradle', name:'gradle-core', version: '1.0'].");
+        }
+
+        protected ModuleVersionSelector parseMap(@MapKey("group") String group, @MapKey("name") String name, @MapKey("version") String version) {
+            return selector(group, name, version);
+        }
+    }
+
+    static class ForcedModuleStringParser extends TypedNotationParser<CharSequence, ModuleVersionSelector> {
+
+        public ForcedModuleStringParser() {
+            super(CharSequence.class);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Strings/CharSequences, e.g. 'org.gradle:gradle-core:1.0'.");
+        }
+
+        public ModuleVersionSelector parseType(CharSequence notation) {
+            ParsedModuleStringNotation parsed;
+            try {
+                parsed = new ParsedModuleStringNotation(notation.toString(), null);
+            } catch (IllegalDependencyNotation e) {
+                throw new InvalidUserDataException(
+                    "Invalid format: '" + notation + "'. The Correct notation is a 3-part group:name:version notation,"
+                    + "e.g: 'org.gradle:gradle-core:1.0'");
+            }
+
+            if (parsed.getGroup() == null || parsed.getName() == null || parsed.getVersion() == null) {
+                throw new InvalidUserDataException(
+                    "Invalid format: '" + notation + "'. Group, name and version cannot be empty. Correct example: "
+                    + "'org.gradle:gradle-core:1.0'");
+            }
+            return selector(parsed.getGroup(), parsed.getName(), parsed.getVersion());
+        }
+    }
+
+    static ModuleVersionSelector selector(final String group, final String name, final String version) {
+        return new DefaultModuleVersionSelector(group, name, version);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
new file mode 100644
index 0000000..9f19950
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolutionStrategyInternal.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.configurations;
+
+import org.gradle.api.artifacts.ConflictResolution;
+import org.gradle.api.artifacts.ResolutionStrategy;
+import org.gradle.api.artifacts.cache.ResolutionRules;
+import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
+
+public interface ResolutionStrategyInternal extends ResolutionStrategy {
+    /**
+     * Gets the current expiry policy for dynamic revisions.
+     *
+     * @return the expiry policy
+     */
+    CachePolicy getCachePolicy();
+
+    /**
+     * Until the feature 'settles' and we receive more feedback, it's internal
+     *
+     * @return conflict resolution
+     */
+    ConflictResolution getConflictResolution();
+
+    /**
+     * The nascent DSL for cache control, and possibly other per-module resolution overrides
+     * @return the resolution rules
+     */
+    ResolutionRules getResolutionRules();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/LatestConflictResolution.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/LatestConflictResolution.java
new file mode 100644
index 0000000..603438c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/LatestConflictResolution.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.configurations.conflicts;
+
+import org.gradle.api.artifacts.ConflictResolution;
+
+/**
+ * Latest resolution strategy
+ * <p>
+ * by Szczepan Faber, created at: 10/5/11
+ */
+public class LatestConflictResolution implements ConflictResolution {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/StrictConflictResolution.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/StrictConflictResolution.java
new file mode 100644
index 0000000..1fdbacb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/conflicts/StrictConflictResolution.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.configurations.conflicts;
+
+import org.gradle.api.artifacts.ConflictResolution;
+
+/**
+ * Strict type, allows configuring (forcing) certain dependency versions using dependency notation
+ * <p>
+ * by Szczepan Faber, created at: 10/5/11
+ */
+public class StrictConflictResolution implements ConflictResolution {
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java
new file mode 100644
index 0000000..9e6121f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/CachePolicy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.configurations.dynamicversion;
+
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+
+import java.io.File;
+
+public interface CachePolicy {
+    boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis);
+
+    boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis);
+
+    boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis);
+
+    boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicy.java
new file mode 100644
index 0000000..6cbe5fa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicy.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2011 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.configurations.dynamicversion;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.ArtifactIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.artifacts.ResolvedModuleVersion;
+import org.gradle.api.artifacts.cache.ArtifactResolutionControl;
+import org.gradle.api.artifacts.cache.DependencyResolutionControl;
+import org.gradle.api.artifacts.cache.ModuleResolutionControl;
+import org.gradle.api.artifacts.cache.ResolutionControl;
+import org.gradle.api.artifacts.cache.ResolutionRules;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class DefaultCachePolicy implements CachePolicy, ResolutionRules {
+    private static final int SECONDS_IN_DAY = 24 * 60 * 60;
+
+    private final List<Action<? super DependencyResolutionControl>> dependencyCacheRules = new ArrayList<Action<? super DependencyResolutionControl>>();
+    private final List<Action<? super ModuleResolutionControl>> moduleCacheRules = new ArrayList<Action<? super ModuleResolutionControl>>();
+    private final List<Action<? super ArtifactResolutionControl>> artifactCacheRules = new ArrayList<Action<? super ArtifactResolutionControl>>();
+
+    public DefaultCachePolicy() {
+        cacheDynamicVersionsFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
+        cacheChangingModulesFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
+        cacheMissingModulesAndArtifactsFor(SECONDS_IN_DAY, TimeUnit.SECONDS);
+    }
+
+    public void eachDependency(Action<? super DependencyResolutionControl> rule) {
+        dependencyCacheRules.add(0, rule);
+    }
+
+    public void eachModule(Action<? super ModuleResolutionControl> rule) {
+        moduleCacheRules.add(0, rule);
+    }
+
+    public void eachArtifact(Action<? super ArtifactResolutionControl> rule) {
+        artifactCacheRules.add(0, rule);
+    }
+
+    public void cacheDynamicVersionsFor(final int value, final TimeUnit unit) {
+        eachDependency(new Action<DependencyResolutionControl>() {
+            public void execute(DependencyResolutionControl dependencyResolutionControl) {
+                dependencyResolutionControl.cacheFor(value, unit);
+            }
+        });
+    }
+
+    public void cacheChangingModulesFor(final int value, final TimeUnit units) {
+        eachModule(new Action<ModuleResolutionControl>() {
+            public void execute(ModuleResolutionControl moduleResolutionControl) {
+                if (moduleResolutionControl.isChanging()) {
+                    moduleResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+    }
+    
+    private void cacheMissingModulesAndArtifactsFor(final int value, final TimeUnit units) {
+        eachModule(new Action<ModuleResolutionControl>() {
+            public void execute(ModuleResolutionControl moduleResolutionControl) {
+                if (moduleResolutionControl.getCachedResult() == null) {
+                    moduleResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+        eachArtifact(new Action<ArtifactResolutionControl>() {
+            public void execute(ArtifactResolutionControl artifactResolutionControl) {
+                if (artifactResolutionControl.getCachedResult() == null) {
+                    artifactResolutionControl.cacheFor(value, units);
+                }
+            }
+        });
+    }
+
+    public boolean mustRefreshDynamicVersion(ModuleVersionSelector selector, ModuleVersionIdentifier moduleId, long ageMillis) {
+        CachedDependencyResolutionControl dependencyResolutionControl = new CachedDependencyResolutionControl(selector, moduleId, ageMillis);
+
+        for (Action<? super DependencyResolutionControl> rule : dependencyCacheRules) {
+            rule.execute(dependencyResolutionControl);
+            if (dependencyResolutionControl.ruleMatch()) {
+                return dependencyResolutionControl.mustCheck();
+            }
+        }
+        
+        return false;
+    }
+
+    public boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, final long ageMillis) {
+        return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, false);
+    }
+
+    public boolean mustRefreshChangingModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion resolvedModuleVersion, long ageMillis) {
+        return mustRefreshModule(moduleVersionId, resolvedModuleVersion, ageMillis, true);
+    }
+
+    private boolean mustRefreshModule(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion version, long ageMillis, boolean changingModule) {
+        CachedModuleResolutionControl moduleResolutionControl = new CachedModuleResolutionControl(moduleVersionId, version, changingModule, ageMillis);
+
+        for (Action<? super ModuleResolutionControl> rule : moduleCacheRules) {
+            rule.execute(moduleResolutionControl);
+            if (moduleResolutionControl.ruleMatch()) {
+                return moduleResolutionControl.mustCheck();
+            }
+        }
+
+        return false;
+    }
+
+    public boolean mustRefreshArtifact(ArtifactIdentifier artifactIdentifier, File cachedArtifactFile, long ageMillis) {
+        CachedArtifactResolutionControl artifactResolutionControl = new CachedArtifactResolutionControl(artifactIdentifier, cachedArtifactFile, ageMillis);
+
+        for (Action<? super ArtifactResolutionControl> rule : artifactCacheRules) {
+            rule.execute(artifactResolutionControl);
+            if (artifactResolutionControl.ruleMatch()) {
+                return artifactResolutionControl.mustCheck();
+            }
+        }
+
+        return false;
+    }
+
+    private abstract static class AbstractResolutionControl<A, B> implements ResolutionControl<A, B> {
+        private final A request;
+        private final B cachedResult;
+        private final long ageMillis;
+        private boolean ruleMatch;
+        private boolean mustCheck;
+
+        private AbstractResolutionControl(A request, B cachedResult, long ageMillis) {
+            this.request = request;
+            this.cachedResult = cachedResult;
+            this.ageMillis = ageMillis;
+        }
+
+        public A getRequest() {
+            return request;
+        }
+
+        public B getCachedResult() {
+            return cachedResult;
+        }
+
+        public void cacheFor(int value, TimeUnit units) {
+            long timeoutMillis = TimeUnit.MILLISECONDS.convert(value, units);
+            if (ageMillis <= timeoutMillis) {
+                setMustCheck(false);
+            } else {
+                setMustCheck(true);
+            }
+        }
+
+        public void useCachedResult() {
+            setMustCheck(false);
+        }
+
+        public void refresh() {
+            setMustCheck(true);
+        }
+        
+        private void setMustCheck(boolean val) {
+            ruleMatch = true;
+            mustCheck = val;
+        }
+        
+        public boolean ruleMatch() {
+            return ruleMatch;
+        }
+        
+        public boolean mustCheck() {
+            return mustCheck;
+        }
+    }
+    
+    private class CachedDependencyResolutionControl extends AbstractResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> implements DependencyResolutionControl {
+        private CachedDependencyResolutionControl(ModuleVersionSelector request, ModuleVersionIdentifier cachedVersion, long ageMillis) {
+            super(request, cachedVersion, ageMillis);
+        }
+    }
+    
+    private class CachedModuleResolutionControl extends AbstractResolutionControl<ModuleVersionIdentifier, ResolvedModuleVersion> implements ModuleResolutionControl {
+        private final boolean changing;
+
+        private CachedModuleResolutionControl(ModuleVersionIdentifier moduleVersionId, ResolvedModuleVersion cachedVersion, boolean changing, long ageMillis) {
+            super(moduleVersionId, cachedVersion, ageMillis);
+            this.changing = changing;
+        }
+
+        public boolean isChanging() {
+            return changing;
+        }
+    }
+
+    private class CachedArtifactResolutionControl extends AbstractResolutionControl<ArtifactIdentifier, File> implements ArtifactResolutionControl {
+        private CachedArtifactResolutionControl(ArtifactIdentifier artifactIdentifier, File cachedResult, long ageMillis) {
+            super(artifactIdentifier, cachedResult, ageMillis);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
index 92f4d2e..07dfb00 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
@@ -16,13 +16,14 @@
 
 package org.gradle.api.internal.artifacts.dependencies;
 
-import org.gradle.api.internal.artifacts.DependencyInternal;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.artifacts.ResolvableDependency;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
 
 /**
 * @author Hans Dockter
 */
-public abstract class AbstractDependency implements DependencyInternal {
+public abstract class AbstractDependency implements ResolvableDependency, Dependency {
     protected void copyTo(AbstractDependency target) {
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
index 931f058..c0cda2b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
@@ -141,8 +141,7 @@ public class DefaultClientModule extends AbstractExternalDependency implements C
         }
 
         ClientModule that = (ClientModule) o;
-
-        return isKeyEquals(that);
+        return isContentEqualsFor(that);
     }
 
     @Override
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
index f717c6d..9ed7976 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
@@ -109,8 +109,7 @@ public class DefaultExternalModuleDependency extends AbstractExternalDependency
         }
 
         DefaultExternalModuleDependency that = (DefaultExternalModuleDependency) o;
-
-        return isKeyEquals(that);
+        return isContentEqualsFor(that);
     }
 
     @Override
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
index 6924114..90d490a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
@@ -23,6 +23,7 @@ import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
 import org.gradle.api.tasks.TaskDependency;
@@ -34,15 +35,15 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public class DefaultProjectDependency extends AbstractModuleDependency implements ProjectDependency {
-    private Project dependencyProject;
+    private ProjectInternal dependencyProject;
     private final ProjectDependenciesBuildInstruction instruction;
     private final TaskDependencyImpl taskDependency = new TaskDependencyImpl();
 
-    public DefaultProjectDependency(Project dependencyProject, ProjectDependenciesBuildInstruction instruction) {
+    public DefaultProjectDependency(ProjectInternal dependencyProject, ProjectDependenciesBuildInstruction instruction) {
         this(dependencyProject, null, instruction);
     }
 
-    public DefaultProjectDependency(Project dependencyProject, String configuration,
+    public DefaultProjectDependency(ProjectInternal dependencyProject, String configuration,
                                     ProjectDependenciesBuildInstruction instruction) {
         super(configuration);
         this.dependencyProject = dependencyProject;
@@ -156,7 +157,7 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
             }
             Configuration configuration = getProjectConfiguration();
             context.add(configuration);
-            context.add(configuration.getBuildArtifacts());
+            context.add(configuration.getAllArtifacts());
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
index 34e86aa..874d011 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
@@ -31,7 +31,7 @@ import org.codehaus.groovy.control.SourceUnit;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.groovy.scripts.Transformer;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -67,7 +67,7 @@ public abstract class ClasspathScriptTransformer extends AbstractScriptTransform
                     Map value = (Map) field.get(source.getAST());
                     value.remove(importedClass.getAlias());
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
             }
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
deleted file mode 100644
index 7780522..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
+++ /dev/null
@@ -1,66 +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.dsl
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.api.artifacts.dsl.ArtifactHandler
-import org.gradle.util.GUtil
-import org.gradle.util.ConfigureUtil
-
-/**
- * @author Hans Dockter
- */
-class DefaultArtifactHandler implements ArtifactHandler {
-
-    ConfigurationContainer configurationContainer
-    PublishArtifactFactory publishArtifactFactory
-
-    def DefaultArtifactHandler(ConfigurationContainer configurationContainer, PublishArtifactFactory publishArtifactFactory) {
-        this.configurationContainer = configurationContainer;
-        this.publishArtifactFactory = publishArtifactFactory;
-    }
-
-    private PublishArtifact pushArtifact(org.gradle.api.artifacts.Configuration configuration, Object notation, Closure configureClosure) {
-        PublishArtifact publishArtifact
-        if (notation instanceof PublishArtifact) {
-            publishArtifact = notation
-        } else {
-            publishArtifact = publishArtifactFactory.createArtifact(notation)
-        }
-        configuration.addArtifact(publishArtifact)
-        ConfigureUtil.configure(configureClosure, publishArtifact)
-        return publishArtifact
-    }
-
-    public def methodMissing(String name, args) {
-        Configuration configuration = configurationContainer.findByName(name)
-        if (configuration == null) {
-            if (!getMetaClass().respondsTo(this, name, args.size())) {
-                throw new MissingMethodException(name, this.getClass(), args);
-            }
-        }
-        Object[] normalizedArgs = GUtil.flatten(args as List, false)
-        if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
-            return pushArtifact(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
-        } 
-        args.each {notation ->
-            pushArtifact(configuration, notation, null)
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
deleted file mode 100644
index 39bf287..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
+++ /dev/null
@@ -1,34 +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.dsl;
-
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.tasks.bundling.AbstractArchiveTask;
-import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
-import org.gradle.api.InvalidUserDataException;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPublishArtifactFactory implements PublishArtifactFactory {
-    public PublishArtifact createArtifact(Object notation) {
-        if (notation instanceof AbstractArchiveTask) {
-            return new ArchivePublishArtifact((AbstractArchiveTask) notation);
-        }
-        throw new InvalidUserDataException("Notation is invalid for an artifact! Passed notation=" + notation);
-    }                            
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
index c2ded27..d481eae 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
@@ -15,196 +15,119 @@
  */
 package org.gradle.api.internal.artifacts.dsl;
 
+import com.google.common.collect.Lists;
 import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.AbstractResolver;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.FileSystemResolver;
 import org.gradle.api.Action;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.dsl.ArtifactRepository;
-import org.gradle.api.artifacts.dsl.IvyArtifactRepository;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
-import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.artifacts.DefaultResolverContainer;
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainer;
+import org.gradle.api.internal.artifacts.ResolverFactory;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
 import org.gradle.util.ConfigureUtil;
-import org.gradle.util.GUtil;
-import org.gradle.util.HashUtil;
-import org.gradle.util.WrapUtil;
+import org.gradle.util.DeprecationLogger;
 
-import java.io.File;
-import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
  * @author Hans Dockter
  */
-public class DefaultRepositoryHandler extends DefaultResolverContainer implements RepositoryHandler {
-    public DefaultRepositoryHandler(ResolverFactory resolverFactory, ClassGenerator classGenerator) {
-        super(resolverFactory, classGenerator);
+public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer implements RepositoryHandler, ResolverProvider {
+    public DefaultRepositoryHandler(ResolverFactory resolverFactory, Instantiator instantiator) {
+        super(resolverFactory, instantiator);
     }
 
-    public FileSystemResolver flatDir(Map args) {
-        Object[] rootDirPaths = getFlatDirRootDirs(args);
-        File[] rootDirs = new File[rootDirPaths.length];
-        for (int i = 0; i < rootDirPaths.length; i++) {
-            Object rootDirPath = rootDirPaths[i];
-            rootDirs[i] = new File(rootDirPath.toString());
-        }
-        FileSystemResolver resolver = getResolverFactory().createFlatDirResolver(
-                getNameFromMap(args, HashUtil.createHash(GUtil.join(rootDirPaths, ""))),
-                rootDirs);
-        return (FileSystemResolver) add(resolver);
+    public FlatDirectoryArtifactRepository flatDir(Action<? super FlatDirectoryArtifactRepository> action) {
+        return addRepository(getResolverFactory().createFlatDirRepository(), action, "flatDir");
     }
 
-    private String getNameFromMap(Map args, String defaultName) {
-        Object name = args.get("name");
-        return name != null ? name.toString() : defaultName;
+    public FlatDirectoryArtifactRepository flatDir(Closure configureClosure) {
+        return addRepository(getResolverFactory().createFlatDirRepository(), configureClosure, "flatDir");
     }
 
-    private Object[] getFlatDirRootDirs(Map args) {
-        List dirs = createStringifiedListFromMapArg(args, "dirs");
-        if (dirs == null) {
-            throw new InvalidUserDataException("You must specify dirs for the flat dir repository.");
+    public FlatDirectoryArtifactRepository flatDir(Map<String, ?> args) {
+        Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
+        if (modifiedArgs.containsKey("dirs")) {
+            modifiedArgs.put("dirs", toList(modifiedArgs.get("dirs")));
         }
-        return dirs.toArray();
+        return addRepository(getResolverFactory().createFlatDirRepository(), modifiedArgs, "flatDir");
     }
 
-    private List<String> createStringifiedListFromMapArg(Map args, String argName) {
-        Object dirs = args.get(argName);
-        if (dirs == null) {
-            return null;
-        }
-        Iterable<Object> iterable;
-        if (dirs instanceof Iterable) {
-            iterable = (Iterable<Object>) dirs;
-        } else {
-            iterable = WrapUtil.toSet(dirs);
-        }
-        List<String> list = new ArrayList<String>();
-        for (Object o : iterable) {
-            list.add(o.toString());
-        }
-        return list;
+    public MavenArtifactRepository mavenCentral() {
+        return mavenCentral(Collections.<String, Object>emptyMap());
     }
 
-    public DependencyResolver mavenCentral() {
-        return mavenCentral(Collections.emptyMap());
-    }
-
-    public DependencyResolver mavenCentral(Map args) {
-        List<String> urls = createStringifiedListFromMapArg(args, "urls");
-        return add(getResolverFactory().createMavenRepoResolver(
-                getNameFromMap(args, DEFAULT_MAVEN_CENTRAL_REPO_NAME),
-                MAVEN_CENTRAL_URL,
-                urls == null ? new String[0] : urls.toArray(new String[urls.size()])));
+    public MavenArtifactRepository mavenCentral(Map<String, ?> args) {
+        Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
+        if (modifiedArgs.containsKey("urls")) {
+            DeprecationLogger.nagUserWith("The 'urls' property of the RepositoryHandler.mavenCentral() method is deprecated and will be removed in a future version of Gradle. "
+                    + "You should use the 'artifactUrls' property to define additional artifact locations.");
+            List<Object> urls = toList(modifiedArgs.remove("urls"));
+            modifiedArgs.put("artifactUrls", urls);
+        }
+        return addRepository(getResolverFactory().createMavenCentralRepository(), modifiedArgs, DEFAULT_MAVEN_CENTRAL_REPO_NAME);
     }
 
-    public DependencyResolver mavenLocal() {
-        return add(getResolverFactory().createMavenLocalResolver(DEFAULT_MAVEN_LOCAL_REPO_NAME));
+    public MavenArtifactRepository mavenLocal() {
+        return addRepository(getResolverFactory().createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME);
     }
 
-    public DependencyResolver mavenRepo(Map args) {
+    public DependencyResolver mavenRepo(Map<String, ?> args) {
         return mavenRepo(args, null);
     }
 
-    public DependencyResolver mavenRepo(Map args, Closure configClosure) {
-        List<String> urls = createStringifiedListFromMapArg(args, "urls");
-        if (urls == null) {
-            throw new InvalidUserDataException("You must specify a urls for a Maven repo.");
+    public DependencyResolver mavenRepo(Map<String, ?> args, Closure configClosure) {
+        Map<String, Object> modifiedArgs = new HashMap<String, Object>(args);
+        if (modifiedArgs.containsKey("urls")) {
+            List<Object> urls = toList(modifiedArgs.remove("urls"));
+            if (!urls.isEmpty()) {
+                DeprecationLogger.nagUserWith("The 'urls' property of the RepositoryHandler.mavenRepo() method is deprecated and will be removed in a future version of Gradle. "
+                        + "You should use the 'url' property to define the core maven repository & the 'artifactUrls' property to define any additional artifact locations.");
+                modifiedArgs.put("url", urls.get(0));
+                List<Object> extraUrls = urls.subList(1, urls.size());
+                modifiedArgs.put("artifactUrls", extraUrls);
+            }
         }
-        List<String> extraUrls = urls.subList(1, urls.size());
-        AbstractResolver resolver = getResolverFactory().createMavenRepoResolver(
-                getNameFromMap(args, urls.get(0)),
-                urls.get(0),
-                urls.size() == 1 ? new String[0] : extraUrls.toArray(new String[extraUrls.size()]));
-        return add(resolver, configClosure);
-    }
-
-    public GroovyMavenDeployer mavenDeployer(Map args) {
-        GroovyMavenDeployer mavenDeployer = createMavenDeployer(args);
-        return (GroovyMavenDeployer) add(mavenDeployer);
-    }
-
-    private GroovyMavenDeployer createMavenDeployer(Map args) {
-        GroovyMavenDeployer mavenDeployer = createMavenDeployer("dummyName");
-        String defaultName = RepositoryHandler.DEFAULT_MAVEN_DEPLOYER_NAME + "-" + System.identityHashCode(
-                mavenDeployer);
-        mavenDeployer.setName(getNameFromMap(args, defaultName));
-        return mavenDeployer;
-    }
-
-    public GroovyMavenDeployer mavenDeployer() {
-        return mavenDeployer(Collections.emptyMap());
-    }
 
-    public GroovyMavenDeployer mavenDeployer(Closure configureClosure) {
-        return mavenDeployer(Collections.emptyMap(), configureClosure);
+        MavenArtifactRepository repository = getResolverFactory().createMavenRepository();
+        ConfigureUtil.configureByMap(modifiedArgs, repository);
+        DependencyResolver resolver = toResolver(DependencyResolver.class, repository);
+        ConfigureUtil.configure(configClosure, resolver);
+        addRepository(new FixedResolverArtifactRepository(resolver), "maven");
+        return resolver;
     }
 
-    public GroovyMavenDeployer mavenDeployer(Map args, Closure configureClosure) {
-        GroovyMavenDeployer mavenDeployer = createMavenDeployer(args);
-        return (GroovyMavenDeployer) add(mavenDeployer, configureClosure);
-    }
-
-    public MavenResolver mavenInstaller() {
-        return mavenInstaller(Collections.emptyMap());
-    }
-
-    public MavenResolver mavenInstaller(Closure configureClosure) {
-        return mavenInstaller(Collections.emptyMap(), configureClosure);
-    }
-
-    public MavenResolver mavenInstaller(Map args) {
-        MavenResolver mavenInstaller = createMavenInstaller(args);
-        return (MavenResolver) add(mavenInstaller);
+    private List<Object> toList(Object object) {
+        if (object instanceof List) {
+            return (List<Object>) object;
+        }
+        if (object instanceof Iterable) {
+            return Lists.newArrayList((Iterable<Object>) object);
+        }
+        return Collections.singletonList(object);
     }
 
-    public MavenResolver mavenInstaller(Map args, Closure configureClosure) {
-        MavenResolver mavenInstaller = createMavenInstaller(args);
-        return (MavenResolver) add(mavenInstaller, configureClosure);
+    public MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action) {
+        return addRepository(getResolverFactory().createMavenRepository(), action, "maven");
     }
 
-    private MavenResolver createMavenInstaller(Map args) {
-        MavenResolver mavenInstaller = createMavenInstaller("dummyName");
-        String defaultName = RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME + "-" + System.identityHashCode(
-                mavenInstaller);
-        mavenInstaller.setName(getNameFromMap(args, defaultName));
-        return mavenInstaller;
+    public MavenArtifactRepository maven(Closure closure) {
+        return addRepository(getResolverFactory().createMavenRepository(), closure, "maven");
     }
 
     public IvyArtifactRepository ivy(Action<? super IvyArtifactRepository> action) {
-        IvyArtifactRepository repository = getResolverFactory().createIvyRepository();
-        addRepository(repository, action);
-        return repository;
+        return addRepository(getResolverFactory().createIvyRepository(), action, "ivy");
     }
 
     public IvyArtifactRepository ivy(Closure closure) {
-        IvyArtifactRepository repository = getResolverFactory().createIvyRepository();
-        addRepository((ArtifactRepositoryInternal) repository, closure);
-        return repository;
-    }
-
-    private <T extends ArtifactRepository> void addRepository(T repository, Action<? super T> action) {
-        action.execute(repository);
-        addRepository((ArtifactRepositoryInternal) repository);
+        return addRepository(getResolverFactory().createIvyRepository(), closure, "ivy");
     }
 
-    private void addRepository(ArtifactRepositoryInternal repository, Closure closure) {
-        ConfigureUtil.configure(closure, repository);
-        addRepository(repository);
-    }
-
-    private void addRepository(ArtifactRepositoryInternal repository) {
-        List<DependencyResolver> resolvers = new ArrayList<DependencyResolver>();
-        repository.createResolvers(resolvers);
-        for (DependencyResolver resolver : resolvers) {
-            add(resolver);
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactory.java
deleted file mode 100644
index a98ead0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactory.java
+++ /dev/null
@@ -1,38 +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.dsl;
-
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultRepositoryHandlerFactory implements Factory<RepositoryHandler> {
-    private final ResolverFactory repositoryFactory;
-    private final ClassGenerator classGenerator;
-
-    public DefaultRepositoryHandlerFactory(ResolverFactory repositoryFactory, ClassGenerator classGenerator) {
-        this.repositoryFactory = repositoryFactory;
-        this.classGenerator = classGenerator;
-    }
-
-    public DefaultRepositoryHandler create() {
-        return classGenerator.newInstance(DefaultRepositoryHandler.class, repositoryFactory, classGenerator);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactFactory.java
deleted file mode 100644
index da0ecfb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactFactory.java
+++ /dev/null
@@ -1,25 +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.dsl;
-
-import org.gradle.api.artifacts.PublishArtifact;
-
-/**
- * @author Hans Dockter
- */
-public interface PublishArtifactFactory {
-    PublishArtifact createArtifact(Object notation);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/SharedConventionRepositoryHandlerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/SharedConventionRepositoryHandlerFactory.java
deleted file mode 100644
index a19f08b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/SharedConventionRepositoryHandlerFactory.java
+++ /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.api.internal.artifacts.dsl;
-
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.ConventionMapping;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.IConventionAware;
-import org.gradle.api.plugins.Convention;
-
-public class SharedConventionRepositoryHandlerFactory implements Factory<RepositoryHandler> {
-    private final Factory<RepositoryHandler> factory;
-    private final Convention convention;
-    private ConventionMapping conventionMapping;
-
-    public SharedConventionRepositoryHandlerFactory(Factory<RepositoryHandler> factory, Convention convention) {
-        this.factory = factory;
-        this.convention = convention;
-    }
-
-    public RepositoryHandler create() {
-        RepositoryHandler repositoryHandler = factory.create();
-        IConventionAware conventionAwareHandler = (IConventionAware) repositoryHandler;
-        if (conventionMapping == null) {
-            conventionMapping = conventionAwareHandler.getConventionMapping();
-            conventionMapping.setConvention(convention);
-        } else {
-            conventionAwareHandler.setConventionMapping(conventionMapping);
-        }
-        return repositoryHandler;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactory.java
deleted file mode 100644
index 6fcb667..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactory.java
+++ /dev/null
@@ -1,49 +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.api.internal.artifacts.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
-import org.gradle.api.internal.file.FileResolver;
-
-public class ClassPathDependencyFactory implements IDependencyImplementationFactory {
-    private final ClassPathRegistry classPathRegistry;
-    private final ClassGenerator classGenerator;
-    private final FileResolver fileResolver;
-
-    public ClassPathDependencyFactory(ClassGenerator classGenerator, ClassPathRegistry classPathRegistry,
-                                      FileResolver fileResolver) {
-        this.classGenerator = classGenerator;
-        this.classPathRegistry = classPathRegistry;
-        this.fileResolver = fileResolver;
-    }
-
-    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription)
-            throws IllegalDependencyNotation {
-        if (userDependencyDescription instanceof DependencyFactory.ClassPathNotation) {
-            DependencyFactory.ClassPathNotation classPathNotation
-                    = (DependencyFactory.ClassPathNotation) userDependencyDescription;
-            FileCollection files = fileResolver.resolveFiles(classPathRegistry.getClassPathFiles(classPathNotation.name()));
-            return type.cast(classGenerator.newInstance(DefaultSelfResolvingDependency.class, files));
-        }
-        
-        throw new IllegalDependencyNotation();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactory.java
deleted file mode 100644
index 1c66426..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactory.java
+++ /dev/null
@@ -1,55 +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.dsl.dependencies;
-
-import groovy.lang.GString;
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
-
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultClientModuleFactory implements IDependencyImplementationFactory {
-    private final MapModuleNotationParser mapNotationParser;
-    private final ClassGenerator classGenerator;
-
-    public DefaultClientModuleFactory(ClassGenerator classGenerator) {
-        this.classGenerator = classGenerator;
-        mapNotationParser = new MapModuleNotationParser(classGenerator);
-    }
-
-    public <T extends Dependency> T createDependency(Class<T> type, Object notation) throws IllegalDependencyNotation {
-        assert notation != null;
-        if (notation instanceof String || notation instanceof GString) {
-            return type.cast(createDependencyFromString(notation.toString()));
-        } else if (notation instanceof Map) {
-            return type.cast(mapNotationParser.createDependency(DefaultClientModule.class, notation));
-        }
-        throw new IllegalDependencyNotation();
-    }
-
-    private DefaultClientModule createDependencyFromString(String notation) {
-        ParsedModuleStringNotation parsedNotation = new ParsedModuleStringNotation(notation, null);
-        DefaultClientModule clientModule = classGenerator.newInstance(DefaultClientModule.class,
-                parsedNotation.getGroup(), parsedNotation.getName(), parsedNotation.getVersion());
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(clientModule, null, parsedNotation.getClassifier());
-        return clientModule;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactory.java
deleted file mode 100644
index 838be15..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactory.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.api.internal.artifacts.dsl.dependencies;
-
-import groovy.lang.Closure;
-import org.gradle.api.GradleException;
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ProjectDependency;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultDependencyFactory implements DependencyFactory {
-    private Set<IDependencyImplementationFactory> dependencyFactories;
-    private IDependencyImplementationFactory clientModuleFactory;
-    private ProjectDependencyFactory projectDependencyFactory;
-
-    public DefaultDependencyFactory(Set<IDependencyImplementationFactory> dependencyFactories,
-                                    IDependencyImplementationFactory clientModuleFactory,
-                                    ProjectDependencyFactory projectDependencyFactory) {
-        this.dependencyFactories = dependencyFactories;
-        this.clientModuleFactory = clientModuleFactory;
-        this.projectDependencyFactory = projectDependencyFactory;
-    }
-
-    public Dependency createDependency(Object dependencyNotation) {
-        if (dependencyNotation instanceof Dependency) {
-            return (Dependency) dependencyNotation;
-        }
-        
-        Dependency dependency = null;
-        for (IDependencyImplementationFactory factory : dependencyFactories) {
-            try {
-                dependency = factory.createDependency(Dependency.class, dependencyNotation);
-                break;
-            } catch (IllegalDependencyNotation e) {
-                // ignore
-            } catch (Exception e) {
-                throw new GradleException(String.format("Could not create a dependency using notation: %s", dependencyNotation), e);
-            }
-        }
-
-        if (dependency == null) {
-            throw new InvalidUserDataException(String.format("The dependency notation: %s is invalid.",
-                    dependencyNotation));
-        }
-        return dependency;
-    }
-
-    public ClientModule createModule(Object dependencyNotation, Closure configureClosure) {
-        ClientModule clientModule = clientModuleFactory.createDependency(ClientModule.class, dependencyNotation);
-        ModuleFactoryDelegate moduleFactoryDelegate = new ModuleFactoryDelegate(clientModule, this);
-        moduleFactoryDelegate.prepareDelegation(configureClosure);
-        if (configureClosure != null) {
-            configureClosure.call();
-        }
-        return clientModule;
-    }
-
-    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder, Map<? extends String, ? extends Object> map) {
-        return projectDependencyFactory.createProjectDependencyFromMap(projectFinder, map);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
index 4ca079e..9cb99db 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
@@ -33,27 +33,40 @@ class DefaultDependencyHandler implements DependencyHandler {
 
     def DefaultDependencyHandler(ConfigurationContainer configurationContainer, DependencyFactory dependencyFactory,
                                  ProjectFinder projectFinder) {
-        this.configurationContainer = configurationContainer;
-        this.dependencyFactory = dependencyFactory;
-        this.projectFinder = projectFinder;
+        this.configurationContainer = configurationContainer
+        this.dependencyFactory = dependencyFactory
+        this.projectFinder = projectFinder
     }
 
     public Dependency add(String configurationName, Object dependencyNotation) {
-        pushDependency(configurationContainer[configurationName], dependencyNotation, null)
+        doAdd(configurationContainer[configurationName], dependencyNotation, null)
     }
 
     public Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure) {
-        pushDependency(configurationContainer[configurationName], dependencyNotation, configureClosure)
+        doAdd(configurationContainer[configurationName], dependencyNotation, configureClosure)
     }
 
-    private Dependency pushDependency(org.gradle.api.artifacts.Configuration configuration, Object notation, Closure configureClosure) {
-        Dependency dependency
-        dependency = dependencyFactory.createDependency(notation)
-        configuration.addDependency(dependency)
+    Dependency create(Object dependencyNotation, Closure configureClosure = null) {
+        def dependency = dependencyFactory.createDependency(dependencyNotation)
         ConfigureUtil.configure(configureClosure, dependency)
         dependency
     }
 
+    private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) {
+        if (dependencyNotation instanceof Configuration) {
+            Configuration other = (Configuration) dependencyNotation;
+            if (!configurationContainer.contains(other)) {
+                throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.")
+            }
+            configuration.extendsFrom(other)
+            return
+        }
+
+        def dependency = create(dependencyNotation, configureClosure)
+        configuration.dependencies << dependency
+        dependency
+    }
+
     public Dependency module(Object notation) {
         module(notation, null)
     }
@@ -74,7 +87,7 @@ class DefaultDependencyHandler implements DependencyHandler {
         return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY);
     }
 
-    public def methodMissing(String name, args) {
+    public Object methodMissing(String name, Object args) {
         Configuration configuration = configurationContainer.findByName(name)
         if (configuration == null) {
             if (!getMetaClass().respondsTo(this, name, args.size())) {
@@ -82,15 +95,15 @@ class DefaultDependencyHandler implements DependencyHandler {
             }
         }
 
-        Object[] normalizedArgs = GUtil.flatten(args as List, false)
+        Object[] normalizedArgs = GUtil.collectionize(args)
         if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
-            return pushDependency(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
+            return doAdd(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
         } else if (normalizedArgs.length == 1) {
-            return pushDependency(configuration, normalizedArgs[0], (Closure) null)
+            return doAdd(configuration, normalizedArgs[0], (Closure) null)
         }
         normalizedArgs.each {notation ->
-            pushDependency(configuration, notation, null)
+            doAdd(configuration, notation, null)
         }
         return null;
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactory.java
deleted file mode 100644
index 5fbfae2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactory.java
+++ /dev/null
@@ -1,64 +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.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
-import org.gradle.util.ConfigureUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectDependencyFactory implements ProjectDependencyFactory {
-    private final ProjectDependenciesBuildInstruction instruction;
-    private final ClassGenerator classGenerator;
-
-    public DefaultProjectDependencyFactory(ProjectDependenciesBuildInstruction instruction, ClassGenerator classGenerator) {
-        this.instruction = instruction;
-        this.classGenerator = classGenerator;
-    }
-
-    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription) throws IllegalDependencyNotation {
-        if (userDependencyDescription instanceof Project) {
-            return type.cast(classGenerator.newInstance(DefaultProjectDependency.class, userDependencyDescription, instruction));
-        }
-        throw new IllegalDependencyNotation();
-    }
-    
-    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder,
-                                                   Map<? extends String, ? extends Object> map) {
-        Map<String, Object> args = new HashMap<String, Object>(map);
-        String path = getAndRemove(args, "path");
-        String configuration = getAndRemove(args, "configuration");
-        ProjectDependency dependency = classGenerator.newInstance(DefaultProjectDependency.class, projectFinder.getProject(path), configuration, instruction);
-        ConfigureUtil.configureByMap(args, dependency);
-        return dependency;
-    }
-
-    private String getAndRemove(Map<String, Object> args, String key) {
-        Object value = args.get(key);
-        args.remove(key);
-        return value != null ? value.toString() : null;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
index 277d0b8..890c4ec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
@@ -26,6 +26,7 @@ import java.util.Map;
  * @author Hans Dockter
  */
 public interface DependencyFactory {
+    //for gradle distribution specific dependencies
     enum ClassPathNotation {
         GRADLE_API, LOCAL_GROOVY
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/IDependencyImplementationFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/IDependencyImplementationFactory.java
deleted file mode 100644
index e1d7325..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/IDependencyImplementationFactory.java
+++ /dev/null
@@ -1,26 +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.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.artifacts.Dependency;
-
-/**
- * @author Hans Dockter
- */
-public interface IDependencyImplementationFactory {
-    <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription) throws IllegalDependencyNotation;
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/MapModuleNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/MapModuleNotationParser.java
deleted file mode 100644
index e0d08c6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/MapModuleNotationParser.java
+++ /dev/null
@@ -1,56 +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.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.util.ConfigureUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-class MapModuleNotationParser implements IDependencyImplementationFactory {
-    private final ClassGenerator classGenerator;
-
-    MapModuleNotationParser(ClassGenerator classGenerator) {
-        this.classGenerator = classGenerator;
-    }
-
-    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription)
-            throws IllegalDependencyNotation {
-        Map<String, Object> args = new HashMap<String, Object>((Map<String, ?>) userDependencyDescription);
-        String group = getAndRemove(args, "group");
-        String name = getAndRemove(args, "name");
-        String version = getAndRemove(args, "version");
-        String configuration = getAndRemove(args, "configuration");
-        ExternalDependency dependency = (ExternalDependency) classGenerator.newInstance(type, group, name, version, configuration);
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency, getAndRemove(args, "ext"), getAndRemove(args,
-                "classifier"));
-        ConfigureUtil.configureByMap(args, dependency);
-        return type.cast(dependency);
-    }
-
-    private String getAndRemove(Map<String, Object> args, String key) {
-        Object value = args.get(key);
-        args.remove(key);
-        return value != null ? value.toString() : null;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactory.java
deleted file mode 100644
index da6f4a7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactory.java
+++ /dev/null
@@ -1,74 +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.dsl.dependencies;
-
-import groovy.lang.GString;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class ModuleDependencyFactory implements IDependencyImplementationFactory {
-    private final MapModuleNotationParser mapNotationParser;
-    private final ClassGenerator classGenerator;
-
-    public ModuleDependencyFactory(ClassGenerator classGenerator) {
-        this.classGenerator = classGenerator;
-        mapNotationParser = new MapModuleNotationParser(classGenerator);
-    }
-
-    public <T extends Dependency> T createDependency(Class<T> type, Object notation) throws IllegalDependencyNotation {
-        assert notation != null;
-        if (notation instanceof String || notation instanceof GString) {
-            return type.cast(createDependencyFromString(notation.toString()));
-        } else if (notation instanceof Map) {
-            return type.cast(mapNotationParser.createDependency(DefaultExternalModuleDependency.class, notation));
-        }
-        throw new IllegalDependencyNotation();
-    }
-
-    private static final Pattern EXTENSION_SPLITTER = Pattern.compile("^(.+)\\@([^:]+$)");
-
-    private DefaultExternalModuleDependency createDependencyFromString(String notation) {
-        ParsedModuleStringNotation parsedNotation = splitDescriptionIntoModuleNotationAndArtifactType(notation);
-        DefaultExternalModuleDependency moduleDependency = classGenerator.newInstance(
-                DefaultExternalModuleDependency.class, parsedNotation.getGroup(), parsedNotation.getName(),
-                parsedNotation.getVersion());
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(moduleDependency, parsedNotation.getArtifactType(),
-                parsedNotation.getClassifier());
-        return moduleDependency;
-    }
-
-    private ParsedModuleStringNotation splitDescriptionIntoModuleNotationAndArtifactType(String notation) {
-        Matcher matcher = EXTENSION_SPLITTER.matcher(notation);
-        boolean hasArtifactType = matcher.matches();
-        if (hasArtifactType) {
-            if (matcher.groupCount() != 2) {
-                throw new InvalidUserDataException("The description " + notation + " is invalid");
-            }
-            return new ParsedModuleStringNotation(matcher.group(1), matcher.group(2));
-        }
-        return new ParsedModuleStringNotation(notation, null);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy
index 4ea3563..592e4bf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy
@@ -31,6 +31,7 @@ class ModuleFactoryDelegate {
     this.dependencyFactory = dependencyFactory
   }
 
+  //TODO SF - this method does not make sense to me. Why not ConfigureUtil.configure?
   void prepareDelegation(Closure configureClosure) {
     if (!configureClosure) {
         return
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
index 850b1ff..88b19bf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
@@ -21,14 +21,14 @@ import org.gradle.util.GUtil;
 /**
  * @author Hans Dockter
  */
-class ParsedModuleStringNotation {
+public class ParsedModuleStringNotation {
     private String group;
     private String name;
     private String version;
     private String classifier;
     private String artifactType;
 
-    ParsedModuleStringNotation(String moduleNotation, String artifactType) {
+    public ParsedModuleStringNotation(String moduleNotation, String artifactType) {
         assignValuesFromModuleNotation(moduleNotation);
         this.artifactType = artifactType;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectDependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectDependencyFactory.java
deleted file mode 100644
index be2b09e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectDependencyFactory.java
+++ /dev/null
@@ -1,28 +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.dsl.dependencies;
-
-import org.gradle.api.artifacts.ProjectDependency;
-
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public interface ProjectDependencyFactory extends IDependencyImplementationFactory {
-    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder,
-                                                            Map<? extends String, ? extends Object> map);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
index 65f8b80..12f736c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.dsl.dependencies;
 
-import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
 
 /**
  * @author Hans Dockter
@@ -26,5 +26,5 @@ public interface ProjectFinder {
      * @param path Can be relative or absolute
      * @return The project belonging to the path
      */
-    Project getProject(String path);
+    ProjectInternal getProject(String path);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactory.java
deleted file mode 100644
index d18f5b7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactory.java
+++ /dev/null
@@ -1,40 +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.dsl.dependencies;
-
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
-
-public class SelfResolvingDependencyFactory implements IDependencyImplementationFactory {
-    private final ClassGenerator classGenerator;
-
-    public SelfResolvingDependencyFactory(ClassGenerator classGenerator) {
-        this.classGenerator = classGenerator;
-    }
-
-    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription)
-            throws IllegalDependencyNotation {
-        if (!(userDependencyDescription instanceof FileCollection)) {
-            throw new IllegalDependencyNotation();
-        }
-
-        FileCollection fileCollection = (FileCollection) userDependencyDescription;
-        return type.cast(classGenerator.newInstance(DefaultSelfResolvingDependency.class, fileCollection));
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolver.java
deleted file mode 100644
index 2d78011..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolver.java
+++ /dev/null
@@ -1,94 +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.ivyservice;
-
-import org.apache.ivy.core.IvyContext;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.resolver.BasicResolver;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.gradle.api.artifacts.ClientModule;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class ClientModuleResolver extends BasicResolver {
-    private Map<String, ModuleDescriptor> moduleRegistry;
-    private DependencyResolver userResolver;
-
-    public ClientModuleResolver(String name, Map<String, ModuleDescriptor> moduleRegistry, DependencyResolver userResolver) {
-        setName(name);
-        this.moduleRegistry = moduleRegistry;
-        this.userResolver = userResolver;
-        setRepositoryCacheManager(new NoOpRepositoryCacheManager(name));
-    }
-
-    public ResolvedModuleRevision getDependency(DependencyDescriptor dde, ResolveData data) {
-        if (dde.getExtraAttribute(ClientModule.CLIENT_MODULE_KEY) == null) {
-            return null;
-        }
-
-        IvyContext context = IvyContext.pushNewCopyContext();
-        try {
-            context.setDependencyDescriptor(dde);
-            context.setResolveData(data);
-            ModuleDescriptor moduleDescriptor = moduleRegistry.get(dde.getExtraAttribute(ClientModule.CLIENT_MODULE_KEY));
-            MetadataArtifactDownloadReport downloadReport = new MetadataArtifactDownloadReport(moduleDescriptor.getMetadataArtifact());
-            downloadReport.setDownloadStatus(DownloadStatus.NO);
-            downloadReport.setSearched(false);
-            return new ResolvedModuleRevision(userResolver, userResolver, moduleDescriptor, downloadReport);
-        } finally {
-            IvyContext.popContext();
-        }
-    }
-
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
-        return null;
-    }
-
-    protected Collection findNames(Map tokenValues, String token) {
-        return null;
-    }
-
-    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
-        return null;
-    }
-
-    protected long get(Resource resource, File dest) {
-        return resource.getContentLength();
-    }
-
-    protected Resource getResource(String s) {
-        return null;
-    }
-
-    public void publish(Artifact artifact, File src, boolean overwrite) {
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyConversionResult.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyConversionResult.java
deleted file mode 100644
index 8eec7f7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyConversionResult.java
+++ /dev/null
@@ -1,50 +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.ivyservice;
-
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyConversionResult implements IvyConversionResult {
-    private final ResolvedDependency root;
-    private final Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies;
-    private final Set<ResolvedArtifact> resolvedArtifacts;
-
-    public DefaultIvyConversionResult(ResolvedDependency root, Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies, Set<ResolvedArtifact> resolvedArtifacts) {
-        this.root = root;
-        this.firstLevelResolvedDependencies = firstLevelResolvedDependencies;
-        this.resolvedArtifacts = resolvedArtifacts;
-    }
-
-    public ResolvedDependency getRoot() {
-        return root;
-    }
-
-    public Map<Dependency, Set<ResolvedDependency>> getFirstLevelResolvedDependencies() {
-        return firstLevelResolvedDependencies;
-    }
-
-    public Set<ResolvedArtifact> getResolvedArtifacts() {
-        return resolvedArtifacts;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolver.java
deleted file mode 100644
index cf9cc40..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolver.java
+++ /dev/null
@@ -1,162 +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.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.report.ResolveReport;
-import org.apache.ivy.core.resolve.ResolveOptions;
-import org.apache.ivy.util.Message;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.CachingDirectedGraphWalker;
-import org.gradle.api.internal.DirectedGraphWithEdgeValues;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.util.Clock;
-import org.gradle.util.WrapUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyDependencyResolver implements IvyDependencyResolver {
-    private static Logger logger = LoggerFactory.getLogger(DefaultIvyDependencyResolver.class);
-
-    private IvyReportConverter ivyReportTranslator;
-
-    public DefaultIvyDependencyResolver(IvyReportConverter ivyReportTranslator) {
-        this.ivyReportTranslator = ivyReportTranslator;
-        Message.setDefaultLogger(new IvyLoggingAdaper());
-    }
-
-    public ResolvedConfiguration resolve(Configuration configuration, Ivy ivy, ModuleDescriptor moduleDescriptor) {
-        Clock clock = new Clock();
-        ResolveOptions resolveOptions = createResolveOptions(configuration);
-        ResolveReport resolveReport;
-        try {
-            resolveReport = ivy.resolve(moduleDescriptor, resolveOptions);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        logger.debug("Timing: Ivy resolve took {}", clock.getTime());
-        return new ResolvedConfigurationImpl(resolveReport, configuration);
-    }
-
-    private ResolveOptions createResolveOptions(Configuration configuration) {
-        ResolveOptions resolveOptions = new ResolveOptions();
-        resolveOptions.setDownload(false);
-        resolveOptions.setConfs(WrapUtil.toArray(configuration.getName()));
-        return resolveOptions;
-    }
-
-    class ResolvedConfigurationImpl implements ResolvedConfiguration {
-        private final Configuration configuration;
-        private boolean hasError;
-        private List<String> problemMessages;
-        private IvyConversionResult conversionResult;
-        private final CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact> walker
-                = new CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact>(new ResolvedDependencyArtifactsGraph());
-
-        public ResolvedConfigurationImpl(ResolveReport resolveReport, Configuration configuration) {
-            this.hasError = resolveReport.hasError();
-            if (this.hasError) {
-                this.problemMessages = resolveReport.getAllProblemMessages();
-            } else {
-                 this.conversionResult = ivyReportTranslator.convertReport(
-                    resolveReport,
-                    configuration);
-            }
-            this.configuration = configuration;
-        }
-
-        public boolean hasError() {
-            return hasError;
-        }
-
-        public void rethrowFailure() throws ResolveException {
-            if (hasError) {
-                Formatter formatter = new Formatter();
-                for (String msg : problemMessages) {
-                    formatter.format("    - %s%n", msg);
-                }
-                throw new ResolveException(configuration, formatter.toString());
-            }
-        }
-
-        public Set<File> getFiles(Spec<Dependency> dependencySpec) {
-            Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
-
-            for (ResolvedDependency resolvedDependency : getFirstLevelModuleDependencies(dependencySpec)) {
-                artifacts.addAll(resolvedDependency.getParentArtifacts(conversionResult.getRoot()));
-                walker.add(resolvedDependency);
-            }
-
-            artifacts.addAll(walker.findValues());
-
-            Set<File> files = new LinkedHashSet<File>();
-            for (ResolvedArtifact artifact : artifacts) {
-                File depFile = artifact.getFile();
-                if (depFile != null) {
-                    files.add(depFile);
-                } else {
-                    logger.debug(String.format("Resolved artifact %s contains a null value.", artifact));
-                }
-            }
-            return files;
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
-            rethrowFailure();
-            return conversionResult.getRoot().getChildren();
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<Dependency> dependencySpec) {
-            rethrowFailure();
-            Set<ModuleDependency> allDependencies = configuration.getAllDependencies(ModuleDependency.class);
-            Set<ModuleDependency> selectedDependencies = Specs.filterIterable(allDependencies, dependencySpec);
-
-            Set<ResolvedDependency> result = new LinkedHashSet<ResolvedDependency>();
-            for (ModuleDependency moduleDependency : selectedDependencies) {
-                result.addAll(conversionResult.getFirstLevelResolvedDependencies().get(moduleDependency));
-            }
-
-            return result;
-        }
-
-        public Set<ResolvedArtifact> getResolvedArtifacts() {
-            rethrowFailure();
-            return conversionResult.getResolvedArtifacts();
-        }
-
-        private class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
-            public void getNodeValues(ResolvedDependency node, Collection<ResolvedArtifact> values,
-                                      Collection<ResolvedDependency> connectedNodes) {
-                values.addAll(node.getModuleArtifacts());
-                connectedNodes.addAll(node.getChildren());
-            }
-
-            public void getEdgeValues(ResolvedDependency from, ResolvedDependency to,
-                                      Collection<ResolvedArtifact> values) {
-                values.addAll(to.getParentArtifacts(from));
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
deleted file mode 100644
index ddafc8b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
+++ /dev/null
@@ -1,28 +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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.settings.IvySettings;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyFactory implements IvyFactory {
-    public Ivy createIvy(IvySettings ivySettings) {
-        return Ivy.newInstance(ivySettings);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyReportConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyReportConverter.java
deleted file mode 100644
index e39534e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyReportConverter.java
+++ /dev/null
@@ -1,329 +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.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.report.ConfigurationResolveReport;
-import org.apache.ivy.core.report.ResolveReport;
-import org.apache.ivy.core.resolve.IvyNode;
-import org.apache.ivy.core.resolve.IvyNodeCallers;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
-import org.gradle.api.internal.artifacts.DefaultResolvedDependency;
-import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
-import org.gradle.util.Clock;
-import org.gradle.util.WrapUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyReportConverter implements IvyReportConverter {
-    private static Logger logger = LoggerFactory.getLogger(DefaultIvyReportConverter.class);
-
-    private DependencyDescriptorFactory dependencyDescriptorFactory;
-
-    public DefaultIvyReportConverter(DependencyDescriptorFactory dependencyDescriptorFactory) {
-        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
-    }
-
-    public IvyConversionResult convertReport(ResolveReport resolveReport, Configuration configuration) {
-        Clock clock = new Clock();
-        ReportConversionContext context = new ReportConversionContext(resolveReport, configuration);
-        List<IvyNode> resolvedNodes = findResolvedNodes(resolveReport, context);
-        for (IvyNode node : resolvedNodes) {
-            constructConfigurationsForNode(node, context);
-        }
-        for (IvyNode node : resolvedNodes) {
-            attachToParents(node, context);
-        }
-
-        if (context.root == null) {
-            context.root = new DefaultResolvedDependency(resolveReport.getModuleDescriptor().getModuleRevisionId().getOrganisation(),
-                    resolveReport.getModuleDescriptor().getModuleRevisionId().getName(),
-                    resolveReport.getModuleDescriptor().getModuleRevisionId().getRevision(), configuration.getName(),
-                    Collections.EMPTY_SET);
-        }
-
-        logger.debug("Timing: Translating report for configuration {} took {}", configuration, clock.getTime());
-        return new DefaultIvyConversionResult(context.root, context.firstLevelResolvedDependencies, context.resolvedArtifacts);
-    }
-
-    private List<IvyNode> findResolvedNodes(ResolveReport resolveReport, ReportConversionContext context) {
-        List<IvyNode> nodes = resolveReport.getDependencies();
-        List<IvyNode> resolvedNodes = new ArrayList<IvyNode>();
-        for (IvyNode node : nodes) {
-            if (!isResolvedNode(node, context.conf)) {
-                continue;
-            }
-            resolvedNodes.add(node);
-        }
-        if (!resolvedNodes.isEmpty()) {
-            resolvedNodes.add(resolvedNodes.get(0).getRoot());
-        }
-        return resolvedNodes;
-    }
-
-    private boolean isResolvedNode(IvyNode node, String configuration) {
-        return node.isLoaded() && !node.isEvicted(configuration);
-    }
-
-    private void attachToParents(IvyNode ivyNode, ReportConversionContext context) {
-        Map<String, ConfigurationDetails> resolvedDependencies = context.handledNodes.get(ivyNode.getId());
-        for (IvyNodeCallers.Caller caller : ivyNode.getCallers(context.conf)) {
-            Set<String> dependencyConfigurationsForNode = getDependencyConfigurationsByCaller(ivyNode, caller);
-            IvyNode parentNode = isRootCaller(context.configurationResolveReport, caller) ? ivyNode.getRoot() : context.configurationResolveReport.getDependency(caller.getModuleRevisionId());
-            if (!isResolvedNode(parentNode, context.conf)) {
-                continue;
-            }
-            Map<String, ConfigurationDetails> parentResolvedDependencies = context.handledNodes.get(parentNode.getId());
-            if (parentResolvedDependencies == null) {
-                throw new IllegalStateException(String.format("Could not find caller node %s for node %s. Available nodes: %s",
-                        parentNode.getId(), ivyNode.getId(), context.handledNodes.keySet()));
-            }
-            createAssociationsBetweenChildAndParentResolvedDependencies(ivyNode, resolvedDependencies, context.resolvedArtifacts, parentNode, caller,
-                    dependencyConfigurationsForNode, parentResolvedDependencies.values());
-        }
-    }
-
-    private void constructConfigurationsForNode(IvyNode ivyNode, ReportConversionContext context) {
-        Map<String, ConfigurationDetails> resolvedDependencies = new LinkedHashMap<String, ConfigurationDetails>();
-        for (IvyNodeCallers.Caller caller : ivyNode.getCallers(context.conf)) {
-            Set<String> dependencyConfigurationsForNode = getDependencyConfigurationsByCaller(ivyNode, caller);
-            for (String dependencyConfiguration : dependencyConfigurationsForNode) {
-                if (!resolvedDependencies.containsKey(dependencyConfiguration)) {
-                    ConfigurationDetails configurationDetails = context.addConfiguration(ivyNode, dependencyConfiguration);
-                    context.resolvedArtifacts.addAll(configurationDetails.dependency.getModuleArtifacts());
-                    resolvedDependencies.put(dependencyConfiguration, configurationDetails);
-                }
-            }
-        }
-        if (ivyNode == ivyNode.getRoot()) {
-            ConfigurationDetails rootConfiguration = resolvedDependencies.get(context.conf);
-            if (rootConfiguration == null) {
-                rootConfiguration = context.addConfiguration(ivyNode, context.conf);
-                resolvedDependencies.put(context.conf, rootConfiguration);
-            }
-            context.root = rootConfiguration.dependency;
-        }
-        context.handledNodes.put(ivyNode.getId(), resolvedDependencies);
-    }
-
-    private void createAssociationsBetweenChildAndParentResolvedDependencies(IvyNode childNode, Map<String, ConfigurationDetails> childConfigurations,
-                                                                             Set<ResolvedArtifact> resolvedArtifacts,
-                                                                             IvyNode parentNode, IvyNodeCallers.Caller caller,
-                                                                             Set<String> childConfigurationsToAttach,
-                                                                             Collection<ConfigurationDetails> parentConfigurations) {
-        for (String dependencyConfiguration : childConfigurationsToAttach) {
-            Set<String> callerConfigurations = getCallerConfigurationsByDependencyConfiguration(caller, childNode, dependencyConfiguration);
-            Set<ConfigurationDetails> parentCallerConfigurations = selectParentConfigurations(parentConfigurations,
-                    callerConfigurations);
-            for (ConfigurationDetails parentConfiguration : parentCallerConfigurations) {
-                ConfigurationDetails childConfiguration = childConfigurations.get(dependencyConfiguration);
-                parentConfiguration.dependency.getChildren().add(childConfiguration.dependency);
-                childConfiguration.dependency.getParents().add(parentConfiguration.dependency);
-                Set<ResolvedArtifact> parentSpecificResolvedArtifacts = getParentSpecificArtifacts(childConfiguration.dependency, parentConfiguration.dependency.getConfiguration(),
-                        parentNode, caller, childNode);
-                childConfiguration.dependency.addParentSpecificArtifacts(parentConfiguration.dependency, parentSpecificResolvedArtifacts);
-                resolvedArtifacts.addAll(parentSpecificResolvedArtifacts);
-            }
-        }
-    }
-
-    private Set<ResolvedArtifact> getParentSpecificArtifacts(DefaultResolvedDependency resolvedDependency, String parentConfiguration, IvyNode callerNode,
-                                                             IvyNodeCallers.Caller caller, IvyNode childNode) {
-        Set<String> parentConfigurations = getConfigurationHierarchy(callerNode, parentConfiguration);
-        Set<DependencyArtifactDescriptor> parentArtifacts = new LinkedHashSet<DependencyArtifactDescriptor>();
-        for (String configuration : parentConfigurations) {
-            parentArtifacts.addAll(WrapUtil.toSet(caller.getDependencyDescriptor().getDependencyArtifacts(configuration)));
-        }
-
-        Artifact[] allArtifacts = childNode.getSelectedArtifacts(null);
-        Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
-        for (Artifact artifact : allArtifacts) {
-            for (DependencyArtifactDescriptor parentArtifact : parentArtifacts) {
-                if (isEquals(parentArtifact, artifact)) {
-                    DefaultResolvedArtifact resolvedArtifact = createResolvedArtifact(artifact, childNode);
-                    resolvedArtifact.setResolvedDependency(resolvedDependency);
-                    artifacts.add(resolvedArtifact);
-                    break;
-                }
-            }
-        }
-        return artifacts;
-    }
-
-    private DefaultResolvedArtifact createResolvedArtifact(Artifact artifact, IvyNode ivyNode) {
-        return new DefaultResolvedArtifact(artifact, ivyNode.getData().getEngine());
-    }
-
-    private boolean isEquals(DependencyArtifactDescriptor parentArtifact, Artifact artifact) {
-        return parentArtifact.getName().equals(artifact.getName())
-                && parentArtifact.getExt().equals(artifact.getExt())
-                && parentArtifact.getType().equals(artifact.getType())
-                && parentArtifact.getQualifiedExtraAttributes().equals(artifact.getQualifiedExtraAttributes());
-    }
-
-    private boolean isRootCaller(ConfigurationResolveReport configurationResolveReport, IvyNodeCallers.Caller caller) {
-        return caller.getModuleDescriptor().equals(configurationResolveReport.getModuleDescriptor());
-    }
-
-    private Set<ConfigurationDetails> selectParentConfigurations(Collection<ConfigurationDetails> parentConfigurations,
-                                                                 Set<String> callerConfigurations) {
-        Set<ConfigurationDetails> matchingParentConfigurations = new LinkedHashSet<ConfigurationDetails>();
-        for (String callerConfiguration : callerConfigurations) {
-            for (ConfigurationDetails parentConfiguration : parentConfigurations) {
-                if (parentConfiguration.containsConfiguration(callerConfiguration)) {
-                    matchingParentConfigurations.add(parentConfiguration);
-                }
-            }
-        }
-        return matchingParentConfigurations;
-    }
-
-    private Set<String> getConfigurationHierarchy(IvyNode node, String configurationName) {
-        Set<String> configurations = new LinkedHashSet<String>();
-        configurations.add(configurationName);
-        org.apache.ivy.core.module.descriptor.Configuration configuration = node.getConfiguration(configurationName);
-        for (String extendedConfigurationNames : configuration.getExtends()) {
-            configurations.addAll(getConfigurationHierarchy(node, extendedConfigurationNames));
-        }
-        return configurations;
-    }
-
-    private Set<String> getCallerConfigurationsByDependencyConfiguration(IvyNodeCallers.Caller caller, IvyNode dependencyNode, String dependencyConfiguration) {
-        Map<String, Set<String>> dependency2CallerConfs = new LinkedHashMap<String, Set<String>>();
-        for (String callerConf : caller.getCallerConfigurations()) {
-            Set<String> dependencyConfs = getRealConfigurations(dependencyNode
-                    , caller.getDependencyDescriptor().getDependencyConfigurations(callerConf));
-            for (String dependencyConf : dependencyConfs) {
-                if (!dependency2CallerConfs.containsKey(dependencyConf)) {
-                    dependency2CallerConfs.put(dependencyConf, new LinkedHashSet<String>());
-                }
-                dependency2CallerConfs.get(dependencyConf).add(callerConf);
-            }
-        }
-        return dependency2CallerConfs.get(dependencyConfiguration);
-    }
-
-    private Set<String> getDependencyConfigurationsByCaller(IvyNode dependencyNode, IvyNodeCallers.Caller caller) {
-        String[] dependencyConfigurations = caller.getDependencyDescriptor().getDependencyConfigurations(caller.getCallerConfigurations());
-        return getRealConfigurations(dependencyNode, dependencyConfigurations);
-    }
-
-    private Set<String> getRealConfigurations(IvyNode dependencyNode, String[] dependencyConfigurations) {
-        Set<String> realDependencyConfigurations = new LinkedHashSet<String>();
-        for (String dependencyConfiguration : dependencyConfigurations) {
-            realDependencyConfigurations.addAll(WrapUtil.toSet(dependencyNode.getRealConfs(dependencyConfiguration)));
-        }
-        return realDependencyConfigurations;
-    }
-
-    private Set<ResolvedArtifact> getArtifacts(IvyNode dependencyNode) {
-        Set<ResolvedArtifact> resolvedArtifacts = new LinkedHashSet<ResolvedArtifact>();
-        Artifact[] artifacts = dependencyNode.getSelectedArtifacts(null);
-        for (Artifact artifact : artifacts) {
-            resolvedArtifacts.add(createResolvedArtifact(artifact, dependencyNode));
-        }
-        return resolvedArtifacts;
-    }
-
-    private class ReportConversionContext {
-        ResolvedDependency root;
-        final Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies = new LinkedHashMap<Dependency, Set<ResolvedDependency>>();
-        final Map<ModuleRevisionId, Map<String, ConfigurationDetails>> handledNodes = new LinkedHashMap<ModuleRevisionId, Map<String, ConfigurationDetails>>();
-        final Set<ResolvedArtifact> resolvedArtifacts = new LinkedHashSet<ResolvedArtifact>();
-        final ConfigurationResolveReport configurationResolveReport;
-        final Map<ResolvedConfigurationIdentifier, ModuleDependency> firstLevelDependenciesModuleRevisionIds = new HashMap<ResolvedConfigurationIdentifier, ModuleDependency>();
-        final Map<ResolvedConfigurationIdentifier, ConfigurationDetails> configurations = new HashMap<ResolvedConfigurationIdentifier, ConfigurationDetails>();
-        final String conf;
-
-        public ReportConversionContext(ResolveReport resolveReport, Configuration configuration) {
-            configurationResolveReport = resolveReport.getConfigurationReport(configuration.getName());
-            createFirstLevelDependenciesModuleRevisionIds(configuration.getAllDependencies(ModuleDependency.class));
-            conf = configuration.getName();
-        }
-
-        public ConfigurationDetails addConfiguration(IvyNode ivyNode, String configuration) {
-            ModuleRevisionId actualId = ivyNode.getResolvedId();
-            Set<String> configurations = getConfigurationHierarchy(ivyNode, configuration);
-            DefaultResolvedDependency resolvedDependency;
-            if (actualId.getAttribute(DependencyDescriptorFactory.PROJECT_PATH_KEY) != null) {
-                resolvedDependency = new DefaultResolvedDependency(
-                        actualId.getAttribute(DependencyDescriptorFactory.PROJECT_PATH_KEY),
-                        actualId.getOrganisation(), actualId.getName(), actualId.getRevision(),
-                        configuration, getArtifacts(ivyNode));
-            } else {
-                resolvedDependency = new DefaultResolvedDependency(
-                        actualId.getOrganisation(), actualId.getName(), actualId.getRevision(),
-                        configuration, getArtifacts(ivyNode));
-            }
-            for (ResolvedArtifact resolvedArtifact : resolvedDependency.getModuleArtifacts()) {
-                ((DefaultResolvedArtifact) resolvedArtifact).setResolvedDependency(resolvedDependency);
-            }
-            ConfigurationDetails configurationDetails = new ConfigurationDetails(resolvedDependency, ivyNode,
-                    configurations);
-            this.configurations.put(resolvedDependency.getId(), configurationDetails);
-
-            // Collect top level dependencies
-            ResolvedConfigurationIdentifier originalId = new ResolvedConfigurationIdentifier(ivyNode.getId(),
-                    configuration);
-            if (firstLevelDependenciesModuleRevisionIds.containsKey(originalId)) {
-                ModuleDependency firstLevelNode = firstLevelDependenciesModuleRevisionIds.get(originalId);
-                firstLevelResolvedDependencies.get(firstLevelNode).add(resolvedDependency);
-            }
-
-            return configurationDetails;
-        }
-
-        private void createFirstLevelDependenciesModuleRevisionIds(Set<ModuleDependency> firstLevelDependencies) {
-            for (ModuleDependency firstLevelDependency : firstLevelDependencies) {
-                ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(dependencyDescriptorFactory.createModuleRevisionId(firstLevelDependency), firstLevelDependency.getConfiguration());
-                firstLevelDependenciesModuleRevisionIds.put(id, firstLevelDependency);
-                firstLevelResolvedDependencies.put(firstLevelDependency, new LinkedHashSet<ResolvedDependency>());
-            }
-        }
-    }
-
-    private static class ConfigurationDetails {
-        final DefaultResolvedDependency dependency;
-        final IvyNode node;
-        final Set<String> configurationHierarchy;
-
-        private ConfigurationDetails(DefaultResolvedDependency dependency, IvyNode node,
-                                     Set<String> configurationHierarchy) {
-            this.dependency = dependency;
-            this.node = node;
-            this.configurationHierarchy = configurationHierarchy;
-        }
-
-        public boolean containsConfiguration(String configuration) {
-            return configurationHierarchy.contains(configuration);
-        }
-
-        @Override
-        public String toString() {
-            return dependency.toString();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyService.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyService.java
deleted file mode 100644
index 0f8b04a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyService.java
+++ /dev/null
@@ -1,191 +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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-import org.gradle.api.internal.artifacts.IvyService;
-import org.gradle.api.internal.artifacts.configurations.Configurations;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyService implements IvyService {
-    private SettingsConverter settingsConverter;
-    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
-    private ModuleDescriptorConverter publishModuleDescriptorConverter;
-    private ModuleDescriptorConverter fileModuleDescriptorConverter;
-    private IvyFactory ivyFactory;
-    private IvyDependencyResolver dependencyResolver;
-    private IvyDependencyPublisher dependencyPublisher;
-    private final DependencyMetaDataProvider metaDataProvider;
-    private final ResolverProvider resolverProvider;
-    private Map clientModuleRegistry;
-
-    public DefaultIvyService(DependencyMetaDataProvider metaDataProvider, ResolverProvider resolverProvider,
-                             SettingsConverter settingsConverter,
-                             ModuleDescriptorConverter resolveModuleDescriptorConverter,
-                             ModuleDescriptorConverter publishModuleDescriptorConverter,
-                             ModuleDescriptorConverter fileModuleDescriptorConverter,
-                             IvyFactory ivyFactory,
-                             IvyDependencyResolver dependencyResolver,
-                             IvyDependencyPublisher dependencyPublisher,
-                             Map clientModuleRegistry) {
-        this.metaDataProvider = metaDataProvider;
-        this.resolverProvider = resolverProvider;
-        this.settingsConverter = settingsConverter;
-        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
-        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
-        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
-        this.ivyFactory = ivyFactory;
-        this.dependencyResolver = dependencyResolver;
-        this.dependencyPublisher = dependencyPublisher;
-        this.clientModuleRegistry = clientModuleRegistry;
-    }
-
-    private Ivy ivyForResolve(List<DependencyResolver> dependencyResolvers, File cacheParentDir,
-                   Map<String, ModuleDescriptor> clientModuleRegistry) {
-        return ivyFactory.createIvy(
-                settingsConverter.convertForResolve(
-                        dependencyResolvers,
-                        cacheParentDir,
-                        metaDataProvider.getInternalRepository(),
-                        clientModuleRegistry
-                )
-        );
-    }
-
-    private Ivy ivyForPublish(List<DependencyResolver> publishResolvers, File cacheParentDir) {
-        return ivyFactory.createIvy(
-                settingsConverter.convertForPublish(
-                        publishResolvers,
-                        cacheParentDir,
-                        metaDataProvider.getInternalRepository()
-                )
-        );
-    }
-
-    public SettingsConverter getSettingsConverter() {
-        return settingsConverter;
-    }
-
-    public ModuleDescriptorConverter getResolveModuleDescriptorConverter() {
-        return resolveModuleDescriptorConverter;
-    }
-
-    public ModuleDescriptorConverter getPublishModuleDescriptorConverter() {
-        return publishModuleDescriptorConverter;
-    }
-
-    public ModuleDescriptorConverter getFileModuleDescriptorConverter() {
-        return fileModuleDescriptorConverter;
-    }
-
-    public IvyFactory getIvyFactory() {
-        return ivyFactory;
-    }
-
-    public DependencyMetaDataProvider getMetaDataProvider() {
-        return metaDataProvider;
-    }
-
-    public ResolverProvider getResolverProvider() {
-        return resolverProvider;
-    }
-
-    public IvyDependencyResolver getDependencyResolver() {
-        return dependencyResolver;
-    }
-
-    public IvyDependencyPublisher getDependencyPublisher() {
-        return dependencyPublisher;
-    }
-
-    public ResolvedConfiguration resolve(final Configuration configuration) {
-        Ivy ivy = ivyForResolve(resolverProvider.getResolvers(), metaDataProvider.getGradleUserHomeDir(),
-                clientModuleRegistry);
-        ModuleDescriptor moduleDescriptor = resolveModuleDescriptorConverter.convert(configuration.getAll(),
-                metaDataProvider.getModule(), ivy.getSettings());
-        return dependencyResolver.resolve(configuration, ivy, moduleDescriptor);
-    }
-
-    public void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
-                        List<DependencyResolver> publishResolvers) {
-        Ivy ivy = ivyForPublish(publishResolvers, metaDataProvider.getGradleUserHomeDir());
-        Set<String> confs = Configurations.getNames(configurationsToPublish, false);
-        writeDescriptorFile(descriptorDestination, configurationsToPublish, ivy.getSettings());
-        dependencyPublisher.publish(
-                confs,
-                publishResolvers,
-                publishModuleDescriptorConverter.convert(configurationsToPublish, metaDataProvider.getModule(), ivy.getSettings()),
-                descriptorDestination,
-                ivy.getPublishEngine());
-    }
-
-    private void writeDescriptorFile(File descriptorDestination, Set<Configuration> configurationsToPublish, IvySettings ivySettings) {
-        if (descriptorDestination == null) {
-            return;
-        }
-        assert configurationsToPublish.size() > 0;
-        Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
-        ModuleDescriptor moduleDescriptor = fileModuleDescriptorConverter.convert(allConfigurations, metaDataProvider.getModule(), ivySettings);
-        try {
-            moduleDescriptor.toIvyFile(descriptorDestination);
-        } catch (ParseException e) {
-            throw new RuntimeException(e);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void setSettingsConverter(SettingsConverter settingsConverter) {
-        this.settingsConverter = settingsConverter;
-    }
-
-    public void setIvyFactory(IvyFactory ivyFactory) {
-        this.ivyFactory = ivyFactory;
-    }
-
-    public void setDependencyResolver(IvyDependencyResolver dependencyResolver) {
-        this.dependencyResolver = dependencyResolver;
-    }
-
-    public void setDependencyPublisher(IvyDependencyPublisher dependencyPublisher) {
-        this.dependencyPublisher = dependencyPublisher;
-    }
-
-    public Map getClientModuleRegistry() {
-        return clientModuleRegistry;
-    }
-
-    public void setClientModuleRegistry(Map clientModuleRegistry) {
-        this.clientModuleRegistry = clientModuleRegistry;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
deleted file mode 100644
index e0f28b4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
+++ /dev/null
@@ -1,218 +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.api.internal.artifacts.ivyservice;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.ivy.core.cache.RepositoryCacheManager;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.apache.ivy.plugins.repository.Repository;
-import org.apache.ivy.plugins.repository.TransferEvent;
-import org.apache.ivy.plugins.repository.TransferListener;
-import org.apache.ivy.plugins.resolver.ChainResolver;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.RepositoryResolver;
-import org.gradle.api.artifacts.ResolverContainer;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.logging.ProgressLogger;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.util.Clock;
-import org.gradle.util.WrapUtil;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultSettingsConverter implements SettingsConverter {
-    private static Logger logger = Logging.getLogger(DefaultSettingsConverter.class);
-
-    private RepositoryCacheManager repositoryCacheManager;
-    private IvySettings ivySettings;
-    private final ProgressLoggerFactory progressLoggerFactory;
-    private final TransferListener transferListener = new ProgressLoggingTransferListener();
-
-    public DefaultSettingsConverter(ProgressLoggerFactory progressLoggerFactory) {
-        this.progressLoggerFactory = progressLoggerFactory;
-    }
-
-    private static String getLengthText(TransferEvent evt) {
-        return getLengthText(evt.isTotalLengthSet() ? evt.getTotalLength() : null);
-    }
-
-    private static String getLengthText(Long bytes) {
-        if (bytes == null) {
-            return "unknown size";
-        }
-        if (bytes < 1024) {
-            return bytes + " B";
-        } else if (bytes < 1048576) {
-            return (bytes / 1024) + " KB";
-        } else {
-            return String.format("%.2f MB", bytes / 1048576.0);
-        }
-    }
-
-    public IvySettings convertForPublish(List<DependencyResolver> publishResolvers, File gradleUserHome, DependencyResolver internalRepository) {
-        if (ivySettings != null) {
-            return ivySettings;
-        }
-        Clock clock = new Clock();
-
-        IvySettings ivySettings = createIvySettings(gradleUserHome);
-        initializeResolvers(ivySettings, getAllResolvers(Collections.<DependencyResolver>emptyList(), publishResolvers));
-        logger.debug("Timing: Ivy convert for publish took {}", clock.getTime());
-        return ivySettings;
-    }
-
-    public IvySettings convertForResolve(List<DependencyResolver> dependencyResolvers,
-                               File gradleUserHome, DependencyResolver internalRepository, Map<String, ModuleDescriptor> clientModuleRegistry) {
-        if (ivySettings != null) {
-            return ivySettings;
-        }
-        Clock clock = new Clock();
-        ChainResolver userResolverChain = createUserResolverChain(dependencyResolvers, internalRepository);
-        ClientModuleResolver clientModuleResolver = createClientModuleResolver(clientModuleRegistry, userResolverChain);
-        ChainResolver outerChain = createOuterChain(userResolverChain, clientModuleResolver);
-
-        IvySettings ivySettings = createIvySettings(gradleUserHome);
-        initializeResolvers(ivySettings, getAllResolvers(dependencyResolvers, Collections.<DependencyResolver>emptyList(), internalRepository, userResolverChain, clientModuleResolver, outerChain));
-        ivySettings.setDefaultResolver(CLIENT_MODULE_CHAIN_NAME);
-        logger.debug("Timing: Ivy convert for resolve took {}", clock.getTime());
-        return ivySettings;
-    }
-
-    private List<DependencyResolver> getAllResolvers(List<DependencyResolver> classpathResolvers,
-                                                     List<DependencyResolver> otherResolvers, DependencyResolver... resolvers) {
-        List<DependencyResolver> allResolvers = new ArrayList<DependencyResolver>(otherResolvers);
-        allResolvers.addAll(classpathResolvers);
-        allResolvers.addAll(WrapUtil.toList(resolvers));
-        return allResolvers;
-    }
-
-    private ChainResolver createOuterChain(ChainResolver userResolverChain, ClientModuleResolver clientModuleResolver) {
-        ChainResolver clientModuleChain = new ChainResolver();
-        clientModuleChain.setName(CLIENT_MODULE_CHAIN_NAME);
-        clientModuleChain.setReturnFirst(true);
-        clientModuleChain.add(clientModuleResolver);
-        clientModuleChain.add(userResolverChain);
-        return clientModuleChain;
-    }
-
-    private ClientModuleResolver createClientModuleResolver(Map<String, ModuleDescriptor> clientModuleRegistry, ChainResolver userResolverChain) {
-        return new ClientModuleResolver(CLIENT_MODULE_NAME, clientModuleRegistry, userResolverChain);
-    }
-
-    private ChainResolver createUserResolverChain(List<DependencyResolver> classpathResolvers, DependencyResolver internalRepository) {
-        ChainResolver chainResolver = new ChainResolver();
-        chainResolver.setName(CHAIN_RESOLVER_NAME);
-        chainResolver.add(internalRepository);
-        // todo Figure out why Ivy thinks this is necessary. The IBiblio resolver has already this pattern which should be good enough. By doing this we let Maven semantics seep into our whole system.
-        chainResolver.setChangingPattern(".*-SNAPSHOT");
-        chainResolver.setChangingMatcher(PatternMatcher.REGEXP);
-        chainResolver.setReturnFirst(true);
-        for (DependencyResolver classpathResolver : classpathResolvers) {
-            chainResolver.add(classpathResolver);
-        }
-        return chainResolver;
-    }
-
-    private IvySettings createIvySettings(File gradleUserHome) {
-        IvySettings ivySettings = new IvySettings();
-        ivySettings.setDefaultCache(new File(gradleUserHome, ResolverContainer.DEFAULT_CACHE_DIR_NAME));
-        ivySettings.setDefaultCacheIvyPattern(ResolverContainer.DEFAULT_CACHE_IVY_PATTERN);
-        ivySettings.setDefaultCacheArtifactPattern(ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN);
-        ivySettings.setVariable("ivy.log.modules.in.use", "false");
-        setRepositoryCacheManager(ivySettings);
-        return ivySettings;
-    }
-
-    private void setRepositoryCacheManager(IvySettings ivySettings) {
-        if (repositoryCacheManager == null) {
-            repositoryCacheManager = ivySettings.getDefaultRepositoryCacheManager();
-        } else {
-            ivySettings.setDefaultRepositoryCacheManager(repositoryCacheManager);
-        }
-    }
-
-    private void initializeResolvers(IvySettings ivySettings, List<DependencyResolver> allResolvers) {
-        for (DependencyResolver dependencyResolver : allResolvers) {
-            ivySettings.addResolver(dependencyResolver);
-            RepositoryCacheManager cacheManager = dependencyResolver.getRepositoryCacheManager();
-            // Validate that each resolver is sharing the same cache instance (ignoring caches which don't actually cache anything)
-            if (cacheManager != ivySettings.getDefaultRepositoryCacheManager()
-                    && !(cacheManager instanceof NoOpRepositoryCacheManager)
-                    && !(cacheManager instanceof LocalFileRepositoryCacheManager)) {
-                throw new IllegalStateException(String.format("Unexpected cache manager %s for repository %s (%s)", cacheManager, dependencyResolver.getName(), dependencyResolver));
-            }
-            if (dependencyResolver instanceof RepositoryResolver) {
-                Repository repository = ((RepositoryResolver) dependencyResolver).getRepository();
-                if (!repository.hasTransferListener(transferListener)) {
-                    repository.addTransferListener(transferListener);
-                }
-            }
-        }
-    }
-
-    public IvySettings getIvySettings() {
-        return ivySettings;
-    }
-
-    public void setIvySettings(IvySettings ivySettings) {
-        this.ivySettings = ivySettings;
-    }
-
-    private class ProgressLoggingTransferListener implements TransferListener {
-        private ProgressLogger logger;
-        private long total;
-
-        public void transferProgress(TransferEvent evt) {
-            if (evt.getResource().isLocal()) {
-                return;
-            }
-            if (evt.getEventType() == TransferEvent.TRANSFER_STARTED) {
-                total = 0;
-                logger = progressLoggerFactory.newOperation(DefaultSettingsConverter.class);
-                String description = String.format("%s %s", StringUtils.capitalize(getRequestType(evt)), evt.getResource().getName());
-                logger.setDescription(description);
-                logger.setLoggingHeader(description);
-                logger.started();
-            }
-            if (evt.getEventType() == TransferEvent.TRANSFER_PROGRESS) {
-                total += evt.getLength();
-                logger.progress(String.format("%s/%s %sed", getLengthText(total), getLengthText(evt), getRequestType(evt)));
-            }
-            if (evt.getEventType() == TransferEvent.TRANSFER_COMPLETED) {
-                logger.completed();
-            }
-        }
-
-        private String getRequestType(TransferEvent evt) {
-            if (evt.getRequestType() == TransferEvent.REQUEST_PUT) {
-                return "upload";
-            } else {
-                return "download";
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyService.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyService.java
deleted file mode 100644
index 76bbae1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyService.java
+++ /dev/null
@@ -1,152 +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.ivyservice;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.artifacts.IvyService;
-import org.gradle.api.specs.Spec;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-public class ErrorHandlingIvyService implements IvyService {
-    private final IvyService ivyService;
-
-    public ErrorHandlingIvyService(IvyService ivyService) {
-        this.ivyService = ivyService;
-    }
-
-    public IvyService getIvyService() {
-        return ivyService;
-    }
-
-    public void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
-                        List<DependencyResolver> publishResolvers) {
-        try {
-            ivyService.publish(configurationsToPublish, descriptorDestination, publishResolvers);
-        } catch (Throwable e) {
-            throw new PublishException(String.format("Could not publish configurations %s.", configurationsToPublish), e);
-        }
-    }
-
-    public ResolvedConfiguration resolve(final Configuration configuration) {
-        final ResolvedConfiguration resolvedConfiguration;
-        try {
-            resolvedConfiguration = ivyService.resolve(configuration);
-        } catch (final Throwable e) {
-            return new BrokenResolvedConfiguration(e, configuration);
-        }
-        return new ErrorHandlingResolvedConfiguration(resolvedConfiguration, configuration);
-    }
-
-    private static ResolveException wrapException(Throwable e, Configuration configuration) {
-        if (e instanceof ResolveException) {
-            return (ResolveException) e;
-        }
-        return new ResolveException(configuration, e);
-    }
-
-    private static class ErrorHandlingResolvedConfiguration implements ResolvedConfiguration {
-        private final ResolvedConfiguration resolvedConfiguration;
-        private final Configuration configuration;
-
-        public ErrorHandlingResolvedConfiguration(ResolvedConfiguration resolvedConfiguration,
-                                                  Configuration configuration) {
-            this.resolvedConfiguration = resolvedConfiguration;
-            this.configuration = configuration;
-        }
-
-        public boolean hasError() {
-            return resolvedConfiguration.hasError();
-        }
-
-        public void rethrowFailure() throws ResolveException {
-            try {
-                resolvedConfiguration.rethrowFailure();
-            } catch (Throwable e) {
-                throw wrapException(e, configuration);
-            }
-        }
-
-        public Set<File> getFiles(Spec<Dependency> dependencySpec) throws ResolveException {
-            try {
-                return resolvedConfiguration.getFiles(dependencySpec);
-            } catch (Throwable e) {
-                throw wrapException(e, configuration);
-            }
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
-            try {
-                return resolvedConfiguration.getFirstLevelModuleDependencies();
-            } catch (Throwable e) {
-                throw wrapException(e, configuration);
-            }
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<Dependency> dependencySpec) throws ResolveException {
-            try {
-                return resolvedConfiguration.getFirstLevelModuleDependencies(dependencySpec);
-            } catch (Throwable e) {
-                throw wrapException(e, configuration);
-            }
-        }
-
-        public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
-            try {
-                return resolvedConfiguration.getResolvedArtifacts();
-            } catch (Throwable e) {
-                throw wrapException(e, configuration);
-            }
-        }
-    }
-
-    private static class BrokenResolvedConfiguration implements ResolvedConfiguration {
-        private final Throwable e;
-        private final Configuration configuration;
-
-        public BrokenResolvedConfiguration(Throwable e, Configuration configuration) {
-            this.e = e;
-            this.configuration = configuration;
-        }
-
-        public boolean hasError() {
-            return true;
-        }
-
-        public void rethrowFailure() throws ResolveException {
-            throw wrapException(e, configuration);
-        }
-
-        public Set<File> getFiles(Spec<Dependency> dependencySpec) throws ResolveException {
-            throw wrapException(e, configuration);
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
-            throw wrapException(e, configuration);
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<Dependency> dependencySpec) throws ResolveException {
-            throw wrapException(e, configuration);
-        }
-
-        public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
-            throw wrapException(e, configuration);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolver.java
deleted file mode 100644
index 255e2f8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolver.java
+++ /dev/null
@@ -1,211 +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.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.resolver.IBiblioResolver;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Calendar;
-import java.util.Date;
-
-/**
- * @author Hans Dockter
- */
-public class GradleIBiblioResolver extends IBiblioResolver {
-    Logger logger = LoggerFactory.getLogger(GradleIBiblioResolver.class);
-
-    public static final CacheTimeoutStrategy NEVER = new CacheTimeoutStrategy() {
-        public boolean isCacheTimedOut(long lastResolvedTime) {
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            return "NEVER Timeout Strategy";
-        }
-    };
-
-    public static final CacheTimeoutStrategy ALWAYS = new CacheTimeoutStrategy() {
-        public boolean isCacheTimedOut(long lastResolvedTime) {
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            return "ALWAYS Timeout Strategy";
-        }
-
-    };
-
-    public static final CacheTimeoutStrategy DAILY = new CacheTimeoutStrategy() {
-        public boolean isCacheTimedOut(long lastResolvedTime) {
-            Calendar calendarCurrent = Calendar.getInstance();
-            calendarCurrent.setTime(new Date());
-            int dayOfYear = calendarCurrent.get(Calendar.DAY_OF_YEAR);
-            int year = calendarCurrent.get(Calendar.YEAR);
-
-            Calendar calendarLastResolved = Calendar.getInstance();
-            calendarLastResolved.setTime(new Date(lastResolvedTime));
-            if (calendarLastResolved.get(Calendar.YEAR) == year && calendarLastResolved.get(Calendar.DAY_OF_YEAR)
-                    == dayOfYear) {
-                return false;
-            }
-            return true;
-        }
-
-        @Override
-        public String toString() {
-            return "DAILY Timeout Strategy";
-        }
-    };
-
-    private CacheTimeoutStrategy snapshotTimeout = DAILY;
-
-    /**
-     * Returns the timeout strategy for a Maven Snapshot in the cache
-     */
-    public CacheTimeoutStrategy getSnapshotTimeout() {
-        return snapshotTimeout;
-    }
-
-    /**
-     * Sets the time in ms a Maven Snapshot in the cache is not checked for a newer version
-     *
-     * @param snapshotLifetime The lifetime in ms
-     */
-    public void setSnapshotTimeout(long snapshotLifetime) {
-        this.snapshotTimeout = new Interval(snapshotLifetime);
-    }
-
-    /**
-     * Sets a timeout strategy for a Maven Snapshot in the cache
-     *
-     * @param cacheTimeoutStrategy The strategy
-     */
-    public void setSnapshotTimeout(CacheTimeoutStrategy cacheTimeoutStrategy) {
-        this.snapshotTimeout = cacheTimeoutStrategy;
-    }
-
-    @Override
-    public void setRoot(String root) {
-        super.setRoot(root);
-
-        URI rootUri;
-        try {
-            rootUri = new URI(root);
-        } catch (URISyntaxException e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-        if (rootUri.getScheme().equalsIgnoreCase("file")) {
-            setSnapshotTimeout(ALWAYS);
-        } else {
-            setSnapshotTimeout(DAILY);
-        }
-    }
-
-    @Override
-    protected ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ResolveData data) {
-        setChangingPattern(null);
-        ResolvedModuleRevision moduleRevision = super.findModuleInCache(dd, data);
-        if (moduleRevision == null) {
-            logger.debug("Dependency not found in cache.");
-            setChangingPattern(".*-SNAPSHOT");
-            return null;
-        }
-        PropertiesFile cacheProperties = getCacheProperties(dd, moduleRevision);
-        Long lastResolvedTime = getLastResolvedTime(cacheProperties);
-        updateCachePropertiesToCurrentTime(cacheProperties);
-        logger.debug("Using the SnapshotTimeOutStrategy=" + snapshotTimeout.toString());
-        if (snapshotTimeout.isCacheTimedOut(lastResolvedTime)) {
-            logger.debug("Cache has timed out.");
-            setChangingPattern(".*-SNAPSHOT");
-            return null;
-        } else {
-            logger.debug("Dependency found in cache..");
-            return moduleRevision;
-        }
-    }
-
-    private void updateCachePropertiesToCurrentTime(PropertiesFile cacheProperties) {
-        cacheProperties.setProperty("resolved.time", "" + System.currentTimeMillis());
-        cacheProperties.save();
-    }
-
-    private long getLastResolvedTime(PropertiesFile cacheProperties) {
-        String lastResolvedProp = cacheProperties.getProperty("resolved.time");
-        if (lastResolvedProp != null) {
-            return Long.parseLong(lastResolvedProp);
-        }
-        // No resolved.time property - assume that the properties file modification time == the resolve time
-        return cacheProperties.file.lastModified();
-    }
-
-    private PropertiesFile getCacheProperties(DependencyDescriptor dd, ResolvedModuleRevision moduleRevision) {
-        DefaultRepositoryCacheManager cacheManager = (DefaultRepositoryCacheManager) getRepositoryCacheManager();
-        PropertiesFile props = new PropertiesFile(new File(cacheManager.getRepositoryCacheRoot(),
-                IvyPatternHelper.substitute(cacheManager.getDataFilePattern(), moduleRevision.getId())),
-                "ivy cached data file for " + dd.getDependencyRevisionId());
-        return props;
-    }
-
-    public interface CacheTimeoutStrategy {
-        boolean isCacheTimedOut(long lastResolvedTime);
-    }
-
-    private static class PropertiesFile extends org.apache.ivy.util.PropertiesFile {
-        private final File file;
-
-        private PropertiesFile(File file, String header) {
-            super(file, header);
-            this.file = file;
-        }
-    }
-
-    public static class Interval implements CacheTimeoutStrategy {
-        Logger logger = LoggerFactory.getLogger(Interval.class);
-
-        private long interval;
-
-        public Interval(long interval) {
-            this.interval = interval;
-        }
-
-        public boolean isCacheTimedOut(long lastResolvedTime) {
-            logger.debug("Last resolved time: " + lastResolvedTime);
-            long currentTime = System.currentTimeMillis();
-            logger.debug("Current time: " + currentTime);
-            boolean timedOut = currentTime - lastResolvedTime > interval;
-            return timedOut;
-        }
-
-        @Override
-        public String toString() {
-            return "Interval{interval=" + interval + '}';
-        }
-    }
-}
-
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyConversionResult.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyConversionResult.java
deleted file mode 100644
index 765b8be..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyConversionResult.java
+++ /dev/null
@@ -1,34 +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.ivyservice;
-
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface IvyConversionResult {
-    Map<Dependency, Set<ResolvedDependency>> getFirstLevelResolvedDependencies();
-
-    Set<ResolvedArtifact> getResolvedArtifacts();
-
-    ResolvedDependency getRoot();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyResolver.java
deleted file mode 100644
index c702792..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyResolver.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-
-/**
- * @author Hans Dockter
- */
-public interface IvyDependencyResolver {
-    ResolvedConfiguration resolve(Configuration configuration, Ivy ivy, ModuleDescriptor moduleDescriptor);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyReportConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyReportConverter.java
deleted file mode 100644
index bf2bc53..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyReportConverter.java
+++ /dev/null
@@ -1,26 +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.ivyservice;
-
-import org.apache.ivy.core.report.ResolveReport;
-import org.gradle.api.artifacts.Configuration;
-
-/**
- * @author Hans Dockter
- */
-public interface IvyReportConverter {
-    IvyConversionResult convertReport(ResolveReport resolveReport, Configuration configuration);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
deleted file mode 100644
index ec85f7a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
+++ /dev/null
@@ -1,54 +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.ivyservice;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.Module;
-import org.gradle.util.GUtil;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class IvyUtil {
-
-    public static ModuleRevisionId createModuleRevisionId(Module module) {
-        return createModuleRevisionId(module, new HashMap());
-    }
-
-    public static ModuleRevisionId createModuleRevisionId(Module module, Map<String, String> extraAttributes) {
-        return createModuleRevisionId(module.getGroup(), module.getName(), module.getVersion(), extraAttributes);
-    }
-
-    public static ModuleRevisionId createModuleRevisionId(Dependency dependency) {
-        return createModuleRevisionId(dependency, new HashMap<String, String>());
-    }
-
-    public static ModuleRevisionId createModuleRevisionId(Dependency dependency, Map<String, String> extraAttributes) {
-        return createModuleRevisionId(dependency.getGroup(), dependency.getName(), dependency.getVersion(), extraAttributes);
-    }
-
-    public static ModuleRevisionId createModuleRevisionId(String group, String name, String version, Map<String, String> extraAttributes) {
-        return ModuleRevisionId.newInstance(emptyStringIfNull(group), name, emptyStringIfNull(version), extraAttributes);
-    }
-
-    private static String emptyStringIfNull(String value) {
-        return GUtil.elvis(value, "");
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/LocalFileRepositoryCacheManager.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/LocalFileRepositoryCacheManager.java
deleted file mode 100644
index d44e158..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/LocalFileRepositoryCacheManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2011 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.ivyservice;
-
-import org.apache.ivy.core.cache.*;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
-import org.apache.ivy.plugins.repository.ResourceDownloader;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-
-import java.io.File;
-import java.text.ParseException;
-
-/**
- * A cache manager for local repositories. Doesn't cache anything, and uses artifacts from their origin.
- */
-public class LocalFileRepositoryCacheManager implements RepositoryCacheManager {
-    private final String name;
-
-    public LocalFileRepositoryCacheManager(String name) {
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName, String artifactResolverName) {
-    }
-
-    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
-        return null;
-    }
-
-    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) {
-        return null;
-    }
-
-    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
-        long start = System.currentTimeMillis();
-        ArtifactDownloadReport report = new ArtifactDownloadReport(artifact);
-        ResolvedResource resolvedResource = resourceResolver.resolve(artifact);
-        if (resolvedResource == null) {
-            report.setDownloadStatus(DownloadStatus.FAILED);
-            report.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
-            report.setDownloadTimeMillis(System.currentTimeMillis() - start);
-            return report;
-        }
-        assert resolvedResource.getResource().isLocal();
-        File file = new File(resolvedResource.getResource().getName());
-        assert file.isFile();
-
-        ArtifactOrigin origin = new ArtifactOrigin(artifact, true, file.getAbsolutePath());
-        report.setDownloadStatus(DownloadStatus.NO);
-        report.setArtifactOrigin(origin);
-        report.setSize(file.length());
-        report.setLocalFile(file);
-        return report;
-    }
-
-    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, ResolvedResource orginalMetadataRef, DependencyDescriptor dd, Artifact requestedMetadataArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
-        return null;
-    }
-
-    public void originalToCachedModuleDescriptor(DependencyResolver resolver, ResolvedResource orginalMetadataRef, Artifact requestedMetadataArtifact, ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
-    }
-
-    public void clean() {
-    }
-
-    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision) {
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
deleted file mode 100644
index 84be07c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
+++ /dev/null
@@ -1,30 +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.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ModuleDescriptorConverter {
-    ModuleDescriptor convert(Set<Configuration> configurations, Module module, IvySettings settings);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/NoOpRepositoryCacheManager.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/NoOpRepositoryCacheManager.java
deleted file mode 100644
index a70b393..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/NoOpRepositoryCacheManager.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2011 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.ivyservice;
-
-import org.apache.ivy.core.cache.*;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
-import org.apache.ivy.plugins.repository.ResourceDownloader;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-
-import java.text.ParseException;
-
-/**
- * A cache manager which does nothing. Only useful for local meta-data only repositories.
- */
-public class NoOpRepositoryCacheManager implements RepositoryCacheManager {
-    private final String name;
-
-    public NoOpRepositoryCacheManager(String name) {
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void saveResolvers(ModuleDescriptor descriptor, String metadataResolverName, String artifactResolverName) {
-    }
-
-    public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
-        return null;
-    }
-
-    public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId, CacheMetadataOptions options, String expectedResolver) {
-        return null;
-    }
-
-    public ArtifactDownloadReport download(Artifact artifact, ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader, CacheDownloadOptions options) {
-        ArtifactDownloadReport report = new ArtifactDownloadReport(null);
-        report.setDownloadStatus(DownloadStatus.NO);
-        return report;
-    }
-
-    public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver, ResolvedResource orginalMetadataRef, DependencyDescriptor dd, Artifact requestedMetadataArtifact, ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
-        return null;
-    }
-
-    public void originalToCachedModuleDescriptor(DependencyResolver resolver, ResolvedResource orginalMetadataRef, Artifact requestedMetadataArtifact, ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
-    }
-
-    public void clean() {
-    }
-
-    public void saveResolvedRevision(ModuleRevisionId dynamicMrid, String revision) {
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolverFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolverFactory.java
deleted file mode 100644
index 3810a76..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolverFactory.java
+++ /dev/null
@@ -1,50 +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.ivyservice;
-
-import org.apache.ivy.plugins.resolver.AbstractResolver;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.plugins.resolver.FileSystemResolver;
-import org.gradle.api.artifacts.ConfigurationContainer;
-import org.gradle.api.artifacts.dsl.IvyArtifactRepository;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
-import org.gradle.api.artifacts.maven.MavenResolver;
-import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
-import org.gradle.api.internal.file.FileResolver;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public interface ResolverFactory {
-    DependencyResolver createResolver(Object userDescription);
-
-    FileSystemResolver createFlatDirResolver(String name, File... roots);
-
-    AbstractResolver createMavenRepoResolver(String name, String root, String... jarRepoUrls);
-
-    AbstractResolver createMavenLocalResolver(String name);
-
-    GroovyMavenDeployer createMavenDeployer(String name, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer,
-                                           Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver);
-
-    MavenResolver createMavenInstaller(String name, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer,
-                                       Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver);
-
-    IvyArtifactRepository createIvyRepository();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
deleted file mode 100644
index 8234815..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
+++ /dev/null
@@ -1,82 +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.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.GradleException;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
-import org.gradle.api.internal.artifacts.DependencyInternal;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-
-import java.io.File;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class SelfResolvingDependencyResolver implements IvyDependencyResolver {
-    private final IvyDependencyResolver resolver;
-
-    public SelfResolvingDependencyResolver(IvyDependencyResolver resolver) {
-        this.resolver = resolver;
-    }
-
-    public IvyDependencyResolver getResolver() {
-        return resolver;
-    }
-
-    public ResolvedConfiguration resolve(final Configuration configuration, Ivy ivy, ModuleDescriptor moduleDescriptor) {
-        final ResolvedConfiguration resolvedConfiguration = resolver.resolve(configuration, ivy, moduleDescriptor);
-        final Set<DependencyInternal> dependencies = configuration.getAllDependencies(DependencyInternal.class);
-
-        return new ResolvedConfiguration() {
-            private final CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
-
-            public Set<File> getFiles(Spec<Dependency> dependencySpec) {
-                Set<File> files = new LinkedHashSet<File>();
-
-                Set<DependencyInternal> selectedDependencies = Specs.filterIterable(dependencies, dependencySpec);
-                for (DependencyInternal dependency : selectedDependencies) {
-                    resolveContext.add(dependency);
-                }
-                files.addAll(resolveContext.resolve().getFiles());
-                files.addAll(resolvedConfiguration.getFiles(dependencySpec));
-                return files;
-            }
-
-            public Set<ResolvedArtifact> getResolvedArtifacts() {
-                return resolvedConfiguration.getResolvedArtifacts();
-            }
-
-            public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
-                return resolvedConfiguration.getFirstLevelModuleDependencies();
-            }
-
-            public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<Dependency> dependencySpec) throws ResolveException {
-                return resolvedConfiguration.getFirstLevelModuleDependencies(dependencySpec);
-            }
-
-            public boolean hasError() {
-                return resolvedConfiguration.hasError();
-            }
-
-            public void rethrowFailure() throws GradleException {
-                resolvedConfiguration.rethrowFailure();
-            }
-        };
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
deleted file mode 100644
index ec11370..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
+++ /dev/null
@@ -1,38 +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.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public interface SettingsConverter {
-    String CHAIN_RESOLVER_NAME = "chain";
-    String CLIENT_MODULE_CHAIN_NAME = "clientModuleChain";
-    String CLIENT_MODULE_NAME = "clientModule";
-
-    IvySettings convertForPublish(List<DependencyResolver> publishResolvers, File gradleUserHome, DependencyResolver internalRepository);
-
-    IvySettings convertForResolve(List<DependencyResolver> classpathResolvers, File gradleUserHome, DependencyResolver internalRepository,
-                        Map<String, ModuleDescriptor> clientModuleRegistry);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyService.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyService.java
deleted file mode 100644
index 0c84ec1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyService.java
+++ /dev/null
@@ -1,74 +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.ivyservice;
-
-import org.gradle.api.internal.artifacts.IvyService;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.specs.Spec;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-import java.util.Set;
-import java.util.List;
-import java.util.Collections;
-import java.io.File;
-
-public class ShortcircuitEmptyConfigsIvyService implements IvyService {
-    private final ResolvedConfiguration emptyConfig = new ResolvedConfiguration() {
-        public boolean hasError() {
-            return false;
-        }
-
-        public void rethrowFailure() throws ResolveException {
-        }
-
-        public Set<File> getFiles(Spec<Dependency> dependencySpec) {
-            return Collections.emptySet();
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
-            return Collections.emptySet();
-        }
-
-        public Set<ResolvedDependency> getFirstLevelModuleDependencies(Spec<Dependency> dependencySpec) throws ResolveException {
-            return Collections.emptySet();
-        }
-
-        public Set<ResolvedArtifact> getResolvedArtifacts() {
-            return Collections.emptySet();
-        }
-    };
-    private final IvyService ivyService;
-
-    public ShortcircuitEmptyConfigsIvyService(IvyService ivyService) {
-        this.ivyService = ivyService;
-    }
-
-    public IvyService getIvyService() {
-        return ivyService;
-    }
-
-    public void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
-                        List<DependencyResolver> publishResolvers) {
-        ivyService.publish(configurationsToPublish, descriptorDestination, publishResolvers);
-    }
-
-    public ResolvedConfiguration resolve(Configuration configuration) {
-        if (configuration.getAllDependencies().isEmpty()) {
-            return emptyConfig;
-        }
-        return ivyService.resolve(configuration);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SnapshotVersionMatcher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SnapshotVersionMatcher.java
deleted file mode 100644
index 5e23471..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SnapshotVersionMatcher.java
+++ /dev/null
@@ -1,36 +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.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.plugins.version.AbstractVersionMatcher;
-
-/**
- * @author Hans Dockter
- */
-public class SnapshotVersionMatcher extends AbstractVersionMatcher {
-    public SnapshotVersionMatcher() {
-        super("snapshot");
-    }
-
-    public boolean isDynamic(ModuleRevisionId askedMrid) {
-        return askedMrid.getRevision().endsWith("-SNAPSHOT");
-    }
-
-    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
-        return askedMrid.getRevision().equals(foundMrid.getRevision());
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/AbstractModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/AbstractModuleDescriptorConverter.java
deleted file mode 100644
index 966b81a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/AbstractModuleDescriptorConverter.java
+++ /dev/null
@@ -1,51 +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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractModuleDescriptorConverter implements ModuleDescriptorConverter {
-    private ModuleDescriptorFactory moduleDescriptorFactory;
-
-    private ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
-    private DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
-
-    public AbstractModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
-                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
-                                            DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
-        this.moduleDescriptorFactory = moduleDescriptorFactory;
-        this.configurationsToModuleDescriptorConverter = configurationsToModuleDescriptorConverter;
-        this.dependenciesToModuleDescriptorConverter = dependenciesToModuleDescriptorConverter;
-    }
-
-    protected DefaultModuleDescriptor createCommonModuleDescriptor(Module module, Set<Configuration> configurations, IvySettings ivySettings) {
-        DefaultModuleDescriptor moduleDescriptor = moduleDescriptorFactory.createModuleDescriptor(module);
-        configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, configurations);
-        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(moduleDescriptor, configurations, ivySettings);
-        return moduleDescriptor;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
deleted file mode 100644
index 4c620c1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ArtifactsToModuleDescriptorConverter {
-    void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
deleted file mode 100644
index b971b28..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface ConfigurationsToModuleDescriptorConverter {
-    void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
deleted file mode 100644
index 067aec4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
-import org.gradle.util.GUtil;
-import org.gradle.util.WrapUtil;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.HashMap;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultArtifactsToModuleDescriptorConverter implements ArtifactsToModuleDescriptorConverter {
-    public final static ArtifactsExtraAttributesStrategy IVY_FILE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
-        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
-            return new HashMap<String, String>();
-        }
-    };
-
-    public final static ArtifactsExtraAttributesStrategy RESOLVE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
-        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
-            return WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
-        }
-    };
-
-    private ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy;
-
-    public DefaultArtifactsToModuleDescriptorConverter(ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy) {
-        this.artifactsExtraAttributesStrategy = artifactsExtraAttributesStrategy;
-    }
-
-    public void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
-        for (Configuration configuration : configurations) {
-            for (PublishArtifact publishArtifact : configuration.getArtifacts()) {
-                Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleDescriptor.getModuleRevisionId());
-                moduleDescriptor.addArtifact(configuration.getName(), ivyArtifact);
-            }
-        }
-    }
-
-    public Artifact createIvyArtifact(PublishArtifact publishArtifact, ModuleRevisionId moduleRevisionId) {
-        Map extraAttributes = artifactsExtraAttributesStrategy.createExtraAttributes(publishArtifact);
-        if (GUtil.isTrue(publishArtifact.getClassifier())) {
-            extraAttributes.put(Dependency.CLASSIFIER, publishArtifact.getClassifier());
-        }
-        return new DefaultArtifact(
-                moduleRevisionId,
-                publishArtifact.getDate(),
-                publishArtifact.getName(),
-                publishArtifact.getType(),
-                publishArtifact.getExtension(),
-                extraAttributes);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
deleted file mode 100644
index fb238f6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,46 +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.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.internal.artifacts.configurations.Configurations;
-
-import java.util.Arrays;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultConfigurationsToModuleDescriptorConverter implements ConfigurationsToModuleDescriptorConverter {
-    public void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
-        for (Configuration configuration : configurations) {
-            moduleDescriptor.addConfiguration(getIvyConfiguration(configuration));
-        }
-    }
-
-    public org.apache.ivy.core.module.descriptor.Configuration getIvyConfiguration(Configuration configuration) {
-        String[] superConfigs = Configurations.getNames(configuration.getExtendsFrom(), false).toArray(new String[configuration.getExtendsFrom().size()]);
-        Arrays.sort(superConfigs);
-        return new org.apache.ivy.core.module.descriptor.Configuration(
-                configuration.getName(),
-                configuration.isVisible() ? org.apache.ivy.core.module.descriptor.Configuration.Visibility.PUBLIC : org.apache.ivy.core.module.descriptor.Configuration.Visibility.PRIVATE,
-                configuration.getDescription(),
-                superConfigs,
-                configuration.isTransitive(),
-                null);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
deleted file mode 100644
index 99f8a9d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
+++ /dev/null
@@ -1,41 +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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
-import org.apache.ivy.core.module.id.ArtifactId;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
-import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.util.GUtil;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
-    public DefaultExcludeRule createExcludeRule(String configurationName, ExcludeRule excludeRule) {
-        String org = GUtil.elvis(excludeRule.getExcludeArgs().get(ExcludeRule.GROUP_KEY), PatternMatcher.ANY_EXPRESSION);
-        String module = GUtil.elvis(excludeRule.getExcludeArgs().get(ExcludeRule.MODULE_KEY), PatternMatcher.ANY_EXPRESSION);
-        DefaultExcludeRule ivyExcludeRule = new DefaultExcludeRule(new ArtifactId(
-                new ModuleId(org, module), PatternMatcher.ANY_EXPRESSION,
-                PatternMatcher.ANY_EXPRESSION,
-                PatternMatcher.ANY_EXPRESSION),
-                ExactPatternMatcher.INSTANCE, null);
-        ivyExcludeRule.addConfiguration(configurationName);
-        return ivyExcludeRule;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
deleted file mode 100644
index 9aa9809..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultModuleDescriptorFactory implements ModuleDescriptorFactory {
-    public DefaultModuleDescriptor createModuleDescriptor(Module module) {
-        return new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(module),
-                module.getStatus(), null);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
deleted file mode 100644
index c0fecc7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.util.Clock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class PublishModuleDescriptorConverter implements ModuleDescriptorConverter {
-    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
-    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
-
-    private static Logger logger = LoggerFactory.getLogger(PublishModuleDescriptorConverter.class);
-    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
-    private ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter;
-
-    public PublishModuleDescriptorConverter(ModuleDescriptorConverter resolveModuleDescriptorConverter,
-                                            ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
-        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
-        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
-    }
-
-    public ModuleDescriptor convert(Set<Configuration> configurations, Module module, IvySettings settings) {
-        Clock clock = new Clock();
-        DefaultModuleDescriptor moduleDescriptor = (DefaultModuleDescriptor) resolveModuleDescriptorConverter.convert(configurations, module, settings);
-        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
-        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, configurations);
-        logger.debug("Timing: Ivy convert for publish took {}", clock.getTime());
-        return moduleDescriptor;
-    }
-
-    public ArtifactsToModuleDescriptorConverter getArtifactsToModuleDescriptorConverter() {
-        return artifactsToModuleDescriptorConverter;
-    }
-
-    public void setArtifactsToModuleDescriptorConverter(ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
-        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
deleted file mode 100644
index 6e64ec0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
-import org.gradle.util.Clock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class ResolveModuleDescriptorConverter extends AbstractModuleDescriptorConverter implements ModuleDescriptorConverter {
-    private static Logger logger = LoggerFactory.getLogger(ResolveModuleDescriptorConverter.class);
-
-    public ResolveModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
-                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
-                                            DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
-        super(moduleDescriptorFactory, configurationsToModuleDescriptorConverter, dependenciesToModuleDescriptorConverter);
-    }
-
-    public ModuleDescriptor convert(Set<Configuration> configurations, Module module, IvySettings settings) {
-        assert configurations.size() > 0;
-        Clock clock = new Clock();
-        DefaultModuleDescriptor moduleDescriptor = createCommonModuleDescriptor(module, configurations, settings);
-        logger.debug("Timing: Ivy convert for resolve took {}", clock.getTime());
-        return moduleDescriptor;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
deleted file mode 100644
index 2d4287b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
+++ /dev/null
@@ -1,123 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.*;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.util.WrapUtil;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractDependencyDescriptorFactoryInternal implements DependencyDescriptorFactoryInternal {
-    private ExcludeRuleConverter excludeRuleConverter;
-
-    public AbstractDependencyDescriptorFactoryInternal(ExcludeRuleConverter excludeRuleConverter) {
-        this.excludeRuleConverter = excludeRuleConverter;
-    }
-
-    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
-        DependencyDescriptor newDescriptor = createDependencyDescriptor(dependency, configuration, moduleDescriptor, moduleRevisionId);
-        DefaultDependencyDescriptor existingDependencyDescriptor = findExistingDescriptor(moduleDescriptor, newDescriptor);
-
-        if (existingDependencyDescriptor == null) {
-            moduleDescriptor.addDependency(newDescriptor);
-        } else {
-            existingDependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
-        }
-    }
-
-    protected abstract DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration,
-                                                            ModuleDescriptor moduleDescriptor, ModuleRevisionId moduleRevisionId);
-
-    private DefaultDependencyDescriptor findExistingDescriptor(DefaultModuleDescriptor moduleDescriptor, DependencyDescriptor targetDescriptor) {
-        for (DependencyDescriptor dependencyDescriptor : moduleDescriptor.getDependencies()) {
-
-            if (dependencyDescriptor.getDependencyRevisionId().equals(targetDescriptor.getDependencyRevisionId())) {
-                HashSet<DependencyArtifactDescriptor> nextDependencies =
-                        new HashSet<DependencyArtifactDescriptor>(Arrays.asList(dependencyDescriptor.getAllDependencyArtifacts()));
-                HashSet<DependencyArtifactDescriptor> targetDependencies =
-                        new HashSet<DependencyArtifactDescriptor>(Arrays.asList(targetDescriptor.getAllDependencyArtifacts()));
-
-                if (nextDependencies.equals(targetDependencies)) {
-                    return (DefaultDependencyDescriptor) dependencyDescriptor;
-                }
-            }
-        }
-        return null;
-    }
-
-    protected void addExcludesArtifactsAndDependencies(String configuration, ModuleDependency dependency,
-                                                     DefaultDependencyDescriptor dependencyDescriptor) {
-        addArtifacts(configuration, dependency.getArtifacts(), dependencyDescriptor);
-        addExcludes(configuration, dependency.getExcludeRules(), dependencyDescriptor);
-        addDependencyConfiguration(configuration, dependency, dependencyDescriptor);
-    }
-
-    private void addArtifacts(String configuration, Set<DependencyArtifact> artifacts,
-                              DefaultDependencyDescriptor dependencyDescriptor) {
-        for (DependencyArtifact artifact : artifacts) {
-            DefaultDependencyArtifactDescriptor artifactDescriptor;
-            try {
-                artifactDescriptor = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, artifact.getName(),
-                        artifact.getType(),
-                        artifact.getExtension() != null ? artifact.getExtension() : artifact.getType(),
-                        artifact.getUrl() != null ? new URL(artifact.getUrl()) : null,
-                        artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER,
-                                artifact.getClassifier()) : null);
-            } catch (MalformedURLException e) {
-                throw new InvalidUserDataException("URL for artifact can't be parsed: " + artifact.getUrl(), e);
-            }
-            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
-        }
-    }
-
-    private void addDependencyConfiguration(String configuration, ModuleDependency dependency,
-                                            DefaultDependencyDescriptor dependencyDescriptor) {
-        dependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
-    }
-
-    private void addExcludes(String configuration, Set<ExcludeRule> excludeRules,
-                             DefaultDependencyDescriptor dependencyDescriptor) {
-        for (ExcludeRule excludeRule : excludeRules) {
-            dependencyDescriptor.addExcludeRule(configuration, excludeRuleConverter.createExcludeRule(configuration,
-                    excludeRule));
-        }
-    }
-
-    public ExcludeRuleConverter getExcludeRuleConverter() {
-        return excludeRuleConverter;
-    }
-
-    public void setExcludeRuleConverter(ExcludeRuleConverter excludeRuleConverter) {
-        this.excludeRuleConverter = excludeRuleConverter;
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
deleted file mode 100644
index dfe1063..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
+++ /dev/null
@@ -1,69 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.util.WrapUtil;
-
-import java.util.Map;
-
-/**
- * @author Hans Dockter
-*/
-public class ClientModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
-    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule;
-    private Map<String, ModuleDescriptor> clientModuleRegistry;
-
-    public ClientModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule, Map<String, ModuleDescriptor> clientModuleRegistry) {
-        super(excludeRuleConverter);
-        this.moduleDescriptorFactoryForClientModule = moduleDescriptorFactoryForClientModule;
-        this.clientModuleRegistry = clientModuleRegistry;
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        return IvyUtil.createModuleRevisionId(dependency,
-                WrapUtil.toMap(getClientModule(dependency).CLIENT_MODULE_KEY, getClientModule(dependency).getId()));
-    }
-
-    public DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
-                                                           ModuleRevisionId moduleRevisionId) {
-        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent,
-                moduleRevisionId, getClientModule(dependency).isForce(),
-                false, getClientModule(dependency).isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, getClientModule(dependency), dependencyDescriptor);
-
-        ModuleDescriptor moduleDescriptor = moduleDescriptorFactoryForClientModule.createModuleDescriptor(
-                dependencyDescriptor.getDependencyRevisionId(), getClientModule(dependency).getDependencies());
-        clientModuleRegistry.put(getClientModule(dependency).getId(), moduleDescriptor);
-
-        return dependencyDescriptor;
-    }
-
-    private ClientModule getClientModule(ModuleDependency dependency) {
-        return (ClientModule) dependency;
-    }
-
-    public boolean canConvert(ModuleDependency dependency) {
-        return dependency instanceof ClientModule;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
deleted file mode 100644
index 8a330a0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.conflict.LatestConflictManager;
-import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultDependenciesToModuleDescriptorConverter implements DependenciesToModuleDescriptorConverter {
-    private DependencyDescriptorFactory dependencyDescriptorFactory;
-    private ExcludeRuleConverter excludeRuleConverter;
-
-    public DefaultDependenciesToModuleDescriptorConverter(DependencyDescriptorFactory dependencyDescriptorFactory,
-                                                          ExcludeRuleConverter excludeRuleConverter) {
-        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
-        this.excludeRuleConverter = excludeRuleConverter;
-    }
-
-    public void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations,
-                                         IvySettings ivySettings) {
-        assert !configurations.isEmpty();
-        addDependencies(moduleDescriptor, configurations);
-        addExcludeRules(moduleDescriptor, configurations);
-        addConflictManager(moduleDescriptor, ivySettings);
-    }
-
-    private void addDependencies(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
-        for (Configuration configuration : configurations) {
-            for (ModuleDependency dependency : configuration.getDependencies(ModuleDependency.class)) {
-                dependencyDescriptorFactory.addDependencyDescriptor(configuration.getName(), moduleDescriptor, dependency);
-            }
-        }
-    }
-
-    private void addExcludeRules(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
-        for (Configuration configuration : configurations) {
-            for (ExcludeRule excludeRule : configuration.getExcludeRules()) {
-                org.apache.ivy.core.module.descriptor.ExcludeRule rule = excludeRuleConverter.createExcludeRule(
-                        configuration.getName(), excludeRule);
-                moduleDescriptor.addExcludeRule(rule);
-            }
-        }
-    }
-
-    private void addConflictManager(DefaultModuleDescriptor moduleDescriptor, IvySettings ivySettings) {
-        LatestConflictManager conflictManager = new LatestConflictManager(new LatestRevisionStrategy());
-        conflictManager.setSettings(ivySettings);
-        moduleDescriptor.addConflictManager(new ModuleId(ExactPatternMatcher.ANY_EXPRESSION,
-                ExactPatternMatcher.ANY_EXPRESSION), ExactPatternMatcher.INSTANCE,
-                conflictManager);
-    }
-
-    public DependencyDescriptorFactory getDependencyDescriptorFactory() {
-        return dependencyDescriptorFactory;
-    }
-
-    public void setDependencyDescriptorFactory(DependencyDescriptorFactory dependencyDescriptorFactory) {
-        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
-    }
-
-    public ExcludeRuleConverter getExcludeRuleConverter() {
-        return excludeRuleConverter;
-    }
-
-    public void setExcludeRuleConverter(ExcludeRuleConverter excludeRuleConverter) {
-        this.excludeRuleConverter = excludeRuleConverter;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
deleted file mode 100644
index 07ca8cb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface DependenciesToModuleDescriptorConverter {
-    void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations, IvySettings ivySettings);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
deleted file mode 100644
index 5fd574f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
+++ /dev/null
@@ -1,32 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleDependency;
-
-/**
- * @author Hans Dockter
- */
-public interface DependencyDescriptorFactory {
-    String PROJECT_PATH_KEY = "org.gradle.projectPath";
-
-    void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
-                                 ModuleDependency dependency);
-
-    ModuleRevisionId createModuleRevisionId(ModuleDependency dependency);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
deleted file mode 100644
index f0f6818..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
+++ /dev/null
@@ -1,56 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.util.WrapUtil;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DependencyDescriptorFactoryDelegate implements DependencyDescriptorFactory {
-    private Set<DependencyDescriptorFactoryInternal> dependencyDescriptorFactories;
-
-    public DependencyDescriptorFactoryDelegate(DependencyDescriptorFactoryInternal... dependencyDescriptorFactories) {
-        this.dependencyDescriptorFactories = WrapUtil.toSet(dependencyDescriptorFactories);
-    }
-
-    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
-                                        ModuleDependency dependency) {
-        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
-        factoryInternal.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
-        return factoryInternal.createModuleRevisionId(dependency);
-    }
-
-    private DependencyDescriptorFactoryInternal findFactoryForDependency(ModuleDependency dependency) {
-        for (DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal : dependencyDescriptorFactories) {
-            if (dependencyDescriptorFactoryInternal.canConvert(dependency)) {
-                return dependencyDescriptorFactoryInternal;
-            }
-        }
-        throw new InvalidUserDataException("Can't map dependency of type: " + dependency.getClass());
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
deleted file mode 100644
index 49ddf60..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
+++ /dev/null
@@ -1,55 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-
-/**
- * @author Hans Dockter
-*/
-public class ExternalModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
-    public ExternalModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
-        super(excludeRuleConverter);
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        return IvyUtil.createModuleRevisionId(dependency);
-    }
-
-    public DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
-                                                           ModuleRevisionId moduleRevisionId) {
-        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent,
-                moduleRevisionId, getExternalModuleDependency(dependency).isForce(),
-                getExternalModuleDependency(dependency).isChanging(), getExternalModuleDependency(dependency).isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, getExternalModuleDependency(dependency), dependencyDescriptor);
-        return dependencyDescriptor;
-    }
-
-    private ExternalModuleDependency getExternalModuleDependency(ModuleDependency dependency) {
-        return (ExternalModuleDependency) dependency;
-    }
-
-    public boolean canConvert(ModuleDependency dependency) {
-        return dependency instanceof ExternalModuleDependency;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
deleted file mode 100644
index e2727f9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
+++ /dev/null
@@ -1,79 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.util.WrapUtil;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
-    public final static ProjectDependencyDescriptorStrategy IVY_FILE_DESCRIPTOR_STRATEGY =
-            new ProjectDependencyDescriptorStrategy() {
-                public ModuleRevisionId createModuleRevisionId(ProjectDependency dependency) {
-                    Module module = ((ProjectInternal) dependency.getDependencyProject()).getModule();
-                    return IvyUtil.createModuleRevisionId(module);
-                }
-                public boolean isChanging() {
-                    return false;
-                }
-            };
-
-    public final static ProjectDependencyDescriptorStrategy RESOLVE_DESCRIPTOR_STRATEGY =
-            new ProjectDependencyDescriptorStrategy() {
-                public ModuleRevisionId createModuleRevisionId(ProjectDependency dependency) {
-                    Module module = ((ProjectInternal) dependency.getDependencyProject()).getModule();
-                    return IvyUtil.createModuleRevisionId(module, WrapUtil.toMap(DependencyDescriptorFactory.PROJECT_PATH_KEY,
-                            dependency.getDependencyProject().getPath()));
-                }
-                public boolean isChanging() {
-                    return true;
-                }
-            };
-
-    private ProjectDependencyDescriptorStrategy projectDependencyDescriptorStrategy;
-
-    public ProjectDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ProjectDependencyDescriptorStrategy projectDependencyDescriptorStrategy) {
-        super(excludeRuleConverter);
-        this.projectDependencyDescriptorStrategy = projectDependencyDescriptorStrategy;
-    }
-
-    public DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
-                                                           ModuleRevisionId moduleRevisionId) {
-        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent,
-                moduleRevisionId, false, projectDependencyDescriptorStrategy.isChanging(), dependency.isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
-        return dependencyDescriptor;
-    }
-
-    public boolean canConvert(ModuleDependency dependency) {
-        return dependency instanceof ProjectDependency;
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        return projectDependencyDescriptorStrategy.createModuleRevisionId((ProjectDependency) dependency);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorStrategy.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorStrategy.java
deleted file mode 100644
index 3c7ac80..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorStrategy.java
+++ /dev/null
@@ -1,27 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ProjectDependency;
-
-/**
- * @author Hans Dockter
- */
-public interface ProjectDependencyDescriptorStrategy {
-    ModuleRevisionId createModuleRevisionId(ProjectDependency dependency);
-    boolean isChanging();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
index a426804..abdc592 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
@@ -23,19 +23,20 @@ import org.gradle.api.tasks.TaskDependency;
  * @author Hans Dockter
  */
 public abstract class AbstractPublishArtifact implements PublishArtifact {
-    private TaskDependency taskDependency;
+    private final DefaultTaskDependency taskDependency;
 
     public AbstractPublishArtifact(Object... tasks) {
         taskDependency = new DefaultTaskDependency();
-        ((DefaultTaskDependency) taskDependency).add(tasks);
+        taskDependency.add(tasks);
     }
 
     public TaskDependency getBuildDependencies() {
         return taskDependency;
     }
 
-    public void setTaskDependency(TaskDependency taskDependency) {
-        this.taskDependency = taskDependency;
+    public AbstractPublishArtifact builtBy(Object... tasks) {
+        taskDependency.add(tasks);
+        return this;
     }
 
     @Override
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainer.java
deleted file mode 100644
index 4318c91..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainer.java
+++ /dev/null
@@ -1,47 +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;
-
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.artifacts.ArtifactContainer;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultArtifactContainer implements ArtifactContainer {
-    private Set<PublishArtifact> artifacts = new HashSet<PublishArtifact>();
-
-    public DefaultArtifactContainer() {
-    }
-    
-    public void addArtifacts(PublishArtifact... publishArtifacts) {
-        artifacts.addAll(Arrays.asList(publishArtifacts));
-    }
-
-    public Set<PublishArtifact> getArtifacts() {
-        return artifacts;
-    }
-
-    public Set<PublishArtifact> getArtifacts(Spec<PublishArtifact> spec) {
-        return new HashSet<PublishArtifact>(Specs.filterIterable(getArtifacts(), spec));
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
index 2707778..8591a4c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
@@ -16,13 +16,15 @@
 
 package org.gradle.api.internal.artifacts.publish;
 
+import org.gradle.api.artifacts.ConfigurablePublishArtifact;
+
 import java.io.File;
 import java.util.Date;
 
 /**
  * @author Hans Dockter
  */
-public class DefaultPublishArtifact extends AbstractPublishArtifact {
+public class DefaultPublishArtifact extends AbstractPublishArtifact implements ConfigurablePublishArtifact {
     private String name;
     private String extension;
     private String type;
@@ -41,6 +43,12 @@ public class DefaultPublishArtifact extends AbstractPublishArtifact {
         this.file = file;
     }
 
+    @Override
+    public DefaultPublishArtifact builtBy(Object... tasks) {
+        super.builtBy(tasks);
+        return this;
+    }
+
     public String getName() {
         return name;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomMetaInfoProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomMetaInfoProvider.java
deleted file mode 100644
index 961ec24..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomMetaInfoProvider.java
+++ /dev/null
@@ -1,22 +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;
-
-import java.io.File;
-
-public interface MavenPomMetaInfoProvider {
-    File getMavenPomDir();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/PomFilter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/PomFilter.java
deleted file mode 100644
index 5ead72e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/PomFilter.java
+++ /dev/null
@@ -1,34 +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.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.maven.PublishFilter;
-
-/**
- * @author Hans Dockter
- */
-public interface PomFilter {
-    String getName();
-
-    PublishFilter getFilter();
-
-    void setFilter(PublishFilter filter);
-
-    MavenPom getPomTemplate();
-
-    void setPomTemplate(MavenPom pom);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java
index 08e4c4a..e07b625 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java
@@ -16,15 +16,8 @@
 package org.gradle.api.internal.artifacts.repositories;
 
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.dsl.ArtifactRepository;
-
-import java.util.Collection;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
 
 public interface ArtifactRepositoryInternal extends ArtifactRepository {
-    /**
-     * Creates the resolvers for this repository.
-     *
-     * @param resolvers The collection to add the resolvers for this repository.
-     */
-    void createResolvers(Collection<DependencyResolver> resolvers);
+    DependencyResolver createResolver();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultInternalRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultInternalRepository.java
deleted file mode 100644
index 6a2a309..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultInternalRepository.java
+++ /dev/null
@@ -1,163 +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.repositories;
-
-import org.apache.ivy.core.IvyContext;
-import org.apache.ivy.core.cache.ArtifactOrigin;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.report.DownloadReport;
-import org.apache.ivy.core.report.DownloadStatus;
-import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
-import org.apache.ivy.core.resolve.DownloadOptions;
-import org.apache.ivy.core.resolve.ResolveData;
-import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.repository.file.FileRepository;
-import org.apache.ivy.plugins.repository.file.FileResource;
-import org.apache.ivy.plugins.resolver.BasicResolver;
-import org.apache.ivy.plugins.resolver.util.ResolvedResource;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ResolverContainer;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.ivyservice.NoOpRepositoryCacheManager;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.util.ReflectionUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultInternalRepository extends BasicResolver implements InternalRepository {
-    private final ModuleDescriptorConverter moduleDescriptorConverter;
-    private final Gradle gradle;
-
-    public DefaultInternalRepository(Gradle gradle, ModuleDescriptorConverter moduleDescriptorConverter) {
-        this.gradle = gradle;
-        this.moduleDescriptorConverter = moduleDescriptorConverter;
-        setName(ResolverContainer.INTERNAL_REPOSITORY_NAME);
-        setRepositoryCacheManager(new NoOpRepositoryCacheManager(getName()));
-    }
-
-    @Override
-    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
-        ModuleDescriptor moduleDescriptor = findProject(dd);
-        if (moduleDescriptor == null) {
-            return data.getCurrentResolvedModuleRevision();
-        }
-
-        IvyContext context = IvyContext.pushNewCopyContext();
-        try {
-            context.setDependencyDescriptor(dd);
-            context.setResolveData(data);
-            MetadataArtifactDownloadReport downloadReport = new MetadataArtifactDownloadReport(moduleDescriptor.getMetadataArtifact());
-            downloadReport.setDownloadStatus(DownloadStatus.NO);
-            downloadReport.setSearched(false);
-            return new ResolvedModuleRevision(this, this, moduleDescriptor, downloadReport);
-        } finally {
-            IvyContext.popContext();
-        }
-    }
-
-    private ModuleDescriptor findProject(DependencyDescriptor descriptor) {
-        String projectPathValue = descriptor.getAttribute(DependencyDescriptorFactory.PROJECT_PATH_KEY);
-        if (projectPathValue == null) {
-            return null;
-        }
-        Project project = gradle.getRootProject().project(projectPathValue);
-        Module projectModule = ((ProjectInternal) project).getModule();
-        ModuleDescriptor projectDescriptor = moduleDescriptorConverter.convert(project.getConfigurations().getAll(),
-                projectModule, IvyContext.getContext().getIvy().getSettings());
-
-        for (DependencyArtifactDescriptor artifactDescriptor : descriptor.getAllDependencyArtifacts()) {
-            for (Artifact artifact : projectDescriptor.getAllArtifacts()) {
-                if (artifact.getName().equals(artifactDescriptor.getName()) && artifact.getExt().equals(
-                        artifactDescriptor.getExt())) {
-                    String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
-                    ReflectionUtil.invoke(artifactDescriptor, "setExtraAttribute",
-                            new Object[]{DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, path});
-                }
-            }
-        }
-
-        return projectDescriptor;
-    }
-
-    @Override
-    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
-        String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
-        if (path == null) {            
-            return null;
-        }
-        File file = new File(path);
-        return new ResolvedResource(new FileResource(new FileRepository(), file), artifact.getId().getRevision());
-    }
-
-    @Override
-    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
-        DownloadReport dr = new DownloadReport();
-        for (Artifact artifact : artifacts) {
-            ArtifactDownloadReport artifactDownloadReport = new ArtifactDownloadReport(artifact);
-            String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
-            if (path == null) {
-                artifactDownloadReport.setDownloadStatus(DownloadStatus.FAILED);
-            } else {
-                File file = new File(path);
-                artifactDownloadReport.setDownloadStatus(DownloadStatus.SUCCESSFUL);
-                artifactDownloadReport.setArtifactOrigin(new ArtifactOrigin(artifact, true, getName()));
-                artifactDownloadReport.setLocalFile(file);
-                artifactDownloadReport.setSize(file.length());
-            }
-            dr.addArtifactReport(artifactDownloadReport);
-        }
-        return dr;
-    }
-
-    @Override
-    protected Resource getResource(String source) throws IOException {
-        return null;
-    }
-
-    @Override
-    protected Collection findNames(Map tokenValues, String token) {
-        return null;
-    }
-
-    @Override
-    protected long get(Resource resource, File dest) throws IOException {
-        return 0;
-    }
-
-    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
-        return null;
-    }
-
-    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
new file mode 100644
index 0000000..b2fe5a8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.repositories;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+
+public class FixedResolverArtifactRepository implements ArtifactRepository, ArtifactRepositoryInternal {
+    protected final DependencyResolver resolver;
+
+    public FixedResolverArtifactRepository(DependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public String getName() {
+        return resolver.getName();
+    }
+
+    public void setName(String name) {
+        resolver.setName(name);
+    }
+
+    public DependencyResolver createResolver() {
+        return resolver;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/InternalRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/InternalRepository.java
deleted file mode 100644
index 204ba40..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/InternalRepository.java
+++ /dev/null
@@ -1,24 +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.repositories;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-/**
- * @author Hans Dockter
- */
-public interface InternalRepository extends DependencyResolver {
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/WebdavRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/WebdavRepository.java
deleted file mode 100644
index d395a96..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/WebdavRepository.java
+++ /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.api.internal.artifacts.repositories;
-
-import org.apache.commons.httpclient.HttpsURL;
-import org.apache.ivy.plugins.repository.url.URLRepository;
-import org.apache.webdav.lib.WebdavResource;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * @author Hans Dockter
- */
-public class WebdavRepository extends URLRepository {
-    private String userPassword;
-
-    private String user;
-
-    public String getUserPassword() {
-        return userPassword;
-    }
-
-    public void setUserPassword(String userPassword) {
-        this.userPassword = userPassword;
-    }
-
-    public String getUser() {
-        return user;
-    }
-
-    public void setUser(String user) {
-        this.user = user;
-    }
-
-    public void put(File source, String destination, boolean overwrite) throws IOException {
-        int fileNameStart = destination.lastIndexOf('/');
-        String baseUrl = destination.substring(0, fileNameStart + 1);
-        String destinationFileName =  destination.substring(fileNameStart + 1);
-        HttpsURL hrl = new HttpsURL(baseUrl);
-        hrl.setUserinfo(user, userPassword);
-        WebdavResource wdr = new WebdavResource(hrl);
-        wdr.putMethod(wdr.getPath() + '/' + destinationFileName, source);
-        wdr.close();
-    }
-
-    //    Alternative implementation with httpclient only. Unfortunately this is slower.
-//
-//    public void put(File source, String destination, boolean overwrite) throws IOException {
-//        HttpClient client = new HttpClient();
-//        HttpState state = client.getState();
-//        PutMethod putMethod = new PutMethod(destination);
-//        Credentials credentials = new UsernamePasswordCredentials("hans_d", "magus96");
-//        state.setCredentials(null, null, credentials);
-//        logger.info("Publishing: " + source.getAbsolutePath());
-////        putMethod.setRequestEntity(new InputStreamRequestEntity(new FileInputStream(source)));
-//        putMethod.setRequestEntity(new FileRequestEntity(source, "application/binary"));
-//        try {
-//            // execute the GET
-//            int status = client.executeMethod(putMethod);
-//            // evaluate status
-//        } finally {
-//            // release any connection resources used by the method
-//            putMethod.releaseConnection();
-//        }
-//    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/Cache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/Cache.java
new file mode 100644
index 0000000..78b53f1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/Cache.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.internal.Factory;
+
+public interface Cache<K, V>  {
+    <T extends K> V get(T key, Factory<? extends V> factory);
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java
new file mode 100644
index 0000000..0a24c09
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.concurrent.Synchronizer;
+
+public class CacheAccessSerializer<K, V> implements Cache<K, V> {
+    
+    final private Synchronizer synchronizer = new Synchronizer();
+    final private Cache<K, V> cache;
+    
+    public CacheAccessSerializer(Cache<K, V> cache) {
+        this.cache = cache;
+    }
+
+    public <T extends K> V get(final T key, final Factory<? extends V> factory) {
+        return synchronizer.synchronize(new Factory<V>() {
+            public V create() {
+                return cache.get(key, factory);
+            }
+        });
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheSupport.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheSupport.java
new file mode 100644
index 0000000..2dd8e0c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheSupport.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.internal.Factory;
+
+abstract public class CacheSupport<K, V> implements Cache<K, V> {
+    
+    public <T extends K> V get(T key, Factory<? extends V> factory) {
+        V value = doGet(key);
+        if (value == null) {
+            value = factory.create();
+            doCache(key, value);
+        }
+        
+        return value;
+    }
+
+    abstract protected <T extends K> V doGet(T key);
+    
+    abstract protected <T extends K, N extends V> void doCache(T key, N value);
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/MapBackedCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/MapBackedCache.java
new file mode 100644
index 0000000..7f218f5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/MapBackedCache.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.cache;
+
+import java.util.Map;
+
+public class MapBackedCache<K, V> extends CacheSupport<K, V> {
+    
+    private final Map<K, V> map;
+    
+    public MapBackedCache(Map<K, V> map) {
+        this.map = map;
+    }
+        
+    protected <T extends K> V doGet(T key) {
+        return map.get(key);
+    }
+    
+    protected <T extends K, N extends V> void doCache(T key, N value) {
+        map.put(key, value);
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.java
index b5cea28..a93221e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepository.java
@@ -15,19 +15,16 @@
  */
 package org.gradle.api.internal.changedetection;
 
-import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentIndexedCache;
 
 public class CacheBackedFileSnapshotRepository implements FileSnapshotRepository {
-    private final CacheRepository repository;
-    private PersistentIndexedCache<Object, Object> cache;
+    private final PersistentIndexedCache<Object, Object> cache;
 
-    public CacheBackedFileSnapshotRepository(CacheRepository repository) {
-        this.repository = repository;
+    public CacheBackedFileSnapshotRepository(TaskArtifactStateCacheAccess cacheAccess) {
+        cache = cacheAccess.createCache("fileSnapshots", Object.class, Object.class);
     }
 
     public Long add(FileCollectionSnapshot snapshot) {
-        open();
         Long id = (Long) cache.get("nextId");
         if (id == null) {
             id = 1L;
@@ -38,18 +35,10 @@ public class CacheBackedFileSnapshotRepository implements FileSnapshotRepository
     }
 
     public FileCollectionSnapshot get(Long id) {
-        open();
         return (FileCollectionSnapshot) cache.get(id);
     }
 
     public void remove(Long id) {
-        open();
         cache.remove(id);
     }
-
-    private void open() {
-        if (cache == null) {
-            cache = repository.cache("fileSnapshots").open().openIndexedCache();
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
index 67678fe..247daec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheBackedTaskHistoryRepository.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.changedetection;
 
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.TaskInternal;
-import org.gradle.cache.CacheRepository;
 import org.gradle.cache.DefaultSerializer;
 import org.gradle.cache.PersistentIndexedCache;
 
@@ -28,28 +28,27 @@ import java.util.List;
 import java.util.Set;
 
 public class CacheBackedTaskHistoryRepository implements TaskHistoryRepository {
-    private final CacheRepository repository;
+    private final TaskArtifactStateCacheAccess cacheAccess;
     private final FileSnapshotRepository snapshotRepository;
-    private PersistentIndexedCache<String, TaskHistory> taskHistoryCache;
-    private DefaultSerializer<TaskHistory> serializer;
+    private final PersistentIndexedCache<String, TaskHistory> taskHistoryCache;
+    private final DefaultSerializer<TaskHistory> serializer = new DefaultSerializer<TaskHistory>();
 
-    public CacheBackedTaskHistoryRepository(CacheRepository repository, FileSnapshotRepository snapshotRepository) {
-        this.repository = repository;
+    public CacheBackedTaskHistoryRepository(TaskArtifactStateCacheAccess cacheAccess, FileSnapshotRepository snapshotRepository) {
+        this.cacheAccess = cacheAccess;
         this.snapshotRepository = snapshotRepository;
+        taskHistoryCache = cacheAccess.createCache("taskArtifacts", String.class, TaskHistory.class, serializer);
     }
 
     public History getHistory(final TaskInternal task) {
-        if (taskHistoryCache == null) {
-            serializer = new DefaultSerializer<TaskHistory>();
-            taskHistoryCache = repository.cache("taskArtifacts").forObject(task.getProject().getGradle()).open().openIndexedCache(serializer);
-        }
         final TaskHistory history = loadHistory(task);
         final LazyTaskExecution currentExecution = new LazyTaskExecution();
         currentExecution.snapshotRepository = snapshotRepository;
+        currentExecution.cacheAccess = cacheAccess;
         currentExecution.setOutputFiles(outputFiles(task));
         final LazyTaskExecution previousExecution = findPreviousExecution(currentExecution, history);
         if (previousExecution != null) {
             previousExecution.snapshotRepository = snapshotRepository;
+            previousExecution.cacheAccess = cacheAccess;
         }
         history.configurations.add(0, currentExecution);
 
@@ -138,6 +137,7 @@ public class CacheBackedTaskHistoryRepository implements TaskHistoryRepository {
         private transient FileSnapshotRepository snapshotRepository;
         private transient FileCollectionSnapshot inputFilesSnapshot;
         private transient FileCollectionSnapshot outputFilesSnapshot;
+        private transient TaskArtifactStateCacheAccess cacheAccess;
 
         @Override
         public FileCollectionSnapshot getInputFilesSnapshot() {
@@ -156,7 +156,11 @@ public class CacheBackedTaskHistoryRepository implements TaskHistoryRepository {
         @Override
         public FileCollectionSnapshot getOutputFilesSnapshot() {
             if (outputFilesSnapshot == null) {
-                outputFilesSnapshot = snapshotRepository.get(outputFilesSnapshotId);
+                outputFilesSnapshot = cacheAccess.useCache("fetch output files", new Factory<FileCollectionSnapshot>() {
+                    public FileCollectionSnapshot create() {
+                        return snapshotRepository.get(outputFilesSnapshotId);
+                    }
+                });
             }
             return outputFilesSnapshot;
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheLockHandlingTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheLockHandlingTaskExecuter.java
new file mode 100644
index 0000000..9663558
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CacheLockHandlingTaskExecuter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.changedetection;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+
+public class CacheLockHandlingTaskExecuter implements TaskExecuter {
+    private final TaskExecuter executer;
+    private final TaskArtifactStateCacheAccess cacheAccess;
+
+    public CacheLockHandlingTaskExecuter(TaskExecuter executer, TaskArtifactStateCacheAccess cacheAccess) {
+        this.executer = executer;
+        this.cacheAccess = cacheAccess;
+    }
+
+    public void execute(final TaskInternal task, final TaskStateInternal state) {
+        cacheAccess.longRunningOperation(String.format("execute %s", task), new Runnable() {
+            public void run() {
+                executer.execute(task, state);
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
index 5a0d9ca..e26fa2c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal.changedetection;
 
-import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.cache.Serializer;
 
@@ -26,9 +25,9 @@ public class CachingHasher implements Hasher {
     private final Hasher hasher;
     private long timestamp;
 
-    public CachingHasher(Hasher hasher, CacheRepository cacheRepository) {
+    public CachingHasher(Hasher hasher, TaskArtifactStateCacheAccess cacheAccess) {
         this.hasher = hasher;
-        cache = cacheRepository.cache("fileHashes").open().openIndexedCache(new FileInfoSerializer());
+        cache = cacheAccess.createCache("fileHashes", File.class, FileInfo.class, new FileInfoSerializer());
     }
 
     public byte[] hash(File file) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
index 59a6b2a..23bfc30 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
@@ -15,12 +15,12 @@
  */
 package org.gradle.api.internal.changedetection;
 
-import org.gradle.util.HashUtil;
+import org.gradle.util.hash.HashUtil;
 
 import java.io.File;
 
 public class DefaultHasher implements Hasher {
     public byte[] hash(File file) {
-        return HashUtil.createHash(file);
+        return HashUtil.createHash(file, "MD5").asByteArray();
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
new file mode 100644
index 0000000..8c2622c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.changedetection;
+
+import org.gradle.internal.Factory;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.listener.LazyCreationProxy;
+
+import java.io.File;
+
+public class DefaultTaskArtifactStateCacheAccess implements TaskArtifactStateCacheAccess {
+    private final Gradle gradle;
+    private final CacheRepository cacheRepository;
+    private PersistentCache cache;
+
+    public DefaultTaskArtifactStateCacheAccess(Gradle gradle, CacheRepository cacheRepository) {
+        this.gradle = gradle;
+        this.cacheRepository = cacheRepository;
+    }
+
+    private PersistentCache getCache() {
+        if (cache == null) {
+            cache = cacheRepository
+                    .cache("taskArtifacts")
+                    .forObject(gradle)
+                    .withDisplayName("task artifact state cache")
+                    .withLockMode(FileLockManager.LockMode.Exclusive)
+                    .open();
+        }
+        return cache;
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(final String cacheName, final Class<K> keyType, final Class<V> valueType) {
+        Factory<PersistentIndexedCache> factory = new Factory<PersistentIndexedCache>() {
+            public PersistentIndexedCache create() {
+                return getCache().createCache(cacheFile(cacheName), keyType, valueType);
+            }
+        };
+        return new LazyCreationProxy<PersistentIndexedCache>(PersistentIndexedCache.class, factory).getSource();
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(final String cacheName, final Class<K> keyType, final Class<V> valueType, final Serializer<V> valueSerializer) {
+        Factory<PersistentIndexedCache> factory = new Factory<PersistentIndexedCache>() {
+            public PersistentIndexedCache create() {
+                return getCache().createCache(cacheFile(cacheName), keyType, valueSerializer);
+            }
+        };
+        return new LazyCreationProxy<PersistentIndexedCache>(PersistentIndexedCache.class, factory).getSource();
+
+    }
+
+    private File cacheFile(String cacheName) {
+        return new File(getCache().getBaseDir(), cacheName + ".bin");
+    }
+
+    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+        return getCache().useCache(operationDisplayName, action);
+    }
+
+    public void useCache(String operationDisplayName, Runnable action) {
+        getCache().useCache(operationDisplayName, action);
+    }
+
+    public void longRunningOperation(String operationDisplayName, Runnable action) {
+        getCache().longRunningOperation(operationDisplayName, action);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
index baf5426..458fb87 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
@@ -16,14 +16,12 @@
 
 package org.gradle.api.internal.changedetection;
 
-import org.apache.commons.lang.StringUtils;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.TaskExecutionHistory;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.cache.CacheRepository;
 
 import java.util.ArrayList;
 import java.util.Formatter;
@@ -37,8 +35,8 @@ public class DefaultTaskArtifactStateRepository implements TaskArtifactStateRepo
     private final TaskHistoryRepository taskHistoryRepository;
     private final UpToDateRule upToDateRule;
 
-    public DefaultTaskArtifactStateRepository(CacheRepository repository, FileSnapshotter inputFilesSnapshotter, FileSnapshotter outputFilesSnapshotter) {
-        this.taskHistoryRepository = new CacheBackedTaskHistoryRepository(repository, new CacheBackedFileSnapshotRepository(repository));
+    public DefaultTaskArtifactStateRepository(TaskHistoryRepository taskHistoryRepository, FileSnapshotter inputFilesSnapshotter, FileSnapshotter outputFilesSnapshotter) {
+        this.taskHistoryRepository = taskHistoryRepository;
         upToDateRule = new CompositeUpToDateRule(
                 new TaskTypeChangedUpToDateRule(),
                 new InputPropertiesChangedUpToDateRule(),
@@ -58,30 +56,6 @@ public class DefaultTaskArtifactStateRepository implements TaskArtifactStateRepo
         FileCollection getPreviousOutputFiles();
     }
 
-    private static class NoDeclaredArtifactsExecution implements TaskExecutionState {
-        private final TaskInternal task;
-
-        private NoDeclaredArtifactsExecution(TaskInternal task) {
-            this.task = task;
-        }
-
-        public List<String> isUpToDate() {
-            List<String> messages = new ArrayList<String>();
-            if (!task.getOutputs().getHasOutput()) {
-                messages.add(String.format("%s has not declared any outputs.", StringUtils.capitalize(task.toString())));
-            }
-            return messages;
-        }
-
-        public boolean snapshot() {
-            return false;
-        }
-
-        public FileCollection getPreviousOutputFiles() {
-            return new SimpleFileCollection();
-        }
-    }
-
     private static class HistoricExecution implements TaskExecutionState {
         private final TaskInternal task;
         private final TaskExecution lastExecution;
@@ -180,9 +154,6 @@ public class DefaultTaskArtifactStateRepository implements TaskArtifactStateRepo
         }
 
         public TaskExecutionState getExecution() {
-            if (!task.getOutputs().getHasOutput()) {
-                return new NoDeclaredArtifactsExecution(task);
-            }
             return new HistoricExecution(task, history, upToDateRule);
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
index a69e4eb..3d02525 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/InMemoryIndexedCache.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.changedetection;
 
 import org.gradle.api.UncheckedIOException;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.io.*;
 import java.util.HashMap;
@@ -38,7 +38,7 @@ public class InMemoryIndexedCache<K, V> implements PersistentIndexedCache<K, V>
             ByteArrayInputStream instr = new ByteArrayInputStream(serialised);
             return (V)new ObjectInputStream(instr).readObject();
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
index e783f2c..5737a5f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
@@ -17,7 +17,6 @@
 package org.gradle.api.internal.changedetection;
 
 import org.gradle.api.file.FileCollection;
-import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.util.ChangeListener;
 import org.gradle.util.DiffUtil;
@@ -45,10 +44,10 @@ public class OutputFilesSnapshotter implements FileSnapshotter {
     private final PersistentIndexedCache<String, Long> dirIdentiferCache;
 
     public OutputFilesSnapshotter(FileSnapshotter snapshotter, IdGenerator<Long> idGenerator,
-                                  CacheRepository cacheRepository) {
+                                  TaskArtifactStateCacheAccess cacheAccess) {
         this.snapshotter = snapshotter;
         this.idGenerator = idGenerator;
-        dirIdentiferCache = cacheRepository.cache("outputFileStates").open().openIndexedCache();
+        dirIdentiferCache = cacheAccess.createCache("outputFileStates", String.class, Long.class);
     }
 
     public FileCollectionSnapshot emptySnapshot() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
old mode 100644
new mode 100755
index 1bd9061..7b490be
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -15,11 +15,16 @@
  */
 package org.gradle.api.internal.changedetection;
 
+import org.apache.commons.lang.StringUtils;
 import org.gradle.StartParameter;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.TaskExecutionHistory;
 import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 
 public class ShortCircuitTaskArtifactStateRepository implements TaskArtifactStateRepository {
+    private static final Logger LOGGER = Logging.getLogger(ShortCircuitTaskArtifactStateRepository.class);
     private final StartParameter startParameter;
     private final TaskArtifactStateRepository repository;
 
@@ -29,27 +34,63 @@ public class ShortCircuitTaskArtifactStateRepository implements TaskArtifactStat
     }
 
     public TaskArtifactState getStateFor(final TaskInternal task) {
-        final TaskArtifactState state = repository.getStateFor(task);
-        return new TaskArtifactState() {
-            public boolean isUpToDate() {
-                return !startParameter.isNoOpt() && task.getOutputs().getUpToDateSpec().isSatisfiedBy(task) && state.isUpToDate();
-            }
-
-            public TaskExecutionHistory getExecutionHistory() {
-                return state.getExecutionHistory();
-            }
-
-            public void beforeTask() {
-                state.beforeTask();
-            }
-
-            public void afterTask() {
-                state.afterTask();
-            }
-
-            public void finished() {
-                state.finished();
-            }
-        };
+        if (task.getOutputs().getHasOutput()) {
+            return new ShortCircuitArtifactState(task, repository.getStateFor(task));
+        }
+        LOGGER.info(String.format("%s has not declared any outputs, assuming that it is out-of-date.", StringUtils.capitalize(task.toString())));
+        return new NoHistoryArtifactState();
+    }
+
+    private static class NoHistoryArtifactState implements TaskArtifactState, TaskExecutionHistory {
+        public boolean isUpToDate() {
+            return false;
+        }
+
+        public void beforeTask() {
+        }
+
+        public void afterTask() {
+        }
+
+        public void finished() {
+        }
+
+        public TaskExecutionHistory getExecutionHistory() {
+            return this;
+        }
+
+        public FileCollection getOutputFiles() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private class ShortCircuitArtifactState implements TaskArtifactState {
+        private final TaskInternal task;
+        private final TaskArtifactState state;
+
+        public ShortCircuitArtifactState(TaskInternal task, TaskArtifactState state) {
+            this.task = task;
+            this.state = state;
+        }
+
+        public boolean isUpToDate() {
+            return !startParameter.isRerunTasks() && task.getOutputs().getUpToDateSpec().isSatisfiedBy(task) && state.isUpToDate();
+        }
+
+        public TaskExecutionHistory getExecutionHistory() {
+            return state.getExecutionHistory();
+        }
+
+        public void beforeTask() {
+            state.beforeTask();
+        }
+
+        public void afterTask() {
+            state.afterTask();
+        }
+
+        public void finished() {
+            state.finished();
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
new file mode 100644
index 0000000..14e26bb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.changedetection;
+
+import org.gradle.internal.Factory;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+
+public interface TaskArtifactStateCacheAccess {
+    /**
+     * Performs some work against the cache. Acquires exclusive locks the appropriate resources, so that the given action is the only
+     * action to execute across all processes (including this one). Releases the locks and all resources at the end of the action.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    <T> T useCache(String operationDisplayName, Factory<? extends T> action);
+
+    /**
+     * Performs some work against the cache. Acquires exclusive locks the appropriate resources, so that the given action is the only
+     * action to execute across all processes (including this one). Releases the locks and all resources at the end of the action.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    void useCache(String operationDisplayName, Runnable action);
+
+    /**
+     * Performs some long running operation. Releases all locks while the operation is running, and reacquires the locks at the end of
+     * the long running operation.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    void longRunningOperation(String operationDisplayName, Runnable action);
+
+    <K, V> PersistentIndexedCache createCache(String cacheName, Class<K> keyType, Class<V> valueType);
+
+    <K, V> PersistentIndexedCache<K, V> createCache(String cacheName, Class<K> keyType, Class<V> valueType, Serializer<V> valueSerializer);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskCacheLockHandlingBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskCacheLockHandlingBuildExecuter.java
new file mode 100644
index 0000000..3dfbf20
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskCacheLockHandlingBuildExecuter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.changedetection;
+
+import org.gradle.execution.BuildExecutionAction;
+import org.gradle.execution.BuildExecutionContext;
+
+public class TaskCacheLockHandlingBuildExecuter implements BuildExecutionAction {
+    private final TaskArtifactStateCacheAccess cacheAccess;
+
+    public TaskCacheLockHandlingBuildExecuter(TaskArtifactStateCacheAccess cacheAccess) {
+        this.cacheAccess = cacheAccess;
+    }
+
+    public void execute(final BuildExecutionContext context) {
+        cacheAccess.useCache("execute tasks", new Runnable(){
+            public void run() {
+                context.proceed();
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
new file mode 100644
index 0000000..71f39cb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2011 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.classpath;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.util.ClassPath;
+import org.gradle.util.ClasspathUtil;
+import org.gradle.util.DefaultClassPath;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Determines the classpath for a module by looking for a '${module}-classpath.properties' resource with 'name' set to the name of the module.
+ */
+public class DefaultModuleRegistry implements ModuleRegistry, GradleDistributionLocator {
+    private final ClassLoader classLoader;
+    private final File distDir;
+    private final Map<String, Module> modules = new HashMap<String, Module>();
+    private final List<File> classpath = new ArrayList<File>();
+    private final Map<String, File> classpathJars = new LinkedHashMap<String, File>();
+    private final List<File> libDirs = new ArrayList<File>();
+
+    public DefaultModuleRegistry() {
+        this(DefaultModuleRegistry.class.getClassLoader(), findDistDir());
+    }
+
+    DefaultModuleRegistry(ClassLoader classLoader, File distDir) {
+        this.classLoader = classLoader;
+        this.distDir = distDir;
+        for (File classpathFile : new EffectiveClassPath(classLoader).getAsFiles()) {
+            classpath.add(classpathFile);
+            if (classpathFile.isFile() && !classpathJars.containsKey(classpathFile.getName())) {
+                classpathJars.put(classpathFile.getName(), classpathFile);
+            }
+        }
+
+        if (distDir != null) {
+            libDirs.add(new File(distDir, "lib"));
+            libDirs.add(new File(distDir, "lib/plugins"));
+        }
+    }
+
+    private static File findDistDir() {
+        File codeSource = ClasspathUtil.getClasspathForClass(DefaultModuleRegistry.class);
+        if (codeSource.isFile()) {
+            // Loaded from a JAR - let's see if its in the lib directory, and there's a lib/plugins directory
+            File libDir = codeSource.getParentFile();
+            if (!libDir.getName().equals("lib") || !new File(libDir, "plugins").isDirectory()) {
+                return null;
+            }
+            return libDir.getParentFile();
+        } else {
+            // Loaded from a classes dir - assume we're running from the ide or tests
+            return null;
+        }
+    }
+
+    /**
+     * Returns all the candidate JARs to be considered by this registry.
+     */
+    public Set<File> getFullClasspath() {
+        return new LinkedHashSet<File>(classpath);
+    }
+
+    public File getGradleHome() {
+        return distDir;
+    }
+
+    public Module getExternalModule(String name) {
+        File externalJar = findExternalJar(name);
+        return new DefaultModule(name, Collections.singleton(externalJar), Collections.<File>emptySet(), Collections.<Module>emptySet());
+    }
+
+    public Module getModule(String name) {
+        Module module = modules.get(name);
+        if (module == null) {
+            module = loadModule(name);
+            modules.put(name, module);
+        }
+        return module;
+    }
+
+    private Module loadModule(String moduleName) {
+        File jarFile = findModuleJar(moduleName);
+        if (jarFile != null) {
+            Set<File> implementationClasspath = new LinkedHashSet<File>();
+            implementationClasspath.add(jarFile);
+            Properties properties = loadModuleProperties(moduleName, jarFile);
+            return module(moduleName, properties, implementationClasspath);
+        }
+
+        String resourceName = String.format("%s-classpath.properties", moduleName);
+        URL propertiesUrl = classLoader.getResource(resourceName);
+        if (propertiesUrl != null) {
+            Set<File> implementationClasspath = new LinkedHashSet<File>();
+            findImplementationClasspath(moduleName, implementationClasspath);
+            implementationClasspath.add(ClasspathUtil.getClasspathForResource(propertiesUrl, resourceName));
+            Properties properties = GUtil.loadProperties(propertiesUrl);
+            return module(moduleName, properties, implementationClasspath);
+        }
+
+        if (distDir == null) {
+            throw new UnknownModuleException(String.format("Cannot locate classpath manifest for module '%s' in classpath.", moduleName));
+        }
+        throw new UnknownModuleException(String.format("Cannot locate JAR for module '%s' in distribution directory '%s'.", moduleName, distDir));
+    }
+
+    private Module module(String moduleName, Properties properties, Set<File> implementationClasspath) {
+        Set<File> runtimeClasspath = new LinkedHashSet<File>();
+        String runtime = properties.getProperty("runtime");
+        for (String jarName : split(runtime)) {
+            runtimeClasspath.add(findDependencyJar(moduleName, jarName));
+        }
+
+        Set<Module> modules = new LinkedHashSet<Module>();
+        String projects = properties.getProperty("projects");
+        for (String project : split(projects)) {
+            modules.add(getModule(project));
+        }
+
+        return new DefaultModule(moduleName, implementationClasspath, runtimeClasspath, modules);
+    }
+
+    private String[] split(String value) {
+        if (value == null) {
+            return new String[0];
+        }
+        value = value.trim();
+        if (value.length() == 0) {
+            return new String[0];
+        }
+        return value.split(",");
+    }
+
+    private void findImplementationClasspath(String name, Collection<File> implementationClasspath) {
+        List<String> suffixes = new ArrayList<String>();
+        Matcher matcher = Pattern.compile("gradle-(.+)").matcher(name);
+        matcher.matches();
+        String projectDirName = matcher.group(1);
+        String projectName = toCamelCase(projectDirName);
+        suffixes.add(String.format("/out/production/%s", projectName).replace('/', File.separatorChar));
+        suffixes.add(String.format("/%s/bin", projectDirName).replace('/', File.separatorChar));
+        suffixes.add(String.format("/%s/src/main/resources", projectDirName).replace('/', File.separatorChar));
+        suffixes.add(String.format("/%s/build/classes/main", projectDirName).replace('/', File.separatorChar));
+        suffixes.add(String.format("/%s/build/resources/main", projectDirName).replace('/', File.separatorChar));
+        for (File file : classpath) {
+            if (file.isDirectory()) {
+                for (String suffix : suffixes) {
+                    if (file.getAbsolutePath().endsWith(suffix)) {
+                        implementationClasspath.add(file);
+                    }
+                }
+            }
+        }
+    }
+
+    private String toCamelCase(String name) {
+        StringBuffer result = new StringBuffer();
+        Matcher matcher = Pattern.compile("-([^-])").matcher(name);
+        while (matcher.find()) {
+            matcher.appendReplacement(result, "");
+            result.append(matcher.group(1).toUpperCase());
+        }
+        matcher.appendTail(result);
+        return result.toString();
+    }
+
+    private Properties loadModuleProperties(String name, File jarFile) {
+        try {
+            ZipFile zipFile = new ZipFile(jarFile);
+            try {
+                ZipEntry entry = zipFile.getEntry(String.format("%s-classpath.properties", name));
+                return GUtil.loadProperties(zipFile.getInputStream(entry));
+            } finally {
+                zipFile.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private File findModuleJar(String name) {
+        Pattern pattern = Pattern.compile(Pattern.quote(name) + "-\\d.+\\.jar");
+        for (File libDir : libDirs) {
+            for (File file : libDir.listFiles()) {
+                if (pattern.matcher(file.getName()).matches()) {
+                    return file;
+                }
+            }
+        }
+        return null;
+    }
+
+    private File findExternalJar(String name) {
+        Pattern pattern = Pattern.compile(Pattern.quote(name) + "-\\d.+\\.jar");
+        for (File file : classpath) {
+            if (pattern.matcher(file.getName()).matches()) {
+                return file;
+            }
+        }
+        for (File libDir : libDirs) {
+            for (File file : libDir.listFiles()) {
+                if (pattern.matcher(file.getName()).matches()) {
+                    return file;
+                }
+            }
+        }
+        throw new UnknownModuleException(String.format("Cannot locate JAR for module '%s' in distribution directory '%s'.", name, distDir));
+    }
+
+    private File findDependencyJar(String module, String name) {
+        File jarFile = classpathJars.get(name);
+        if (jarFile != null) {
+            return jarFile;
+        }
+        if (distDir == null) {
+            throw new IllegalArgumentException(String.format("Cannot find JAR '%s' required by module '%s' using classpath.", name, module));
+        }
+        for (File libDir : libDirs) {
+            jarFile = new File(libDir, name);
+            if (jarFile.isFile()) {
+                return jarFile;
+            }
+        }
+        throw new IllegalArgumentException(String.format("Cannot find JAR '%s' required by module '%s' using classpath or distribution directory '%s'", name, module, distDir));
+    }
+
+    private static class DefaultModule implements Module {
+        private final String name;
+        private final ClassPath implementationClasspath;
+        private final ClassPath runtimeClasspath;
+        private final Set<Module> modules;
+        private final ClassPath classpath;
+
+        public DefaultModule(String name, Set<File> implementationClasspath, Set<File> runtimeClasspath, Set<Module> modules) {
+            this.name = name;
+            this.implementationClasspath = new DefaultClassPath(implementationClasspath);
+            this.runtimeClasspath = new DefaultClassPath(runtimeClasspath);
+            this.modules = modules;
+            Set<File> classpath = new LinkedHashSet<File>();
+            classpath.addAll(implementationClasspath);
+            classpath.addAll(runtimeClasspath);
+            this.classpath = new DefaultClassPath(classpath);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("module '%s'", name);
+        }
+
+        public Set<Module> getRequiredModules() {
+            return modules;
+        }
+
+        public ClassPath getImplementationClasspath() {
+            return implementationClasspath;
+        }
+
+        public ClassPath getRuntimeClasspath() {
+            return runtimeClasspath;
+        }
+
+        public ClassPath getClasspath() {
+            return classpath;
+        }
+
+        public Set<Module> getAllRequiredModules() {
+            Set<Module> modules = new LinkedHashSet<Module>();
+            modules.add(this);
+            for (Module module : this.modules) {
+                modules.addAll(module.getAllRequiredModules());
+            }
+            return modules;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultPluginModuleRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultPluginModuleRegistry.java
new file mode 100644
index 0000000..da98d08
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultPluginModuleRegistry.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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.classpath;
+
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashSet;
+import java.util.Properties;
+import java.util.Set;
+
+public class DefaultPluginModuleRegistry implements PluginModuleRegistry {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPluginModuleRegistry.class);
+    private final ModuleRegistry moduleRegistry;
+
+    public DefaultPluginModuleRegistry(ModuleRegistry moduleRegistry) {
+        this.moduleRegistry = moduleRegistry;
+    }
+
+    public Set<Module> getPluginModules() {
+        Set<Module> modules = new LinkedHashSet<Module>();
+        Properties properties = loadPluginProperties();
+        for (String pluginModule : properties.getProperty("plugins").split(",")) {
+            try {
+                modules.add(moduleRegistry.getModule(pluginModule));
+            } catch (UnknownModuleException e) {
+                // Ignore
+                LOGGER.debug("Cannot find module for plugin {}. Ignoring.", pluginModule);
+            }
+        }
+        return modules;
+    }
+
+    private Properties loadPluginProperties() {
+        return GUtil.loadProperties(getClass().getResource("/gradle-plugins.properties"));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
new file mode 100644
index 0000000..4ec97d0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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.classpath;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.ClasspathUtil;
+import org.gradle.util.DefaultClassPath;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EffectiveClassPath extends DefaultClassPath {
+    public EffectiveClassPath(ClassLoader classLoader) {
+        super(findAvailableClasspathFiles(classLoader));
+    }
+
+    private static List<File> findAvailableClasspathFiles(ClassLoader classLoader) {
+        List<URL> rawClasspath = ClasspathUtil.getClasspath(classLoader);
+        List<File> classpathFiles = new ArrayList<File>();
+        for (URL url : rawClasspath) {
+            if (url.getProtocol().equals("file")) {
+                try {
+                    File classpathFile = new File(url.toURI());
+                    addClasspathFile(classpathFile, classpathFiles);
+                } catch (URISyntaxException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        }
+
+        // The file names passed to -cp are canonicalised by the JVM when it creates the system classloader, and so the file names are
+        // lost if they happen to refer to links, for example, into the Gradle artifact cache. Try to reconstitute the file names
+        // from the system classpath
+        if (classLoader == ClassLoader.getSystemClassLoader()) {
+            for (String value : System.getProperty("java.class.path").split(File.pathSeparator)) {
+                addClasspathFile(new File(value), classpathFiles);
+            }
+        }
+
+        return classpathFiles;
+    }
+
+    private static void addClasspathFile(File classpathFile, List<File> classpathFiles) {
+        if (classpathFile.exists() && !classpathFiles.contains(classpathFile)) {
+            classpathFiles.add(classpathFile);
+            addManifestClasspathFiles(classpathFile, classpathFiles);
+        }
+    }
+
+    private static void addManifestClasspathFiles(File classpathFile, List<File> classpathFiles) {
+        List<URI> classpathUris = ManifestUtil.parseManifestClasspath(classpathFile);
+        for (URI classpathUri : classpathUris) {
+            addClasspathFile(new File(classpathUri), classpathFiles);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/ManifestUtil.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/ManifestUtil.java
new file mode 100644
index 0000000..b8dd642
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/ManifestUtil.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 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.classpath;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipException;
+
+public class ManifestUtil {
+    private static final String[] EMPTY = new String[0];
+
+    public static String createManifestClasspath(File jarFile, Collection<File> classpath) {
+        List<String> paths = new ArrayList<String>(classpath.size());
+        for (File file : classpath) {
+            String path = constructRelativeClasspathUri(jarFile, file);
+            paths.add(path);
+        }
+
+        return GUtil.join(paths, " ");
+    }
+
+    // TODO:DAZ The returned URI will only be relative if the file is contained in the jarfile directory. Otherwise, an absolute URI is returned.
+    private static String constructRelativeClasspathUri(File jarFile, File file) {
+        URI jarFileUri = jarFile.getParentFile().toURI();
+        URI fileUri = file.toURI();
+        URI relativeUri = jarFileUri.relativize(fileUri);
+        return relativeUri.getRawPath();
+    }
+
+    public static List<URI> parseManifestClasspath(File jarFile) {
+        List<URI> manifestClasspath = new ArrayList<URI>();
+        for (String value : readManifestClasspathString(jarFile)) {
+            try {
+                URI uri = new URI(value);
+                uri = jarFile.toURI().resolve(uri);
+                manifestClasspath.add(uri);
+            } catch (URISyntaxException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+        return manifestClasspath;
+    }
+
+    private static String[] readManifestClasspathString(File classpathFile) {
+        try {
+            Manifest manifest = findManifest(classpathFile);
+            if (manifest == null) {
+                return EMPTY;
+            }
+            String classpathEntry = manifest.getMainAttributes().getValue("Class-Path");
+            if (classpathEntry == null || classpathEntry.trim().length() == 0) {
+                return EMPTY;
+            }
+            return classpathEntry.split(" ");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /*
+     * The manifest if this is a jar file and has a manifest, null otherwise.
+     */
+    private static Manifest findManifest(File possibleJarFile) throws IOException {
+        if (!possibleJarFile.exists() || !possibleJarFile.isFile()) {
+            return null;
+        }
+        JarFile jarFile;
+        try {
+            jarFile = new JarFile(possibleJarFile);
+        } catch (ZipException e) {
+            // Not a zip file
+            return null;
+        }
+        try {
+            return jarFile.getManifest();
+        } finally {
+            jarFile.close();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java
new file mode 100644
index 0000000..7c8ff95
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.classpath;
+
+import org.gradle.util.ClassPath;
+
+import java.util.Set;
+
+/**
+ * Meta-data about a dynamically loadable module.
+ */
+public interface Module {
+    /**
+     * Returns the classpath for the module implementation. This is the classpath of the module itself.
+     */
+    ClassPath getImplementationClasspath();
+
+    /**
+     * Returns the classpath containing the runtime dependencies of the module.
+     */
+    ClassPath getRuntimeClasspath();
+
+    /**
+     * Returns implementation + runtime
+     */
+    ClassPath getClasspath();
+
+    /**
+     * Returns the modules required by this module.
+     */
+    Set<Module> getRequiredModules();
+
+    /**
+     * Returns the transitive closure of all modules required by this module, including the module itself.
+     */
+    Set<Module> getAllRequiredModules();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/ModuleRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/ModuleRegistry.java
new file mode 100644
index 0000000..69d05c1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/ModuleRegistry.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.classpath;
+
+/**
+ * A registry of dynamically loadable modules.
+ */
+public interface ModuleRegistry {
+    /**
+     * Locates an external module by name. An external module is one for which there is no meta-data available. Assumed to be packaged as a single jar file, and to have no runtime dependencies.
+     *
+     * @return the module. Does not return null.
+     */
+    Module getExternalModule(String name) throws UnknownModuleException;
+
+    /**
+     * Locates a module by name.
+     *
+     * @return the module. Does not return null.
+     */
+    Module getModule(String name) throws UnknownModuleException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/PluginModuleRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/PluginModuleRegistry.java
new file mode 100644
index 0000000..d2de256
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/PluginModuleRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.classpath;
+
+import java.util.Set;
+
+/**
+ * A registry of dynamically loaded plugin modules.
+ */
+public interface PluginModuleRegistry {
+    Set<Module> getPluginModules();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/UnknownModuleException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/UnknownModuleException.java
new file mode 100644
index 0000000..f956755
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/UnknownModuleException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.classpath;
+
+public class UnknownModuleException extends RuntimeException {
+    public UnknownModuleException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/CollectionEventRegister.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/CollectionEventRegister.java
new file mode 100644
index 0000000..472d9d8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/CollectionEventRegister.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2011 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.collections;
+
+import org.gradle.api.internal.FilteredAction;
+import org.gradle.api.Action;
+import org.gradle.listener.ActionBroadcast;
+import org.gradle.api.specs.Specs;
+
+public class CollectionEventRegister<T> {
+
+    private final ActionBroadcast<T> addActions;
+    private final ActionBroadcast<T> removeActions;
+
+    public CollectionEventRegister() {
+        this(new ActionBroadcast<T>(), new ActionBroadcast<T>());
+    }
+
+    public CollectionEventRegister(ActionBroadcast<T> addActions, ActionBroadcast<T> removeActions) {
+        this.addActions = addActions;
+        this.removeActions = removeActions;
+    }
+
+    public Action<T> getAddAction() {
+        return addActions;
+    }   
+    
+    public Action<T> getRemoveAction() {
+        return removeActions;
+    }
+    
+    public Action<? super T> registerAddAction(Action<? super T> addAction) {
+        this.addActions.add(addAction);
+        return addAction;
+    }
+
+    public Action<? super T> registerRemoveAction(Action<? super T> removeAction) {
+        this.removeActions.add(removeAction);
+        return removeAction;
+    }
+
+    public <S extends T> CollectionEventRegister<S> filtered(CollectionFilter<S> filter) {
+        return new FilteringCollectionEventRegister<S>(filter, (ActionBroadcast)addActions, (ActionBroadcast)removeActions);
+    }
+    
+
+    private static class FilteringCollectionEventRegister<S> extends CollectionEventRegister<S> {
+        private final CollectionFilter<S> filter;
+
+        public FilteringCollectionEventRegister(CollectionFilter<S> filter, ActionBroadcast<S> addActions, ActionBroadcast<S> removeActions) {
+            super(addActions, removeActions);
+            this.filter = filter;
+        }
+
+        public Action<? super S> registerAddAction(Action<? super S> addAction) {
+            return super.registerAddAction(new FilteredAction<S>(filter, addAction));
+        }
+
+        public Action<? super S> registerRemoveAction(Action<? super S> removeAction) {
+            return super.registerRemoveAction(new FilteredAction<S>(filter, removeAction));
+        }
+
+        public <K extends S> CollectionEventRegister<K> filtered(CollectionFilter<K> filter) {
+            return super.filtered(new CollectionFilter<K>(filter.getType(), Specs.<K>and(filter, this.filter)));
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/CollectionFilter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/CollectionFilter.java
new file mode 100644
index 0000000..dbb1aa4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/CollectionFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.collections;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+/**
+ * @param T filtered type
+ */
+public class CollectionFilter<T> implements Spec<T> {
+
+    private Class<? extends T> type;
+    private Spec<? super T> spec;
+
+    public CollectionFilter(Class<T> type) {
+        this(type, Specs.<T>satisfyAll());
+    }
+
+    public CollectionFilter(Class<? extends T> type, Spec<? super T> spec) {
+        this.type = type;
+        this.spec = spec;
+    }
+
+    public Class<? extends T> getType() {
+        return type;
+    }
+
+    public T filter(Object object) {
+        if (!type.isInstance(object)) {
+            return null;
+        }
+
+        T t = type.cast(object);
+        if (spec.isSatisfiedBy(t)) {
+            return t;
+        } else {
+            return null;
+        }
+    }
+
+    public boolean isSatisfiedBy(T element) {
+        return filter(element) != null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredCollection.java
new file mode 100644
index 0000000..903ffc2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredCollection.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2011 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.collections;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class FilteredCollection<T, S extends T> implements Collection<S> {
+
+    protected final Collection<T> collection;
+    protected final CollectionFilter<S> filter;
+
+    public FilteredCollection(Collection<T> collection, CollectionFilter<S> filter) {
+        this.collection = collection;
+        this.filter = filter;
+    }
+    
+    public boolean add(S o) {
+        throw new UnsupportedOperationException(String.format("Cannot add '%s' to '%s' as it is a filtered collection", o, this));
+    }
+
+    public boolean addAll(Collection<? extends S> c) {
+        throw new UnsupportedOperationException(String.format("Cannot add all from '%s' to '%s' as it is a filtered collection", c, this));
+    }
+
+    public void clear() {
+        throw new UnsupportedOperationException(String.format("Cannot clear '%s' as it is a filtered collection", this));
+    }
+    
+    protected boolean accept(Object o) {
+        return filter.filter(o) != null;
+    }
+    
+    public boolean contains(Object o) {
+        return collection.contains(o) && accept(o);
+    }
+
+    public boolean containsAll(Collection<?> c) {
+        if (collection.containsAll(c)) {
+            for (Object o : c) {
+                if (!accept(o)) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    // boolean equals(Object o) {
+    // 
+    // }
+    // 
+    // int hashCode() {
+    //     
+    // }
+
+    public boolean isEmpty() {
+        if (collection.isEmpty()) {
+            return true;
+        } else {
+            for (T o : collection) {
+                if (accept(o)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    protected static class FilteringIterator<T, S extends T> implements Iterator<S> {
+        private final CollectionFilter<S> filter;
+        private final Iterator<T> iterator;
+        
+        private S next;
+        
+        public FilteringIterator(Iterator<T> iterator, CollectionFilter<S> filter) {
+            this.iterator = iterator;
+            this.filter = filter;
+            this.next = findNext();
+        }
+        
+        private S findNext() {
+            while (iterator.hasNext()) {
+                T potentialNext = iterator.next();
+                S filtered = filter.filter(potentialNext);
+                if (filtered != null) {
+                    return filtered;
+                }
+            }
+            
+            return null;
+        }
+        
+        public boolean hasNext() {
+            return next != null;
+        }
+
+        public S next() {
+            if (next != null) {
+                S thisNext = next;
+                next = findNext();
+                return thisNext;
+            } else {
+                throw new NoSuchElementException();
+            }
+        }
+        
+        public void remove() {
+            throw new UnsupportedOperationException("This iterator does not support removal");
+        }
+    } 
+    
+    public Iterator<S> iterator() {
+        return new FilteringIterator<T, S>(collection.iterator(), filter);
+    }
+
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException(String.format("Cannot remove '%s' from '%s' as it is a filtered collection", o, this));
+    }
+    
+    public boolean removeAll(Collection<?> c) {
+        throw new UnsupportedOperationException(String.format("Cannot remove all of '%s' from '%s' as it is a filtered collection", c, this));
+    }
+
+    public boolean retainAll(Collection<?> c) {
+        throw new UnsupportedOperationException(String.format("Cannot retain all of '%s' from '%s' as it is a filtered collection", c, this));
+    }
+
+    public int size() {
+        int i = 0;
+        for (T o : collection) {
+            if (accept(o)) {
+                ++i;
+            }
+        }
+        return i;
+    }
+
+    public Object[] toArray() {
+        Object[] a = new Object[size()];
+        int i = 0;
+        for (T o : collection) {
+            if (accept(o)) {
+                a[i++] = o;
+            }
+        }
+        return a;
+    }
+
+    // TODO - a proper implementation of this
+    public <T> T[] toArray(T[] a) {
+        return (T[])toArray();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredList.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredList.java
new file mode 100644
index 0000000..54929e5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredList.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2011 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.collections;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+public class FilteredList<T, S extends T> extends FilteredCollection<T, S> implements List<S> {
+    public FilteredList(Collection<T> collection, CollectionFilter<S> filter) {
+        super(collection, filter);
+    }
+
+    public void add(int index, S element) {
+        throw new UnsupportedOperationException(String.format("Cannot add '%s' to '%s' as it is a filtered collection", element, this));
+    }
+
+    public boolean addAll(int index, Collection<? extends S> c) {
+        throw new UnsupportedOperationException(String.format("Cannot add all from '%s' to '%s' as it is a filtered collection", c, this));
+    }
+
+    public S get(int index) {
+        int nextIndex = 0;
+        for (T t : collection) {
+            S s = filter.filter(t);
+            if (s != null) {
+                if (nextIndex == index) {
+                    return s;
+                }
+                nextIndex++;
+            }
+        }
+        throw new IndexOutOfBoundsException();
+    }
+
+    public S set(int index, S element) {
+        throw new UnsupportedOperationException(String.format("Cannot set '%s' in '%s' as it is a filtered collection", element, this));
+    }
+
+    public S remove(int index) {
+        throw new UnsupportedOperationException(String.format("Cannot remove element from '%s' as it is a filtered collection", this));
+    }
+
+    public int indexOf(Object o) {
+        int nextIndex = 0;
+        for (T t : collection) {
+            S s = filter.filter(t);
+            if (s != null) {
+                if (s.equals(o)) {
+                    return nextIndex;
+                }
+                nextIndex++;
+            }
+        }
+        return -1;
+    }
+
+    public int lastIndexOf(Object o) {
+        int nextIndex = 0;
+        int lastMatch = -1;
+        for (T t : collection) {
+            S s = filter.filter(t);
+            if (s != null) {
+                if (s.equals(o)) {
+                    lastMatch = nextIndex;
+                }
+                nextIndex++;
+            }
+        }
+        return lastMatch;
+    }
+
+    public ListIterator<S> listIterator() {
+        return new FilteredListIterator<S>(iterator());
+    }
+
+    public ListIterator<S> listIterator(int index) {
+        ListIterator<S> iterator = listIterator();
+        for (int i = 0; i < index; i++) {
+            iterator.next();
+        }
+        return iterator;
+    }
+
+    public List<S> subList(int fromIndex, int toIndex) {
+        throw new UnsupportedOperationException("Not implemented yet.");
+    }
+
+    private static class FilteredListIterator<T> implements ListIterator<T> {
+        private final Iterator<T> iterator;
+        private int nextIndex;
+
+        public FilteredListIterator(Iterator<T> iterator) {
+            this.iterator = iterator;
+        }
+
+        public boolean hasNext() {
+            return iterator.hasNext();
+        }
+
+        public boolean hasPrevious() {
+            throw new UnsupportedOperationException("Not implemented yet.");
+        }
+
+        public T next() {
+            nextIndex++;
+            return iterator.next();
+        }
+
+        public T previous() {
+            throw new UnsupportedOperationException("Not implemented yet.");
+        }
+
+        public int nextIndex() {
+            return nextIndex;
+        }
+
+        public int previousIndex() {
+            return nextIndex - 1;
+        }
+
+        public void add(T t) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void set(T t) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredSet.java
new file mode 100644
index 0000000..8987afb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/collections/FilteredSet.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.collections;
+
+import java.util.Set;
+
+public class FilteredSet<T, S extends T> extends FilteredCollection<T, S> implements Set<S> {
+
+    public FilteredSet(Set<T> collection, CollectionFilter<S> filter) {
+        super(collection, filter);
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistry.java
new file mode 100644
index 0000000..1ee773e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistry.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.concurrent;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.UnknownServiceException;
+
+/**
+ * by Szczepan Faber, created at: 11/24/11
+ */
+public class SynchronizedServiceRegistry implements ServiceRegistry {
+    private final Synchronizer synchronizer = new Synchronizer();
+    private final ServiceRegistry delegate;
+
+    public SynchronizedServiceRegistry(ServiceRegistry delegate) {
+        this.delegate = delegate;
+    }
+
+    public <T> T get(final Class<T> serviceType) throws UnknownServiceException {
+        return synchronizer.synchronize(new Factory<T>() {
+            public T create() {
+                return delegate.get(serviceType);
+            }
+        });
+    }
+
+    public <T> Factory<T> getFactory(final Class<T> type) throws UnknownServiceException {
+        return synchronizer.synchronize(new Factory<Factory<T>>() {
+            public Factory<T> create() {
+                return delegate.getFactory(type);
+            }
+        });
+    }
+
+    public <T> T newInstance(final Class<T> type) throws UnknownServiceException {
+        return synchronizer.synchronize(new Factory<T>() {
+            public T create() {
+                return delegate.newInstance(type);
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/Synchronizer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/Synchronizer.java
new file mode 100644
index 0000000..8bc145c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/Synchronizer.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.concurrent;
+
+import org.gradle.api.internal.Operation;
+import org.gradle.internal.Factory;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class Synchronizer {
+
+    private final Lock lock = new ReentrantLock();
+
+    public <T> T synchronize(Factory<T> factory) {
+        lock.lock();
+        try {
+            return factory.create();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void synchronize(Operation operation) {
+        lock.lock();
+        try {
+            operation.execute();
+        } finally {
+            lock.unlock();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
index c268672..14483df 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
@@ -27,6 +27,7 @@ import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.util.PatternSet;
 import org.gradle.util.GUtil;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.*;
@@ -208,7 +209,7 @@ public abstract class AbstractFileCollection implements FileCollection, MinimalF
             }
 
             public Set<File> getFiles() {
-                return Specs.filterIterable(AbstractFileCollection.this, filterSpec);
+                return CollectionUtils.filter(AbstractFileCollection.this, new LinkedHashSet<File>(), filterSpec);
             }
         };
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
index 27dd1ea..69b357e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
@@ -22,23 +22,30 @@ import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.OperatingSystem;
+import org.gradle.api.resources.ReadableResource;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.util.GUtil;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public abstract class AbstractFileResolver implements FileResolver {
-    private static final Pattern URI_SCHEME = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-\\.]*:.+");
-    private static final Pattern ENCODED_URI = Pattern.compile("%([0-9a-fA-F]{2})");
+    private final FileSystem fileSystem;
+    private FileOrUriNotationParser fileNotationParser;
+
+    protected AbstractFileResolver(FileSystem fileSystem) {
+        this.fileSystem = fileSystem;
+        this.fileNotationParser = new FileOrUriNotationParser(fileSystem);
+    }
 
     public FileResolver withBaseDir(Object path) {
-        return new BaseDirConverter(resolve(path));
+        return new BaseDirFileResolver(fileSystem, resolve(path));
     }
 
     public File resolve(Object path) {
@@ -47,11 +54,84 @@ public abstract class AbstractFileResolver implements FileResolver {
 
     public File resolve(Object path, PathValidation validation) {
         File file = doResolve(path);
-        file = GFileUtils.canonicalise(file);
+
+        file = normalise(file);
+
         validate(file, validation);
+
         return file;
     }
 
+    // normalizes a path in similar ways as File.getCanonicalFile(), except that it
+    // does NOT resolve symlinks (by design)
+    private File normalise(File file) {
+        try {
+            assert file.isAbsolute() : String.format("Cannot normalize a relative file: '%s'", file);
+
+            if (OperatingSystem.current().isWindows()) {
+                // on Windows, File.getCanonicalFile() doesn't resolve symlinks
+                return file.getCanonicalFile();
+            }
+
+            String[] segments = file.getPath().split(String.format("[/%s]", Pattern.quote(File.separator)));
+            List<String> path = new ArrayList<String>(segments.length);
+            for (String segment : segments) {
+                if (segment.equals("..")) {
+                    if (!path.isEmpty()) {
+                        path.remove(path.size() - 1);
+                    }
+                } else if (!segment.equals(".") && segment.length() > 0) {
+                    path.add(segment);
+                }
+            }
+
+            String resolvedPath = GUtil.join(path, File.separator);
+            boolean needLeadingSeparator = File.listRoots()[0].getPath().startsWith(File.separator);
+            if (needLeadingSeparator) {
+                resolvedPath = File.separator + resolvedPath;
+            }
+            File candidate = new File(resolvedPath);
+            if (fileSystem.isCaseSensitive()) {
+                return candidate;
+            }
+
+            // Short-circuit the slower lookup method by using the canonical file
+            File canonical = candidate.getCanonicalFile();
+            if (candidate.getPath().equalsIgnoreCase(canonical.getPath())) {
+                return canonical;
+            }
+
+            // Canonical path is different to what we expected (eg there is a link somewhere in there). Normalise a segment at a time
+            // TODO - start resolving only from where the expected and canonical paths are different
+            File current = File.listRoots()[0];
+            for (int pos = 0; pos < path.size(); pos++) {
+                File child = findChild(current, path.get(pos));
+                if (child == null) {
+                    current = new File(current, GUtil.join(path.subList(pos, path.size()), File.separator));
+                    break;
+                }
+                current = child;
+            }
+            return current;
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Could not normalize path for file '%s'.", file), e);
+        }
+    }
+
+    private File findChild(File current, String segment) throws IOException {
+        String[] children = current.list();
+        if (children == null) {
+            return null;
+        }
+        // TODO - find some native methods for doing this
+        for (String child : children) {
+            if (child.equalsIgnoreCase(segment)) {
+                return new File(current, child);
+            }
+        }
+        return new File(current, segment);
+    }
+
     public FileSource resolveLater(final Object path) {
         return new FileSource() {
             public File get() {
@@ -68,7 +148,7 @@ public abstract class AbstractFileResolver implements FileResolver {
 
     protected URI convertObjectToURI(Object path) {
         Object object = unpack(path);
-        Object converted = convertToFileOrUri(object);
+        Object converted = fileNotationParser.parseNotation(object);
         if (converted instanceof File) {
             return resolve(converted).toURI();
         }
@@ -80,73 +160,12 @@ public abstract class AbstractFileResolver implements FileResolver {
         if (object == null) {
             return null;
         }
-        Object converted = convertToFileOrUri(object);
+        Object converted = fileNotationParser.parseNotation(object);
         if (converted instanceof File) {
             return (File) converted;
         }
         throw new InvalidUserDataException(String.format("Cannot convert URL '%s' to a file.", converted));
-    }
-
-    private Object convertToFileOrUri(Object path) {
-        if (path instanceof File) {
-            return path;
-        }
-
-        if (path instanceof URL) {
-            try {
-                path = ((URL) path).toURI();
-            } catch (URISyntaxException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
 
-        if (path instanceof URI) {
-            URI uri = (URI) path;
-            if (uri.getScheme().equals("file")) {
-                return new File(uri.getPath());
-            }
-            return uri;
-        }
-
-        String str = path.toString();
-        if (str.startsWith("file:")) {
-            return new File(uriDecode(str.substring(5)));
-        }
-
-        for (File file : File.listRoots()) {
-            String rootPath = file.getAbsolutePath();
-            String normalisedStr = str;
-            if (!OperatingSystem.current().isCaseSensitiveFileSystem()) {
-                rootPath = rootPath.toLowerCase();
-                normalisedStr = normalisedStr.toLowerCase();
-            }
-            if (normalisedStr.startsWith(rootPath) || normalisedStr.startsWith(rootPath.replace(File.separatorChar,
-                    '/'))) {
-                return new File(str);
-            }
-        }
-
-        // Check if string starts with a URI scheme
-        if (URI_SCHEME.matcher(str).matches()) {
-            try {
-                return new URI(str);
-            } catch (URISyntaxException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-
-        return new File(str);
-    }
-
-    private String uriDecode(String path) {
-        StringBuffer builder = new StringBuffer();
-        Matcher matcher = ENCODED_URI.matcher(path);
-        while (matcher.find()) {
-            String val = matcher.group(1);
-            matcher.appendReplacement(builder, String.valueOf((char) (Integer.parseInt(val, 16))));
-        }
-        matcher.appendTail(builder);
-        return builder.toString();
     }
 
     private Object unpack(Object path) {
@@ -161,7 +180,7 @@ public abstract class AbstractFileResolver implements FileResolver {
                     throw new RuntimeException(e);
                 }
             } else if (current instanceof FileSource) {
-                return ((FileSource)current).get();
+                return ((FileSource) current).get();
             } else {
                 return current;
             }
@@ -207,4 +226,11 @@ public abstract class AbstractFileResolver implements FileResolver {
     public FileTree resolveFilesAsTree(Object... paths) {
         return resolveFiles(paths).getAsFileTree();
     }
+
+    public ReadableResource resolveResource(Object path) {
+        if (path instanceof ReadableResource) {
+            return (ReadableResource) path;
+        }
+        return new FileResource(resolve(path));
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java
new file mode 100644
index 0000000..2bc0f45
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.file;
+
+import org.gradle.api.resources.ReadableResource;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * by Szczepan Faber, created at: 11/23/11
+ */
+public abstract class AbstractFileResource implements ReadableResource {
+
+    protected final File file;
+
+    public AbstractFileResource(File file) {
+        assert file != null;
+        this.file = file;
+    }
+
+    public String getDisplayName() {
+        return file.getAbsolutePath();
+    }
+
+    public URI getURI() {
+        return file.toURI();
+    }
+
+    public String getBaseName() {
+        return file.getName();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
index 3da8e6f..6f658e4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
@@ -19,10 +19,12 @@ import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileTreeElement;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 
 import java.io.*;
 
-public abstract class AbstractFileTreeElement implements FileTreeElement {
+public abstract class  AbstractFileTreeElement implements FileTreeElement {
     public abstract String getDisplayName();
 
     @Override
@@ -52,6 +54,7 @@ public abstract class AbstractFileTreeElement implements FileTreeElement {
     }
 
     public boolean copyTo(File target) {
+        validateTimeStamps();
         try {
             target.getParentFile().mkdirs();
             if (isDirectory()) {
@@ -59,13 +62,20 @@ public abstract class AbstractFileTreeElement implements FileTreeElement {
             } else {
                 copyFile(target);
             }
-            target.setLastModified(getLastModified());
+            FileSystems.getDefault().chmod(target, getMode());
             return true;
         } catch (Exception e) {
             throw new GradleException(String.format("Could not copy %s to '%s'.", getDisplayName(), target), e);
         }
     }
 
+    private void validateTimeStamps() {
+        final long lastModified = getLastModified();
+        if(lastModified < 0) {
+            throw new GradleException(String.format("Invalid Timestamp %s for '%s'.", lastModified, getDisplayName()));
+        }
+    }
+
     private void copyFile(File target) throws IOException {
         FileOutputStream outputStream = new FileOutputStream(target);
         try {
@@ -74,4 +84,10 @@ public abstract class AbstractFileTreeElement implements FileTreeElement {
             outputStream.close();
         }
     }
+
+    public int getMode() {
+        return isDirectory()
+            ? FileSystem.DEFAULT_DIR_MODE
+            : FileSystem.DEFAULT_FILE_MODE;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/BaseDirConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/BaseDirConverter.java
deleted file mode 100644
index 0496ebe..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/BaseDirConverter.java
+++ /dev/null
@@ -1,75 +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.api.internal.file;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class BaseDirConverter extends AbstractFileResolver {
-    private final File baseDir;
-
-    public BaseDirConverter(File baseDir) {
-        this.baseDir = baseDir;
-    }
-
-    public String resolveAsRelativePath(Object path) {
-        List<String> basePath = Arrays.asList(StringUtils.split(baseDir.getAbsolutePath(), "/" + File.separator));
-        File targetFile = resolve(path);
-        List<String> targetPath = new ArrayList<String>(Arrays.asList(StringUtils.split(targetFile.getAbsolutePath(),
-                "/" + File.separator)));
-
-        // Find and remove common prefix
-        int maxDepth = Math.min(basePath.size(), targetPath.size());
-        int prefixLen = 0;
-        while (prefixLen < maxDepth && basePath.get(prefixLen).equals(targetPath.get(prefixLen))) {
-            prefixLen++;
-        }
-        basePath = basePath.subList(prefixLen, basePath.size());
-        targetPath = targetPath.subList(prefixLen, targetPath.size());
-
-        for (int i = 0; i < basePath.size(); i++) {
-            targetPath.add(0, "..");
-        }
-        if (targetPath.isEmpty()) {
-            return ".";
-        }
-        return GUtil.join(targetPath, File.separator);
-    }
-
-    @Override
-    protected File doResolve(Object path) {
-        if (!GUtil.isTrue(path) || !GUtil.isTrue(baseDir)) {
-            throw new IllegalArgumentException(String.format(
-                    "Neither path nor baseDir may be null or empty string. path='%s' basedir='%s'", path, baseDir));
-        }
-
-        File file = convertObjectToFile(path);
-        if (!file.isAbsolute()) {
-            file = new File(baseDir, file.getPath());
-        }
-
-        return file;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/BaseDirFileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/BaseDirFileResolver.java
new file mode 100644
index 0000000..884b171
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/BaseDirFileResolver.java
@@ -0,0 +1,78 @@
+/*
+ * 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.file;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseDirFileResolver extends AbstractFileResolver {
+    private final File baseDir;
+
+    public BaseDirFileResolver(FileSystem fileSystem, File baseDir) {
+        super(fileSystem);
+        assert baseDir.isAbsolute() : String.format("base dir '%s' is not an absolute file.", baseDir);
+        this.baseDir = baseDir;
+    }
+
+    public String resolveAsRelativePath(Object path) {
+        List<String> basePath = Arrays.asList(StringUtils.split(baseDir.getAbsolutePath(), "/" + File.separator));
+        File targetFile = resolve(path);
+        List<String> targetPath = new ArrayList<String>(Arrays.asList(StringUtils.split(targetFile.getAbsolutePath(),
+                "/" + File.separator)));
+
+        // Find and remove common prefix
+        int maxDepth = Math.min(basePath.size(), targetPath.size());
+        int prefixLen = 0;
+        while (prefixLen < maxDepth && basePath.get(prefixLen).equals(targetPath.get(prefixLen))) {
+            prefixLen++;
+        }
+        basePath = basePath.subList(prefixLen, basePath.size());
+        targetPath = targetPath.subList(prefixLen, targetPath.size());
+
+        for (int i = 0; i < basePath.size(); i++) {
+            targetPath.add(0, "..");
+        }
+        if (targetPath.isEmpty()) {
+            return ".";
+        }
+        return GUtil.join(targetPath, File.separator);
+    }
+
+    @Override
+    protected File doResolve(Object path) {
+        if (!GUtil.isTrue(path) || !GUtil.isTrue(baseDir)) {
+            throw new IllegalArgumentException(String.format(
+                    "Neither path nor baseDir may be null or empty string. path='%s' basedir='%s'", path, baseDir));
+        }
+
+        File file = convertObjectToFile(path);
+        if (!file.isAbsolute()) {
+            file = new File(baseDir, file.getPath());
+        }
+
+        return file;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
old mode 100644
new mode 100755
index 213087e..1c90179
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
@@ -19,13 +19,16 @@ import groovy.lang.Closure;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.PathValidation;
 import org.gradle.api.file.*;
+import org.gradle.api.internal.ProcessOperations;
 import org.gradle.api.internal.file.archive.TarFileTree;
 import org.gradle.api.internal.file.archive.ZipFileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileTree;
 import org.gradle.api.internal.file.collections.FileTreeAdapter;
 import org.gradle.api.internal.file.copy.*;
+import org.gradle.api.internal.resources.DefaultResourceHandler;
 import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.api.resources.ReadableResource;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.DefaultExecAction;
@@ -41,17 +44,19 @@ import java.util.Map;
 
 import static org.gradle.util.ConfigureUtil.configure;
 
-public class DefaultFileOperations implements FileOperations {
+public class DefaultFileOperations implements FileOperations, ProcessOperations {
     private final FileResolver fileResolver;
     private final TaskResolver taskResolver;
     private final TemporaryFileProvider temporaryFileProvider;
     private DeleteAction deleteAction;
+    private final DefaultResourceHandler resourceHandler;
 
     public DefaultFileOperations(FileResolver fileResolver, TaskResolver taskResolver, TemporaryFileProvider temporaryFileProvider) {
         this.fileResolver = fileResolver;
         this.taskResolver = taskResolver;
         this.temporaryFileProvider = temporaryFileProvider;
         this.deleteAction = new DeleteActionImpl(fileResolver);
+        this.resourceHandler = new DefaultResourceHandler(fileResolver);
     }
 
     public File file(Object path) {
@@ -78,11 +83,16 @@ public class DefaultFileOperations implements FileOperations {
         return new DefaultConfigurableFileTree(baseDir, fileResolver, taskResolver);
     }
 
+    public ConfigurableFileTree fileTree(Object baseDir, Closure closure) {
+        return ConfigureUtil.configure(closure, fileTree(baseDir));
+    }
+
     public ConfigurableFileTree fileTree(Map<String, ?> args) {
         return new DefaultConfigurableFileTree(args, fileResolver, taskResolver);
     }
 
     public ConfigurableFileTree fileTree(Closure closure) {
+        // This method is deprecated, but the deprecation warning is added on public classes that delegate to this. 
         return configure(closure, new DefaultConfigurableFileTree(Collections.emptyMap(), fileResolver, taskResolver));
     }
 
@@ -91,7 +101,10 @@ public class DefaultFileOperations implements FileOperations {
     }
 
     public FileTree tarTree(Object tarPath) {
-        return new FileTreeAdapter(new TarFileTree(file(tarPath), getExpandDir()));
+        ReadableResource res = getResources().maybeCompressed(tarPath);
+
+        TarFileTree tarTree = new TarFileTree(res, getExpandDir());
+        return new FileTreeAdapter(tarTree);
     }
 
     private File getExpandDir() {
@@ -146,4 +159,8 @@ public class DefaultFileOperations implements FileOperations {
         ExecAction execAction = ConfigureUtil.configure(cl, new DefaultExecAction(fileResolver));
         return execAction.execute();
     }
+
+    public DefaultResourceHandler getResources() {
+        return resourceHandler;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
index c9a9522..19081a6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
@@ -15,11 +15,14 @@
  */
 package org.gradle.api.internal.file;
 
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.RelativePath;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.io.InputStream;
+import java.io.IOException;
 
 public class DefaultFileTreeElement extends AbstractFileTreeElement {
     private final File file;
@@ -57,4 +60,12 @@ public class DefaultFileTreeElement extends AbstractFileTreeElement {
     public RelativePath getRelativePath() {
         return relativePath;
     }
+
+    public int getMode() {
+        try {
+            return FileSystems.getDefault().getUnixMode(file);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
index 1c7a04d..7a3db70 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
@@ -32,16 +32,26 @@ import java.util.*;
 
 public class DefaultSourceDirectorySet extends CompositeFileTree implements SourceDirectorySet {
     private final List<Object> source = new ArrayList<Object>();
+    private final String name;
     private final String displayName;
     private final FileResolver fileResolver;
     private final PatternSet patterns = new PatternSet();
     private final PatternSet filter = new PatternSet();
 
-    public DefaultSourceDirectorySet(String displayName, FileResolver fileResolver) {
+    public DefaultSourceDirectorySet(String name, String displayName, FileResolver fileResolver) {
+        this.name = name;
         this.displayName = displayName;
         this.fileResolver = fileResolver;
     }
 
+    public DefaultSourceDirectorySet(String name, FileResolver fileResolver) {
+        this(name, name, fileResolver);
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
     public Set<File> getSrcDirs() {
         Set<File> dirs = new LinkedHashSet<File>();
         for (DirectoryTree tree : getSrcDirTrees()) {
@@ -166,7 +176,7 @@ public class DefaultSourceDirectorySet extends CompositeFileTree implements Sour
         return this;
     }
 
-    public SourceDirectorySet setSrcDirs(Iterable<Object> srcPaths) {
+    public SourceDirectorySet setSrcDirs(Iterable<?> srcPaths) {
         source.clear();
         GUtil.addToCollection(source, srcPaths);
         return this;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
old mode 100644
new mode 100755
index 48d9a58..a2a0945
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
@@ -1,34 +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.file;
-
-import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-
-public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
-    private final FileSource baseDir;
-
-    public DefaultTemporaryFileProvider(FileSource baseDir) {
-        this.baseDir = baseDir;
-    }
-
-    public File newTemporaryFile(String... path) {
-        return GFileUtils.canonicalise(new File(baseDir.get(), GUtil.join(path, "/")));
-    }
-}
+/*
+ * 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.file;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
+    private final FileSource baseDir;
+
+    public DefaultTemporaryFileProvider(FileSource baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    public File newTemporaryFile(String... path) {
+        return GFileUtils.canonicalise(new File(baseDir.get(), GUtil.join(path, "/")));
+    }
+
+    public File createTemporaryFile(String prefix, @Nullable String suffix, String... path) {
+        File dir = new File(baseDir.get(), GUtil.join(path, "/"));
+        GFileUtils.createDirectory(dir);
+        try {
+            return File.createTempFile(prefix, suffix, dir);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e.getMessage(), e);
+        }
+    }
+
+    public File createTemporaryDirectory(@Nullable String prefix, @Nullable String suffix, @Nullable String... path) {
+        File dir = new File(baseDir.get(), GUtil.join(path, "/"));
+        GFileUtils.createDirectory(dir);
+        try {
+            // TODO: This is not a great paradigm for creating a temporary directory.
+            // See http://guava-libraries.googlecode.com/svn/tags/release08/javadoc/com/google/common/io/Files.html#createTempDir%28%29 for an alternative.
+            File tmpDir = File.createTempFile("gradle", "projectDir", dir);
+            tmpDir.delete();
+            tmpDir.mkdir();
+            return tmpDir;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e.getMessage(), e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
old mode 100644
new mode 100755
index 18cf477..30ec18b
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
@@ -1,67 +1,68 @@
-/*
- * 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.file;
-
-import groovy.lang.Closure;
-import org.gradle.api.PathValidation;
-import org.gradle.api.file.ConfigurableFileCollection;
-import org.gradle.api.file.ConfigurableFileTree;
-import org.gradle.api.file.CopySpec;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.WorkResult;
-import org.gradle.process.ExecResult;
-
-import java.io.File;
-import java.net.URI;
-import java.util.Map;
-
-public interface FileOperations {
-    File file(Object path);
-
-    File file(Object path, PathValidation validation);
-
-    URI uri(Object path);
-
-    FileResolver getFileResolver();
-
-    String relativePath(Object path);
-
-    ConfigurableFileCollection files(Object... paths);
-
-    ConfigurableFileCollection files(Object paths, Closure configureClosure);
-
-    ConfigurableFileTree fileTree(Object baseDir);
-
-    ConfigurableFileTree fileTree(Map<String, ?> args);
-
-    ConfigurableFileTree fileTree(Closure closure);
-
-    FileTree zipTree(Object zipPath);
-
-    FileTree tarTree(Object tarPath);
-
-    CopySpec copySpec(Closure closure);
-
-    WorkResult copy(Closure closure);
-
-    File mkdir(Object path);
-
-    boolean delete(Object... paths);
-
-    ExecResult javaexec(Closure cl);
-
-    ExecResult exec(Closure cl);
-}
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.PathValidation;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.resources.ResourceHandler;
+import org.gradle.api.tasks.WorkResult;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+public interface FileOperations {
+    File file(Object path);
+
+    File file(Object path, PathValidation validation);
+
+    URI uri(Object path);
+
+    FileResolver getFileResolver();
+
+    String relativePath(Object path);
+
+    ConfigurableFileCollection files(Object... paths);
+
+    ConfigurableFileCollection files(Object paths, Closure configureClosure);
+
+    ConfigurableFileTree fileTree(Object baseDir);
+
+    ConfigurableFileTree fileTree(Map<String, ?> args);
+
+    @Deprecated
+    ConfigurableFileTree fileTree(Closure closure);
+
+    ConfigurableFileTree fileTree(Object baseDir, Closure closure);
+
+    FileTree zipTree(Object zipPath);
+
+    FileTree tarTree(Object tarPath);
+
+    CopySpec copySpec(Closure closure);
+
+    WorkResult copy(Closure closure);
+
+    File mkdir(Object path);
+
+    boolean delete(Object... paths);
+
+    ResourceHandler getResources();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOrUriNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOrUriNotationParser.java
new file mode 100644
index 0000000..c30638e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileOrUriNotationParser.java
@@ -0,0 +1,110 @@
+/*
+ * 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.file;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.util.DeprecationLogger;
+
+import java.io.File;
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FileOrUriNotationParser<T extends Serializable> implements NotationParser<T> {
+
+    private static final Pattern URI_SCHEME = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-\\.]*:.+");
+    private static final Pattern ENCODED_URI = Pattern.compile("%([0-9a-fA-F]{2})");
+    private final FileSystem fileSystem;
+
+    public FileOrUriNotationParser(FileSystem fileSystem) {
+        this.fileSystem = fileSystem;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("File, URI, URL or CharSequence is supported");
+    }
+
+    public T parseNotation(Object notation) throws UnsupportedNotationException {
+        if (notation instanceof File) {
+            return (T) notation;
+        }
+        if (notation instanceof URL) {
+            try {
+                notation = ((URL) notation).toURI();
+            } catch (URISyntaxException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+        if (notation instanceof URI) {
+            URI uri = (URI) notation;
+            if (uri.getScheme().equals("file")) {
+                return (T) new File(uri.getPath());
+            } else {
+                return (T) uri;
+            }
+        }
+        if (notation instanceof CharSequence) {
+            String notationString = notation.toString();
+            if (notationString.startsWith("file:")) {
+                return (T) new File(uriDecode(notationString.substring(5)));
+            }
+            for (File file : File.listRoots()) {
+                String rootPath = file.getAbsolutePath();
+                String normalisedStr = notationString;
+                if (!fileSystem.isCaseSensitive()) {
+                    rootPath = rootPath.toLowerCase();
+                    normalisedStr = normalisedStr.toLowerCase();
+                }
+                if (normalisedStr.startsWith(rootPath) || normalisedStr.startsWith(rootPath.replace(File.separatorChar,
+                        '/'))) {
+                    return (T) new File(notationString);
+                }
+            }
+            // Check if string starts with a URI scheme
+            if (URI_SCHEME.matcher(notationString).matches()) {
+                try {
+                    return (T) new URI(notationString);
+                } catch (URISyntaxException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        } else {
+            DeprecationLogger.nagUserWith(String.format("Converting class %s to File using toString() Method. "
+                    + " This has been deprecated and will be removed in the next version of Gradle. Please use java.io.File, java.lang.String, java.net.URL, or java.net.URI instead.", notation.getClass().getName()));
+        }
+        return (T) new File(notation.toString());
+    }
+
+    private String uriDecode(String path) {
+        StringBuffer builder = new StringBuffer();
+        Matcher matcher = ENCODED_URI.matcher(path);
+        while (matcher.find()) {
+            String val = matcher.group(1);
+            matcher.appendReplacement(builder, String.valueOf((char) (Integer.parseInt(val, 16))));
+        }
+        matcher.appendTail(builder);
+        return builder.toString();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
index 6f75df1..3c6168c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file;
 import org.gradle.api.PathValidation;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.resources.ReadableResource;
 
 import java.io.File;
 import java.net.URI;
@@ -25,6 +26,8 @@ import java.net.URI;
 public interface FileResolver {
     File resolve(Object path);
 
+    ReadableResource resolveResource(Object path);
+
     File resolve(Object path, PathValidation validation);
 
     FileSource resolveLater(Object path);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java
new file mode 100644
index 0000000..55c0a62
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResource.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.file;
+
+import org.gradle.api.internal.resources.ResourceIsAFolderException;
+import org.gradle.api.resources.MissingResourceException;
+
+import java.io.*;
+
+/**
+ * by Szczepan Faber, created at: 11/22/11
+ */
+public class FileResource extends AbstractFileResource {
+
+    public FileResource(File file) {
+        super(file);
+    }
+
+    public InputStream read() {
+        if (file.isDirectory()) {
+            throw new ResourceIsAFolderException(String.format("Cannot read resource %s because it is a folder.", file.getName()));
+        }
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            return new BufferedInputStream(fis);
+        } catch (FileNotFoundException e) {
+            throw new MissingResourceException(String.format("Cannot read the file resource because the file %s does not exist", file.getName()));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
index ef3be8c..3f1068d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
@@ -1,39 +1,45 @@
-/*
- * 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.file;
-
-import java.io.File;
-
-/**
- * FileResolver that uses the file provided to it or constructs one from the toString of the provided object. Used in
- * cases where a FileResolver is needed by the infrastructure, but no base directory can be known.
- *
- * @author Steve Appling
- */
-public class IdentityFileResolver extends AbstractFileResolver {
-    @Override
-    protected File doResolve(Object path) {
-        File file = convertObjectToFile(path);
-        if (!file.isAbsolute()) {
-            throw new UnsupportedOperationException(String.format("Cannot convert relative path %s to an absolute file.", path));
-        }
-        return file;
-    }
-
-    public String resolveAsRelativePath(Object path) {
-        throw new UnsupportedOperationException(String.format("Cannot convert path %s to a relative path.", path));
-    }
-}
+/*
+ * 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.file;
+
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+
+import java.io.File;
+
+/**
+ * FileResolver that uses the file provided to it or constructs one from the toString of the provided object. Used in cases where a FileResolver is needed by the infrastructure, but no base directory
+ * can be known.
+ *
+ * @author Steve Appling
+ */
+public class IdentityFileResolver extends AbstractFileResolver {
+    public IdentityFileResolver() {
+        super(FileSystems.getDefault());
+    }
+
+    @Override
+    protected File doResolve(Object path) {
+        File file = convertObjectToFile(path);
+        if (!file.isAbsolute()) {
+            throw new UnsupportedOperationException(String.format("Cannot convert relative path %s to an absolute file.", path));
+        }
+        return file;
+    }
+
+    public String resolveAsRelativePath(Object path) {
+        throw new UnsupportedOperationException(String.format("Cannot convert path %s to a relative path.", path));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java
new file mode 100644
index 0000000..cbc1a15
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/MaybeCompressedFileResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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.file;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.compression.GzipArchiver;
+import org.gradle.api.resources.MissingResourceException;
+import org.gradle.api.resources.ReadableResource;
+import org.gradle.api.tasks.bundling.Compression;
+
+import java.io.InputStream;
+import java.net.URI;
+
+/**
+ * by Szczepan Faber, created at: 11/23/11
+ */
+public class MaybeCompressedFileResource implements ReadableResource {
+
+    private final ReadableResource resource;
+
+    public MaybeCompressedFileResource(ReadableResource resource) {
+        String ext = FilenameUtils.getExtension(resource.getURI().toString());
+
+        if (Compression.BZIP2.getSupportedExtensions().contains(ext)) {
+            this.resource = new Bzip2Archiver(resource);
+        } else if (Compression.GZIP.getSupportedExtensions().contains(ext)) {
+            this.resource = new GzipArchiver(resource);
+        } else {
+            this.resource = resource;
+        }
+    }
+
+    public InputStream read() throws MissingResourceException {
+        return resource.read();
+    }
+
+    public ReadableResource getResource() {
+        return resource;
+    }
+
+    public String getDisplayName() {
+        return resource.getDisplayName();
+    }
+
+    public URI getURI() {
+        return resource.getURI();
+    }
+
+    public String getBaseName() {
+        return resource.getBaseName();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java
old mode 100644
new mode 100755
index 15176c2..86f9cb3
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java
@@ -1,28 +1,40 @@
-/*
- * 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.file;
-
-import java.io.File;
-
-public interface TemporaryFileProvider {
-    /**
-     * Allocates a new temporary file. Does not create the file.
-     *
-     * @param path The tail path components for the file.
-     * @return The file
-     */
-    File newTemporaryFile(String... path);
-}
+/*
+ * 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.file;
+
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+public interface TemporaryFileProvider {
+    /**
+     * Allocates a new temporary file with the exact specified path,
+     * relative to the temporary file directory. Does not create the file.
+     * Provides no guarantees around whether the file already exists.
+     *
+     * @param path The tail path components for the file.
+     * @return The file
+     */
+    File newTemporaryFile(String... path);
+
+    /**
+     * Allocates and creates a new temporary file with the given prefix, suffix,
+     * and path, relative to the temporary file directory.
+     */
+    File createTemporaryFile(@Nullable String prefix, @Nullable String suffix, @Nullable String... path);
+    
+    File createTemporaryDirectory(@Nullable String prefix, @Nullable String suffix, @Nullable String... path);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java
new file mode 100644
index 0000000..53704f0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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.file;
+
+import org.gradle.api.Nullable;
+import org.gradle.internal.SystemProperties;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TmpDirTemporaryFileProvider extends DefaultTemporaryFileProvider {
+    private final List<File> createdFiles = new ArrayList<File>();
+
+    public TmpDirTemporaryFileProvider() {
+        super(new FileSource() {
+            public File get() {
+                return GFileUtils.canonicalise(new File(SystemProperties.getJavaIoTmpDir()));
+            }
+        });
+    }
+
+    public File createTemporaryFile(@Nullable String prefix, @Nullable String suffix, @Nullable String... path) {
+        return deleteLater(super.createTemporaryFile(prefix, suffix, path));
+    }
+
+    public File createTemporaryDirectory(@Nullable String prefix, @Nullable String suffix, @Nullable String... path) {
+        return deleteLater(super.createTemporaryDirectory(prefix, suffix, path));
+    }
+
+    public void deleteAllCreated() {
+        for (File createdFile : createdFiles) {
+            GFileUtils.deleteQuietly(createdFile);
+        }
+    }
+
+    private File deleteLater(File tmpFile) {
+        createdFiles.add(tmpFile);
+        return tmpFile;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
index 5d9b69f..55c52f9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.api.internal.file.archive;
 
+import org.gradle.api.internal.file.archive.compression.Compressor;
 import org.gradle.api.internal.file.copy.ArchiveCopyAction;
-import org.gradle.api.tasks.bundling.Compression;
 
 public interface TarCopyAction extends ArchiveCopyAction {
-    Compression getCompression();
+    Compressor getCompressor();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
index 75bfe6f..9d46946 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
@@ -15,43 +15,28 @@
  */
 package org.gradle.api.internal.file.archive;
 
+import org.apache.tools.tar.TarEntry;
+import org.apache.tools.tar.TarOutputStream;
+import org.apache.tools.zip.UnixStat;
 import org.gradle.api.GradleException;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.file.copy.CopyAction;
 import org.gradle.api.file.FileVisitDetails;
-import org.apache.tools.tar.TarOutputStream;
-import org.apache.tools.tar.TarEntry;
-import org.apache.tools.bzip2.CBZip2OutputStream;
-import org.apache.tools.zip.UnixStat;
+import org.gradle.api.internal.file.copy.CopyAction;
 import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
-import org.gradle.api.internal.file.copy.ReadableCopySpec;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.zip.GZIPOutputStream;
 
 public class TarCopySpecVisitor extends EmptyCopySpecVisitor {
     private TarOutputStream tarOutStr;
     private File tarFile;
-    private ReadableCopySpec spec;
 
     public void startVisit(CopyAction action) {
         TarCopyAction archiveAction = (TarCopyAction) action;
         try {
             tarFile = archiveAction.getArchivePath();
-            OutputStream outStr = new FileOutputStream(tarFile);
-            switch (archiveAction.getCompression()) {
-                case GZIP:
-                    outStr = new GZIPOutputStream(outStr);
-                    break;
-                case BZIP2:
-                    outStr.write('B');
-                    outStr.write('Z');
-                    outStr = new CBZip2OutputStream(outStr);
-                    break;
-            }
+            OutputStream outStr = archiveAction.getCompressor().compress(tarFile);
             tarOutStr = new TarOutputStream(outStr);
             tarOutStr.setLongFileMode(TarOutputStream.LONGFILE_GNU);
         } catch (Exception e) {
@@ -66,20 +51,15 @@ public class TarCopySpecVisitor extends EmptyCopySpecVisitor {
             throw new UncheckedIOException(e);
         } finally {
             tarOutStr = null;
-            spec = null;
         }
     }
 
-    public void visitSpec(ReadableCopySpec spec) {
-        this.spec = spec;
-    }
-
     public void visitFile(FileVisitDetails fileDetails) {
         try {
             TarEntry archiveEntry = new TarEntry(fileDetails.getRelativePath().getPathString());
             archiveEntry.setModTime(fileDetails.getLastModified());
             archiveEntry.setSize(fileDetails.getSize());
-            archiveEntry.setMode(UnixStat.FILE_FLAG | spec.getFileMode());
+            archiveEntry.setMode(UnixStat.FILE_FLAG | fileDetails.getMode());
             tarOutStr.putNextEntry(archiveEntry);
             fileDetails.copyTo(tarOutStr);
             tarOutStr.closeEntry();
@@ -93,7 +73,7 @@ public class TarCopySpecVisitor extends EmptyCopySpecVisitor {
             // Trailing slash on name indicates entry is a directory
             TarEntry archiveEntry = new TarEntry(dirDetails.getRelativePath().getPathString() + '/');
             archiveEntry.setModTime(dirDetails.getLastModified());
-            archiveEntry.setMode(UnixStat.DIR_FLAG | spec.getDirMode());
+            archiveEntry.setMode(UnixStat.DIR_FLAG | dirDetails.getMode());
             tarOutStr.putNextEntry(archiveEntry);
             tarOutStr.closeEntry();
         } catch (Exception e) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java
index 89b90fd..648ea73 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java
@@ -26,27 +26,30 @@ import org.gradle.api.internal.file.AbstractFileTreeElement;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
 import org.gradle.api.internal.file.collections.FileSystemMirroringFileTree;
 import org.gradle.api.internal.file.collections.MinimalFileTree;
+import org.gradle.api.resources.MissingResourceException;
+import org.gradle.api.resources.ReadableResource;
+import org.gradle.api.resources.ResourceException;
+import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.HashUtil;
+import org.gradle.util.hash.HashUtil;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
-    private final File tarFile;
+    private final ReadableResource resource;
     private final File tmpDir;
 
-    public TarFileTree(File tarFile, File tmpDir) {
-        this.tarFile = tarFile;
-        String expandDirName = String.format("%s_%s", tarFile.getName(), HashUtil.createHash(tarFile.getAbsolutePath()));
+    public TarFileTree(ReadableResource resource, File tmpDir) {
+        this.resource = resource;
+        String expandDirName = String.format("%s_%s", resource.getBaseName(), HashUtil.createCompactMD5(resource.getURI().toString()));
         this.tmpDir = new File(tmpDir, expandDirName);
     }
 
     public String getDisplayName() {
-        return String.format("TAR '%s'", tarFile);
+        return String.format("TAR '%s'", resource.getDisplayName());
     }
 
     public DirectoryFileTree getMirror() {
@@ -54,32 +57,44 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
     }
 
     public void visit(FileVisitor visitor) {
-        if (!tarFile.exists()) {
+        InputStream inputStream;
+        try {
+            inputStream = resource.read();
+            assert inputStream != null;
+        } catch (MissingResourceException e) {
+            DeprecationLogger.nagUserWith(String.format("The specified tar file %s does not exist and will be silently ignored."
+                    + " This behaviour has been deprecated and will cause an error in the next version of Gradle.", getDisplayName()));
             return;
-        }
-        if (!tarFile.isFile()) {
-            throw new InvalidUserDataException(String.format("Cannot expand %s as it is not a file.", getDisplayName()));
+        } catch (ResourceException e) {
+            throw new InvalidUserDataException(String.format("Cannot expand %s.", getDisplayName()), e);
         }
 
-        AtomicBoolean stopFlag = new AtomicBoolean();
         try {
-            FileInputStream inputStream = new FileInputStream(tarFile);
             try {
-                NoCloseTarInputStream tar = new NoCloseTarInputStream(inputStream);
-                TarEntry entry;
-                while (!stopFlag.get() && (entry = tar.getNextEntry()) != null) {
-                    if (entry.isDirectory()) {
-                        visitor.visitDir(new DetailsImpl(entry, tar, stopFlag));
-                    } else {
-                        visitor.visitFile(new DetailsImpl(entry, tar, stopFlag));
-                    }
-
-                }
+                visitImpl(visitor, inputStream);
             } finally {
                 inputStream.close();
             }
         } catch (Exception e) {
-            throw new GradleException(String.format("Could not expand %s.", getDisplayName()), e);
+            String message = "Unable to expand " + getDisplayName() + "\n"
+                    + "  The tar might be corrupted or it is compressed in an unexpected way.\n"
+                    + "  By default the tar tree tries to guess the compression based on the file extension.\n"
+                    + "  If you need to specify the compression explicitly please refer to the DSL reference.";
+            throw new GradleException(message, e);
+        }
+    }
+
+    private void visitImpl(FileVisitor visitor, InputStream inputStream) throws IOException {
+        AtomicBoolean stopFlag = new AtomicBoolean();
+        NoCloseTarInputStream tar = new NoCloseTarInputStream(inputStream);
+        TarEntry entry;
+        while (!stopFlag.get() && (entry = tar.getNextEntry()) != null) {
+            if (entry.isDirectory()) {
+                visitor.visitDir(new DetailsImpl(entry, tar, stopFlag));
+            } else {
+                visitor.visitFile(new DetailsImpl(entry, tar, stopFlag));
+            }
+
         }
     }
 
@@ -97,7 +112,7 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         }
 
         public String getDisplayName() {
-            return String.format("tar entry %s!%s", tarFile, entry.getName());
+            return String.format("tar entry %s!%s", resource.getDisplayName(), entry.getName());
         }
 
         public void stopVisiting() {
@@ -138,6 +153,10 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         public RelativePath getRelativePath() {
             return new RelativePath(!entry.isDirectory(), entry.getName().split("/"));
         }
+
+        public int getMode() {
+            return entry.getMode() & 0777;
+        }
     }
 
     private static class NoCloseTarInputStream extends TarInputStream {
@@ -153,4 +172,4 @@ public class TarFileTree implements MinimalFileTree, FileSystemMirroringFileTree
             return currEntry;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
index bb6cc3c..28881ed 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
@@ -22,7 +22,6 @@ import org.gradle.api.internal.file.copy.CopyAction;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
-import org.gradle.api.internal.file.copy.ReadableCopySpec;
 
 import java.io.File;
 import java.io.IOException;
@@ -30,7 +29,6 @@ import java.io.IOException;
 public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
     private ZipOutputStream zipOutStr;
     private File zipFile;
-    private ReadableCopySpec spec;
 
     public void startVisit(CopyAction action) {
         ArchiveCopyAction archiveAction = (ArchiveCopyAction) action;
@@ -48,21 +46,16 @@ public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         } finally {
-            spec = null;
             zipOutStr = null;
         }
     }
 
-    public void visitSpec(ReadableCopySpec spec) {
-        this.spec = spec;
-    }
-
     public void visitFile(FileVisitDetails fileDetails) {
         try {
             ZipEntry archiveEntry = new ZipEntry(fileDetails.getRelativePath().getPathString());
             archiveEntry.setMethod(ZipEntry.DEFLATED);
             archiveEntry.setTime(fileDetails.getLastModified());
-            archiveEntry.setUnixMode(UnixStat.FILE_FLAG | spec.getFileMode());
+            archiveEntry.setUnixMode(UnixStat.FILE_FLAG | fileDetails.getMode());
             zipOutStr.putNextEntry(archiveEntry);
             fileDetails.copyTo(zipOutStr);
             zipOutStr.closeEntry();
@@ -76,7 +69,7 @@ public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
             // Trailing slash in name indicates that entry is a directory
             ZipEntry archiveEntry = new ZipEntry(dirDetails.getRelativePath().getPathString() + '/');
             archiveEntry.setTime(dirDetails.getLastModified());
-            archiveEntry.setUnixMode(UnixStat.DIR_FLAG | spec.getDirMode());
+            archiveEntry.setUnixMode(UnixStat.DIR_FLAG | dirDetails.getMode());
             zipOutStr.putNextEntry(archiveEntry);
             zipOutStr.closeEntry();
         } catch (Exception e) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
index 17566b5..7c3d1e5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
@@ -27,7 +27,9 @@ import org.gradle.api.internal.file.AbstractFileTreeElement;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
 import org.gradle.api.internal.file.collections.FileSystemMirroringFileTree;
 import org.gradle.api.internal.file.collections.MinimalFileTree;
-import org.gradle.util.HashUtil;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.util.DeprecationLogger;
+import org.gradle.util.hash.HashUtil;
 
 import java.io.File;
 import java.io.IOException;
@@ -44,7 +46,7 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
 
     public ZipFileTree(File zipFile, File tmpDir) {
         this.zipFile = zipFile;
-        String expandDirName = String.format("%s_%s", zipFile.getName(), HashUtil.createHash(zipFile.getAbsolutePath()));
+        String expandDirName = String.format("%s_%s", zipFile.getName(), HashUtil.createCompactMD5(zipFile.getAbsolutePath()));
         this.tmpDir = new File(tmpDir, expandDirName);
     }
 
@@ -58,6 +60,8 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
 
     public void visit(FileVisitor visitor) {
         if (!zipFile.exists()) {
+            DeprecationLogger.nagUserWith(String.format("The specified zip file %s does not exist and will be silently ignored."
+                + " This behaviour has been deprecated and will cause an error in the next version of Gradle.", getDisplayName()));
             return;
         }
         if (!zipFile.isFile()) {
@@ -145,5 +149,18 @@ public class ZipFileTree implements MinimalFileTree, FileSystemMirroringFileTree
         public RelativePath getRelativePath() {
             return new RelativePath(!entry.isDirectory(), entry.getName().split("/"));
         }
+
+        public int getMode() {
+            int unixMode = entry.getUnixMode() & 0777;
+            if(unixMode == 0){
+                //no mode infos available - fall back to defaults
+                if(isDirectory()){
+                    unixMode = FileSystem.DEFAULT_DIR_MODE;
+                }else{
+                    unixMode = FileSystem.DEFAULT_FILE_MODE;
+                }
+            }
+            return unixMode;
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java
new file mode 100644
index 0000000..95893bd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Bzip2Archiver.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 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.file.archive.compression;
+
+import org.apache.tools.bzip2.CBZip2InputStream;
+import org.apache.tools.bzip2.CBZip2OutputStream;
+import org.gradle.api.internal.resources.URIBuilder;
+import org.gradle.api.resources.ReadableResource;
+import org.gradle.api.resources.ResourceException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+
+/**
+ * by Szczepan Faber, created at: 11/16/11
+ */
+public class Bzip2Archiver implements ReadableResource {
+
+    private final ReadableResource resource;
+    private final URI uri;
+
+    public Bzip2Archiver(ReadableResource resource) {
+        assert resource != null;
+        this.resource = resource;
+        this.uri = new URIBuilder(resource.getURI()).schemePrefix("bzip2:").build();
+    }
+
+    public static Compressor getCompressor() {
+        // this is not very beautiful but at some point we will
+        // get rid of Compressor in favor of the writable Resource
+        return new Compressor() {
+            public OutputStream compress(File destination) {
+                try {
+                    OutputStream outStr = new FileOutputStream(destination);
+                    outStr.write('B');
+                    outStr.write('Z');
+                    return new CBZip2OutputStream(outStr);
+                } catch (Exception e) {
+                    String message = String.format("Unable to create bzip2 output stream for file %s", destination);
+                    throw new RuntimeException(message, e);
+                }
+            }
+        };
+    }
+
+    public InputStream read() {
+        InputStream is = resource.read();
+        try {
+            // CBZip2InputStream expects the opening "BZ" to be skipped
+            byte[] skip = new byte[2];
+            is.read(skip);
+            return new CBZip2InputStream(is);
+        } catch (Exception e) {
+            String message = String.format("Unable to create bzip2 input stream for resource %s.", resource.getDisplayName());
+            throw new ResourceException(message, e);
+        }
+    }
+
+    public String getDisplayName() {
+        return resource.getDisplayName();
+    }
+
+    public URI getURI() {
+        return uri;
+    }
+
+    public String getBaseName() {
+        return resource.getBaseName();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Compressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Compressor.java
new file mode 100644
index 0000000..dce667d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Compressor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.file.archive.compression;
+
+import java.io.File;
+import java.io.OutputStream;
+
+/**
+ * Compresses the input
+ */
+public interface Compressor {
+
+    /**
+     * Returns the output stream that is able to compress into the destination file
+     *
+     * @param destination the destination of the compression
+     */
+    OutputStream compress(File destination);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java
new file mode 100644
index 0000000..9666700
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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.file.archive.compression;
+
+import org.gradle.api.internal.resources.URIBuilder;
+import org.gradle.api.resources.ReadableResource;
+import org.gradle.api.resources.ResourceException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * by Szczepan Faber, created at: 11/16/11
+ */
+public class GzipArchiver implements ReadableResource {
+
+    private ReadableResource resource;
+    private URI uri;
+
+    public GzipArchiver(ReadableResource resource) {
+        assert resource != null;
+        this.resource = resource;
+        this.uri = new URIBuilder(resource.getURI()).schemePrefix("gzip:").build();
+    }
+
+    public static Compressor getCompressor() {
+        // this is not very beautiful but at some point we will
+        // get rid of Compressor in favor of the writable Resource
+        return new Compressor() {
+            public OutputStream compress(File destination) {
+                try {
+                    OutputStream outStr = new FileOutputStream(destination);
+                    return new GZIPOutputStream(outStr);
+                } catch (Exception e) {
+                    String message = String.format("Unable to create gzip output stream for file %s.", destination);
+                    throw new RuntimeException(message, e);
+                }
+            }
+        };
+    }
+
+    public InputStream read() {
+        InputStream is = resource.read();
+        try {
+            return new GZIPInputStream(is);
+        } catch (Exception e) {
+            String message = String.format("Unable to create gzip input stream for resource %s.", resource.getDisplayName());
+            throw new ResourceException(message, e);
+        }
+    }
+
+    public String getDisplayName() {
+        return resource.getDisplayName();
+    }
+
+    public URI getURI() {
+        return uri;
+    }
+
+    public String getBaseName() {
+        return resource.getBaseName();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
new file mode 100644
index 0000000..9d29703
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.file.archive.compression;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+/**
+ * by Szczepan Faber, created at: 11/16/11
+ */
+public class SimpleCompressor implements Compressor {
+
+    public OutputStream compress(File destination) {
+        try {
+            return new FileOutputStream(destination);
+        } catch (Exception e) {
+            String message = String.format("Unable to create output stream for file %s.", destination);
+            throw new RuntimeException(message, e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java
index 861fe29..374063a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContext.java
@@ -16,13 +16,15 @@
 package org.gradle.api.internal.file.collections;
 
 import groovy.lang.Closure;
+import org.gradle.api.Task;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.TaskOutputs;
+import org.gradle.internal.UncheckedException;
 import org.gradle.util.GUtil;
-import org.gradle.util.UncheckedException;
 
 import java.io.File;
 import java.util.*;
@@ -99,6 +101,12 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
                 resolveNested(fileCollection);
             } else if (element instanceof FileCollection || element instanceof MinimalFileCollection) {
                 converter.convertInto(element, result, fileResolver);
+            } else if (element instanceof Task) {
+                Task task = (Task) element;
+                queue.add(0, task.getOutputs().getFiles());
+            } else if (element instanceof TaskOutputs) {
+                TaskOutputs outputs = (TaskOutputs) element;
+                queue.add(0, outputs.getFiles());
             } else if (element instanceof Closure) {
                 Closure closure = (Closure) element;
                 Object closureResult = closure.call();
@@ -111,7 +119,7 @@ public class DefaultFileCollectionResolveContext implements ResolvableFileCollec
                 try {
                     callableResult = callable.call();
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
                 if (callableResult != null) {
                     queue.add(0, callableResult);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionAdapter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionAdapter.java
index 18e1e2e..d831d16 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionAdapter.java
@@ -20,12 +20,13 @@ import org.gradle.api.internal.file.AbstractFileCollection;
 import org.gradle.api.tasks.TaskDependency;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.Set;
 
 /**
  * Adapts a {@link MinimalFileSet} into a full {@link org.gradle.api.file.FileCollection}.
  */
-public class FileCollectionAdapter extends AbstractFileCollection implements FileCollectionContainer {
+public class FileCollectionAdapter extends AbstractFileCollection implements FileCollectionContainer, Serializable {
     private final MinimalFileSet fileCollection;
 
     public FileCollectionAdapter(MinimalFileSet fileSet) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionResolveContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionResolveContext.java
index 7fd43a3..8379115 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionResolveContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/FileCollectionResolveContext.java
@@ -1,54 +1,55 @@
-/*
- * Copyright 2011 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.file.collections;
-
-import org.gradle.api.internal.file.FileResolver;
-
-public interface FileCollectionResolveContext {
-    /**
-     * Adds the given element to be resolved. Handles the following types:
-     *
-     * <ul>
-     *     <li>{@link Iterable} - elements are recursively resolved.
-     *     <li>{@link groovy.lang.Closure} - return value is recursively resolved, if not null.
-     *     <li>{@link java.util.concurrent.Callable} - return value is recursively resolved, if not null.
-     *     <li>{@link org.gradle.api.file.FileCollection} - resolved as is.
-     *     <li>{@link MinimalFileSet} - wrapped as a {@link org.gradle.api.file.FileCollection}.
-     *     <li>{@link MinimalFileTree} - wrapped as a {@link org.gradle.api.file.FileTree}.
-     *     <li>{@link FileCollectionContainer} - recursively resolved.
-     *     <li>{@link org.gradle.api.tasks.TaskDependency} - resolved to an empty {@link org.gradle.api.file.FileCollection} which is builtBy the given dependency.
-     *     <li>Everything else - resolved to a File and wrapped in a singleton {@link org.gradle.api.file.FileCollection}.
-     * </ul>
-     *
-     * Generally, the result of resolution is a composite {@link org.gradle.api.file.FileCollection} which contains the union of all files and dependencies add to this context.
-     *
-     * @param element The element to add.
-     * @return this
-     */
-    FileCollectionResolveContext add(Object element);
-
-    /**
-     * Adds a nested context which resolves elements using the given resolvers. Any element added to the returned context will be added to this context. Those elements
-     * which need to be resolved using a file resolver will use the provided resolver, instead of the default used by this context.
-     */
-    FileCollectionResolveContext push(FileResolver fileResolver);
-
-    /**
-     * Creates a new context which can be used to resolve element. Elements added to the returned context will not be added to this context. Instead, the caller should use
-     * one of {@link ResolvableFileCollectionResolveContext#resolveAsFileCollections()} or {@link ResolvableFileCollectionResolveContext#resolveAsFileTrees()}.
-     */
-    ResolvableFileCollectionResolveContext newContext();
-}
+/*
+ * Copyright 2011 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.file.collections;
+
+import org.gradle.api.internal.file.FileResolver;
+
+public interface FileCollectionResolveContext {
+    /**
+     * Adds the given element to be resolved. Handles the following types:
+     *
+     * <ul>
+     *     <li>{@link Iterable} - elements are recursively resolved.
+     *     <li>{@link groovy.lang.Closure} - return value is recursively resolved, if not null.
+     *     <li>{@link java.util.concurrent.Callable} - return value is recursively resolved, if not null.
+     *     <li>{@link org.gradle.api.file.FileCollection} - resolved as is.
+     *     <li>{@link org.gradle.api.Task} - resolved to task.outputs.files
+     *     <li>{@link MinimalFileSet} - wrapped as a {@link org.gradle.api.file.FileCollection}.
+     *     <li>{@link MinimalFileTree} - wrapped as a {@link org.gradle.api.file.FileTree}.
+     *     <li>{@link FileCollectionContainer} - recursively resolved.
+     *     <li>{@link org.gradle.api.tasks.TaskDependency} - resolved to an empty {@link org.gradle.api.file.FileCollection} which is builtBy the given dependency.
+     *     <li>Everything else - resolved to a File and wrapped in a singleton {@link org.gradle.api.file.FileCollection}.
+     * </ul>
+     *
+     * Generally, the result of resolution is a composite {@link org.gradle.api.file.FileCollection} which contains the union of all files and dependencies add to this context.
+     *
+     * @param element The element to add.
+     * @return this
+     */
+    FileCollectionResolveContext add(Object element);
+
+    /**
+     * Adds a nested context which resolves elements using the given resolver. Any element added to the returned context will be added to this context. Those elements
+     * which need to be resolved using a file resolver will use the provided resolver, instead of the default used by this context.
+     */
+    FileCollectionResolveContext push(FileResolver fileResolver);
+
+    /**
+     * Creates a new context which can be used to resolve element. Elements added to the returned context will not be added to this context. Instead, the caller should use
+     * one of {@link ResolvableFileCollectionResolveContext#resolveAsFileCollections()} or {@link ResolvableFileCollectionResolveContext#resolveAsFileTrees()}.
+     */
+    ResolvableFileCollectionResolveContext newContext();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ListBackedFileSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ListBackedFileSet.java
index f230f74..43bdce8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ListBackedFileSet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/ListBackedFileSet.java
@@ -18,12 +18,13 @@ package org.gradle.api.internal.file.collections;
 import org.gradle.util.GUtil;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.*;
 
 /**
  * Adapts a java util collection into a file set.
  */
-public class ListBackedFileSet implements MinimalFileSet {
+public class ListBackedFileSet implements MinimalFileSet, Serializable {
     private final Set<File> files;
 
     public ListBackedFileSet(File... files) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java
index 9224e65..ab9509c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/MapFileTree.java
@@ -19,6 +19,7 @@ import groovy.lang.Closure;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.FileVisitor;
 import org.gradle.api.file.RelativePath;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
 
 import java.io.File;
@@ -35,10 +36,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
  */
 public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree {
     private final Map<RelativePath, Closure> elements = new LinkedHashMap<RelativePath, Closure>();
-    private final File tmpDir;
+    private final Factory<File> tmpDirSource;
 
-    public MapFileTree(File tmpDir) {
-        this.tmpDir = tmpDir;
+    public MapFileTree(final File tmpDir) {
+        this(new Factory<File>() { public File create() { return tmpDir; }});
+    }
+
+    public MapFileTree(Factory<File> tmpDirSource) {
+        this.tmpDirSource = tmpDirSource;
+    }
+
+    private File getTmpDir() {
+        return tmpDirSource.create();
     }
 
     public String getDisplayName() {
@@ -46,7 +55,7 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
     }
 
     public DirectoryFileTree getMirror() {
-        return new DirectoryFileTree(tmpDir);
+        return new DirectoryFileTree(getTmpDir());
     }
 
     public void visit(FileVisitor visitor) {
@@ -120,7 +129,7 @@ public class MapFileTree implements MinimalFileTree, FileSystemMirroringFileTree
 
         public File getFile() {
             if (file == null) {
-                file = path.getFile(tmpDir);
+                file = path.getFile(getTmpDir());
                 copyTo(file);
             }
             return file;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SimpleFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SimpleFileCollection.java
index fb87dda..f866910 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SimpleFileCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/collections/SimpleFileCollection.java
@@ -16,9 +16,10 @@
 package org.gradle.api.internal.file.collections;
 
 import java.io.File;
+import java.io.Serializable;
 import java.util.Collection;
 
-public class SimpleFileCollection extends FileCollectionAdapter {
+public class SimpleFileCollection extends FileCollectionAdapter implements Serializable {
     public SimpleFileCollection(File... files) {
         super(new ListBackedFileSet(files));
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
index 01730f8..f18670a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
@@ -209,11 +209,11 @@ public class CopyActionImpl implements CopyAction, CopySpecSource {
         mainContent.setCaseSensitive(caseSensitive);
     }
 
-    public int getDirMode() {
+    public Integer getDirMode() {
         return mainContent.getDirMode();
     }
 
-    public CopyProcessingSpec setDirMode(int mode) {
+    public CopyProcessingSpec setDirMode(Integer mode) {
         mainContent.setDirMode(mode);
         return this;
     }
@@ -223,11 +223,11 @@ public class CopyActionImpl implements CopyAction, CopySpecSource {
         return this;
     }
 
-    public int getFileMode() {
+    public Integer getFileMode() {
         return mainContent.getFileMode();
     }
 
-    public CopyProcessingSpec setFileMode(int mode) {
+    public CopyProcessingSpec setFileMode(Integer mode) {
         mainContent.setFileMode(mode);
         return this;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
index c95b39c..fabbe41 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.file.copy;
 
 import groovy.lang.Closure;
-import org.apache.tools.zip.UnixStat;
 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.gradle.api.Action;
 import org.gradle.api.file.*;
@@ -46,10 +45,12 @@ public class CopySpecImpl implements CopySpec, ReadableCopySpec {
     private Integer fileMode;
     private Boolean caseSensitive;
     private Boolean includeEmptyDirs;
+    private PathNotationParser<String> pathNotationParser;
 
     private CopySpecImpl(FileResolver resolver, CopySpecImpl parentSpec) {
         this.parentSpec = parentSpec;
         this.resolver = resolver;
+        this.pathNotationParser = new PathNotationParser<String>();
         sourcePaths = new LinkedHashSet<Object>();
         childSpecs = new ArrayList<ReadableCopySpec>();
         patternSet = new PatternSet();
@@ -79,11 +80,7 @@ public class CopySpecImpl implements CopySpec, ReadableCopySpec {
 
     public CopySpec from(Object... sourcePaths) {
         for (Object sourcePath : sourcePaths) {
-            if (sourcePath instanceof CopySpec) {
-                with((CopySpec) sourcePath);
-            } else {
-                this.sourcePaths.add(sourcePath);
-            }
+            this.sourcePaths.add(sourcePath);
         }
         return this;
     }
@@ -166,16 +163,7 @@ public class CopySpecImpl implements CopySpec, ReadableCopySpec {
     }
 
     private String resolveToPath(Object destDir) {
-        Object value = destDir;
-        while (true) {
-            if (value instanceof Closure) {
-                Closure closure = (Closure) value;
-                value = closure.call();
-            } else {
-                break;
-            }
-        }
-        return value.toString();
+        return pathNotationParser.parseNotation(destDir);
     }
 
     public PatternSet getPatternSet() {
@@ -345,32 +333,32 @@ public class CopySpecImpl implements CopySpec, ReadableCopySpec {
         return this;
     }
 
-    public int getDirMode() {
+    public Integer getDirMode() {
         if (dirMode != null) {
             return dirMode;
         }
         if (parentSpec != null) {
             return parentSpec.getDirMode();
         }
-        return UnixStat.DEFAULT_DIR_PERM;
+        return null;
     }
 
-    public int getFileMode() {
+    public Integer getFileMode() {
         if (fileMode != null) {
             return fileMode;
         }
         if (parentSpec != null) {
             return parentSpec.getFileMode();
         }
-        return UnixStat.DEFAULT_FILE_PERM;
+        return null;
     }
 
-    public CopyProcessingSpec setDirMode(int mode) {
+    public CopyProcessingSpec setDirMode(Integer mode) {
         dirMode = mode;
         return this;
     }
 
-    public CopyProcessingSpec setFileMode(int mode) {
+    public CopyProcessingSpec setFileMode(Integer mode) {
         fileMode = mode;
         return this;
     }
@@ -438,11 +426,11 @@ public class CopySpecImpl implements CopySpec, ReadableCopySpec {
             return root.getDestPath().append(spec.getDestPath());
         }
 
-        public int getFileMode() {
+        public Integer getFileMode() {
             return spec.getFileMode();
         }
 
-        public int getDirMode() {
+        public Integer getDirMode() {
             return spec.getDirMode();
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java
index 98a5c3b..a034bdb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java
@@ -1,103 +1,103 @@
-/*
- * 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.file.copy;
-
-import groovy.lang.Closure;
-import groovy.text.SimpleTemplateEngine;
-import groovy.text.Template;
-import org.apache.tools.ant.util.ReaderInputStream;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Transformer;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.ChainingTransformer;
-import org.gradle.util.ConfigureUtil;
-
-import java.io.*;
-import java.lang.reflect.Constructor;
-import java.util.Map;
-
-public class FilterChain implements Transformer<InputStream> {
-    private final ChainingTransformer<Reader> transformers = new ChainingTransformer<Reader>(Reader.class);
-
-    /**
-     * Transforms the given Reader. The original Reader will be closed by the returned Reader.
-     */
-    public Reader transform(Reader original) {
-        return transformers.transform(original);
-    }
-
-    /**
-     * Transforms the given InputStream. The original InputStream will be closed by the returned InputStream.
-     */
-    public InputStream transform(InputStream original) {
-        return new ReaderInputStream(transform(new InputStreamReader(original)));
-    }
-
-    public boolean hasFilters() {
-        return transformers.hasTransformers();
-    }
-
-    public void add(Class<? extends FilterReader> filterType) {
-        add(filterType, null);
-    }
-
-    public void add(final Class<? extends FilterReader> filterType, final Map<String, ?> properties) {
-        transformers.add(new Transformer<Reader>() {
-            public Reader transform(Reader original) {
-                try {
-                    Constructor<? extends FilterReader> constructor = filterType.getConstructor(Reader.class);
-                    FilterReader result = constructor.newInstance(original);
-
-                    if (properties != null) {
-                        ConfigureUtil.configureByMap(properties, result);
-                    }
-                    return result;
-                } catch (Throwable th) {
-                    throw new InvalidUserDataException("Error - Invalid filter specification for " + filterType.getName());
-                }
-            }
-        });
-    }
-
-    public void add(final Closure closure) {
-        transformers.add(new Transformer<Reader>() {
-            public Reader transform(Reader original) {
-                return new LineFilter(original, closure);
-            }
-        });
-    }
-
-    public void expand(final Map<String, ?> properties) {
-        transformers.add(new Transformer<Reader>() {
-            public Reader transform(Reader original) {
-                try {
-                    Template template;
-                    try {
-                        SimpleTemplateEngine engine = new SimpleTemplateEngine();
-                        template = engine.createTemplate(original);
-                    } finally {
-                        original.close();
-                    }
-                    StringWriter writer = new StringWriter();
-                    template.make(properties).writeTo(writer);
-                    return new StringReader(writer.toString());
-                } catch (IOException e) {
-                    throw new UncheckedIOException(e);
-                }
-            }
-        });
-    }
-}
+/*
+ * 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.file.copy;
+
+import groovy.lang.Closure;
+import groovy.text.SimpleTemplateEngine;
+import groovy.text.Template;
+import org.apache.tools.ant.util.ReaderInputStream;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.ChainingTransformer;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.util.Map;
+
+public class FilterChain implements Transformer<InputStream, InputStream> {
+    private final ChainingTransformer<Reader> transformers = new ChainingTransformer<Reader>(Reader.class);
+
+    /**
+     * Transforms the given Reader. The original Reader will be closed by the returned Reader.
+     */
+    public Reader transform(Reader original) {
+        return transformers.transform(original);
+    }
+
+    /**
+     * Transforms the given InputStream. The original InputStream will be closed by the returned InputStream.
+     */
+    public InputStream transform(InputStream original) {
+        return new ReaderInputStream(transform(new InputStreamReader(original)));
+    }
+
+    public boolean hasFilters() {
+        return transformers.hasTransformers();
+    }
+
+    public void add(Class<? extends FilterReader> filterType) {
+        add(filterType, null);
+    }
+
+    public void add(final Class<? extends FilterReader> filterType, final Map<String, ?> properties) {
+        transformers.add(new Transformer<Reader, Reader>() {
+            public Reader transform(Reader original) {
+                try {
+                    Constructor<? extends FilterReader> constructor = filterType.getConstructor(Reader.class);
+                    FilterReader result = constructor.newInstance(original);
+
+                    if (properties != null) {
+                        ConfigureUtil.configureByMap(properties, result);
+                    }
+                    return result;
+                } catch (Throwable th) {
+                    throw new InvalidUserDataException("Error - Invalid filter specification for " + filterType.getName(), th);
+                }
+            }
+        });
+    }
+
+    public void add(final Closure closure) {
+        transformers.add(new Transformer<Reader, Reader>() {
+            public Reader transform(Reader original) {
+                return new LineFilter(original, closure);
+            }
+        });
+    }
+
+    public void expand(final Map<String, ?> properties) {
+        transformers.add(new Transformer<Reader, Reader>() {
+            public Reader transform(Reader original) {
+                try {
+                    Template template;
+                    try {
+                        SimpleTemplateEngine engine = new SimpleTemplateEngine();
+                        template = engine.createTemplate(original);
+                    } finally {
+                        original.close();
+                    }
+                    StringWriter writer = new StringWriter();
+                    template.make(properties).writeTo(writer);
+                    return new StringReader(writer.toString());
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java
index 447c7e6..297f226 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.file.copy;
 
 import groovy.lang.Closure;
 
-import org.gradle.util.SystemProperties;
+import org.gradle.internal.SystemProperties;
 
 import java.io.BufferedReader;
 import java.io.IOException;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
index 97ded0f..5efdd32 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
@@ -17,8 +17,13 @@ package org.gradle.api.internal.file.copy;
 
 import groovy.lang.Closure;
 import org.gradle.api.Action;
-import org.gradle.api.file.*;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.ContentFilterable;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 
 import java.io.*;
 import java.util.Map;
@@ -56,6 +61,7 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
         private final FilterChain filterChain = new FilterChain();
         private RelativePath relativePath;
         private boolean excluded;
+        private Integer mode;
 
         public FileVisitDetailsImpl(FileVisitDetails fileDetails, ReadableCopySpec spec) {
             this.fileDetails = fileDetails;
@@ -116,7 +122,20 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
             if (filterChain.hasFilters()) {
                 return super.copyTo(target);
             } else {
-                return fileDetails.copyTo(target);
+                final boolean copied = fileDetails.copyTo(target);
+                adaptPermissionSpecs(target);
+                return copied;
+            }
+        }
+
+        private void adaptPermissionSpecs(File target) {
+            final Integer specMode = getSpecMode();
+            if(specMode !=null){
+                try {
+                    FileSystems.getDefault().chmod(target, specMode);
+                } catch (IOException e) {
+                    throw new GradleException(String.format("Could not set permission %s on '%s'.", specMode, target), e);
+                }
             }
         }
 
@@ -128,6 +147,23 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
             return relativePath;
         }
 
+        public int getMode() {
+            if (mode != null) {
+                return mode;
+            }
+
+            Integer specMode = getSpecMode();
+            if (specMode != null) {
+                return specMode;
+            }
+
+            return fileDetails.getMode();
+        }
+
+        private Integer getSpecMode() {
+            return fileDetails.isDirectory() ? spec.getDirMode() : spec.getFileMode();
+        }
+
         public void setRelativePath(RelativePath path) {
             this.relativePath = path;
         }
@@ -144,6 +180,10 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
             excluded = true;
         }
 
+        public void setMode(int mode) {
+            this.mode = mode;
+        }
+
         public ContentFilterable filter(Closure closure) {
             filterChain.add(closure);
             return this;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/PathNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/PathNotationParser.java
new file mode 100644
index 0000000..3fd1fde
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/PathNotationParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 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.file.copy;
+
+import groovy.lang.Closure;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.DeprecationLogger;
+
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+public class PathNotationParser<T extends String> implements NotationParser<T> {
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Strings, Boolean, Number like: 'path/to', true, Boolean.TRUE, 42, 3.14");
+        candidateFormats.add("Closures, Callables");
+    }
+
+    public T parseNotation(Object notation) {
+        if (notation == null) {
+            return null;
+        }
+        if (notation instanceof CharSequence
+                || notation instanceof Number
+                || notation instanceof Boolean) {
+            return (T) notation.toString();
+        }
+        if (notation instanceof Closure) {
+            final Closure closure = (Closure) notation;
+            final Object called = closure.call();
+            return parseNotation(called);
+        }
+        if (notation instanceof Callable) {
+            try {
+                final Callable callableNotation = (Callable) notation;
+                final Object called = callableNotation.call();
+                return parseNotation(called);
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+        DeprecationLogger.nagUserWith(String.format("Converting class %s to path using toString() Method. "
+                + "This has been deprecated and will be removed in the next version of Gradle. "
+                + "Please use java.io.File, java.lang.CharSequence, java.lang.Number, java.util.concurrent.Callable "
+                + "or groovy.lang.Closure.", notation.getClass().getName()));
+        return (T) notation.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
index 67aad67..6a623b8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
@@ -25,9 +25,9 @@ import java.util.Collection;
 public interface ReadableCopySpec {
     RelativePath getDestPath();
 
-    int getFileMode();
+    Integer getFileMode();
 
-    int getDirMode();
+    Integer getDirMode();
 
     FileTree getSource();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
index 2fc7709..c871185 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
@@ -23,7 +23,7 @@ import java.util.regex.Pattern;
 /**
  * @author Steve Appling
  */
-public class RegExpNameMapper implements Transformer<String> {
+public class RegExpNameMapper implements Transformer<String, String> {
     private Matcher matcher;
     private String replacement;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java
index c988a42..fa2a60c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java
@@ -21,9 +21,9 @@ import org.gradle.api.file.FileCopyDetails;
 import org.gradle.api.file.RelativePath;
 
 public class RenamingCopyAction implements Action<FileCopyDetails> {
-    private final Transformer<String> transformer;
+    private final Transformer<String, String> transformer;
 
-    public RenamingCopyAction(Transformer<String> transformer) {
+    public RenamingCopyAction(Transformer<String, String> transformer) {
         this.transformer = transformer;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
index 82aa8a3..27c19f6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
@@ -22,7 +22,7 @@ import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.util.ConfigureUtil;
-import org.gradle.util.ObservableUrlClassLoader;
+import org.gradle.util.MutableURLClassLoader;
 
 import java.io.File;
 import java.net.URI;
@@ -32,10 +32,10 @@ public abstract class AbstractScriptHandler implements ScriptHandlerInternal {
     private final RepositoryHandler repositoryHandler;
     private final DependencyHandler dependencyHandler;
     private final ConfigurationContainer configContainer;
-    private final ObservableUrlClassLoader classLoader;
+    private final MutableURLClassLoader classLoader;
     private final Configuration classpathConfiguration;
 
-    public AbstractScriptHandler(ObservableUrlClassLoader classLoader, RepositoryHandler repositoryHandler,
+    public AbstractScriptHandler(MutableURLClassLoader classLoader, RepositoryHandler repositoryHandler,
                                  DependencyHandler dependencyHandler, ScriptSource scriptSource,
                                  ConfigurationContainer configContainer) {
         this.classLoader = classLoader;
@@ -70,7 +70,7 @@ public abstract class AbstractScriptHandler implements ScriptHandlerInternal {
         return configContainer;
     }
 
-    public ObservableUrlClassLoader getClassLoader() {
+    public MutableURLClassLoader getClassLoader() {
         return classLoader;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
index 652a9c5..a801482 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
@@ -19,7 +19,7 @@ import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.ObservableUrlClassLoader;
+import org.gradle.util.MutableURLClassLoader;
 
 import java.io.File;
 import java.net.MalformedURLException;
@@ -28,7 +28,7 @@ public class DefaultScriptHandler extends AbstractScriptHandler {
 
     public DefaultScriptHandler(ScriptSource scriptSource, RepositoryHandler repositoryHandler,
                                 DependencyHandler dependencyHandler, ConfigurationContainer configContainer,
-                                ObservableUrlClassLoader classLoader) {
+                                MutableURLClassLoader classLoader) {
         super(classLoader, repositoryHandler, dependencyHandler, scriptSource, configContainer);
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
old mode 100644
new mode 100755
index a72d432..da94f98
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
@@ -16,20 +16,19 @@
 
 package org.gradle.api.internal.initialization;
 
-import org.gradle.api.Project;
 import org.gradle.api.UnknownProjectException;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.DependencyManagementServices;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
-import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.ObservableUrlClassLoader;
+import org.gradle.util.MutableURLClassLoader;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -37,47 +36,41 @@ import java.util.HashMap;
 import java.util.Map;
 
 public class DefaultScriptHandlerFactory implements ScriptHandlerFactory {
-    private final Factory<RepositoryHandler> repositoryHandlerFactory;
-    private final ConfigurationContainerFactory configurationContainerFactory;
+    private final DependencyManagementServices dependencyManagementServices;
     private final DependencyMetaDataProvider dependencyMetaDataProvider;
-    private final DependencyFactory dependencyFactory;
-    private final Map<Collection<Object>, ObservableUrlClassLoader> classLoaderCache = new HashMap<Collection<Object>, ObservableUrlClassLoader>();  
+    private final Map<Collection<Object>, MutableURLClassLoader> classLoaderCache = new HashMap<Collection<Object>, MutableURLClassLoader>();
+    private final FileResolver fileResolver;
     private final ProjectFinder projectFinder = new ProjectFinder() {
-        public Project getProject(String path) {
+        public ProjectInternal getProject(String path) {
             throw new UnknownProjectException("Cannot use project dependencies in a script classpath definition.");
         }
     };
 
-    public DefaultScriptHandlerFactory(Factory<RepositoryHandler> repositoryHandlerFactory,
-                                       ConfigurationContainerFactory configurationContainerFactory,
-                                       DependencyMetaDataProvider dependencyMetaDataProvider,
-                                       DependencyFactory dependencyFactory) {
-        this.repositoryHandlerFactory = repositoryHandlerFactory;
-        this.configurationContainerFactory = configurationContainerFactory;
+    public DefaultScriptHandlerFactory(DependencyManagementServices dependencyManagementServices,
+                                       FileResolver fileResolver,
+                                       DependencyMetaDataProvider dependencyMetaDataProvider) {
+        this.dependencyManagementServices = dependencyManagementServices;
+        this.fileResolver = fileResolver;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
-        this.dependencyFactory = dependencyFactory;
     }
 
     public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader) {
         return create(scriptSource, parentClassLoader, new BasicDomainObjectContext());
     }
 
-    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader,
-                                        DomainObjectContext context) {
-        RepositoryHandler repositoryHandler = repositoryHandlerFactory.create();
-        ConfigurationContainer configurationContainer = configurationContainerFactory.createConfigurationContainer(
-                repositoryHandler, dependencyMetaDataProvider, context);
-        DependencyHandler dependencyHandler = new DefaultDependencyHandler(configurationContainer, dependencyFactory,
-                projectFinder);
+    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader, DomainObjectContext context) {
+        DependencyResolutionServices services = dependencyManagementServices.create(fileResolver, dependencyMetaDataProvider, projectFinder, context);
+        RepositoryHandler repositoryHandler = services.getResolveRepositoryHandler();
+        ConfigurationContainer configurationContainer = services.getConfigurationContainer();
+        DependencyHandler dependencyHandler = services.getDependencyHandler();
         Collection<Object> key = Arrays.asList(scriptSource.getClassName(), parentClassLoader);
-        ObservableUrlClassLoader classLoader = classLoaderCache.get(key);
+        MutableURLClassLoader classLoader = classLoaderCache.get(key);
         if (classLoader == null) {
-            classLoader = new ObservableUrlClassLoader(parentClassLoader);
+            classLoader = new MutableURLClassLoader(parentClassLoader);
             classLoaderCache.put(key, classLoader);
-            return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer,
-                    classLoader);
+            return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoader);
         }
-        
+
         return new NoClassLoaderUpdateScriptHandler(classLoader, repositoryHandler, dependencyHandler, scriptSource, configurationContainer);
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
index c19f4db..5a596f6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
@@ -19,10 +19,10 @@ import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.util.ObservableUrlClassLoader;
+import org.gradle.util.MutableURLClassLoader;
 
 public class NoClassLoaderUpdateScriptHandler extends AbstractScriptHandler {
-    public NoClassLoaderUpdateScriptHandler(ObservableUrlClassLoader classLoader, RepositoryHandler repositoryHandler,
+    public NoClassLoaderUpdateScriptHandler(MutableURLClassLoader classLoader, RepositoryHandler repositoryHandler,
                                             DependencyHandler dependencyHandler, ScriptSource scriptSource,
                                             ConfigurationContainer configContainer) {
         super(classLoader, repositoryHandler, dependencyHandler, scriptSource, configContainer);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/NotationParserBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/NotationParserBuilder.java
new file mode 100644
index 0000000..f0f6499
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/NotationParserBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.notations;
+
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.parsers.CompositeNotationParser;
+import org.gradle.api.internal.notations.parsers.ErrorHandlingNotationParser;
+import org.gradle.api.internal.notations.parsers.FlatteningNotationParser;
+import org.gradle.api.internal.notations.parsers.JustReturningParser;
+import org.gradle.util.GUtil;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * by Szczepan Faber, created at: 11/8/11
+ */
+public class NotationParserBuilder<T> {
+    private Class<T> resultingType;
+    private String invalidNotationMessage;
+    private Collection<NotationParser<? extends T>> notationParsers = new LinkedList<NotationParser<? extends T>>();
+
+    public NotationParserBuilder<T> resultingType(Class<T> resultingType) {
+        this.resultingType = resultingType;
+        return this;
+    }
+
+    public NotationParserBuilder<T> parser(NotationParser<? extends T> parser) {
+        this.notationParsers.add(parser);
+        return this;
+    }
+
+    public NotationParserBuilder<T> invalidNotationMessage(String invalidNotationMessage) {
+        this.invalidNotationMessage = invalidNotationMessage;
+        return this;
+    }
+
+    public NotationParserBuilder<T> parsers(Iterable<? extends NotationParser<? extends T>> notationParsers) {
+        GUtil.addToCollection(this.notationParsers, notationParsers);
+        return this;
+    }
+
+    public NotationParser<Set<T>> toFlatteningComposite() {
+        return wrapInErrorHandling(new FlatteningNotationParser<T>(create()));
+    }
+
+    public NotationParser<T> toComposite() {
+        return wrapInErrorHandling(create());
+    }
+
+    private <S> NotationParser<S> wrapInErrorHandling(NotationParser<S> parser) {
+        return new ErrorHandlingNotationParser<S>(resultingType.getSimpleName(), invalidNotationMessage, parser);
+    }
+
+    private CompositeNotationParser<T> create() {
+        assert resultingType != null : "resultingType cannot be null";
+
+        List<NotationParser<? extends T>> composites = new LinkedList<NotationParser<? extends T>>();
+        composites.add(new JustReturningParser<T>(resultingType));
+        composites.addAll(this.notationParsers);
+
+        return new CompositeNotationParser<T>(composites);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/NotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/NotationParser.java
new file mode 100644
index 0000000..32bfe0d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/NotationParser.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.notations.api;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 11/8/11
+ */
+public interface NotationParser<T> {
+    void describe(Collection<String> candidateFormats);
+
+    T parseNotation(Object notation) throws UnsupportedNotationException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/TopLevelNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/TopLevelNotationParser.java
new file mode 100644
index 0000000..e0c1ff8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/TopLevelNotationParser.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.notations.api;
+
+/**
+ * Marker interface to group all top-level notation parsers.
+ *
+ * by Szczepan Faber, created at: 11/8/11
+ */
+public interface TopLevelNotationParser {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/UnsupportedNotationException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/UnsupportedNotationException.java
new file mode 100644
index 0000000..5fcd33a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/UnsupportedNotationException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.notations.api;
+
+public class UnsupportedNotationException extends RuntimeException {
+    private final Object notation;
+
+    public UnsupportedNotationException(Object notation) {
+        this.notation = notation;
+    }
+
+    public Object getNotation() {
+        return notation;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
new file mode 100644
index 0000000..0e9cf2f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 11/10/11
+ */
+public class CompositeNotationParser<T> implements NotationParser<T> {
+
+    private final Collection<NotationParser<? extends T>> delegates;
+
+    public CompositeNotationParser(NotationParser<? extends T>... delegates) {
+        this.delegates = Arrays.asList(delegates);
+    }
+
+    public CompositeNotationParser(Collection<NotationParser<? extends T>> delegates) {
+        assert delegates != null : "delegates cannot be null!";
+        this.delegates = delegates;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        for (NotationParser<? extends T> delegate : delegates) {
+            delegate.describe(candidateFormats);
+        }
+    }
+
+    public T parseNotation(Object notation) {
+        for (NotationParser<? extends T> delegate : delegates) {
+            try {
+                return delegate.parseNotation(notation);
+            } catch (UnsupportedNotationException e) {
+                // Ignore
+            }
+        }
+
+        throw new UnsupportedNotationException(notation);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
new file mode 100644
index 0000000..979d79e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.util.GUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Formatter;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 11/8/11
+ */
+public class ErrorHandlingNotationParser<T> implements NotationParser<T> {
+    private final String targetTypeDisplayName;
+    private final String invalidNotationMessage;
+    private final NotationParser<T> delegate;
+
+    public ErrorHandlingNotationParser(String targetTypeDisplayName, String invalidNotationMessage, NotationParser<T> delegate) {
+        this.targetTypeDisplayName = targetTypeDisplayName;
+        this.invalidNotationMessage = invalidNotationMessage;
+        this.delegate = delegate;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+    }
+
+    public T parseNotation(Object notation) {
+        Object brokenNotation;
+        try {
+            return delegate.parseNotation(notation);
+        } catch (UnsupportedNotationException e) {
+            brokenNotation = e.getNotation();
+        }
+
+        Formatter message = new Formatter();
+        if (brokenNotation == null) {
+            message.format("Cannot convert a null value to an object of type %s.%n", targetTypeDisplayName);
+        } else {
+            message.format("Cannot convert the provided notation to an object of type %s: %s.%n", targetTypeDisplayName, brokenNotation);
+        }
+
+        message.format("The following types/formats are supported:");
+        List<String> formats = new ArrayList<String>();
+        describe(formats);
+        for (String format : formats) {
+            message.format("%n  - %s", format);
+        }
+        if (GUtil.isTrue(invalidNotationMessage)) {
+            message.format("%n%s", invalidNotationMessage);
+        }
+        throw new InvalidUserDataException(message.toString());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/FlatteningNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/FlatteningNotationParser.java
new file mode 100644
index 0000000..c962462
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/FlatteningNotationParser.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.util.GUtil;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Flattens or collectionizes input and passes the input notations to the delegates. Returns a set.
+ */
+public class FlatteningNotationParser<T> implements NotationParser<Set<T>> {
+
+    private final NotationParser<T> delegate;
+
+    public FlatteningNotationParser(NotationParser<T> delegate) {
+        assert delegate != null : "delegate cannot be null";
+        this.delegate = delegate;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        delegate.describe(candidateFormats);
+        candidateFormats.add("Collections or arrays of any other supported format. Nested collections/arrays will be flattened.");
+    }
+
+    public Set<T> parseNotation(Object notation) {
+        Set<T> out = new LinkedHashSet<T>();
+        Collection notations = GUtil.collectionize(notation);
+        for (Object n : notations) {
+            out.add(delegate.parseNotation(n));
+        }
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/JustReturningParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/JustReturningParser.java
new file mode 100644
index 0000000..7b7efc6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/JustReturningParser.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 11/8/11
+ */
+public class JustReturningParser<T> implements NotationParser<T> {
+
+    private final Class<T> passThroughType;
+
+    public JustReturningParser(Class<T> passThroughType) {
+        this.passThroughType = passThroughType;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add(String.format("Instances of %s.", passThroughType.getSimpleName()));
+    }
+
+    public T parseNotation(Object notation) {
+        if (!passThroughType.isInstance(notation)) {
+            throw new UnsupportedNotationException(notation);
+        }
+        return passThroughType.cast(notation);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapKey.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapKey.java
new file mode 100644
index 0000000..d23326d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapKey.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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.notations.parsers;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.PARAMETER)
+public @interface MapKey {
+    String value();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
new file mode 100644
index 0000000..f7ce1b8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.api.tasks.Optional;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.ConfigureUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * Converts a {@code Map<String, Object>} to the target type. Subclasses should define a {@code T parseMap()} method which takes a parameter 
+ * for each key value required from the source map. Each parameter should be annotated with a {@code @MapKey} annotation, and can also
+ * be annotated with a {@code @optional} annotation.
+ */
+public abstract class MapNotationParser<T> extends TypedNotationParser<Map, T> implements NotationParser<T> {
+    private final Method convertMethod;
+    private final String[] keyNames;
+    private final boolean[] optional;
+
+    public MapNotationParser() {
+        super(Map.class);
+        convertMethod = findConvertMethod();
+        keyNames = new String[convertMethod.getParameterAnnotations().length];
+        optional = new boolean[convertMethod.getParameterAnnotations().length];
+        for (int i = 0; i < convertMethod.getParameterAnnotations().length; i++) {
+            Annotation[] annotations = convertMethod.getParameterAnnotations()[i];
+            keyNames[i] = keyName(annotations);
+            optional[i] = optional(annotations);
+        }
+    }
+
+    private Method findConvertMethod() {
+        for (Method method : getClass().getDeclaredMethods()) {
+            if (method.getName().equals("parseMap")) {
+                method.setAccessible(true);
+                return method;
+            }
+        }
+        throw new UnsupportedOperationException(String.format("No parseMap() method found on class %s.", getClass().getSimpleName()));
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add("Maps");
+    }
+
+    public T parseType(Map values) throws UnsupportedNotationException {
+        Map<String, Object> mutableValues = new HashMap<String, Object>(values);
+        Set<String> missing = new TreeSet<String>();
+
+        Object[] params = new Object[convertMethod.getParameterTypes().length];
+        for (int i = 0; i < params.length; i++) {
+            String keyName = keyNames[i];
+            boolean optional = this.optional[i];
+            Class<?> type = convertMethod.getParameterTypes()[i];
+            Object value;
+            if (type.equals(String.class)) {
+                value = get(mutableValues, keyName);
+            } else {
+                value = type.cast(mutableValues.get(keyName));
+            }
+            if (!optional && value == null) {
+                missing.add(keyName);
+            }
+            mutableValues.remove(keyName);
+            params[i] = value;
+        }
+        if (!missing.isEmpty()) {
+            throw new InvalidUserDataException(String.format("Required keys %s are missing from map %s.", missing, values));
+        }
+
+        T result;
+        try {
+            result = (T) convertMethod.invoke(this, params);
+        } catch (IllegalAccessException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.unwrapAndRethrow(e);
+        }
+
+        ConfigureUtil.configureByMap(mutableValues, result);
+        return result;
+    }
+
+    private boolean optional(Annotation[] annotations) {
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof Optional) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String keyName(Annotation[] annotations) {
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof MapKey) {
+                return ((MapKey) annotation).value();
+            }
+        }
+        throw new UnsupportedOperationException("No @Key annotation on parameter of parseMap() method");
+    }
+
+    protected String get(Map<String, Object> args, String key) {
+        Object value = args.get(key);
+        String str = value != null ? value.toString() : null;
+        if (str != null && str.length() == 0) {
+            return null;
+        }
+        return str;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParser.java
new file mode 100644
index 0000000..627c6a5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParser.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+
+import java.util.Collection;
+
+/**
+ * by Szczepan Faber, created at: 11/9/11
+ */
+public abstract class TypedNotationParser<N, T> implements NotationParser<T> {
+
+    private final Class<N> typeToken;
+
+    public TypedNotationParser(Class<N> typeToken) {
+        assert typeToken != null : "typeToken cannot be null";
+        this.typeToken = typeToken;
+    }
+
+    public void describe(Collection<String> candidateFormats) {
+        candidateFormats.add(String.format("Instances of %s.", typeToken.getSimpleName()));
+    }
+
+    public T parseNotation(Object notation) {
+        if (!typeToken.isInstance(notation)) {
+            throw new UnsupportedNotationException(notation);
+        }
+        return parseType(typeToken.cast(notation));
+    }
+
+    abstract protected T parseType(N notation);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/AbstractConvention.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/AbstractConvention.java
deleted file mode 100644
index 4211376..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/AbstractConvention.java
+++ /dev/null
@@ -1,127 +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.plugins;
-
-import org.gradle.api.plugins.Convention;
-import org.gradle.api.internal.BeanDynamicObject;
-
-import java.util.*;
-
-import groovy.lang.MissingPropertyException;
-import groovy.lang.MissingMethodException;
-
-/**
- * @author Hans Dockter
- */
-public class AbstractConvention implements Convention {
-
-    private final Map<String, Object> plugins = new LinkedHashMap<String, Object>();
-
-    public Map<String, Object> getPlugins() {
-        return plugins;
-    }
-
-    public boolean hasProperty(String property) {
-        for (Object object : plugins.values()) {
-            if (new BeanDynamicObject(object).hasProperty(property)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public Map<String, Object> getProperties() {
-        Map<String, Object> properties = new HashMap<String, Object>();
-        List<Object> reverseOrder = new ArrayList<Object>(plugins.values());
-        Collections.reverse(reverseOrder);
-        for (Object object : reverseOrder) {
-            properties.putAll(new BeanDynamicObject(object).getProperties());
-        }
-        return properties;
-    }
-
-    public Object getProperty(String name) throws MissingPropertyException {
-        BeanDynamicObject dynamicObject = new BeanDynamicObject(this);
-        if (dynamicObject.hasProperty(name)) {
-            return dynamicObject.getProperty(name);
-        }
-        for (Object object : plugins.values()) {
-            dynamicObject = new BeanDynamicObject(object);
-            if (dynamicObject.hasProperty(name)) {
-                return dynamicObject.getProperty(name);
-            }
-        }
-        throw new MissingPropertyException(name, Convention.class);
-    }
-
-    public void setProperty(String property, Object value) {
-        for (Object object : plugins.values()) {
-            BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
-            if (dynamicObject.hasProperty(property)) {
-                dynamicObject.setProperty(property, value);
-                return;
-            }
-        }
-        throw new MissingPropertyException(property, Convention.class);
-    }
-
-    public Object invokeMethod(String name, Object... arguments) {
-        for (Object object : plugins.values()) {
-            BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
-            if (dynamicObject.hasMethod(name, arguments)) {
-                return dynamicObject.invokeMethod(name, arguments);
-            }
-        }
-        throw new MissingMethodException(name, Convention.class, arguments);
-    }
-
-    public boolean hasMethod(String method, Object... args) {
-        for (Object object : plugins.values()) {
-            BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
-            if (dynamicObject.hasMethod(method, args)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public <T> T getPlugin(Class<T> type) {
-        T value = findPlugin(type);
-        if (value == null) {
-            throw new IllegalStateException(String.format("Could not find any convention object of type %s.",
-                    type.getSimpleName()));
-        }
-        return value;
-    }
-
-    public <T> T findPlugin(Class<T> type) throws IllegalStateException {
-        List<T> values = new ArrayList<T>();
-        for (Object object : plugins.values()) {
-            if (type.isInstance(object)) {
-                values.add(type.cast(object));
-            }
-        }
-        if (values.isEmpty()) {
-            return null;
-        }
-        if (values.size() > 1) {
-            throw new IllegalStateException(String.format("Found multiple convention objects of type %s.",
-                    type.getSimpleName()));
-        }
-        return values.get(0);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.groovy
deleted file mode 100644
index 76d2f67..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.groovy
+++ /dev/null
@@ -1,34 +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.plugins
-
-/**
- * @author Hans Dockter
- */
-class DefaultConvention extends AbstractConvention {
-    def propertyMissing(String property) {
-        super.getProperty(property)
-    }
-
-    void setProperty(String property, value) {
-        super.setProperty(property, value)
-    }
-
-    def methodMissing(String method, arguments) {
-        super.invokeMethod(method, arguments)
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
new file mode 100644
index 0000000..faf8711
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
@@ -0,0 +1,235 @@
+/*
+ * 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.plugins;
+
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.BeanDynamicObject;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
+import org.gradle.util.DeprecationLogger;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConvention implements Convention {
+
+    private final Map<String, Object> plugins = new LinkedHashMap<String, Object>();
+    private final DefaultConvention.ExtensionsDynamicObject extensionsDynamicObject = new ExtensionsDynamicObject();
+    private final ExtensionsStorage extensionsStorage = new ExtensionsStorage();
+    private final ExtraPropertiesExtension extraProperties = new DefaultExtraPropertiesExtension();
+    private final Instantiator instantiator;
+
+    /**
+     * This method should be used in runtime code proper as means that the convention cannot create
+     * dynamic extensions.
+     *
+     * It's here for backwards compatibility with our tests and for convenience.
+     *
+     * @see #DefaultConvention(org.gradle.api.internal.Instantiator)
+     */
+    public DefaultConvention() {
+        this(null);
+    }
+
+    public DefaultConvention(Instantiator instantiator) {
+        this.instantiator = instantiator;
+        add(ExtraPropertiesExtension.EXTENSION_NAME, extraProperties);
+    }
+
+    public Map<String, Object> getPlugins() {
+        return plugins;
+    }
+
+    public DynamicObject getExtensionsAsDynamicObject() {
+        return extensionsDynamicObject;
+    }
+
+    private Instantiator getInstantiator() {
+        if (instantiator == null) {
+            throw new GradleException("request for DefaultConvention.instantiator when the object was constructed without a convention");
+        }
+        return instantiator;
+    }
+
+    public <T> T getPlugin(Class<T> type) {
+        T value = findPlugin(type);
+        if (value == null) {
+            throw new IllegalStateException(String.format("Could not find any convention object of type %s.",
+                    type.getSimpleName()));
+        }
+        return value;
+    }
+
+    public <T> T findPlugin(Class<T> type) throws IllegalStateException {
+        List<T> values = new ArrayList<T>();
+        for (Object object : plugins.values()) {
+            if (type.isInstance(object)) {
+                values.add(type.cast(object));
+            }
+        }
+        if (values.isEmpty()) {
+            return null;
+        }
+        if (values.size() > 1) {
+            throw new IllegalStateException(String.format("Found multiple convention objects of type %s.",
+                    type.getSimpleName()));
+        }
+        return values.get(0);
+    }
+
+    public void add(String name, Object extension) {
+        if (extension instanceof Class) {
+            create(name, (Class<?>) extension);
+        } else {
+            extensionsStorage.add(name, extension);
+        }
+    }
+
+    public void add(String name, Class<?> type, Object... constructionArguments) {
+        DeprecationLogger.nagUserOfReplacedMethod("extensions.add(String, Class, Object...)", "extensions.create(String, Class, Object...)");
+        create(name, type, constructionArguments);
+    }
+
+    public <T> T create(String name, Class<T> type, Object... constructionArguments) {
+        T instance = getInstantiator().newInstance(type, constructionArguments);
+        add(name, instance);
+        return instance;
+    }
+
+    public ExtraPropertiesExtension getExtraProperties() {
+        return extraProperties;
+    }
+
+    public <T> T getByType(Class<T> type) {
+        return extensionsStorage.getByType(type);
+    }
+
+    public <T> T findByType(Class<T> type) {
+        return extensionsStorage.findByType(type);
+    }
+
+    public Object getByName(String name) {
+        return extensionsStorage.getByName(name);
+    }
+
+    public Object findByName(String name) {
+        return extensionsStorage.findByName(name);
+    }
+
+    public Object propertyMissing(String name) {
+        return getByName(name);
+    }
+
+    public void propertyMissing(String name, Object value) {
+        extensionsStorage.checkExtensionIsNotReassigned(name);
+        add(name, value);
+    }
+    
+    private class ExtensionsDynamicObject implements DynamicObject {
+        public boolean hasProperty(String name) {
+            if (extensionsStorage.hasExtension(name)) {
+                return true;
+            }
+            for (Object object : plugins.values()) {
+                if (new BeanDynamicObject(object).hasProperty(name)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public Map<String, Object> getProperties() {
+            Map<String, Object> properties = new HashMap<String, Object>();
+            List<Object> reverseOrder = new ArrayList<Object>(plugins.values());
+            Collections.reverse(reverseOrder);
+            for (Object object : reverseOrder) {
+                properties.putAll(new BeanDynamicObject(object).getProperties());
+            }
+            properties.putAll(extensionsStorage.getAsMap());
+            return properties;
+        }
+
+        public Object getProperty(String name) throws MissingPropertyException {
+            if (extensionsStorage.hasExtension(name)) {
+                return extensionsStorage.getByName(name);
+            }
+            for (Object object : plugins.values()) {
+                DynamicObject dynamicObject = new BeanDynamicObject(object);
+                if (dynamicObject.hasProperty(name)) {
+                    return dynamicObject.getProperty(name);
+                }
+            }
+            throw new MissingPropertyException(name, Convention.class);
+        }
+
+        public Object propertyMissing(String name) {
+            return getProperty(name);
+        }
+
+        public void setProperty(String name, Object value) {
+            extensionsStorage.checkExtensionIsNotReassigned(name);
+            for (Object object : plugins.values()) {
+                BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
+                if (dynamicObject.hasProperty(name)) {
+                    dynamicObject.setProperty(name, value);
+                    return;
+                }
+            }
+            throw new MissingPropertyException(name, Convention.class);
+        }
+
+        public void propertyMissing(String name, Object value) {
+            setProperty(name, value);
+        }
+
+        public Object invokeMethod(String name, Object... args) {
+            if (extensionsStorage.isConfigureExtensionMethod(name, args)) {
+                return extensionsStorage.configureExtension(name, args);
+            }
+            for (Object object : plugins.values()) {
+                BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
+                if (dynamicObject.hasMethod(name, args)) {
+                    return dynamicObject.invokeMethod(name, args);
+                }
+            }
+            throw new MissingMethodException(name, Convention.class, args);
+        }
+
+        public Object methodMissing(String name, Object args) {
+            return invokeMethod(name, (Object[])args);
+        }
+        
+        public boolean hasMethod(String name, Object... args) {
+            if (extensionsStorage.isConfigureExtensionMethod(name, args)) {
+                return true;
+            }
+            for (Object object : plugins.values()) {
+                BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
+                if (dynamicObject.hasMethod(name, args)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultExtraPropertiesExtension.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultExtraPropertiesExtension.java
new file mode 100644
index 0000000..1d2c3c0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultExtraPropertiesExtension.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 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 groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import groovy.lang.MissingPropertyException;
+import groovy.lang.ReadOnlyPropertyException;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultExtraPropertiesExtension extends GroovyObjectSupport implements ExtraPropertiesExtension {
+
+    Map<String, Object> storage = new HashMap<String, Object>();
+
+    public boolean has(String name) {
+        return storage.containsKey(name);
+    }
+
+    public Object get(String name) {
+        if (storage.containsKey(name)) {
+            return storage.get(name);    
+        } else {
+            throw new UnknownPropertyException(this, name);
+        }
+    }
+
+    public void set(String name, Object value) {
+        storage.put(name, value);
+    }
+
+    public Object getProperty(String name) {
+        if (name.equals("properties")) {
+            return getProperties();
+        }
+
+        try {
+            return get(name);
+        } catch (UnknownPropertyException e) {
+            throw new MissingPropertyException(e.getMessage());
+        }
+    }
+
+    public void setProperty(String name, Object newValue) {
+        if (name.equals("properties")) {
+            throw new ReadOnlyPropertyException("name", ExtraPropertiesExtension.class);
+        }
+        set(name, newValue);
+    }
+
+    public Map<String, Object> getProperties() {
+        return new HashMap<String, Object>(storage);
+    }
+
+    public Object methodMissing(String name, Object args) {
+        Object item = storage.get(name);
+        if (item != null && item instanceof Closure) {
+            Closure closure = (Closure)item;
+            return closure.call((Object[])args);
+        } else {
+            throw new groovy.lang.MissingMethodException(name, getClass(), (Object[])args);
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
old mode 100644
new mode 100755
index 991f247..31f7524
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
@@ -89,7 +89,7 @@ public class DefaultObjectConfigurationAction implements ObjectConfigurationActi
                 Project project = (Project) target;
                 project.getPlugins().apply(pluginClass);
             } else {
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException(String.format("Cannot apply plugin of class '%s' to '%s' (class: %s) as it is not a Project", pluginClass.getName(), target.toString(), target.getClass().getName()));
             }
         }
     }
@@ -100,7 +100,7 @@ public class DefaultObjectConfigurationAction implements ObjectConfigurationActi
                 Project project = (Project) target;
                 project.getPlugins().apply(pluginId);
             } else {
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException(String.format("Cannot apply plugin with id '%s' to '%s' (class: %s) as it is not a Project", pluginId, target.toString(), target.getClass().getName()));
             }
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java
index 8b2bd27..416d3bc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java
@@ -18,33 +18,36 @@ package org.gradle.api.internal.plugins;
 import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.api.Plugin;
-import org.gradle.api.internal.DefaultDomainObjectContainer;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.collections.CollectionFilter;
 import org.gradle.api.plugins.PluginCollection;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
-import org.gradle.util.DeprecationLogger;
 
-public class DefaultPluginCollection<T extends Plugin> extends DefaultDomainObjectContainer<T>
-        implements PluginCollection<T> {
+public class DefaultPluginCollection<T extends Plugin> extends DefaultDomainObjectSet<T> implements PluginCollection<T> {
+
     public DefaultPluginCollection(Class<T> type) {
         super(type);
     }
+    
+    protected DefaultPluginCollection(DefaultPluginCollection<? super T> collection, CollectionFilter<T> filter) {
+        super(collection, filter);
+    }
 
-    protected DefaultPluginCollection(Class<T> type, ObjectStore<T> store) {
-        super(type, store);
+    protected <S extends T> DefaultPluginCollection<S> filtered(CollectionFilter<S> filter) {
+        return new DefaultPluginCollection<S>(this, filter);
     }
 
-    public PluginCollection<T> matching(Spec<? super T> spec) {
-        return new DefaultPluginCollection<T>(getType(), storeWithSpec(spec));
+    public <S extends T> PluginCollection<S> withType(Class<S> type) {
+        return filtered(createFilter(type));
     }
 
-    @Override
-    public PluginCollection<T> matching(Closure spec) {
-        return matching(Specs.convertClosureToSpec(spec));
+    public PluginCollection<T> matching(Spec<? super T> spec) {
+        return filtered(createFilter(spec));
     }
 
-    public <S extends T> PluginCollection<S> withType(Class<S> type) {
-        return new DefaultPluginCollection<S>(type, storeWithType(type));
+    public PluginCollection<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
     }
 
     public Action<? super T> whenPluginAdded(Action<? super T> action) {
@@ -55,13 +58,4 @@ public class DefaultPluginCollection<T extends Plugin> extends DefaultDomainObje
         whenObjectAdded(closure);
     }
 
-    public void allPlugins(Action<? super T> action) {
-        DeprecationLogger.nagUser("PluginCollection.allPlugins()", "all()");
-        all(action);
-    }
-
-    public void allPlugins(Closure closure) {
-        DeprecationLogger.nagUser("PluginCollection.allPlugins()", "all()");
-        all(closure);
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java
index d973901..b8bdcc3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java
@@ -97,10 +97,15 @@ public class DefaultPluginRegistry implements PluginRegistry {
         }
 
         try {
-            implClass = classLoader.loadClass(implClassName).asSubclass(Plugin.class);
+            Class<?> rawClass = classLoader.loadClass(implClassName);
+            if (!Plugin.class.isAssignableFrom(rawClass)) {
+                throw new PluginInstantiationException(String.format("Implementation class '%s' specified for plugin '%s' does not implement the Plugin interface. Specified in %s.",
+                        implClassName, pluginId, resource));
+            }
+            implClass = rawClass.asSubclass(Plugin.class);
         } catch (ClassNotFoundException e) {
             throw new PluginInstantiationException(String.format(
-                    "Could not find implementation class '%s' for plugin '%s' specified in %s.", implClass, pluginId,
+                    "Could not find implementation class '%s' for plugin '%s' specified in %s.", implClassName, pluginId,
                     resource), e);
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
index 1630aa7..a6fe69a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
@@ -50,11 +50,15 @@ public class DefaultProjectsPluginContainer extends DefaultPluginCollection<Plug
     }
 
     public Plugin findPlugin(String id) {
-        return findPlugin(getTypeForId(id));
+        try {
+            return findPlugin(getTypeForId(id));
+        } catch (UnknownPluginException e) {
+            return null;
+        }
     }
 
     public <T extends Plugin> T findPlugin(Class<T> type) {
-        for (Plugin plugin : getAll()) {
+        for (Plugin plugin : this) {
             if (plugin.getClass().equals(type)) {
                 return type.cast(plugin);
             }
@@ -65,7 +69,7 @@ public class DefaultProjectsPluginContainer extends DefaultPluginCollection<Plug
     private <T extends Plugin> T addPluginInternal(Class<T> type) {
         if (findPlugin(type) == null) {
             Plugin plugin = providePlugin(type);
-            addObject(plugin);
+            add(plugin);
         }
         return type.cast(findPlugin(type));
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java
new file mode 100644
index 0000000..f1f58ec
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DslObject.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 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.gradle.api.internal.*;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.api.plugins.ExtensionContainer;
+
+/**
+ * Provides a unified, typed, interface to an enhanced DSL object.
+ * 
+ * This is intended to be used with objects that have been decorated by the class generator.
+ * <p>
+ * Accessing each “aspect” of a DSL object may fail (with an {@link IllegalStateException}) if the DSL
+ * object does not have that functionality. For example, calling {@link #getConventionMapping()} will fail
+ * if the backing object does not implement {@link IConventionAware}.
+ */
+public class DslObject implements DynamicObjectAware, ExtensionAware, IConventionAware, HasConvention {
+
+    private DynamicObject dynamicObject;
+    private ExtensionContainer extensionContainer;
+    private ConventionMapping conventionMapping;
+    private Convention convention;
+    
+    private final Object object;
+
+    public DslObject(Object object) {
+        this.object = object;
+    }
+
+    public DynamicObject getAsDynamicObject() {
+        if (dynamicObject == null) {
+            this.dynamicObject = toType(object, DynamicObjectAware.class).getAsDynamicObject();
+        }
+        return dynamicObject;
+    }
+    
+    public Convention getConvention() {
+        if (convention == null) {
+            this.convention = toType(object, HasConvention.class).getConvention();
+        }
+        return convention;
+    }
+
+    public ExtensionContainer getExtensions() {
+        if (extensionContainer == null) {
+            this.extensionContainer = toType(object, ExtensionAware.class).getExtensions();
+        }
+        return extensionContainer;
+    }
+
+    public ConventionMapping getConventionMapping() {
+        if (conventionMapping == null) {
+            this.conventionMapping = toType(object, IConventionAware.class).getConventionMapping();
+        }
+        return conventionMapping;
+    }
+
+    private static <T> T toType(Object delegate, Class<T> type) {
+        if (type.isInstance(delegate)) {
+            return type.cast(delegate);
+        } else {
+            throw new IllegalStateException(
+                    String.format("Cannot create DslObject for '%s' (class: %s) as it does not implement '%s' (it is not a DSL object)",
+                            delegate, delegate.getClass().getSimpleName(), type.getName())
+            );
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
new file mode 100644
index 0000000..acb8e91
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 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 groovy.lang.Closure;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.*;
+
+/**
+ * @author: Szczepan Faber, created at: 6/24/11
+ */
+public class ExtensionsStorage {
+
+    private final Map<String, Object> extensions = new LinkedHashMap<String, Object>();
+
+    public void add(String name, Object extension) {
+        if (extensions.containsKey(name)) {
+            throw new IllegalArgumentException(String.format("Cannot add extension with name '%s', as there is an extension already registered with that name.", name));
+        }
+        extensions.put(name, extension);
+    }
+
+    public boolean hasExtension(String name) {
+        return extensions.containsKey(name);
+    }
+
+    public Map<String, Object> getAsMap() {
+        return extensions;
+    }
+
+    public void checkExtensionIsNotReassigned(String name) {
+        if (hasExtension(name)) {
+            throw new IllegalArgumentException(String.format("There's an extension registered with name '%s'. You should not reassign it via a property setter.", name));
+        }
+    }
+
+    public boolean isConfigureExtensionMethod(String methodName, Object ... arguments) {
+        return extensions.containsKey(methodName) && arguments.length == 1 && arguments[0] instanceof Closure;
+    }
+
+    public Object configureExtension(String methodName, Object ... arguments) {
+        return ConfigureUtil.configure((Closure) arguments[0], extensions.get(methodName));
+    }
+
+    public <T> T getByType(Class<T> type) {
+        Collection<Object> values = extensions.values();
+        List types = new LinkedList();
+        for (Object e : values) {
+            Class clazz = e.getClass();
+            types.add(clazz.getSimpleName());
+            if (type.isAssignableFrom(clazz)) {
+                return (T) e;
+            }
+        }
+        throw new UnknownDomainObjectException("Extension of type '" + type.getSimpleName() + "' does not exist. Currently registered extension types: " + types);
+    }
+
+    public <T> T findByType(Class<T> type) {
+        Collection<Object> values = extensions.values();
+        for (Object e : values) {
+            if (type.isAssignableFrom(e.getClass())) {
+                return (T) e;
+            }
+        }
+        return null;
+    }
+
+    public Object getByName(String name) {
+        if (!hasExtension(name)) {
+            throw new UnknownDomainObjectException("Extension with name '" + name + "' does not exist. Currently registered extension names: " + extensions.keySet());
+        }
+        return extensions.get(name);
+    }
+
+    public Object findByName(String name) {
+        return extensions.get(name);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtraPropertiesDynamicObjectAdapter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtraPropertiesDynamicObjectAdapter.java
new file mode 100644
index 0000000..a67a9ab
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtraPropertiesDynamicObjectAdapter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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 groovy.lang.MissingPropertyException;
+import org.gradle.api.internal.BeanDynamicObject;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
+import org.gradle.util.DeprecationLogger;
+
+import java.util.Map;
+
+public class ExtraPropertiesDynamicObjectAdapter extends BeanDynamicObject {
+
+    private final ExtraPropertiesExtension extension;
+    private final Object delegate;
+    private final DynamicObject dynamicOwner;
+
+    public ExtraPropertiesDynamicObjectAdapter(Object delegate, DynamicObject dynamicOwner, ExtraPropertiesExtension extension) {
+        super(extension);
+        this.delegate = delegate;
+        this.dynamicOwner = dynamicOwner;
+        this.extension = extension;
+    }
+
+    public boolean hasProperty(String name) {
+        return super.hasProperty(name) || extension.has(name);
+    }
+
+    public Map<String, ?> getProperties() {
+        return extension.getProperties();
+    }
+
+    @Override
+    public void setProperty(String name, Object value) throws MissingPropertyException {
+        if (!dynamicOwner.hasProperty(name)) {
+            DeprecationLogger.nagUserAboutDynamicProperty(name, delegate, value);
+        }
+
+        super.setProperty(name, value);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java
index 8205d35..407601d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.plugins;
 
 import org.gradle.api.Plugin;
 import org.gradle.api.plugins.PluginInstantiationException;
+import org.gradle.api.plugins.UnknownPluginException;
 
 /**
  * @author Hans Dockter
@@ -25,7 +26,7 @@ import org.gradle.api.plugins.PluginInstantiationException;
 public interface PluginRegistry {
     <T extends Plugin> T loadPlugin(Class<T> pluginClass) throws PluginInstantiationException;
 
-    Class<? extends Plugin> getTypeForId(String pluginId);
+    Class<? extends Plugin> getTypeForId(String pluginId) throws UnknownPluginException, PluginInstantiationException;
 
     PluginRegistry createChild(ClassLoader childClassPath);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
index 6d953e9..21239d0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
@@ -20,7 +20,6 @@ import groovy.lang.Closure;
 import groovy.lang.MissingPropertyException;
 import groovy.lang.Script;
 import org.gradle.api.*;
-import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
@@ -31,24 +30,27 @@ import org.gradle.api.file.CopySpec;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.*;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileOperations;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
 import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
-import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.resources.ResourceHandler;
 import org.gradle.api.tasks.Directory;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.configuration.ProjectEvaluator;
 import org.gradle.configuration.ScriptPlugin;
 import org.gradle.configuration.ScriptPluginFactory;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.Factory;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
@@ -62,8 +64,9 @@ import java.io.File;
 import java.net.URI;
 import java.util.*;
 
-import static java.util.Collections.*;
-import static org.gradle.util.GUtil.*;
+import static java.util.Collections.singletonMap;
+import static org.gradle.util.GUtil.addMaps;
+import static org.gradle.util.GUtil.isTrue;
 
 /**
  * @author Hans Dockter
@@ -102,6 +105,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private FileResolver fileResolver;
     private FileOperations fileOperations;
+    private ProcessOperations processOperations;
 
     private Factory<AntBuilder> antBuilderFactory;
 
@@ -121,12 +125,10 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private DependencyHandler dependencyHandler;
 
-    private ConfigurationContainer configurationContainer;
+    private ConfigurationContainerInternal configurationContainer;
 
     private ArtifactHandler artifactHandler;
 
-    private Factory<RepositoryHandler> repositoryHandlerFactory;
-
     private RepositoryHandler repositoryHandler;
 
     private ScriptHandler scriptHandler;
@@ -137,7 +139,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
 
     private LoggingManagerInternal loggingManager;
 
-    private DynamicObjectHelper dynamicObjectHelper;
+    private ExtensibleDynamicObject extensibleDynamicObject;
 
     private String description;
 
@@ -173,10 +175,10 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         taskContainer = services.newInstance(TaskContainerInternal.class);
         implicitTasksContainer = services.newInstance(TaskContainerInternal.class);
         fileOperations = services.get(FileOperations.class);
-        repositoryHandlerFactory = services.getFactory(RepositoryHandler.class);
+        processOperations = services.get(ProcessOperations.class);
         projectEvaluator = services.get(ProjectEvaluator.class);
-        repositoryHandler = repositoryHandlerFactory.create();
-        configurationContainer = services.get(ConfigurationContainer.class);
+        repositoryHandler = services.get(RepositoryHandler.class);
+        configurationContainer = services.get(ConfigurationContainerInternal.class);
         pluginContainer = services.get(PluginContainer.class);
         artifactHandler = services.get(ArtifactHandler.class);
         dependencyHandler = services.get(DependencyHandler.class);
@@ -185,20 +187,15 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         projectRegistry = services.get(IProjectRegistry.class);
         loggingManager = services.get(LoggingManagerInternal.class);
 
-        dynamicObjectHelper = new DynamicObjectHelper(this);
-        dynamicObjectHelper.setConvention(services.get(Convention.class));
+        extensibleDynamicObject = new ExtensibleDynamicObject(this, services.get(Instantiator.class));
         if (parent != null) {
-            dynamicObjectHelper.setParent(parent.getInheritedScope());
+            extensibleDynamicObject.setParent(parent.getInheritedScope());
         }
-        dynamicObjectHelper.addObject(taskContainer.getAsDynamicObject(), DynamicObjectHelper.Location.AfterConvention);
+        extensibleDynamicObject.addObject(taskContainer.getTasksAsDynamicObject(), ExtensibleDynamicObject.Location.AfterConvention);
 
         evaluationListener.add(gradle.getProjectEvaluationBroadcaster());
     }
 
-    public RepositoryHandler createRepositoryHandler() {
-        return repositoryHandlerFactory.create();
-    }
-
     public ProjectInternal getRootProject() {
         return rootProject;
     }
@@ -243,8 +240,8 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public void setScript(Script buildScript) {
-        dynamicObjectHelper.addObject(new BeanDynamicObject(buildScript).withNoProperties(),
-                DynamicObjectHelper.Location.BeforeConvention);
+        extensibleDynamicObject.addObject(new BeanDynamicObject(buildScript).withNoProperties(),
+                ExtensibleDynamicObject.Location.BeforeConvention);
     }
 
     public ScriptSource getBuildScriptSource() {
@@ -264,11 +261,11 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public DynamicObject getAsDynamicObject() {
-        return dynamicObjectHelper;
+        return extensibleDynamicObject;
     }
 
     public DynamicObject getInheritedScope() {
-        return dynamicObjectHelper.getInheritable();
+        return extensibleDynamicObject.getInheritable();
     }
 
     public String getName() {
@@ -328,9 +325,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return dependsOnProjects;
     }
 
-    public Map<String, Object> getAdditionalProperties() {
-        return dynamicObjectHelper.getAdditionalProperties();
-    }
+
 
     public ProjectStateInternal getState() {
         return state;
@@ -360,33 +355,18 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return repositoryHandler;
     }
 
-    public Factory<RepositoryHandler> getRepositoryHandlerFactory() {
-        return repositoryHandlerFactory;
-    }
-
-    public ConfigurationContainer getConfigurations() {
+    public ConfigurationContainerInternal getConfigurations() {
         return configurationContainer;
     }
 
-    public void setConfigurationContainer(ConfigurationContainer configurationContainer) {
+    public void setConfigurationContainer(ConfigurationContainerInternal configurationContainer) {
         this.configurationContainer = configurationContainer;
     }
 
-    public String getBuildDirName() {
-        return buildDir.toString();
-    }
 
-    public void setBuildDirName(String buildDirName) {
-        DeprecationLogger.nagUser("Project.setBuildDirName()", "setBuildDir()");
-        this.buildDir = buildDirName;
-    }
 
     public Convention getConvention() {
-        return dynamicObjectHelper.getConvention();
-    }
-
-    public void setConvention(Convention convention) {
-        dynamicObjectHelper.setConvention(convention);
+        return extensibleDynamicObject.getConvention();
     }
 
     public String getPath() {
@@ -414,11 +394,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         }
     }
 
-    public String absolutePath(String path) {
-        DeprecationLogger.nagUser("Project.absolutePath()", "Project.absoluteProjectPath()");
-        return absoluteProjectPath(path);
-    }
-
     public String absoluteProjectPath(String path) {
         return this.path.absolutePath(path);
     }
@@ -427,15 +402,15 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return this.path.relativePath(path);
     }
 
-    public Project project(String path) {
-        Project project = findProject(path);
+    public ProjectInternal project(String path) {
+        ProjectInternal project = findProject(path);
         if (project == null) {
             throw new UnknownProjectException(String.format("Project with path '%s' could not be found in %s.", path, this));
         }
         return project;
     }
 
-    public Project findProject(String path) {
+    public ProjectInternal findProject(String path) {
         if (!isTrue(path)) {
             throw new InvalidUserDataException("A path must be specified!");
         }
@@ -489,18 +464,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return this;
     }
 
-    public Project usePlugin(String pluginId) {
-        warnUsePluginDeprecated();
-        pluginContainer.apply(pluginId);
-        return this;
-    }
-
-    public Project usePlugin(Class<? extends Plugin> pluginClass) {
-        warnUsePluginDeprecated();
-        pluginContainer.apply(pluginClass);
-        return this;
-    }
-
     public TaskContainerInternal getTasks() {
         return taskContainer;
     }
@@ -522,22 +485,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         }
     }
 
-    public Task createTask(String name) {
-        return createTask(new HashMap<String, Object>(), name, (Action) null);
-    }
-
-    public Task createTask(Map<String, ?> args, String name) {
-        return createTask(args, name, (Action) null);
-    }
-
-    public Task createTask(String name, Action<? super Task> action) {
-        return createTask(new HashMap<String, Object>(), name, action);
-    }
-
-    public Task createTask(String name, Closure action) {
-        return createTask(new HashMap<String, Object>(), name, action);
-    }
-
     public Task createTask(Map args, String name, Closure action) {
         warnCreateTaskDeprecated();
         Map<String, Object> allArgs = new HashMap<String, Object>(args);
@@ -557,11 +504,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     private void warnCreateTaskDeprecated() {
-        DeprecationLogger.nagUser("Project.createTask()", "task()");
-    }
-
-    private void warnUsePluginDeprecated() {
-        DeprecationLogger.nagUser("Project.usePlugin()", "apply()");
+        DeprecationLogger.nagUserOfReplacedMethod("Project.createTask()", "task()");
     }
 
     public void addChildProject(ProjectInternal childProject) {
@@ -580,8 +523,14 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         buildDir = path;
     }
 
-    public void dependsOn(String path) {
-        dependsOn(path, true);
+    public void dependsOn(final String path) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Project.dependsOn(String path)");
+        DeprecationLogger.whileDisabled(new Factory<Void>() {
+            public Void create() {
+                dependsOn(path, true);
+                return null;
+            }
+        });
     }
 
     public void dependsOn(String path, boolean evaluateDependsOnProject) {
@@ -594,11 +543,22 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         }
     }
 
+    public void evaluationDependsOnChildren() {
+        for (Project project : childProjects.values()) {
+            DefaultProject defaultProjectToEvaluate = (DefaultProject) project;
+            evaluationDependsOn(defaultProjectToEvaluate);
+        }
+    }
+
     public Project evaluationDependsOn(String path) {
         if (!isTrue(path)) {
             throw new InvalidUserDataException("You must specify a project!");
         }
         DefaultProject projectToEvaluate = (DefaultProject) project(path);
+        return evaluationDependsOn(projectToEvaluate);
+    }
+
+    private Project evaluationDependsOn(DefaultProject projectToEvaluate) {
         if (projectToEvaluate.getState().getExecuting()) {
             throw new CircularReferenceException(String.format("Circular referencing during evaluation for %s.",
                     projectToEvaluate));
@@ -607,20 +567,38 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public Project childrenDependOnMe() {
-        for (Project project : childProjects.values()) {
-            project.dependsOn(getPath(), false);
-        }
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Project.childrenDependOnMe()");
+        DeprecationLogger.whileDisabled(new Factory<Void>() {
+            public Void create() {
+                for (Project project : childProjects.values()) {
+                    project.dependsOn(getPath(), false);
+                }
+                return null;
+            }
+        });
+
         return this;
     }
 
     public Project dependsOnChildren() {
-        return dependsOnChildren(false);
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Project.dependsOnChildren()");
+        return DeprecationLogger.whileDisabled(new Factory<Project>() {
+            public Project create() {
+               return dependsOnChildren(false);
+            }
+        });
     }
 
-    public Project dependsOnChildren(boolean evaluateDependsOnProject) {
-        for (Project project : childProjects.values()) {
-            dependsOn(project.getPath(), evaluateDependsOnProject);
-        }
+    public Project dependsOnChildren(final boolean evaluateDependsOnProject) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Project.dependsOnChildren(boolean)");
+        DeprecationLogger.whileDisabled(new Factory<Void>() {
+            public Void create() {
+                for (Project project : childProjects.values()) {
+                    dependsOn(project.getPath(), evaluateDependsOnProject);
+                }
+                return null;
+            }
+        });
         return this;
     }
 
@@ -636,7 +614,7 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         final Map<Project, Set<Task>> foundTargets = new TreeMap<Project, Set<Task>>();
         Action<Project> action = new Action<Project>() {
             public void execute(Project project) {
-                foundTargets.put(project, new TreeSet<Task>(project.getTasks().getAll()));
+                foundTargets.put(project, new TreeSet<Task>(project.getTasks()));
             }
         };
         if (recursive) {
@@ -692,11 +670,16 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return fileOperations.fileTree(baseDir);
     }
 
+    public ConfigurableFileTree fileTree(Object baseDir, Closure closure) {
+        return fileOperations.fileTree(baseDir, closure);
+    }
+
     public ConfigurableFileTree fileTree(Map<String, ?> args) {
         return fileOperations.fileTree(args);
     }
 
     public ConfigurableFileTree fileTree(Closure closure) {
+        DeprecationLogger.nagUserWith("fileTree(Closure) is a deprecated method. Use fileTree((Object){ baseDir }) to have the closure used as the file tree base directory");
         return fileOperations.fileTree(closure);
     }
 
@@ -708,6 +691,10 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return fileOperations.tarTree(tarPath);
     }
 
+    public ResourceHandler getResources() {
+        return fileOperations.getResources();
+    }
+
     public String relativePath(Object path) {
         return fileOperations.relativePath(path);
     }
@@ -720,7 +707,12 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return fileOperations.delete(paths);
     }
 
+    /**
+     * @deprecated Use the {@link #mkdir(Object)} instead.
+     */
+    @Deprecated
     public Directory dir(String path) {
+        DeprecationLogger.nagUserOfReplacedMethod("AbstractProject.dir()", "mkdir()");
         String[] pathElements = path.split("/");
         String name = "";
         Directory dirTask = null;
@@ -790,30 +782,24 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return loggingManager;
     }
 
-    public void disableStandardOutputCapture() {
-        DeprecationLogger.nagUser("Project.disableStandardOutputCapture()");
-        loggingManager.disableStandardOutputCapture();
-    }
-
-    public void captureStandardOutput(LogLevel level) {
-        DeprecationLogger.nagUser("Project.captureStandardOutput()", "getLogging().captureStandardOutput()");
-        loggingManager.captureStandardOutput(level);
-    }
-
     public Object property(String propertyName) throws MissingPropertyException {
-        return dynamicObjectHelper.getProperty(propertyName);
+        return extensibleDynamicObject.getProperty(propertyName);
     }
 
     public void setProperty(String name, Object value) {
-        dynamicObjectHelper.setProperty(name, value);
+        extensibleDynamicObject.setProperty(name, value);
     }
 
     public boolean hasProperty(String propertyName) {
-        return dynamicObjectHelper.hasProperty(propertyName);
+        return extensibleDynamicObject.hasProperty(propertyName);
     }
 
     public Map<String, ?> getProperties() {
-        return dynamicObjectHelper.getProperties();
+        return DeprecationLogger.whileDisabled(new Factory<Map<String, ?>>() {
+            public Map<String, ?> create() {
+                return extensibleDynamicObject.getProperties();
+            }
+        });
     }
 
     public WorkResult copy(Closure closure) {
@@ -825,11 +811,11 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public ExecResult javaexec(Closure closure) {
-        return fileOperations.javaexec(closure);
+        return processOperations.javaexec(closure);
     }
 
     public ExecResult exec(Closure closure) {
-        return fileOperations.exec(closure);
+        return processOperations.exec(closure);
     }
 
     public ServiceRegistryFactory getServices() {
@@ -941,17 +927,21 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
     }
 
     public <T> NamedDomainObjectContainer<T> container(Class<T> type) {
-        ClassGenerator classGenerator = getServices().get(ClassGenerator.class);
-        return classGenerator.newInstance(DefaultAutoCreateDomainObjectContainer.class, type, classGenerator);
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        return instantiator.newInstance(FactoryNamedDomainObjectContainer.class, type, instantiator, new DynamicPropertyNamer());
     }
 
     public <T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory) {
-        ClassGenerator classGenerator = getServices().get(ClassGenerator.class);
-        return classGenerator.newInstance(DefaultAutoCreateDomainObjectContainer.class, type, classGenerator, factory);
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        return instantiator.newInstance(FactoryNamedDomainObjectContainer.class, type, instantiator, new DynamicPropertyNamer(), factory);
     }
 
     public <T> NamedDomainObjectContainer<T> container(Class<T> type, Closure factoryClosure) {
-        ClassGenerator classGenerator = getServices().get(ClassGenerator.class);
-        return classGenerator.newInstance(DefaultAutoCreateDomainObjectContainer.class, type, classGenerator, factoryClosure);
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        return instantiator.newInstance(FactoryNamedDomainObjectContainer.class, type, instantiator, new DynamicPropertyNamer(), factoryClosure);
+    }
+
+    public ExtensionContainer getExtensions() {
+        return getConvention();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
index 44d9c35..6b39102 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.project;
 import org.apache.tools.ant.BuildListener;
 import org.gradle.api.AntBuilder;
 import org.gradle.api.Project;
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 
 /**
  * @author Hans Dockter
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
index dd37262..4ce083e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
@@ -15,27 +15,31 @@
  */
 package org.gradle.api.internal.project
 
-import org.gradle.api.internal.project.ant.BasicAntBuilder
+import org.gradle.api.internal.ClassPathRegistry
 import org.gradle.api.internal.project.ant.AntLoggingAdapter
+import org.gradle.api.internal.project.ant.BasicAntBuilder
 import org.gradle.util.*
-import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.internal.jvm.Jvm
 
 class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
-    private final Map<List<File>, Map<String, Object>> classloaders
+    private final Map<List<File>, ClassLoader> baseClassloaders = [:]
+    private final Map<List<File>, Map<String, Object>> classloaders = [:]
     private final ClassPathRegistry classPathRegistry
+    private final ClassLoaderFactory classLoaderFactory
     private final Iterable<File> groovyClasspath
-    private final Iterable<File> libClasspath
+    private final Iterable<File> libClasspath = []
 
-    def DefaultIsolatedAntBuilder(ClassPathRegistry classPathRegistry) {
+    def DefaultIsolatedAntBuilder(ClassPathRegistry classPathRegistry, ClassLoaderFactory classLoaderFactory) {
         this.classPathRegistry = classPathRegistry
-        classloaders = [:]
-        groovyClasspath = classPathRegistry.getClassPathFiles("LOCAL_GROOVY")
-        libClasspath = []
+        this.classLoaderFactory = classLoaderFactory
+        groovyClasspath = classPathRegistry.getClassPath("GROOVY").asFiles
     }
 
     private DefaultIsolatedAntBuilder(DefaultIsolatedAntBuilder copy, Iterable<File> groovyClasspath, Iterable<File> libClasspath) {
         this.classPathRegistry = copy.classPathRegistry
+        this.classLoaderFactory = copy.classLoaderFactory
         this.classloaders = copy.classloaders
+        this.baseClassloaders = copy.baseClassloaders
         this.groovyClasspath = groovyClasspath
         this.libClasspath = libClasspath
     }
@@ -49,9 +53,24 @@ class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
     }
 
     void execute(Closure antClosure) {
+        List<File> baseClasspath = []
+        baseClasspath.addAll(classPathRegistry.getClassPath("ANT").asFiles)
+        baseClasspath.addAll(groovyClasspath as List)
+
+        ClassLoader baseLoader = baseClassloaders[baseClasspath]
+        if (baseLoader == null) {
+            // Need tools.jar for compile tasks
+            List<File> fullClasspath = baseClasspath
+            File toolsJar = Jvm.current().toolsJar
+            if (toolsJar) {
+                fullClasspath += toolsJar
+            }
+            List<URI> classpathUrls = fullClasspath.collect { it.toURI() }
+            baseLoader = classLoaderFactory.createIsolatedClassLoader(classpathUrls)
+            baseClassloaders[baseClasspath] = baseLoader
+        }
+
         List<File> normalisedClasspath = []
-        normalisedClasspath.addAll(classPathRegistry.getClassPathFiles("ANT"))
-        normalisedClasspath.addAll(groovyClasspath as List)
         normalisedClasspath.addAll(libClasspath as List)
 
         Map<String, Object> classloadersForPath = classloaders[normalisedClasspath]
@@ -61,23 +80,18 @@ class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
             antLoader = classloadersForPath.antLoader
             gradleLoader = classloadersForPath.gradleLoader
         } else {
-            // Need tools.jar for compile tasks
-            List<File> fullClasspath = normalisedClasspath
-            File toolsJar = Jvm.current().toolsJar
-            if (toolsJar) {
-                fullClasspath += toolsJar
-            }
-
-            Closure converter = {File file -> file.toURI().toURL() }
-            URL[] classpathUrls = fullClasspath.collect(converter)
-            // Need gradle core to pick up ant logging adapter
-            URL[] gradleCoreUrls = classPathRegistry.getClassPathUrls("GRADLE_CORE")
+            // Need gradle core to pick up ant logging adapter, AntBuilder and such
+            def gradleCoreUrls = classPathRegistry.getClassPath("GRADLE_CORE")
 
             FilteringClassLoader loggingLoader = new FilteringClassLoader(getClass().classLoader)
             loggingLoader.allowPackage('org.slf4j')
+            loggingLoader.allowPackage('org.apache.commons.logging')
+            loggingLoader.allowPackage('org.apache.log4j')
+            ClassLoader parent = new MultiParentClassLoader(baseLoader, loggingLoader)
 
-            antLoader = new URLClassLoader(classpathUrls, ClassLoader.systemClassLoader.parent)
-            gradleLoader = new URLClassLoader(gradleCoreUrls, new MultiParentClassLoader(antLoader, loggingLoader))
+            List<URL> classpathUrls = normalisedClasspath.collect { it.toURI().toURL() }
+            antLoader = new URLClassLoader(classpathUrls as URL[], parent)
+            gradleLoader = new MutableURLClassLoader(parent, gradleCoreUrls)
 
             classloaders[normalisedClasspath] = [antLoader: antLoader, gradleLoader: gradleLoader]
         }
@@ -94,7 +108,7 @@ class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
             // Ideally, we'd delegate directly to the AntBuilder, but it's Closure class is different to our caller's
             // Closure class, so the AntBuilder's methodMissing() doesn't work. It just converts our Closures to String
             // because they are not an instanceof it's Closure class
-            Object delegate = new AntBuilderDelegate(antBuilder)
+            Object delegate = new AntBuilderDelegate(antBuilder, antLoader)
             ConfigureUtil.configure(antClosure, delegate)
         } finally {
             Thread.currentThread().contextClassLoader = originalLoader
@@ -103,10 +117,31 @@ class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
 }
 
 class AntBuilderDelegate extends BuilderSupport {
-    def Object builder
+    final Object builder
+    final ClassLoader antlibClassLoader
 
-    def AntBuilderDelegate(builder) {
+    def AntBuilderDelegate(builder, antlibClassLoader) {
         this.builder = builder;
+        this.antlibClassLoader = antlibClassLoader
+    }
+
+    def getAnt() {
+        return this
+    }
+    
+    def taskdef(Map<String, ?> args) {
+        if (args.keySet() == ['name', 'classname'] as Set) {
+            builder.project.addTaskDefinition(args.name, antlibClassLoader.loadClass(args.classname))            
+        } else if (args.keySet() == ['resource'] as Set) {
+            antlibClassLoader.getResource(args.resource).withInputStream { instr ->
+                def xml = new XmlParser().parse(instr)
+                xml.taskdef.each {
+                    builder.project.addTaskDefinition(it. at name, antlibClassLoader.loadClass(it. at classname))
+                }
+            }            
+        } else {
+            throw new RuntimeException("Unsupported parameters for taskdef().")
+        }
     }
 
     def propertyMissing(String name) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultServiceRegistry.java
deleted file mode 100644
index 81e1d9d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultServiceRegistry.java
+++ /dev/null
@@ -1,340 +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.api.internal.project;
-
-import org.gradle.api.internal.Factory;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-import org.gradle.messaging.concurrent.Stoppable;
-import org.gradle.util.UncheckedException;
-
-import java.lang.reflect.*;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A hierarchical {@link ServiceRegistry} implementation.
- *
- * <p>Subclasses can register services by:</p>
- *
- * <ul> <li>Calling {@link #add(org.gradle.api.internal.project.DefaultServiceRegistry.Service)} to register a factory
- * for the service.</li>
- *
- * <li>Calling {@link #add(Class, Object)} to register a service instance.</li>
- *
- * <li>Adding a factory method. A factory method should have a name that starts with 'create', take no parameters, and
- * have a non-void return type. For example, <code>protected SomeService createSomeService() { .... }</code>.</li>
- *
- * <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single
- * parameter, and a have a non-void return type. The before invoking the method, the parameter is located in the parent
- * service registry and then passed to the method.</li>
- *
- * </ul>
- *
- * <p>Service instances are created on demand. {@link #getFactory(Class)} looks for a service instance which implements
- * {@code Factory<T>} where {@code T} is the expected type.</p>.
- *
- * <p>Service registries are arranged in a heirarchy. If a service of a given type cannot be located, the registry uses
- * its parent registry, if any, to locate the service.</p>
- */
-public class DefaultServiceRegistry implements ServiceRegistry {
-    private final List<Service> services = new ArrayList<Service>();
-    private final ServiceRegistry parent;
-    private boolean closed;
-
-    public DefaultServiceRegistry() {
-        this(null);
-    }
-
-    public DefaultServiceRegistry(ServiceRegistry parent) {
-        this.parent = parent;
-        for (Class<?> type = getClass(); type != Object.class; type = type.getSuperclass()) {
-            findFactoryMethods(type);
-            findDecoratorMethods(type);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getSimpleName();
-    }
-
-    private void findFactoryMethods(Class<?> type) {
-        for (Method method : type.getDeclaredMethods()) {
-            if (method.getName().startsWith("create")
-                    && method.getParameterTypes().length == 0
-                    && method.getReturnType() != Void.class) {
-                add(new FactoryMethodService(method));
-            }
-        }
-    }
-
-    private void findDecoratorMethods(Class<?> type) {
-        for (Method method : type.getDeclaredMethods()) {
-            if (method.getName().startsWith("create")
-                    && method.getParameterTypes().length == 1
-                    && method.getReturnType() != Void.class
-                    && method.getParameterTypes()[0].equals(method.getReturnType())) {
-                add(new DecoratorMethodService(method));
-            }
-        }
-    }
-
-    protected void add(Service service) {
-        services.add(0, service);
-    }
-
-    public <T> void add(Class<T> serviceType, final T serviceInstance) {
-        add(new FixedInstanceService<T>(serviceType, serviceInstance));
-    }
-
-    /**
-     * Closes all services for this registry. For each service, if the service has a public void close() method, that
-     * method is called to close the service.
-     */
-    public void close() {
-        try {
-            new CompositeStoppable(services).stop();
-        } finally {
-            closed = true;
-            services.clear();
-        }
-    }
-
-    public <T> T get(Class<T> serviceType) throws IllegalArgumentException {
-        if (closed) {
-            throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.",
-                    serviceType.getSimpleName(), this));
-        }
-
-        for (Service service : services) {
-            T t = service.getService(serviceType);
-            if (t != null) {
-                return t;
-            }
-        }
-
-        if (parent != null) {
-            try {
-                return parent.get(serviceType);
-            } catch (UnknownServiceException e) {
-                if (!e.type.equals(serviceType)) {
-                    throw e;
-                }
-                // Ignore
-            }
-        }
-
-        throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.",
-                serviceType.getSimpleName(), this));
-    }
-
-    public <T> Factory<T> getFactory(Class<T> type) {
-        if (closed) {
-            throw new IllegalStateException(String.format("Cannot locate factory for objects of type %s, as %s has been closed.",
-                    type.getSimpleName(), this));
-        }
-
-        for (Service service : services) {
-            Factory<T> factory = service.getFactory(type);
-            if (factory != null) {
-                return factory;
-            }
-        }
-
-        if (parent != null) {
-            try {
-                return parent.getFactory(type);
-            } catch (UnknownServiceException e) {
-                if (!e.type.equals(type)) {
-                    throw e;
-                }
-                // Ignore
-            }
-        }
-
-        throw new UnknownServiceException(type, String.format("No factory for objects of type %s available in %s.",
-                type.getSimpleName(), this));
-    }
-
-    public <T> T newInstance(Class<T> type) {
-        return getFactory(type).create();
-    }
-
-    private static Object invoke(Method method, Object target, Object... args) {
-        try {
-            method.setAccessible(true);
-            return method.invoke(target, args);
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.asUncheckedException(e.getCause());
-        } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-    }
-
-    protected static abstract class Service implements Stoppable {
-        final Type serviceType;
-        final Class serviceClass;
-        Object service;
-
-        Service(Type serviceType) {
-            this.serviceType = serviceType;
-            serviceClass = toClass(serviceType);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("Service %s", serviceType);
-        }
-
-        <T> T getService(Class<T> serviceType) {
-            if (!serviceType.isAssignableFrom(this.serviceClass)) {
-                return null;
-            }
-            if (service == null) {
-                service = create();
-                assert service != null;
-            }
-            return serviceType.cast(service);
-        }
-
-        protected abstract Object create();
-
-        public void stop() {
-            try {
-                if (service != null) {
-                    try {
-                        invoke(service.getClass().getMethod("stop"), service);
-                    } catch (NoSuchMethodException e) {
-                        // ignore
-                    }
-                    try {
-                        invoke(service.getClass().getMethod("close"), service);
-                    } catch (NoSuchMethodException e) {
-                        // ignore
-                    }
-                }
-            } finally {
-                service = null;
-            }
-        }
-
-        public <T> Factory<T> getFactory(Class<T> elementType) {
-            if (!Factory.class.isAssignableFrom(serviceClass)) {
-                return null;
-            }
-            return getFactory(serviceType, elementType);
-        }
-
-        private Factory getFactory(Type type, Class elementType) {
-            Class c = toClass(type);
-            if (!Factory.class.isAssignableFrom(c)) {
-                return null;
-            }
-
-            if (type instanceof ParameterizedType) {
-                ParameterizedType parameterizedType = (ParameterizedType) type;
-                if (parameterizedType.getRawType().equals(Factory.class) && parameterizedType.getActualTypeArguments()[0].equals(elementType)) {
-                    return getService(Factory.class);
-                }
-            }
-
-            for (Type interfaceType : c.getGenericInterfaces()) {
-                Factory f = getFactory(interfaceType, elementType);
-                if (f != null) {
-                    return f;
-                }
-            }
-
-            return null;
-        }
-
-        private Class toClass(Type type) {
-            if (type instanceof Class) {
-                return (Class) type;
-            } else {
-                ParameterizedType parameterizedType = (ParameterizedType) type;
-                return (Class) parameterizedType.getRawType();
-            }
-        }
-    }
-
-    private class FactoryMethodService extends Service {
-        private final Method method;
-
-        public FactoryMethodService(Method method) {
-            super(method.getGenericReturnType());
-            this.method = method;
-        }
-
-        @Override
-        protected Object create() {
-            return invoke(method, DefaultServiceRegistry.this);
-        }
-    }
-
-    private static class FixedInstanceService<T> extends Service {
-        private final T serviceInstance;
-
-        public FixedInstanceService(Class<T> serviceType, T serviceInstance) {
-            super(serviceType);
-            this.serviceInstance = serviceInstance;
-            getService(serviceType);
-        }
-
-        @Override
-        protected Object create() {
-            return serviceInstance;
-        }
-    }
-
-    private class DecoratorMethodService extends Service {
-        private final Method method;
-
-        public DecoratorMethodService(Method method) {
-            super(method.getGenericReturnType());
-            this.method = method;
-        }
-
-        @Override
-        protected Object create() {
-            Object value;
-            if (Factory.class.isAssignableFrom(method.getParameterTypes()[0])) {
-                ParameterizedType fatoryType = (ParameterizedType) method.getGenericParameterTypes()[0];
-                Type typeArg = fatoryType.getActualTypeArguments()[0];
-                Class<?> type;
-                if (typeArg instanceof WildcardType) {
-                    WildcardType wildcardType = (WildcardType) typeArg;
-                    type = (Class) wildcardType.getUpperBounds()[0];
-                } else {
-                    type = (Class) typeArg;
-                }
-                value = parent.getFactory(type);
-            } else {
-                value = parent.get(method.getParameterTypes()[0]);
-            }
-            return invoke(method, DefaultServiceRegistry.this, value);
-        }
-    }
-
-    static class UnknownServiceException extends IllegalArgumentException {
-        private final Class<?> type;
-
-        UnknownServiceException(Class<?> type, String message) {
-            super(message);
-            this.type = type;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
old mode 100644
new mode 100755
index 9731d2f..6a8055c
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
@@ -1,75 +1,107 @@
-/*
- * 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.project;
-
-import org.gradle.StartParameter;
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.DefaultClassPathProvider;
-import org.gradle.api.internal.DefaultClassPathRegistry;
-import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.cache.AutoCloseCacheFactory;
-import org.gradle.cache.CacheFactory;
-import org.gradle.cache.DefaultCacheFactory;
-import org.gradle.initialization.ClassLoaderFactory;
-import org.gradle.initialization.CommandLineConverter;
-import org.gradle.initialization.DefaultClassLoaderFactory;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.LoggingServiceRegistry;
-
-/**
- * Contains the services shared by all builds in a given process.
- */
-public class GlobalServicesRegistry extends DefaultServiceRegistry {
-    public GlobalServicesRegistry() {
-        this(LoggingServiceRegistry.newCommandLineProcessLogging());
-    }
-
-    public GlobalServicesRegistry(ServiceRegistry loggingServices) {
-        super(loggingServices);
-    }
-
-    protected CommandLineConverter<StartParameter> createCommandLine2StartParameterConverter() {
-        return new DefaultCommandLineConverter();
-    }
-
-    protected ClassPathRegistry createClassPathRegistry() {
-        return new DefaultClassPathRegistry();
-    }
-
-    protected CacheFactory createCacheFactory() {
-        return new AutoCloseCacheFactory(new DefaultCacheFactory());
-    }
-
-    protected ClassLoaderFactory createClassLoaderFactory() {
-        return new DefaultClassLoaderFactory(get(ClassPathRegistry.class));
-    }
-
-    protected ListenerManager createListenerManager() {
-        return new DefaultListenerManager();
-    }
-   
-    protected GradleDistributionLocator createGradleDistributionLocator() {
-        return new DefaultClassPathProvider();
-    }
-    
-    protected IsolatedAntBuilder createIsolatedAntBuilder() {
-        return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class));
-    }
-    
-}
+/*
+ * 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.project;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.DefaultPluginModuleRegistry;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.cache.internal.*;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.initialization.ClassLoaderRegistry;
+import org.gradle.initialization.DefaultClassLoaderRegistry;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.MessagingServices;
+import org.gradle.util.ClassLoaderFactory;
+import org.gradle.util.DefaultClassLoaderFactory;
+
+/**
+ * Contains the services shared by all builds in a given process.
+ */
+public class GlobalServicesRegistry extends DefaultServiceRegistry {
+    public GlobalServicesRegistry() {
+        this(LoggingServiceRegistry.newCommandLineProcessLogging());
+    }
+
+    public GlobalServicesRegistry(ServiceRegistry loggingServices) {
+        super(loggingServices);
+        add(new NativeServices());
+    }
+
+    protected CommandLineConverter<StartParameter> createCommandLine2StartParameterConverter() {
+        return new DefaultCommandLineConverter();
+    }
+
+    protected ClassPathRegistry createClassPathRegistry() {
+        return new DefaultClassPathRegistry(new DefaultClassPathProvider(get(ModuleRegistry.class)), new DynamicModulesClassPathProvider(get(ModuleRegistry.class), get(PluginModuleRegistry.class)));
+    }
+
+    protected DefaultModuleRegistry createModuleRegistry() {
+        return new DefaultModuleRegistry();
+    }
+
+    protected PluginModuleRegistry createPluginModuleRegistry() {
+        return new DefaultPluginModuleRegistry(get(ModuleRegistry.class));
+    }
+
+    protected Factory<CacheFactory> createCacheFactory() {
+        return new DefaultCacheFactory(get(FileLockManager.class));
+    }
+
+    protected ClassLoaderRegistry createClassLoaderRegistry() {
+        return new DefaultClassLoaderRegistry(get(ClassPathRegistry.class), get(ClassLoaderFactory.class));
+    }
+
+    protected ListenerManager createListenerManager() {
+        return new DefaultListenerManager();
+    }
+   
+    protected ClassLoaderFactory createClassLoaderFactory() {
+        return new DefaultClassLoaderFactory();
+    }
+
+    protected MessagingServices createMessagingServices() {
+        return new MessagingServices(get(ClassLoaderRegistry.class).getPluginsClassLoader());
+    }
+
+    protected MessagingServer createMessagingServer() {
+        return get(MessagingServices.class).get(MessagingServer.class);
+    }
+
+    protected ClassGenerator createClassGenerator() {
+        return new AsmBackedClassGenerator();
+    }
+
+    protected Instantiator createInstantiator() {
+        return new ClassGeneratorBackedInstantiator(get(ClassGenerator.class), new DirectInstantiator());
+    }
+
+    protected FileLockManager createFileLockManager() {
+        return new DefaultFileLockManager(new DefaultProcessMetaDataProvider(get(ProcessEnvironment.class)));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
index 30100d5..f1c7589 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
@@ -15,18 +15,19 @@
  */
 package org.gradle.api.internal.project;
 
-import org.gradle.api.Project;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.PublishModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.repositories.DefaultInternalRepository;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
+import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
+import org.gradle.api.internal.changedetection.TaskCacheLockHandlingBuildExecuter;
 import org.gradle.api.internal.plugins.DefaultPluginRegistry;
 import org.gradle.api.internal.plugins.PluginRegistry;
-import org.gradle.execution.DefaultTaskGraphExecuter;
-import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.execution.*;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
 
+import static java.util.Arrays.asList;
+
 /**
  * Contains the services for a given {@link GradleInternal} instance.
  */
@@ -36,11 +37,22 @@ public class GradleInternalServiceRegistry extends DefaultServiceRegistry implem
     public GradleInternalServiceRegistry(ServiceRegistry parent, final GradleInternal gradle) {
         super(parent);
         this.gradle = gradle;
+        add(new TaskExecutionServices(parent, gradle));
+    }
+
+    protected BuildExecuter createBuildExecuter() {
+        return new DefaultBuildExecuter(
+                asList(new DefaultTasksBuildExecutionAction(),
+                        new ExcludedTaskFilteringBuildConfigurationAction(),
+                        new TaskNameResolvingBuildConfigurationAction()),
+                asList(new DryRunBuildExecutionAction(),
+                        new TaskCacheLockHandlingBuildExecuter(get(TaskArtifactStateCacheAccess.class)),
+                        new SelectedTaskExecutionAction()));
     }
 
     protected ProjectFinder createProjectFinder() {
         return new ProjectFinder() {
-            public Project getProject(String path) {
+            public ProjectInternal getProject(String path) {
                 return gradle.getRootProject().project(path);
             }
         };
@@ -58,10 +70,6 @@ public class GradleInternalServiceRegistry extends DefaultServiceRegistry implem
         return new DefaultPluginRegistry(gradle.getScriptClassLoader());
     }
 
-    protected InternalRepository createInternalRepository() {
-        return new DefaultInternalRepository(gradle, get(PublishModuleDescriptorConverter.class));
-    }
-
     public ServiceRegistryFactory createFor(Object domainObject) {
         if (domainObject instanceof ProjectInternal) {
             return new ProjectInternalServiceRegistry(this, (ProjectInternal) domainObject);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
index 5f9bd28..601598f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
@@ -17,8 +17,8 @@
 package org.gradle.api.internal.project;
 
 import org.gradle.api.initialization.ProjectDescriptor;
-import org.gradle.api.internal.ClassGenerator;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
@@ -29,26 +29,22 @@ import java.io.File;
  * @author Hans Dockter
  */
 public class ProjectFactory implements IProjectFactory {
-    private ScriptSource embeddedScript;
-    private final ClassGenerator classGenerator;
+    private final Instantiator instantiator;
 
-    public ProjectFactory(ScriptSource embeddedScript, ClassGenerator classGenerator) {
-        this.embeddedScript = embeddedScript;
-        this.classGenerator = classGenerator;
+    public ProjectFactory(Instantiator instantiator) {
+        this.instantiator = instantiator;
     }
 
     public DefaultProject createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle) {
         File buildFile = projectDescriptor.getBuildFile();
         ScriptSource source;
-        if (embeddedScript != null) {
-            source = embeddedScript;
-        } else if (!buildFile.exists()) {
+        if (!buildFile.exists()) {
             source = new StringScriptSource("empty build file", "");
         } else {
             source = new UriScriptSource("build file", buildFile);
         }
 
-        DefaultProject project = classGenerator.newInstance(DefaultProject.class,
+        DefaultProject project = instantiator.newInstance(DefaultProject.class,
                 projectDescriptor.getName(),
                 parent,
                 projectDescriptor.getProjectDir(),
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
index 0a17a88..94bd8c2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
@@ -18,10 +18,13 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.artifacts.Module;
+import org.gradle.api.UnknownProjectException;
 import org.gradle.api.internal.DomainObjectContext;
 import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.ProcessOperations;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileOperations;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
@@ -29,7 +32,7 @@ import org.gradle.groovy.scripts.ScriptAware;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.logging.StandardOutputCapture;
 
-public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware, FileOperations, DomainObjectContext {
+public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware, FileOperations, ProcessOperations, DomainObjectContext, DependencyMetaDataProvider {
     ProjectInternal getParent();
 
     ProjectInternal getRootProject();
@@ -40,10 +43,16 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
 
     TaskContainerInternal getImplicitTasks();
 
+    ConfigurationContainerInternal getConfigurations();
+
     ScriptSource getBuildScriptSource();
 
     void addChildProject(ProjectInternal childProject);
 
+    ProjectInternal project(String path) throws UnknownProjectException;
+
+    ProjectInternal findProject(String path);
+
     IProjectRegistry<ProjectInternal> getProjectRegistry();
 
     DynamicObject getInheritedScope();
@@ -56,7 +65,5 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
 
     ServiceRegistryFactory getServices();
 
-    Module getModule();
-
     StandardOutputCapture getStandardOutputCapture();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
index a7d5d3c..6db671a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
@@ -17,38 +17,35 @@
 package org.gradle.api.internal.project;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.Factory;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
 import org.gradle.api.internal.artifacts.DefaultModule;
+import org.gradle.api.internal.artifacts.DependencyManagementServices;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
-import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
-import org.gradle.api.internal.artifacts.dsl.SharedConventionRepositoryHandlerFactory;
-import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
-import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
 import org.gradle.api.internal.file.*;
 import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptHandlerInternal;
-import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.internal.plugins.DefaultProjectsPluginContainer;
 import org.gradle.api.internal.plugins.PluginRegistry;
 import org.gradle.api.internal.project.ant.AntLoggingAdapter;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
-import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.PluginContainer;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 
 import java.io.File;
@@ -69,14 +66,14 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
     }
 
     protected FileResolver createFileResolver() {
-        return new BaseDirConverter(project.getProjectDir());
+        return new BaseDirFileResolver(get(FileSystem.class), project.getProjectDir());
     }
 
     protected LoggingManagerInternal createLoggingManager() {
         return getFactory(LoggingManagerInternal.class).create();
     }
 
-    protected FileOperations createFileOperations() {
+    protected DefaultFileOperations createFileOperations() {
         return new DefaultFileOperations(get(FileResolver.class), project.getTasks(), get(TemporaryFileProvider.class));
     }
 
@@ -97,49 +94,51 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
     }
 
     protected Factory<TaskContainerInternal> createTaskContainerInternal() {
-        return new DefaultTaskContainerFactory(get(ClassGenerator.class), get(ITaskFactory.class), project);
+        return new DefaultTaskContainerFactory(get(Instantiator.class), get(ITaskFactory.class), project);
     }
 
-    protected Convention createConvention() {
-        return new DefaultConvention();
+    //TODO SF what's going on here?
+    protected Factory<ArtifactPublicationServices> createRepositoryHandlerFactory() {
+        return get(DependencyResolutionServices.class).getPublishServicesFactory();
     }
 
-    protected Factory<RepositoryHandler> createRepositoryHandlerFactory(Factory<RepositoryHandler> factory) {
-        return new SharedConventionRepositoryHandlerFactory(factory, get(Convention.class));
+    protected RepositoryHandler createRepositoryHandler() {
+        return get(DependencyResolutionServices.class).getResolveRepositoryHandler();
     }
 
-    protected RepositoryHandler createRepositoryHandler() {
-        return getFactory(RepositoryHandler.class).create();
+    protected ConfigurationContainerInternal createConfigurationContainer() {
+        return get(DependencyResolutionServices.class).getConfigurationContainer();
+    }
+
+    protected DependencyResolutionServices createDependencyResolutionServices() {
+        return newDependencyResolutionServices();
     }
-    
-    protected ConfigurationContainer createConfigurationContainer() {
-        return get(ConfigurationContainerFactory.class).createConfigurationContainer(project.getRepositories(),
-                get(DependencyMetaDataProvider.class), project);
+
+    private DependencyResolutionServices newDependencyResolutionServices() {
+        return get(DependencyManagementServices.class).create(get(FileResolver.class), get(DependencyMetaDataProvider.class), get(ProjectFinder.class), project);
     }
 
     protected ArtifactHandler createArtifactHandler() {
-        return new DefaultArtifactHandler(get(ConfigurationContainer.class), get(PublishArtifactFactory.class));
+        return get(DependencyResolutionServices.class).getArtifactHandler();
     }
 
     protected ProjectFinder createProjectFinder() {
         return new ProjectFinder() {
-            public Project getProject(String path) {
+            public ProjectInternal getProject(String path) {
                 return project.project(path);
             }
         };
     }
 
     protected DependencyHandler createDependencyHandler() {
-        return new DefaultDependencyHandler(get(ConfigurationContainer.class), get(DependencyFactory.class),
-                get(ProjectFinder.class));
+        return get(DependencyResolutionServices.class).getDependencyHandler();
     }
 
     protected ScriptHandlerInternal createScriptHandler() {
-        DefaultScriptHandlerFactory factory = new DefaultScriptHandlerFactory(
-                getFactory(RepositoryHandler.class),
-                get(ConfigurationContainerFactory.class),
-                get(DependencyMetaDataProvider.class),
-                get(DependencyFactory.class));
+        ScriptHandlerFactory factory = new DefaultScriptHandlerFactory(
+                get(DependencyManagementServices.class),
+                get(FileResolver.class),
+                get(DependencyMetaDataProvider.class));
         ClassLoader parentClassLoader;
         if (project.getParent() != null) {
             parentClassLoader = project.getParent().getBuildscript().getClassLoader();
@@ -151,13 +150,6 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
 
     protected DependencyMetaDataProvider createDependencyMetaDataProvider() {
         return new DependencyMetaDataProvider() {
-            public InternalRepository getInternalRepository() {
-                return get(InternalRepository.class);
-            }
-
-            public File getGradleUserHomeDir() {
-                return project.getGradle().getGradleUserHomeDir();
-            }
 
             public Module getModule() {
                 return new DefaultModule(project.getGroup().toString(), project.getName(), project.getVersion().toString(), project.getStatus().toString());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
index 6d21ae5..b4d14ad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
@@ -19,11 +19,10 @@
 package org.gradle.api.internal.project
 
 import org.gradle.api.initialization.dsl.ScriptHandler
-import org.gradle.api.logging.LogLevel
 import org.gradle.api.logging.Logger
+import org.gradle.api.logging.LoggingManager
 import org.gradle.groovy.scripts.DefaultScript
 import org.gradle.logging.StandardOutputCapture
-import org.gradle.api.logging.LoggingManager
 
 abstract class ProjectScript extends DefaultScript {
 
@@ -43,14 +42,6 @@ abstract class ProjectScript extends DefaultScript {
         scriptTarget.buildscript(configureClosure)
     }
 
-    def void disableStandardOutputCapture() {
-        scriptTarget.disableStandardOutputCapture()
-    }
-
-    def void captureStandardOutput(LogLevel level) {
-        scriptTarget.captureStandardOutput(level)
-    }
-
     def StandardOutputCapture getStandardOutputCapture() {
         scriptTarget.standardOutputCapture
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
index e35b207..badaac5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.project;
 
 import org.gradle.api.ProjectState;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 public class ProjectStateInternal implements ProjectState {
     private boolean executing;
@@ -54,6 +54,24 @@ public class ProjectStateInternal implements ProjectState {
         if (failure == null) {
             return;
         }
-        throw UncheckedException.asUncheckedException(failure);
+        throw UncheckedException.throwAsUncheckedException(failure);
+    }
+    
+    public String toString() {
+        String state;
+        
+        if (getExecuting()) {
+            state = "EXECUTING";
+        } else if (getExecuted()) {
+            if (failure == null) {
+                state = "EXECUTED";
+            } else {
+                state = String.format("FAILED (%s)", failure.getMessage());
+            }
+        } else {
+            state = "NOT EXECUTED";
+        }
+        
+        return String.format("project state '%s'", state);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistry.java
deleted file mode 100644
index e0449af..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistry.java
+++ /dev/null
@@ -1,53 +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.project;
-
-import org.gradle.api.internal.Factory;
-
-/**
- * A registry of services.
- */
-public interface ServiceRegistry {
-    /**
-     * Locates the service of the given type. There is a single instance for each service type.
-     *
-     * @param serviceType The service type.
-     * @param <T>         The service type.
-     * @return The service instance. Never returns null.
-     * @throws IllegalArgumentException When there is no service of the given type available.
-     */
-    <T> T get(Class<T> serviceType) throws IllegalArgumentException;
-
-    /**
-     * Locates a factory which can create services of the given type.
-     *
-     * @param type The service type that the factory should create.
-     * @param <T>  The service type that the factory should create.
-     * @return The factory. Never returns null.
-     * @throws IllegalArgumentException When there is no factory available for services of the given type.
-     */
-    <T> Factory<T> getFactory(Class<T> type) throws IllegalArgumentException;
-
-    /**
-     * Creates a new service instance of the given type.
-     *
-     * @param type The service type
-     * @param <T>  The service type.
-     * @return The instance. Never returns null.
-     * @throws IllegalArgumentException When there is no factory available for services of the given type.
-     */
-    <T> T newInstance(Class<T> type) throws IllegalArgumentException;
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
index 7a3e61b..1b3e36f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
@@ -15,8 +15,10 @@
  */
 package org.gradle.api.internal.project;
 
+import org.gradle.internal.service.ServiceRegistry;
+
 /**
- * A heirarchical service registry.
+ * A hierarchical service registry.
  */
 public interface ServiceRegistryFactory extends ServiceRegistry {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
new file mode 100644
index 0000000..ab1203b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.project;
+
+import org.gradle.StartParameter;
+import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.internal.changedetection.*;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.execution.*;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheRepository;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.RandomLongIdGenerator;
+
+public class TaskExecutionServices extends DefaultServiceRegistry {
+    private final Gradle gradle;
+
+    public TaskExecutionServices(ServiceRegistry parent, Gradle gradle) {
+        super(parent);
+        this.gradle = gradle;
+    }
+
+    protected TaskExecuter createTaskExecuter() {
+        return new ExecuteAtMostOnceTaskExecuter(
+                new SkipOnlyIfTaskExecuter(
+                        new SkipTaskWithNoActionsExecuter(
+                                new SkipEmptySourceFilesTaskExecuter(
+                                        new ValidatingTaskExecuter(
+                                                new SkipUpToDateTaskExecuter(
+                                                        new CacheLockHandlingTaskExecuter(
+                                                                new PostExecutionAnalysisTaskExecuter(
+                                                                        new ExecuteActionsTaskExecuter(
+                                                                                get(ListenerManager.class).getBroadcaster(TaskActionListener.class))),
+                                                                get(TaskArtifactStateCacheAccess.class)),
+                                                        get(TaskArtifactStateRepository.class)))))));
+    }
+
+    protected TaskArtifactStateCacheAccess createCacheAccess() {
+        return new DefaultTaskArtifactStateCacheAccess(gradle, get(CacheRepository.class));
+    }
+
+    protected TaskArtifactStateRepository createTaskArtifactStateRepository() {
+        TaskArtifactStateCacheAccess cacheAccess = get(TaskArtifactStateCacheAccess.class);
+
+        FileSnapshotter fileSnapshotter = new DefaultFileSnapshotter(
+                new CachingHasher(
+                        new DefaultHasher(),
+                        cacheAccess));
+
+        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(fileSnapshotter, new RandomLongIdGenerator(), cacheAccess);
+
+        TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess, new CacheBackedFileSnapshotRepository(cacheAccess));
+
+        return new FileCacheBroadcastTaskArtifactStateRepository(
+                new ShortCircuitTaskArtifactStateRepository(
+                        get(StartParameter.class),
+                        new DefaultTaskArtifactStateRepository(
+                                taskHistoryRepository,
+                                fileSnapshotter,
+                                outputFilesSnapshotter)),
+                new DefaultFileCacheListener());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
index cd8745b..b2ea432 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
@@ -18,9 +18,11 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.api.internal.tasks.DefaultTaskInputs;
 import org.gradle.api.internal.tasks.DefaultTaskOutputs;
 import org.gradle.api.tasks.TaskInputs;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 
 /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
index 9f38832..c225988 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
@@ -16,29 +16,16 @@
 
 package org.gradle.api.internal.project;
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.plugins.resolver.ChainResolver;
 import org.gradle.StartParameter;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.artifacts.maven.MavenFactory;
-import org.gradle.api.execution.TaskActionListener;
 import org.gradle.api.internal.*;
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
-import org.gradle.api.internal.artifacts.DefaultConfigurationContainerFactory;
 import org.gradle.api.internal.artifacts.DefaultModule;
 import org.gradle.api.internal.artifacts.DependencyManagementServices;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
-import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandlerFactory;
-import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
-import org.gradle.api.internal.artifacts.dsl.dependencies.*;
-import org.gradle.api.internal.artifacts.ivyservice.*;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.*;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.*;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
-import org.gradle.api.internal.changedetection.*;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptHandlerFactory;
@@ -46,56 +33,49 @@ import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFacto
 import org.gradle.api.internal.project.taskfactory.DependencyAutoWireTaskFactory;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.internal.project.taskfactory.TaskFactory;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.api.internal.tasks.execution.*;
-import org.gradle.cache.AutoCloseCacheFactory;
-import org.gradle.cache.CacheFactory;
 import org.gradle.cache.CacheRepository;
-import org.gradle.cache.DefaultCacheRepository;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.internal.CacheFactory;
+import org.gradle.cache.internal.DefaultCacheRepository;
 import org.gradle.configuration.*;
-import org.gradle.groovy.scripts.*;
+import org.gradle.groovy.scripts.DefaultScriptCompilerFactory;
+import org.gradle.groovy.scripts.ScriptCompilerFactory;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.internal.*;
 import org.gradle.initialization.*;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.messaging.actor.internal.DefaultActorFactory;
 import org.gradle.messaging.concurrent.DefaultExecutorFactory;
 import org.gradle.messaging.concurrent.ExecutorFactory;
 import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.internal.TcpMessagingServer;
 import org.gradle.process.internal.DefaultWorkerProcessFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
+import org.gradle.profile.ProfileEventAdapter;
+import org.gradle.profile.ProfileListener;
 import org.gradle.util.*;
 
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Contains the singleton services which are shared by all builds executed by a single {@link org.gradle.GradleLauncher}
  * invocation.
  */
 public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
     private final StartParameter startParameter;
-    private final Map<String, ModuleDescriptor> clientModuleRegistry = new HashMap<String, ModuleDescriptor>();
 
     public TopLevelBuildServiceRegistry(final ServiceRegistry parent, final StartParameter startParameter) {
         super(parent);
         this.startParameter = startParameter;
-    }
-
-    protected PublishArtifactFactory createPublishArtifactFactory() {
-        return new DefaultPublishArtifactFactory();
+        add(StartParameter.class, startParameter);
     }
 
     protected ImportsReader createImportsReader() {
         return new ImportsReader();
     }
-    protected ClassGenerator createClassGenerator() {
-        return new AsmBackedClassGenerator();
-    }
 
     protected TimeProvider createTimeProvider() {
         return new TrueTimeProvider();
@@ -106,140 +86,50 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     }
 
     protected IProjectFactory createProjectFactory() {
-        return new ProjectFactory(
-                startParameter.getBuildScriptSource(),
-                get(ClassGenerator.class));
+        return new ProjectFactory(get(Instantiator.class));
     }
 
     protected ListenerManager createListenerManager(ListenerManager listenerManager) {
         return listenerManager.createChild();
     }
 
-    protected CacheFactory createCacheFactory(CacheFactory parentFactory) {
-        return new AutoCloseCacheFactory(parentFactory);
-    }
-
     protected ClassPathRegistry createClassPathRegistry() {
-        return new DefaultClassPathRegistry(new WorkerProcessClassPathProvider(get(CacheRepository.class)));
-    }
-    
-    protected ActorFactory createActorFactory() {
-        return new DefaultActorFactory(get(ExecutorFactory.class));
+        return new DefaultClassPathRegistry(
+                new DefaultClassPathProvider(get(ModuleRegistry.class)),
+                new DependencyClassPathProvider(get(ModuleRegistry.class), get(PluginModuleRegistry.class)),
+                new WorkerProcessClassPathProvider(get(CacheRepository.class), get(ModuleRegistry.class)));
     }
 
-    protected TaskExecuter createTaskExecuter() {
-        return new ExecuteAtMostOnceTaskExecuter(
-                new SkipOnlyIfTaskExecuter(
-                        new SkipTaskWithNoActionsExecuter(
-                                new SkipEmptySourceFilesTaskExecuter(
-                                        new ValidatingTaskExecuter(
-                                                new SkipUpToDateTaskExecuter(
-                                                        new PostExecutionAnalysisTaskExecuter(
-                                                                new ExecuteActionsTaskExecuter(
-                                                                        get(ListenerManager.class).getBroadcaster(TaskActionListener.class))),
-                                                        get(TaskArtifactStateRepository.class)))))));
+    protected IsolatedAntBuilder createIsolatedAntBuilder() {
+        return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class), get(ClassLoaderFactory.class));
     }
 
-    protected Factory<RepositoryHandler> createRepositoryHandlerFactory() {
-        return new DefaultRepositoryHandlerFactory(
-                get(DependencyManagementServices.class).get(ResolverFactory.class),
-                get(ClassGenerator.class));
-    }
-
-    protected CacheRepository createCacheRepository() {
-        return new DefaultCacheRepository(startParameter.getGradleUserHomeDir(),
-                startParameter.getCacheUsage(), get(CacheFactory.class));
+    protected ActorFactory createActorFactory() {
+        return new DefaultActorFactory(get(ExecutorFactory.class));
     }
 
-    protected ModuleDescriptorFactory createModuleDescriptorFactory() {
-        return new DefaultModuleDescriptorFactory();
+    protected IGradlePropertiesLoader createGradlePropertiesLoader() {
+        return new DefaultGradlePropertiesLoader(startParameter);
     }
 
-    protected ExcludeRuleConverter createExcludeRuleConverter() {
-        return new DefaultExcludeRuleConverter();
+    protected BuildLoader createBuildLoader() {
+        return new ProjectPropertySettingBuildLoader(
+                get(IGradlePropertiesLoader.class),
+                new InstantiatingBuildLoader(get(IProjectFactory.class)));
     }
 
-    protected ExternalModuleDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory() {
-        return new ExternalModuleDependencyDescriptorFactory(get(ExcludeRuleConverter.class));
+    protected CacheFactory createCacheFactory() {
+        return getFactory(CacheFactory.class).create();
     }
 
-    protected ConfigurationsToModuleDescriptorConverter createConfigurationsToModuleDescriptorConverter() {
-        return new DefaultConfigurationsToModuleDescriptorConverter();
+    protected CacheRepository createCacheRepository() {
+        CacheFactory factory = get(CacheFactory.class);
+        return new DefaultCacheRepository(startParameter.getGradleUserHomeDir(), startParameter.getProjectCacheDir(),
+                startParameter.getCacheUsage(), factory);
     }
     
-    private ResolveModuleDescriptorConverter createResolveModuleDescriptorConverter(ProjectDependencyDescriptorStrategy projectDependencyStrategy) {
-        DependencyDescriptorFactory dependencyDescriptorFactoryDelegate = createDependencyDescriptorFactory(projectDependencyStrategy);
-        return new ResolveModuleDescriptorConverter(
-                get(ModuleDescriptorFactory.class),
-                get(ConfigurationsToModuleDescriptorConverter.class),
-                new DefaultDependenciesToModuleDescriptorConverter(
-                        dependencyDescriptorFactoryDelegate,
-                        get(ExcludeRuleConverter.class)));
-    }
-
-    private DependencyDescriptorFactory createDependencyDescriptorFactory(ProjectDependencyDescriptorStrategy projectDependencyStrategy) {
-        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory = new DefaultModuleDescriptorFactoryForClientModule();
-        DependencyDescriptorFactory dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
-                new ClientModuleDependencyDescriptorFactory(
-                        get(ExcludeRuleConverter.class), clientModuleDescriptorFactory, clientModuleRegistry),
-                new ProjectDependencyDescriptorFactory(
-                        get(ExcludeRuleConverter.class),
-                        projectDependencyStrategy),
-                get(ExternalModuleDependencyDescriptorFactory.class));
-        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactoryDelegate);
-        return dependencyDescriptorFactoryDelegate;
-    }
-
-    protected PublishModuleDescriptorConverter createPublishModuleDescriptorConverter() {
-        return new PublishModuleDescriptorConverter(
-                createResolveModuleDescriptorConverter(ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY),
-                new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY));
-    }
-
-    protected ConfigurationContainerFactory createConfigurationContainerFactory() {
-        DependencyDescriptorFactory dependencyDescriptorFactoryDelegate = createDependencyDescriptorFactory(ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY);
-        PublishModuleDescriptorConverter fileModuleDescriptorConverter = new PublishModuleDescriptorConverter(
-                createResolveModuleDescriptorConverter(ProjectDependencyDescriptorFactory.IVY_FILE_DESCRIPTOR_STRATEGY),
-                new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
-
-        return new DefaultConfigurationContainerFactory(clientModuleRegistry,
-                new DefaultSettingsConverter(
-                        get(ProgressLoggerFactory.class)
-                ),
-                get(PublishModuleDescriptorConverter.class),
-                get(PublishModuleDescriptorConverter.class),
-                fileModuleDescriptorConverter,
-                new DefaultIvyFactory(),
-                new SelfResolvingDependencyResolver(
-                        new DefaultIvyDependencyResolver(
-                                new DefaultIvyReportConverter(dependencyDescriptorFactoryDelegate))),
-                new DefaultIvyDependencyPublisher(new DefaultPublishOptionsFactory()),
-                get(ClassGenerator.class));
-    }
-
-    protected DependencyFactory createDependencyFactory() {
-        ClassGenerator classGenerator = get(ClassGenerator.class);
-        DefaultProjectDependencyFactory projectDependencyFactory = new DefaultProjectDependencyFactory(
-                startParameter.getProjectDependenciesBuildInstruction(),
-                classGenerator);
-        return new DefaultDependencyFactory(
-                WrapUtil.<IDependencyImplementationFactory>toSet(
-                        new ModuleDependencyFactory(
-                                classGenerator),
-                        new SelfResolvingDependencyFactory(
-                                classGenerator),
-                        new ClassPathDependencyFactory(
-                                classGenerator,
-                                get(ClassPathRegistry.class),
-                                new IdentityFileResolver()),
-                        projectDependencyFactory),
-                new DefaultClientModuleFactory(
-                        classGenerator),
-                projectDependencyFactory);
-    }
-
     protected ProjectEvaluator createProjectEvaluator() {
-        return new DefaultProjectEvaluator(
+        return new LifecycleProjectEvaluator(
                 new BuildScriptProcessor(
                         get(ScriptPluginFactory.class)));
     }
@@ -251,31 +141,24 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
                                 get(ClassGenerator.class))));
     }
 
-    protected TaskArtifactStateRepository createTaskArtifactStateRepository() {
-        CacheRepository cacheRepository = get(CacheRepository.class);
-        FileSnapshotter fileSnapshotter = new DefaultFileSnapshotter(
-                new CachingHasher(
-                        new DefaultHasher(),
-                        cacheRepository));
-
-        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(fileSnapshotter, new RandomLongIdGenerator(), cacheRepository);
-        return new FileCacheBroadcastTaskArtifactStateRepository(
-                new ShortCircuitTaskArtifactStateRepository(
-                        startParameter,
-                        new DefaultTaskArtifactStateRepository(cacheRepository,
-                                fileSnapshotter,
-                                outputFilesSnapshotter)),
-                new DefaultFileCacheListener());
-    }
-
     protected ScriptCompilerFactory createScriptCompileFactory() {
         ScriptExecutionListener scriptExecutionListener = get(ListenerManager.class).getBroadcaster(ScriptExecutionListener.class);
+        EmptyScriptGenerator emptyScriptGenerator = new AsmBackedEmptyScriptGenerator();
+        CacheValidator scriptCacheInvalidator =  new CacheValidator() {
+            public boolean isValid() {
+                return !get(StartParameter.class).isRecompileScripts();
+            }
+        };
         return new DefaultScriptCompilerFactory(
-                new CachingScriptCompilationHandler(
-                        new DefaultScriptCompilationHandler()),
-                new DefaultScriptRunnerFactory(
-                        scriptExecutionListener),
-                get(CacheRepository.class));
+                new CachingScriptClassCompiler(
+                        new ShortCircuitEmptyScriptCompiler(
+                                new FileCacheBackedScriptClassCompiler(
+                                        get(CacheRepository.class),
+                                        scriptCacheInvalidator,
+                                        new DefaultScriptCompilationHandler(
+                                                emptyScriptGenerator)),
+                                emptyScriptGenerator)),
+                new DefaultScriptRunnerFactory(scriptExecutionListener));
     }
 
     protected ScriptPluginFactory createScriptObjectConfigurerFactory() {
@@ -288,24 +171,29 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     }
 
     protected MultiParentClassLoader createRootClassLoader() {
-        return get(ClassLoaderFactory.class).createScriptClassLoader();
+        return get(ClassLoaderRegistry.class).createScriptClassLoader();
     }
     
     protected InitScriptHandler createInitScriptHandler() {
         return new InitScriptHandler(
-                new UserHomeInitScriptFinder(
-                        new DefaultInitScriptFinder()),
+                new CompositeInitScriptFinder(
+                        new ProvidedInitScriptFinder(),
+                        new UserHomeInitScriptFinder(),
+                        new DistributionInitScriptFinder(
+                                get(GradleDistributionLocator.class))),
                 new DefaultInitScriptProcessor(
                         get(ScriptPluginFactory.class)));
 
     }
 
     protected SettingsProcessor createSettingsProcessor() {
-        return new PropertiesLoadingSettingsProcessor(new
-                ScriptEvaluatingSettingsProcessor(
+        return new PropertiesLoadingSettingsProcessor(
+                new ScriptEvaluatingSettingsProcessor(
                     get(ScriptPluginFactory.class),
                     new SettingsFactory(
-                        new DefaultProjectDescriptorRegistry())));
+                        new DefaultProjectDescriptorRegistry()),
+                        get(IGradlePropertiesLoader.class)),
+                get(IGradlePropertiesLoader.class));
     }
 
     protected ExceptionAnalyser createExceptionAnalyser() {
@@ -314,10 +202,13 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
 
     protected ScriptHandlerFactory createScriptHandlerFactory() {
         return new DefaultScriptHandlerFactory(
-                getFactory(RepositoryHandler.class),
-                get(ConfigurationContainerFactory.class),
-                new DependencyMetaDataProviderImpl(), 
-                get(DependencyFactory.class));
+                get(DependencyManagementServices.class),
+                get(FileResolver.class),
+                new DependencyMetaDataProviderImpl());
+    }
+
+    protected FileResolver createFileResolver() {
+        return new IdentityFileResolver();
     }
 
     protected Factory<WorkerProcessBuilder> createWorkerProcessFactory() {
@@ -325,10 +216,6 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
         return new DefaultWorkerProcessFactory(startParameter.getLogLevel(), get(MessagingServer.class), classPathRegistry,
                 new IdentityFileResolver(), new LongIdGenerator());
     }
-    
-    protected MessagingServer createMessagingServer() {
-        return new TcpMessagingServer(get(ClassLoaderFactory.class).getRootClassLoader());
-    }
 
     protected BuildConfigurer createBuildConfigurer() {
         return new DefaultBuildConfigurer(
@@ -336,19 +223,15 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
                 new ProjectDependencies2TaskResolver(),
                 new ImplicitTasksConfigurer());
     }
-
-    protected MavenFactory createMavenFactory() {
-        return get(DependencyManagementServices.class).get(MavenFactory.class);
+    
+    protected ProfileEventAdapter createProfileEventAdapter() {
+        return new ProfileEventAdapter(get(BuildRequestMetaData.class), get(TimeProvider.class), get(ListenerManager.class).getBroadcaster(ProfileListener.class));
     }
 
     protected DependencyManagementServices createDependencyManagementServices() {
-        ClassLoader coreImplClassLoader = get(ClassLoaderFactory.class).getCoreImplClassLoader();
-        try {
-            Class<?> implClass = coreImplClassLoader.loadClass("org.gradle.api.internal.artifacts.DefaultDependencyManagementServices");
-            return (DependencyManagementServices) implClass.getConstructor(ServiceRegistry.class).newInstance(this);
-        } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
+        ClassLoader coreImplClassLoader = get(ClassLoaderRegistry.class).getCoreImplClassLoader();
+        ServiceLocator serviceLocator = new ServiceLocator(coreImplClassLoader);
+        return serviceLocator.getFactory(DependencyManagementServices.class).newInstance(this);
     }
 
     public ServiceRegistryFactory createFor(Object domainObject) {
@@ -360,19 +243,9 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     }
 
     private class DependencyMetaDataProviderImpl implements DependencyMetaDataProvider {
-        public InternalRepository getInternalRepository() {
-            return new EmptyInternalRepository();
-        }
-
-        public File getGradleUserHomeDir() {
-            return startParameter.getGradleUserHomeDir();
-        }
 
         public Module getModule() {
             return new DefaultModule("unspecified", "unspecified", Project.DEFAULT_VERSION, Project.DEFAULT_STATUS);
         }
     }
-
-    private static class EmptyInternalRepository extends ChainResolver implements InternalRepository {
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java
index 398baa3..0a6c305 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java
@@ -19,13 +19,14 @@ import org.apache.commons.lang.StringUtils;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.Task;
+import org.gradle.api.Transformer;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.execution.TaskValidator;
-import org.gradle.api.tasks.Optional;
-import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.*;
 import org.gradle.util.ReflectionUtil;
 
+import java.io.File;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Field;
@@ -40,12 +41,29 @@ import java.util.concurrent.Callable;
 public class AnnotationProcessingTaskFactory implements ITaskFactory {
     private final ITaskFactory taskFactory;
     private final Map<Class, List<Action<Task>>> actionsForType = new HashMap<Class, List<Action<Task>>>();
+    
+    private final Transformer<Iterable<File>, Object> filePropertyTransformer = new Transformer<Iterable<File>, Object>() {
+        public Iterable<File> transform(Object original) {
+            File file = (File) original;
+            return file == null ? Collections.<File>emptyList() : Collections.singleton(file);
+        }
+    };
+
+    private final Transformer<Iterable<File>, Object> iterableFilePropertyTransformer = new Transformer<Iterable<File>, Object>() {
+        @SuppressWarnings("unchecked")
+        public Iterable<File> transform(Object original) {
+            return original != null ? (Iterable<File>) original : Collections.<File>emptyList();
+        }
+    };
+    
     private final List<? extends PropertyAnnotationHandler> handlers = Arrays.asList(
             new InputFilePropertyAnnotationHandler(),
             new InputDirectoryPropertyAnnotationHandler(),
             new InputFilesPropertyAnnotationHandler(),
-            new OutputFilePropertyAnnotationHandler(),
-            new OutputDirectoryPropertyAnnotationHandler(),
+            new OutputFilePropertyAnnotationHandler(OutputFile.class, filePropertyTransformer),
+            new OutputFilePropertyAnnotationHandler(OutputFiles.class, iterableFilePropertyTransformer),
+            new OutputDirectoryPropertyAnnotationHandler(OutputDirectory.class, filePropertyTransformer),
+            new OutputDirectoryPropertyAnnotationHandler(OutputDirectories.class, iterableFilePropertyTransformer),
             new InputPropertyAnnotationHandler(),
             new NestedBeanPropertyAnnotationHandler());
     private final ValidationAction notNullValidator = new ValidationAction() {
@@ -126,7 +144,13 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
         methods.add(method.getName());
         actions.add(new Action<Task>() {
             public void execute(Task task) {
-                ReflectionUtil.invoke(task, method.getName(), new Object[0]);
+                ClassLoader original = Thread.currentThread().getContextClassLoader();
+                Thread.currentThread().setContextClassLoader(method.getDeclaringClass().getClassLoader());
+                try {
+                    ReflectionUtil.invoke(task, method.getName());
+                } finally {
+                    Thread.currentThread().setContextClassLoader(original);
+                }
             }
         });
     }
@@ -182,7 +206,7 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
                 if (parent != null) {
                     propertyName = parent.getName() + '.' + propertyName;
                 }
-                PropertyInfo propertyInfo = new PropertyInfo(this, parent, propertyName, method);
+                PropertyInfo propertyInfo = new PropertyInfo(type, this, parent, propertyName, method);
 
                 attachValidationActions(propertyInfo, fieldName);
 
@@ -265,12 +289,14 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
         private ValidationAction notNullValidator = NO_OP_VALIDATION_ACTION;
         private UpdateAction configureAction = NO_OP_CONFIGURATION_ACTION;
         public boolean required;
+        private final Class<?> type;
 
-        private PropertyInfo(Validator validator, PropertyInfo parent, String propertyName, Method method) {
+        private PropertyInfo(Class<?> type, Validator validator, PropertyInfo parent, String propertyName, Method method) {
+            this.type = type;
             this.validator = validator;
             this.parent = parent;
             this.propertyName = propertyName;
-            this.method = method;
+            this.method = method;   
         }
 
         @Override
@@ -286,6 +312,19 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
             return method.getReturnType();
         }
 
+        public Class<?> getInstanceVariableType() {
+            Class<?> currentType = type;
+            while (!currentType.equals(Object.class)) {
+                try {
+                    return currentType.getDeclaredField(propertyName).getType();
+                } catch (NoSuchFieldException e) {
+                    currentType = currentType.getSuperclass();
+                }
+            }
+
+            return null;
+        }
+
         public AnnotatedElement getTarget() {
             return method;
         }
@@ -316,7 +355,7 @@ public class AnnotationProcessingTaskFactory implements ITaskFactory {
                 bean = parentValue.getValue();
             }
 
-            final Object value = ReflectionUtil.invoke(bean, method.getName(), new Object[0]);
+            final Object value = ReflectionUtil.invoke(bean, method.getName());
 
             return new PropertyValue() {
                 public Object getValue() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputPropertyAnnotationHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputPropertyAnnotationHandler.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java
index 240099e..607d019 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java
@@ -28,7 +28,11 @@ public class NestedBeanPropertyAnnotationHandler implements PropertyAnnotationHa
     }
 
     public void attachActions(final PropertyActionContext context) {
-        context.attachActions(context.getType());
+        Class<?> nestedType = context.getInstanceVariableType();
+        if (nestedType == null) {
+            nestedType = context.getType();
+        }
+        context.attachActions(nestedType);
         context.setConfigureAction(new UpdateAction() {
             public void update(Task task, final Callable<Object> futureValue) {
                 task.getInputs().property(context.getName() + ".class", new Callable<Object>() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
index 0ec5adb..c6a81ce 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
@@ -18,9 +18,9 @@ package org.gradle.api.internal.project.taskfactory;
 import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Task;
-import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.Transformer;
+import org.gradle.internal.UncheckedException;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.UncheckedException;
 
 import java.io.File;
 import java.lang.annotation.Annotation;
@@ -28,27 +28,37 @@ import java.util.Collection;
 import java.util.concurrent.Callable;
 
 public class OutputDirectoryPropertyAnnotationHandler implements PropertyAnnotationHandler {
+
+    private final Class<? extends Annotation> annotationType;
+    private final Transformer<Iterable<File>, Object> valueTransformer;
+    
+    public OutputDirectoryPropertyAnnotationHandler(Class<? extends Annotation> annotationType, Transformer<Iterable<File>, Object> valueTransformer) {
+        this.annotationType = annotationType;
+        this.valueTransformer = valueTransformer;
+    }
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return annotationType;
+    }
+
     private final ValidationAction outputDirValidation = new ValidationAction() {
         public void validate(String propertyName, Object value, Collection<String> messages) {
-            File fileValue = (File) value;
-            if (fileValue.exists() && !fileValue.isDirectory()) {
-                messages.add(String.format("Directory '%s' specified for property '%s' is not a directory.", fileValue, propertyName));
-                return;
-            }
+            for (File file : valueTransformer.transform(value)) {
+                if (file.exists() && !file.isDirectory()) {
+                    messages.add(String.format("Directory '%s' specified for property '%s' is not a directory.", file, propertyName));
+                    return;
+                }
 
-            for (File candidate = fileValue; candidate != null && !candidate.isDirectory(); candidate = candidate.getParentFile()) {
-                if (candidate.exists() && !candidate.isDirectory()) {
-                    messages.add(String.format("Cannot write to directory '%s' specified for property '%s', as ancestor '%s' is not a directory.", fileValue, propertyName, candidate));
-                    break;
+                for (File candidate = file; candidate != null && !candidate.isDirectory(); candidate = candidate.getParentFile()) {
+                    if (candidate.exists() && !candidate.isDirectory()) {
+                        messages.add(String.format("Cannot write to directory '%s' specified for property '%s', as ancestor '%s' is not a directory.", file, propertyName, candidate));
+                        break;
+                    }
                 }
             }
         }
     };
-
-    public Class<? extends Annotation> getAnnotationType() {
-        return OutputDirectory.class;
-    }
-
+    
     public void attachActions(final PropertyActionContext context) {
         context.setValidationAction(outputDirValidation);
         context.setConfigureAction(new UpdateAction() {
@@ -56,19 +66,18 @@ public class OutputDirectoryPropertyAnnotationHandler implements PropertyAnnotat
                 task.getOutputs().files(futureValue);
                 task.doFirst(new Action<Task>() {
                     public void execute(Task task) {
-                        File fileValue;
+                        Iterable<File> files;
                         try {
-                            fileValue = (File) futureValue.call();
+                            files = valueTransformer.transform(futureValue.call());
                         } catch (Exception e) {
-                            throw UncheckedException.asUncheckedException(e);
-                        }
-                        if (fileValue == null) {
-                            return;
+                            throw UncheckedException.throwAsUncheckedException(e);
                         }
-                        fileValue = GFileUtils.canonicalise(fileValue);
-                        if (!fileValue.isDirectory() && !fileValue.mkdirs()) {
-                            throw new InvalidUserDataException(String.format(
-                                    "Cannot create directory '%s' specified for property '%s'.", fileValue, context.getName()));
+                        for (File file : files) {
+                            file = GFileUtils.canonicalise(file);
+                            if (!file.isDirectory() && !file.mkdirs()) {
+                                throw new InvalidUserDataException(String.format(
+                                        "Cannot create directory '%s' specified for property '%s'.", file, context.getName()));
+                            }
                         }
                     }
                 });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
index 0ca9b6d..dd5874b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
@@ -18,9 +18,9 @@ package org.gradle.api.internal.project.taskfactory;
 import org.gradle.api.Action;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Task;
-import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.Transformer;
+import org.gradle.internal.UncheckedException;
 import org.gradle.util.GFileUtils;
-import org.gradle.util.UncheckedException;
 
 import java.io.File;
 import java.lang.annotation.Annotation;
@@ -28,45 +28,54 @@ import java.util.Collection;
 import java.util.concurrent.Callable;
 
 public class OutputFilePropertyAnnotationHandler implements PropertyAnnotationHandler {
-    private final ValidationAction ouputFileValidation = new ValidationAction() {
+
+    private final Class<? extends Annotation> annotationType;
+    private final Transformer<Iterable<File>, Object> valueTransformer;
+
+    public OutputFilePropertyAnnotationHandler(Class<? extends Annotation> annotationType, Transformer<Iterable<File>, Object> valueTransformer) {
+        this.annotationType = annotationType;
+        this.valueTransformer = valueTransformer;
+    }
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return annotationType;
+    }
+
+    private final ValidationAction outputDirValidation = new ValidationAction() {
         public void validate(String propertyName, Object value, Collection<String> messages) {
-            File fileValue = GFileUtils.canonicalise((File) value);
-            if (fileValue.exists() && fileValue.isDirectory()) {
-                messages.add(String.format("Cannot write to file '%s' specified for property '%s' as it is a directory.", fileValue, propertyName));
-            }
-            
-            for (File candidate = fileValue.getParentFile(); candidate != null && !candidate.isDirectory(); candidate = candidate.getParentFile()) {
-                if (candidate.exists() && !candidate.isDirectory()) {
-                    messages.add(String.format("Cannot write to file '%s' specified for property '%s', as ancestor '%s' is not a directory.", fileValue, propertyName, candidate));
-                    break;
+            for (File file : valueTransformer.transform(value)) {
+                if (file.exists() && file.isDirectory()) {
+                    messages.add(String.format("Cannot write to file '%s' specified for property '%s' as it is a directory.", file, propertyName));
+                }
+
+                for (File candidate = file.getParentFile(); candidate != null && !candidate.isDirectory(); candidate = candidate.getParentFile()) {
+                    if (candidate.exists() && !candidate.isDirectory()) {
+                        messages.add(String.format("Cannot write to file '%s' specified for property '%s', as ancestor '%s' is not a directory.", file, propertyName, candidate));
+                        break;
+                    }
                 }
             }
         }
     };
 
-    public Class<? extends Annotation> getAnnotationType() {
-        return OutputFile.class;
-    }
-
     public void attachActions(final PropertyActionContext context) {
-        context.setValidationAction(ouputFileValidation);
+        context.setValidationAction(outputDirValidation);
         context.setConfigureAction(new UpdateAction() {
             public void update(Task task, final Callable<Object> futureValue) {
                 task.getOutputs().files(futureValue);
                 task.doFirst(new Action<Task>() {
                     public void execute(Task task) {
-                        File fileValue;
+                        Iterable<File> files;
                         try {
-                            fileValue = (File) futureValue.call();
+                            files = valueTransformer.transform(futureValue.call());
                         } catch (Exception e) {
-                            throw UncheckedException.asUncheckedException(e);
-                        }
-                        if (fileValue == null) {
-                            return;
+                            throw UncheckedException.throwAsUncheckedException(e);
                         }
-                        fileValue = GFileUtils.canonicalise(fileValue);
-                        if (!fileValue.getParentFile().isDirectory() && !fileValue.getParentFile().mkdirs()) {
-                            throw new InvalidUserDataException(String.format("Cannot create parent directory '%s' of file specified for property '%s'.", fileValue.getParentFile(), context.getName()));
+                        for (File file : files) {
+                            file = GFileUtils.canonicalise(file);
+                            if (!file.getParentFile().isDirectory() && !file.getParentFile().mkdirs()) {
+                                throw new InvalidUserDataException(String.format("Cannot create parent directory '%s' of file specified for property '%s'.", file.getParentFile(), context.getName()));
+                            }
                         }
                     }
                 });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java
index 296a8a0..359013a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java
@@ -29,6 +29,14 @@ public interface PropertyActionContext {
     Class<?> getType();
 
     /**
+     * If the property has an instance variable, returns the declared type of the instance variable.
+     *
+     * This may be different to {@link #getType()} if the public getter has a different return type.
+     * If there is no instance variable for this property, will return null.
+     */
+    Class<?> getInstanceVariableType();
+
+    /**
      * Returns the target for this property.
      */
     AnnotatedElement getTarget();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/Resource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/Resource.java
index bb6f60a..5675034 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/Resource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/Resource.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.resource;
 
 import java.io.File;
+import java.io.Serializable;
 import java.net.URI;
 
 /**
@@ -24,7 +25,7 @@ import java.net.URI;
  *
  * <p>Implementations are not required to be thread-safe.</p>
  */
-public interface Resource {
+public interface Resource extends Serializable {
     /**
      * Returns a display name for this resource. This can be used in log and error messages.
      *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java
index 276d81a..9fb0794 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java
@@ -58,7 +58,7 @@ public class UriResource implements Resource {
                 inputStream.close();
             }
         } catch (FileNotFoundException e) {
-            throw new ResourceNotFoundException(String.format(String.format("Could not read %s as it does not exist.", getDisplayName())));
+            throw new ResourceNotFoundException(String.format("Could not read %s as it does not exist.", getDisplayName()));
         } catch (Exception e) {
             throw new ResourceException(String.format("Could not read %s.", getDisplayName()), e);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java
new file mode 100644
index 0000000..453c454
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/DefaultResourceHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.resources;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.MaybeCompressedFileResource;
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.compression.GzipArchiver;
+import org.gradle.api.resources.ReadableResource;
+import org.gradle.api.resources.ResourceHandler;
+
+/**
+ * by Szczepan Faber, created at: 11/24/11
+ */
+public class DefaultResourceHandler implements ResourceHandler {
+    private final FileResolver resolver;
+
+    public DefaultResourceHandler(FileResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public ReadableResource gzip(Object path) {
+        return new GzipArchiver(resolver.resolveResource(path));
+    }
+
+    public ReadableResource bzip2(Object path) {
+        return new Bzip2Archiver(resolver.resolveResource(path));
+    }
+
+    //this method is not on the interface, at least for now
+    public ReadableResource maybeCompressed(Object tarPath) {
+        if (tarPath instanceof ReadableResource) {
+            return (ReadableResource) tarPath;
+        } else {
+            return new MaybeCompressedFileResource(resolver.resolveResource(tarPath));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/ResourceIsAFolderException.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/ResourceIsAFolderException.java
new file mode 100644
index 0000000..c2f0d13
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/ResourceIsAFolderException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.resources;
+
+import org.gradle.api.resources.ResourceException;
+
+/**
+ * Exception thrown when one attempts to read a folder
+ */
+public class ResourceIsAFolderException extends ResourceException {
+    public ResourceIsAFolderException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java
new file mode 100644
index 0000000..288ca63
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/resources/URIBuilder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.resources;
+
+import org.gradle.util.GUtil;
+
+import java.net.URI;
+
+/**
+ * by Szczepan Faber, created at: 12/13/11
+ */
+public class URIBuilder {
+    private final URI uri;
+    private String schemePrefix = "";
+
+    public URIBuilder(URI uri) {
+        this.uri = uri;
+    }
+
+    public URIBuilder schemePrefix(String schemePrefix) {
+        assert GUtil.isTrue(schemePrefix);
+        this.schemePrefix = schemePrefix;
+        return this;
+    }
+
+    public URI build() {
+        assert GUtil.isTrue(schemePrefix);
+        try {
+            return new URI(schemePrefix + ":" + uri.toString());
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to build URI based on supplied URI: " + uri, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
index 506014c..14cb7a0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
@@ -16,41 +16,55 @@
 package org.gradle.api.internal.tasks;
 
 import groovy.lang.Closure;
-import org.gradle.api.*;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.DefaultNamedDomainObjectContainer;
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.UnknownTaskException;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.collections.CollectionEventRegister;
+import org.gradle.api.internal.collections.CollectionFilter;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.TaskCollection;
-import org.gradle.util.DeprecationLogger;
 
-public class DefaultTaskCollection<T extends Task> extends DefaultNamedDomainObjectContainer<T> implements TaskCollection<T> {
+import java.util.Set;
+
+public class DefaultTaskCollection<T extends Task> extends DefaultNamedDomainObjectSet<T> implements TaskCollection<T> {
     protected final ProjectInternal project;
 
-    public DefaultTaskCollection(Class<T> type, ClassGenerator classGenerator, ProjectInternal project) {
-        super(type, classGenerator);
+    public DefaultTaskCollection(Class<T> type, Instantiator instantiator, ProjectInternal project) {
+        super(type, instantiator, new Task.Namer());
         this.project = project;
     }
 
-    public DefaultTaskCollection(Class<T> type, ClassGenerator classGenerator, ProjectInternal project, NamedObjectStore<T> store) {
-        super(type, classGenerator, store);
+    protected DefaultTaskCollection(Class<? extends T> type, Set<T> store, CollectionEventRegister<T> eventRegister, Instantiator instantiator, ProjectInternal project) {
+        super(type, store, eventRegister, instantiator, new Task.Namer());
         this.project = project;
     }
 
+    public DefaultTaskCollection(DefaultTaskCollection<? super T> collection, CollectionFilter<T> filter, Instantiator instantiator, ProjectInternal project) {
+        this(filter.getType(), collection.filteredStore(filter), collection.filteredEvents(filter), instantiator, project);
+    }
+
+    protected <S extends T> DefaultTaskCollection<S> filtered(CollectionFilter<S> filter) {
+        return getInstantiator().newInstance(DefaultTaskCollection.class, this, filter, getInstantiator(), project);
+    }
+
     @Override
-    public TaskCollection<T> matching(Spec<? super T> spec) {
-        return getClassGenerator().newInstance(DefaultTaskCollection.class, getType(), getClassGenerator(), project, storeWithSpec(spec));
+    public <S extends T> TaskCollection<S> withType(Class<S> type) {
+        return filtered(createFilter(type));
     }
 
     @Override
-    public TaskCollection<T> matching(Closure spec) {
-        return matching(Specs.convertClosureToSpec(spec));
+    public TaskCollection<T> matching(Spec<? super T> spec) {
+        return filtered(createFilter(spec));
     }
 
     @Override
-    public <S extends T> TaskCollection<S> withType(Class<S> type) {
-        return getClassGenerator().newInstance(DefaultTaskCollection.class, type, getClassGenerator(), project, storeWithType(type));
+    public TaskCollection<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
     }
 
     public Action<? super T> whenTaskAdded(Action<? super T> action) {
@@ -61,16 +75,6 @@ public class DefaultTaskCollection<T extends Task> extends DefaultNamedDomainObj
         whenObjectAdded(closure);
     }
 
-    public void allTasks(Action<? super T> action) {
-        DeprecationLogger.nagUser("TaskCollection.allTasks()", "all()");
-        all(action);
-    }
-
-    public void allTasks(Closure action) {
-        DeprecationLogger.nagUser("TaskCollection.allTasks()", "all()");
-        all(action);
-    }
-
     @Override
     public String getTypeDisplayName() {
         return "task";
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
index 1b37fab..e4e5af9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
@@ -21,9 +21,13 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.UnknownTaskException;
-import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.NamedDomainObjectContainerConfigureDelegate;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.DeprecationLogger;
 import org.gradle.util.GUtil;
 
 import java.util.HashMap;
@@ -32,8 +36,8 @@ import java.util.Map;
 public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements TaskContainerInternal {
     private final ITaskFactory taskFactory;
 
-    public DefaultTaskContainer(ProjectInternal project, ClassGenerator classGenerator, ITaskFactory taskFactory) {
-        super(Task.class, classGenerator, project);
+    public DefaultTaskContainer(ProjectInternal project, Instantiator instantiator, ITaskFactory taskFactory) {
+        super(Task.class, instantiator, project);
         this.taskFactory = taskFactory;
     }
 
@@ -46,12 +50,17 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         Task task = taskFactory.createTask(project, mutableOptions);
         String name = task.getName();
 
-        if (!replace && findByNameWithoutRules(name) != null) {
-            throw new InvalidUserDataException(String.format(
-                    "Cannot add %s as a task with that name already exists.", task));
+        Task existing = findByNameWithoutRules(name);
+        if (existing != null) {
+            if (replace) {
+                remove(existing);
+            } else {
+                throw new InvalidUserDataException(String.format(
+                        "Cannot add %s as a task with that name already exists.", task));
+            }
         }
 
-        addObject(name, task);
+        add(task);
 
         return task;
     }
@@ -64,6 +73,10 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         return type.cast(add(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type)));
     }
 
+    public Task create(String name) {
+        return add(name);
+    }
+
     public Task add(String name) {
         return add(GUtil.map(Task.TASK_NAME, name));
     }
@@ -72,6 +85,10 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         return add(GUtil.map(Task.TASK_NAME, name, Task.TASK_OVERWRITE, true));
     }
 
+    public Task create(String name, Closure configureClosure) {
+        return add(name, configureClosure);
+    }
+
     public Task add(String name, Closure configureClosure) {
         return add(GUtil.map(Task.TASK_NAME, name)).configure(configureClosure);
     }
@@ -100,6 +117,11 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         if (!GUtil.isTrue(path)) {
             throw new InvalidUserDataException("A path must be specified!");
         }
+        if(!(path instanceof CharSequence)) {
+            DeprecationLogger.nagUserWith(String.format("Converting class %s to a task dependency using toString()."
+                    + " This has been deprecated and will be removed in the next version of Gradle. Please use org.gradle.api.Task, java.lang.String, "
+                    + "org.gradle.api.Buildable, org.gradle.tasks.TaskDependency or a Closure to declare your task dependencies.", path.getClass().getName()));
+        }
         return getByPath(path.toString());
     }
 
@@ -110,4 +132,17 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         }
         return task;
     }
+
+    protected Object createConfigureDelegate(Closure configureClosure) {
+        return new NamedDomainObjectContainerConfigureDelegate(configureClosure.getOwner(), this);
+    }
+
+    public TaskContainerInternal configure(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, createConfigureDelegate(configureClosure));
+        return this;
+    }
+
+    public DynamicObject getTasksAsDynamicObject() {
+        return getElementsAsDynamicObject();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
index 4ad0be0..49663bc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
@@ -16,22 +16,22 @@
 package org.gradle.api.internal.tasks;
 
 import org.gradle.api.Project;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 
 public class DefaultTaskContainerFactory implements Factory<TaskContainerInternal> {
-    private final ClassGenerator classGenerator;
+    private final Instantiator instantiator;
     private final ITaskFactory taskFactory;
     private Project project;
 
-    public DefaultTaskContainerFactory(ClassGenerator classGenerator, ITaskFactory taskFactory, Project project) {
-        this.classGenerator = classGenerator;
+    public DefaultTaskContainerFactory(Instantiator instantiator, ITaskFactory taskFactory, Project project) {
+        this.instantiator = instantiator;
         this.taskFactory = taskFactory;
         this.project = project;
     }
 
     public TaskContainerInternal create() {
-        return classGenerator.newInstance(DefaultTaskContainer.class, project, classGenerator, taskFactory);
+        return instantiator.newInstance(DefaultTaskContainer.class, project, instantiator, taskFactory);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java
index 7298fa8..239756e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java
@@ -21,8 +21,8 @@ import org.gradle.api.Buildable;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Task;
 import org.gradle.api.tasks.TaskDependency;
+import org.gradle.internal.UncheckedException;
 import org.gradle.util.GUtil;
-import org.gradle.util.UncheckedException;
 
 import java.util.*;
 import java.util.concurrent.Callable;
@@ -75,7 +75,7 @@ public class DefaultTaskDependency extends AbstractTaskDependency {
                 try {
                     callableResult = callable.call();
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
                 if (callableResult != null) {
                     queue.add(0, callableResult);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
index 841becc..af87a1a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
@@ -22,7 +22,7 @@ import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.UnionFileCollection;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.tasks.TaskInputs;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -102,7 +102,7 @@ public class DefaultTaskInputs implements TaskInputs {
                 try {
                     value = callable.call();
                 } catch (Exception e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
             } else if (value instanceof Closure) {
                 Closure closure = (Closure) value;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
index db445f0..174a0c1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
@@ -19,5 +19,5 @@ import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.internal.DynamicObject;
 
 public interface TaskContainerInternal extends TaskContainer, TaskResolver {
-    DynamicObject getAsDynamicObject();
+    DynamicObject getTasksAsDynamicObject();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
index 7819b6a..12322c3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java
@@ -41,15 +41,12 @@ public class ExecuteActionsTaskExecuter implements TaskExecuter {
 
     public void execute(TaskInternal task, TaskStateInternal state) {
         listener.beforeActions(task);
-        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
-        Thread.currentThread().setContextClassLoader(task.getClass().getClassLoader());
         state.setExecuting(true);
         try {
             GradleException failure = executeActions(task, state);
             state.executed(failure);
         } finally {
             state.setExecuting(false);
-            Thread.currentThread().setContextClassLoader(originalClassLoader);
             listener.afterActions(task);
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java b/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java
index 4b5ab95..afecbf2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java
@@ -18,6 +18,7 @@ package org.gradle.api.invocation;
 import groovy.lang.Closure;
 import org.gradle.BuildListener;
 import org.gradle.StartParameter;
+import org.gradle.api.Action;
 import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.execution.TaskExecutionGraph;
@@ -63,8 +64,25 @@ public interface Gradle {
      * <p>Returns the root project of this build.</p>
      *
      * @return The root project. Never returns null.
+     * @throws IllegalStateException When called before the root project is available.
      */
-    Project getRootProject();
+    Project getRootProject() throws IllegalStateException;
+
+    /**
+     * Adds an action to execute against the root project of this build. If the root project is already available, the action
+     * is executed immediately. Otherwise, the action is executed when the root project becomes available.
+     *
+     * @param action The action to execute.
+     */
+    void rootProject(Action<? super Project> action);
+
+    /**
+     * Adds an action to execute against all projects of this build. The action is executed immediately against all projects which are
+     * already available. It is also executed as subsequent projects are added to this build.
+     *
+     * @param action The action to execute.
+     */
+    void allprojects(Action<? super Project> action);
 
     /**
      * <p>Returns the {@link TaskExecutionGraph} for this build.</p>
@@ -132,6 +150,21 @@ public interface Gradle {
     /**
      * 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.
+     * <p>
+     * An example of hooking into the projectsLoaded to configure buildscript classpath from the init script.
+     * <pre autoTested=''>
+     * //init.gradle
+     * gradle.projectsLoaded {
+     *   rootProject.buildscript {
+     *     repositories {
+     *       //...
+     *     }
+     *     dependencies {
+     *       //...
+     *     }
+     *   }
+     * }
+     * </pre>
      *
      * @param closure The closure to execute.
      */
@@ -181,6 +214,10 @@ public interface Gradle {
      *
      * <li>{@link org.gradle.api.tasks.testing.TestListener}
      *
+     * <li>{@link org.gradle.api.tasks.testing.TestOutputListener}
+     *
+     * <li>{@link org.gradle.api.artifacts.DependencyResolutionListener}
+     *
      * </ul>
      *
      * @param listener The listener to add. Does nothing if this listener has already been added.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java b/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
index 4107c32..6c059d9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
@@ -15,12 +15,14 @@
  */
 package org.gradle.api.logging;
 
+import java.io.Serializable;
+
 /**
  * The log levels supported by Gradle.
  *
  * @author Hans Dockter
  */
-public enum LogLevel {
+public enum LogLevel implements Serializable {
     DEBUG {
         boolean isEnabled(Logger logger) {
             return logger.isDebugEnabled();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/logging/Logger.java b/subprojects/core/src/main/groovy/org/gradle/api/logging/Logger.java
index 2a2d70d..532b4e6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/logging/Logger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/logging/Logger.java
@@ -32,6 +32,14 @@ public interface Logger extends org.slf4j.Logger {
     boolean isLifecycleEnabled();
 
     /**
+     * Multiple-parameters friendly debug method
+     *
+     * @param message the log message
+     * @param objects the log message parameters
+     */
+    void debug(String message, Object... objects);
+
+    /**
      * Logs the given message at lifecycle log level.
      *
      * @param message the log message.
@@ -75,6 +83,14 @@ public interface Logger extends org.slf4j.Logger {
     void quiet(String message, Object... objects);
 
     /**
+     * Logs the given message at info log level.
+     *
+     * @param message the log message.
+     * @param objects the log message parameters.
+     */
+    void info(String message, Object... objects);
+
+    /**
      * Logs the given message at quiet log level.
      *
      * @param message the log message.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/logging/LoggingManager.java b/subprojects/core/src/main/groovy/org/gradle/api/logging/LoggingManager.java
index f1b70ab..0fa81d4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/logging/LoggingManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/logging/LoggingManager.java
@@ -40,22 +40,6 @@ public interface LoggingManager extends LoggingOutput {
     LoggingManager captureStandardError(LogLevel level);
 
     /**
-     * Disables routing System.out and System.err to Gradle's logging system.
-     *
-     * @return this
-     */
-    @Deprecated
-    LoggingManager disableStandardOutputCapture();
-
-    /**
-     * Returns true when standard output capture is enabled.
-     *
-     * @return true when standard output capture is enabled.
-     */
-    @Deprecated
-    boolean isStandardOutputCaptureEnabled();
-
-    /**
      * Returns the log level that output written to System.out will be mapped to.
      *
      * @return The log level. Returns null when standard output capture is disabled.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java
index 047df3b..b38dad3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/Convention.java
@@ -27,7 +27,7 @@ import java.util.Map;
  *
  * @author Hans Dockter
  */
-public interface Convention extends DynamicObject {
+public interface Convention extends ExtensionContainer {
 
     /**
      * Returns the plugin convention objects contained in this convention.
@@ -54,4 +54,12 @@ public interface Convention extends DynamicObject {
      * @throws IllegalStateException When there there are multiple matching objects.
      */
     <T> T findPlugin(Class<T> type) throws IllegalStateException;
+
+    /**
+     * Returns a dynamic object which represents the properties and methods contributed by the extensions and convention objects contained in this
+     * convention.
+     *
+     * @return The dynamic object
+     */
+    DynamicObject getExtensionsAsDynamicObject();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionAware.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionAware.java
new file mode 100644
index 0000000..3d51df9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionAware.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.plugins;
+
+/**
+ * Objects that can be extended at runtime with other objects.
+ *
+ * <pre autoTested="">
+ * // Extensions are just plain objects, there is no interface/type
+ * class MyExtension {
+ *   String foo
+ *
+ *   MyExtension(String foo) {
+ *     this.foo = foo
+ *   }
+ * }
+ *
+ * // Add new extensions via the extension container
+ * project.extensions.create('custom', MyExtension, "bar")
+ * //                       («name»,   «type»,       «constructor args», …)
+ *
+ * // extensions appear as properties on the target object by the given name
+ * assert project.custom instanceof MyExtension
+ * assert project.custom.foo == "bar"
+ *
+ * // also via a namespace method
+ * project.custom {
+ *   assert foo == "bar"
+ *   foo = "other"
+ * }
+ * assert project.custom.foo == "other"
+ *
+ * // Extensions added with the extensnion container's create method are themselves extensible
+ * assert project.custom instanceof ExtensionAware
+ * project.custom.extensions.create("nested", MyExtension, "baz")
+ * assert project.custom.nested.foo == "baz"
+ *
+ * // All extension aware objects have a special “ext” extension of type ExtraPropertiesExtension
+ * assert project.hasProperty("myProperty") == false
+ * project.ext.myProperty = "myValue"
+ *
+ * // Properties added to the “ext” extension are promoted to the owning object
+ * assert project.myProperty == "myValue"
+ * </pre>
+ *
+ * Many Gradle objects are extension aware. This includes; projects, tasks, configurations, dependencies etc.
+ * <p>
+ * For more on adding & creating extensions, see {@link ExtensionContainer}.
+ * <p>
+ * For more on extra properties, see {@link ExtraPropertiesExtension}.
+ */
+public interface ExtensionAware {
+
+    /**
+     * The container of extensions.
+     */
+    ExtensionContainer getExtensions();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
new file mode 100644
index 0000000..7d345a2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.plugins;
+
+import org.gradle.api.UnknownDomainObjectException;
+
+/**
+ * Allows adding 'namespaced' DSL extensions to a target object.
+ */
+public interface ExtensionContainer {
+
+    /**
+     * Adding an extension of name 'foo' will:
+     * <li> add 'foo' dynamic property
+     * <li> add 'foo' dynamic method that accepts a closure that is a configuration script block
+     *
+     * @param name Will be used as a sort of namespace of properties/methods.
+     * @param extension Any object whose methods and properties will extend the target object
+     */
+    void add(String name, Object extension);
+
+    /**
+     * Deprecated. Use {@link #create}
+     *
+     * @param name The name for the extension
+     * @param type The type of the extension
+     * @param constructionArguments The arguments to be used to construct the extension instance
+     * @deprecated use {@link #create}
+     */
+    @Deprecated
+    void add(String name, Class<?> type, Object... constructionArguments);
+
+    /**
+     * Adds a new extension to this container, that itself is dynamically made {@link ExtensionAware}.
+     *
+     * A new instance of the given {@code type} will be created using the given {@code constructionArguments}. The new
+     * instance will have been dynamically which means that you can cast the object to {@link ExtensionAware}.
+     *
+     * @see #add(String, Object)
+     * @param name The name for the extension
+     * @param type The type of the extension
+     * @param constructionArguments The arguments to be used to construct the extension instance
+     * @return The created instance
+     */
+    <T> T create(String name, Class<T> type, Object... constructionArguments);
+
+    /**
+     * Looks for the extension of a given type (useful to avoid casting). If none found it will throw an exception.
+     *
+     * @param type extension type
+     * @return extension, never null
+     * @throws UnknownDomainObjectException When the given extension is not found.
+     */
+    <T> T getByType(Class<T> type) throws UnknownDomainObjectException;
+
+    /**
+     * Looks for the extension of a given type (useful to avoid casting). If none found null is returned.
+     *
+     * @param type extension type
+     * @return extension or null
+     */
+    <T> T findByType(Class<T> type);
+
+    /**
+     * Looks for the extension of a given name. If none found it will throw an exception.
+     *
+     * @param name extension name
+     * @return extension, never null
+     * @throws UnknownDomainObjectException When the given extension is not found.
+     */
+    Object getByName(String name) throws UnknownDomainObjectException;
+
+    /**
+     * Looks for the extension of a given name. If none found null is returned.
+     *
+     * @param name extension name
+     * @return extension or null
+     */
+    Object findByName(String name);
+
+    /**
+     * The extra properties extension in this extension container.
+     *
+     * This extension is always present in the container, with the name “ext”.
+     *
+     * @return The extra properties extension in this extension container.
+     */
+    ExtraPropertiesExtension getExtraProperties();
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtraPropertiesExtension.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtraPropertiesExtension.java
new file mode 100644
index 0000000..a7c58e2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtraPropertiesExtension.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2012 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.plugins;
+
+import org.gradle.api.InvalidUserDataException;
+
+import java.util.Map;
+
+/**
+ * Additional, ad-hoc, properties for Gradle domain objects.
+ * <p>
+ * Extra properties extensions allow new properties to be added to existing domain objects. They act like maps,
+ * allowing the storage of arbitrary key/value pairs. All {@link ExtensionAware} Gradle domain objects intrinsically have an extension
+ * named “{@value #EXTENSION_NAME}” of this type. 
+ * <p>
+ * An important feature of extra properties extensions is that all of its properties are exposed for reading and writing via the {@link ExtensionAware}
+ * object that owns the extension.
+ *
+ * <pre autoTested="">
+ * project.ext.set("myProp", "myValue")
+ * assert project.myProp == "myValue"
+ *
+ * project.myProp = "anotherValue"
+ * assert project.myProp == "anotherValue"
+ * assert project.ext.get("myProp") == "anotherValue"
+ * </pre>
+ *
+ * Extra properties extension objects support Groovy property syntax. That is, a property can be read via {@code extension.«name»} and set via
+ * {@code extension.«name» = "value"}. <b>Wherever possible, the Groovy property syntax should be preferred over the
+ * {@link #get(String)} and {@link #set(String, Object)} methods.</b>
+ *
+ * <pre autoTested="">
+ * project.ext {
+ *   myprop = "a"
+ * }
+ * assert project.myprop == "a"
+ * assert project.ext.myprop == "a"
+ *
+ * project.myprop = "b"
+ * assert project.myprop == "b"
+ * assert project.ext.myprop == "b"
+ * </pre>
+ *
+ * You can also use the Groovy accessor syntax to get and set properties on an extra properties extension.
+ *
+ * <pre autoTested="">
+ * project.ext["otherProp"] = "a"
+ * assert project.otherProp == "a"
+ * assert project.ext["otherProp"] == "a"
+ * </pre>
+ *
+ * The exception that is thrown when an attempt is made to get the value of a property that does not exist is different depending on whether the
+ * Groovy syntax is used or not. If Groovy property syntax is used, the Groovy {@link groovy.lang.MissingPropertyException} will be thrown.
+ * When the {@link #get(String)} method is used, an {@link UnknownPropertyException} will be thrown.
+ *
+ * <b>This mechanism replaces the ability to create new properties on domain objects directly.</b> In versions of Gradle prior to {@code 1.0-milestone-9},
+ * new properties could be added to objects by assignment:
+ *
+ * <pre>
+ * project.myProp = "myValue"
+ * </pre>
+ *
+ * This functionality has been <b>deprecated</b> (a deprecation warning message will be issued when this is attempted). Future versions of Gradle will
+ * remove this functionality completely, resulting in an exception being thrown.
+ */
+public interface ExtraPropertiesExtension {
+
+    /**
+     * The name of this extension in all {@link ExtensionContainer ExtensnionContainers}, {@value}.
+     */
+    public static final String EXTENSION_NAME = "ext";
+
+    /**
+     * Returns whether or not the extension has a property registered via the given name.
+     *
+     * <pre autoTested="">
+     * assert project.ext.has("foo") == false
+     * assert project.hasProperty("foo") == false
+     *
+     * project.ext.foo = "bar"
+     *
+     * assert project.ext.has("foo")
+     * assert project.hasProperty("foo")
+     * </pre>
+     *
+     * @param name The name of the property to check for
+     * @return {@code true} if a property has been registered with this name, otherwise {@code false}.
+     */
+    boolean has(String name);
+
+    /**
+     * Returns the value for the registered property with the given name.
+     *
+     * When using an extra properties extension from Groovy, you can also get properties via Groovy's property syntax.
+     * All of the following lines of code are equivalent.
+     *
+     * <pre autoTested="">
+     * project.ext { foo = "bar" }
+     *
+     * assert project.ext.get("foo") == "bar"
+     * assert project.ext.foo == "bar"
+     * assert project.ext["foo"] == "bar"
+     *
+     * assert project.foo == "bar"
+     * assert project["foo"] == "bar"
+     * </pre>
+     *
+     * When using the first form, an {@link UnknownPropertyException} exception will be thrown if the
+     * extension does not have a property called “{@code foo}”. When using the second forms (i.e. Groovy notation),
+     * Groovy's {@link groovy.lang.MissingPropertyException} will be thrown instead.
+     *
+     * @param name The name of the property to get the value of
+     * @return The value for the property with the given name.
+     * @throws UnknownPropertyException if there is no property registered with the given name
+     */
+    Object get(String name) throws UnknownPropertyException;
+
+    /**
+     * Updates the value for, or creates, the registered property with the given name to the given value.
+     *
+     * When using an extra properties extension from Groovy, you can also set properties via Groovy's property syntax.
+     * All of the following lines of code are equivalent.
+     *
+     * <pre autoTested="">
+     * project.ext.set("foo", "bar")
+     * project.ext.foo = "bar"
+     * project.ext["foo"] = "bar"
+     *
+     * // Once the property has been created via the extension, it can be changed by the owner.
+     * project.foo = "bar"
+     * project["foo"] = "bar"
+     * </pre>
+     *
+     * @param name The name of the property to update the value of or create
+     * @param value The value to set for the property
+     */
+    void set(String name, Object value);
+
+    /**
+     * Returns all of the registered properties and their current values as a map.
+     *
+     * The returned map is detached from the extension. That is, any changes made to the map do not
+     * change the extension from which it originated.
+     *
+     * <pre autoTested="true">
+     * project.version = "1.0"
+     *
+     * assert project.hasProperty("version")
+     * assert project.ext.properties.containsKey("version") == false
+     *
+     * project.ext.foo = "bar"
+     *
+     * assert project.ext.properties.containsKey("foo")
+     * assert project.ext.properties.foo == project.ext.foo
+     *
+     * assert project.ext.properties.every { key, value -> project.properties[key] == value }
+     * </pre>
+     *
+     * @return All of the registered properties and their current values as a map.
+     */
+    Map<String, Object> getProperties();
+
+    /**
+     * The exception that will be thrown when an attempt is made to read a property that is not set.
+     */
+    public static class UnknownPropertyException extends InvalidUserDataException {
+        public UnknownPropertyException(ExtraPropertiesExtension extension, String propertyName) {
+            super(String.format("cannot get property '%s' on extra properties extension as it does not exist", propertyName, extension));
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
index 0262dd3..1430c64 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.plugins;
 
-import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.DomainObjectSet;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.Action;
 import org.gradle.api.Plugin;
@@ -28,7 +28,7 @@ import groovy.lang.Closure;
  * @author Hans Dockter
  * @param <T> The type of plugins which this collection contains.
  */
-public interface PluginCollection<T extends Plugin> extends DomainObjectCollection<T> {
+public interface PluginCollection<T extends Plugin> extends DomainObjectSet<T> {
     /**
      * {@inheritDoc}
      */
@@ -50,6 +50,7 @@ public interface PluginCollection<T extends Plugin> extends DomainObjectCollecti
      * @param action The action to be executed
      * @return the supplied action
      */
+    @SuppressWarnings("UnusedDeclaration")
     Action<? super T> whenPluginAdded(Action<? super T> action);
 
     /**
@@ -58,25 +59,7 @@ public interface PluginCollection<T extends Plugin> extends DomainObjectCollecti
      *
      * @param closure The closure to be called
      */
+    @SuppressWarnings("UnusedDeclaration")
     void whenPluginAdded(Closure closure);
 
-    /**
-     * Executes the given action against all plugins in this collection, and any plugins subsequently added to this
-     * collection.
-     *
-     * @param action The action to be executed
-     * @deprecated Use {@link #all(org.gradle.api.Action)} instead.
-     */
-    @Deprecated
-    void allPlugins(Action<? super T> action);
-
-    /**
-     * Executes the given closure against all plugins in this collection, and any plugins subsequently added to this
-     * collection.
-     *
-     * @param closure The closure to be called
-     * @deprecated Use {@link #all(groovy.lang.Closure)} instead.
-     */
-    @Deprecated
-    void allPlugins(Closure closure);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/MissingResourceException.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/MissingResourceException.java
new file mode 100644
index 0000000..0850145
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/MissingResourceException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.resources;
+
+/**
+ * Exception thrown when the resource does not exist
+ */
+public class MissingResourceException extends ResourceException {
+    public MissingResourceException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/ReadableResource.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/ReadableResource.java
new file mode 100644
index 0000000..5fcdfc6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/ReadableResource.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.resources;
+
+import java.io.InputStream;
+
+/**
+ * A resource that can be read. The simplest example is a file.
+ */
+public interface ReadableResource extends Resource {
+
+    /**
+     * Returns an input stream that provides means to read the resource
+     *
+     * @return allows reading the resource
+     */
+    InputStream read() throws MissingResourceException, ResourceException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/Resource.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/Resource.java
new file mode 100644
index 0000000..517cbd4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/Resource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.resources;
+
+import java.net.URI;
+
+/**
+ * A generic resource of some kind. Only describes the resource.
+ * There are more specific interface that extend this one and specify ways of accessing the resource's content.
+ */
+public interface Resource {
+
+    /**
+     * Human readable name of this resource
+     *
+     * @return human readable name, should not be null
+     */
+    String getDisplayName();
+
+    /**
+     * Uniform resource identifier that uniquely describes this resource
+     *
+     * @return unique URI, should not be null
+     */
+    URI getURI();
+
+    /**
+     * Short name that concisely describes this resource
+     *
+     * @return concise base name, should not be null
+     */
+    String getBaseName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceException.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceException.java
new file mode 100644
index 0000000..0634540
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.resources;
+
+import org.gradle.api.GradleException;
+
+/**
+ * Generic resource exception that all other resource-related exceptions inherit from.
+ */
+public class ResourceException extends GradleException {
+
+    public ResourceException() {}
+
+    public ResourceException(String message) {
+        super(message);
+    }
+
+    public ResourceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java
new file mode 100644
index 0000000..3db24e9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.resources;
+
+/**
+ * Provides access to resource-specific utility methods, for example factory methods that create various resources.
+ */
+public interface ResourceHandler {
+
+    /**
+     * Creates resource that points to a gzip compressed file at the given path.
+     * The path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     *
+     * @param path The path evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     */
+    ReadableResource gzip(Object path);
+
+    /**
+     * Creates resource that points to a bzip2 compressed file at the given path.
+     * The path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     *
+     * @param path The path evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     */
+    ReadableResource bzip2(Object path);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/package-info.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/package-info.java
new file mode 100644
index 0000000..01deb65
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Interfaces and API for the 'Resources' concept.
+ */
+package org.gradle.api.resources;
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/AndSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/AndSpec.java
index b1745a8..2cfa857 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/AndSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/specs/AndSpec.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.specs;
 
+import com.google.common.collect.Iterables;
 import groovy.lang.Closure;
-import org.gradle.util.GUtil;
 
 import java.util.Arrays;
 
@@ -46,7 +46,7 @@ public class AndSpec<T> extends CompositeSpec<T> {
     }
 
     public AndSpec<T> and(Spec<? super T>... specs) {
-        return new AndSpec<T>(GUtil.addLists(getSpecs(), Arrays.asList(specs)));
+        return new AndSpec<T>(Iterables.concat(getSpecs(), Arrays.asList(specs)));
     }
 
     public AndSpec<T> and(Closure spec) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java
index a43ace9..4aff0ad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java
@@ -15,9 +15,8 @@
  */
 package org.gradle.api.specs;
 
-import org.gradle.util.GUtil;
+import com.google.common.collect.Lists;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -31,11 +30,11 @@ abstract public class CompositeSpec<T> implements Spec<T> {
     private List<Spec<? super T>> specs;
 
     protected CompositeSpec(Spec<? super T>... specs) {
-        this.specs = Arrays.asList(specs);
+        this.specs = Lists.newArrayList(specs);
     }
 
     protected CompositeSpec(Iterable<? extends Spec<? super T>> specs) {
-        this.specs = GUtil.addLists(specs);
+        this.specs = Lists.newArrayList(specs);
     }
 
     public List<Spec<? super T>> getSpecs() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java
index b349346..b7a2334 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api.specs;
 
+import org.gradle.util.DeprecationLogger;
+
 import groovy.lang.Closure;
 
 import java.util.LinkedHashSet;
@@ -27,26 +29,24 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public class Specs {
-    public static final Spec SATISFIES_ALL = new Spec() {
-        public boolean isSatisfiedBy(Object dependency) {
+    public static final Spec<Object> SATISFIES_ALL = new Spec<Object>() {
+        public boolean isSatisfiedBy(Object element) {
             return true;
         }
     };
 
     public static <T> Spec<T> satisfyAll() {
-        return new Spec<T>() {
-            public boolean isSatisfiedBy(T element) {
-                return true;
-            }
-        };
+        return (Spec<T>)SATISFIES_ALL;
     }
 
+    public static final Spec<Object> SATISFIES_NONE = new Spec<Object>() {
+        public boolean isSatisfiedBy(Object element) {
+            return false;
+        }
+    };
+    
     public static <T> Spec<T> satisfyNone() {
-        return new Spec<T>() {
-            public boolean isSatisfiedBy(T element) {
-                return false;
-            }
-        };
+        return (Spec<T>)SATISFIES_NONE;
     }
 
     public static <T> Spec<T> convertClosureToSpec(final Closure cl) {
@@ -59,6 +59,7 @@ public class Specs {
     }
 
     public static <T> Set<T> filterIterable(Iterable<? extends T> iterable, Spec<? super T> spec) {
+        DeprecationLogger.nagUserOfReplacedMethod("Specs.filterIterable", "CollectionUtils.filter");
         Set<T> result = new LinkedHashSet<T>();
         for (T t : iterable) {
             if (spec.isSatisfiedBy(t)) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
index 45ba32f..9692a69 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
@@ -23,6 +23,8 @@ import org.gradle.api.internal.file.copy.CopyActionImpl;
 import org.gradle.api.internal.file.copy.CopySpecSource;
 import org.gradle.api.internal.file.copy.ReadableCopySpec;
 import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factory;
+import org.gradle.util.DeprecationLogger;
 
 import java.io.FilterReader;
 import java.util.Map;
@@ -49,8 +51,14 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
             }
         }
     }
-    
+
+    /**
+     * Returns the default source for this task.
+     * @deprecated Use getSource() instead.
+     */
+    @Deprecated
     public FileCollection getDefaultSource() {
+        DeprecationLogger.nagUserOfReplacedMethod("AbstractCopyTask.getDefaultSource()", "getSource()");
         return null;
     }
 
@@ -60,7 +68,15 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
      */
     @InputFiles @SkipWhenEmpty @Optional
     public FileCollection getSource() {
-        return getCopyAction().hasSource() ? getCopyAction().getAllSource() : getDefaultSource();
+        if(getCopyAction().hasSource()){
+            return getCopyAction().getAllSource();
+        }else{
+            return DeprecationLogger.whileDisabled(new Factory<FileCollection>() {
+                public FileCollection create() {
+                    return getDefaultSource(); //To change body of implemented methods use File | Settings | File Templates.
+                }
+            });
+        }
     }
     
     protected abstract CopyActionImpl getCopyAction();
@@ -298,21 +314,21 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
     /**
      * {@inheritDoc}
      */
-    public int getDirMode() {
+    public Integer getDirMode() {
         return getMainSpec().getDirMode();
     }
 
     /**
      * {@inheritDoc}
      */
-    public int getFileMode() {
+    public Integer getFileMode() {
         return getMainSpec().getFileMode();
     }
 
     /**
      * {@inheritDoc}
      */
-    public AbstractCopyTask setDirMode(int mode) {
+    public AbstractCopyTask setDirMode(Integer mode) {
         getMainSpec().setDirMode(mode);
         return this;
     }
@@ -320,7 +336,7 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
     /**
      * {@inheritDoc}
      */
-    public AbstractCopyTask setFileMode(int mode) {
+    public AbstractCopyTask setFileMode(Integer mode) {
         getMainSpec().setFileMode(mode);
         return this;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
index c66efba..4db9eb5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2008 the original author or authors.
+ * Copyright 2012 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.
@@ -13,23 +13,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.gradle.api.tasks;
 
 import org.gradle.api.internal.IConventionAware;
 import org.gradle.api.plugins.Convention;
 
 /**
- * A ConventionValue can be assigned to a {@link org.gradle.api.internal.IConventionAware} task. If a property
- * of such an object is not set internally, a ConventionValue is used to calculate the value for the property.
+ * A ConventionValue can be assigned to a {@link org.gradle.api.internal.IConventionAware} task. If a property of such an object is not set internally, a ConventionValue is used to calculate the value
+ * for the property.
  *
  * @author Hans Dockter
+ * @deprecated Use {@link groovy.lang.Closure} or {@link java.util.concurrent.Callable} instead.
  */
+ at Deprecated
 public interface ConventionValue {
     /**
      * Returns some object.
-     * 
+     *
      * @param convention The convention object belonging to the task's project
-     * @param conventionAwareObject The convention aware object  
+     * @param conventionAwareObject The convention aware object
      */
     Object getValue(Convention convention, IConventionAware conventionAwareObject);
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java
index 40730a3..1cdf48b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Copy.java
@@ -1,99 +1,104 @@
-/*
- * 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 org.gradle.api.internal.file.*;
-import org.gradle.api.internal.file.copy.FileCopyActionImpl;
-import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
-
-import java.io.File;
-
-/**
- * Copies files into a destination directory.  This task can also rename and filter files as it copies. The task
- * implements {@link org.gradle.api.file.CopySpec CopySpec} for specifying what to copy.
- *
- * <p> Examples:
- * <pre>
- * task mydoc(type:Copy) {
- *    from 'src/main/doc'
- *    into 'build/target/doc'
- * }
- *
- * task initconfig(type:Copy) {
- *    from('src/main/config') {
- *       include '**/*.properties'
- *       include '**/*.xml'
- *       filter(ReplaceTokens, tokens:[version:'2.3.1'])
- *    }
- *    from('src/main/config') {
- *       exclude '**/*.properties', '**/*.xml'
- *    }
- *    from('src/main/languages') {
- *       rename 'EN_US_(*.)', '$1'
- *    }
- *    into 'build/target/config'
- *    exclude '**/*.bak'
- * }
- * </pre>
- *
- * @author Steve Appling
- */
-public class Copy extends AbstractCopyTask {
-    private FileCopyActionImpl copyAction;
-
-    public Copy() {
-        FileResolver fileResolver = getServices().get(FileResolver.class);
-        copyAction = new FileCopyActionImpl(fileResolver, new FileCopySpecVisitor());
-    }
-
-    protected void configureRootSpec() {
-        super.configureRootSpec();
-        if (getCopyAction().getDestinationDir() == null) {
-            File destDir = getDestinationDir();
-            if (destDir != null) {
-                into(destDir);
-            }
-        }
-    }
-
-    public FileCopyActionImpl getCopyAction() {
-        return copyAction;
-    }
-
-    public void setCopyAction(FileCopyActionImpl copyAction) {
-        this.copyAction = copyAction;
-    }
-
-    /**
-     * Returns the directory to copy files into.
-     *
-     * @return The destination dir.
-     */
-    @OutputDirectory
-    public File getDestinationDir() {
-        return getCopyAction().getDestinationDir();
-    }
-
-    /**
-     * Sets the directory to copy files into. This is the same as calling {@link #into(Object)} on this task.
-     *
-     * @param destinationDir The destination directory. Must not be null.
-     */
-    public void setDestinationDir(File destinationDir) {
-        into(destinationDir);
-    }
-}
+/*
+ * 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 org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.copy.FileCopyActionImpl;
+import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
+
+import java.io.File;
+
+/**
+ * Copies files into a destination directory.  This task can also rename and filter files as it copies. The task
+ * implements {@link org.gradle.api.file.CopySpec CopySpec} for specifying what to copy.
+ *
+ * <p> Examples:
+ * <pre autoTested=''>
+ * task mydoc(type:Copy) {
+ *    from 'src/main/doc'
+ *    into 'build/target/doc'
+ * }
+ *
+ * //for ant filter
+ * import org.apache.tools.ant.filters.ReplaceTokens
+ *
+ * task initconfig(type:Copy) {
+ *    from('src/main/config') {
+ *       include '**/*.properties'
+ *       include '**/*.xml'
+ *       filter(ReplaceTokens, tokens:[version:'2.3.1'])
+ *    }
+ *    from('src/main/config') {
+ *       exclude '**/*.properties', '**/*.xml'
+ *    }
+ *    from('src/main/languages') {
+ *       rename 'EN_US_(.*)', '$1'
+ *    }
+ *    into 'build/target/config'
+ *    exclude '**/*.bak'
+ *
+ *    includeEmptyDirs = false
+ * }
+ * </pre>
+ *
+ * @author Steve Appling
+ */
+public class Copy extends AbstractCopyTask {
+    private FileCopyActionImpl copyAction;
+
+    public Copy() {
+        FileResolver fileResolver = getServices().get(FileResolver.class);
+        copyAction = new FileCopyActionImpl(fileResolver, new FileCopySpecVisitor());
+    }
+
+    protected void configureRootSpec() {
+        super.configureRootSpec();
+        if (getCopyAction().getDestinationDir() == null) {
+            File destDir = getDestinationDir();
+            if (destDir != null) {
+                into(destDir);
+            }
+        }
+    }
+
+    public FileCopyActionImpl getCopyAction() {
+        return copyAction;
+    }
+
+    public void setCopyAction(FileCopyActionImpl copyAction) {
+        this.copyAction = copyAction;
+    }
+
+    /**
+     * Returns the directory to copy files into.
+     *
+     * @return The destination dir.
+     */
+    @OutputDirectory
+    public File getDestinationDir() {
+        return getCopyAction().getDestinationDir();
+    }
+
+    /**
+     * Sets the directory to copy files into. This is the same as calling {@link #into(Object)} on this task.
+     *
+     * @param destinationDir The destination directory. Must not be null.
+     */
+    public void setDestinationDir(File destinationDir) {
+        into(destinationDir);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
index 78cc54b..dc9a040 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
@@ -18,16 +18,18 @@ package org.gradle.api.tasks
 
 import org.gradle.api.DefaultTask
 import org.gradle.api.InvalidUserDataException
+import org.gradle.util.DeprecationLogger
 
 /**
  * Creates a directory.
- *
- * @author Hans Dockter
+ * @deprecated Use {@link org.gradle.api.Project#mkdir(java.lang.Object)} to create directories in Gradle.
  */
+ at Deprecated
 public class Directory extends DefaultTask {
     File dir
     
     Directory() {
+        DeprecationLogger.nagUserOfReplacedTask("Directory", "Project.mkdir(java.lang.Object)");
         if (new File(name).isAbsolute()) { throw new InvalidUserDataException('Path must not be absolute.')}
         dir = project.file(name)
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Exec.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Exec.java
index d8637dd6..4d95679 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Exec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Exec.java
@@ -33,13 +33,13 @@ import java.util.Map;
  * Executes a command line process. Example:
  * <pre autoTested=''>
  * task stopTomcat(type:Exec) {
- *   workingDir = file('../tomcat/bin')
+ *   workingDir '../tomcat/bin'
  *
  *   //on windows:
- *   commandLine = ['cmd', '/c', 'stop.bat']
+ *   commandLine 'cmd', '/c', 'stop.bat'
  *
  *   //on linux (oh yeah!!!)
- *   commandLine = ['./stop.sh']
+ *   commandLine './stop.sh'
  * }
  * </pre>
  * 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Input.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Input.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
index 63f8fb3..5552289 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
@@ -19,7 +19,6 @@ package org.gradle.api.tasks;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.specs.Specs;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.process.JavaForkOptions;
 import org.gradle.process.ProcessForkOptions;
@@ -43,7 +42,6 @@ public class JavaExec extends ConventionTask implements JavaExecSpec {
     public JavaExec() {
         FileResolver fileResolver = getServices().get(FileResolver.class);
         javaExecHandleBuilder = new DefaultJavaExecAction(fileResolver);
-        getOutputs().upToDateWhen(Specs.<Object>satisfyNone());
     }
 
     @TaskAction
@@ -152,6 +150,34 @@ public class JavaExec extends ConventionTask implements JavaExecSpec {
     /**
      * {@inheritDoc}
      */
+    public String getMinHeapSize() {
+        return javaExecHandleBuilder.getMinHeapSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setMinHeapSize(String heapSize) {
+        javaExecHandleBuilder.setMinHeapSize(heapSize);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getDefaultCharacterEncoding() {
+        return javaExecHandleBuilder.getDefaultCharacterEncoding();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        javaExecHandleBuilder.setDefaultCharacterEncoding(defaultCharacterEncoding);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public String getMaxHeapSize() {
         return javaExecHandleBuilder.getMaxHeapSize();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java
index d2fbe7c..b670e36 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java
@@ -39,6 +39,6 @@ import java.lang.annotation.Target;
  * <li>{@link org.gradle.api.tasks.OutputDirectory}</li> </ul>
  */
 @Retention(RetentionPolicy.RUNTIME)
- at Target({ElementType.METHOD, ElementType.FIELD})
+ at Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
 public @interface Optional {
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java
new file mode 100644
index 0000000..96ac77a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java
@@ -0,0 +1,32 @@
+/*
+ * 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.tasks;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Marks a property as specifying one or more output directories for a task.</p>
+ *
+ * <p>This annotation should be attached to the getter method or the field for the property.</p>
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface OutputDirectories {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java
new file mode 100644
index 0000000..3a3fcb9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java
@@ -0,0 +1,32 @@
+/*
+ * 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.tasks;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Marks a property as specifying one or more output files for a task.</p>
+ *
+ * <p>This annotation should be attached to the getter method or the field for the property.</p>
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface OutputFiles {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
index 024aa0f..2fe35aa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
@@ -16,19 +16,20 @@
 
 package org.gradle.api.tasks;
 
+import groovy.lang.Closure;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
-import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factory;
+import org.gradle.util.DeprecationLogger;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
-import groovy.lang.Closure;
-
 /**
  * A {@code SourceTask} performs some operation on source files.
  */
@@ -37,15 +38,23 @@ public class SourceTask extends ConventionTask implements PatternFilterable {
     private final PatternFilterable patternSet = new PatternSet();
 
     /**
-     * Returns the source for this task, after the include and exclude patterns have been applied. Ignores source files
-     * which do not exist.
+     * Returns the source for this task, after the include and exclude patterns have been applied. Ignores source files which do not exist.
      *
      * @return The source.
      */
     @InputFiles
     @SkipWhenEmpty
     public FileTree getSource() {
-        FileTree src = this.source.isEmpty() ? getDefaultSource() : getProject().files(new ArrayList<Object>(this.source)).getAsFileTree();
+        FileTree src;
+        if (this.source.isEmpty()) {
+            src = DeprecationLogger.whileDisabled(new Factory<FileTree>() {
+                public FileTree create() {
+                    return getDefaultSource();
+                }
+            });
+        } else {
+            src = getProject().files(new ArrayList<Object>(this.source)).getAsFileTree();
+        }
         return src == null ? getProject().files().getAsFileTree() : src.matching(patternSet);
     }
 
@@ -53,14 +62,16 @@ public class SourceTask extends ConventionTask implements PatternFilterable {
      * Returns the default source for this task, if any.
      *
      * @return The source. May return null.
+     * @deprecated Use getSource() instead.
      */
+    @Deprecated
     protected FileTree getDefaultSource() {
+        DeprecationLogger.nagUserOfReplacedMethod("SourceTask.getDefaultSource()", "getSource()");
         return null;
     }
 
     /**
-     * Sets the source for this task. The given source object is evaluated as for {@link
-     * org.gradle.api.Project#files(Object...)}.
+     * Sets the source for this task. The given source object is evaluated as for {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param source The source.
      */
@@ -70,8 +81,7 @@ public class SourceTask extends ConventionTask implements PatternFilterable {
     }
 
     /**
-     * Adds some source to this task. The given source objects will be evaluated as for {@link
-     * org.gradle.api.Project#files(Object...)}.
+     * Adds some source to this task. The given source objects will be evaluated as for {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param sources The source to add
      * @return this
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java
index 5c30130..e42cc90 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java
@@ -16,7 +16,10 @@
 package org.gradle.api.tasks;
 
 import groovy.lang.Closure;
-import org.gradle.api.*;
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownTaskException;
 import org.gradle.api.specs.Spec;
 
 /**
@@ -24,7 +27,7 @@ import org.gradle.api.specs.Spec;
  *
  * @param <T> The type of tasks which this collection contains.
  */
-public interface TaskCollection<T extends Task> extends NamedDomainObjectCollection<T> {
+public interface TaskCollection<T extends Task> extends NamedDomainObjectSet<T> {
 
     /**
      * {@inheritDoc}
@@ -57,6 +60,7 @@ public interface TaskCollection<T extends Task> extends NamedDomainObjectCollect
      * @param action The action to be executed
      * @return the supplied action
      */
+    @SuppressWarnings("UnusedDeclaration")
     Action<? super T> whenTaskAdded(Action<? super T> action);
 
     /**
@@ -65,29 +69,10 @@ public interface TaskCollection<T extends Task> extends NamedDomainObjectCollect
      *
      * @param closure The closure to be called
      */
+    @SuppressWarnings("UnusedDeclaration")
     void whenTaskAdded(Closure closure);
 
     /**
-     * Executes the given action against all tasks in this collection, and any tasks subsequently added to this
-     * collection.
-     *
-     * @param action The action to be executed
-     * @deprecated Use {@link #all(org.gradle.api.Action)} instead.
-     */
-    @Deprecated
-    void allTasks(Action<? super T> action);
-
-    /**
-     * Executes the given closure against all tasks in this collection, and any tasks subsequently added to this
-     * collection.
-     *
-     * @param closure The closure to be called
-     * @deprecated Use {@link #all(groovy.lang.Closure)} instead.
-     */
-    @Deprecated
-    void allTasks(Closure closure);
-
-    /**
      * {@inheritDoc}
      */
     T getAt(String name) throws UnknownTaskException;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
index 8bf94d7..7250e27 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
@@ -102,7 +102,7 @@ public interface TaskInputs {
     boolean getHasSourceFiles();
 
     /**
-     * Returns the set of source files for this task. These are the subset of input files which the task does work on.
+     * Returns the set of source files for this task. These are the subset of input files which the task actually does work on.
      * A task is skipped if it has declared it accepts source files, and this collection is empty.
      *
      * @return The set of source files for this task.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java
old mode 100644
new mode 100755
index f4a86d2..6ab3026
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java
@@ -1,27 +1,31 @@
-/*
- * 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.tasks;
-
-import org.gradle.api.GradleException;
-
-/**
- * A {@code TaskInstantiationException} is thrown when a task cannot be instantiated for some reason.
- */
-public class TaskInstantiationException extends GradleException {
-    public TaskInstantiationException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
+/*
+ * 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.tasks;
+
+import org.gradle.api.GradleException;
+
+/**
+ * A {@code TaskInstantiationException} is thrown when a task cannot be instantiated for some reason.
+ */
+public class TaskInstantiationException extends GradleException {
+    public TaskInstantiationException(String message) {
+        this(message, null);
+    }
+
+    public TaskInstantiationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
index 912bf11..9e37b65 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
@@ -21,6 +21,9 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.util.ConfigureUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,14 +49,18 @@ public class Upload extends ConventionTask {
      */
     private RepositoryHandler repositories;
 
+    private ArtifactPublisher artifactPublisher;
+
     public Upload() {
-        repositories = getProject().createRepositoryHandler();
+        ArtifactPublicationServices publicationServices = getServices().getFactory(ArtifactPublicationServices.class).create();
+        repositories = publicationServices.getRepositoryHandler();
+        artifactPublisher = publicationServices.getArtifactPublisher();
     }
 
     @TaskAction
     protected void upload() {
-        logger.info("Publishing configurations: " + configuration);
-        configuration.publish(repositories.getResolvers(), isUploadDescriptor() ? getDescriptorDestination() : null);
+        logger.info("Publishing configuration: " + configuration);
+        artifactPublisher.publish((ConfigurationInternal) configuration, isUploadDescriptor() ? getDescriptorDestination() : null);
     }
 
     /**
@@ -85,10 +92,6 @@ public class Upload extends ConventionTask {
         return repositories;
     }
 
-    public void setRepositories(RepositoryHandler repositories) {
-        this.repositories = repositories;
-    }
-
     /**
      * Returns the configuration to upload.
      */
@@ -115,6 +118,14 @@ public class Upload extends ConventionTask {
     @InputFiles
     public FileCollection getArtifacts() {
         Configuration configuration = getConfiguration();
-        return configuration == null ? null : configuration.getAllArtifactFiles();
+        return configuration == null ? null : configuration.getAllArtifacts().getFiles();
+    }
+
+    void setRepositories(RepositoryHandler repositories) {
+        this.repositories = repositories;
+    }
+
+    void setArtifactPublisher(ArtifactPublisher artifactPublisher) {
+        this.artifactPublisher = artifactPublisher;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java
old mode 100644
new mode 100755
index 99b57ab..b0ecaa7
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java
@@ -1,36 +1,35 @@
-/*
- * 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.tasks;
-
-/**
- * A {@code VerificationTask} is a task which performs some verification of the artifacts produced by a build.
- */
-public interface VerificationTask {
-    /**
-     * Specifies whether the build should break when the verifications performed by this task fail.
-     *
-     * @param ignoreFailures false to break the build on failure, true to ignore the failures. The default is false.
-     * @return this
-     */
-    VerificationTask setIgnoreFailures(boolean ignoreFailures);
-
-    /**
-     * Specifies whether the build should break when the verifications performed by this task fail.
-     *
-     * @return false, when the build should break on failure, true when the failures should be ignored.
-     */
-    boolean isIgnoreFailures();
-}
+/*
+ * 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.tasks;
+
+/**
+ * A {@code VerificationTask} is a task which performs some verification of the artifacts produced by a build.
+ */
+public interface VerificationTask {
+    /**
+     * Specifies whether the build should break when the verifications performed by this task fail.
+     *
+     * @param ignoreFailures false to break the build on failure, true to ignore the failures. The default is false.
+     */
+    void setIgnoreFailures(boolean ignoreFailures);
+
+    /**
+     * Specifies whether the build should break when the verifications performed by this task fail.
+     *
+     * @return false, when the build should break on failure, true when the failures should be ignored.
+     */
+    boolean getIgnoreFailures();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
index d0e516a..877784b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.tasks.bundling;
 
+import groovy.lang.Closure;
 import org.gradle.api.tasks.AbstractCopyTask;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.util.GUtil;
@@ -157,4 +158,31 @@ public abstract class AbstractArchiveTask extends AbstractCopyTask {
     public void setClassifier(String classifier) {
         this.classifier = classifier;
     }
+
+    /**
+     * Specifies the destination directory *inside* the archive for the files.
+     * The destination is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * Don't mix it up with {@link #getDestinationDir()} which specifies the output directory for the archive.
+     *
+     * @param destPath destination directory *inside* the archive for the files
+     * @return this
+     */
+    public AbstractArchiveTask into(Object destPath) {
+        super.into(destPath);
+        return this;
+    }
+
+    /**
+     * Creates and configures a child {@code CopySpec} with a destination directory *inside* the archive for the files.
+     * The destination is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * Don't mix it up with {@link #getDestinationDir()} which specifies the output directory for the archive.
+     *
+     * @param destPath destination directory *inside* the archive for the files
+     * @param configureClosure The closure to use to configure the child {@code CopySpec}.
+     * @return this
+     */
+    public AbstractArchiveTask into(Object destPath, Closure configureClosure) {
+        super.into(destPath, configureClosure);
+        return this;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
index 629322e..f8b04db 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
@@ -15,6 +15,12 @@
  */
 package org.gradle.api.tasks.bundling;
 
+import org.gradle.util.DeprecationLogger;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Specifies the compression which should be applied to a TAR archive.
  * 
@@ -22,16 +28,32 @@ package org.gradle.api.tasks.bundling;
  */
 public enum Compression {
     NONE("tar"),
-    GZIP("tgz"),
-    BZIP2("tbz2");
+    GZIP("tgz", "gz"),
+    BZIP2("tbz2", "bz2");
 
-    private final String extension;
+    private final String defaultExtension;
+    private final List<String> supportedExtensions = new ArrayList<String>(2);
 
-    private Compression(String extension) {
-        this.extension = extension;
+    private Compression(String defaultExtension, String... additionalSupportedExtensions) {
+        this.defaultExtension = defaultExtension;
+        this.supportedExtensions.addAll(Arrays.asList(additionalSupportedExtensions));
+        this.supportedExtensions.add(defaultExtension);
     }
 
+    /**
+     * <p>Returns the file extension of the type of Compression.</p>
+     * @deprecated Use {@link #getDefaultExtension()} instead.
+     */
     public String getExtension() {
-        return extension;
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Compression.getExtension()");
+        return defaultExtension;
+    }
+
+    public String getDefaultExtension(){
+        return defaultExtension;
+    }
+
+    public List<String> getSupportedExtensions(){
+        return supportedExtensions;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
index 87610dd..e65b9cd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
@@ -16,9 +16,13 @@
 
 package org.gradle.api.tasks.bundling;
 
-import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.archive.TarCopyAction;
 import org.gradle.api.internal.file.archive.TarCopySpecVisitor;
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.compression.Compressor;
+import org.gradle.api.internal.file.archive.compression.GzipArchiver;
+import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
 import org.gradle.api.internal.file.copy.CopyActionImpl;
 
 import java.io.File;
@@ -31,15 +35,14 @@ import java.util.concurrent.Callable;
  */
 public class Tar extends AbstractArchiveTask {
     private final CopyActionImpl action;
-
     private Compression compression;
 
     public Tar() {
-        compression = Compression.NONE;
+        setCompression(Compression.NONE);
         action = new TarCopyActionImpl(getServices().get(FileResolver.class));
         getConventionMapping().map("extension", new Callable<Object>(){
             public Object call() throws Exception {
-                return getCompression().getExtension();
+                return getCompression().getDefaultExtension();
             }
         });
     }
@@ -49,7 +52,8 @@ public class Tar extends AbstractArchiveTask {
     }
 
     /**
-     * Returns the compression to use for this archive.
+     * Returns the compression that is used for this archive.
+     *
      * @return The compression. Never returns null.
      */
     public Compression getCompression() {
@@ -57,7 +61,8 @@ public class Tar extends AbstractArchiveTask {
     }
 
     /**
-     * Specifies the compression to use for this archive.
+     * Configures the compressor based on passed in compression.
+     *
      * @param compression The compression. Should not be null.
      */
     public void setCompression(Compression compression) {
@@ -73,8 +78,12 @@ public class Tar extends AbstractArchiveTask {
             return Tar.this.getArchivePath();
         }
 
-        public Compression getCompression() {
-            return Tar.this.getCompression();
+        public Compressor getCompressor() {
+            switch(compression) {
+                case BZIP2: return Bzip2Archiver.getCompressor();
+                case GZIP:  return GzipArchiver.getCompressor();
+                default:    return new SimpleCompressor();
+            }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
index 6cfab91..10af158 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
@@ -66,7 +66,7 @@ public class DependencyReportTask extends AbstractReportTask {
     }
 
     private Set<Configuration> getConfigurations(Project project) {
-        return configurations != null ? configurations : project.getConfigurations().getAll();
+        return configurations != null ? configurations : project.getConfigurations();
     }
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
index b0fb3e2..8fa2c2c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
@@ -15,12 +15,12 @@
  */
 package org.gradle.api.tasks.diagnostics;
 
+import com.google.common.collect.Sets;
 import org.gradle.api.Project;
 import org.gradle.api.Rule;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.CommandLineOption;
 import org.gradle.api.tasks.diagnostics.internal.*;
-import org.gradle.util.GUtil;
 
 import java.io.IOException;
 
@@ -59,12 +59,12 @@ public class TaskReportTask extends AbstractReportTask {
 
         SingleProjectTaskReportModel projectTaskModel = new SingleProjectTaskReportModel(taskDetailsFactory);
         ProjectInternal projectInternal = (ProjectInternal) project;
-        projectTaskModel.build(GUtil.addSets(projectInternal.getTasks(), projectInternal.getImplicitTasks()));
+        projectTaskModel.build(Sets.union(projectInternal.getTasks(), projectInternal.getImplicitTasks()));
         aggregateModel.add(projectTaskModel);
 
         for (Project subprojects : project.getSubprojects()) {
             SingleProjectTaskReportModel subprojectTaskModel = new SingleProjectTaskReportModel(taskDetailsFactory);
-            subprojectTaskModel.build(subprojects.getTasks().getAll());
+            subprojectTaskModel.build(subprojects.getTasks());
             aggregateModel.add(subprojectTaskModel);
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRenderer.java
index d2e29b7..26a2018 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRenderer.java
@@ -35,12 +35,14 @@ import static org.gradle.logging.StyledTextOutput.Style.*;
  */
 public class AsciiReportRenderer extends TextReportRenderer implements DependencyReportRenderer {
     private boolean hasConfigs;
+    private boolean hasCyclicDependencies;
     private GraphRenderer renderer;
 
     @Override
     public void startProject(Project project) {
         super.startProject(project);
         hasConfigs = false;
+        hasCyclicDependencies = false;
     }
 
     @Override
@@ -79,25 +81,46 @@ public class AsciiReportRenderer extends TextReportRenderer implements Dependenc
             getTextOutput().println();
             return;
         }
-        renderChildren(mergedRoots);
+        renderChildren(mergedRoots, new HashSet<String>());
     }
 
-    private void render(final MergedResolvedDependency resolvedDependency, boolean lastChild) {
+    public void complete() throws IOException {
+        if (hasCyclicDependencies) {
+            getTextOutput().withStyle(Info).println("\n(*) - dependencies omitted (listed previously)");
+        }
+        
+        super.complete();
+    }
+    
+    private void render(final MergedResolvedDependency resolvedDependency, Set<String> visitedDependencyNames, boolean lastChild) {
+        final boolean isFirstVisitOfDependencyInConfiguration = visitedDependencyNames.add(resolvedDependency.getName());
+        if (!isFirstVisitOfDependencyInConfiguration) {
+            hasCyclicDependencies = true;
+        }
+
         renderer.visit(new Action<StyledTextOutput>() {
             public void execute(StyledTextOutput styledTextOutput) {
                 getTextOutput().text(resolvedDependency.getName());
-                getTextOutput().withStyle(Info).format(" [%s]", resolvedDependency.getConfiguration());
+                StyledTextOutput infoStyle = getTextOutput().withStyle(Info);
+                infoStyle.format(" [%s]", resolvedDependency.getConfiguration());
+
+                if (!isFirstVisitOfDependencyInConfiguration) {
+                    infoStyle.append(" (*)");
+                }
             }
         }, lastChild);
-        renderChildren(mergeChildren(resolvedDependency.getChildren()));
+
+        if (isFirstVisitOfDependencyInConfiguration) {
+            renderChildren(mergeChildren(resolvedDependency.getChildren()), visitedDependencyNames);
+        }
     }
 
-    private void renderChildren(Set<MergedResolvedDependency> children) {
+    private void renderChildren(Set<MergedResolvedDependency> children, Set<String> visitedDependencyNames) {
         renderer.startChildren();
         List<MergedResolvedDependency> mergedChildren = new ArrayList<MergedResolvedDependency>(children);
         for (int i = 0; i < mergedChildren.size(); i++) {
             MergedResolvedDependency dependency = mergedChildren.get(i);
-            render(dependency, i == mergedChildren.size() - 1);
+            render(dependency, visitedDependencyNames, i == mergedChildren.size() - 1);
         }
         renderer.completeChildren();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java
index 03affcf..5bc8491 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/SingleProjectTaskReportModel.java
@@ -51,7 +51,7 @@ public class SingleProjectTaskReportModel implements TaskReportModel {
         GraphAggregator<Task> aggregator = new GraphAggregator<Task>(new DirectedGraph<Task, Object>() {
             public void getNodeValues(Task node, Collection<Object> values, Collection<Task> connectedNodes) {
                 for (Task dep : node.getTaskDependencies().getDependencies(node)) {
-                    if (tasks.contains(dep)) {
+                    if (containsTaskWithPath(tasks, dep.getPath())) {
                         connectedNodes.add(dep);
                     }
                 }
@@ -69,7 +69,7 @@ public class SingleProjectTaskReportModel implements TaskReportModel {
                             Collections.<TaskDetails>emptySet()));
                 }
                 for (Task dep : node.getTaskDependencies().getDependencies(node)) {
-                    if (topLevelTasks.contains(dep) || !tasks.contains(dep)) {
+                    if (topLevelTasks.contains(dep) || !containsTaskWithPath(tasks, dep.getPath())) {
                         dependencies.add(factory.create(dep));
                     }
                 }
@@ -80,6 +80,15 @@ public class SingleProjectTaskReportModel implements TaskReportModel {
         }
     }
 
+    private boolean containsTaskWithPath(Collection<? extends Task> tasks, String path) {
+        for (Task task : tasks) {
+            if (task.getPath().equals(path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
     public Set<String> getGroups() {
         return groups.keySet();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java
index 770e3d1..2df766e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRenderer.java
@@ -49,6 +49,12 @@ public class TaskReportRenderer extends TextReportRenderer {
         super.startProject(project);
     }
 
+    @Override
+    protected String createHeader(Project project) {
+        String header = super.createHeader(project);
+        return "All tasks runnable from " + StringUtils.uncapitalize(header);
+    }
+
     public void showDetail(boolean detail) {
         this.detail = detail;
     }
@@ -142,7 +148,7 @@ public class TaskReportRenderer extends TextReportRenderer {
     public void complete() throws IOException {
         if (!detail) {
             getTextOutput().println();
-            getTextOutput().text("To see all tasks and more detail, run with ").style(UserInput).text("--all").style(Normal).text(".");
+            getTextOutput().text("To see all tasks and more detail, run with ").style(UserInput).text("--all.");
             getTextOutput().println();
         }
         super.complete();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java
index 45e9149..ceef522 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRenderer.java
@@ -43,16 +43,21 @@ public class TextReportRenderer implements ReportRenderer {
     }
 
     public void startProject(Project project) {
+        String header = createHeader(project);
+        writeHeading(header);
+    }
+
+    protected String createHeader(Project project) {
         String header;
         if (project.getRootProject() == project) {
-            header = "Root Project";
+            header = "Root project";
         } else {
             header = String.format("Project %s", project.getPath());
         }
         if (GUtil.isTrue(project.getDescription())) {
             header = header + " - " + project.getDescription();
         }
-        writeHeading(header);
+        return header;
     }
 
     public void completeProject(Project project) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/AutoCloseCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/AutoCloseCacheFactory.java
deleted file mode 100644
index 576ad67..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/AutoCloseCacheFactory.java
+++ /dev/null
@@ -1,91 +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.cache;
-
-import org.gradle.CacheUsage;
-import org.gradle.util.GFileUtils;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-public class AutoCloseCacheFactory implements CacheFactory {
-    private final CacheFactory cacheFactory;
-    private final Map<File, CacheInfo> openCaches = new HashMap<File, CacheInfo>();
-
-    public AutoCloseCacheFactory(CacheFactory cacheFactory) {
-        this.cacheFactory = cacheFactory;
-    }
-
-    public PersistentCache open(File cacheDir, CacheUsage usage, Map<String, ?> properties) {
-        File canonicalDir = GFileUtils.canonicalise(cacheDir);
-        CacheInfo cacheInfo = openCaches.get(canonicalDir);
-        if (cacheInfo == null) {
-            PersistentCache cache = cacheFactory.open(cacheDir, usage, properties);
-            cacheInfo = new CacheInfo(cache, properties);
-            openCaches.put(canonicalDir, cacheInfo);
-        } else {
-            if (!properties.equals(cacheInfo.properties)) {
-                throw new UnsupportedOperationException(String.format("Cache '%s' is already open with different state.", cacheDir));
-            }
-        }
-        cacheInfo.addReference();
-        return cacheInfo.cache;
-    }
-
-    public void close(PersistentCache cache) {
-        for (CacheInfo cacheInfo : openCaches.values()) {
-            if (cacheInfo.cache == cache) {
-                if (cacheInfo.removeReference()) {
-                    openCaches.values().remove(cacheInfo);
-                    cacheFactory.close(cacheInfo.cache);
-                }
-                return;
-            }
-        }
-        throw new IllegalArgumentException("Attempting to close unknown cache " + cache);
-    }
-
-    public void close() {
-        try {
-            for (CacheInfo cacheInfo : openCaches.values()) {
-                cacheFactory.close(cacheInfo.cache);
-            }
-        } finally {
-            openCaches.clear();
-        }
-    }
-
-    private static class CacheInfo {
-        int count;
-        final Map<String, ?> properties;
-        final PersistentCache cache;
-
-        private CacheInfo(PersistentCache cache, Map<String, ?> properties) {
-            this.cache = cache;
-            this.properties = new HashMap<String, Object>(properties);
-        }
-
-        public void addReference() {
-            count++;
-        }
-
-        public boolean removeReference() {
-            count--;
-            return count == 0;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheAccess.java
new file mode 100644
index 0000000..d6225e2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheAccess.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.internal.Factory;
+
+/**
+ * Provides synchronised access to a cache.
+ */
+public interface CacheAccess {
+    /**
+     * Performs some work against the cache. Acquires exclusive locks the appropriate resources, so that the given action is the only
+     * action to execute across all processes (including this one). Releases the locks and all resources at the end of the action.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    <T> T useCache(String operationDisplayName, Factory<? extends T> action);
+
+    /**
+     * Performs some work against the cache. Acquires exclusive locks the appropriate resources, so that the given action is the only
+     * action to execute across all processes (including this one). Releases the locks and all resources at the end of the action.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    void useCache(String operationDisplayName, Runnable action);
+
+    /**
+     * Performs some long running operation within an action invoked by {@link #useCache(String, org.gradle.internal.Factory)}. Releases all
+     * locks while the operation is running, and reacquires the locks at the end of the long running operation.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action);
+
+    /**
+     * Performs some long running operation within an action invoked by {@link #useCache(String, org.gradle.internal.Factory)}. Releases all
+     * locks while the operation is running, and reacquires the locks at the end of the long running operation.
+     *
+     * <p>This method is re-entrant, so that an action can call back into this method.</p>
+     */
+    void longRunningOperation(String operationDisplayName, Runnable action);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java
index 55e1478..d484ac8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheBuilder.java
@@ -18,34 +18,60 @@ package org.gradle.cache;
 
 import java.util.Map;
 
-public interface CacheBuilder {
+public interface CacheBuilder<T> {
+    enum VersionStrategy {
+        /**
+         * A separate cache instance for each Gradle version. This is the default.
+         */
+        CachePerVersion,
+        /**
+         * A single cache instance shared by all Gradle versions. It is the caller's responsibility to make sure that this is shared only with
+         * those versions of Gradle that are compatible with the cache implementation and contents.
+         */
+        SharedCache,
+        /**
+         * A single cache instance, which is invalidated when the Gradle version changes.
+         */
+        SharedCacheInvalidateOnVersionChange,
+    }
+
     /**
-     * Specifies the additional key properties for the cache. The cache is treated as invalid if any of the properties
-     * do not match the properties used to create the cache. The default for this is an empty map.
+     * Specifies the additional key properties for the cache. The cache is treated as invalid if any of the properties do not match the properties used to create the cache. The default for this is an
+     * empty map.
      *
      * @param properties additional properties for the cache.
      * @return this
      */
-    CacheBuilder withProperties(Map<String, ?> properties);
+    CacheBuilder<T> withProperties(Map<String, ?> properties);
 
     /**
-     * Specifies the target domain object.  This might be a task, project, or similar. The cache is scoped for the given
-     * target object. The default is to use a globally-scoped cache.
+     * Specifies the target domain object.  This might be a task, project, or similar. The cache is scoped for the given target object. The default is to use a globally-scoped cache.
      *
      * @param target The target domain object which the cache is for.
      * @return this
      */
-    CacheBuilder forObject(Object target);
+    CacheBuilder<T> forObject(Object target);
 
     /**
-     * Invalidates this cache on Gradle version change. The default is to maintain a separate cache for each version.
+     * Specifies the versioning strategy for this cache. The default is {@link VersionStrategy#CachePerVersion}.
      *
+     * @param strategy The strategy
      * @return this
      */
-    CacheBuilder invalidateOnVersionChange();
+    CacheBuilder<T> withVersionStrategy(VersionStrategy strategy);
 
     /**
-     * Creates the cache.
+     * Specifies a cache validator for this cache. If {@link CacheValidator#isValid()} results in false, the Cache is considered as invalid.
+     *
+     * @param validator The validator
+     * @return this
+     */
+    CacheBuilder<T> withValidator(CacheValidator validator);
+
+    /**
+     * Opens the cache.
+     *
+     * @return The cache.
      */
-    PersistentCache open();
+    T open() throws CacheOpenException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheFactory.java
deleted file mode 100644
index 4ab018d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheFactory.java
+++ /dev/null
@@ -1,31 +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.cache;
-
-import org.gradle.CacheUsage;
-
-import java.io.File;
-import java.util.Map;
-
-public interface CacheFactory {
-    PersistentCache open(File cacheDir, CacheUsage usage, Map<String, ?> properties);
-
-    void close(PersistentCache cache);
-}
-
-
-
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java
new file mode 100644
index 0000000..16014b1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheOpenException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.Contextual;
+
+ at Contextual
+public class CacheOpenException extends GradleException {
+    public CacheOpenException(String message) {
+        super(message);
+    }
+
+    public CacheOpenException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java
index d3f1463..a9977a4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheRepository.java
@@ -15,13 +15,71 @@
  */
 package org.gradle.cache;
 
+/**
+ * A repository of persistent caches and stores. A <em>store</em> is a store for persistent data. A <em>cache</em> is a store for persistent
+ * cache data. The only real difference between the two is that a store cannot be invalidated, whereas a cache can be invalidated when things
+ * change. For example, running with {@code --cache rebuild} will invalidate the contents of all caches, but not the contents of any stores.
+ *
+ * <p>There are 3 types of caches and stores:
+ *
+ * <ul>
+ *
+ * <li>A directory backed store, represented by {@link PersistentCache}. The caller is responsible for managing the contents of this directory.</li>
+ *
+ * <li>An indexed store, essentially a persistent {@link java.util.Map}, represented by {@link PersistentIndexedCache}.</li>
+ *
+ * <li>A state store, essentially a persistent {@link java.util.concurrent.atomic.AtomicReference}, represented by {@link PersistentStateCache}.</li>
+ *
+ * </ul>
+ */
 public interface CacheRepository {
     /**
-     * Returns a builder for the cache with the given key. Default is a Gradle version-specific cache shared by all
-     * builds, though this can be changed using the given builder.
+     * Returns a builder for the store with the given key. Default is a Gradle version-specific store shared by all builds, though this can be
+     * changed using the given builder.
+     *
+     * <p>A store is always opened with a shared lock, so that it can be accessed by multiple processes. It is the caller's responsibility to
+     * coordinate access to the cache.</p>
+     *
+     * @param key The cache key.
+     * @return The builder.
+     */
+    DirectoryCacheBuilder store(String key);
+
+    /**
+     * Returns a builder for the cache with the given key. Default is a Gradle version-specific cache shared by all builds, though this can be
+     * changed using the given builder.
+     *
+     * <p>A state cache is always opened with a shared lock, so that it can be accessed by multiple processes. It is the caller's responsibility
+     * to coordinate access to the cache.</p>
+     *
+     * @param key The cache key.
+     * @return The builder.
+     */
+    DirectoryCacheBuilder cache(String key);
+
+    /**
+     * Returns a builder for the state cache with the given key. Default is a Gradle version-specific cache shared by all builds, though this
+     * can be changed using the given
+     * builder.
+     *
+     * <p>A state cache is always opened with an exclusive lock, so that it can be accessed only by this process.</p>
+     *
+     * @param key The cache key.
+     * @param elementType The type of element kept in the cache.
+     * @return The builder.
+     */
+    <E> ObjectCacheBuilder<E, PersistentStateCache<E>> stateCache(Class<E> elementType, String key);
+
+    /**
+     * Returns a builder for the indexed cache with the given key. Default is a Gradle version-specific cache shared by all builds, though this
+     * can be changed using the given builder.
+     *
+     * <p>An indexed cache is always opened with an exclusive lock, so that it can be accessed only by this process.</p>
      *
      * @param key The cache key.
+     * @param keyType The type of key kept in the cache.
+     * @param elementType The type of element kept in the cache.
      * @return The builder.
      */
-    CacheBuilder cache(String key);
+    <K, V> ObjectCacheBuilder<V, PersistentIndexedCache<K, V>> indexedCache(Class<K> keyType, Class<V> elementType, String key);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java b/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java
new file mode 100644
index 0000000..af53db8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/CacheValidator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.cache;
+
+/**
+ * CacheValidator interface can be used for specify a particular cache validation logic.
+ * */
+public interface CacheValidator {
+    boolean isValid();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultCacheFactory.java
deleted file mode 100644
index b944f39..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultCacheFactory.java
+++ /dev/null
@@ -1,34 +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.cache;
-
-import org.gradle.CacheUsage;
-import org.gradle.util.GFileUtils;
-
-import java.io.File;
-import java.util.Map;
-
-public class DefaultCacheFactory implements CacheFactory {
-
-    public PersistentCache open(File cacheDir, CacheUsage usage, Map<String, ?> properties) {
-        File canonicalDir = GFileUtils.canonicalise(cacheDir);
-        return new DefaultPersistentDirectoryCache(canonicalDir, usage, properties);
-    }
-
-    public void close(PersistentCache cache) {
-        ((DefaultPersistentDirectoryCache) cache).close();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultCacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultCacheRepository.java
deleted file mode 100644
index a3f7919..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultCacheRepository.java
+++ /dev/null
@@ -1,92 +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.cache;
-
-import org.gradle.CacheUsage;
-import org.gradle.api.Project;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.util.GradleVersion;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DefaultCacheRepository implements CacheRepository {
-    private final GradleVersion version = GradleVersion.current();
-    private final File globalCacheDir;
-    private final CacheUsage cacheUsage;
-    private final CacheFactory factory;
-
-    public DefaultCacheRepository(File userHomeDir, CacheUsage cacheUsage, CacheFactory factory) {
-        this.factory = factory;
-        this.globalCacheDir = new File(userHomeDir, "caches");
-        this.cacheUsage = cacheUsage;
-    }
-
-    public CacheBuilder cache(String key) {
-        return new PersistentCacheBuilder(key);
-    }
-
-    private class PersistentCacheBuilder implements CacheBuilder {
-        private final String key;
-        private Map<String, ?> properties = Collections.emptyMap();
-        private Object target;
-        private boolean invalidateOnVersionChange;
-
-        private PersistentCacheBuilder(String key) {
-            this.key = key;
-        }
-
-        public CacheBuilder withProperties(Map<String, ?> properties) {
-            this.properties = properties;
-            return this;
-        }
-
-        public CacheBuilder forObject(Object target) {
-            this.target = target;
-            return this;
-        }
-
-        public CacheBuilder invalidateOnVersionChange() {
-            invalidateOnVersionChange = true;
-            return this;
-        }
-
-        public PersistentCache open() {
-            File cacheBaseDir;
-            Map<String, Object> properties = new HashMap<String, Object>(this.properties);
-            if (target == null) {
-                cacheBaseDir = globalCacheDir;
-            } else if (target instanceof Gradle) {
-                Gradle gradle = (Gradle) target;
-                cacheBaseDir = new File(gradle.getRootProject().getProjectDir(), Project.TMP_DIR_NAME);
-            } else if (target instanceof File) {
-                File dir = (File) target;
-                cacheBaseDir = new File(dir, Project.TMP_DIR_NAME);
-            } else {
-                throw new IllegalArgumentException(String.format("Cannot create cache for unrecognised domain object %s.", target));
-            }
-            if (invalidateOnVersionChange) {
-                properties.put("gradle.version", version.getVersion());
-                cacheBaseDir = new File(cacheBaseDir, "noVersion");
-            } else {
-                cacheBaseDir = new File(cacheBaseDir, version.getVersion());
-            }
-            return factory.open(new File(cacheBaseDir, key), cacheUsage, properties);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultPersistentDirectoryCache.java
deleted file mode 100644
index 03969aa..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultPersistentDirectoryCache.java
+++ /dev/null
@@ -1,117 +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.cache;
-
-import org.gradle.CacheUsage;
-import org.gradle.cache.btree.BTreePersistentIndexedCache;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-public class DefaultPersistentDirectoryCache implements PersistentCache {
-    private final File dir;
-    private final File propertiesFile;
-    private final Properties properties = new Properties();
-    private boolean valid;
-    private BTreePersistentIndexedCache indexedCache;
-    private SimpleStateCache stateCache;
-
-    public DefaultPersistentDirectoryCache(File dir, CacheUsage cacheUsage, Map<String, ?> properties) {
-        this.dir = dir;
-        propertiesFile = new File(dir, "cache.properties");
-        this.properties.putAll(properties);
-        determineIfCacheIsValid(cacheUsage, properties);
-        buildCacheDir();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Cache %s", dir);
-    }
-
-    private void buildCacheDir() {
-        if (!valid) {
-            GFileUtils.deleteDirectory(dir);
-        }
-        if (!dir.isDirectory()) {
-            dir.mkdirs();
-        }
-    }
-
-    private void determineIfCacheIsValid(CacheUsage cacheUsage, Map<String, ?> properties) {
-        valid = false;
-
-        if (cacheUsage != CacheUsage.ON) {
-            return;
-        }
-
-        if (!propertiesFile.isFile()) {
-            return;
-        }
-
-        Properties currentProperties = GUtil.loadProperties(propertiesFile);
-        for (Map.Entry<String, ?> entry : properties.entrySet()) {
-            if (!entry.getValue().toString().equals(currentProperties.getProperty(entry.getKey()))) {
-                return;
-            }
-        }
-        valid = true;
-    }
-
-    public <K, V> BTreePersistentIndexedCache<K, V> openIndexedCache(Serializer<V> serializer) {
-        if (indexedCache == null) {
-            indexedCache = new BTreePersistentIndexedCache<K, V>(this, serializer);
-        }
-        return indexedCache;
-    }
-
-    public <K, V> BTreePersistentIndexedCache<K, V> openIndexedCache() {
-        return openIndexedCache(new DefaultSerializer<V>());
-    }
-
-    public <T> SimpleStateCache<T> openStateCache() {
-        if (stateCache == null) {
-            stateCache = new SimpleStateCache<T>(this, new DefaultSerializer<T>());
-        }
-        return stateCache;
-    }
-
-    public Properties getProperties() {
-        return properties;
-    }
-
-    public File getBaseDir() {
-        return dir;
-    }
-
-    public boolean isValid() {
-        return valid;
-    }
-
-    public void markValid() {
-        GUtil.saveProperties(properties, propertiesFile);
-        valid = true;
-    }
-
-    public void close() {
-        if (indexedCache != null) {
-            indexedCache.close();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
index 393470b..7825645 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
@@ -27,7 +27,7 @@ public class DefaultSerializer<T> implements Serializer<T> {
     }
 
     public DefaultSerializer(ClassLoader classLoader) {
-        this.classLoader = classLoader;
+        this.classLoader = classLoader != null ? classLoader : getClass().getClassLoader();
     }
 
     public ClassLoader getClassLoader() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DirectoryCacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/DirectoryCacheBuilder.java
new file mode 100644
index 0000000..7f859a7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/DirectoryCacheBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.cache;
+
+import org.gradle.api.Action;
+import org.gradle.cache.internal.FileLockManager;
+
+import java.util.Map;
+
+public interface DirectoryCacheBuilder extends CacheBuilder<PersistentCache> {
+    DirectoryCacheBuilder withVersionStrategy(VersionStrategy strategy);
+
+    DirectoryCacheBuilder withProperties(Map<String, ?> properties);
+
+    DirectoryCacheBuilder forObject(Object target);
+
+    /**
+     * Specifies the display name for this cache. This display name is used in logging and error messages.
+     */
+    DirectoryCacheBuilder withDisplayName(String displayName);
+
+    /**
+     * Specifies the <em>initial</em> lock mode to use. See {@link PersistentCache} for details.
+     *
+     * <p>Note that not every combination of cache type and lock mode is supported.
+     */
+    DirectoryCacheBuilder withLockMode(FileLockManager.LockMode lockMode);
+
+    /**
+     * Specifies an action to execute to initialize the cache contents, if the cache does not exist or is invalid. An exclusive lock is held while the initializer is executing, to prevent
+     * cross-process access.
+     */
+    DirectoryCacheBuilder withInitializer(Action<? super PersistentCache> initializer);
+
+    DirectoryCacheBuilder withValidator(CacheValidator validator);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
new file mode 100644
index 0000000..cca3769
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.cache;
+
+import java.util.Map;
+
+public interface ObjectCacheBuilder<E, T> extends CacheBuilder<T> {
+    /**
+     * {@inheritDoc}
+     */
+    ObjectCacheBuilder<E, T> forObject(Object target);
+
+    /**
+     * {@inheritDoc}
+     */
+    ObjectCacheBuilder<E, T> withProperties(Map<String, ?> properties);
+
+    /**
+     * {@inheritDoc}
+     */
+    ObjectCacheBuilder<E, T> withVersionStrategy(VersionStrategy strategy);
+
+    ObjectCacheBuilder<E, T> withSerializer(Serializer<E> serializer);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
index 23378d4..a203490 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2011 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.
@@ -18,45 +18,41 @@ package org.gradle.cache;
 import java.io.File;
 
 /**
- * Represents a directory which can be used for caching.
+ * Represents a directory that can be used for caching.
+ *
+ * <p>By default, a shared lock is held on this cache by this process, to prevent it being removed or rebuilt by another process
+ * while it is in use. You can change this use {@link DirectoryCacheBuilder#withLockMode(org.gradle.cache.internal.FileLockManager.LockMode)}.
+ *
+ * <p>You can use {@link DirectoryCacheBuilder#withInitializer(org.gradle.api.Action)} to provide an action to initialize the contents
+ * of the cache, for building a read-only cache. An exclusive lock is held by this process while the initializer is running.</p>
+ *
+ * <p>You can also use {@link #useCache(String, org.gradle.internal.Factory)} to perform some action on the cache while holding an exclusive
+ * lock on the cache.
+ * </p>
  */
-public interface PersistentCache {
+public interface PersistentCache extends CacheAccess {
     /**
      * Returns the base directory for this cache.
      */
     File getBaseDir();
 
     /**
-     * Returns true if this cache is valid. If the cache is valid, its contents can be used. If not, the base directory
-     * will be empty, and the cache contents must be rebuilt. You must call {@link #markValid} to indicate that the
-     * contents have been rebuilt.
-     */
-    boolean isValid();
-
-    /**
-     * Marks the contents of the cache as valid.
-     */
-    void markValid();
-
-    /**
-     * Opens an indexed cache backed by this cache.
+     * Creates an indexed cache implementation that is contained within this cache. This method may be used at any time.
      *
-     * @param serializer The serializer to use to serialise the cache entries.
-     * @return The cache.
-     */
-    <K, V> PersistentIndexedCache<K, V> openIndexedCache(Serializer<V> serializer);
-
-    /**
-     * Opens an indexed cache backed by this cache.
+     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
+     * In this instance, an exclusive lock will be held on the cache.
      *
-     * @return The cache.
+     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
      */
-    <K, V> PersistentIndexedCache<K, V> openIndexedCache();
+    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType);
 
     /**
-     * Opens a state cache backed by this cache.
+     * Creates an indexed cache implementation that is contained within this cache. This method may be used at any time.
+     *
+     * <p>The returned cache may only be used by an action being run from {@link #useCache(String, org.gradle.internal.Factory)}.
+     * In this instance, an exclusive lock will be held on the cache.
      *
-     * @return The cache.
+     * <p>The returned cache may not be used by an action being run from {@link #longRunningOperation(String, org.gradle.internal.Factory)}.
      */
-    <T> PersistentStateCache<T> openStateCache();
+    <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
index 9afa879..d78cc24 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
@@ -19,9 +19,20 @@ package org.gradle.cache;
  * A persistent store of objects of type V indexed by a key of type K.
  */
 public interface PersistentIndexedCache<K, V> {
+    /**
+     * Fetches the value of a key from this cache. A shared or exclusive lock is held while fetching the value, depending on implementation.
+     *
+     * @return The value, or null if no value associated with the key.
+     */
     V get(K key);
 
+    /**
+     * Puts/replaces the value of a key in this cache. A shared lock is held while updating the value.
+     */
     void put(K key, V value);
 
+    /**
+     * Removes a key-value mapping from this cache. A shared lock is held while updating the value.
+     */
     void remove(K key);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStateCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStateCache.java
index 163122c..81d54a5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStateCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentStateCache.java
@@ -20,7 +20,28 @@ package org.gradle.cache;
  * A persistent store containing an object of type T.
  */
 public interface PersistentStateCache<T> {
+    /**
+     * Fetches the value from this cache. A shared or exclusive lock is held while fetching the value, depending on implementation.
+     */
     T get();
 
+    /**
+     * Sets the value for this cache. An exclusive lock is held while setting the value.
+     */
     void set(T newValue);
+
+    /**
+     * Replaces the value for this cache. An exclusive lock is held while the update action is executing.
+     */
+    void update(UpdateAction<T> updateAction);
+
+    static interface UpdateAction<T> {
+        /**
+         * should return the new value
+         *
+         * @param oldValue, null means there wasn't any value before
+         * @return new value
+         */
+        T update(T oldValue);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/SimpleStateCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/SimpleStateCache.java
deleted file mode 100644
index c1e7311..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/SimpleStateCache.java
+++ /dev/null
@@ -1,63 +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.cache;
-
-import org.gradle.api.GradleException;
-
-import java.io.*;
-
-public class SimpleStateCache<T> implements PersistentStateCache<T> {
-    private final Serializer<T> serializer;
-    private final File cacheFile;
-    private PersistentCache cache;
-
-    public SimpleStateCache(PersistentCache cache, Serializer<T> serializer) {
-        this.cache = cache;
-        this.serializer = serializer;
-        cacheFile = new File(cache.getBaseDir(), "state.bin");
-    }
-
-    public T get() {
-        if (!cacheFile.isFile()) {
-            return null;
-        }
-        try {
-            InputStream inStr = new BufferedInputStream(new FileInputStream(cacheFile));
-            try {
-                return serializer.read(inStr);
-            } finally {
-                inStr.close();
-            }
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not read cache value from '%s'.", cacheFile), e);
-        }
-    }
-
-    public void set(T newValue) {
-        try {
-            OutputStream outStr = new BufferedOutputStream(new FileOutputStream(cacheFile));
-            try {
-                serializer.write(outStr, newValue);
-            } finally {
-                outStr.close();
-            }
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not write cache value to '%s'.", cacheFile), e);
-        }
-        cache.markValid();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BTreePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/BTreePersistentIndexedCache.java
deleted file mode 100644
index 67acb43..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BTreePersistentIndexedCache.java
+++ /dev/null
@@ -1,684 +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.cache.btree;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.cache.PersistentCache;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.*;
-import java.util.*;
-
-// todo - stream serialised value to file
-// todo - handle hash collisions
-// todo - don't store null links to child blocks in leaf index blocks
-// todo - align block boundaries
-// todo - concurrency control
-// todo - remove the check-sum from each block
-// todo - merge small values into a single data block
-// todo - discard when file corrupt
-// todo - include data directly in index entry when serializer can guarantee small fixed sized data
-// todo - free list leaks disk space
-// todo - merge adjacent free blocks
-// todo - use more efficient lookup for free block with nearest size
-public class BTreePersistentIndexedCache<K, V> implements PersistentIndexedCache<K, V> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BTreePersistentIndexedCache.class);
-    private final File cacheFile;
-    private final PersistentCache backingCache;
-    private final Serializer<V> serializer;
-    private final short maxChildIndexEntries;
-    private final int minIndexChildNodes;
-    private final StateCheckBlockStore store;
-    private HeaderBlock header;
-
-    public BTreePersistentIndexedCache(PersistentCache backingCache, Serializer<V> serializer) {
-        this(backingCache, serializer, (short) 512, 512);
-    }
-
-    public BTreePersistentIndexedCache(PersistentCache backingCache, Serializer<V> serializer,
-                                       short maxChildIndexEntries, int maxFreeListEntries) {
-        this.backingCache = backingCache;
-        this.serializer = serializer;
-        this.maxChildIndexEntries = maxChildIndexEntries;
-        this.minIndexChildNodes = maxChildIndexEntries / 2;
-        cacheFile = new File(backingCache.getBaseDir(), "cache.bin");
-        BlockStore cachingStore = new CachingBlockStore(new FileBackedBlockStore(cacheFile), IndexBlock.class, FreeListBlockStore.FreeListBlock.class);
-        store = new StateCheckBlockStore(new FreeListBlockStore(cachingStore, maxFreeListEntries));
-        try {
-            open();
-        } catch (Exception e) {
-            throw new UncheckedIOException(String.format("Could not open %s.", this), e);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return String.format("cache '%s'", cacheFile);
-    }
-
-    private void open() throws Exception {
-        try {
-            doOpen();
-        } catch (CorruptedCacheException e) {
-            rebuild();
-        }
-    }
-
-    private void doOpen() throws Exception {
-        BlockStore.Factory factory = new BlockStore.Factory() {
-            public Object create(Class<? extends BlockPayload> type) {
-                if (type == HeaderBlock.class) {
-                    return new HeaderBlock();
-                }
-                if (type == IndexBlock.class) {
-                    return new IndexBlock();
-                }
-                if (type == DataBlock.class) {
-                    return new DataBlock();
-                }
-                throw new UnsupportedOperationException();
-            }
-        };
-        Runnable initAction = new Runnable() {
-            public void run() {
-                header = new HeaderBlock();
-                store.write(header);
-                header.index.newRoot();
-                store.flush();
-                backingCache.markValid();
-            }
-        };
-
-        store.open(initAction, factory);
-        header = store.readFirst(HeaderBlock.class);
-    }
-
-    public V get(K key) {
-        try {
-            try {
-                DataBlock block = header.getRoot().get(key);
-                if (block != null) {
-                    return block.getValue();
-                }
-                return null;
-            } catch (CorruptedCacheException e) {
-                rebuild();
-                return null;
-            }
-        } catch (Exception e) {
-            throw new UncheckedIOException(String.format("Could not read entry '%s' from %s.", key, this), e);
-        }
-    }
-
-    public void put(K key, V value) {
-        try {
-            String keyString = key.toString();
-            long hashCode = keyString.hashCode();
-            Lookup lookup = header.getRoot().find(hashCode);
-            boolean needNewBlock = true;
-            if (lookup.entry != null) {
-                DataBlock block = store.read(lookup.entry.dataBlock, DataBlock.class);
-                needNewBlock = !block.useNewValue(value);
-                if (needNewBlock) {
-                    store.remove(block);
-                }
-            }
-            if (needNewBlock) {
-                DataBlock block = new DataBlock(keyString, value);
-                store.write(block);
-                lookup.indexBlock.put(hashCode, block.getPos());
-            }
-            store.flush();
-        } catch (Exception e) {
-            throw new UncheckedIOException(String.format("Could not add entry '%s' to %s.", key, this), e);
-        }
-    }
-
-    public void remove(K key) {
-        try {
-            Lookup lookup = header.getRoot().find(key.toString());
-            if (lookup.entry == null) {
-                return;
-            }
-            lookup.indexBlock.remove(lookup.entry);
-            DataBlock block = store.read(lookup.entry.dataBlock, DataBlock.class);
-            store.remove(block);
-            store.flush();
-        } catch (Exception e) {
-            throw new UncheckedIOException(String.format("Could not remove entry '%s' from %s.", key, this), e);
-        }
-    }
-
-    private IndexBlock load(BlockPointer pos, IndexRoot root, IndexBlock parent, int index) {
-        IndexBlock block = store.read(pos, IndexBlock.class);
-        block.root = root;
-        block.parent = parent;
-        block.parentEntryIndex = index;
-        return block;
-    }
-
-    public void reset() {
-        close();
-        try {
-            open();
-        } catch (Exception e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void close() {
-        try {
-            store.close();
-        } catch (Exception e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public boolean isOpen() {
-        return store.isOpen();
-    }
-
-    private void rebuild() throws Exception {
-        LOGGER.warn(String.format("%s is corrupt. Discarding.", this));
-        store.clear();
-        close();
-        doOpen();
-    }
-
-    public void verify() {
-        try {
-            doVerify();
-        } catch (Exception e) {
-            throw new UncheckedIOException(String.format("Some problems were found when checking the integrity of %s.",
-                    this), e);
-        }
-    }
-
-    private void doVerify() throws Exception {
-        List<BlockPayload> blocks = new ArrayList<BlockPayload>();
-
-        HeaderBlock header = store.readFirst(HeaderBlock.class);
-        blocks.add(header);
-        verifyTree(header.getRoot(), "", blocks, Long.MAX_VALUE, true);
-
-        Collections.sort(blocks, new Comparator<BlockPayload>() {
-            public int compare(BlockPayload block, BlockPayload block1) {
-                return block.getPos().compareTo(block1.getPos());
-            }
-        });
-
-        for (int i = 0; i < blocks.size() - 1; i++) {
-            Block b1 = blocks.get(i).getBlock();
-            Block b2 = blocks.get(i + 1).getBlock();
-            if (b1.getPos().getPos() + b1.getSize() > b2.getPos().getPos()) {
-                throw new IOException(String.format("%s overlaps with %s", b1, b2));
-            }
-        }
-    }
-
-    private void verifyTree(IndexBlock current, String prefix, Collection<BlockPayload> blocks, long maxValue,
-                            boolean loadData) throws Exception {
-        blocks.add(current);
-
-        if (!prefix.equals("") && current.entries.size() < maxChildIndexEntries / 2) {
-            throw new IOException(String.format("Too few entries found in %s", current));
-        }
-        if (current.entries.size() > maxChildIndexEntries) {
-            throw new IOException(String.format("Too many entries found in %s", current));
-        }
-
-        boolean isLeaf = current.entries.size() == 0 || current.entries.get(0).childIndexBlock.isNull();
-        if (isLeaf ^ current.tailPos.isNull()) {
-            throw new IOException(String.format("Mismatched leaf/tail-node in %s", current));
-        }
-
-        long min = Long.MIN_VALUE;
-        for (IndexEntry entry : current.entries) {
-            if (isLeaf ^ entry.childIndexBlock.isNull()) {
-                throw new IOException(String.format("Mismatched leaf/non-leaf entry in %s", current));
-            }
-            if (entry.hashCode >= maxValue || entry.hashCode <= min) {
-                throw new IOException(String.format("Out-of-order key in %s", current));
-            }
-            min = entry.hashCode;
-            if (!entry.childIndexBlock.isNull()) {
-                IndexBlock child = store.read(entry.childIndexBlock, IndexBlock.class);
-                verifyTree(child, "   " + prefix, blocks, entry.hashCode, loadData);
-            }
-            if (loadData) {
-                DataBlock block = store.read(entry.dataBlock, DataBlock.class);
-                blocks.add(block);
-            }
-        }
-        if (!current.tailPos.isNull()) {
-            IndexBlock tail = store.read(current.tailPos, IndexBlock.class);
-            verifyTree(tail, "   " + prefix, blocks, maxValue, loadData);
-        }
-    }
-
-    private class IndexRoot {
-        private BlockPointer rootPos = new BlockPointer();
-        private HeaderBlock owner;
-
-        private IndexRoot(HeaderBlock owner) {
-            this.owner = owner;
-        }
-
-        public void setRootPos(BlockPointer rootPos) {
-            this.rootPos = rootPos;
-            store.write(owner);
-        }
-
-        public IndexBlock getRoot() {
-            return load(rootPos, this, null, 0);
-        }
-
-        public IndexBlock newRoot() {
-            IndexBlock block = new IndexBlock();
-            store.write(block);
-            setRootPos(block.getPos());
-            return block;
-        }
-    }
-
-    private class HeaderBlock extends BlockPayload {
-        private IndexRoot index;
-
-        private HeaderBlock() {
-            index = new IndexRoot(this);
-        }
-
-        @Override
-        protected int getType() {
-            return 0x55;
-        }
-
-        @Override
-        protected int getSize() {
-            return Block.LONG_SIZE + Block.SHORT_SIZE;
-        }
-
-        @Override
-        protected void read(DataInputStream instr) throws Exception {
-            index.rootPos = new BlockPointer(instr.readLong());
-
-            short actualChildIndexEntries = instr.readShort();
-            if (actualChildIndexEntries != maxChildIndexEntries) {
-                throw blockCorruptedException();
-            }
-        }
-
-        @Override
-        protected void write(DataOutputStream outstr) throws Exception {
-            outstr.writeLong(index.rootPos.getPos());
-            outstr.writeShort(maxChildIndexEntries);
-        }
-
-        public IndexBlock getRoot() throws Exception {
-            return index.getRoot();
-        }
-    }
-
-    private class IndexBlock extends BlockPayload {
-        private final List<IndexEntry> entries = new ArrayList<IndexEntry>();
-        private BlockPointer tailPos = new BlockPointer();
-        // Transient fields
-        private IndexBlock parent;
-        private int parentEntryIndex;
-        private IndexRoot root;
-
-        @Override
-        protected int getType() {
-            return 0x77;
-        }
-
-        @Override
-        protected int getSize() {
-            return Block.INT_SIZE + Block.LONG_SIZE + (3 * Block.LONG_SIZE) * maxChildIndexEntries;
-        }
-
-        public void read(DataInputStream instr) throws IOException {
-            int count = instr.readInt();
-            entries.clear();
-            for (int i = 0; i < count; i++) {
-                IndexEntry entry = new IndexEntry();
-                entry.hashCode = instr.readLong();
-                entry.dataBlock = new BlockPointer(instr.readLong());
-                entry.childIndexBlock = new BlockPointer(instr.readLong());
-                entries.add(entry);
-            }
-            tailPos = new BlockPointer(instr.readLong());
-        }
-
-        public void write(DataOutputStream outstr) throws IOException {
-            outstr.writeInt(entries.size());
-            for (IndexEntry entry : entries) {
-                outstr.writeLong(entry.hashCode);
-                outstr.writeLong(entry.dataBlock.getPos());
-                outstr.writeLong(entry.childIndexBlock.getPos());
-            }
-            outstr.writeLong(tailPos.getPos());
-        }
-
-        public void put(long hashCode, BlockPointer pos) throws Exception {
-            int index = Collections.binarySearch(entries, new IndexEntry(hashCode));
-            IndexEntry entry;
-            if (index >= 0) {
-                entry = entries.get(index);
-            } else {
-                assert tailPos.isNull();
-                entry = new IndexEntry();
-                entry.hashCode = hashCode;
-                entry.childIndexBlock = new BlockPointer();
-                index = -index - 1;
-                entries.add(index, entry);
-            }
-
-            entry.dataBlock = pos;
-            store.write(this);
-
-            maybeSplit();
-        }
-
-        private void maybeSplit() throws Exception {
-            if (entries.size() > maxChildIndexEntries) {
-                int splitPos = entries.size() / 2;
-                IndexEntry splitEntry = entries.remove(splitPos);
-                if (parent == null) {
-                    parent = root.newRoot();
-                }
-                IndexBlock sibling = new IndexBlock();
-                store.write(sibling);
-                List<IndexEntry> siblingEntries = entries.subList(splitPos, entries.size());
-                sibling.entries.addAll(siblingEntries);
-                siblingEntries.clear();
-                sibling.tailPos = tailPos;
-                tailPos = splitEntry.childIndexBlock;
-                splitEntry.childIndexBlock = new BlockPointer();
-                parent.add(this, splitEntry, sibling);
-            }
-        }
-
-        private void add(IndexBlock left, IndexEntry entry, IndexBlock right) throws Exception {
-            int index = left.parentEntryIndex;
-            if (index < entries.size()) {
-                IndexEntry parentEntry = entries.get(index);
-                assert parentEntry.childIndexBlock.equals(left.getPos());
-                parentEntry.childIndexBlock = right.getPos();
-            } else {
-                assert index == entries.size() && (tailPos.isNull() || tailPos.equals(left.getPos()));
-                tailPos = right.getPos();
-            }
-            entries.add(index, entry);
-            entry.childIndexBlock = left.getPos();
-            store.write(this);
-
-            maybeSplit();
-        }
-
-        public DataBlock get(K key) throws Exception {
-            Lookup lookup = find(key.toString());
-            if (lookup.entry == null) {
-                return null;
-            }
-
-            return store.read(lookup.entry.dataBlock, DataBlock.class);
-        }
-
-        public Lookup find(String keyString) throws Exception {
-            return find((long) keyString.hashCode());
-        }
-
-        private Lookup find(long hashCode) throws Exception {
-            int index = Collections.binarySearch(entries, new IndexEntry(hashCode));
-            if (index >= 0) {
-                return new Lookup(this, entries.get(index));
-            }
-
-            index = -index - 1;
-            BlockPointer childBlockPos;
-            if (index == entries.size()) {
-                childBlockPos = tailPos;
-            } else {
-                childBlockPos = entries.get(index).childIndexBlock;
-            }
-            if (childBlockPos.isNull()) {
-                return new Lookup(this, null);
-            }
-
-            IndexBlock childBlock = load(childBlockPos, root, this, index);
-            return childBlock.find(hashCode);
-        }
-
-        public void remove(IndexEntry entry) throws Exception {
-            int index = entries.indexOf(entry);
-            assert index >= 0;
-            entries.remove(index);
-            store.write(this);
-
-            if (entry.childIndexBlock.isNull()) {
-                maybeMerge();
-            } else {
-                // Not a leaf node. Move up an entry from a leaf node, then possibly merge the leaf node
-                IndexBlock leafBlock = load(entry.childIndexBlock, root, this, index);
-                leafBlock = leafBlock.findHighestLeaf();
-                IndexEntry highestEntry = leafBlock.entries.remove(leafBlock.entries.size() - 1);
-                highestEntry.childIndexBlock = entry.childIndexBlock;
-                entries.add(index, highestEntry);
-                store.write(leafBlock);
-                leafBlock.maybeMerge();
-            }
-        }
-
-        private void maybeMerge() throws Exception {
-            if (parent == null) {
-                // This is the root block. Can have any number of children <= maxChildIndexEntries
-                if (entries.size() == 0 && !tailPos.isNull()) {
-                    // This is an empty root block, discard it
-                    header.index.setRootPos(tailPos);
-                    store.remove(this);
-                }
-                return;
-            }
-
-            // This is not the root block. Must have children >= minIndexChildNodes
-            if (entries.size() >= minIndexChildNodes) {
-                return;
-            }
-
-            // Attempt to merge with the left sibling
-            IndexBlock left = parent.getPrevious(this);
-            if (left != null) {
-                assert entries.size() + left.entries.size() <= maxChildIndexEntries * 2;
-                if (left.entries.size() > minIndexChildNodes) {
-                    // There are enough entries in this block and the left sibling to make up 2 blocks, so redistribute
-                    // the entries evenly between them
-                    left.mergeFrom(this);
-                    left.maybeSplit();
-                    return;
-                } else {
-                    // There are only enough entries to make up 1 block, so move the entries of the left sibling into
-                    // this block and discard the left sibling. Might also need to merge the parent
-                    left.mergeFrom(this);
-                    parent.maybeMerge();
-                    return;
-                }
-            }
-
-            // Attempt to merge with the right sibling
-            IndexBlock right = parent.getNext(this);
-            if (right != null) {
-                assert entries.size() + right.entries.size() <= maxChildIndexEntries * 2;
-                if (right.entries.size() > minIndexChildNodes) {
-                    // There are enough entries in this block and the right sibling to make up 2 blocks, so redistribute
-                    // the entries evenly between them
-                    mergeFrom(right);
-                    maybeSplit();
-                    return;
-                } else {
-                    // There are only enough entries to make up 1 block, so move the entries of the right sibling into
-                    // this block and discard this block. Might also need to merge the parent
-                    mergeFrom(right);
-                    parent.maybeMerge();
-                    return;
-                }
-            }
-
-            // Should not happen
-            throw new IllegalStateException(String.format("%s does not have any siblings.", getBlock()));
-        }
-
-        private void mergeFrom(IndexBlock right) throws Exception {
-            IndexEntry newChildEntry = parent.entries.remove(parentEntryIndex);
-            if (right.getPos().equals(parent.tailPos)) {
-                parent.tailPos = getPos();
-            } else {
-                IndexEntry newParentEntry = parent.entries.get(parentEntryIndex);
-                assert newParentEntry.childIndexBlock.equals(right.getPos());
-                newParentEntry.childIndexBlock = getPos();
-            }
-            entries.add(newChildEntry);
-            entries.addAll(right.entries);
-            newChildEntry.childIndexBlock = tailPos;
-            tailPos = right.tailPos;
-            store.write(parent);
-            store.write(this);
-            store.remove(right);
-        }
-
-        private IndexBlock getNext(IndexBlock indexBlock) throws Exception {
-            int index = indexBlock.parentEntryIndex + 1;
-            if (index > entries.size()) {
-                return null;
-            }
-            if (index == entries.size()) {
-                return load(tailPos, root, this, index);
-            }
-            return load(entries.get(index).childIndexBlock, root, this, index);
-        }
-
-        private IndexBlock getPrevious(IndexBlock indexBlock) throws Exception {
-            int index = indexBlock.parentEntryIndex - 1;
-            if (index < 0) {
-                return null;
-            }
-            return load(entries.get(index).childIndexBlock, root, this, index);
-        }
-
-        private IndexBlock findHighestLeaf() throws Exception {
-            if (tailPos.isNull()) {
-                return this;
-            }
-            return load(tailPos, root, this, entries.size()).findHighestLeaf();
-        }
-    }
-
-    private static class IndexEntry implements Comparable<IndexEntry> {
-        long hashCode;
-        BlockPointer dataBlock;
-        BlockPointer childIndexBlock;
-
-        private IndexEntry() {
-        }
-
-        private IndexEntry(long hashCode) {
-            this.hashCode = hashCode;
-        }
-
-        public int compareTo(IndexEntry indexEntry) {
-            if (hashCode > indexEntry.hashCode) {
-                return 1;
-            }
-            if (hashCode < indexEntry.hashCode) {
-                return -1;
-            }
-            return 0;
-        }
-    }
-
-    private class Lookup {
-        final IndexBlock indexBlock;
-        final IndexEntry entry;
-
-        private Lookup(IndexBlock indexBlock, IndexEntry entry) {
-            this.indexBlock = indexBlock;
-            this.entry = entry;
-        }
-    }
-
-    private class DataBlock extends BlockPayload {
-        private int size;
-        private byte[] serialisedValue;
-        private V value;
-
-        private DataBlock() {
-        }
-
-        public DataBlock(String key, V value) throws Exception {
-            this.value = value;
-            setValue(value);
-            size = serialisedValue.length;
-        }
-
-        public void setValue(V value) throws Exception {
-            ByteArrayOutputStream outStr = new ByteArrayOutputStream();
-            serializer.write(outStr, value);
-            this.serialisedValue = outStr.toByteArray();
-        }
-
-        public V getValue() throws Exception {
-            if (value == null) {
-                value = serializer.read(new ByteArrayInputStream(serialisedValue));
-            }
-            return value;
-        }
-
-        @Override
-        protected int getType() {
-            return 0x33;
-        }
-
-        @Override
-        protected int getSize() {
-            return 2 * Block.INT_SIZE + size;
-        }
-
-        public void read(DataInputStream instr) throws Exception {
-            size = instr.readInt();
-            int bytes = instr.readInt();
-            serialisedValue = new byte[bytes];
-            instr.readFully(serialisedValue);
-        }
-
-        public void write(DataOutputStream outstr) throws Exception {
-            outstr.writeInt(size);
-            outstr.writeInt(serialisedValue.length);
-            outstr.write(serialisedValue);
-        }
-
-        public boolean useNewValue(V value) throws Exception {
-            setValue(value);
-            boolean ok = serialisedValue.length <= size;
-            if (ok) {
-                store.write(this);
-            }
-            return ok;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/Block.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/Block.java
deleted file mode 100644
index 230318d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/Block.java
+++ /dev/null
@@ -1,59 +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.cache.btree;
-
-public abstract class Block {
-    static final int LONG_SIZE = 8;
-    static final int INT_SIZE = 4;
-    static final int SHORT_SIZE = 2;
-
-    private BlockPayload payload;
-
-    protected Block(BlockPayload payload) {
-        this.payload = payload;
-        payload.setBlock(this);
-    }
-
-    public BlockPayload getPayload() {
-        return payload;
-    }
-
-    protected void detach() {
-        payload.setBlock(null);
-        payload = null;
-    }
-    
-    public abstract BlockPointer getPos();
-
-    public abstract int getSize();
-
-    public abstract RuntimeException blockCorruptedException();
-
-    @Override
-    public String toString() {
-        return String.format("%s %s", payload.getClass().getSimpleName(), getPos());
-    }
-
-    public BlockPointer getNextPos() {
-        return new BlockPointer(getPos().getPos() + getSize());
-    }
-
-    public abstract boolean hasPos();
-
-    public abstract void setPos(BlockPointer pos);
-
-    public abstract void setSize(int size);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockPayload.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockPayload.java
deleted file mode 100644
index e07bc30..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockPayload.java
+++ /dev/null
@@ -1,51 +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.cache.btree;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-
-public abstract class BlockPayload {
-    private Block block;
-
-    public Block getBlock() {
-        return block;
-    }
-
-    public void setBlock(Block block) {
-        this.block = block;
-    }
-
-    public BlockPointer getPos() {
-        return getBlock().getPos();
-    }
-
-    public BlockPointer getNextPos() {
-        return getBlock().getNextPos();
-    }
-
-    protected abstract int getSize();
-
-    protected abstract int getType();
-
-    protected abstract void read(DataInputStream inputStream) throws Exception;
-
-    protected abstract void write(DataOutputStream outputStream) throws Exception;
-
-    protected RuntimeException blockCorruptedException() {
-        return getBlock().blockCorruptedException();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockPointer.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockPointer.java
deleted file mode 100644
index 62ef4e2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockPointer.java
+++ /dev/null
@@ -1,68 +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.cache.btree;
-
-public class BlockPointer implements Comparable<BlockPointer> {
-    private final long pos;
-
-    public BlockPointer() {
-        pos = -1;
-    }
-
-    public BlockPointer(long pos) {
-        this.pos = pos;
-    }
-
-    public boolean isNull() {
-        return pos < 0;
-    }
-
-    public long getPos() {
-        return pos;
-    }
-
-    @Override
-    public String toString() {
-        return String.valueOf(pos);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return true;
-        }
-        if (obj == null || obj.getClass() != getClass()) {
-            return false;
-        }
-        BlockPointer other = (BlockPointer) obj;
-        return pos == other.pos;
-    }
-
-    @Override
-    public int hashCode() {
-        return (int) pos;
-    }
-
-    public int compareTo(BlockPointer o) {
-        if (pos > o.pos) {
-            return 1;
-        }
-        if (pos < o.pos) {
-            return -1;
-        }
-        return 0;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockStore.java
deleted file mode 100644
index 71db2a8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/BlockStore.java
+++ /dev/null
@@ -1,68 +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.cache.btree;
-
-public interface BlockStore {
-    /**
-     * Opens this store, calling the given action if the store is empty.
-     */
-    void open(Runnable initAction, Factory factory);
-
-    /**
-     * Closes this store.
-     */
-    void close();
-
-    /**
-     * Discards all blocks from this store.
-     */
-    void clear();
-
-    /**
-     * Removes the given block from this store.
-     */
-    void remove(BlockPayload block);
-
-    /**
-     * Reads the first block from this store.
-     */
-    <T extends BlockPayload> T readFirst(Class<T> payloadType);
-    
-    /**
-     * Reads a block from this store.
-     */
-    <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType);
-
-    /**
-     * Writes a block to this store, adding the block if required.
-     */
-    void write(BlockPayload block);
-
-    /**
-     * Adds a new block to this store. Allocates space for the block, but does not write the contents of the block
-     * until {@link #write(BlockPayload)} is called.
-     */
-    void attach(BlockPayload block);
-
-    /**
-     * Flushes any pending updates for this store.
-     */
-    void flush();
-
-    interface Factory {
-        Object create(Class<? extends BlockPayload> type);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/CachingBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/CachingBlockStore.java
deleted file mode 100644
index 77fb95e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/CachingBlockStore.java
+++ /dev/null
@@ -1,100 +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.cache.btree;
-
-import org.apache.commons.collections.map.LRUMap;
-
-import java.util.*;
-
-public class CachingBlockStore implements BlockStore {
-    private final BlockStore store;
-    private final Map<BlockPointer, BlockPayload> dirty = new LinkedHashMap<BlockPointer, BlockPayload>();
-    private final Map<BlockPointer, BlockPayload> indexBlockCache = new LRUMap(100);
-    private final Set<Class<?>> cachableTypes = new HashSet<Class<?>>();
-
-    public CachingBlockStore(BlockStore store, Class<? extends BlockPayload>... cacheableBlockTypes) {
-        this.store = store;
-        cachableTypes.addAll(Arrays.asList(cacheableBlockTypes));
-    }
-
-    public void open(Runnable initAction, Factory factory) {
-        store.open(initAction, factory);
-    }
-
-    public void close() {
-        flush();
-        indexBlockCache.clear();
-        store.close();
-    }
-
-    public void clear() {
-        dirty.clear();
-        indexBlockCache.clear();
-        store.clear();
-    }
-
-    public void flush() {
-        Iterator<BlockPayload> iterator = dirty.values().iterator();
-        while (iterator.hasNext()) {
-            BlockPayload block = iterator.next();
-            iterator.remove();
-            store.write(block);
-        }
-        store.flush();
-    }
-
-    public void attach(BlockPayload block) {
-        store.attach(block);
-    }
-
-    public void remove(BlockPayload block) {
-        dirty.remove(block.getPos());
-        indexBlockCache.remove(block.getPos());
-        store.remove(block);
-    }
-
-    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
-        T block = store.readFirst(payloadType);
-        maybeCache(block);
-        return block;
-    }
-
-    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
-        T block = payloadType.cast(dirty.get(pos));
-        if (block != null) {
-            return block;
-        }
-        block = payloadType.cast(indexBlockCache.get(pos));
-        if (block != null) {
-            return block;
-        }
-        block = store.read(pos, payloadType);
-        maybeCache(block);
-        return block;
-    }
-
-    public void write(BlockPayload block) {
-        store.attach(block);
-        maybeCache(block);
-        dirty.put(block.getPos(), block);
-    }
-
-    private <T extends BlockPayload> void maybeCache(T block) {
-        if (cachableTypes.contains(block.getClass())) {
-            indexBlockCache.put(block.getPos(), block);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/CorruptedCacheException.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/CorruptedCacheException.java
deleted file mode 100644
index c9af8ba..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/CorruptedCacheException.java
+++ /dev/null
@@ -1,22 +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.cache.btree;
-
-class CorruptedCacheException extends RuntimeException {
-    CorruptedCacheException(String message) {
-        super(message);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/FileBackedBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/FileBackedBlockStore.java
deleted file mode 100644
index 540c8d1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/FileBackedBlockStore.java
+++ /dev/null
@@ -1,352 +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.cache.btree;
-
-import org.gradle.api.UncheckedIOException;
-
-import java.io.*;
-import java.util.zip.CRC32;
-
-public class FileBackedBlockStore implements BlockStore {
-    private RandomAccessFile file;
-    private final File cacheFile;
-    private long nextBlock;
-    private Factory factory;
-
-    public FileBackedBlockStore(File cacheFile) {
-        this.cacheFile = cacheFile;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("cache '%s'", cacheFile);
-    }
-
-    public void open(Runnable runnable, Factory factory) {
-        this.factory = factory;
-        try {
-            file = new RandomAccessFile(cacheFile, "rw");
-            nextBlock = file.length();
-            if (file.length() == 0) {
-                runnable.run();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void close() {
-        try {
-            file.close();
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void clear() {
-        try {
-            file.setLength(0);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        nextBlock = 0;
-    }
-
-    public void attach(BlockPayload block) {
-        if (block.getBlock() == null) {
-            block.setBlock(new BlockImpl(block));
-        }
-    }
-
-    public void remove(BlockPayload block) {
-        BlockImpl blockImpl = (BlockImpl) block.getBlock();
-        blockImpl.detach();
-    }
-
-    public void flush() {
-    }
-
-    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
-        return read(new BlockPointer(0), payloadType);
-    }
-
-    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
-        assert !pos.isNull();
-        try {
-            T payload = payloadType.cast(factory.create(payloadType));
-            BlockImpl block = new BlockImpl(payload, pos);
-            block.read();
-            return payload;
-        } catch (CorruptedCacheException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void write(BlockPayload block) {
-        BlockImpl blockImpl = (BlockImpl) block.getBlock();
-        try {
-            blockImpl.write();
-        } catch (CorruptedCacheException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    private long alloc(long length) {
-        long pos = nextBlock;
-        nextBlock += length;
-        return pos;
-    }
-
-    private final class BlockImpl extends Block {
-        private static final int HEADER_SIZE = 2 + INT_SIZE;
-        private static final int TAIL_SIZE = LONG_SIZE;
-        static final int BLOCK_MARKER = 0xCC;
-
-        private BlockPointer pos;
-        private int payloadSize;
-
-        private BlockImpl(BlockPayload payload, BlockPointer pos) {
-            this(payload);
-            setPos(pos);
-        }
-
-        public BlockImpl(BlockPayload payload) {
-            super(payload);
-            pos = null;
-            payloadSize = -1;
-        }
-
-        @Override
-        public boolean hasPos() {
-            return pos != null;
-        }
-
-        @Override
-        public BlockPointer getPos() {
-            if (pos == null) {
-                pos = new BlockPointer(alloc(getSize()));
-            }
-            return pos;
-        }
-
-        @Override
-        public void setPos(BlockPointer pos) {
-            assert this.pos == null && !pos.isNull();
-            this.pos = pos;
-        }
-
-        public int getSize() {
-            if (payloadSize < 0) {
-                payloadSize = getPayload().getSize();
-            }
-            return payloadSize + HEADER_SIZE + TAIL_SIZE;
-        }
-
-        @Override
-        public void setSize(int size) {
-            int newPayloadSize = size - HEADER_SIZE - TAIL_SIZE;
-            assert newPayloadSize >= payloadSize;
-            payloadSize = newPayloadSize;
-        }
-
-        public void write() throws Exception {
-            long pos = getPos().getPos();
-            file.seek(pos);
-
-            Crc32OutputStream checkSumOutputStream = new Crc32OutputStream(new BufferedOutputStream(
-                    new RandomAccessFileOutputStream(file)));
-            DataOutputStream outputStream = new DataOutputStream(checkSumOutputStream);
-
-            BlockPayload payload = getPayload();
-
-            // Write header
-            outputStream.writeByte(BLOCK_MARKER);
-            outputStream.writeByte(payload.getType());
-            outputStream.writeInt(payloadSize);
-            long finalSize = pos + HEADER_SIZE + TAIL_SIZE + payloadSize;
-
-            // Write body
-            payload.write(outputStream);
-
-            // Write checksum
-            outputStream.writeLong(checkSumOutputStream.checksum.getValue());
-            outputStream.close();
-
-            // Pad
-            if (file.length() < finalSize) {
-                file.setLength(finalSize);
-            }
-        }
-
-        public void read() throws Exception {
-            long pos = getPos().getPos();
-            assert pos >= 0;
-            if (pos + HEADER_SIZE >= file.length()) {
-                throw blockCorruptedException();
-            }
-            file.seek(pos);
-
-            Crc32InputStream checkSumInputStream = new Crc32InputStream(new BufferedInputStream(
-                    new RandomAccessFileInputStream(file)));
-            DataInputStream inputStream = new DataInputStream(checkSumInputStream);
-
-            BlockPayload payload = getPayload();
-
-            // Read header
-            byte type = inputStream.readByte();
-            if (type != (byte) BLOCK_MARKER) {
-                throw blockCorruptedException();
-            }
-            type = inputStream.readByte();
-            if (type != (byte) payload.getType()) {
-                throw blockCorruptedException();
-            }
-
-            // Read body
-            payloadSize = inputStream.readInt();
-            if (pos + HEADER_SIZE + TAIL_SIZE + payloadSize > file.length()) {
-                throw blockCorruptedException();
-            }
-            payload.read(inputStream);
-
-            // Read and verify checksum
-            long actualChecksum = checkSumInputStream.checksum.getValue();
-            long checksum = inputStream.readLong();
-            if (actualChecksum != checksum) {
-                throw blockCorruptedException();
-            }
-            inputStream.close();
-        }
-
-        public RuntimeException blockCorruptedException() {
-            return new CorruptedCacheException(String.format("Corrupted %s found in %s.", this,
-                    FileBackedBlockStore.this));
-        }
-    }
-
-    private static class RandomAccessFileInputStream extends InputStream {
-        private final RandomAccessFile file;
-
-        private RandomAccessFileInputStream(RandomAccessFile file) {
-            this.file = file;
-        }
-
-        @Override
-        public int read(byte[] bytes) throws IOException {
-            return file.read(bytes);
-        }
-
-        @Override
-        public int read() throws IOException {
-            return file.read();
-        }
-
-        @Override
-        public int read(byte[] bytes, int offset, int length) throws IOException {
-            return file.read(bytes, offset, length);
-        }
-    }
-
-    private static class RandomAccessFileOutputStream extends OutputStream {
-        private final RandomAccessFile file;
-
-        private RandomAccessFileOutputStream(RandomAccessFile file) {
-            this.file = file;
-        }
-
-        @Override
-        public void write(int i) throws IOException {
-            file.write(i);
-        }
-
-        @Override
-        public void write(byte[] bytes) throws IOException {
-            file.write(bytes);
-        }
-
-        @Override
-        public void write(byte[] bytes, int offset, int length) throws IOException {
-            file.write(bytes, offset, length);
-        }
-    }
-
-    private static class Crc32InputStream extends FilterInputStream {
-        private final CRC32 checksum;
-
-        private Crc32InputStream(InputStream inputStream) {
-            super(inputStream);
-            checksum = new CRC32();
-        }
-
-        @Override
-        public int read() throws IOException {
-            int b = in.read();
-            if (b >= 0) {
-                checksum.update(b);
-            }
-            return b;
-        }
-
-        @Override
-        public int read(byte[] bytes) throws IOException {
-            int count = in.read(bytes);
-            if (count > 0) {
-                checksum.update(bytes, 0, count);
-            }
-            return count;
-        }
-
-        @Override
-        public int read(byte[] bytes, int offset, int max) throws IOException {
-            int count = in.read(bytes, offset, max);
-            if (count > 0) {
-                checksum.update(bytes, offset, count);
-            }
-            return count;
-        }
-    }
-
-    private static class Crc32OutputStream extends FilterOutputStream {
-        private final CRC32 checksum;
-
-        private Crc32OutputStream(OutputStream outputStream) {
-            super(outputStream);
-            this.checksum = new CRC32();
-        }
-
-        @Override
-        public void write(int b) throws IOException {
-            checksum.update(b);
-            out.write(b);
-        }
-
-        @Override
-        public void write(byte[] bytes) throws IOException {
-            checksum.update(bytes);
-            out.write(bytes);
-        }
-
-        @Override
-        public void write(byte[] bytes, int offset, int count) throws IOException {
-            checksum.update(bytes, offset, count);
-            out.write(bytes, offset, count);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/FreeListBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/FreeListBlockStore.java
deleted file mode 100644
index 7f4c4e0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/FreeListBlockStore.java
+++ /dev/null
@@ -1,271 +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.cache.btree;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class FreeListBlockStore implements BlockStore {
-    private final BlockStore store;
-    private final BlockStore freeListStore;
-    private final int maxBlockEntries;
-    private FreeListBlock freeListBlock;
-
-    public FreeListBlockStore(BlockStore store, int maxBlockEntries) {
-        this.store = store;
-        freeListStore = this;
-        this.maxBlockEntries = maxBlockEntries;
-    }
-
-    public void open(final Runnable initAction, final Factory factory) {
-        Runnable freeListInitAction = new Runnable() {
-            public void run() {
-                freeListBlock = new FreeListBlock();
-                store.write(freeListBlock);
-                store.flush();
-                initAction.run();
-            }
-        };
-        Factory freeListFactory = new Factory() {
-            public Object create(Class<? extends BlockPayload> type) {
-                if (type == FreeListBlock.class) {
-                    return new FreeListBlock();
-                }
-                return factory.create(type);
-            }
-        };
-
-        store.open(freeListInitAction, freeListFactory);
-        freeListBlock = store.readFirst(FreeListBlock.class);
-    }
-
-    public void close() {
-        freeListBlock = null;
-        store.close();
-    }
-
-    public void clear() {
-        store.clear();
-    }
-
-    public void remove(BlockPayload block) {
-        Block container = block.getBlock();
-        store.remove(block);
-        freeListBlock.add(container.getPos(), container.getSize());
-    }
-
-    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
-        return store.read(freeListBlock.getNextPos(), payloadType);
-    }
-
-    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
-        return store.read(pos, payloadType);
-    }
-
-    public void write(BlockPayload block) {
-        attach(block);
-        store.write(block);
-    }
-
-    public void attach(BlockPayload block) {
-        store.attach(block);
-        freeListBlock.alloc(block.getBlock());
-    }
-
-    public void flush() {
-        store.flush();
-    }
-
-    private void verify() {
-        FreeListBlock block = store.readFirst(FreeListBlock.class);
-        verify(block, Integer.MAX_VALUE);
-    }
-
-    private void verify(FreeListBlock block, int maxValue) {
-        if (block.largestInNextBlock > maxValue) {
-            throw new RuntimeException("corrupt free list");
-        }
-        int current = 0;
-        for (FreeListEntry entry : block.entries) {
-            if (entry.size > maxValue) {
-                throw new RuntimeException("corrupt free list");
-            }
-            if (entry.size < block.largestInNextBlock) {
-                throw new RuntimeException("corrupt free list");
-            }
-            if (entry.size < current) {
-                throw new RuntimeException("corrupt free list");
-            }
-            current = entry.size;
-        }
-        if (!block.nextBlock.isNull()) {
-            verify(store.read(block.nextBlock, FreeListBlock.class), block.largestInNextBlock);
-        }
-    }
-
-    public class FreeListBlock extends BlockPayload {
-        private List<FreeListEntry> entries = new ArrayList<FreeListEntry>();
-        private int largestInNextBlock;
-        private BlockPointer nextBlock = new BlockPointer();
-        // Transient fields
-        private FreeListBlock prev;
-        private FreeListBlock next;
-
-        @Override
-        protected int getSize() {
-            return Block.LONG_SIZE + Block.INT_SIZE + Block.INT_SIZE + maxBlockEntries * (Block.LONG_SIZE
-                    + Block.INT_SIZE);
-        }
-
-        @Override
-        protected int getType() {
-            return 0x44;
-        }
-
-        @Override
-        protected void read(DataInputStream inputStream) throws Exception {
-            nextBlock = new BlockPointer(inputStream.readLong());
-            largestInNextBlock = inputStream.readInt();
-            int count = inputStream.readInt();
-            for (int i = 0; i < count; i++) {
-                BlockPointer pos = new BlockPointer(inputStream.readLong());
-                int size = inputStream.readInt();
-                entries.add(new FreeListEntry(pos, size));
-            }
-        }
-
-        @Override
-        protected void write(DataOutputStream outputStream) throws Exception {
-            outputStream.writeLong(nextBlock.getPos());
-            outputStream.writeInt(largestInNextBlock);
-            outputStream.writeInt(entries.size());
-            for (FreeListEntry entry : entries) {
-                outputStream.writeLong(entry.pos.getPos());
-                outputStream.writeInt(entry.size);
-            }
-        }
-
-        public void add(BlockPointer pos, int size) {
-            assert !pos.isNull() && size >= 0;
-            if (size == 0) {
-                return;
-            }
-
-            if (size < largestInNextBlock) {
-                FreeListBlock next = getNextBlock();
-                next.add(pos, size);
-                return;
-            }
-
-            FreeListEntry entry = new FreeListEntry(pos, size);
-            int index = Collections.binarySearch(entries, entry);
-            if (index < 0) {
-                index = -index - 1;
-            }
-            entries.add(index, entry);
-
-            if (entries.size() > maxBlockEntries) {
-                FreeListBlock newBlock = new FreeListBlock();
-                newBlock.largestInNextBlock = largestInNextBlock;
-                newBlock.nextBlock = nextBlock;
-                newBlock.prev = this;
-                newBlock.next = next;
-                next = newBlock;
-
-                List<FreeListEntry> newBlockEntries = entries.subList(0, entries.size() / 2);
-                newBlock.entries.addAll(newBlockEntries);
-                newBlockEntries.clear();
-                largestInNextBlock = newBlock.entries.get(newBlock.entries.size() - 1).size;
-                freeListStore.write(newBlock);
-                nextBlock = newBlock.getPos();
-            }
-
-            freeListStore.write(this);
-        }
-
-        private FreeListBlock getNextBlock() {
-            if (next == null) {
-                next = freeListStore.read(nextBlock, FreeListBlock.class);
-                next.prev = this;
-            }
-            return next;
-        }
-
-        public void alloc(Block block) {
-            if (block.hasPos()) {
-                return;
-            }
-
-            int requiredSize = block.getSize();
-
-            if (entries.isEmpty() || requiredSize <= largestInNextBlock) {
-                if (nextBlock.isNull()) {
-                    return;
-                }
-                getNextBlock().alloc(block);
-                return;
-            }
-
-            int index = Collections.binarySearch(entries, new FreeListEntry(null, requiredSize));
-            if (index < 0) {
-                index = -index - 1;
-            }
-            if (index == entries.size()) {
-                // Largest free block is too small
-                return;
-            }
-
-            FreeListEntry entry = entries.remove(index);
-            block.setPos(entry.pos);
-            block.setSize(entry.size);
-            freeListStore.write(this);
-
-            if (entries.size() == 0 && prev != null) {
-                prev.nextBlock = nextBlock;
-                prev.largestInNextBlock = largestInNextBlock;
-                prev.next = next;
-                if (next != null) {
-                    next.prev = prev;
-                }
-                freeListStore.write(prev);
-                freeListStore.remove(this);
-            }
-        }
-    }
-
-    private static class FreeListEntry implements Comparable<FreeListEntry> {
-        final BlockPointer pos;
-        final int size;
-
-        private FreeListEntry(BlockPointer pos, int size) {
-            this.pos = pos;
-            this.size = size;
-        }
-
-        public int compareTo(FreeListEntry o) {
-            if (size > o.size) {
-                return 1;
-            }
-            if (size < o.size) {
-                return -1;
-            }
-            return 0;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/btree/StateCheckBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/btree/StateCheckBlockStore.java
deleted file mode 100644
index 5ad762c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/btree/StateCheckBlockStore.java
+++ /dev/null
@@ -1,78 +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.cache.btree;
-
-public class StateCheckBlockStore implements BlockStore {
-    private final BlockStore blockStore;
-    private boolean open;
-
-    public StateCheckBlockStore(BlockStore blockStore) {
-        this.blockStore = blockStore;
-    }
-
-    public void open(Runnable initAction, Factory factory) {
-        assert !open;
-        open = true;
-        blockStore.open(initAction, factory);
-    }
-
-    public boolean isOpen() {
-        return open;
-    }
-
-    public void close() {
-        if (!open) {
-            return;
-        }
-        open = false;
-        blockStore.close();
-    }
-
-    public void clear() {
-        assert open;
-        blockStore.clear();
-    }
-
-    public void remove(BlockPayload block) {
-        assert open;
-        blockStore.remove(block);
-    }
-
-    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
-        assert open;
-        return blockStore.readFirst(payloadType);
-    }
-
-    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
-        assert open;
-        return blockStore.read(pos, payloadType);
-    }
-
-    public void write(BlockPayload block) {
-        assert open;
-        blockStore.write(block);
-    }
-
-    public void attach(BlockPayload block) {
-        assert open;
-        blockStore.attach(block);
-    }
-
-    public void flush() {
-        assert open;
-        blockStore.flush();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java
new file mode 100644
index 0000000..75202ba
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.UncheckedException;
+
+import java.util.concurrent.Callable;
+
+public abstract class AbstractFileAccess implements FileAccess {
+    public <T> T readFromFile(final Callable<? extends T> action) throws LockTimeoutException {
+        return readFromFile(new Factory<T>() {
+            public T create() {
+                try {
+                    return action.call();
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
new file mode 100755
index 0000000..1b8d443
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.cache.internal;
+
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.cache.*;
+import org.gradle.cache.internal.FileLockManager.LockMode;
+
+import java.io.File;
+import java.util.Map;
+
+public interface CacheFactory {
+    PersistentCache openStore(File storeDir, String displayName, LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException;
+
+    PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException;
+
+    <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Serializer<E> serializer) throws CacheOpenException;
+
+    <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Serializer<V> serializer) throws CacheOpenException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
new file mode 100644
index 0000000..d8d3438
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import net.jcip.annotations.ThreadSafe;
+import org.gradle.internal.Factory;
+import org.gradle.cache.CacheAccess;
+import org.gradle.cache.DefaultSerializer;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive;
+
+ at ThreadSafe
+public class DefaultCacheAccess implements CacheAccess {
+    private final String cacheDiplayName;
+    private final File lockFile;
+    private final FileLockManager lockManager;
+    private final FileAccess fileAccess = new UnitOfWorkFileAccess();
+    private final Set<MultiProcessSafePersistentIndexedCache<?, ?>> caches = new HashSet<MultiProcessSafePersistentIndexedCache<?, ?>>();
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private Thread owner;
+    private FileLockManager.LockMode lockMode;
+    private FileLock fileLock;
+    private boolean started;
+    private final List<String> operationStack = new ArrayList<String>();
+
+    public DefaultCacheAccess(String cacheDisplayName, File lockFile, FileLockManager lockManager) {
+        this.cacheDiplayName = cacheDisplayName;
+        this.lockFile = lockFile;
+        this.lockManager = lockManager;
+    }
+
+    /**
+     * Opens this cache access with the given lock mode. Calling this with {@link org.gradle.cache.internal.FileLockManager.LockMode#Exclusive} will
+     * lock the cache for exclusive access from all other threads (including those in this process and all other processes), until
+     * {@link #close()} is called.
+     */
+    public void open(FileLockManager.LockMode lockMode) {
+        lock.lock();
+        try {
+            if (owner != null) {
+                throw new IllegalStateException(String.format("Cannot open the %s, as it is already in use.", cacheDiplayName));
+            }
+            this.lockMode = lockMode;
+            if (lockMode == FileLockManager.LockMode.None) {
+                return;
+            }
+            started = true;
+            fileLock = lockManager.lock(lockFile, lockMode, cacheDiplayName);
+            lockCache(String.format("Access %s", cacheDiplayName));
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void close() {
+        lock.lock();
+        try {
+            for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
+                cache.close();
+            }
+            operationStack.clear();
+            started = false;
+            lockMode = null;
+            owner = null;
+            if (fileLock != null) {
+                try {
+                    fileLock.close();
+                } finally {
+                    fileLock = null;
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public FileLock getFileLock() {
+        return fileLock;
+    }
+
+    public void useCache(String operationDisplayName, final Runnable action) {
+        useCache(operationDisplayName, new Factory<Object>() {
+            public Object create() {
+                action.run();
+                return null;
+            }
+        });
+    }
+
+    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+        if (lockMode == FileLockManager.LockMode.Shared) {
+            throw new UnsupportedOperationException("Not implemented yet.");
+        }
+
+        lockCache(operationDisplayName);
+        try {
+            boolean wasStarted = onStartWork();
+            try {
+                return action.create();
+            } finally {
+                if (wasStarted) {
+                    onEndWork();
+                }
+            }
+        } finally {
+            unlockCache();
+        }
+    }
+
+    private void lockCache(String operationDisplayName) {
+        lock.lock();
+        try {
+            while (owner != null && owner != Thread.currentThread()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+            owner = Thread.currentThread();
+            operationStack.add(0, operationDisplayName);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void unlockCache() {
+        lock.lock();
+        try {
+            operationStack.remove(0);
+            if (operationStack.isEmpty()) {
+                owner = null;
+                condition.signalAll();
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
+        startLongRunningOperation();
+        try {
+            boolean wasEnded = onEndWork();
+            try {
+                return action.create();
+            } finally {
+                if (wasEnded) {
+                    onStartWork();
+                }
+            }
+        } finally {
+            endLongRunningOperation();
+        }
+    }
+
+    private void startLongRunningOperation() {
+        lock.lock();
+        try {
+            if (owner != Thread.currentThread()) {
+                throw new IllegalStateException(String.format("Cannot start long running operation, as the %s has not been locked.", cacheDiplayName));
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void endLongRunningOperation() {
+    }
+
+    public void longRunningOperation(String operationDisplayName, final Runnable action) {
+        longRunningOperation(operationDisplayName, new Factory<Object>() {
+            public Object create() {
+                action.run();
+                return null;
+            }
+        });
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Class<K> keyType, final Class<V> valueType) {
+        return newCache(cacheFile, keyType, new DefaultSerializer<V>(valueType.getClassLoader()));
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> newCache(final File cacheFile, final Class<K> keyType, final Serializer<V> valueSerializer) {
+        Factory<BTreePersistentIndexedCache<K, V>> indexedCacheFactory = new Factory<BTreePersistentIndexedCache<K, V>>() {
+            public BTreePersistentIndexedCache<K, V> create() {
+                return doCreateCache(cacheFile, new DefaultSerializer<K>(keyType.getClassLoader()), valueSerializer);
+            }
+        };
+        MultiProcessSafePersistentIndexedCache<K, V> indexedCache = new MultiProcessSafePersistentIndexedCache<K, V>(indexedCacheFactory, fileAccess);
+        lock.lock();
+        try {
+            caches.add(indexedCache);
+            if (started) {
+                indexedCache.onStartWork(operationStack.get(0));
+            }
+        } finally {
+            lock.unlock();
+        }
+        return indexedCache;
+    }
+
+    <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(final File cacheFile, final Serializer<K> keySerializer, final Serializer<V> valueSerializer) {
+        return new BTreePersistentIndexedCache<K, V>(cacheFile, keySerializer, valueSerializer);
+    }
+
+    private boolean onStartWork() {
+        if (started) {
+            return false;
+        }
+
+        started = true;
+        for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
+            cache.onStartWork(operationStack.get(0));
+        }
+        return true;
+    }
+
+    private boolean onEndWork() {
+        if (!started) {
+            return false;
+        }
+
+        try {
+            for (MultiProcessSafePersistentIndexedCache<?, ?> cache : caches) {
+                cache.onEndWork();
+            }
+            if (fileLock != null) {
+                fileLock.close();
+            }
+        } finally {
+            started = false;
+            fileLock = null;
+        }
+        return true;
+    }
+
+    private FileLock getLock() {
+        lock.lock();
+        try {
+            if (Thread.currentThread() != owner || !started) {
+                throw new IllegalStateException(String.format("The %s has not been locked.", cacheDiplayName));
+            }
+        } finally {
+            lock.unlock();
+        }
+        if (fileLock == null) {
+            fileLock = lockManager.lock(lockFile, Exclusive, cacheDiplayName, operationStack.get(0));
+        }
+        return fileLock;
+    }
+
+    private class UnitOfWorkFileAccess extends AbstractFileAccess {
+        public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
+            return getLock().readFromFile(action);
+        }
+
+        public void writeToFile(Runnable action) throws LockTimeoutException {
+            getLock().writeToFile(action);
+        }
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
new file mode 100755
index 0000000..50c4131
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
@@ -0,0 +1,239 @@
+/*
+ * 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.cache.internal;
+
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.internal.Factory;
+import org.gradle.cache.*;
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.*;
+
+import static org.gradle.cache.internal.FileLockManager.LockMode;
+
+public class DefaultCacheFactory implements Factory<CacheFactory> {
+    private final Map<File, DirCacheReference> dirCaches = new HashMap<File, DirCacheReference>();
+    private final FileLockManager lockManager;
+
+    public DefaultCacheFactory(FileLockManager fileLockManager) {
+        this.lockManager = fileLockManager;
+    }
+
+    public CacheFactory create() {
+        return new CacheFactoryImpl();
+    }
+
+    void onOpen(Object cache) {
+    }
+
+    void onClose(Object cache) {
+    }
+
+    public void close() {
+        for (DirCacheReference dirCacheReference : dirCaches.values()) {
+            dirCacheReference.close();
+        }
+    }
+
+    private class CacheFactoryImpl implements CacheFactory {
+        private final Set<BasicCacheReference<?>> caches = new LinkedHashSet<BasicCacheReference<?>>();
+
+        private DirCacheReference doOpenDir(File cacheDir, String displayName, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Action<? super PersistentCache> action) {
+            File canonicalDir = GFileUtils.canonicalise(cacheDir);
+            DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
+            if (dirCacheReference == null) {
+                DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, lockMode, action, lockManager);
+                cache.open();
+                dirCacheReference = new DirCacheReference(cache, properties, lockMode);
+                dirCaches.put(canonicalDir, dirCacheReference);
+            } else {
+                if (usage == CacheUsage.REBUILD && dirCacheReference.rebuiltBy != this) {
+                    throw new IllegalStateException(String.format("Cannot rebuild cache '%s' as it is already open.", cacheDir));
+                }
+                if (lockMode != dirCacheReference.lockMode) {
+                    throw new IllegalStateException(String.format("Cannot open cache '%s' with %s lock mode as it is already open with %s lock mode.", cacheDir, lockMode.toString().toLowerCase(), dirCacheReference.lockMode.toString().toLowerCase()));
+                }
+                if (!properties.equals(dirCacheReference.properties)) {
+                    throw new IllegalStateException(String.format("Cache '%s' is already open with different state.", cacheDir));
+                }
+            }
+            if (usage == CacheUsage.REBUILD) {
+                dirCacheReference.rebuiltBy = this;
+            }
+            dirCacheReference.addReference(this);
+            return dirCacheReference;
+        }
+
+        public PersistentCache openStore(File storeDir, String displayName, LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException {
+            if (initializer != null) {
+                throw new UnsupportedOperationException("Initializer actions are not currently supported by the directory store implementation.");
+            }
+            File canonicalDir = GFileUtils.canonicalise(storeDir);
+            DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
+            if (dirCacheReference == null) {
+                DefaultPersistentDirectoryStore cache = new DefaultPersistentDirectoryStore(canonicalDir, displayName, lockMode, lockManager);
+                cache.open();
+                dirCacheReference = new DirCacheReference(cache, Collections.<String, Object>emptyMap(), lockMode);
+                dirCaches.put(canonicalDir, dirCacheReference);
+            }
+            dirCacheReference.addReference(this);
+            return dirCacheReference.getCache();
+        }
+
+        public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initializer) {
+            DirCacheReference dirCacheReference = doOpenDir(cacheDir, displayName, usage, cacheValidator, properties, lockMode, initializer);
+            return dirCacheReference.getCache();
+        }
+
+        public <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Serializer<E> serializer) {
+            StateCacheReference<E> cacheReference = doOpenDir(cacheDir, null, usage, validator, properties, lockMode, null).getStateCache(serializer);
+            cacheReference.addReference(this);
+            return cacheReference.getCache();
+        }
+
+        public <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Serializer<V> serializer) {
+            if (lockMode != LockMode.Exclusive) {
+                throw new UnsupportedOperationException(String.format("No %s mode indexed cache implementation is available.", lockMode));
+            }
+            IndexedCacheReference<K, V> cacheReference = doOpenDir(cacheDir, null, usage, validator, properties, LockMode.Exclusive, null).getIndexedCache(serializer);
+            cacheReference.addReference(this);
+            return cacheReference.getCache();
+        }
+
+        public void close() {
+            try {
+                List<BasicCacheReference<?>> caches = new ArrayList<BasicCacheReference<?>>(this.caches);
+                Collections.reverse(caches);
+                for (BasicCacheReference cache : caches) {
+                    cache.release(this);
+                }
+            } finally {
+                caches.clear();
+            }
+        }
+    }
+
+    private abstract class BasicCacheReference<T> {
+        private Set<CacheFactoryImpl> references = new HashSet<CacheFactoryImpl>();
+        private final T cache;
+
+        protected BasicCacheReference(T cache) {
+            this.cache = cache;
+            onOpen(cache);
+        }
+
+        public T getCache() {
+            return cache;
+        }
+
+        public void release(CacheFactoryImpl owner) {
+            boolean removed = references.remove(owner);
+            assert removed;
+            if (references.isEmpty()) {
+                onClose(cache);
+                close();
+            }
+        }
+
+        public void addReference(CacheFactoryImpl owner) {
+            references.add(owner);
+            owner.caches.add(this);
+        }
+
+        public void close() {
+        }
+    }
+
+    private class DirCacheReference extends BasicCacheReference<DefaultPersistentDirectoryStore> {
+        private final Map<String, ?> properties;
+        private final FileLockManager.LockMode lockMode;
+        IndexedCacheReference indexedCache;
+        StateCacheReference stateCache;
+        CacheFactoryImpl rebuiltBy;
+
+        public DirCacheReference(DefaultPersistentDirectoryStore cache, Map<String, ?> properties, FileLockManager.LockMode lockMode) {
+            super(cache);
+            this.properties = properties;
+            this.lockMode = lockMode;
+        }
+
+        public <E> StateCacheReference<E> getStateCache(Serializer<E> serializer) {
+            if (stateCache == null) {
+                SimpleStateCache<E> stateCache = new SimpleStateCache<E>(new File(getCache().getBaseDir(), "state.bin"), getCache().getLock(), serializer);
+                this.stateCache = new StateCacheReference<E>(stateCache, this);
+            }
+            return stateCache;
+        }
+
+        public <K, V> IndexedCacheReference<K, V> getIndexedCache(Serializer<V> serializer) {
+            if (indexedCache == null) {
+                final BTreePersistentIndexedCache<K, V> indexedCache = new BTreePersistentIndexedCache<K, V>(new File(getCache().getBaseDir(), "cache.bin"),
+                        new DefaultSerializer<K>(),
+                        serializer);
+                Factory<BTreePersistentIndexedCache<K, V>> cacheFactory = new Factory<BTreePersistentIndexedCache<K, V>>() {
+                    public BTreePersistentIndexedCache<K, V> create() {
+                        return indexedCache;
+                    }
+                };
+                MultiProcessSafePersistentIndexedCache<K, V> safeCache = new MultiProcessSafePersistentIndexedCache<K, V>(cacheFactory, getCache().getLock());
+                this.indexedCache = new IndexedCacheReference<K, V>(safeCache, this);
+            }
+            return indexedCache;
+        }
+
+        public void close() {
+            dirCaches.values().remove(this);
+            getCache().close();
+        }
+    }
+
+    private abstract class NestedCacheReference<T> extends BasicCacheReference<T> {
+        protected final DefaultCacheFactory.DirCacheReference backingCache;
+
+        protected NestedCacheReference(T cache, DirCacheReference backingCache) {
+            super(cache);
+            this.backingCache = backingCache;
+        }
+    }
+
+    private class IndexedCacheReference<K, V> extends NestedCacheReference<MultiProcessSafePersistentIndexedCache<K, V>> {
+        private IndexedCacheReference(MultiProcessSafePersistentIndexedCache<K, V> cache, DirCacheReference backingCache) {
+            super(cache, backingCache);
+        }
+
+        @Override
+        public void close() {
+            backingCache.indexedCache = null;
+            getCache().close();
+            super.close();
+        }
+    }
+
+    private class StateCacheReference<E> extends NestedCacheReference<SimpleStateCache<E>> {
+        private StateCacheReference(SimpleStateCache<E> cache, DirCacheReference backingCache) {
+            super(cache, backingCache);
+        }
+
+        @Override
+        public void close() {
+            backingCache.stateCache = null;
+            super.close();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
new file mode 100644
index 0000000..50dae8f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
@@ -0,0 +1,252 @@
+/*
+ * 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.cache.internal;
+
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.*;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.cache.internal.FileLockManager.LockMode;
+
+public class DefaultCacheRepository implements CacheRepository {
+    private final GradleVersion version = GradleVersion.current();
+    private final File globalCacheDir;
+    private final CacheUsage cacheUsage;
+    private final File projectCacheDir;
+    private final CacheFactory factory;
+
+    public DefaultCacheRepository(File userHomeDir, File projectCacheDir, CacheUsage cacheUsage, CacheFactory factory) {
+        this.projectCacheDir = projectCacheDir;
+        this.factory = factory;
+        this.globalCacheDir = new File(userHomeDir, "caches");
+        this.cacheUsage = cacheUsage;
+    }
+
+    public DirectoryCacheBuilder store(String key) {
+        return new PersistentStoreBuilder(key);
+    }
+
+    public DirectoryCacheBuilder cache(String key) {
+        return new PersistentCacheBuilder(key);
+    }
+
+    public <E> ObjectCacheBuilder<E, PersistentStateCache<E>> stateCache(Class<E> elementType, String key) {
+        return new StateCacheBuilder<E>(key);
+    }
+
+    public <K, V> ObjectCacheBuilder<V, PersistentIndexedCache<K, V>> indexedCache(Class<K> keyType, Class<V> elementType, String key) {
+        return new IndexedCacheBuilder<K, V>(key);
+    }
+
+    private abstract class AbstractCacheBuilder<T> implements CacheBuilder<T> {
+        private final String key;
+        private Map<String, ?> properties = Collections.emptyMap();
+        private Object target;
+        private VersionStrategy versionStrategy = VersionStrategy.CachePerVersion;
+        private CacheValidator validator;
+
+        protected AbstractCacheBuilder(String key) {
+            this.key = key;
+        }
+
+        public CacheBuilder<T> withProperties(Map<String, ?> properties) {
+            this.properties = properties;
+            return this;
+        }
+
+        public CacheBuilder<T> withVersionStrategy(VersionStrategy strategy) {
+            this.versionStrategy = strategy;
+            return this;
+        }
+
+        public CacheBuilder<T> forObject(Object target) {
+            this.target = target;
+            return this;
+        }
+
+        public CacheBuilder<T> withValidator(CacheValidator validator) {
+            this.validator = validator;
+            return this;
+        }
+
+        public T open() {
+            File cacheBaseDir;
+            Map<String, Object> properties = new HashMap<String, Object>(this.properties);
+            if (target == null) {
+                cacheBaseDir = globalCacheDir;
+            } else if (target instanceof Gradle) {
+                Gradle gradle = (Gradle) target;
+                File rootProjectDir = gradle.getRootProject().getProjectDir();
+                cacheBaseDir = maybeProjectCacheDir(rootProjectDir);
+            } else if (target instanceof File) {
+                cacheBaseDir = new File((File) target, ".gradle");
+            } else {
+                throw new IllegalArgumentException(String.format("Cannot create cache for unrecognised domain object %s.", target));
+            }
+            switch (versionStrategy) {
+                case SharedCache:
+                    // Use the root directory
+                    break;
+                case CachePerVersion:
+                    cacheBaseDir = new File(cacheBaseDir, version.getVersion());
+                    break;
+                case SharedCacheInvalidateOnVersionChange:
+                    // Include the 'noVersion' suffix for backwards compatibility
+                    cacheBaseDir = new File(cacheBaseDir, "noVersion");
+                    properties.put("gradle.version", version.getVersion());
+                    break;
+            }
+            return doOpen(new File(cacheBaseDir, key), properties, validator);
+        }
+
+        protected abstract T doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator);
+
+        private File maybeProjectCacheDir(File potentialParentDir) {
+            if (projectCacheDir != null) {
+                return projectCacheDir;
+            }
+            return new File(potentialParentDir, ".gradle");
+        }
+    }
+
+    private class PersistentCacheBuilder extends AbstractCacheBuilder<PersistentCache> implements DirectoryCacheBuilder {
+        Action<? super PersistentCache> initializer;
+        LockMode lockMode = LockMode.Shared;
+        String displayName;
+
+        protected PersistentCacheBuilder(String key) {
+            super(key);
+        }
+
+        @Override
+        public DirectoryCacheBuilder forObject(Object target) {
+            super.forObject(target);
+            return this;
+        }
+
+        @Override
+        public DirectoryCacheBuilder withProperties(Map<String, ?> properties) {
+            super.withProperties(properties);
+            return this;
+        }
+
+        @Override
+        public DirectoryCacheBuilder withVersionStrategy(VersionStrategy strategy) {
+            super.withVersionStrategy(strategy);
+            return this;
+        }
+
+        @Override
+        public DirectoryCacheBuilder withValidator(CacheValidator validator) {
+            super.withValidator(validator);
+            return this;
+        }
+
+        public DirectoryCacheBuilder withInitializer(Action<? super PersistentCache> initializer) {
+            this.initializer = initializer;
+            return this;
+        }
+
+        public DirectoryCacheBuilder withDisplayName(String displayName) {
+            this.displayName = displayName;
+            return this;
+        }
+
+        public DirectoryCacheBuilder withLockMode(LockMode lockMode) {
+            this.lockMode = lockMode;
+            return this;
+        }
+
+        @Override
+        protected PersistentCache doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
+            return factory.open(cacheDir, displayName, cacheUsage, validator, properties, lockMode, initializer);
+        }
+    }
+
+    private class PersistentStoreBuilder extends PersistentCacheBuilder {
+        private PersistentStoreBuilder(String key) {
+            super(key);
+        }
+
+        @Override
+        protected PersistentCache doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
+            if (!properties.isEmpty()) {
+                throw new UnsupportedOperationException("Properties are not supported for stores.");
+            }
+            return factory.openStore(cacheDir, displayName, lockMode, initializer);
+        }
+    }
+
+    private abstract class AbstractObjectCacheBuilder<E, T> extends AbstractCacheBuilder<T> implements ObjectCacheBuilder<E, T> {
+        protected Serializer<E> serializer = new DefaultSerializer<E>();
+
+        protected AbstractObjectCacheBuilder(String key) {
+            super(key);
+        }
+
+        @Override
+        public ObjectCacheBuilder<E, T> forObject(Object target) {
+            super.forObject(target);
+            return this;
+        }
+
+        @Override
+        public ObjectCacheBuilder<E, T> withProperties(Map<String, ?> properties) {
+            super.withProperties(properties);
+            return this;
+        }
+
+        @Override
+        public ObjectCacheBuilder<E, T> withVersionStrategy(VersionStrategy strategy) {
+            super.withVersionStrategy(strategy);
+            return this;
+        }
+
+        public ObjectCacheBuilder<E, T> withSerializer(Serializer<E> serializer) {
+            this.serializer = serializer;
+            return this;
+        }
+    }
+
+    private class StateCacheBuilder<E> extends AbstractObjectCacheBuilder<E, PersistentStateCache<E>>  {
+        protected StateCacheBuilder(String key) {
+            super(key);
+        }
+
+        @Override
+        protected PersistentStateCache<E> doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
+            return factory.openStateCache(cacheDir, cacheUsage, validator, properties, LockMode.Exclusive, serializer);
+        }
+    }
+
+    private class IndexedCacheBuilder<K, V> extends AbstractObjectCacheBuilder<V, PersistentIndexedCache<K, V>> {
+        private IndexedCacheBuilder(String key) {
+            super(key);
+        }
+
+        @Override
+        protected PersistentIndexedCache<K, V> doOpen(File cacheDir, Map<String, ?> properties, CacheValidator validator) {
+            return factory.openIndexedCache(cacheDir, cacheUsage, validator, properties, LockMode.Exclusive, serializer);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
new file mode 100644
index 0000000..03520b3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.GFileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Uses file system locks on a lock file per target file. Each lock file is made up of 2 regions:
+ *
+ * <ul>
+ *     <li>State region: 1 byte version field, 1 byte clean flag.</li>
+ *     <li>Owner information region: 1 byte version field, utf-8 encoded owner process id, utf-8 encoded owner operation display name.</li>
+ * </ul>
+ */
+public class DefaultFileLockManager implements FileLockManager {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileLockManager.class);
+    private static final int LOCK_TIMEOUT = 60000;
+    private static final byte STATE_REGION_PROTOCOL = 1;
+    private static final int STATE_REGION_SIZE = 2;
+    private static final int STATE_REGION_POS = 0;
+    private static final byte INFORMATION_REGION_PROTOCOL = 2;
+    private static final int INFORMATION_REGION_POS = STATE_REGION_POS + STATE_REGION_SIZE;
+    private final Set<File> lockedFiles = new CopyOnWriteArraySet<File>();
+    private final ProcessMetaDataProvider metaDataProvider;
+
+    public DefaultFileLockManager(ProcessMetaDataProvider metaDataProvider) {
+        this.metaDataProvider = metaDataProvider;
+    }
+
+    public FileLock lock(File target, LockMode mode, String targetDisplayName) throws LockTimeoutException {
+        return lock(target, mode, targetDisplayName, "");
+    }
+
+    public FileLock lock(File target, LockMode mode, String targetDisplayName, String operationDisplayName) {
+        if (mode == LockMode.None) {
+            throw new UnsupportedOperationException(String.format("No %s mode lock implementation available.", mode));
+        }
+        File canonicalTarget = GFileUtils.canonicalise(target);
+        if (!lockedFiles.add(canonicalTarget)) {
+            throw new IllegalStateException(String.format("Cannot lock %s as it has already been locked by this process.", targetDisplayName));
+        }
+        try {
+            return new DefaultFileLock(canonicalTarget, mode, targetDisplayName, operationDisplayName);
+        } catch (Throwable t) {
+            lockedFiles.remove(canonicalTarget);
+            throw UncheckedException.throwAsUncheckedException(t);
+        }
+    }
+
+    private class DefaultFileLock extends AbstractFileAccess implements FileLock {
+        private final File lockFile;
+        private final File target;
+        private final LockMode mode;
+        private final String displayName;
+        private final String operationDisplayName;
+        private java.nio.channels.FileLock lock;
+        private RandomAccessFile lockFileAccess;
+
+        public DefaultFileLock(File target, LockMode mode, String displayName, String operationDisplayName) throws Throwable {
+            this.target = target;
+            this.mode = mode;
+            this.displayName = displayName;
+            this.operationDisplayName = operationDisplayName;
+            if (target.isDirectory()) {
+                lockFile = new File(target, target.getName() + ".lock");
+            } else {
+                lockFile = new File(target.getParentFile(), target.getName() + ".lock");
+            }
+            lockFile.getParentFile().mkdirs();
+            lockFile.createNewFile();
+            lockFileAccess = new RandomAccessFile(lockFile, "rw");
+            try {
+                lock = lock(mode);
+            } catch (Throwable t) {
+                // Also releases any locks
+                lockFileAccess.close();
+                throw t;
+            }
+        }
+
+        public boolean isLockFile(File file) {
+            return file.equals(lockFile);
+        }
+
+        public boolean getUnlockedCleanly() {
+            return readFromFile(new Callable<Boolean>() {
+                public Boolean call() throws Exception {
+                    try {
+                        lockFileAccess.seek(STATE_REGION_POS + 1);
+                        if (!lockFileAccess.readBoolean()) {
+                            // Process has crashed while updating target file
+                            return false;
+                        }
+                    } catch (EOFException e) {
+                        // Process has crashed writing to lock file
+                        return false;
+                    }
+                    return true;
+                }
+            });
+        }
+
+        public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
+            assertOpen();
+            return action.create();
+        }
+
+        public void writeToFile(Runnable action) {
+            assertOpen();
+            try {
+                // TODO - need to escalate without releasing lock
+                java.nio.channels.FileLock updateLock = null;
+                if (mode != LockMode.Exclusive) {
+                    lock.release();
+                    lock = null;
+                    updateLock = lock(LockMode.Exclusive);
+                }
+                try {
+                    markDirty();
+                    action.run();
+                    markClean();
+                } finally {
+                    if (mode != LockMode.Exclusive) {
+                        updateLock.release();
+                        lock = lock(mode);
+                    }
+                }
+            } catch (Throwable t) {
+                throw UncheckedException.throwAsUncheckedException(t);
+            }
+        }
+
+        private void assertOpen() {
+            if (lock == null) {
+                throw new IllegalStateException("This lock has been closed.");
+            }
+        }
+
+        private void markClean() throws IOException {
+            lockFileAccess.seek(STATE_REGION_POS);
+            lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
+            lockFileAccess.writeBoolean(true);
+            assert lockFileAccess.getFilePointer() == STATE_REGION_SIZE + STATE_REGION_POS;
+        }
+
+        private void markDirty() throws IOException {
+            lockFileAccess.seek(STATE_REGION_POS);
+            lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
+            lockFileAccess.writeBoolean(false);
+            assert lockFileAccess.getFilePointer() == STATE_REGION_SIZE + STATE_REGION_POS;
+        }
+
+        public void close() {
+            if (lockFileAccess == null) {
+                return;
+            }
+            try {
+                LOGGER.debug("Releasing lock on {}.", displayName);
+                lockedFiles.remove(target);
+                // Also releases any locks
+                try {
+                    if (lock != null && !lock.isShared()) {
+                        // Discard information region
+                        lockFileAccess.setLength(INFORMATION_REGION_POS);
+                    }
+                } finally {
+                    lockFileAccess.close();
+                }
+            } catch (IOException e) {
+                LOGGER.warn("Error releasing lock on {}: {}", displayName, e);
+            } finally {
+                lock = null;
+                lockFileAccess = null;
+            }
+        }
+
+        private java.nio.channels.FileLock lock(FileLockManager.LockMode lockMode) throws Throwable {
+            LOGGER.debug("Waiting to acquire {} lock on {}.", lockMode.toString().toLowerCase(), displayName);
+            long timeout = System.currentTimeMillis() + LOCK_TIMEOUT;
+
+            // Lock the state region, with the requested mode
+            java.nio.channels.FileLock stateRegionLock = lockStateRegion(lockMode, timeout);
+            if (stateRegionLock == null) {
+                // Can't acquire lock, get details of owner to include in the error message
+                String ownerPid = "unknown";
+                String ownerOperation = "unknown";
+                java.nio.channels.FileLock informationRegionLock = lockInformationRegion(LockMode.Shared, timeout);
+                if (informationRegionLock == null) {
+                    LOGGER.debug("Could not lock information region for {}. Ignoring.", displayName);
+                } else {
+                    try {
+                        if (lockFileAccess.length() < INFORMATION_REGION_POS) {
+                            LOGGER.debug("Lock file for {} is too short to contain information region. Ignoring.", displayName);
+                        } else {
+                            lockFileAccess.seek(INFORMATION_REGION_POS);
+                            if (lockFileAccess.readByte() != INFORMATION_REGION_PROTOCOL) {
+                                throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file '%s' for %s.", lockFile, displayName));
+                            }
+                            ownerPid = lockFileAccess.readUTF();
+                            ownerOperation= lockFileAccess.readUTF();
+                        }
+                    } finally {
+                        informationRegionLock.release();
+                    }
+                }
+
+                throw new LockTimeoutException(String.format("Timeout waiting to lock %s. It is currently in use by another Gradle instance.%nOwner PID: %s%nOur PID: %s%nOwner Operation: %s%nOur operation: %s%nLock file: %s",
+                        displayName, metaDataProvider.getProcessIdentifier(), ownerPid, ownerOperation, operationDisplayName, lockFile));
+            }
+
+            try {
+                if (lockFileAccess.length() > 0) {
+                    lockFileAccess.seek(STATE_REGION_POS);
+                    if (lockFileAccess.readByte() != STATE_REGION_PROTOCOL) {
+                        throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file '%s' for %s.", lockFile, displayName));
+                    }
+                }
+
+                if (!stateRegionLock.isShared()) {
+                    // We have an exclusive lock (whether we asked for it or not).
+                    // Update the state region
+                    if (lockFileAccess.length() < STATE_REGION_SIZE) {
+                        // File did not exist before locking
+                        lockFileAccess.seek(STATE_REGION_POS);
+                        lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
+                        lockFileAccess.writeBoolean(false);
+                    }
+                    // Acquire an exclusive lock on the information region and write our details there
+                    java.nio.channels.FileLock informationRegionLock = lockInformationRegion(LockMode.Exclusive, timeout);
+                    if (informationRegionLock == null) {
+                        throw new IllegalStateException(String.format("Timeout waiting to lock the information region for lock %s", displayName));
+                    }
+                    try {
+                        lockFileAccess.seek(INFORMATION_REGION_POS);
+                        lockFileAccess.writeByte(INFORMATION_REGION_PROTOCOL);
+                        lockFileAccess.writeUTF(metaDataProvider.getProcessIdentifier());
+                        lockFileAccess.writeUTF(operationDisplayName);
+                        lockFileAccess.setLength(lockFileAccess.getFilePointer());
+                    } finally {
+                        informationRegionLock.release();
+                    }
+                }
+            } catch (Throwable t) {
+                stateRegionLock.release();
+                throw t;
+            }
+
+            LOGGER.debug("Lock acquired.");
+            return stateRegionLock;
+        }
+
+        private java.nio.channels.FileLock lockStateRegion(LockMode lockMode, long timeout) throws IOException, InterruptedException {
+            return lockRegion(lockMode, timeout, STATE_REGION_POS, STATE_REGION_SIZE);
+        }
+
+        private java.nio.channels.FileLock lockInformationRegion(LockMode lockMode, long timeout) throws IOException, InterruptedException {
+            return lockRegion(lockMode, timeout, INFORMATION_REGION_POS, Long.MAX_VALUE - INFORMATION_REGION_POS);
+        }
+
+        private java.nio.channels.FileLock lockRegion(FileLockManager.LockMode lockMode, long timeout, long start, long size) throws IOException, InterruptedException {
+            do {
+                java.nio.channels.FileLock fileLock = lockFileAccess.getChannel().tryLock(start, size, lockMode == LockMode.Shared);
+                if (fileLock != null) {
+                    return fileLock;
+                }
+                Thread.sleep(200L);
+            } while (System.currentTimeMillis() < timeout);
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
new file mode 100644
index 0000000..a9da3f0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.PersistentCache;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.gradle.cache.internal.FileLockManager.LockMode;
+
+public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryStore {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPersistentDirectoryCache.class);
+    private final File propertiesFile;
+    private final Properties properties = new Properties();
+    private final CacheUsage cacheUsage;
+    private final Action<? super PersistentCache> initAction;
+    private final CacheValidator validator;
+
+    public DefaultPersistentDirectoryCache(File dir, String displayName, CacheUsage cacheUsage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initAction, FileLockManager lockManager) {
+        super(dir, displayName, lockMode, lockManager);
+        if (lockMode == LockMode.None) {
+            throw new UnsupportedOperationException("Locking mode None is not supported.");
+        }
+        this.validator = validator;
+        this.cacheUsage = cacheUsage;
+        this.initAction = initAction;
+        propertiesFile = new File(dir, "cache.properties");
+        this.properties.putAll(properties);
+    }
+
+    @Override
+    protected File getLockTarget() {
+        // Lock the properties file, instead of the directory, for backwards compatibility
+        return propertiesFile;
+    }
+
+    protected void init() throws IOException {
+        boolean valid = determineIfCacheIsValid(getLock());
+        if (!valid) {
+            // Escalate to exclusive lock and rebuild the cache
+            getLock().writeToFile(new Runnable() {
+                public void run() {
+                    buildCacheDir(initAction, getLock());
+                }
+            });
+        }
+    }
+
+    private void buildCacheDir(Action<? super PersistentCache> initAction, FileLock fileLock) {
+        for (File file : getBaseDir().listFiles()) {
+            if (fileLock.isLockFile(file) || file.equals(propertiesFile)) {
+                continue;
+            }
+            GFileUtils.forceDelete(file);
+        }
+        if (initAction != null) {
+            initAction.execute(this);
+        }
+        GUtil.saveProperties(properties, propertiesFile);
+    }
+
+    private boolean determineIfCacheIsValid(FileLock lock) throws IOException {
+        if (cacheUsage != CacheUsage.ON) {
+            LOGGER.debug("Invalidating {} as cache usage is set to rebuild.", this);
+            return false;
+        }
+        if (validator!=null && !validator.isValid()) {
+            LOGGER.debug("Invalidating {} as cache validator return false.", this);
+            return false;
+        }
+
+        if (!lock.getUnlockedCleanly()) {
+            LOGGER.debug("Invalidating {} as it was not closed cleanly.", this);
+            return false;
+        }
+        Properties currentProperties = GUtil.loadProperties(propertiesFile);
+        for (Map.Entry<?, ?> entry : properties.entrySet()) {
+            if (!entry.getValue().toString().equals(currentProperties.getProperty(entry.getKey().toString()))) {
+                LOGGER.debug("Invalidating {} as cache property {} has changed value.", this, entry.getKey());
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public Properties getProperties() {
+        return properties;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
new file mode 100644
index 0000000..e516256
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.cache.CacheOpenException;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DefaultPersistentDirectoryStore implements PersistentCache {
+    private final File dir;
+    private final FileLockManager.LockMode lockMode;
+    private final FileLockManager lockManager;
+    private final String displayName;
+    private DefaultCacheAccess cacheAccess;
+
+    public DefaultPersistentDirectoryStore(File dir, String displayName, FileLockManager.LockMode lockMode, FileLockManager fileLockManager) {
+        this.dir = dir;
+        this.lockMode = lockMode;
+        this.lockManager = fileLockManager;
+        this.displayName = displayName != null ? String.format("%s (%s)", displayName, dir) : String.format("cache directory %s (%s)", dir.getName(), dir);
+    }
+
+    public DefaultPersistentDirectoryStore open() {
+        dir.mkdirs();
+        cacheAccess = new DefaultCacheAccess(displayName, getLockTarget(), lockManager);
+        try {
+            cacheAccess.open(lockMode);
+            try {
+                init();
+            } catch (Throwable throwable) {
+                cacheAccess.close();
+                throw throwable;
+            }
+        } catch (Throwable e) {
+            throw new CacheOpenException(String.format("Could not open %s.", this), e);
+        }
+
+        return this;
+    }
+
+    protected File getLockTarget() {
+        return dir;
+    }
+
+    protected void init() throws IOException {
+    }
+
+    public void close() {
+        if (cacheAccess != null) {
+            try {
+                cacheAccess.close();
+            } finally {
+                cacheAccess = null;
+            }
+        }
+
+    }
+
+    protected FileLock getLock() {
+        return cacheAccess.getFileLock();
+    }
+
+    public File getBaseDir() {
+        return dir;
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
+        return cacheAccess.newCache(cacheFile, keyType, valueType);
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
+        return cacheAccess.newCache(cacheFile, keyType, valueSerializer);
+    }
+
+    public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+        return cacheAccess.useCache(operationDisplayName, action);
+    }
+
+    public void useCache(String operationDisplayName, Runnable action) {
+        cacheAccess.useCache(operationDisplayName, action);
+    }
+
+    public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
+        return cacheAccess.longRunningOperation(operationDisplayName, action);
+    }
+
+    public void longRunningOperation(String operationDisplayName, Runnable action) {
+        cacheAccess.longRunningOperation(operationDisplayName, action);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultProcessMetaDataProvider.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultProcessMetaDataProvider.java
new file mode 100644
index 0000000..c1a5b1f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultProcessMetaDataProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.nativeplatform.*;
+
+public class DefaultProcessMetaDataProvider implements ProcessMetaDataProvider {
+    private final ProcessEnvironment environment;
+
+    public DefaultProcessMetaDataProvider(ProcessEnvironment environment) {
+        this.environment = environment;
+    }
+
+    public String getProcessIdentifier() {
+        Long pid = environment.maybeGetPid();
+        return pid == null ? "gradle" : String.valueOf(pid);
+    }
+
+    public String getProcessDisplayName() {
+        return "gradle";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
new file mode 100644
index 0000000..f36ca7d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.Factory;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Provides synchronization with other processes for a particular file.
+ */
+public interface FileAccess {
+    /**
+     * Runs the given action under a shared or exclusive lock on the target file.
+     *
+     * <p>If an exclusive or shared lock is already held, the lock level is not changed and the action is executed. If no lock is already held,
+     * a shared lock is acquired, the action executed, and the lock released. This method blocks until the lock can be acquired.
+     *
+     * @throws LockTimeoutException On timeout acquiring lock, if required.
+     * @throws IllegalStateException When this lock has been closed.
+     */
+    <T> T readFromFile(Callable<? extends T> action) throws LockTimeoutException;
+
+    /**
+     * Runs the given action under a shared or exclusive lock on the target file.
+     *
+     * <p>If an exclusive or shared lock is already held, the lock level is not changed and the action is executed. If no lock is already held,
+     * a shared lock is acquired, the action executed, and the lock released. This method blocks until the lock can be acquired.
+     *
+     * @throws LockTimeoutException On timeout acquiring lock, if required.
+     * @throws IllegalStateException When this lock has been closed.
+     */
+    <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException;
+
+    /**
+     * Runs the given action under an exclusive lock on the target file. If the given action fails, the lock is marked as uncleanly unlocked.
+     *
+     * <p>If an exclusive lock is already held, the lock level is not changed and the action is executed. If a shared lock is already held,
+     * the lock is escalated to an exclusive lock, and reverted back to a shared lock when the action completes. If no lock is already held, an
+     * exclusive lock is acquired, the action executed, and the lock released.
+     *
+     * @throws LockTimeoutException On timeout acquiring lock, if required.
+     * @throws IllegalStateException When this lock has been closed.
+     */
+    void writeToFile(Runnable action) throws LockTimeoutException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
new file mode 100644
index 0000000..da81890
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import java.io.Closeable;
+import java.io.File;
+
+public interface FileLock extends Closeable, FileAccess {
+    /**
+     * Returns true if the most recent {@link #writeToFile(Runnable)} by any process succeeded (ie a process did not crash while updating
+     * the target file). Returns false if {@link #writeToFile(Runnable)} has never been called for the target file.
+     */
+    boolean getUnlockedCleanly();
+
+    /**
+     * Returns true if the given file is used by this lock.
+     */
+    boolean isLockFile(File file);
+
+    /**
+     * Closes this lock, releasing the lock and any resources associated with it.
+     */
+    void close();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java
new file mode 100644
index 0000000..fab7db9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLockManager.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import java.io.File;
+
+public interface FileLockManager {
+    /**
+     * Creates a locks for the given file with the given mode. Acquires a lock with the given mode, which is held until the lock is
+     * released by calling {@link org.gradle.cache.internal.FileLock#close()}. This method blocks until the lock can be acquired.
+     *
+     * @param target The file to be locked.
+     * @param mode The lock mode.
+     * @param targetDisplayName A display name for the target file. This is used in log and error messages.
+     */
+    FileLock lock(File target, LockMode mode, String targetDisplayName) throws LockTimeoutException;
+
+    /**
+     * Creates a locks for the given file with the given mode. Acquires a lock with the given mode, which is held until the lock is
+     * released by calling {@link org.gradle.cache.internal.FileLock#close()}. This method blocks until the lock can be acquired.
+     *
+     * @param target The file to be locked.
+     * @param mode The lock mode.
+     * @param targetDisplayName A display name for the target file. This is used in log and error messages.
+     * @param operationDisplayName A display name for the operation being performed on the target file. This is used in log and error messages.
+     */
+    FileLock lock(File target, LockMode mode, String targetDisplayName, String operationDisplayName) throws LockTimeoutException;
+
+    enum LockMode {
+        /**
+         * No synchronisation is done.
+         */
+        None,
+        /**
+         * Multiple readers, no writers.
+         */
+        Shared,
+        /**
+         * Single writer, no readers.
+         */
+        Exclusive,
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/LockTimeoutException.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/LockTimeoutException.java
new file mode 100644
index 0000000..f10a4bb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/LockTimeoutException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+/**
+ * Thrown on timeout acquiring a lock on a file.
+ */
+public class LockTimeoutException extends RuntimeException {
+    public LockTimeoutException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
new file mode 100644
index 0000000..543fd65
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+
+import java.io.Closeable;
+
+public class MultiProcessSafePersistentIndexedCache<K, V> implements PersistentIndexedCache<K, V>, UnitOfWorkParticipant, Closeable {
+    private final FileAccess fileAccess;
+    private final Factory<BTreePersistentIndexedCache<K, V>> factory;
+    private BTreePersistentIndexedCache<K, V> cache;
+
+    public MultiProcessSafePersistentIndexedCache(Factory<BTreePersistentIndexedCache<K, V>> factory, FileAccess fileAccess) {
+        this.factory = factory;
+        this.fileAccess = fileAccess;
+    }
+
+    public V get(final K key) {
+        final PersistentIndexedCache<K, V> cache = getCache();
+        return fileAccess.readFromFile(new Factory<V>() {
+            public V create() {
+                return cache.get(key);
+            }
+        });
+    }
+
+    public void put(final K key, final V value) {
+        final PersistentIndexedCache<K, V> cache = getCache();
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                cache.put(key, value);
+            }
+        });
+    }
+
+    public void remove(final K key) {
+        final PersistentIndexedCache<K, V> cache = getCache();
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                cache.remove(key);
+            }
+        });
+    }
+
+    public void onStartWork(String operationDisplayName) {
+    }
+
+    public void onEndWork() {
+        close();
+    }
+
+    public void close() {
+        if (cache != null) {
+            try {
+                fileAccess.writeToFile(new Runnable() {
+                    public void run() {
+                        cache.close();
+                    }
+                });
+            } finally {
+                cache = null;
+            }
+        }
+    }
+
+    private PersistentIndexedCache<K, V> getCache() {
+        if (cache == null) {
+            fileAccess.writeToFile(new Runnable() {
+                public void run() {
+                    cache = factory.create();
+                }
+            });
+        }
+        return cache;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
new file mode 100644
index 0000000..e8ecb00
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+import org.gradle.internal.Factory;
+
+import java.io.File;
+
+public class OnDemandFileAccess extends AbstractFileAccess {
+    private final String displayName;
+    private final FileLockManager manager;
+    private final File targetFile;
+
+    public OnDemandFileAccess(File targetFile, String displayName, FileLockManager manager) {
+        this.targetFile = targetFile;
+        this.displayName = displayName;
+        this.manager = manager;
+    }
+
+    public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
+        FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Shared, displayName);
+        try {
+            return lock.readFromFile(action);
+        } finally {
+            lock.close();
+        }
+    }
+
+    public void writeToFile(Runnable action) throws LockTimeoutException {
+        FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Exclusive, displayName);
+        try {
+            lock.writeToFile(action);
+        } finally {
+            lock.close();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/ProcessMetaDataProvider.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/ProcessMetaDataProvider.java
new file mode 100644
index 0000000..1886acb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/ProcessMetaDataProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+/**
+ * Provides meta-data about the current process. Generally used for logging and error messages.
+ */
+public interface ProcessMetaDataProvider {
+    /**
+     * Returns a unique identifier for this process. Should be unique across all processes on the local machine.
+     */
+    String getProcessIdentifier();
+
+    /**
+     * Returns a display name for this process. Should allow a human to figure out which process the display name refers to.
+     */
+    String getProcessDisplayName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
new file mode 100644
index 0000000..e5eeda4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
@@ -0,0 +1,91 @@
+/*
+ * 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.cache.internal;
+
+import org.gradle.api.GradleException;
+import org.gradle.internal.Factory;
+import org.gradle.cache.PersistentStateCache;
+import org.gradle.cache.Serializer;
+
+import java.io.*;
+
+public class SimpleStateCache<T> implements PersistentStateCache<T> {
+    private final FileAccess fileAccess;
+    private final Serializer<T> serializer;
+    private final File cacheFile;
+
+    public SimpleStateCache(File cacheFile, FileAccess fileAccess, Serializer<T> serializer) {
+        this.cacheFile = cacheFile;
+        this.fileAccess = fileAccess;
+        this.serializer = serializer;
+    }
+
+    public T get() {
+        return fileAccess.readFromFile(new Factory<T>() {
+            public T create() {
+                return deserialize();
+            }
+        });
+    }
+
+    public void set(final T newValue) {
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                serialize(newValue);
+            }
+        });
+    }
+
+    public void update(final UpdateAction<T> updateAction) {
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                T oldValue = deserialize();
+                T newValue = updateAction.update(oldValue);
+                serialize(newValue);
+            }
+        });
+    }
+
+    private void serialize(T newValue) {
+        try {
+            OutputStream outStr = new BufferedOutputStream(new FileOutputStream(cacheFile));
+            try {
+                serializer.write(outStr, newValue);
+            } finally {
+                outStr.close();
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not write cache value to '%s'.", cacheFile), e);
+        }
+    }
+
+    private T deserialize() {
+        if (!cacheFile.isFile()) {
+            return null;
+        }
+        try {
+            InputStream inStr = new BufferedInputStream(new FileInputStream(cacheFile));
+            try {
+                return serializer.read(inStr);
+            } finally {
+                inStr.close();
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not read cache value from '%s'.", cacheFile), e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java
new file mode 100644
index 0000000..0a542bd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/UnitOfWorkParticipant.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.cache.internal;
+
+public interface UnitOfWorkParticipant {
+    void onStartWork(String operationDisplayName);
+
+    void onEndWork();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
new file mode 100644
index 0000000..a652eed
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
@@ -0,0 +1,720 @@
+/*
+ * 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.cache.internal.btree;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+// todo - stream serialised value to file
+// todo - handle hash collisions (properly, this time)
+// todo - don't store null links to child blocks in leaf index blocks
+// todo - align block boundaries
+// todo - thread safety control
+// todo - remove the check-sum from each block
+// todo - merge small values into a single data block
+// todo - discard when file corrupt
+// todo - include data directly in index entry when serializer can guarantee small fixed sized data
+// todo - free list leaks disk space
+// todo - merge adjacent free blocks
+// todo - use more efficient lookup for free block with nearest size
+public class BTreePersistentIndexedCache<K, V> implements PersistentIndexedCache<K, V> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BTreePersistentIndexedCache.class);
+    private final File cacheFile;
+    private final Serializer<K> keySerializer;
+    private final Serializer<V> serializer;
+    private final short maxChildIndexEntries;
+    private final int minIndexChildNodes;
+    private final StateCheckBlockStore store;
+    private HeaderBlock header;
+
+    public BTreePersistentIndexedCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+        this(cacheFile, keySerializer, valueSerializer, (short) 512, 512);
+    }
+
+    public BTreePersistentIndexedCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer,
+                                       short maxChildIndexEntries, int maxFreeListEntries) {
+        this.cacheFile = cacheFile;
+        this.keySerializer = keySerializer;
+        this.serializer = valueSerializer;
+        this.maxChildIndexEntries = maxChildIndexEntries;
+        this.minIndexChildNodes = maxChildIndexEntries / 2;
+        BlockStore cachingStore = new CachingBlockStore(new FileBackedBlockStore(cacheFile), IndexBlock.class, FreeListBlockStore.FreeListBlock.class);
+        store = new StateCheckBlockStore(new FreeListBlockStore(cachingStore, maxFreeListEntries));
+        try {
+            open();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not open %s.", this), e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("cache %s (%s)", cacheFile.getName(), cacheFile);
+    }
+
+    private void open() throws Exception {
+        LOGGER.debug("Opening {}", this);
+        try {
+            doOpen();
+        } catch (CorruptedCacheException e) {
+            rebuild();
+        }
+    }
+
+    private void doOpen() throws Exception {
+        BlockStore.Factory factory = new BlockStore.Factory() {
+            public Object create(Class<? extends BlockPayload> type) {
+                if (type == HeaderBlock.class) {
+                    return new HeaderBlock();
+                }
+                if (type == IndexBlock.class) {
+                    return new IndexBlock();
+                }
+                if (type == DataBlock.class) {
+                    return new DataBlock();
+                }
+                throw new UnsupportedOperationException();
+            }
+        };
+        Runnable initAction = new Runnable() {
+            public void run() {
+                header = new HeaderBlock();
+                store.write(header);
+                header.index.newRoot();
+                store.flush();
+            }
+        };
+
+        store.open(initAction, factory);
+        header = store.readFirst(HeaderBlock.class);
+    }
+
+    public V get(K key) {
+        try {
+            try {
+                DataBlock block = header.getRoot().get(key);
+                if (block != null) {
+                    return block.getValue();
+                }
+                return null;
+            } catch (CorruptedCacheException e) {
+                rebuild();
+                return null;
+            }
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not read entry '%s' from %s.", key, this), e);
+        }
+    }
+
+    public void put(K key, V value) {
+        try {
+            MessageDigestStream digestStream = new MessageDigestStream();
+            keySerializer.write(digestStream, key);
+            long hashCode = digestStream.getChecksum();
+            Lookup lookup = header.getRoot().find(hashCode);
+            boolean needNewBlock = true;
+            if (lookup.entry != null) {
+                DataBlock block = store.read(lookup.entry.dataBlock, DataBlock.class);
+                needNewBlock = !block.useNewValue(value);
+                if (needNewBlock) {
+                    store.remove(block);
+                }
+            }
+            if (needNewBlock) {
+                DataBlock block = new DataBlock(value);
+                store.write(block);
+                lookup.indexBlock.put(hashCode, block.getPos());
+            }
+            store.flush();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not add entry '%s' to %s.", key, this), e);
+        }
+    }
+
+    public void remove(K key) {
+        try {
+            Lookup lookup = header.getRoot().find(key);
+            if (lookup.entry == null) {
+                return;
+            }
+            lookup.indexBlock.remove(lookup.entry);
+            DataBlock block = store.read(lookup.entry.dataBlock, DataBlock.class);
+            store.remove(block);
+            store.flush();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not remove entry '%s' from %s.", key, this), e);
+        }
+    }
+
+    private IndexBlock load(BlockPointer pos, IndexRoot root, IndexBlock parent, int index) {
+        IndexBlock block = store.read(pos, IndexBlock.class);
+        block.root = root;
+        block.parent = parent;
+        block.parentEntryIndex = index;
+        return block;
+    }
+
+    public void reset() {
+        close();
+        try {
+            open();
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void close() {
+        LOGGER.debug("Closing {}", this);
+        try {
+            store.close();
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public boolean isOpen() {
+        return store.isOpen();
+    }
+
+    private void rebuild() throws Exception {
+        LOGGER.warn(String.format("%s is corrupt. Discarding.", this));
+        store.clear();
+        close();
+        doOpen();
+    }
+
+    public void verify() {
+        try {
+            doVerify();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Some problems were found when checking the integrity of %s.",
+                    this), e);
+        }
+    }
+
+    private void doVerify() throws Exception {
+        List<BlockPayload> blocks = new ArrayList<BlockPayload>();
+
+        HeaderBlock header = store.readFirst(HeaderBlock.class);
+        blocks.add(header);
+        verifyTree(header.getRoot(), "", blocks, Long.MAX_VALUE, true);
+
+        Collections.sort(blocks, new Comparator<BlockPayload>() {
+            public int compare(BlockPayload block, BlockPayload block1) {
+                return block.getPos().compareTo(block1.getPos());
+            }
+        });
+
+        for (int i = 0; i < blocks.size() - 1; i++) {
+            Block b1 = blocks.get(i).getBlock();
+            Block b2 = blocks.get(i + 1).getBlock();
+            if (b1.getPos().getPos() + b1.getSize() > b2.getPos().getPos()) {
+                throw new IOException(String.format("%s overlaps with %s", b1, b2));
+            }
+        }
+    }
+
+    private void verifyTree(IndexBlock current, String prefix, Collection<BlockPayload> blocks, long maxValue,
+                            boolean loadData) throws Exception {
+        blocks.add(current);
+
+        if (!prefix.equals("") && current.entries.size() < maxChildIndexEntries / 2) {
+            throw new IOException(String.format("Too few entries found in %s", current));
+        }
+        if (current.entries.size() > maxChildIndexEntries) {
+            throw new IOException(String.format("Too many entries found in %s", current));
+        }
+
+        boolean isLeaf = current.entries.size() == 0 || current.entries.get(0).childIndexBlock.isNull();
+        if (isLeaf ^ current.tailPos.isNull()) {
+            throw new IOException(String.format("Mismatched leaf/tail-node in %s", current));
+        }
+
+        long min = Long.MIN_VALUE;
+        for (IndexEntry entry : current.entries) {
+            if (isLeaf ^ entry.childIndexBlock.isNull()) {
+                throw new IOException(String.format("Mismatched leaf/non-leaf entry in %s", current));
+            }
+            if (entry.hashCode >= maxValue || entry.hashCode <= min) {
+                throw new IOException(String.format("Out-of-order key in %s", current));
+            }
+            min = entry.hashCode;
+            if (!entry.childIndexBlock.isNull()) {
+                IndexBlock child = store.read(entry.childIndexBlock, IndexBlock.class);
+                verifyTree(child, "   " + prefix, blocks, entry.hashCode, loadData);
+            }
+            if (loadData) {
+                DataBlock block = store.read(entry.dataBlock, DataBlock.class);
+                blocks.add(block);
+            }
+        }
+        if (!current.tailPos.isNull()) {
+            IndexBlock tail = store.read(current.tailPos, IndexBlock.class);
+            verifyTree(tail, "   " + prefix, blocks, maxValue, loadData);
+        }
+    }
+
+    private class IndexRoot {
+        private BlockPointer rootPos = new BlockPointer();
+        private HeaderBlock owner;
+
+        private IndexRoot(HeaderBlock owner) {
+            this.owner = owner;
+        }
+
+        public void setRootPos(BlockPointer rootPos) {
+            this.rootPos = rootPos;
+            store.write(owner);
+        }
+
+        public IndexBlock getRoot() {
+            return load(rootPos, this, null, 0);
+        }
+
+        public IndexBlock newRoot() {
+            IndexBlock block = new IndexBlock();
+            store.write(block);
+            setRootPos(block.getPos());
+            return block;
+        }
+    }
+
+    private class HeaderBlock extends BlockPayload {
+        private IndexRoot index;
+
+        private HeaderBlock() {
+            index = new IndexRoot(this);
+        }
+
+        @Override
+        protected int getType() {
+            return 0x55;
+        }
+
+        @Override
+        protected int getSize() {
+            return Block.LONG_SIZE + Block.SHORT_SIZE;
+        }
+
+        @Override
+        protected void read(DataInputStream instr) throws Exception {
+            index.rootPos = new BlockPointer(instr.readLong());
+
+            short actualChildIndexEntries = instr.readShort();
+            if (actualChildIndexEntries != maxChildIndexEntries) {
+                throw blockCorruptedException();
+            }
+        }
+
+        @Override
+        protected void write(DataOutputStream outstr) throws Exception {
+            outstr.writeLong(index.rootPos.getPos());
+            outstr.writeShort(maxChildIndexEntries);
+        }
+
+        public IndexBlock getRoot() throws Exception {
+            return index.getRoot();
+        }
+    }
+
+    private class IndexBlock extends BlockPayload {
+        private final List<IndexEntry> entries = new ArrayList<IndexEntry>();
+        private BlockPointer tailPos = new BlockPointer();
+        // Transient fields
+        private IndexBlock parent;
+        private int parentEntryIndex;
+        private IndexRoot root;
+
+        @Override
+        protected int getType() {
+            return 0x77;
+        }
+
+        @Override
+        protected int getSize() {
+            return Block.INT_SIZE + Block.LONG_SIZE + (3 * Block.LONG_SIZE) * maxChildIndexEntries;
+        }
+
+        public void read(DataInputStream instr) throws IOException {
+            int count = instr.readInt();
+            entries.clear();
+            for (int i = 0; i < count; i++) {
+                IndexEntry entry = new IndexEntry();
+                entry.hashCode = instr.readLong();
+                entry.dataBlock = new BlockPointer(instr.readLong());
+                entry.childIndexBlock = new BlockPointer(instr.readLong());
+                entries.add(entry);
+            }
+            tailPos = new BlockPointer(instr.readLong());
+        }
+
+        public void write(DataOutputStream outstr) throws IOException {
+            outstr.writeInt(entries.size());
+            for (IndexEntry entry : entries) {
+                outstr.writeLong(entry.hashCode);
+                outstr.writeLong(entry.dataBlock.getPos());
+                outstr.writeLong(entry.childIndexBlock.getPos());
+            }
+            outstr.writeLong(tailPos.getPos());
+        }
+
+        public void put(long hashCode, BlockPointer pos) throws Exception {
+            int index = Collections.binarySearch(entries, new IndexEntry(hashCode));
+            IndexEntry entry;
+            if (index >= 0) {
+                entry = entries.get(index);
+            } else {
+                assert tailPos.isNull();
+                entry = new IndexEntry();
+                entry.hashCode = hashCode;
+                entry.childIndexBlock = new BlockPointer();
+                index = -index - 1;
+                entries.add(index, entry);
+            }
+
+            entry.dataBlock = pos;
+            store.write(this);
+
+            maybeSplit();
+        }
+
+        private void maybeSplit() throws Exception {
+            if (entries.size() > maxChildIndexEntries) {
+                int splitPos = entries.size() / 2;
+                IndexEntry splitEntry = entries.remove(splitPos);
+                if (parent == null) {
+                    parent = root.newRoot();
+                }
+                IndexBlock sibling = new IndexBlock();
+                store.write(sibling);
+                List<IndexEntry> siblingEntries = entries.subList(splitPos, entries.size());
+                sibling.entries.addAll(siblingEntries);
+                siblingEntries.clear();
+                sibling.tailPos = tailPos;
+                tailPos = splitEntry.childIndexBlock;
+                splitEntry.childIndexBlock = new BlockPointer();
+                parent.add(this, splitEntry, sibling);
+            }
+        }
+
+        private void add(IndexBlock left, IndexEntry entry, IndexBlock right) throws Exception {
+            int index = left.parentEntryIndex;
+            if (index < entries.size()) {
+                IndexEntry parentEntry = entries.get(index);
+                assert parentEntry.childIndexBlock.equals(left.getPos());
+                parentEntry.childIndexBlock = right.getPos();
+            } else {
+                assert index == entries.size() && (tailPos.isNull() || tailPos.equals(left.getPos()));
+                tailPos = right.getPos();
+            }
+            entries.add(index, entry);
+            entry.childIndexBlock = left.getPos();
+            store.write(this);
+
+            maybeSplit();
+        }
+
+        public DataBlock get(K key) throws Exception {
+            Lookup lookup = find(key);
+            if (lookup.entry == null) {
+                return null;
+            }
+
+            return store.read(lookup.entry.dataBlock, DataBlock.class);
+        }
+
+        public Lookup find(K key) throws Exception {
+            MessageDigestStream digestStream = new MessageDigestStream();
+            keySerializer.write(digestStream, key);
+            long checksum = digestStream.getChecksum();
+            return find(checksum);
+        }
+
+        private Lookup find(long hashCode) throws Exception {
+            int index = Collections.binarySearch(entries, new IndexEntry(hashCode));
+            if (index >= 0) {
+                return new Lookup(this, entries.get(index));
+            }
+
+            index = -index - 1;
+            BlockPointer childBlockPos;
+            if (index == entries.size()) {
+                childBlockPos = tailPos;
+            } else {
+                childBlockPos = entries.get(index).childIndexBlock;
+            }
+            if (childBlockPos.isNull()) {
+                return new Lookup(this, null);
+            }
+
+            IndexBlock childBlock = load(childBlockPos, root, this, index);
+            return childBlock.find(hashCode);
+        }
+
+        public void remove(IndexEntry entry) throws Exception {
+            int index = entries.indexOf(entry);
+            assert index >= 0;
+            entries.remove(index);
+            store.write(this);
+
+            if (entry.childIndexBlock.isNull()) {
+                maybeMerge();
+            } else {
+                // Not a leaf node. Move up an entry from a leaf node, then possibly merge the leaf node
+                IndexBlock leafBlock = load(entry.childIndexBlock, root, this, index);
+                leafBlock = leafBlock.findHighestLeaf();
+                IndexEntry highestEntry = leafBlock.entries.remove(leafBlock.entries.size() - 1);
+                highestEntry.childIndexBlock = entry.childIndexBlock;
+                entries.add(index, highestEntry);
+                store.write(leafBlock);
+                leafBlock.maybeMerge();
+            }
+        }
+
+        private void maybeMerge() throws Exception {
+            if (parent == null) {
+                // This is the root block. Can have any number of children <= maxChildIndexEntries
+                if (entries.size() == 0 && !tailPos.isNull()) {
+                    // This is an empty root block, discard it
+                    header.index.setRootPos(tailPos);
+                    store.remove(this);
+                }
+                return;
+            }
+
+            // This is not the root block. Must have children >= minIndexChildNodes
+            if (entries.size() >= minIndexChildNodes) {
+                return;
+            }
+
+            // Attempt to merge with the left sibling
+            IndexBlock left = parent.getPrevious(this);
+            if (left != null) {
+                assert entries.size() + left.entries.size() <= maxChildIndexEntries * 2;
+                if (left.entries.size() > minIndexChildNodes) {
+                    // There are enough entries in this block and the left sibling to make up 2 blocks, so redistribute
+                    // the entries evenly between them
+                    left.mergeFrom(this);
+                    left.maybeSplit();
+                    return;
+                } else {
+                    // There are only enough entries to make up 1 block, so move the entries of the left sibling into
+                    // this block and discard the left sibling. Might also need to merge the parent
+                    left.mergeFrom(this);
+                    parent.maybeMerge();
+                    return;
+                }
+            }
+
+            // Attempt to merge with the right sibling
+            IndexBlock right = parent.getNext(this);
+            if (right != null) {
+                assert entries.size() + right.entries.size() <= maxChildIndexEntries * 2;
+                if (right.entries.size() > minIndexChildNodes) {
+                    // There are enough entries in this block and the right sibling to make up 2 blocks, so redistribute
+                    // the entries evenly between them
+                    mergeFrom(right);
+                    maybeSplit();
+                    return;
+                } else {
+                    // There are only enough entries to make up 1 block, so move the entries of the right sibling into
+                    // this block and discard this block. Might also need to merge the parent
+                    mergeFrom(right);
+                    parent.maybeMerge();
+                    return;
+                }
+            }
+
+            // Should not happen
+            throw new IllegalStateException(String.format("%s does not have any siblings.", getBlock()));
+        }
+
+        private void mergeFrom(IndexBlock right) throws Exception {
+            IndexEntry newChildEntry = parent.entries.remove(parentEntryIndex);
+            if (right.getPos().equals(parent.tailPos)) {
+                parent.tailPos = getPos();
+            } else {
+                IndexEntry newParentEntry = parent.entries.get(parentEntryIndex);
+                assert newParentEntry.childIndexBlock.equals(right.getPos());
+                newParentEntry.childIndexBlock = getPos();
+            }
+            entries.add(newChildEntry);
+            entries.addAll(right.entries);
+            newChildEntry.childIndexBlock = tailPos;
+            tailPos = right.tailPos;
+            store.write(parent);
+            store.write(this);
+            store.remove(right);
+        }
+
+        private IndexBlock getNext(IndexBlock indexBlock) throws Exception {
+            int index = indexBlock.parentEntryIndex + 1;
+            if (index > entries.size()) {
+                return null;
+            }
+            if (index == entries.size()) {
+                return load(tailPos, root, this, index);
+            }
+            return load(entries.get(index).childIndexBlock, root, this, index);
+        }
+
+        private IndexBlock getPrevious(IndexBlock indexBlock) throws Exception {
+            int index = indexBlock.parentEntryIndex - 1;
+            if (index < 0) {
+                return null;
+            }
+            return load(entries.get(index).childIndexBlock, root, this, index);
+        }
+
+        private IndexBlock findHighestLeaf() throws Exception {
+            if (tailPos.isNull()) {
+                return this;
+            }
+            return load(tailPos, root, this, entries.size()).findHighestLeaf();
+        }
+    }
+
+    private static class IndexEntry implements Comparable<IndexEntry> {
+        long hashCode;
+        BlockPointer dataBlock;
+        BlockPointer childIndexBlock;
+
+        private IndexEntry() {
+        }
+
+        private IndexEntry(long hashCode) {
+            this.hashCode = hashCode;
+        }
+
+        public int compareTo(IndexEntry indexEntry) {
+            if (hashCode > indexEntry.hashCode) {
+                return 1;
+            }
+            if (hashCode < indexEntry.hashCode) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+
+    private class Lookup {
+        final IndexBlock indexBlock;
+        final IndexEntry entry;
+
+        private Lookup(IndexBlock indexBlock, IndexEntry entry) {
+            this.indexBlock = indexBlock;
+            this.entry = entry;
+        }
+    }
+
+    private class DataBlock extends BlockPayload {
+        private int size;
+        private byte[] serialisedValue;
+        private V value;
+
+        private DataBlock() {
+        }
+
+        public DataBlock(V value) throws Exception {
+            this.value = value;
+            setValue(value);
+            size = serialisedValue.length;
+        }
+
+        public void setValue(V value) throws Exception {
+            ByteArrayOutputStream outStr = new ByteArrayOutputStream();
+            serializer.write(outStr, value);
+            this.serialisedValue = outStr.toByteArray();
+        }
+
+        public V getValue() throws Exception {
+            if (value == null) {
+                value = serializer.read(new ByteArrayInputStream(serialisedValue));
+            }
+            return value;
+        }
+
+        @Override
+        protected int getType() {
+            return 0x33;
+        }
+
+        @Override
+        protected int getSize() {
+            return 2 * Block.INT_SIZE + size;
+        }
+
+        public void read(DataInputStream instr) throws Exception {
+            size = instr.readInt();
+            int bytes = instr.readInt();
+            serialisedValue = new byte[bytes];
+            instr.readFully(serialisedValue);
+        }
+
+        public void write(DataOutputStream outstr) throws Exception {
+            outstr.writeInt(size);
+            outstr.writeInt(serialisedValue.length);
+            outstr.write(serialisedValue);
+        }
+
+        public boolean useNewValue(V value) throws Exception {
+            setValue(value);
+            boolean ok = serialisedValue.length <= size;
+            if (ok) {
+                store.write(this);
+            }
+            return ok;
+        }
+    }
+
+    private static class MessageDigestStream extends OutputStream {
+        MessageDigest messageDigest;
+
+        private MessageDigestStream() throws NoSuchAlgorithmException {
+            messageDigest = MessageDigest.getInstance("MD5");
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            messageDigest.update((byte) b);
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            messageDigest.update(b);
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            messageDigest.update(b, off, len);
+        }
+
+        long getChecksum() {
+            byte[] digest = messageDigest.digest();
+            assert digest.length == 16;
+            return new BigInteger(digest).longValue();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/Block.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/Block.java
new file mode 100755
index 0000000..b28fab3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/Block.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cache.internal.btree;
+
+public abstract class Block {
+    static final int LONG_SIZE = 8;
+    static final int INT_SIZE = 4;
+    static final int SHORT_SIZE = 2;
+
+    private BlockPayload payload;
+
+    protected Block(BlockPayload payload) {
+        this.payload = payload;
+        payload.setBlock(this);
+    }
+
+    public BlockPayload getPayload() {
+        return payload;
+    }
+
+    protected void detach() {
+        payload.setBlock(null);
+        payload = null;
+    }
+    
+    public abstract BlockPointer getPos();
+
+    public abstract int getSize();
+
+    public abstract RuntimeException blockCorruptedException();
+
+    @Override
+    public String toString() {
+        return String.format("%s %s", payload.getClass().getSimpleName(), getPos());
+    }
+
+    public BlockPointer getNextPos() {
+        return new BlockPointer(getPos().getPos() + getSize());
+    }
+
+    public abstract boolean hasPos();
+
+    public abstract void setPos(BlockPointer pos);
+
+    public abstract void setSize(int size);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockPayload.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockPayload.java
new file mode 100755
index 0000000..00b9a37
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockPayload.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cache.internal.btree;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public abstract class BlockPayload {
+    private Block block;
+
+    public Block getBlock() {
+        return block;
+    }
+
+    public void setBlock(Block block) {
+        this.block = block;
+    }
+
+    public BlockPointer getPos() {
+        return getBlock().getPos();
+    }
+
+    public BlockPointer getNextPos() {
+        return getBlock().getNextPos();
+    }
+
+    protected abstract int getSize();
+
+    protected abstract int getType();
+
+    protected abstract void read(DataInputStream inputStream) throws Exception;
+
+    protected abstract void write(DataOutputStream outputStream) throws Exception;
+
+    protected RuntimeException blockCorruptedException() {
+        return getBlock().blockCorruptedException();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockPointer.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockPointer.java
new file mode 100755
index 0000000..15ae781
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockPointer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.cache.internal.btree;
+
+public class BlockPointer implements Comparable<BlockPointer> {
+    private final long pos;
+
+    public BlockPointer() {
+        pos = -1;
+    }
+
+    public BlockPointer(long pos) {
+        this.pos = pos;
+    }
+
+    public boolean isNull() {
+        return pos < 0;
+    }
+
+    public long getPos() {
+        return pos;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(pos);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        BlockPointer other = (BlockPointer) obj;
+        return pos == other.pos;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) pos;
+    }
+
+    public int compareTo(BlockPointer o) {
+        if (pos > o.pos) {
+            return 1;
+        }
+        if (pos < o.pos) {
+            return -1;
+        }
+        return 0;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockStore.java
new file mode 100755
index 0000000..8319678
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BlockStore.java
@@ -0,0 +1,68 @@
+/*
+ * 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.cache.internal.btree;
+
+public interface BlockStore {
+    /**
+     * Opens this store, calling the given action if the store is empty.
+     */
+    void open(Runnable initAction, Factory factory);
+
+    /**
+     * Closes this store.
+     */
+    void close();
+
+    /**
+     * Discards all blocks from this store.
+     */
+    void clear();
+
+    /**
+     * Removes the given block from this store.
+     */
+    void remove(BlockPayload block);
+
+    /**
+     * Reads the first block from this store.
+     */
+    <T extends BlockPayload> T readFirst(Class<T> payloadType);
+    
+    /**
+     * Reads a block from this store.
+     */
+    <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType);
+
+    /**
+     * Writes a block to this store, adding the block if required.
+     */
+    void write(BlockPayload block);
+
+    /**
+     * Adds a new block to this store. Allocates space for the block, but does not write the contents of the block
+     * until {@link #write(BlockPayload)} is called.
+     */
+    void attach(BlockPayload block);
+
+    /**
+     * Flushes any pending updates for this store.
+     */
+    void flush();
+
+    interface Factory {
+        Object create(Class<? extends BlockPayload> type);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/CachingBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/CachingBlockStore.java
new file mode 100755
index 0000000..a47e4e5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/CachingBlockStore.java
@@ -0,0 +1,100 @@
+/*
+ * 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.cache.internal.btree;
+
+import org.apache.commons.collections.map.LRUMap;
+
+import java.util.*;
+
+public class CachingBlockStore implements BlockStore {
+    private final BlockStore store;
+    private final Map<BlockPointer, BlockPayload> dirty = new LinkedHashMap<BlockPointer, BlockPayload>();
+    private final Map<BlockPointer, BlockPayload> indexBlockCache = new LRUMap(100);
+    private final Set<Class<?>> cachableTypes = new HashSet<Class<?>>();
+
+    public CachingBlockStore(BlockStore store, Class<? extends BlockPayload>... cacheableBlockTypes) {
+        this.store = store;
+        cachableTypes.addAll(Arrays.asList(cacheableBlockTypes));
+    }
+
+    public void open(Runnable initAction, Factory factory) {
+        store.open(initAction, factory);
+    }
+
+    public void close() {
+        flush();
+        indexBlockCache.clear();
+        store.close();
+    }
+
+    public void clear() {
+        dirty.clear();
+        indexBlockCache.clear();
+        store.clear();
+    }
+
+    public void flush() {
+        Iterator<BlockPayload> iterator = dirty.values().iterator();
+        while (iterator.hasNext()) {
+            BlockPayload block = iterator.next();
+            iterator.remove();
+            store.write(block);
+        }
+        store.flush();
+    }
+
+    public void attach(BlockPayload block) {
+        store.attach(block);
+    }
+
+    public void remove(BlockPayload block) {
+        dirty.remove(block.getPos());
+        indexBlockCache.remove(block.getPos());
+        store.remove(block);
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        T block = store.readFirst(payloadType);
+        maybeCache(block);
+        return block;
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        T block = payloadType.cast(dirty.get(pos));
+        if (block != null) {
+            return block;
+        }
+        block = payloadType.cast(indexBlockCache.get(pos));
+        if (block != null) {
+            return block;
+        }
+        block = store.read(pos, payloadType);
+        maybeCache(block);
+        return block;
+    }
+
+    public void write(BlockPayload block) {
+        store.attach(block);
+        maybeCache(block);
+        dirty.put(block.getPos(), block);
+    }
+
+    private <T extends BlockPayload> void maybeCache(T block) {
+        if (cachableTypes.contains(block.getClass())) {
+            indexBlockCache.put(block.getPos(), block);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/CorruptedCacheException.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/CorruptedCacheException.java
new file mode 100755
index 0000000..e504a94
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/CorruptedCacheException.java
@@ -0,0 +1,22 @@
+/*
+ * 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.cache.internal.btree;
+
+class CorruptedCacheException extends RuntimeException {
+    CorruptedCacheException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java
new file mode 100755
index 0000000..8781b35
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FileBackedBlockStore.java
@@ -0,0 +1,352 @@
+/*
+ * 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.cache.internal.btree;
+
+import org.gradle.api.UncheckedIOException;
+
+import java.io.*;
+import java.util.zip.CRC32;
+
+public class FileBackedBlockStore implements BlockStore {
+    private RandomAccessFile file;
+    private final File cacheFile;
+    private long nextBlock;
+    private Factory factory;
+
+    public FileBackedBlockStore(File cacheFile) {
+        this.cacheFile = cacheFile;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("cache '%s'", cacheFile);
+    }
+
+    public void open(Runnable runnable, Factory factory) {
+        this.factory = factory;
+        try {
+            file = new RandomAccessFile(cacheFile, "rw");
+            nextBlock = file.length();
+            if (file.length() == 0) {
+                runnable.run();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void close() {
+        try {
+            file.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void clear() {
+        try {
+            file.setLength(0);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        nextBlock = 0;
+    }
+
+    public void attach(BlockPayload block) {
+        if (block.getBlock() == null) {
+            block.setBlock(new BlockImpl(block));
+        }
+    }
+
+    public void remove(BlockPayload block) {
+        BlockImpl blockImpl = (BlockImpl) block.getBlock();
+        blockImpl.detach();
+    }
+
+    public void flush() {
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        return read(new BlockPointer(0), payloadType);
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        assert !pos.isNull();
+        try {
+            T payload = payloadType.cast(factory.create(payloadType));
+            BlockImpl block = new BlockImpl(payload, pos);
+            block.read();
+            return payload;
+        } catch (CorruptedCacheException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void write(BlockPayload block) {
+        BlockImpl blockImpl = (BlockImpl) block.getBlock();
+        try {
+            blockImpl.write();
+        } catch (CorruptedCacheException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private long alloc(long length) {
+        long pos = nextBlock;
+        nextBlock += length;
+        return pos;
+    }
+
+    private final class BlockImpl extends Block {
+        private static final int HEADER_SIZE = 2 + INT_SIZE;
+        private static final int TAIL_SIZE = LONG_SIZE;
+        static final int BLOCK_MARKER = 0xCC;
+
+        private BlockPointer pos;
+        private int payloadSize;
+
+        private BlockImpl(BlockPayload payload, BlockPointer pos) {
+            this(payload);
+            setPos(pos);
+        }
+
+        public BlockImpl(BlockPayload payload) {
+            super(payload);
+            pos = null;
+            payloadSize = -1;
+        }
+
+        @Override
+        public boolean hasPos() {
+            return pos != null;
+        }
+
+        @Override
+        public BlockPointer getPos() {
+            if (pos == null) {
+                pos = new BlockPointer(alloc(getSize()));
+            }
+            return pos;
+        }
+
+        @Override
+        public void setPos(BlockPointer pos) {
+            assert this.pos == null && !pos.isNull();
+            this.pos = pos;
+        }
+
+        public int getSize() {
+            if (payloadSize < 0) {
+                payloadSize = getPayload().getSize();
+            }
+            return payloadSize + HEADER_SIZE + TAIL_SIZE;
+        }
+
+        @Override
+        public void setSize(int size) {
+            int newPayloadSize = size - HEADER_SIZE - TAIL_SIZE;
+            assert newPayloadSize >= payloadSize;
+            payloadSize = newPayloadSize;
+        }
+
+        public void write() throws Exception {
+            long pos = getPos().getPos();
+            file.seek(pos);
+
+            Crc32OutputStream checkSumOutputStream = new Crc32OutputStream(new BufferedOutputStream(
+                    new RandomAccessFileOutputStream(file)));
+            DataOutputStream outputStream = new DataOutputStream(checkSumOutputStream);
+
+            BlockPayload payload = getPayload();
+
+            // Write header
+            outputStream.writeByte(BLOCK_MARKER);
+            outputStream.writeByte(payload.getType());
+            outputStream.writeInt(payloadSize);
+            long finalSize = pos + HEADER_SIZE + TAIL_SIZE + payloadSize;
+
+            // Write body
+            payload.write(outputStream);
+
+            // Write checksum
+            outputStream.writeLong(checkSumOutputStream.checksum.getValue());
+            outputStream.close();
+
+            // Pad
+            if (file.length() < finalSize) {
+                file.setLength(finalSize);
+            }
+        }
+
+        public void read() throws Exception {
+            long pos = getPos().getPos();
+            assert pos >= 0;
+            if (pos + HEADER_SIZE >= file.length()) {
+                throw blockCorruptedException();
+            }
+            file.seek(pos);
+
+            Crc32InputStream checkSumInputStream = new Crc32InputStream(new BufferedInputStream(
+                    new RandomAccessFileInputStream(file)));
+            DataInputStream inputStream = new DataInputStream(checkSumInputStream);
+
+            BlockPayload payload = getPayload();
+
+            // Read header
+            byte type = inputStream.readByte();
+            if (type != (byte) BLOCK_MARKER) {
+                throw blockCorruptedException();
+            }
+            type = inputStream.readByte();
+            if (type != (byte) payload.getType()) {
+                throw blockCorruptedException();
+            }
+
+            // Read body
+            payloadSize = inputStream.readInt();
+            if (pos + HEADER_SIZE + TAIL_SIZE + payloadSize > file.length()) {
+                throw blockCorruptedException();
+            }
+            payload.read(inputStream);
+
+            // Read and verify checksum
+            long actualChecksum = checkSumInputStream.checksum.getValue();
+            long checksum = inputStream.readLong();
+            if (actualChecksum != checksum) {
+                throw blockCorruptedException();
+            }
+            inputStream.close();
+        }
+
+        public RuntimeException blockCorruptedException() {
+            return new CorruptedCacheException(String.format("Corrupted %s found in %s.", this,
+                    FileBackedBlockStore.this));
+        }
+    }
+
+    private static class RandomAccessFileInputStream extends InputStream {
+        private final RandomAccessFile file;
+
+        private RandomAccessFileInputStream(RandomAccessFile file) {
+            this.file = file;
+        }
+
+        @Override
+        public int read(byte[] bytes) throws IOException {
+            return file.read(bytes);
+        }
+
+        @Override
+        public int read() throws IOException {
+            return file.read();
+        }
+
+        @Override
+        public int read(byte[] bytes, int offset, int length) throws IOException {
+            return file.read(bytes, offset, length);
+        }
+    }
+
+    private static class RandomAccessFileOutputStream extends OutputStream {
+        private final RandomAccessFile file;
+
+        private RandomAccessFileOutputStream(RandomAccessFile file) {
+            this.file = file;
+        }
+
+        @Override
+        public void write(int i) throws IOException {
+            file.write(i);
+        }
+
+        @Override
+        public void write(byte[] bytes) throws IOException {
+            file.write(bytes);
+        }
+
+        @Override
+        public void write(byte[] bytes, int offset, int length) throws IOException {
+            file.write(bytes, offset, length);
+        }
+    }
+
+    private static class Crc32InputStream extends FilterInputStream {
+        private final CRC32 checksum;
+
+        private Crc32InputStream(InputStream inputStream) {
+            super(inputStream);
+            checksum = new CRC32();
+        }
+
+        @Override
+        public int read() throws IOException {
+            int b = in.read();
+            if (b >= 0) {
+                checksum.update(b);
+            }
+            return b;
+        }
+
+        @Override
+        public int read(byte[] bytes) throws IOException {
+            int count = in.read(bytes);
+            if (count > 0) {
+                checksum.update(bytes, 0, count);
+            }
+            return count;
+        }
+
+        @Override
+        public int read(byte[] bytes, int offset, int max) throws IOException {
+            int count = in.read(bytes, offset, max);
+            if (count > 0) {
+                checksum.update(bytes, offset, count);
+            }
+            return count;
+        }
+    }
+
+    private static class Crc32OutputStream extends FilterOutputStream {
+        private final CRC32 checksum;
+
+        private Crc32OutputStream(OutputStream outputStream) {
+            super(outputStream);
+            this.checksum = new CRC32();
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            checksum.update(b);
+            out.write(b);
+        }
+
+        @Override
+        public void write(byte[] bytes) throws IOException {
+            checksum.update(bytes);
+            out.write(bytes);
+        }
+
+        @Override
+        public void write(byte[] bytes, int offset, int count) throws IOException {
+            checksum.update(bytes, offset, count);
+            out.write(bytes, offset, count);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FreeListBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FreeListBlockStore.java
new file mode 100755
index 0000000..6982906
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/FreeListBlockStore.java
@@ -0,0 +1,271 @@
+/*
+ * 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.cache.internal.btree;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FreeListBlockStore implements BlockStore {
+    private final BlockStore store;
+    private final BlockStore freeListStore;
+    private final int maxBlockEntries;
+    private FreeListBlock freeListBlock;
+
+    public FreeListBlockStore(BlockStore store, int maxBlockEntries) {
+        this.store = store;
+        freeListStore = this;
+        this.maxBlockEntries = maxBlockEntries;
+    }
+
+    public void open(final Runnable initAction, final Factory factory) {
+        Runnable freeListInitAction = new Runnable() {
+            public void run() {
+                freeListBlock = new FreeListBlock();
+                store.write(freeListBlock);
+                store.flush();
+                initAction.run();
+            }
+        };
+        Factory freeListFactory = new Factory() {
+            public Object create(Class<? extends BlockPayload> type) {
+                if (type == FreeListBlock.class) {
+                    return new FreeListBlock();
+                }
+                return factory.create(type);
+            }
+        };
+
+        store.open(freeListInitAction, freeListFactory);
+        freeListBlock = store.readFirst(FreeListBlock.class);
+    }
+
+    public void close() {
+        freeListBlock = null;
+        store.close();
+    }
+
+    public void clear() {
+        store.clear();
+    }
+
+    public void remove(BlockPayload block) {
+        Block container = block.getBlock();
+        store.remove(block);
+        freeListBlock.add(container.getPos(), container.getSize());
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        return store.read(freeListBlock.getNextPos(), payloadType);
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        return store.read(pos, payloadType);
+    }
+
+    public void write(BlockPayload block) {
+        attach(block);
+        store.write(block);
+    }
+
+    public void attach(BlockPayload block) {
+        store.attach(block);
+        freeListBlock.alloc(block.getBlock());
+    }
+
+    public void flush() {
+        store.flush();
+    }
+
+    private void verify() {
+        FreeListBlock block = store.readFirst(FreeListBlock.class);
+        verify(block, Integer.MAX_VALUE);
+    }
+
+    private void verify(FreeListBlock block, int maxValue) {
+        if (block.largestInNextBlock > maxValue) {
+            throw new RuntimeException("corrupt free list");
+        }
+        int current = 0;
+        for (FreeListEntry entry : block.entries) {
+            if (entry.size > maxValue) {
+                throw new RuntimeException("corrupt free list");
+            }
+            if (entry.size < block.largestInNextBlock) {
+                throw new RuntimeException("corrupt free list");
+            }
+            if (entry.size < current) {
+                throw new RuntimeException("corrupt free list");
+            }
+            current = entry.size;
+        }
+        if (!block.nextBlock.isNull()) {
+            verify(store.read(block.nextBlock, FreeListBlock.class), block.largestInNextBlock);
+        }
+    }
+
+    public class FreeListBlock extends BlockPayload {
+        private List<FreeListEntry> entries = new ArrayList<FreeListEntry>();
+        private int largestInNextBlock;
+        private BlockPointer nextBlock = new BlockPointer();
+        // Transient fields
+        private FreeListBlock prev;
+        private FreeListBlock next;
+
+        @Override
+        protected int getSize() {
+            return Block.LONG_SIZE + Block.INT_SIZE + Block.INT_SIZE + maxBlockEntries * (Block.LONG_SIZE
+                    + Block.INT_SIZE);
+        }
+
+        @Override
+        protected int getType() {
+            return 0x44;
+        }
+
+        @Override
+        protected void read(DataInputStream inputStream) throws Exception {
+            nextBlock = new BlockPointer(inputStream.readLong());
+            largestInNextBlock = inputStream.readInt();
+            int count = inputStream.readInt();
+            for (int i = 0; i < count; i++) {
+                BlockPointer pos = new BlockPointer(inputStream.readLong());
+                int size = inputStream.readInt();
+                entries.add(new FreeListEntry(pos, size));
+            }
+        }
+
+        @Override
+        protected void write(DataOutputStream outputStream) throws Exception {
+            outputStream.writeLong(nextBlock.getPos());
+            outputStream.writeInt(largestInNextBlock);
+            outputStream.writeInt(entries.size());
+            for (FreeListEntry entry : entries) {
+                outputStream.writeLong(entry.pos.getPos());
+                outputStream.writeInt(entry.size);
+            }
+        }
+
+        public void add(BlockPointer pos, int size) {
+            assert !pos.isNull() && size >= 0;
+            if (size == 0) {
+                return;
+            }
+
+            if (size < largestInNextBlock) {
+                FreeListBlock next = getNextBlock();
+                next.add(pos, size);
+                return;
+            }
+
+            FreeListEntry entry = new FreeListEntry(pos, size);
+            int index = Collections.binarySearch(entries, entry);
+            if (index < 0) {
+                index = -index - 1;
+            }
+            entries.add(index, entry);
+
+            if (entries.size() > maxBlockEntries) {
+                FreeListBlock newBlock = new FreeListBlock();
+                newBlock.largestInNextBlock = largestInNextBlock;
+                newBlock.nextBlock = nextBlock;
+                newBlock.prev = this;
+                newBlock.next = next;
+                next = newBlock;
+
+                List<FreeListEntry> newBlockEntries = entries.subList(0, entries.size() / 2);
+                newBlock.entries.addAll(newBlockEntries);
+                newBlockEntries.clear();
+                largestInNextBlock = newBlock.entries.get(newBlock.entries.size() - 1).size;
+                freeListStore.write(newBlock);
+                nextBlock = newBlock.getPos();
+            }
+
+            freeListStore.write(this);
+        }
+
+        private FreeListBlock getNextBlock() {
+            if (next == null) {
+                next = freeListStore.read(nextBlock, FreeListBlock.class);
+                next.prev = this;
+            }
+            return next;
+        }
+
+        public void alloc(Block block) {
+            if (block.hasPos()) {
+                return;
+            }
+
+            int requiredSize = block.getSize();
+
+            if (entries.isEmpty() || requiredSize <= largestInNextBlock) {
+                if (nextBlock.isNull()) {
+                    return;
+                }
+                getNextBlock().alloc(block);
+                return;
+            }
+
+            int index = Collections.binarySearch(entries, new FreeListEntry(null, requiredSize));
+            if (index < 0) {
+                index = -index - 1;
+            }
+            if (index == entries.size()) {
+                // Largest free block is too small
+                return;
+            }
+
+            FreeListEntry entry = entries.remove(index);
+            block.setPos(entry.pos);
+            block.setSize(entry.size);
+            freeListStore.write(this);
+
+            if (entries.size() == 0 && prev != null) {
+                prev.nextBlock = nextBlock;
+                prev.largestInNextBlock = largestInNextBlock;
+                prev.next = next;
+                if (next != null) {
+                    next.prev = prev;
+                }
+                freeListStore.write(prev);
+                freeListStore.remove(this);
+            }
+        }
+    }
+
+    private static class FreeListEntry implements Comparable<FreeListEntry> {
+        final BlockPointer pos;
+        final int size;
+
+        private FreeListEntry(BlockPointer pos, int size) {
+            this.pos = pos;
+            this.size = size;
+        }
+
+        public int compareTo(FreeListEntry o) {
+            if (size > o.size) {
+                return 1;
+            }
+            if (size < o.size) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java
new file mode 100644
index 0000000..000a1fd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 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.cache.internal.btree;
+
+import org.gradle.cache.internal.FileAccess;
+
+import java.util.concurrent.Callable;
+
+public class LockingBlockStore implements BlockStore {
+    private final BlockStore store;
+    private final FileAccess fileAccess;
+
+    public LockingBlockStore(BlockStore store, FileAccess fileAccess) {
+        this.store = store;
+        this.fileAccess = fileAccess;
+    }
+
+    public void open(final Runnable initAction, final BlockStore.Factory factory) {
+        store.open(new Runnable() {
+            public void run() {
+                fileAccess.writeToFile(initAction);
+            }
+        }, factory);
+    }
+
+    public void close() {
+        store.close();
+    }
+
+    public void flush() {
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                store.flush();
+            }
+        });
+    }
+
+    public void clear() {
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                store.clear();
+            }
+        });
+    }
+
+    public void attach(BlockPayload block) {
+        store.attach(block);
+    }
+
+    public <T extends BlockPayload> T read(final BlockPointer pos, final Class<T> payloadType) {
+        return fileAccess.readFromFile(new Callable<T>() {
+            public T call() throws Exception {
+                return store.read(pos, payloadType);
+            }
+        });
+    }
+
+    public <T extends BlockPayload> T readFirst(final Class<T> payloadType) {
+        return fileAccess.readFromFile(new Callable<T>() {
+            public T call() throws Exception {
+                return store.readFirst(payloadType);
+            }
+        });
+    }
+
+    public void write(final BlockPayload block) {
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                store.write(block);
+            }
+        });
+    }
+
+    public void remove(final BlockPayload block) {
+        fileAccess.writeToFile(new Runnable() {
+            public void run() {
+                store.remove(block);
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/StateCheckBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/StateCheckBlockStore.java
new file mode 100755
index 0000000..cf5cdf7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/StateCheckBlockStore.java
@@ -0,0 +1,78 @@
+/*
+ * 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.cache.internal.btree;
+
+public class StateCheckBlockStore implements BlockStore {
+    private final BlockStore blockStore;
+    private boolean open;
+
+    public StateCheckBlockStore(BlockStore blockStore) {
+        this.blockStore = blockStore;
+    }
+
+    public void open(Runnable initAction, Factory factory) {
+        assert !open;
+        open = true;
+        blockStore.open(initAction, factory);
+    }
+
+    public boolean isOpen() {
+        return open;
+    }
+
+    public void close() {
+        if (!open) {
+            return;
+        }
+        open = false;
+        blockStore.close();
+    }
+
+    public void clear() {
+        assert open;
+        blockStore.clear();
+    }
+
+    public void remove(BlockPayload block) {
+        assert open;
+        blockStore.remove(block);
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        assert open;
+        return blockStore.readFirst(payloadType);
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        assert open;
+        return blockStore.read(pos, payloadType);
+    }
+
+    public void write(BlockPayload block) {
+        assert open;
+        blockStore.write(block);
+    }
+
+    public void attach(BlockPayload block) {
+        assert open;
+        blockStore.attach(block);
+    }
+
+    public void flush() {
+        assert open;
+        blockStore.flush();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultProjectEvaluator.java
deleted file mode 100644
index 87ba542..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultProjectEvaluator.java
+++ /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.configuration;
-
-import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-
-public class DefaultProjectEvaluator implements ProjectEvaluator {
-    private final ProjectEvaluator evaluator;
-
-    public DefaultProjectEvaluator(ProjectEvaluator evaluator) {
-        this.evaluator = evaluator;
-    }
-
-    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
-        if (state.getExecuted()) {
-            return;
-        }
-
-        ProjectEvaluationListener listener = project.getProjectEvaluationBroadcaster();
-        listener.beforeEvaluate(project);
-        state.setExecuting(true);
-        try {
-            evaluator.evaluate(project, state);
-        } finally {
-            state.setExecuting(false);
-            state.executed();
-            listener.afterEvaluate(project, state);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
old mode 100644
new mode 100755
index 15a498c..3fc010e
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
@@ -16,13 +16,13 @@
 
 package org.gradle.configuration;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.api.internal.artifacts.dsl.BuildScriptClasspathScriptTransformer;
 import org.gradle.api.internal.artifacts.dsl.BuildScriptTransformer;
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
 import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptHandlerInternal;
-import org.gradle.api.internal.project.DefaultServiceRegistry;
 import org.gradle.groovy.scripts.*;
 import org.gradle.logging.LoggingManagerInternal;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java b/subprojects/core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
index 1cf07fd..ef76396 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/GradleLauncherMetaData.java
@@ -16,7 +16,7 @@
 package org.gradle.configuration;
 
 import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.io.IOException;
 import java.io.Serializable;
@@ -32,7 +32,7 @@ public class GradleLauncherMetaData implements Serializable, BuildClientMetaData
                 output.append(arg);
             }
         } catch (IOException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
index 3470ec6..8fda816 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
@@ -44,7 +44,7 @@ public class ImplicitTasksConfigurer implements Action<ProjectInternal> {
         task.setGroup(HELP_GROUP);
 
         task = tasks.add(TASKS_TASK, TaskReportTask.class);
-        task.setDescription(String.format("Displays the tasks in %s.", project));
+        task.setDescription(String.format("Displays the tasks runnable from %s (some of the displayed tasks may belong to subprojects).", project));
         task.setGroup(HELP_GROUP);
 
         task = tasks.add(DEPENDENCIES_TASK, DependencyReportTask.class);
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.groovy b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.groovy
deleted file mode 100644
index 1f762b1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.groovy
+++ /dev/null
@@ -1,32 +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.configuration
-
-import org.gradle.groovy.scripts.ScriptSource
-
-/**
- * @author Hans Dockter
- */
-class ImportsReader {
-    String getImports() {
-        getClass().getResource('default-imports.txt').text
-    }
-
-    ScriptSource withImports(ScriptSource source) {
-        new ImportsScriptSource(source, this)
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java
new file mode 100644
index 0000000..2ed96f4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsReader.java
@@ -0,0 +1,61 @@
+/*
+ * 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.configuration;
+
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.internal.UncheckedException;
+
+import java.net.URL;
+import java.io.InputStreamReader;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+public class ImportsReader {
+
+    private String importsText;
+
+    public String getImports() {
+        if (importsText == null) {
+            try {
+                URL url = getClass().getResource("default-imports.txt");
+                InputStreamReader reader = new InputStreamReader(url.openStream(), "UTF8");
+
+                int bufferSize = 2048; // at time of writing, the file was about 1k so this should cover in one read
+                StringBuilder imports = new StringBuilder(bufferSize);
+                char[] chars = new char[bufferSize];
+
+                int numRead = reader.read(chars, 0, bufferSize);
+                while (numRead != -1) {
+                    imports.append(chars, 0, numRead);
+                    numRead = reader.read(chars, 0, bufferSize);
+                }
+
+                importsText = imports.toString();
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        return importsText;
+    }
+
+    public ScriptSource withImports(ScriptSource source) {
+        return new ImportsScriptSource(source, this);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java
index dd1d108..b5a1f1e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java
@@ -43,14 +43,11 @@ public class ImportsScriptSource extends DelegatingScriptSource {
             String text = getResource().getText();
             assert text != null;
 
-            String imports;
-            if (text.length() > 0) {
-                imports = '\n' + importsReader.getImports();
+            if (text.matches("\\s*")) {
+                return text;
             } else {
-                imports = "";
+                return text + '\n' + importsReader.getImports();
             }
-
-            return text + imports;
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
new file mode 100644
index 0000000..e2bb2ff
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+
+/**
+ * Manages lifecycle concerns while delegating actual evaluation to another evaluator
+ * 
+ * @see org.gradle.api.internal.project.TopLevelBuildServiceRegistry#createProjectEvaluator()
+ */
+public class LifecycleProjectEvaluator implements ProjectEvaluator {
+    private final ProjectEvaluator evaluator;
+
+    public LifecycleProjectEvaluator(ProjectEvaluator evaluator) {
+        this.evaluator = evaluator;
+    }
+
+    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
+        if (state.getExecuted()) {
+            return;
+        }
+
+        ProjectEvaluationListener listener = project.getProjectEvaluationBroadcaster();
+        listener.beforeEvaluate(project);
+        state.setExecuting(true);
+        try {
+            evaluator.evaluate(project, state);
+        } finally {
+            state.setExecuting(false);
+            state.executed();
+            listener.afterEvaluate(project, state);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/BuildConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/BuildConfigurationAction.java
new file mode 100644
index 0000000..bfe2edf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/BuildConfigurationAction.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.execution;
+
+public interface BuildConfigurationAction {
+    void configure(BuildExecutionContext context);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecuter.java
index 5b91cb1..1a153d4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecuter.java
@@ -28,12 +28,6 @@ public interface BuildExecuter {
     void select(GradleInternal gradle);
 
     /**
-     * Returns the description of this executer. The result is used for log and error messages. Called after {@link
-     * #select(org.gradle.api.internal.GradleInternal)}.
-     */
-    String getDisplayName();
-
-    /**
      * Executes the selected tasks. Called after {@link #select(org.gradle.api.internal.GradleInternal)}.
      */
     void execute();
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecutionAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecutionAction.java
new file mode 100644
index 0000000..d4cc136
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecutionAction.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.execution;
+
+public interface BuildExecutionAction {
+    void execute(BuildExecutionContext context);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecutionContext.java b/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecutionContext.java
new file mode 100644
index 0000000..44fd43a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/BuildExecutionContext.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.execution;
+
+import org.gradle.api.internal.GradleInternal;
+
+public interface BuildExecutionContext {
+    GradleInternal getGradle();
+
+    void proceed();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java
index 684f9da..5a1e292 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2011 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.
@@ -15,43 +15,57 @@
  */
 package org.gradle.execution;
 
+import com.google.common.collect.Lists;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.Task;
-import org.gradle.api.specs.Spec;
 
-import java.util.*;
+import java.util.List;
 
-/**
- * <p>The standard {@link BuildExecuter} implementation.</p>
- */
-public class DefaultBuildExecuter extends DelegatingBuildExecuter {
-    private final Set<String> excludedTaskNames;
-
-    public DefaultBuildExecuter(Collection<String> includedTaskNames, Collection<String> excludedTaskNames) {
-        this.excludedTaskNames = new HashSet<String>(excludedTaskNames);
-        if (includedTaskNames.isEmpty()) {
-            setDelegate(new ProjectDefaultsBuildExecuter());
-        } else {
-            setDelegate(new TaskNameResolvingBuildExecuter(includedTaskNames));
-        }
+public class DefaultBuildExecuter implements BuildExecuter {
+    private final List<BuildConfigurationAction> configurationActions;
+    private final List<BuildExecutionAction> executionActions;
+    private GradleInternal gradle;
+
+    public DefaultBuildExecuter(Iterable<? extends BuildConfigurationAction> configurationActions, Iterable<? extends BuildExecutionAction> executionActions) {
+        this.configurationActions = Lists.newArrayList(configurationActions);
+        this.executionActions = Lists.newArrayList(executionActions);
     }
 
-    @Override
     public void select(GradleInternal gradle) {
-        if (!excludedTaskNames.isEmpty()) {
-            final Set<Task> excludedTasks = new HashSet<Task>();
-            TaskSelector selector = new TaskSelector();
-            for (String taskName : excludedTaskNames) {
-                selector.selectTasks(gradle, taskName);
-                excludedTasks.addAll(selector.getTasks());
+        this.gradle = gradle;
+        configure(0);
+    }
+
+    private void configure(final int index) {
+        if (index >= configurationActions.size()) {
+            return;
+        }
+        configurationActions.get(index).configure(new BuildExecutionContext() {
+            public GradleInternal getGradle() {
+                return gradle;
+            }
+
+            public void proceed() {
+                configure(index + 1);
             }
-            gradle.getTaskGraph().useFilter(new Spec<Task>() {
-                public boolean isSatisfiedBy(Task task) {
-                    return !excludedTasks.contains(task);
-                }
-            });
+        });
+    }
+
+    public void execute() {
+        execute(0);
+    }
+
+    private void execute(final int index) {
+        if (index >= executionActions.size()) {
+            return;
         }
+        executionActions.get(index).execute(new BuildExecutionContext() {
+            public GradleInternal getGradle() {
+                return gradle;
+            }
 
-        super.select(gradle);
+            public void proceed() {
+                execute(index + 1);
+            }
+        });
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
index e8b7a7c..32ac012 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
@@ -41,9 +41,14 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
 
     private final ListenerBroadcast<TaskExecutionGraphListener> graphListeners;
     private final ListenerBroadcast<TaskExecutionListener> taskListeners;
-    private final Set<Task> executionPlan = new LinkedHashSet<Task>();
+    private final Map<Task, TaskInfo> executionPlan = new LinkedHashMap<Task, TaskInfo>();
     private boolean populated;
     private Spec<? super Task> filter = Specs.satisfyAll();
+    private TaskFailureHandler failureHandler = new TaskFailureHandler() {
+        public void onTaskFailure(Task task) {
+            task.getState().rethrowFailure();
+        }
+    };
 
     public DefaultTaskGraphExecuter(ListenerManager listenerManager) {
         graphListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionGraphListener.class);
@@ -75,7 +80,7 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
         graphListeners.getSource().graphPopulated(this);
 
         try {
-            doExecute(executionPlan);
+            doExecute(executionPlan.values());
             logger.debug("Timing: Executing the DAG took " + clock.getTime());
         } finally {
             executionPlan.clear();
@@ -100,7 +105,7 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
                 queue.remove(0);
                 continue;
             }
-            if (executionPlan.contains(task)) {
+            if (executionPlan.containsKey(task)) {
                 // Already in plan - skip
                 queue.remove(0);
                 continue;
@@ -122,7 +127,15 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
                 // Have visited this task's dependencies - add it to the end of the plan
                 queue.remove(0);
                 visiting.remove(task);
-                executionPlan.add(task);
+                Set<TaskInfo> dependencies = new HashSet<TaskInfo>();
+                for (Task dependency : context.getDependencies(task)) {
+                    TaskInfo dependencyInfo = executionPlan.get(dependency);
+                    if (dependencyInfo != null) {
+                        dependencies.add(dependencyInfo);
+                    }
+                    // else - the dependency has been filtered, so ignore it
+                }
+                executionPlan.put(task, new TaskInfo((TaskInternal) task, dependencies));
             }
         }
     }
@@ -155,16 +168,33 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
         taskListeners.add("afterExecute", closure);
     }
 
-    private void doExecute(Iterable<? extends Task> tasks) {
-        for (Task task : tasks) {
+    public void useFailureHandler(TaskFailureHandler handler) {
+        this.failureHandler = handler;
+    }
+
+    private void doExecute(Iterable<? extends TaskInfo> tasks) {
+        for (TaskInfo task : tasks) {
             executeTask(task);
         }
     }
 
-    private void executeTask(Task task) {
+    private void executeTask(TaskInfo taskInfo) {
+        TaskInternal task = taskInfo.task;
+        for (TaskInfo dependency : taskInfo.dependencies) {
+            if (!dependency.executed) {
+                // Cannot execute this task, as some dependencies have not been executed
+                return;
+            }
+        }
+        
         taskListeners.getSource().beforeExecute(task);
         try {
-            ((TaskInternal) task).execute();
+            task.executeWithoutThrowingTaskFailure();
+            if (task.getState().getFailure() != null) {
+                failureHandler.onTaskFailure(task);
+            } else {
+                taskInfo.executed = true;
+            }
         } finally {
             taskListeners.getSource().afterExecute(task, task.getState());
         }
@@ -172,7 +202,7 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
 
     public boolean hasTask(Task task) {
         assertPopulated();
-        return executionPlan.contains(task);
+        return executionPlan.containsKey(task);
     }
 
     public boolean hasTask(String path) {
@@ -188,7 +218,7 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
 
     public List<Task> getAllTasks() {
         assertPopulated();
-        return new ArrayList<Task>(executionPlan);
+        return new ArrayList<Task>(executionPlan.keySet());
     }
 
     private void assertPopulated() {
@@ -197,4 +227,15 @@ public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
                     "Task information is not available, as this task execution graph has not been populated.");
         }
     }
+    
+    private static class TaskInfo {
+        private final TaskInternal task;
+        private final Set<TaskInfo> dependencies;
+        private boolean executed;
+
+        private TaskInfo(TaskInternal task, Set<TaskInfo> dependencies) {
+            this.task = task;
+            this.dependencies = dependencies;
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTasksBuildExecutionAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTasksBuildExecutionAction.java
new file mode 100644
index 0000000..6d8f614
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/DefaultTasksBuildExecutionAction.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.execution;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.configuration.ImplicitTasksConfigurer;
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A {@link BuildConfigurationAction} that selects the default tasks for a project, or if none are defined, the 'help' task.
+ */
+public class DefaultTasksBuildExecutionAction implements BuildConfigurationAction {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTasksBuildExecutionAction.class);
+
+    public void configure(BuildExecutionContext context) {
+        StartParameter startParameter = context.getGradle().getStartParameter();
+
+        if (!startParameter.getTaskNames().isEmpty()) {
+            context.proceed();
+            return;
+        }
+
+        // Gather the default tasks from this first group project
+        ProjectInternal project = context.getGradle().getDefaultProject();
+        List<String> defaultTasks = project.getDefaultTasks();
+        if (defaultTasks.size() == 0) {
+            defaultTasks = Arrays.asList(ImplicitTasksConfigurer.HELP_TASK);
+            LOGGER.info("No tasks specified. Using default task {}", GUtil.toString(defaultTasks));
+        } else {
+            LOGGER.info("No tasks specified. Using project default tasks {}", GUtil.toString(defaultTasks));
+        }
+
+        startParameter.setTaskNames(defaultTasks);
+        context.proceed();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DelegatingBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/DelegatingBuildExecuter.java
deleted file mode 100644
index fe1410e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/DelegatingBuildExecuter.java
+++ /dev/null
@@ -1,56 +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.execution;
-
-import org.gradle.api.internal.GradleInternal;
-
-public class DelegatingBuildExecuter implements BuildExecuter {
-    private BuildExecuter delegate;
-    private GradleInternal gradle;
-
-    public DelegatingBuildExecuter(BuildExecuter delegate) {
-        this.delegate = delegate;
-    }
-
-    public DelegatingBuildExecuter() {
-    }
-
-    protected GradleInternal getBuild() {
-        return gradle;
-    }
-
-    protected BuildExecuter getDelegate() {
-        return delegate;
-    }
-
-    protected void setDelegate(BuildExecuter delegate) {
-        this.delegate = delegate;
-    }
-
-    public void select(GradleInternal gradle) {
-        this.gradle = gradle;
-        delegate.select(gradle);
-    }
-
-    public String getDisplayName() {
-        return delegate.getDisplayName();
-    }
-
-    public void execute() {
-        delegate.execute();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DryRunBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/DryRunBuildExecuter.java
deleted file mode 100644
index 036a119..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/DryRunBuildExecuter.java
+++ /dev/null
@@ -1,35 +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.execution;
-
-import org.gradle.api.Task;
-
-/**
- * A {@link org.gradle.execution.BuildExecuter} which disables all selected tasks before they are executed.
- */
-public class DryRunBuildExecuter extends DelegatingBuildExecuter {
-    public DryRunBuildExecuter(BuildExecuter delegate) {
-        super(delegate);
-    }
-
-    @Override
-    public void execute() {
-        for (Task task : getBuild().getTaskGraph().getAllTasks()) {
-            task.setEnabled(false);
-        }
-        super.execute();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/DryRunBuildExecutionAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/DryRunBuildExecutionAction.java
new file mode 100644
index 0000000..56bc159
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/DryRunBuildExecutionAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+
+/**
+ * A {@link org.gradle.execution.BuildExecutionAction} that disables all selected tasks before they are executed.
+ */
+public class DryRunBuildExecutionAction implements BuildExecutionAction {
+    public void execute(BuildExecutionContext context) {
+        GradleInternal gradle = context.getGradle();
+        if (gradle.getStartParameter().isDryRun()) {
+            for (Task task : gradle.getTaskGraph().getAllTasks()) {
+                task.setEnabled(false);
+            }
+        }
+        context.proceed();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
new file mode 100644
index 0000000..92ae183
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationAction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.specs.Spec;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link BuildConfigurationAction} which filters excluded tasks.
+ */
+public class ExcludedTaskFilteringBuildConfigurationAction implements BuildConfigurationAction {
+    private final TaskSelector selector;
+
+    public ExcludedTaskFilteringBuildConfigurationAction() {
+        this(new TaskSelector());
+    }
+
+    ExcludedTaskFilteringBuildConfigurationAction(TaskSelector taskSelector) {
+        selector = taskSelector;
+    }
+
+    public void configure(BuildExecutionContext context) {
+        GradleInternal gradle = context.getGradle();
+        Set<String> excludedTaskNames = gradle.getStartParameter().getExcludedTaskNames();
+        if (!excludedTaskNames.isEmpty()) {
+            final Set<Task> excludedTasks = new HashSet<Task>();
+            for (String taskName : excludedTaskNames) {
+                selector.selectTasks(gradle, taskName);
+                excludedTasks.addAll(selector.getTasks());
+            }
+            gradle.getTaskGraph().useFilter(new Spec<Task>() {
+                public boolean isSatisfiedBy(Task task) {
+                    return !excludedTasks.contains(task);
+                }
+            });
+        }
+
+        context.proceed();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectDefaultsBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectDefaultsBuildExecuter.java
deleted file mode 100644
index b87166a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectDefaultsBuildExecuter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 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.execution;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.configuration.ImplicitTasksConfigurer;
-import org.gradle.util.GUtil;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A {@link BuildExecuter} which selects the default tasks for a project.
- */
-public class ProjectDefaultsBuildExecuter extends DelegatingBuildExecuter {
-    private List<String> defaultTasks;
-    private String displayName;
-
-    public void select(GradleInternal gradle) {
-        if (getDelegate() == null) {
-            // Gather the default tasks from this first group project
-            ProjectInternal project = gradle.getDefaultProject();
-            defaultTasks = project.getDefaultTasks();
-            displayName = String.format("project default tasks %s", GUtil.toString(defaultTasks));
-            if (defaultTasks.size() == 0) {
-                defaultTasks = Arrays.asList(ImplicitTasksConfigurer.HELP_TASK);
-                displayName = String.format("default task %s", GUtil.toString(defaultTasks));
-            }
-            setDelegate(new TaskNameResolvingBuildExecuter(defaultTasks));
-        }
-
-        super.select(gradle);
-    }
-
-    @Override
-    public String getDisplayName() {
-        return displayName;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java
new file mode 100644
index 0000000..69dac7d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/SelectedTaskExecutionAction.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.AbstractMultiCauseException;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.internal.UncheckedException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SelectedTaskExecutionAction implements BuildExecutionAction {
+    public void execute(BuildExecutionContext context) {
+        GradleInternal gradle = context.getGradle();
+        TaskGraphExecuter taskGraph = gradle.getTaskGraph();
+        if (gradle.getStartParameter().isContinueOnFailure()) {
+            MultipleFailuresHandler handler = new MultipleFailuresHandler();
+            taskGraph.useFailureHandler(handler);
+            taskGraph.execute();
+            handler.rethrowFailures();
+        } else {
+            taskGraph.execute();
+        }
+    }
+
+    private static class MultipleFailuresHandler implements TaskFailureHandler {
+        final List<Throwable> failures = new ArrayList<Throwable>();
+        
+        public void onTaskFailure(Task task) {
+            failures.add(task.getState().getFailure());
+        }
+
+        public void rethrowFailures() {
+            if (failures.isEmpty()) {
+                return;
+            }
+            if (failures.size() == 1) {
+                throw UncheckedException.throwAsUncheckedException(failures.get(0));
+            } else {
+                throw new AbstractMultiCauseException("Multiple tasks failed.", failures);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskFailureHandler.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskFailureHandler.java
new file mode 100644
index 0000000..7cc2f86
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskFailureHandler.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.execution;
+
+import org.gradle.api.Task;
+
+public interface TaskFailureHandler {
+    /**
+     * Called when execution for a task fails. Can throw an exception to abort execution.
+     */
+    void onTaskFailure(Task task);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java
index 7764306..6978a45 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java
@@ -38,8 +38,7 @@ public interface TaskGraphExecuter extends TaskExecutionGraph {
     void execute();
 
     /**
-     * Adds the given tasks and their dependencies to this graph, then executes all the tasks in this graph. Discards
-     * the contents of this graph when completed.
+     * Sets the handler to use when a task fails.
      */
-    void execute(Iterable<? extends Task> tasks);
+    void useFailureHandler(TaskFailureHandler handler);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
new file mode 100644
index 0000000..3a493a4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationAction.java
@@ -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.execution;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.util.GUtil;
+import org.gradle.util.JavaMethod;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link BuildConfigurationAction} which selects tasks which match the provided names. For each name, selects all tasks in all
+ * projects whose name is the given name.
+ */
+public class TaskNameResolvingBuildConfigurationAction implements BuildConfigurationAction {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TaskNameResolvingBuildConfigurationAction.class);
+    private final TaskNameResolver taskNameResolver;
+
+    public TaskNameResolvingBuildConfigurationAction() {
+        this(new TaskNameResolver());
+    }
+
+    TaskNameResolvingBuildConfigurationAction(TaskNameResolver taskNameResolver) {
+        this.taskNameResolver = taskNameResolver;
+    }
+
+    public void configure(BuildExecutionContext context) {
+        GradleInternal gradle = context.getGradle();
+        List<String> taskNames = gradle.getStartParameter().getTaskNames();
+        Multimap<String, Task> selectedTasks = doSelect(gradle, taskNames, taskNameResolver);
+
+        TaskGraphExecuter executer = gradle.getTaskGraph();
+        for (String name : selectedTasks.keySet()) {
+            executer.addTasks(selectedTasks.get(name));
+        }
+
+        if (selectedTasks.keySet().size() == 1) {
+            LOGGER.info("Selected primary task {}", GUtil.toString(selectedTasks.keySet()));
+        } else {
+            LOGGER.info("Selected primary tasks {}", GUtil.toString(selectedTasks.keySet()));
+        }
+
+        context.proceed();
+    }
+
+    private Multimap<String, Task> doSelect(GradleInternal gradle, List<String> paths, TaskNameResolver taskNameResolver) {
+        SetMultimap<String, Task> matches = LinkedHashMultimap.create();
+        TaskSelector selector = new TaskSelector(taskNameResolver);
+        List<String> remainingPaths = paths;
+        while (!remainingPaths.isEmpty()) {
+            String path = remainingPaths.get(0);
+            selector.selectTasks(gradle, path);
+
+            CommandLineParser commandLineParser = new CommandLineParser();
+            Set<Task> tasks = selector.getTasks();
+            Map<String, JavaMethod<Task, ?>> options = new HashMap<String, JavaMethod<Task, ?>>();
+            if (tasks.size() == 1) {
+                for (Class<?> type = tasks.iterator().next().getClass(); type != Object.class; type = type.getSuperclass()) {
+                    for (Method method : type.getDeclaredMethods()) {
+                        CommandLineOption commandLineOption = method.getAnnotation(CommandLineOption.class);
+                        if (commandLineOption != null) {
+                            commandLineParser.option(commandLineOption.options()).hasDescription(commandLineOption.description());
+                            options.put(commandLineOption.options()[0], JavaMethod.create(Task.class, Object.class, method));
+                        }
+                    }
+                }
+            }
+
+            ParsedCommandLine commandLine = commandLineParser.parse(remainingPaths.subList(1, remainingPaths.size()));
+            for (Map.Entry<String, JavaMethod<Task, ?>> entry : options.entrySet()) {
+                if (commandLine.hasOption(entry.getKey())) {
+                    for (Task task : tasks) {
+                        entry.getValue().invoke(task, true);
+                    }
+                }
+            }
+            remainingPaths = commandLine.getExtraArguments();
+
+            matches.putAll(selector.getTaskName(), tasks);
+        }
+
+        return matches;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildExecuter.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildExecuter.java
deleted file mode 100644
index 1208d09..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildExecuter.java
+++ /dev/null
@@ -1,115 +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.execution;
-
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.SetMultimap;
-import org.gradle.api.Task;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.tasks.CommandLineOption;
-import org.gradle.initialization.CommandLineParser;
-import org.gradle.initialization.ParsedCommandLine;
-import org.gradle.util.GUtil;
-import org.gradle.util.JavaMethod;
-
-import java.lang.reflect.Method;
-import java.util.*;
-
-/**
- * A {@link BuildExecuter} which selects tasks which match the provided names. For each name, selects all tasks in all
- * projects whose name is the given name.
- */
-public class TaskNameResolvingBuildExecuter implements BuildExecuter {
-    private final List<String> names;
-    private String description;
-    private TaskGraphExecuter executer;
-    private final TaskNameResolver taskNameResolver;
-
-    public TaskNameResolvingBuildExecuter(Collection<String> names) {
-        this(names, new TaskNameResolver());
-    }
-
-    TaskNameResolvingBuildExecuter(Collection<String> names, TaskNameResolver taskNameResolver) {
-        this.taskNameResolver = taskNameResolver;
-        this.names = new ArrayList<String>(names);
-    }
-
-    public List<String> getNames() {
-        return names;
-    }
-
-    public void select(GradleInternal gradle) {
-        Multimap<String, Task> selectedTasks = doSelect(gradle, names, taskNameResolver);
-
-        this.executer = gradle.getTaskGraph();
-        for (String name : selectedTasks.keySet()) {
-            executer.addTasks(selectedTasks.get(name));
-        }
-        if (selectedTasks.keySet().size() == 1) {
-            description = String.format("primary task %s", GUtil.toString(selectedTasks.keySet()));
-        } else {
-            description = String.format("primary tasks %s", GUtil.toString(selectedTasks.keySet()));
-        }
-    }
-
-    private Multimap<String, Task> doSelect(GradleInternal gradle, List<String> paths, TaskNameResolver taskNameResolver) {
-        SetMultimap<String, Task> matches = LinkedHashMultimap.create();
-        TaskSelector selector = new TaskSelector(taskNameResolver);
-        List<String> remainingPaths = paths;
-        while (!remainingPaths.isEmpty()) {
-            String path = remainingPaths.get(0);
-            selector.selectTasks(gradle, path);
-
-            CommandLineParser commandLineParser = new CommandLineParser();
-            Set<Task> tasks = selector.getTasks();
-            Map<String, JavaMethod<Task, ?>> options = new HashMap<String, JavaMethod<Task, ?>>();
-            if (tasks.size() == 1) {
-                for (Class<?> type = tasks.iterator().next().getClass(); type != Object.class; type = type.getSuperclass()) {
-                    for (Method method : type.getDeclaredMethods()) {
-                        CommandLineOption commandLineOption = method.getAnnotation(CommandLineOption.class);
-                        if (commandLineOption != null) {
-                            commandLineParser.option(commandLineOption.options()).hasDescription(commandLineOption.description());
-                            options.put(commandLineOption.options()[0], new JavaMethod<Task, Object>(Task.class, Object.class, method));
-                        }
-                    }
-                }
-            }
-
-            ParsedCommandLine commandLine = commandLineParser.parse(remainingPaths.subList(1, remainingPaths.size()));
-            for (Map.Entry<String, JavaMethod<Task, ?>> entry : options.entrySet()) {
-                if (commandLine.hasOption(entry.getKey())) {
-                    for (Task task : tasks) {
-                        entry.getValue().invoke(task, true);
-                    }
-                }
-            }
-            remainingPaths = commandLine.getExtraArguments();
-
-            matches.putAll(selector.getTaskName(), tasks);
-        }
-
-        return matches;
-    }
-
-    public String getDisplayName() {
-        return description;
-    }
-
-    public void execute() {
-        executer.execute();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGenerator.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGenerator.java
deleted file mode 100644
index 0b985f7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGenerator.java
+++ /dev/null
@@ -1,84 +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.groovy.scripts;
-
-import groovy.lang.Script;
-import org.gradle.util.ReflectionUtil;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class AsmBackedEmptyScriptGenerator {
-    private static final Map<Class<?>, Class<?>> CACHED_CLASSES = new HashMap<Class<?>, Class<?>>();
-
-    <T extends Script> Class<? extends T> generate(Class<T> type) {
-        Class<?> subclass = CACHED_CLASSES.get(type);
-        if (subclass == null) {
-            subclass = generateEmptyScriptClass(type);
-            CACHED_CLASSES.put(type, subclass);
-        }
-        return subclass.asSubclass(type);
-    }
-
-    private <T extends Script> Class<? extends T> generateEmptyScriptClass(Class<T> type) {
-        ClassWriter visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-        String typeName = type.getName() + "_Decorated";
-        Type generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";");
-        Type superclassType = Type.getType(type);
-        visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
-                superclassType.getInternalName(), new String[0]);
-
-        // Constructor
-
-        String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0]);
-        MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", constructorDescriptor, null,
-                new String[0]);
-        methodVisitor.visitCode();
-
-        // super()
-        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
-                constructorDescriptor);
-
-        methodVisitor.visitInsn(Opcodes.RETURN);
-        methodVisitor.visitMaxs(0, 0);
-        methodVisitor.visitEnd();
-
-        // run() method
-
-        String runDesciptor = Type.getMethodDescriptor(Type.getType(Object.class), new Type[0]);
-        methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "run", runDesciptor, null, new String[0]);
-        methodVisitor.visitCode();
-
-        // return null
-        methodVisitor.visitInsn(Opcodes.ACONST_NULL);
-
-        methodVisitor.visitInsn(Opcodes.ARETURN);
-        methodVisitor.visitMaxs(0, 0);
-        methodVisitor.visitEnd();
-
-        visitor.visitEnd();
-
-        byte[] bytecode = visitor.toByteArray();
-        return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
-                typeName, bytecode, 0, bytecode.length
-        });
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
old mode 100644
new mode 100755
index 1afd6fb..22d7dad
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
@@ -1,72 +1,77 @@
-/*
- * 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.groovy.scripts
-
-import org.gradle.api.internal.project.ServiceRegistry
-import org.gradle.api.internal.file.FileOperations
-import org.gradle.logging.StandardOutputCapture
-
-/**
- * @author Hans Dockter
- *
- */
-abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations {
-    private StandardOutputCapture standardOutputCapture
-    private Object target
-
-    void init(Object target, ServiceRegistry services) {
-        standardOutputCapture = services.get(StandardOutputCapture.class)
-        this.target = target
-    }
-
-    def Object getScriptTarget() {
-        return target
-    }
-
-    def StandardOutputCapture getStandardOutputCapture() {
-        return standardOutputCapture
-    }
-
-    void setProperty(String property, newValue) {
-        if ("metaClass" == property) {
-            setMetaClass((MetaClass) newValue)
-        } else if ("scriptTarget" == property) {
-            target = newValue
-        } else {
-            target."$property" = newValue
-        }
-    }
-
-    def propertyMissing(String property) {
-        if ('out' == property) {
-            System.out
-        } else {
-            target."$property"
-        }
-    }
-
-    def hasProperty(String property) {
-        target.hasProperty(property)
-    }
-
-    def methodMissing(String name, Object params) {
-        return target.invokeMethod(name, params)
-    }
-}
-
-
+/*
+ * 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.groovy.scripts
+
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.api.internal.file.FileOperations
+import org.gradle.logging.StandardOutputCapture
+import org.gradle.api.internal.ProcessOperations
+
+/**
+ * @author Hans Dockter
+ *
+ */
+abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations {
+    private StandardOutputCapture standardOutputCapture
+    private Object target
+
+    void init(Object target, ServiceRegistry services) {
+        standardOutputCapture = services.get(StandardOutputCapture.class)
+        this.target = target
+    }
+
+    def Object getScriptTarget() {
+        return target
+    }
+
+    def StandardOutputCapture getStandardOutputCapture() {
+        return standardOutputCapture
+    }
+
+    void setProperty(String property, newValue) {
+        if ("metaClass" == property) {
+            setMetaClass((MetaClass) newValue)
+        } else if ("scriptTarget" == property) {
+            target = newValue
+        } else {
+            target."$property" = newValue
+        }
+    }
+
+    def propertyMissing(String property) {
+        if ('out' == property) {
+            System.out
+        } else {
+            target."$property"
+        }
+    }
+
+    def getProperties() {
+        return target.getProperties()
+    }
+
+    def hasProperty(String property) {
+        target.hasProperty(property)
+    }
+
+    def methodMissing(String name, Object params) {
+        return target.invokeMethod(name, params)
+    }
+}
+
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandler.java
deleted file mode 100644
index 6704f75..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandler.java
+++ /dev/null
@@ -1,46 +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.groovy.scripts;
-
-import groovy.lang.Script;
-
-import java.io.File;
-import java.util.*;
-
-public class CachingScriptCompilationHandler implements ScriptCompilationHandler {
-    private final ScriptCompilationHandler handler;
-    private final Map<Collection<Object>, Class<?>> cachedClasses = new HashMap<Collection<Object>, Class<?>>();
-
-    public CachingScriptCompilationHandler(ScriptCompilationHandler handler) {
-        this.handler = handler;
-    }
-
-    public void compileToDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir, Transformer transformer,
-                             Class<? extends Script> scriptBaseClass) {
-        handler.compileToDir(source, classLoader, scriptCacheDir, transformer, scriptBaseClass);
-    }
-
-    public <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader,
-                                                             File scriptCacheDir, Class<T> scriptBaseClass) {
-        List<Object> key = Arrays.asList(source.getClassName(), classLoader, scriptCacheDir);
-        Class<?> c = cachedClasses.get(key);
-        if (c == null) {
-            c = handler.loadFromDir(source, classLoader, scriptCacheDir, scriptBaseClass);
-            cachedClasses.put(key, c);
-        }
-        return c.asSubclass(scriptBaseClass);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptSource.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
old mode 100644
new mode 100755
index 05abd83..127c53f
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
@@ -1,172 +1,175 @@
-/*
- * 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.groovy.scripts
-
-import org.gradle.api.PathValidation
-import org.gradle.api.Script
-import org.gradle.api.file.ConfigurableFileCollection
-import org.gradle.api.file.ConfigurableFileTree
-import org.gradle.api.file.CopySpec
-import org.gradle.api.file.FileTree
-import org.gradle.api.initialization.dsl.ScriptHandler
-import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction
-import org.gradle.api.internal.project.ServiceRegistry
-import org.gradle.api.logging.LogLevel
-import org.gradle.api.logging.Logger
-import org.gradle.api.logging.Logging
-import org.gradle.api.logging.LoggingManager
-import org.gradle.api.plugins.ObjectConfigurationAction
-import org.gradle.api.tasks.WorkResult
-import org.gradle.configuration.ScriptPluginFactory
-import org.gradle.util.ConfigureUtil
-import org.gradle.process.ExecResult
-import org.gradle.api.internal.file.*
-import org.gradle.util.DeprecationLogger
-
-abstract class DefaultScript extends BasicScript {
-    private static final Logger LOGGER = Logging.getLogger(Script.class)
-    private ServiceRegistry services
-    private FileOperations fileOperations
-    private LoggingManager loggingManager
-
-    def void init(Object target, ServiceRegistry services) {
-        super.init(target, services);
-        this.services = services
-        loggingManager = services.get(LoggingManager.class)
-        if (target instanceof FileOperations) {
-            fileOperations = target
-        } else if (scriptSource.resource.file) {
-            fileOperations = new DefaultFileOperations(new BaseDirConverter(scriptSource.resource.file.parentFile), null, null)
-        } else {
-            fileOperations = new DefaultFileOperations(new IdentityFileResolver(), null, null)
-        }
-    }
-
-    FileResolver getFileResolver() {
-        fileOperations.fileResolver
-    }
-
-    void apply(Closure closure) {
-        ObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(ScriptPluginFactory.class), scriptTarget)
-        ConfigureUtil.configure(closure, action)
-        action.execute()
-    }
-
-    void apply(Map options) {
-        ObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(ScriptPluginFactory.class), scriptTarget)
-        ConfigureUtil.configureByMap(options, action)
-        action.execute()
-    }
-
-    ScriptHandler getBuildscript() {
-        return services.get(ScriptHandler.class);
-    }
-
-    void buildscript(Closure configureClosure) {
-        ConfigureUtil.configure(configureClosure, getBuildscript())
-    }
-
-    File file(Object path) {
-        fileOperations.file(path)
-    }
-
-    File file(Object path, PathValidation validation) {
-        fileOperations.file(path, validation)
-    }
-
-    URI uri(Object path) {
-        fileOperations.uri(path)
-    }
-
-    ConfigurableFileCollection files(Object... paths) {
-        fileOperations.files(paths)
-    }
-
-    ConfigurableFileCollection files(Object paths, Closure configureClosure) {
-        fileOperations.files(paths, configureClosure)
-    }
-
-    String relativePath(Object path) {
-        fileOperations.relativePath(path)
-    }
-
-    ConfigurableFileTree fileTree(Object baseDir) {
-        fileOperations.fileTree(baseDir)
-    }
-
-    ConfigurableFileTree fileTree(Map args) {
-        fileOperations.fileTree(args)
-    }
-
-    ConfigurableFileTree fileTree(Closure closure) {
-        fileOperations.fileTree(closure)
-    }
-
-    FileTree zipTree(Object zipPath) {
-        fileOperations.zipTree(zipPath)
-    }
-
-    FileTree tarTree(Object tarPath) {
-        fileOperations.tarTree(tarPath)
-    }
-
-    WorkResult copy(Closure closure) {
-        fileOperations.copy(closure)
-    }
-
-    CopySpec copySpec(Closure closure) {
-        fileOperations.copySpec(closure)
-    }
-
-    File mkdir(Object path) {
-        return fileOperations.mkdir(path);
-    }
-
-    boolean delete(Object... paths) {
-        return fileOperations.delete(paths);
-    }
-
-    ExecResult javaexec(Closure closure) {
-        return fileOperations.javaexec(closure);
-    }
-
-    ExecResult exec(Closure closure) {
-        return fileOperations.exec(closure);
-    }
-
-    LoggingManager getLogging() {
-        return loggingManager
-    }
-
-    public void captureStandardOutput(LogLevel level) {
-        DeprecationLogger.nagUser('captureStandardOutput()', 'getLogging().captureStandardOutput()')
-        logging.captureStandardOutput(level)
-    }
-
-    public void disableStandardOutputCapture() {
-        DeprecationLogger.nagUser('disableStandardOutputCapture')
-        logging.disableStandardOutputCapture()
-    }
-
-    public Logger getLogger() {
-        return LOGGER;
-    }
-
-    def String toString() {
-        return "script"
-    }
-}
+/*
+ * 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.groovy.scripts
+
+import org.gradle.api.PathValidation
+import org.gradle.api.Script
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.ConfigurableFileTree
+import org.gradle.api.file.CopySpec
+import org.gradle.api.file.FileTree
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.ProcessOperations
+import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.api.logging.LoggingManager
+import org.gradle.api.plugins.ObjectConfigurationAction
+import org.gradle.api.resources.ResourceHandler
+import org.gradle.api.tasks.WorkResult
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.process.ExecResult
+import org.gradle.util.ConfigureUtil
+import org.gradle.util.DeprecationLogger
+import org.gradle.api.internal.file.*
+
+abstract class DefaultScript extends BasicScript {
+    private static final Logger LOGGER = Logging.getLogger(Script.class)
+    private ServiceRegistry services
+    private FileOperations fileOperations
+    private ProcessOperations processOperations
+    private LoggingManager loggingManager
+
+    def void init(Object target, ServiceRegistry services) {
+        super.init(target, services);
+        this.services = services
+        loggingManager = services.get(LoggingManager.class)
+        if (target instanceof FileOperations) {
+            fileOperations = target
+        } else if (scriptSource.resource.file) {
+            fileOperations = new DefaultFileOperations(new BaseDirFileResolver(FileSystems.default, scriptSource.resource.file.parentFile), null, null)
+        } else {
+            fileOperations = new DefaultFileOperations(new IdentityFileResolver(), null, null)
+        }
+        processOperations = fileOperations
+    }
+
+    FileResolver getFileResolver() {
+        fileOperations.fileResolver
+    }
+
+    void apply(Closure closure) {
+        ObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(ScriptPluginFactory.class), scriptTarget)
+        ConfigureUtil.configure(closure, action)
+        action.execute()
+    }
+
+    void apply(Map options) {
+        ObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(ScriptPluginFactory.class), scriptTarget)
+        ConfigureUtil.configureByMap(options, action)
+        action.execute()
+    }
+
+    ScriptHandler getBuildscript() {
+        return services.get(ScriptHandler.class);
+    }
+
+    void buildscript(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getBuildscript())
+    }
+
+    File file(Object path) {
+        fileOperations.file(path)
+    }
+
+    File file(Object path, PathValidation validation) {
+        fileOperations.file(path, validation)
+    }
+
+    URI uri(Object path) {
+        fileOperations.uri(path)
+    }
+
+    ConfigurableFileCollection files(Object... paths) {
+        fileOperations.files(paths)
+    }
+
+    ConfigurableFileCollection files(Object paths, Closure configureClosure) {
+        fileOperations.files(paths, configureClosure)
+    }
+
+    String relativePath(Object path) {
+        fileOperations.relativePath(path)
+    }
+
+    ConfigurableFileTree fileTree(Object baseDir) {
+        fileOperations.fileTree((Object)baseDir)
+    }
+
+    ConfigurableFileTree fileTree(Map args) {
+        fileOperations.fileTree(args)
+    }
+
+    ConfigurableFileTree fileTree(Closure closure) {
+        DeprecationLogger.nagUserWith("fileTree(Closure) is a deprecated method. Use fileTree((Object){ baseDir }) to have the closure used as the file tree base directory");
+        fileOperations.fileTree(closure)
+    }
+
+    ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure) {
+        fileOperations.fileTree(baseDir, configureClosure);
+    }
+
+    FileTree zipTree(Object zipPath) {
+        fileOperations.zipTree(zipPath)
+    }
+
+    FileTree tarTree(Object tarPath) {
+        fileOperations.tarTree(tarPath)
+    }
+
+    ResourceHandler getResources() {
+        fileOperations.resources
+    }
+
+    WorkResult copy(Closure closure) {
+        fileOperations.copy(closure)
+    }
+
+    CopySpec copySpec(Closure closure) {
+        fileOperations.copySpec(closure)
+    }
+
+    File mkdir(Object path) {
+        return fileOperations.mkdir(path);
+    }
+
+    boolean delete(Object... paths) {
+        return fileOperations.delete(paths);
+    }
+
+    ExecResult javaexec(Closure closure) {
+        return processOperations.javaexec(closure);
+    }
+
+    ExecResult exec(Closure closure) {
+        return processOperations.exec(closure);
+    }
+
+    LoggingManager getLogging() {
+        return loggingManager
+    }
+
+    public Logger getLogger() {
+        return LOGGER;
+    }
+
+    def String toString() {
+        return "script"
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandler.java
deleted file mode 100644
index 310c3e0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandler.java
+++ /dev/null
@@ -1,225 +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.groovy.scripts;
-
-import groovy.lang.GroovyClassLoader;
-import groovy.lang.GroovyCodeSource;
-import groovy.lang.Script;
-import groovyjarjarasm.asm.ClassWriter;
-import org.apache.commons.lang.StringUtils;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.stmt.ReturnStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.control.*;
-import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
-import org.codehaus.groovy.syntax.SyntaxException;
-import org.gradle.api.GradleException;
-import org.gradle.api.ScriptCompilationException;
-import org.gradle.util.Clock;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.UncheckedException;
-import org.gradle.util.WrapUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.net.URLClassLoader;
-import java.security.CodeSource;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultScriptCompilationHandler implements ScriptCompilationHandler {
-    private Logger logger = LoggerFactory.getLogger(DefaultScriptCompilationHandler.class);
-    private static final String EMPTY_SCRIPT_MARKER_FILE_NAME = "emptyScript.txt";
-
-    public void compileToDir(ScriptSource source, ClassLoader classLoader, File classesDir,
-                             Transformer transformer, Class<? extends Script> scriptBaseClass) {
-        Clock clock = new Clock();
-        GFileUtils.deleteDirectory(classesDir);
-        classesDir.mkdirs();
-        CompilerConfiguration configuration = createBaseCompilerConfiguration(scriptBaseClass);
-        configuration.setTargetDirectory(classesDir);
-        try {
-            compileScript(source, classLoader, configuration, classesDir, transformer);
-        } catch (GradleException e) {
-            GFileUtils.deleteDirectory(classesDir);
-            throw e;
-        }
-
-        logger.debug("Timing: Writing script to cache at {} took: {}", classesDir.getAbsolutePath(),
-                clock.getTime());
-    }
-
-    private void compileScript(final ScriptSource source, ClassLoader classLoader, CompilerConfiguration configuration,
-                               File classesDir, final Transformer transformer) {
-        logger.info("Compiling {} using {}.", source.getDisplayName(), transformer != null ? transformer.getClass().getSimpleName() : "no transformer");
-
-        final EmptyScriptDetector emptyScriptDetector = new EmptyScriptDetector();
-        final PackageStatementDetector packageDetector = new PackageStatementDetector();
-        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader, configuration, false) {
-            @Override
-            protected CompilationUnit createCompilationUnit(CompilerConfiguration compilerConfiguration,
-                                                            CodeSource codeSource) {
-                CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration, codeSource, this) {
-                    // This creepy bit of code is here to put the full source path of the script into the debug info for
-                    // the class.  This makes it possible for a debugger to find the source file for the class.  By default
-                    // Groovy will only put the filename into the class, but that does not help a debugger for Gradle
-                    // because it does not know where Gradle scripts might live.
-                    @Override
-                    protected groovyjarjarasm.asm.ClassVisitor createClassVisitor() {
-                        return new ClassWriter(ClassWriter.COMPUTE_MAXS) {
-                            // ignore the sourcePath that is given by Groovy (this is only the filename) and instead
-                            // insert the full path if our script source has a source file
-                            @Override
-                            public void visitSource(String sourcePath, String debugInfo) {
-                                super.visitSource(source.getFileName(), debugInfo);
-                            }
-                        };
-                    }
-                };
-
-                if (transformer != null) {
-                    transformer.register(compilationUnit);
-                }
-
-                compilationUnit.addPhaseOperation(packageDetector, Phases.CANONICALIZATION);
-                compilationUnit.addPhaseOperation(emptyScriptDetector, Phases.CANONICALIZATION);
-                return compilationUnit;
-            }
-        };
-        String scriptText = source.getResource().getText();
-        String scriptName = source.getClassName();
-        GroovyCodeSource codeSource = new GroovyCodeSource(scriptText == null ? "" : scriptText, scriptName, "/groovy/script");
-        try {
-            groovyClassLoader.parseClass(codeSource, false);
-        } catch (MultipleCompilationErrorsException e) {
-            wrapCompilationFailure(source, e);
-        } catch (CompilationFailedException e) {
-            throw new GradleException(String.format("Could not compile %s.", source.getDisplayName()), e);
-        }
-
-        if (packageDetector.hasPackageStatement) {
-            throw new UnsupportedOperationException(String.format("%s should not contain a package statement.",
-                    StringUtils.capitalize(source.getDisplayName())));
-        }
-        if (emptyScriptDetector.isEmptyScript()) {
-            GFileUtils.touch(new File(classesDir, EMPTY_SCRIPT_MARKER_FILE_NAME));
-        }
-    }
-
-    private void wrapCompilationFailure(ScriptSource source, MultipleCompilationErrorsException e) {
-        // Fix the source file name displayed in the error messages
-        for (Object message : e.getErrorCollector().getErrors()) {
-            if (message instanceof SyntaxErrorMessage) {
-                try {
-                    SyntaxErrorMessage syntaxErrorMessage = (SyntaxErrorMessage) message;
-                    Field sourceField = SyntaxErrorMessage.class.getDeclaredField("source");
-                    sourceField.setAccessible(true);
-                    SourceUnit sourceUnit = (SourceUnit) sourceField.get(syntaxErrorMessage);
-                    Field nameField = SourceUnit.class.getDeclaredField("name");
-                    nameField.setAccessible(true);
-                    nameField.set(sourceUnit, source.getDisplayName());
-                } catch (Exception failure) {
-                    throw UncheckedException.asUncheckedException(failure);
-                }
-            }
-        }
-
-        SyntaxException syntaxError = e.getErrorCollector().getSyntaxError(0);
-        Integer lineNumber = syntaxError == null ? null : syntaxError.getLine();
-        throw new ScriptCompilationException(String.format("Could not compile %s.", source.getDisplayName()), e, source,
-                lineNumber);
-    }
-
-    private CompilerConfiguration createBaseCompilerConfiguration(Class<? extends Script> scriptBaseClass) {
-        CompilerConfiguration configuration = new CompilerConfiguration();
-        configuration.setScriptBaseClass(scriptBaseClass.getName());
-        return configuration;
-    }
-
-    public <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir,
-                                              Class<T> scriptBaseClass) {
-        if (new File(scriptCacheDir, EMPTY_SCRIPT_MARKER_FILE_NAME).isFile()) {
-            return new AsmBackedEmptyScriptGenerator().generate(scriptBaseClass);
-        }
-        
-        try {
-            URLClassLoader urlClassLoader = new URLClassLoader(WrapUtil.toArray(scriptCacheDir.toURI().toURL()),
-                    classLoader);
-            return urlClassLoader.loadClass(source.getClassName()).asSubclass(scriptBaseClass);
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not load compiled classes for %s from cache.\n"
-                    + "*****\n"
-                    + "Sometimes this error occurs when the cache was tinkered with.\n"
-                    + "You may try to resolve it by deleting this folder:\n"
-                    + "%s\n"
-                    + "*****\n",
-                    source.getDisplayName(), scriptCacheDir.getAbsolutePath()), e);
-        }
-    }
-
-    private static class PackageStatementDetector extends CompilationUnit.SourceUnitOperation {
-        private boolean hasPackageStatement;
-
-        @Override
-        public void call(SourceUnit source) throws CompilationFailedException {
-            hasPackageStatement = source.getAST().getPackageName() != null;
-        }
-    }
-
-    private static class EmptyScriptDetector extends CompilationUnit.SourceUnitOperation {
-        private boolean emptyScript;
-
-        @Override
-        public void call(SourceUnit source) throws CompilationFailedException {
-            emptyScript = isEmpty(source);
-        }
-
-        private boolean isEmpty(SourceUnit source) {
-            if (!source.getAST().getMethods().isEmpty()) {
-                return false;
-            }
-            List<Statement> statements = source.getAST().getStatementBlock().getStatements();
-            if (statements.size() > 1) {
-                return false;
-            }
-            if (statements.isEmpty()) {
-                return true;
-            }
-
-            Statement statement = statements.get(0);
-            if (statement instanceof ReturnStatement) {
-                ReturnStatement returnStatement = (ReturnStatement) statement;
-                if (returnStatement.getExpression() instanceof ConstantExpression) {
-                    ConstantExpression constantExpression = (ConstantExpression) returnStatement.getExpression();
-                    if (constantExpression.getValue() == null) {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        public boolean isEmptyScript() {
-            return emptyScript;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
index 4409b20..c9cae76 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
@@ -15,27 +15,20 @@
  */
 package org.gradle.groovy.scripts;
 
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.PersistentCache;
-import org.gradle.util.HashUtil;
-import org.gradle.util.ReflectionUtil;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
+import org.gradle.api.internal.DirectInstantiator;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.groovy.scripts.internal.ScriptClassCompiler;
+import org.gradle.groovy.scripts.internal.ScriptRunnerFactory;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultScriptCompilerFactory implements ScriptCompilerFactory {
-    private final ScriptCompilationHandler scriptCompilationHandler;
-    private final CacheRepository cacheRepository;
     private final ScriptRunnerFactory scriptRunnerFactory;
+    private final ScriptClassCompiler scriptClassCompiler;
 
-    public DefaultScriptCompilerFactory(ScriptCompilationHandler scriptCompilationHandler,
-                                        ScriptRunnerFactory scriptRunnerFactory, CacheRepository cacheRepository) {
-        this.scriptCompilationHandler = scriptCompilationHandler;
-        this.cacheRepository = cacheRepository;
+    public DefaultScriptCompilerFactory(ScriptClassCompiler scriptClassCompiler, ScriptRunnerFactory scriptRunnerFactory) {
+        this.scriptClassCompiler = scriptClassCompiler;
         this.scriptRunnerFactory = scriptRunnerFactory;
     }
 
@@ -47,6 +40,7 @@ public class DefaultScriptCompilerFactory implements ScriptCompilerFactory {
         private final ScriptSource source;
         private ClassLoader classloader;
         private Transformer transformer;
+        private final Instantiator instantiator = new DirectInstantiator();
 
         public ScriptCompilerImpl(ScriptSource source) {
             this.source = new CachingScriptSource(source);
@@ -63,36 +57,11 @@ public class DefaultScriptCompilerFactory implements ScriptCompilerFactory {
         }
 
         public <T extends Script> ScriptRunner<T> compile(Class<T> scriptType) {
-            ClassLoader classloader = this.classloader != null ? this.classloader
-                    : Thread.currentThread().getContextClassLoader();
-
-            T script = loadViaCache(classloader, scriptType);
+            Class<? extends T> scriptClass = scriptClassCompiler.compile(source, classloader, transformer, scriptType);
+            T script = instantiator.newInstance(scriptClass);
             script.setScriptSource(source);
             script.setContextClassloader(classloader);
             return scriptRunnerFactory.create(script);
         }
-
-        private <T extends Script> T loadViaCache(ClassLoader classLoader, Class<T> scriptBaseClass) {
-            Map<String, Object> properties = new HashMap<String, Object>();
-            properties.put("source.filename", source.getFileName());
-            properties.put("source.hash", HashUtil.createHash(source.getResource().getText()));
-
-            PersistentCache cache = cacheRepository.cache(String.format("scripts/%s", source.getClassName())).withProperties(properties).open();
-            File classesDir;
-            if (transformer != null) {
-                String subdirName = String.format("%s_%s", transformer.getId(), scriptBaseClass.getSimpleName());
-                classesDir = new File(cache.getBaseDir(), subdirName);
-            } else {
-                classesDir = new File(cache.getBaseDir(), scriptBaseClass.getSimpleName());
-            }
-
-            if (!cache.isValid() || !classesDir.exists()) {
-                scriptCompilationHandler.compileToDir(source, classLoader, classesDir, transformer, scriptBaseClass);
-                cache.markValid();
-            }
-            Class<? extends T> scriptClass = scriptCompilationHandler.loadFromDir(source, classLoader, classesDir,
-                    scriptBaseClass);
-            return scriptBaseClass.cast(ReflectionUtil.newInstance(scriptClass, new Object[0]));
-        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactory.java
deleted file mode 100644
index 97857c3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactory.java
+++ /dev/null
@@ -1,61 +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.groovy.scripts;
-
-import org.gradle.api.GradleScriptException;
-
-public class DefaultScriptRunnerFactory implements ScriptRunnerFactory {
-    private final ScriptExecutionListener listener;
-
-    public DefaultScriptRunnerFactory(ScriptExecutionListener listener) {
-        this.listener = listener;
-    }
-
-    public <T extends Script> ScriptRunner<T> create(T script) {
-        return new ScriptRunnerImpl<T>(script);
-    }
-
-    private class ScriptRunnerImpl<T extends Script> implements ScriptRunner<T> {
-        private final T script;
-
-        public ScriptRunnerImpl(T script) {
-            this.script = script;
-        }
-
-        public T getScript() {
-            return script;
-        }
-
-        public void run() throws GradleScriptException {
-            ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
-            listener.beforeScript(script);
-            GradleScriptException failure = null;
-            Thread.currentThread().setContextClassLoader(script.getContextClassloader());
-            script.getStandardOutputCapture().start();
-            try {
-                script.run();
-            } catch (Throwable e) {
-                failure = new GradleScriptException(String.format("A problem occurred evaluating %s.", script), e);
-            }
-            script.getStandardOutputCapture().stop();
-            Thread.currentThread().setContextClassLoader(originalLoader);
-            listener.afterScript(script, failure);
-            if (failure != null) {
-                throw failure;
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DelegatingScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DelegatingScriptSource.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/Script.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/Script.java
index 4d62766..4ea0fec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/Script.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/Script.java
@@ -16,7 +16,7 @@
 
 package org.gradle.groovy.scripts;
 
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.StandardOutputCapture;
 
 /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationException.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationException.java
new file mode 100755
index 0000000..cc7d8be
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.groovy.scripts;
+
+import org.gradle.api.GradleScriptException;
+
+/**
+ * A {@code ScriptCompilationException} is thrown when a script cannot be compiled.
+ */
+public class ScriptCompilationException extends GradleScriptException {
+    private final ScriptSource scriptSource;
+    private final Integer lineNumber;
+
+    public ScriptCompilationException(String message, Throwable cause, ScriptSource scriptSource, Integer lineNumber) {
+        super(message, cause);
+        this.scriptSource = scriptSource;
+        this.lineNumber = lineNumber;
+    }
+
+    public ScriptSource getScriptSource() {
+        return scriptSource;
+    }
+
+    public Integer getLineNumber() {
+        return lineNumber;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationHandler.java
deleted file mode 100644
index 08434d6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationHandler.java
+++ /dev/null
@@ -1,31 +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.groovy.scripts;
-
-import groovy.lang.Script;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public interface ScriptCompilationHandler {
-    void compileToDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir, Transformer transformer,
-                      Class<? extends Script> scriptBaseClass);
-
-    <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir,
-                                       Class<T> scriptBaseClass);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java
index 0ecb560..a49297e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java
@@ -15,8 +15,6 @@
  */
 package org.gradle.groovy.scripts;
 
-import org.gradle.api.ScriptCompilationException;
-
 /**
  * Compiles a script into a {@code Script} object.
  */
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptExecutionListener.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptExecutionListener.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptMetaData.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptMetaData.java
deleted file mode 100644
index 9066f0d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptMetaData.java
+++ /dev/null
@@ -1,25 +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.groovy.scripts;
-
-import groovy.lang.Script;
-
-/**
- * @author Hans Dockter
- */
-public interface ScriptMetaData {
-    void applyMetaData(Script script, Object delegate);
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunnerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunnerFactory.java
deleted file mode 100644
index af93917..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunnerFactory.java
+++ /dev/null
@@ -1,20 +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.groovy.scripts;
-
-public interface ScriptRunnerFactory {
-    <T extends Script> ScriptRunner<T> create(T script);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java
index 30890a1..a975cf4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java
@@ -17,10 +17,12 @@ package org.gradle.groovy.scripts;
 
 import org.gradle.api.internal.resource.Resource;
 
+import java.io.Serializable;
+
 /**
  * The source for the text of a script, with some meta-info about the script.
  */
-public interface ScriptSource {
+public interface ScriptSource extends Serializable {
     /**
      * Returns the name to use for the compiled class for this script. Never returns null.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
index a471833..a4c68fc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
@@ -17,7 +17,7 @@ package org.gradle.groovy.scripts;
 
 import org.gradle.api.internal.resource.Resource;
 import org.gradle.api.internal.resource.StringResource;
-import org.gradle.util.HashUtil;
+import org.gradle.util.hash.HashUtil;
 
 public class StringScriptSource implements ScriptSource {
     private final Resource resource;
@@ -27,7 +27,7 @@ public class StringScriptSource implements ScriptSource {
     }
 
     public String getClassName() {
-        return "script_" + HashUtil.createHash(resource.getText());
+        return "script_" + HashUtil.createCompactMD5(resource.getText());
     }
 
     public Resource getResource() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
index d3d92f1..5dba9b1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
@@ -18,7 +18,7 @@ package org.gradle.groovy.scripts;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.internal.resource.Resource;
 import org.gradle.api.internal.resource.UriResource;
-import org.gradle.util.HashUtil;
+import org.gradle.util.hash.HashUtil;
 
 import java.io.File;
 import java.net.URI;
@@ -45,7 +45,7 @@ public class UriScriptSource implements ScriptSource {
     public String getClassName() {
         if (className == null) {
             URI sourceUri = resource.getURI();
-            String name = StringUtils.substringBeforeLast(StringUtils.substringAfterLast(sourceUri.getPath(), "/"), ".");
+            String name = StringUtils.substringBeforeLast(StringUtils.substringAfterLast(sourceUri.toString(), "/"), ".");
             StringBuilder className = new StringBuilder(name.length());
             for (int i = 0; i < name.length(); i++) {
                 char ch = name.charAt(i);
@@ -61,7 +61,7 @@ public class UriScriptSource implements ScriptSource {
             className.setLength(Math.min(className.length(), 30));
             className.append('_');
             String path = sourceUri.toString();
-            className.append(HashUtil.createHash(path));
+            className.append(HashUtil.createCompactMD5(path));
 
             this.className = className.toString();
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java
new file mode 100644
index 0000000..b6c2fbb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGenerator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.groovy.scripts.internal;
+
+import groovy.lang.Script;
+import org.gradle.util.ReflectionUtil;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AsmBackedEmptyScriptGenerator implements EmptyScriptGenerator {
+    private static final Map<Class<?>, Class<?>> CACHED_CLASSES = new HashMap<Class<?>, Class<?>>();
+
+    public <T extends Script> Class<? extends T> generate(Class<T> type) {
+        Class<?> subclass = CACHED_CLASSES.get(type);
+        if (subclass == null) {
+            subclass = generateEmptyScriptClass(type);
+            CACHED_CLASSES.put(type, subclass);
+        }
+        return subclass.asSubclass(type);
+    }
+
+    private <T extends Script> Class<? extends T> generateEmptyScriptClass(Class<T> type) {
+        ClassWriter visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+        String typeName = type.getName() + "_Decorated";
+        Type generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";");
+        Type superclassType = Type.getType(type);
+        visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
+                superclassType.getInternalName(), new String[0]);
+
+        // Constructor
+
+        String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0]);
+        MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", constructorDescriptor, null,
+                new String[0]);
+        methodVisitor.visitCode();
+
+        // super()
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
+                constructorDescriptor);
+
+        methodVisitor.visitInsn(Opcodes.RETURN);
+        methodVisitor.visitMaxs(0, 0);
+        methodVisitor.visitEnd();
+
+        // run() method
+
+        String runDesciptor = Type.getMethodDescriptor(Type.getType(Object.class), new Type[0]);
+        methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "run", runDesciptor, null, new String[0]);
+        methodVisitor.visitCode();
+
+        // return null
+        methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+
+        methodVisitor.visitInsn(Opcodes.ARETURN);
+        methodVisitor.visitMaxs(0, 0);
+        methodVisitor.visitEnd();
+
+        visitor.visitEnd();
+
+        byte[] bytecode = visitor.toByteArray();
+        return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
+                typeName, bytecode, 0, bytecode.length
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/CachingScriptClassCompiler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/CachingScriptClassCompiler.java
new file mode 100644
index 0000000..871be5e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/CachingScriptClassCompiler.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.groovy.scripts.internal;
+
+import groovy.lang.Script;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+
+import java.util.*;
+
+public class CachingScriptClassCompiler implements ScriptClassCompiler {
+    private final Map<Collection<Object>, Class<?>> cachedClasses = new HashMap<Collection<Object>, Class<?>>();
+    private final ScriptClassCompiler scriptClassCompiler;
+
+    public CachingScriptClassCompiler(ScriptClassCompiler scriptClassCompiler) {
+        this.scriptClassCompiler = scriptClassCompiler;
+    }
+
+    public <T extends Script> Class<? extends T> compile(ScriptSource source, ClassLoader classLoader, Transformer transformer, Class<T> scriptBaseClass) {
+        List<Object> key = Arrays.asList(source.getClassName(), classLoader, transformer.getId(), scriptBaseClass.getName());
+        Class<?> c = cachedClasses.get(key);
+        if (c == null) {
+            c = scriptClassCompiler.compile(source, classLoader, transformer, scriptBaseClass);
+            cachedClasses.put(key, c);
+        }
+        return c.asSubclass(scriptBaseClass);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
new file mode 100644
index 0000000..aaa5e91
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
@@ -0,0 +1,226 @@
+/*
+ * 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.groovy.scripts.internal;
+
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.GroovyCodeSource;
+import groovy.lang.Script;
+import groovyjarjarasm.asm.ClassWriter;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.*;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.gradle.api.GradleException;
+import org.gradle.groovy.scripts.ScriptCompilationException;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.Clock;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.WrapUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultScriptCompilationHandler implements ScriptCompilationHandler {
+    private Logger logger = LoggerFactory.getLogger(DefaultScriptCompilationHandler.class);
+    private static final String EMPTY_SCRIPT_MARKER_FILE_NAME = "emptyScript.txt";
+    private final EmptyScriptGenerator emptyScriptGenerator;
+
+    public DefaultScriptCompilationHandler(EmptyScriptGenerator emptyScriptGenerator) {
+        this.emptyScriptGenerator = emptyScriptGenerator;
+    }
+
+    public void compileToDir(ScriptSource source, ClassLoader classLoader, File classesDir,
+                             Transformer transformer, Class<? extends Script> scriptBaseClass) {
+        Clock clock = new Clock();
+        GFileUtils.deleteDirectory(classesDir);
+        classesDir.mkdirs();
+        CompilerConfiguration configuration = createBaseCompilerConfiguration(scriptBaseClass);
+        configuration.setTargetDirectory(classesDir);
+        try {
+            compileScript(source, classLoader, configuration, classesDir, transformer);
+        } catch (GradleException e) {
+            GFileUtils.deleteDirectory(classesDir);
+            throw e;
+        }
+
+        logger.debug("Timing: Writing script to cache at {} took: {}", classesDir.getAbsolutePath(),
+                clock.getTime());
+    }
+
+    private void compileScript(final ScriptSource source, ClassLoader classLoader, CompilerConfiguration configuration,
+                               File classesDir, final Transformer transformer) {
+        logger.info("Compiling {} using {}.", source.getDisplayName(), transformer != null ? transformer.getClass().getSimpleName() : "no transformer");
+
+        final EmptyScriptDetector emptyScriptDetector = new EmptyScriptDetector();
+        final PackageStatementDetector packageDetector = new PackageStatementDetector();
+        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader, configuration, false) {
+            @Override
+            protected CompilationUnit createCompilationUnit(CompilerConfiguration compilerConfiguration,
+                                                            CodeSource codeSource) {
+                CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration, codeSource, this) {
+                    // This creepy bit of code is here to put the full source path of the script into the debug info for
+                    // the class.  This makes it possible for a debugger to find the source file for the class.  By default
+                    // Groovy will only put the filename into the class, but that does not help a debugger for Gradle
+                    // because it does not know where Gradle scripts might live.
+                    @Override
+                    protected groovyjarjarasm.asm.ClassVisitor createClassVisitor() {
+                        return new ClassWriter(ClassWriter.COMPUTE_MAXS) {
+                            // ignore the sourcePath that is given by Groovy (this is only the filename) and instead
+                            // insert the full path if our script source has a source file
+                            @Override
+                            public void visitSource(String sourcePath, String debugInfo) {
+                                super.visitSource(source.getFileName(), debugInfo);
+                            }
+                        };
+                    }
+                };
+
+                if (transformer != null) {
+                    transformer.register(compilationUnit);
+                }
+
+                compilationUnit.addPhaseOperation(packageDetector, Phases.CANONICALIZATION);
+                compilationUnit.addPhaseOperation(emptyScriptDetector, Phases.CANONICALIZATION);
+                return compilationUnit;
+            }
+        };
+        String scriptText = source.getResource().getText();
+        String scriptName = source.getClassName();
+        GroovyCodeSource codeSource = new GroovyCodeSource(scriptText == null ? "" : scriptText, scriptName, "/groovy/script");
+        try {
+            groovyClassLoader.parseClass(codeSource, false);
+        } catch (MultipleCompilationErrorsException e) {
+            wrapCompilationFailure(source, e);
+        } catch (CompilationFailedException e) {
+            throw new GradleException(String.format("Could not compile %s.", source.getDisplayName()), e);
+        }
+
+        if (packageDetector.hasPackageStatement) {
+            throw new UnsupportedOperationException(String.format("%s should not contain a package statement.",
+                    StringUtils.capitalize(source.getDisplayName())));
+        }
+        if (emptyScriptDetector.isEmptyScript()) {
+            GFileUtils.touch(new File(classesDir, EMPTY_SCRIPT_MARKER_FILE_NAME));
+        }
+    }
+
+    private void wrapCompilationFailure(ScriptSource source, MultipleCompilationErrorsException e) {
+        // Fix the source file name displayed in the error messages
+        for (Object message : e.getErrorCollector().getErrors()) {
+            if (message instanceof SyntaxErrorMessage) {
+                try {
+                    SyntaxErrorMessage syntaxErrorMessage = (SyntaxErrorMessage) message;
+                    Field sourceField = SyntaxErrorMessage.class.getDeclaredField("source");
+                    sourceField.setAccessible(true);
+                    SourceUnit sourceUnit = (SourceUnit) sourceField.get(syntaxErrorMessage);
+                    Field nameField = SourceUnit.class.getDeclaredField("name");
+                    nameField.setAccessible(true);
+                    nameField.set(sourceUnit, source.getDisplayName());
+                } catch (Exception failure) {
+                    throw UncheckedException.throwAsUncheckedException(failure);
+                }
+            }
+        }
+
+        SyntaxException syntaxError = e.getErrorCollector().getSyntaxError(0);
+        Integer lineNumber = syntaxError == null ? null : syntaxError.getLine();
+        throw new ScriptCompilationException(String.format("Could not compile %s.", source.getDisplayName()), e, source,
+                lineNumber);
+    }
+
+    private CompilerConfiguration createBaseCompilerConfiguration(Class<? extends Script> scriptBaseClass) {
+        CompilerConfiguration configuration = new CompilerConfiguration();
+        configuration.setScriptBaseClass(scriptBaseClass.getName());
+        return configuration;
+    }
+
+    public <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir,
+                                              Class<T> scriptBaseClass) {
+        if (new File(scriptCacheDir, EMPTY_SCRIPT_MARKER_FILE_NAME).isFile()) {
+            return emptyScriptGenerator.generate(scriptBaseClass);
+        }
+        
+        try {
+            URLClassLoader urlClassLoader = new URLClassLoader(WrapUtil.toArray(scriptCacheDir.toURI().toURL()),
+                    classLoader);
+            return urlClassLoader.loadClass(source.getClassName()).asSubclass(scriptBaseClass);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not load compiled classes for %s from cache.", source.getDisplayName()), e);
+        }
+    }
+
+    private static class PackageStatementDetector extends CompilationUnit.SourceUnitOperation {
+        private boolean hasPackageStatement;
+
+        @Override
+        public void call(SourceUnit source) throws CompilationFailedException {
+            hasPackageStatement = source.getAST().getPackageName() != null;
+        }
+    }
+
+    private static class EmptyScriptDetector extends CompilationUnit.SourceUnitOperation {
+        private boolean emptyScript;
+
+        @Override
+        public void call(SourceUnit source) throws CompilationFailedException {
+            emptyScript = isEmpty(source);
+        }
+
+        private boolean isEmpty(SourceUnit source) {
+            if (!source.getAST().getMethods().isEmpty()) {
+                return false;
+            }
+            List<Statement> statements = source.getAST().getStatementBlock().getStatements();
+            if (statements.size() > 1) {
+                return false;
+            }
+            if (statements.isEmpty()) {
+                return true;
+            }
+
+            Statement statement = statements.get(0);
+            if (statement instanceof ReturnStatement) {
+                ReturnStatement returnStatement = (ReturnStatement) statement;
+                if (returnStatement.getExpression() instanceof ConstantExpression) {
+                    ConstantExpression constantExpression = (ConstantExpression) returnStatement.getExpression();
+                    if (constantExpression.getValue() == null) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        public boolean isEmptyScript() {
+            return emptyScript;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptRunnerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptRunnerFactory.java
new file mode 100644
index 0000000..b74fb42
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptRunnerFactory.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.groovy.scripts.internal;
+
+import org.gradle.api.GradleScriptException;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.ScriptRunner;
+
+public class DefaultScriptRunnerFactory implements ScriptRunnerFactory {
+    private final ScriptExecutionListener listener;
+
+    public DefaultScriptRunnerFactory(ScriptExecutionListener listener) {
+        this.listener = listener;
+    }
+
+    public <T extends Script> ScriptRunner<T> create(T script) {
+        return new ScriptRunnerImpl<T>(script);
+    }
+
+    private class ScriptRunnerImpl<T extends Script> implements ScriptRunner<T> {
+        private final T script;
+
+        public ScriptRunnerImpl(T script) {
+            this.script = script;
+        }
+
+        public T getScript() {
+            return script;
+        }
+
+        public void run() throws GradleScriptException {
+            ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
+            listener.beforeScript(script);
+            GradleScriptException failure = null;
+            Thread.currentThread().setContextClassLoader(script.getContextClassloader());
+            script.getStandardOutputCapture().start();
+            try {
+                script.run();
+            } catch (Throwable e) {
+                failure = new GradleScriptException(String.format("A problem occurred evaluating %s.", script), e);
+            }
+            script.getStandardOutputCapture().stop();
+            Thread.currentThread().setContextClassLoader(originalLoader);
+            listener.afterScript(script, failure);
+            if (failure != null) {
+                throw failure;
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/EmptyScriptGenerator.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/EmptyScriptGenerator.java
new file mode 100644
index 0000000..eee3198
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/EmptyScriptGenerator.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.groovy.scripts.internal;
+
+public interface EmptyScriptGenerator {
+    <T extends groovy.lang.Script> Class<? extends T> generate(Class<T> type);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java
new file mode 100644
index 0000000..cfe8340
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompiler.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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.groovy.scripts.internal;
+
+import groovy.lang.Script;
+import org.gradle.api.Action;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.PersistentCache;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+import org.gradle.util.hash.HashUtil;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link ScriptClassCompiler} which compiles scripts to a cache directory, and loads them from there.
+ */
+public class FileCacheBackedScriptClassCompiler implements ScriptClassCompiler {
+    private final ScriptCompilationHandler scriptCompilationHandler;
+    private final CacheRepository cacheRepository;
+    private final CacheValidator validator;
+
+    public FileCacheBackedScriptClassCompiler(CacheRepository cacheRepository, CacheValidator validator, ScriptCompilationHandler scriptCompilationHandler) {
+        this.cacheRepository = cacheRepository;
+        this.validator = validator;
+        this.scriptCompilationHandler = scriptCompilationHandler;
+    }
+
+    public <T extends Script> Class<? extends T> compile(ScriptSource source, ClassLoader classLoader, Transformer transformer, Class<T> scriptBaseClass) {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.put("source.filename", source.getFileName());
+        properties.put("source.hash", HashUtil.createCompactMD5(source.getResource().getText()));
+
+        String cacheName = String.format("scripts/%s/%s/%s", source.getClassName(), scriptBaseClass.getSimpleName(), transformer.getId());
+        PersistentCache cache = cacheRepository.cache(cacheName)
+                .withProperties(properties)
+                .withValidator(validator)
+                .withDisplayName(String.format("%s class cache for %s", transformer.getId(), source.getDisplayName()))
+                .withInitializer(new CacheInitializer(source, classLoader, transformer, scriptBaseClass)).open();
+
+        File classesDir = classesDir(cache);
+        return scriptCompilationHandler.loadFromDir(source, classLoader, classesDir, scriptBaseClass);
+    }
+
+    private File classesDir(PersistentCache cache) {
+        return new File(cache.getBaseDir(), "classes");
+    }
+
+    private class CacheInitializer implements Action<PersistentCache> {
+        private final Class<? extends Script> scriptBaseClass;
+        private final ClassLoader classLoader;
+        private final Transformer transformer;
+        private final ScriptSource source;
+
+        private CacheInitializer(ScriptSource source, ClassLoader classLoader, Transformer transformer, Class<? extends Script> scriptBaseClass) {
+            this.source = source;
+            this.classLoader = classLoader;
+            this.transformer = transformer;
+            this.scriptBaseClass = scriptBaseClass;
+        }
+
+        public void execute(PersistentCache cache) {
+            File classesDir = classesDir(cache);
+            scriptCompilationHandler.compileToDir(source, classLoader, classesDir, transformer, scriptBaseClass);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptClassCompiler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptClassCompiler.java
new file mode 100644
index 0000000..de621d4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptClassCompiler.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.groovy.scripts.internal;
+
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+
+public interface ScriptClassCompiler {
+    <T extends groovy.lang.Script> Class<? extends T> compile(ScriptSource source, ClassLoader classLoader, Transformer transformer, Class<T> scriptBaseClass);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java
new file mode 100644
index 0000000..afd9584
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptCompilationHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.groovy.scripts.internal;
+
+import groovy.lang.Script;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ScriptCompilationHandler {
+    void compileToDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir, Transformer transformer,
+                      Class<? extends Script> scriptBaseClass);
+
+    <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir,
+                                       Class<T> scriptBaseClass);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptRunnerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptRunnerFactory.java
new file mode 100644
index 0000000..4fa2b2b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ScriptRunnerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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.groovy.scripts.internal;
+
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptRunner;
+
+public interface ScriptRunnerFactory {
+    <T extends Script> ScriptRunner<T> create(T script);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ShortCircuitEmptyScriptCompiler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ShortCircuitEmptyScriptCompiler.java
new file mode 100644
index 0000000..483ece2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ShortCircuitEmptyScriptCompiler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.groovy.scripts.internal;
+
+import groovy.lang.Script;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+
+public class ShortCircuitEmptyScriptCompiler implements ScriptClassCompiler {
+    private final ScriptClassCompiler compiler;
+    private final EmptyScriptGenerator emptyScriptGenerator;
+
+    public ShortCircuitEmptyScriptCompiler(ScriptClassCompiler compiler, EmptyScriptGenerator emptyScriptGenerator) {
+        this.compiler = compiler;
+        this.emptyScriptGenerator = emptyScriptGenerator;
+    }
+
+    public <T extends Script> Class<? extends T> compile(ScriptSource source, ClassLoader classLoader, Transformer transformer, Class<T> scriptBaseClass) {
+        if (source.getResource().getText().matches("\\s*")) {
+            return emptyScriptGenerator.generate(scriptBaseClass);
+        }
+        return compiler.compile(source, classLoader, transformer, scriptBaseClass);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java
deleted file mode 100644
index d1d9be3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractCommandLineConverter.java
+++ /dev/null
@@ -1,38 +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.CommandLineArgumentException;
-
-public abstract class AbstractCommandLineConverter<T> implements CommandLineConverter<T> {
-    public T convert(Iterable<String> args) throws CommandLineArgumentException {
-        CommandLineParser parser = new CommandLineParser();
-        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/core/src/main/groovy/org/gradle/initialization/AbstractSettingsFileSearchStrategyTemplate.java b/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractSettingsFileSearchStrategyTemplate.java
deleted file mode 100644
index f739081..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/AbstractSettingsFileSearchStrategyTemplate.java
+++ /dev/null
@@ -1,37 +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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.initialization.Settings;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractSettingsFileSearchStrategyTemplate implements ISettingsFileSearchStrategy {
-    public File find(StartParameter startParameter) {
-        File currentDirSettingsFile = new File(startParameter.getCurrentDir(), Settings.DEFAULT_SETTINGS_FILE);
-        if (currentDirSettingsFile.isFile()) {
-            return currentDirSettingsFile;
-        }
-        return findBeyondCurrentDir(startParameter);
-    }
-
-    protected abstract File findBeyondCurrentDir(StartParameter startParameter);
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
index e2fdef4..4948886 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BaseSettings.java
@@ -19,7 +19,8 @@ import org.gradle.StartParameter;
 import org.gradle.api.UnknownProjectException;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.initialization.Settings;
-import org.gradle.api.internal.DynamicObjectHelper;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.ExtensibleDynamicObject;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.IProjectRegistry;
@@ -45,7 +46,7 @@ public class BaseSettings implements SettingsInternal {
 
     private DefaultProjectDescriptor rootProjectDescriptor;
 
-    private DynamicObjectHelper dynamicObjectHelper;
+    private ExtensibleDynamicObject dynamicObject;
 
     private GradleInternal gradle;
     private IProjectDescriptorRegistry projectDescriptorRegistry;
@@ -64,7 +65,7 @@ public class BaseSettings implements SettingsInternal {
         this.startParameter = startParameter;
         this.classloader = classloader;
         rootProjectDescriptor = createProjectDescriptor(null, settingsDir.getName(), settingsDir);
-        dynamicObjectHelper = new DynamicObjectHelper(this);
+        dynamicObject = new ExtensibleDynamicObject(this);
     }
 
     @Override
@@ -187,12 +188,12 @@ public class BaseSettings implements SettingsInternal {
         this.projectDescriptorRegistry = projectDescriptorRegistry;
     }
 
-    public Map<String, Object> getAdditionalProperties() {
-        return dynamicObjectHelper.getAdditionalProperties();
+    public void addDynamicProperties(Map<String, ?> properties) {
+        dynamicObject.addProperties(properties);
     }
 
-    protected DynamicObjectHelper getDynamicObjectHelper() {
-        return dynamicObjectHelper;
+    protected DynamicObject getDynamicObject() {
+        return dynamicObject;
     }
 
     public IProjectRegistry<DefaultProjectDescriptor> getProjectRegistry() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
index 10d4687..e291545 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
@@ -22,8 +22,9 @@ import org.gradle.api.internal.project.IProjectRegistry;
 import org.gradle.api.InvalidUserDataException;
 
 import java.io.File;
+import java.io.Serializable;
 
-public class BuildFileProjectSpec extends AbstractProjectSpec {
+public class BuildFileProjectSpec extends AbstractProjectSpec implements Serializable {
     private final File buildFile;
 
     public BuildFileProjectSpec(File buildFile) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java
index 5e45c69..08e3719 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLoader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2011 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.
@@ -13,94 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.gradle.initialization;
 
-import org.gradle.api.GradleException;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.IProjectFactory;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.util.Clock;
-import org.gradle.util.GUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * @author Hans Dockter
- */
-public class BuildLoader {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BuildLoader.class);
-
-    private final IProjectFactory projectFactory;
-
-    public BuildLoader(IProjectFactory projectFactory) {
-        this.projectFactory = projectFactory;
-    }
 
+public interface BuildLoader {
     /**
-     * Creates the {@link org.gradle.api.internal.GradleInternal} and {@link ProjectInternal} instances for the given root project,
+     * Creates the {@link org.gradle.api.internal.GradleInternal} and {@link org.gradle.api.internal.project.ProjectInternal} instances for the given root project,
      * ready for the projects to be evaluated.
      */
-    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle,
-                     Map<String, String> externalProjectProperties) {
-        LOGGER.debug("Loading Project objects");
-        Clock clock = new Clock();
-        createProjects(rootProjectDescriptor, gradle, externalProjectProperties);
-        attachDefaultProject(gradle);
-        LOGGER.debug("Timing: Loading projects took: " + clock.getTime());
-    }
-
-    private void attachDefaultProject(GradleInternal gradle) {
-        ProjectSpec selector = gradle.getStartParameter().getDefaultProjectSelector();
-        ProjectInternal defaultProject;
-        try {
-            defaultProject = selector.selectProject(gradle.getRootProject().getProjectRegistry());
-        } catch (InvalidUserDataException e) {
-            throw new GradleException(String.format("Could not select the default project for this build. %s",
-                    e.getMessage()), e);
-        }
-        gradle.setDefaultProject(defaultProject);
-    }
-
-    private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle,
-                                Map<String, String> externalProjectProperties) {
-        ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle);
-        gradle.setRootProject(rootProject);
-
-        addPropertiesToProject(externalProjectProperties, rootProject);
-        addProjects(rootProject, rootProjectDescriptor, gradle, externalProjectProperties);
-    }
-
-    private void addProjects(ProjectInternal parent, ProjectDescriptor parentProjectDescriptor, GradleInternal gradle,
-                             Map<String, String> externalProjectProperties) {
-        for (ProjectDescriptor childProjectDescriptor : parentProjectDescriptor.getChildren()) {
-            ProjectInternal childProject = projectFactory.createProject(childProjectDescriptor, parent, gradle);
-            addPropertiesToProject(externalProjectProperties, childProject);
-            addProjects(childProject, childProjectDescriptor, gradle, externalProjectProperties);
-        }
-    }
-
-    private void addPropertiesToProject(Map<String, String> externalProperties, ProjectInternal project) {
-        Properties projectProperties = new Properties();
-        File projectPropertiesFile = new File(project.getProjectDir(), Project.GRADLE_PROPERTIES);
-        LOGGER.debug("Looking for project properties from: {}", projectPropertiesFile);
-        if (projectPropertiesFile.isFile()) {
-            projectProperties = GUtil.loadProperties(projectPropertiesFile);
-            LOGGER.debug("Adding project properties (if not overwritten by user properties): {}",
-                    projectProperties.keySet());
-        } else {
-            LOGGER.debug("project property file does not exists. We continue!");
-        }
-        projectProperties.putAll(externalProperties);
-        for (Object key : projectProperties.keySet()) {
-            project.setProperty((String) key, projectProperties.get(key));
-        }
-    }
+    void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
index eb52f1d..0da1bcf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
@@ -16,21 +16,20 @@
 
 package org.gradle.initialization;
 
-import org.apache.commons.io.IOUtils;
-import org.gradle.*;
-import org.gradle.api.Project;
+import org.gradle.BuildAdapter;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.plugins.EmbeddableJavaProject;
 import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheBuilder;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentStateCache;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.util.WrapUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
@@ -43,14 +42,14 @@ public class BuildSourceBuilder {
     private static final Logger LOGGER = LoggerFactory.getLogger(BuildSourceBuilder.class);
 
     private final GradleLauncherFactory gradleLauncherFactory;
-    private final ClassLoaderFactory classLoaderFactory;
+    private final ClassLoaderRegistry classLoaderRegistry;
     private final CacheRepository cacheRepository;
 
     private static final String DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE = "defaultBuildSourceScript.txt";
 
-    public BuildSourceBuilder(GradleLauncherFactory gradleLauncherFactory, ClassLoaderFactory classLoaderFactory, CacheRepository cacheRepository) {
+    public BuildSourceBuilder(GradleLauncherFactory gradleLauncherFactory, ClassLoaderRegistry classLoaderRegistry, CacheRepository cacheRepository) {
         this.gradleLauncherFactory = gradleLauncherFactory;
-        this.classLoaderFactory = classLoaderFactory;
+        this.classLoaderRegistry = classLoaderRegistry;
         this.cacheRepository = cacheRepository;
     }
 
@@ -65,7 +64,7 @@ public class BuildSourceBuilder {
                 throw new UncheckedIOException(e);
             }
         }
-        return new URLClassLoader(urls, classLoaderFactory.getRootClassLoader());
+        return new URLClassLoader(urls, classLoaderRegistry.getRootClassLoader());
     }
 
     public Set<File> createBuildSourceClasspath(StartParameter startParameter) {
@@ -84,15 +83,9 @@ public class BuildSourceBuilder {
 
         // If we were not the most recent version of Gradle to build the buildSrc dir, then do a clean build
         // Otherwise, just to a regular build
-        PersistentStateCache<Boolean> stateCache = cacheRepository.cache("buildSrc").forObject(startParameter.getCurrentDir()).invalidateOnVersionChange().open().openStateCache();
+        PersistentStateCache<Boolean> stateCache = cacheRepository.stateCache(Boolean.class, "buildSrc").forObject(startParameter.getCurrentDir()).withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange).open();
         boolean rebuild = stateCache.get() == null;
 
-        if (!new File(startParameter.getCurrentDir(), Project.DEFAULT_BUILD_FILE).isFile()) {
-            LOGGER.debug("Gradle script file does not exist. Using default one.");
-            ScriptSource source = new StringScriptSource("default buildSrc build script", getDefaultScript());
-            startParameterArg.setBuildScriptSource(source);
-        }
-
         GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameterArg);
         BuildSrcBuildListener listener = new BuildSrcBuildListener(rebuild);
         gradleLauncher.addListener(listener);
@@ -108,12 +101,8 @@ public class BuildSourceBuilder {
         return buildSourceClasspath;
     }
 
-    static String getDefaultScript() {
-        try {
-            return IOUtils.toString(BuildSourceBuilder.class.getResourceAsStream(DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE));
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
+    static URL getDefaultScript() {
+        return BuildSourceBuilder.class.getResource(DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE);
     }
 
     private static class BuildSrcBuildListener extends BuildAdapter {
@@ -126,6 +115,11 @@ public class BuildSourceBuilder {
         }
 
         @Override
+        public void projectsLoaded(Gradle gradle) {
+            gradle.getRootProject().apply(WrapUtil.toMap("from", getDefaultScript()));
+        }
+
+        @Override
         public void projectsEvaluated(Gradle gradle) {
             projectInfo = gradle.getRootProject().getConvention().getPlugin(EmbeddableJavaProject.class);
             gradle.getStartParameter().setTaskNames(rebuild ? projectInfo.getRebuildTasks() : projectInfo.getBuildTasks());
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderFactory.java
deleted file mode 100644
index bd1cc43..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderFactory.java
+++ /dev/null
@@ -1,35 +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.initialization;
-
-import org.gradle.util.MultiParentClassLoader;
-
-public interface ClassLoaderFactory {
-    /**
-     * Returns the root class loader shared by all builds.
-     */
-    ClassLoader getRootClassLoader();
-
-    /**
-     * Returns the class loader for the coreImpl project.
-     */
-    ClassLoader getCoreImplClassLoader();
-
-    /**
-     * Creates the script class loader for a build.
-     */
-    MultiParentClassLoader createScriptClassLoader();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java
new file mode 100755
index 0000000..bf1ca5a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ClassLoaderRegistry.java
@@ -0,0 +1,40 @@
+/*
+ * 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.MultiParentClassLoader;
+
+public interface ClassLoaderRegistry {
+    /**
+     * Returns the root class loader shared by all builds.
+     */
+    ClassLoader getRootClassLoader();
+
+    /**
+     * Returns the class loader for the coreImpl project.
+     */
+    ClassLoader getCoreImplClassLoader();
+
+    /**
+     * Returns the class loader for the plugins.
+     */
+    ClassLoader getPluginsClassLoader();
+
+    /**
+     * Creates the script class loader for a build.
+     */
+    MultiParentClassLoader createScriptClassLoader();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java
deleted file mode 100644
index 15ea697..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineConverter.java
+++ /dev/null
@@ -1,33 +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.CommandLineArgumentException;
-
-/**
- * @author Hans Dockter
- */
-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/core/src/main/groovy/org/gradle/initialization/CommandLineOption.java b/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineOption.java
deleted file mode 100644
index d5325aa..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineOption.java
+++ /dev/null
@@ -1,72 +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.initialization;
-
-import org.gradle.util.GUtil;
-
-import java.util.List;
-import java.util.Set;
-
-public class CommandLineOption {
-    private final Set<String> options;
-    private Class<?> argumentType = Void.TYPE;
-    private String description;
-    private String subcommand;
-
-    public CommandLineOption(Iterable<String> options) {
-        this.options = GUtil.addSets(options);
-    }
-
-    public Set<String> getOptions() {
-        return options;
-    }
-
-    public CommandLineOption hasArgument() {
-        argumentType = String.class;
-        return this;
-    }
-
-    public CommandLineOption hasArguments() {
-        argumentType = List.class;
-        return this;
-    }
-
-    public String getSubcommand() {
-        return subcommand;
-    }
-
-    public CommandLineOption mapsToSubcommand(String command) {
-        this.subcommand = command;
-        return this;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public CommandLineOption hasDescription(String description) {
-        this.description = description;
-        return this;
-    }
-
-    public boolean getAllowsArguments() {
-        return argumentType != Void.TYPE;
-    }
-
-    public boolean getAllowsMultipleArguments() {
-        return argumentType == List.class;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineParser.java b/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineParser.java
deleted file mode 100644
index 62be97e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/CommandLineParser.java
+++ /dev/null
@@ -1,460 +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.initialization;
-
-import org.gradle.CommandLineArgumentException;
-import org.gradle.util.GUtil;
-
-import java.io.OutputStream;
-import java.util.*;
-
-/**
- * <p>A command-line parser which supports a command/subcommand style command-line interface. Supports the following
- * syntax:</p>
- * <pre>
- * <option>* (<subcommand> <subcommand-option>*)*
- * </pre>
- *
- * <ul> <li>Short options are a '-' followed by a single character. For example: {@code -a}.</li>
- *
- * <li>Long options are '--' followed by multiple characters. For example: {@code --long-option}.</li>
- *
- * <li>Options can take arguments. The argument follows the option. For example: {@code -a arg} or {@code --long
- * arg}.</li>
- *
- * <li>Arguments can be attached to the option using '='. For example: {@code -a=arg} or {@code --long=arg}.</li>
- *
- * <li>Arguments can be attached to short options. For example: {@code -aarg}.</li>
- *
- * <li>Short options can be combined. For example {@code -ab} is equivalent to {@code -a -b}.</li>
- *
- * <li>Anything else is treated as an extra argument. This includes a single {@code -} character.</li>
- *
- * <li>'--' indicates the end of the options. Anything following is not parsed and is treated as extra arguments.</li>
- *
- * <li>The parser is forgiving, and allows '--' to be used with short options and '-' to be used with long
- * options.</li>
- *
- * <li>The set of options must be known at parse time. Subcommands and their options do not need to be known at parse
- * time. Use {@link ParsedCommandLine#getExtraArguments()} to obtain the non-option command-line arguments.</li>
- *
- * </ul>
- */
-public class CommandLineParser {
-    private Map<String, CommandLineOption> optionsByString = new HashMap<String, CommandLineOption>();
-    private boolean allowMixedOptions;
-
-    /**
-     * Parses the given command-line.
-     *
-     * @param commandLine The command-line.
-     * @return The parsed command line.
-     * @throws org.gradle.CommandLineArgumentException
-     *          On parse failure.
-     */
-    public ParsedCommandLine parse(String[] commandLine) throws CommandLineArgumentException {
-        return parse(Arrays.asList(commandLine));
-    }
-
-    /**
-     * Parses the given command-line.
-     *
-     * @param commandLine The command-line.
-     * @return The parsed command line.
-     * @throws org.gradle.CommandLineArgumentException
-     *          On parse failure.
-     */
-    public ParsedCommandLine parse(Iterable<String> commandLine) {
-        ParsedCommandLine parsedCommandLine = new ParsedCommandLine(new HashSet<CommandLineOption>(optionsByString.values()));
-        ParserState parseState = new BeforeFirstSubCommand(parsedCommandLine);
-        for (String arg : commandLine) {
-            if (parseState.maybeStartOption(arg)) {
-                if (arg.equals("--")) {
-                    parseState = new AfterOptions(parsedCommandLine);
-                } else if (arg.matches("--[^=]+")) {
-                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2));
-                    parseState = parsedOption.onStartNextArg();
-                } else if (arg.matches("--[^=]+=.*")) {
-                    int endArg = arg.indexOf('=');
-                    OptionParserState parsedOption = parseState.onStartOption(arg, arg.substring(2, endArg));
-                    parseState = parsedOption.onArgument(arg.substring(endArg + 1));
-                } else if (arg.matches("-[^=]=.*")) {
-                    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)) {
-                        OptionParserState parsedOption = parseState.onStartOption(arg, option);
-                        parseState = parsedOption.onStartNextArg();
-                    } else {
-                        String option1 = arg.substring(1, 2);
-                        OptionParserState parsedOption = parseState.onStartOption("-" + option1, option1);
-                        if (parsedOption.getHasArgument()) {
-                            parseState = parsedOption.onArgument(arg.substring(2));
-                        } else {
-                            parseState = parsedOption.onComplete();
-                            for (int i = 2; i < arg.length(); i++) {
-                                String optionStr = arg.substring(i, i + 1);
-                                parsedOption = parseState.onStartOption("-" + optionStr, optionStr);
-                                parseState = parsedOption.onComplete();
-                            }
-                        }
-                    }
-                }
-            } else {
-                parseState = parseState.onNonOption(arg);
-            }
-        }
-
-        parseState.onCommandLineEnd();
-        return parsedCommandLine;
-    }
-
-    public CommandLineParser allowMixedSubcommandsAndOptions() {
-        allowMixedOptions = true;
-        return this;
-    }
-
-    /**
-     * Prints a usage message to the given stream.
-     *
-     * @param out The output stream to write to.
-     */
-    public void printUsage(OutputStream out) {
-        Formatter formatter = new Formatter(out);
-        Set<CommandLineOption> orderedOptions = new TreeSet<CommandLineOption>(new OptionComparator());
-        orderedOptions.addAll(optionsByString.values());
-        Map<String, String> lines = new LinkedHashMap<String, String>();
-        for (CommandLineOption option : orderedOptions) {
-            Set<String> orderedOptionStrings = new TreeSet<String>(new OptionStringComparator());
-            orderedOptionStrings.addAll(option.getOptions());
-            List<String> prefixedStrings = new ArrayList<String>();
-            for (String optionString : orderedOptionStrings) {
-                if (optionString.length() == 1) {
-                    prefixedStrings.add("-" + optionString);
-                } else {
-                    prefixedStrings.add("--" + optionString);
-                }
-            }
-            lines.put(GUtil.join(prefixedStrings, ", "), GUtil.elvis(option.getDescription(), ""));
-        }
-        int max = 0;
-        for (String optionStr : lines.keySet()) {
-            max = Math.max(max, optionStr.length());
-        }
-        for (Map.Entry<String, String> entry : lines.entrySet()) {
-            if (entry.getValue().length() == 0) {
-                formatter.format("%s%n", entry.getKey());
-            } else {
-                formatter.format("%-" + max + "s  %s%n", entry.getKey(), entry.getValue());
-            }
-        }
-        formatter.flush();
-    }
-
-    /**
-     * Defines a new option. By default, the option takes no arguments and has no description.
-     *
-     * @param options The options values.
-     * @return The option, which can be further configured.
-     */
-    public CommandLineOption option(String... options) {
-        for (String option : options) {
-            if (optionsByString.containsKey(option)) {
-                throw new IllegalArgumentException(String.format("Option '%s' is already defined.", option));
-            }
-            if (option.startsWith("-")) {
-                throw new IllegalArgumentException(String.format("Cannot add option '%s' as an option cannot start with '-'.", option));
-            }
-        }
-        CommandLineOption option = new CommandLineOption(Arrays.asList(options));
-        for (String optionStr : option.getOptions()) {
-            this.optionsByString.put(optionStr, option);
-        }
-        return option;
-    }
-
-    private static class OptionString {
-        private final String arg;
-        private final String option;
-
-        private OptionString(String arg, String option) {
-            this.arg = arg;
-            this.option = option;
-        }
-
-        public String getDisplayName() {
-            return arg.startsWith("--") ? "--" + option : "-" + option;
-        }
-
-        @Override
-        public String toString() {
-            return getDisplayName();
-        }
-    }
-
-    private static abstract class ParserState {
-        public abstract boolean maybeStartOption(String arg);
-
-        boolean isOption(String arg) {
-            return arg.matches("-.+");
-        }
-
-        public abstract OptionParserState onStartOption(String arg, String option);
-
-        public abstract ParserState onNonOption(String arg);
-
-        public void onCommandLineEnd() {
-        }
-    }
-
-    private abstract class OptionAwareParserState extends ParserState {
-        protected final ParsedCommandLine commandLine;
-
-        protected OptionAwareParserState(ParsedCommandLine commandLine) {
-            this.commandLine = commandLine;
-        }
-
-        @Override
-        public boolean maybeStartOption(String arg) {
-            return isOption(arg);
-        }
-
-        @Override
-        public ParserState onNonOption(String arg) {
-            commandLine.addExtraValue(arg);
-            return allowMixedOptions ? new AfterFirstSubCommand(commandLine) : new AfterOptions(commandLine);
-        }
-    }
-
-    private class BeforeFirstSubCommand extends OptionAwareParserState {
-        private BeforeFirstSubCommand(ParsedCommandLine commandLine) {
-            super(commandLine);
-        }
-
-        @Override
-        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));
-            }
-            return new KnownOptionParserState(optionString, commandLineOption, commandLine, this);
-        }
-    }
-
-    private class AfterFirstSubCommand extends OptionAwareParserState {
-        private AfterFirstSubCommand(ParsedCommandLine commandLine) {
-            super(commandLine);
-        }
-
-        @Override
-        public OptionParserState onStartOption(String arg, String option) {
-            CommandLineOption commandLineOption = optionsByString.get(option);
-            if (commandLineOption == null) {
-                return new UnknownOptionParserState(arg, commandLine, this);
-            }
-            return new KnownOptionParserState(new OptionString(arg, option), commandLineOption, commandLine, this);
-        }
-    }
-
-    private static class AfterOptions extends ParserState {
-        private final ParsedCommandLine commandLine;
-
-        private AfterOptions(ParsedCommandLine commandLine) {
-            this.commandLine = commandLine;
-        }
-
-        @Override
-        public boolean maybeStartOption(String arg) {
-            return false;
-        }
-
-        @Override
-        public OptionParserState onStartOption(String arg, String option) {
-            return new UnknownOptionParserState(arg, commandLine, this);
-        }
-
-        @Override
-        public ParserState onNonOption(String arg) {
-            commandLine.addExtraValue(arg);
-            return this;
-        }
-    }
-
-    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 void onCommandLineEnd() {
-            option.onComplete();
-        }
-    }
-
-    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 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
-        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 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 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> {
-        public int compare(CommandLineOption option1, CommandLineOption option2) {
-            String min1 = Collections.min(option1.getOptions(), new OptionStringComparator());
-            String min2 = Collections.min(option2.getOptions(), new OptionStringComparator());
-            return new CaseInsensitiveStringComparator().compare(min1, min2);
-        }
-    }
-
-    private static final class CaseInsensitiveStringComparator implements Comparator<String> {
-        public int compare(String option1, String option2) {
-            int diff = option1.compareToIgnoreCase(option2);
-            if (diff != 0) {
-                return diff;
-            }
-            return option1.compareTo(option2);
-        }
-    }
-
-    private static final class OptionStringComparator implements Comparator<String> {
-        public int compare(String option1, String option2) {
-            boolean short1 = option1.length() == 1;
-            boolean short2 = option2.length() == 1;
-            if (short1 && !short2) {
-                return -1;
-            }
-            if (!short1 && short2) {
-                return 1;
-            }
-            return new CaseInsensitiveStringComparator().compare(option1, option2);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java
new file mode 100644
index 0000000..7ced66c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/CompositeInitScriptFinder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class CompositeInitScriptFinder implements InitScriptFinder {
+    private final List<InitScriptFinder> finders;
+
+    public CompositeInitScriptFinder(InitScriptFinder...finders) {
+        this.finders = Arrays.asList(finders);
+    }
+
+    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
+        for (InitScriptFinder finder : finders) {
+            finder.findScripts(gradle, scripts);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderFactory.java
deleted file mode 100644
index 49469d3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderFactory.java
+++ /dev/null
@@ -1,63 +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.initialization;
-
-import org.apache.tools.ant.Project;
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.util.ClasspathUtil;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.Jvm;
-import org.gradle.util.MultiParentClassLoader;
-
-import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Collections;
-
-public class DefaultClassLoaderFactory implements ClassLoaderFactory {
-    private final URLClassLoader rootClassLoader;
-    private final URLClassLoader coreImplClassLoader;
-
-    public DefaultClassLoaderFactory(ClassPathRegistry classPathRegistry) {
-        // Add in tools.jar to the Ant classloader
-        ClassLoader antClassloader = Project.class.getClassLoader();
-        File toolsJar = Jvm.current().getToolsJar();
-        if (toolsJar != null) {
-            ClasspathUtil.addUrl((URLClassLoader) antClassloader, GFileUtils.toURLs(Collections.singleton(toolsJar)));
-        }
-
-        // Add in libs for plugins
-        ClassLoader runtimeClassloader = getClass().getClassLoader();
-        URL[] pluginsClassPath = classPathRegistry.getClassPathUrls("GRADLE_PLUGINS");
-        rootClassLoader = new URLClassLoader(pluginsClassPath, runtimeClassloader);
-
-        URL[] coreImplClassPath = classPathRegistry.getClassPathUrls("GRADLE_CORE_IMPL");
-        coreImplClassLoader = new URLClassLoader(coreImplClassPath, rootClassLoader);
-    }
-
-    public ClassLoader getRootClassLoader() {
-        return rootClassLoader;
-    }
-
-    public ClassLoader getCoreImplClassLoader() {
-        return coreImplClassLoader;
-    }
-
-    public MultiParentClassLoader createScriptClassLoader() {
-        return new MultiParentClassLoader(rootClassLoader);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
new file mode 100755
index 0000000..214d8b7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
@@ -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.initialization;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.util.*;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
+    private final FilteringClassLoader rootClassLoader;
+    private final ClassLoader coreImplClassLoader;
+    private final ClassLoader pluginsClassLoader;
+
+    public DefaultClassLoaderRegistry(ClassPathRegistry classPathRegistry, ClassLoaderFactory classLoaderFactory) {
+        // Add in tools.jar to the systemClassloader parent
+        File toolsJar = Jvm.current().getToolsJar();
+        if (toolsJar != null) {
+            final ClassLoader systemClassLoaderParent = ClassLoader.getSystemClassLoader().getParent();
+            ClasspathUtil.addUrl((URLClassLoader) systemClassLoaderParent, GFileUtils.toURLs(Collections.singleton(toolsJar)));
+        }
+
+        ClassLoader runtimeClassLoader = getClass().getClassLoader();
+
+        // Core impl
+        ClassPath coreImplClassPath = classPathRegistry.getClassPath("GRADLE_CORE_IMPL");
+        coreImplClassLoader = new MutableURLClassLoader(runtimeClassLoader, coreImplClassPath);
+
+        // Add in libs for plugins
+        ClassPath pluginsClassPath = classPathRegistry.getClassPath("GRADLE_PLUGINS");
+        MultiParentClassLoader pluginsImports = new MultiParentClassLoader(runtimeClassLoader, coreImplClassLoader);
+        pluginsClassLoader = new MutableURLClassLoader(pluginsImports, pluginsClassPath);
+
+        rootClassLoader = classLoaderFactory.createFilteringClassLoader(pluginsClassLoader);
+        rootClassLoader.allowPackage("org.gradle");
+        rootClassLoader.allowResources("META-INF/gradle-plugins");
+        rootClassLoader.allowPackage("org.apache.tools.ant");
+        rootClassLoader.allowPackage("groovy");
+        rootClassLoader.allowPackage("org.codehaus.groovy");
+        rootClassLoader.allowPackage("groovyjarjarantlr");
+        rootClassLoader.allowPackage("org.apache.ivy");
+        rootClassLoader.allowPackage("org.slf4j");
+        rootClassLoader.allowPackage("org.apache.commons.logging");
+        rootClassLoader.allowPackage("org.apache.log4j");
+    }
+
+    public ClassLoader getRootClassLoader() {
+        return rootClassLoader;
+    }
+
+    public ClassLoader getCoreImplClassLoader() {
+        return coreImplClassLoader;
+    }
+
+    public ClassLoader getPluginsClassLoader() {
+        return pluginsClassLoader;
+    }
+
+    public MultiParentClassLoader createScriptClassLoader() {
+        return new MultiParentClassLoader(rootClassLoader);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
index d4f4280..79d4eaa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -15,22 +15,21 @@
  */
 package org.gradle.initialization;
 
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
 import org.gradle.CacheUsage;
-import org.gradle.CommandLineArgumentException;
 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.BaseDirFileResolver;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.configuration.ImplicitTasksConfigurer;
+import org.gradle.cli.*;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
+import org.gradle.util.DeprecationLogger;
 
-import java.util.Collection;
-import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 /**
  * @author Hans Dockter
@@ -42,58 +41,49 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
     private static final String BUILD_FILE = "b";
     public static final String INIT_SCRIPT = "I";
     private static final String SETTINGS_FILE = "c";
-    private static final String TASKS = "t";
-    private static final String PROPERTIES = "r";
-    private static final String DEPENDENCIES = "n";
-    public static final String FULL_STACKTRACE = "S";
-    public static final String FULL_STACKTRACE_LONG = "full-stacktrace";
-    public static final String STACKTRACE = "s";
-    public static final String STACKTRACE_LONG = "stacktrace";
-    private static final String SYSTEM_PROP = "D";
-    private static final String PROJECT_PROP = "P";
     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";
     private static final String NO_OPT = "no-opt";
+    private static final String RERUN_TASKS = "rerun-tasks";
     private static final String EXCLUDE_TASK = "x";
     private static final String PROFILE = "profile";
+    private static final String CONTINUE = "continue";
+    private static final String OFFLINE = "offline";
+    private static final String REFRESH = "refresh";
+    private static final String REFRESH_DEPENDENCIES = "refresh-dependencies";
+    private static final String PROJECT_CACHE_DIR = "project-cache-dir";
+    private static final String RECOMPILE_SCRIPTS = "recompile-scripts";
 
-    private static BiMap<String, StartParameter.ShowStacktrace> showStacktraceMap = HashBiMap.create();
     private final CommandLineConverter<LoggingConfiguration> loggingConfigurationCommandLineConverter = new LoggingCommandLineConverter();
-
-    //Initialize bi-directional maps so you can convert these back and forth from their command line options to their
-    //object representation.
-
-    static {
-        showStacktraceMap.put(FULL_STACKTRACE, StartParameter.ShowStacktrace.ALWAYS_FULL);
-        showStacktraceMap.put(STACKTRACE, StartParameter.ShowStacktrace.ALWAYS);
-        //showStacktraceMap.put( , StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS ); there is no command argument for this. Rather, the lack of an argument means 'default to this'.
-    }
+    private final SystemPropertiesCommandLineConverter systemPropertiesCommandLineConverter = new SystemPropertiesCommandLineConverter();
+    private final ProjectPropertiesCommandLineConverter projectPropertiesCommandLineConverter = new ProjectPropertiesCommandLineConverter();
 
     public void configure(CommandLineParser parser) {
         loggingConfigurationCommandLineConverter.configure(parser);
+        systemPropertiesCommandLineConverter.configure(parser);
+        projectPropertiesCommandLineConverter.configure(parser);
         parser.allowMixedSubcommandsAndOptions();
         parser.option(NO_SEARCH_UPWARDS, "no-search-upward").hasDescription(String.format("Don't search in parent folders for a %s file.", Settings.DEFAULT_SETTINGS_FILE));
-        parser.option(CACHE, "cache").hasArgument().hasDescription("Specifies how compiled build scripts should be cached. Possible values are: 'rebuild' and 'on'. Default value is 'on'");
+        parser.option(CACHE, "cache").hasArgument().hasDescription("Specifies how compiled build scripts should be cached. Possible values are: 'rebuild' and 'on'. Default value is 'on'")
+                    .deprecated("Use '--rerun-tasks' or '--recompile-scripts' instead");
+        parser.option(PROJECT_CACHE_DIR).hasArgument().hasDescription("Specifies the project-specific cache directory. Defaults to .gradle in the root project directory.");
         parser.option(DRY_RUN, "dry-run").hasDescription("Runs the builds with all task actions disabled.");
-        parser.option(STACKTRACE, STACKTRACE_LONG).hasDescription("Print out the stacktrace also for user exceptions (e.g. compile error).");
-        parser.option(FULL_STACKTRACE, FULL_STACKTRACE_LONG).hasDescription("Print out the full (very verbose) stacktrace for any exceptions.");
-        parser.option(TASKS, "tasks").mapsToSubcommand(ImplicitTasksConfigurer.TASKS_TASK).hasDescription("Show list of available tasks [deprecated - use 'gradle tasks' instead].");
-        parser.option(PROPERTIES, "properties").mapsToSubcommand(ImplicitTasksConfigurer.PROPERTIES_TASK).hasDescription("Show list of all available project properties [deprecated - use 'gradle properties' instead].");
-        parser.option(DEPENDENCIES, "dependencies").mapsToSubcommand(ImplicitTasksConfigurer.DEPENDENCIES_TASK).hasDescription("Show list of all project dependencies [deprecated - use 'gradle dependencies' instead].");
         parser.option(PROJECT_DIR, "project-dir").hasArgument().hasDescription("Specifies the start directory for Gradle. Defaults to current directory.");
         parser.option(GRADLE_USER_HOME, "gradle-user-home").hasArgument().hasDescription("Specifies the gradle user home directory.");
         parser.option(INIT_SCRIPT, "init-script").hasArguments().hasDescription("Specifies an initialization script.");
         parser.option(SETTINGS_FILE, "settings-file").hasArgument().hasDescription("Specifies the settings file.");
         parser.option(BUILD_FILE, "build-file").hasArgument().hasDescription("Specifies the build file.");
-        parser.option(SYSTEM_PROP, "system-prop").hasArguments().hasDescription("Set system property of the JVM (e.g. -Dmyprop=myvalue).");
-        parser.option(PROJECT_PROP, "project-prop").hasArguments().hasDescription("Set project property for the build script (e.g. -Pmyprop=myvalue).");
-        parser.option(EMBEDDED_SCRIPT, "embedded").hasArgument().hasDescription("Specify an embedded build script.");
         parser.option(NO_PROJECT_DEPENDENCY_REBUILD, "no-rebuild").hasDescription("Do not rebuild project dependencies.");
-        parser.option(NO_OPT).hasDescription("Ignore any task optimization.");
+        parser.option(NO_OPT).hasDescription("Ignore any task optimization.").deprecated("Use '--rerun-tasks' instead");
+        parser.option(RERUN_TASKS).hasDescription("Ignore previously cached task results.");
+        parser.option(RECOMPILE_SCRIPTS).hasDescription("Force build script recompiling.");
         parser.option(EXCLUDE_TASK, "exclude-task").hasArguments().hasDescription("Specify a task to be excluded from execution.");
         parser.option(PROFILE).hasDescription("Profiles build execution time and generates a report in the <build_dir>/reports/profile directory.");
+        parser.option(CONTINUE).hasDescription("Continues task execution after a task failure.").experimental();
+        parser.option(OFFLINE).hasDescription("The build should operate without accessing network resources.");
+        parser.option(REFRESH).hasArguments().hasDescription("Refresh the state of resources of the type(s) specified. Currently only 'dependencies' is supported.").deprecated("Use '--refresh-dependencies' instead.");
+        parser.option(REFRESH_DEPENDENCIES).hasDescription("Refresh the state of dependencies.");
     }
 
     @Override
@@ -101,21 +91,15 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         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());
+    public StartParameter convert(final ParsedCommandLine options, final StartParameter startParameter) throws CommandLineArgumentException {
+        loggingConfigurationCommandLineConverter.convert(options, startParameter);
+        FileResolver resolver = new BaseDirFileResolver(FileSystems.getDefault(), startParameter.getCurrentDir());
 
-        for (String keyValueExpression : options.option(SYSTEM_PROP).getValues()) {
-            String[] elements = keyValueExpression.split("=");
-            startParameter.getSystemPropertiesArgs().put(elements[0], elements.length == 1 ? "" : elements[1]);
-        }
+        Map<String, String> systemProperties = systemPropertiesCommandLineConverter.convert(options);
+        convertCommandLineSystemProperties(systemProperties, startParameter, resolver);
 
-        for (String keyValueExpression : options.option(PROJECT_PROP).getValues()) {
-            String[] elements = keyValueExpression.split("=");
-            startParameter.getProjectProperties().put(elements[0], elements.length == 1 ? "" : elements[1]);
-        }
+        Map<String, String> projectProperties = projectPropertiesCommandLineConverter.convert(options);
+        startParameter.getProjectProperties().putAll(projectProperties);
 
         if (options.hasOption(NO_SEARCH_UPWARDS)) {
             startParameter.setSearchUpwards(false);
@@ -140,37 +124,28 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
 
         if (options.hasOption(CACHE)) {
             try {
-                startParameter.setCacheUsage(CacheUsage.fromString(options.option(CACHE).getValue()));
+                final CacheUsage cacheUsage =  DeprecationLogger.whileDisabled(new Factory<CacheUsage>() {
+                    public CacheUsage create() {
+                        return CacheUsage.fromString(options.option(CACHE).getValue());  //To change body of implemented methods use File | Settings | File Templates.
+                    }
+                });
+                DeprecationLogger.whileDisabled(new Factory<Void>() {
+                    public Void create() {
+                        startParameter.setCacheUsage(cacheUsage);
+                        return null;
+                    }
+                });
             } catch (InvalidUserDataException e) {
                 throw new CommandLineArgumentException(e.getMessage());
             }
         }
 
-        if (options.hasOption(EMBEDDED_SCRIPT)) {
-            if (options.hasOption(BUILD_FILE) || options.hasOption(NO_SEARCH_UPWARDS) || options.hasOption(SETTINGS_FILE)) {
-                System.err.println(String.format(
-                        "Error: The -%s option can't be used together with the -%s, -%s or -%s options.",
-                        EMBEDDED_SCRIPT, BUILD_FILE, SETTINGS_FILE, NO_SEARCH_UPWARDS));
-                throw new CommandLineArgumentException(String.format(
-                        "Error: The -%s option can't be used together with the -%s, -%s or -%s options.",
-                        EMBEDDED_SCRIPT, BUILD_FILE, SETTINGS_FILE, NO_SEARCH_UPWARDS));
-            }
-            startParameter.useEmbeddedBuildFile(options.option(EMBEDDED_SCRIPT).getValue());
-        }
-
-        if (options.hasOption(FULL_STACKTRACE)) {
-            if (options.hasOption(STACKTRACE)) {
-                throw new CommandLineArgumentException(String.format(
-                        "Error: The -%s option can't be used together with the -%s option.", FULL_STACKTRACE,
-                        STACKTRACE));
-            }
-            startParameter.setShowStacktrace(StartParameter.ShowStacktrace.ALWAYS_FULL);
-        } else if (options.hasOption(STACKTRACE)) {
-            startParameter.setShowStacktrace(StartParameter.ShowStacktrace.ALWAYS);
+        if (options.hasOption(PROJECT_CACHE_DIR)) {
+            startParameter.setProjectCacheDir(resolver.resolve(options.option(PROJECT_CACHE_DIR).getValue()));
         }
 
         if (options.hasOption(NO_PROJECT_DEPENDENCY_REBUILD)) {
-            startParameter.setProjectDependenciesBuildInstruction(new ProjectDependenciesBuildInstruction(false));
+            startParameter.setBuildProjectDependencies(false);
         }
 
         if (!options.getExtraArguments().isEmpty()) {
@@ -182,7 +157,15 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         }
 
         if (options.hasOption(NO_OPT)) {
-            startParameter.setNoOpt(true);
+            startParameter.setRerunTasks(true);
+        }
+
+        if (options.hasOption(RERUN_TASKS)) {
+            startParameter.setRerunTasks(true);
+        }
+
+        if (options.hasOption(RECOMPILE_SCRIPTS)) {
+            startParameter.setRecompileScripts(true);
         }
 
         if (options.hasOption(EXCLUDE_TASK)) {
@@ -193,48 +176,38 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
             startParameter.setProfile(true);
         }
 
-        return startParameter;
-    }
+        if (options.hasOption(CONTINUE)) {
+            startParameter.setContinueOnFailure(true);
+        }
 
-    /**
-     * This returns the stack trace level object represented by the command line argument
-     *
-     * @param commandLineArgument a single command line argument (with no '-')
-     * @return the corresponding stack trace level or null if it doesn't match any.
-     * @author mhunsicker
-     */
-    public StartParameter.ShowStacktrace getShowStacktrace(String commandLineArgument) {
-        StartParameter.ShowStacktrace showStacktrace = showStacktraceMap.get(commandLineArgument);
-        if (showStacktrace == null) {
-            return null;
+        if (options.hasOption(OFFLINE)) {
+            startParameter.setOffline(true);
         }
 
-        return showStacktrace;
-    }
+        if (options.hasOption(REFRESH)) {
+            //as refreshoptions is deprecated redirect refresh dependencies to --refresh-dependencies
+            final List<String> refreshOptions = options.option(REFRESH).getValues();
+            for(String refreshOption : refreshOptions){
+                if(refreshOption.equalsIgnoreCase("dependencies")){
+                    startParameter.setRefreshDependencies(true);
+                }else{
+                    throw new CommandLineArgumentException(String.format("Unknown refresh option '%s' specified.", refreshOption));
+                }
+            }
+        }
 
-    /**
-     * This returns the command line argument that represents the specified stack trace level.
-     *
-     * @param showStacktrace the stack trace level.
-     * @return the command line argument or null if this level cannot be represented on the command line.
-     * @author mhunsicker
-     */
-    public String getShowStacktraceCommandLine(StartParameter.ShowStacktrace showStacktrace) {
-        String commandLine = showStacktraceMap.inverse().get(showStacktrace);
-        if (commandLine == null) {
-            return null;
+        if (options.hasOption(REFRESH_DEPENDENCIES)) {
+            startParameter.setRefreshDependencies(true);
         }
 
-        return commandLine;
+        return startParameter;
     }
 
-    /**
-     * This returns the ShowStacktrace levels that are supported on the command line.
-     *
-     * @return a collection of available ShowStacktrace levels
-     * @author mhunsicker
-     */
-    public Collection<StartParameter.ShowStacktrace> getShowStacktrace() {
-        return Collections.unmodifiableCollection(showStacktraceMap.values());
+    void convertCommandLineSystemProperties(Map<String, String> systemProperties, StartParameter startParameter, FileResolver resolver) {
+        startParameter.getSystemPropertiesArgs().putAll(systemProperties);
+        String gradleUserHomeProp = "gradle.user.home";
+        if (systemProperties.containsKey(gradleUserHomeProp)) {
+            startParameter.setGradleUserHomeDir(resolver.resolve(systemProperties.get(gradleUserHomeProp)));
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
old mode 100644
new mode 100755
index 4de6a82..cfa9c9d
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
@@ -1,109 +1,109 @@
-/*
- * 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.api.GradleScriptException;
-import org.gradle.api.LocationAwareException;
-import org.gradle.api.ScriptCompilationException;
-import org.gradle.api.internal.Contextual;
-import org.gradle.api.internal.ExceptionAnalyser;
-import org.gradle.api.tasks.TaskExecutionException;
-import org.gradle.groovy.scripts.Script;
-import org.gradle.groovy.scripts.ScriptExecutionListener;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.listener.ListenerManager;
-import org.gradle.listener.ListenerNotificationException;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecutionListener {
-    private final Map<String, ScriptSource> scripts = new HashMap<String, ScriptSource>();
-
-    public DefaultExceptionAnalyser(ListenerManager listenerManager) {
-        listenerManager.addListener(this);
-    }
-
-    public void beforeScript(Script script) {
-        ScriptSource source = script.getScriptSource();
-        scripts.put(source.getFileName(), source);
-    }
-
-    public void afterScript(Script script, Throwable result) {
-    }
-
-    public Throwable transform(Throwable exception) {
-        Throwable actualException = findDeepest(exception);
-        if (actualException == null) {
-            return exception;
-        }
-        if (actualException instanceof LocationAwareException) {
-            return actualException;
-        }
-
-        ScriptSource source = null;
-        Integer lineNumber = null;
-        Throwable target = actualException;
-
-        // todo - remove these special cases
-        if (actualException instanceof ScriptCompilationException) {
-            ScriptCompilationException scriptCompilationException = (ScriptCompilationException) actualException;
-            source = scriptCompilationException.getScriptSource();
-            lineNumber = scriptCompilationException.getLineNumber();
-        }
-        if (actualException instanceof ListenerNotificationException && actualException.getCause() != null) {
-            target = actualException.getCause();
-        }
-
-        if (source == null) {
-            for (
-                    Throwable currentException = actualException; currentException != null;
-                    currentException = currentException.getCause()) {
-                for (StackTraceElement element : currentException.getStackTrace()) {
-                    if (scripts.containsKey(element.getFileName())) {
-                        source = scripts.get(element.getFileName());
-                        lineNumber = element.getLineNumber() >= 0 ? element.getLineNumber() : null;
-                        break;
-                    }
-                }
-            }
-        }
-
-        return new LocationAwareException(actualException, target, source, lineNumber);
-    }
-
-    private Throwable findDeepest(Throwable exception) {
-        Throwable locationAware = null;
-        Throwable result = null;
-        Throwable contextMatch = null;
-        for (Throwable current = exception; current != null; current = current.getCause()) {
-            if (current instanceof LocationAwareException) {
-                locationAware = current;
-            } else if (current instanceof GradleScriptException || current instanceof TaskExecutionException) {
-                result = current;
-            } else if (current.getClass().getAnnotation(Contextual.class) != null) {
-                contextMatch = current;
-            }
-        }
-        if (locationAware != null) {
-            return locationAware;
-        } else if (result != null) {
-            return result;
-        } else {
-            return contextMatch;
-        }
-    }
-}
+/*
+ * 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.api.GradleScriptException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.LocationAwareException;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptCompilationException;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.listener.ListenerManager;
+import org.gradle.listener.ListenerNotificationException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecutionListener {
+    private final Map<String, ScriptSource> scripts = new HashMap<String, ScriptSource>();
+
+    public DefaultExceptionAnalyser(ListenerManager listenerManager) {
+        listenerManager.addListener(this);
+    }
+
+    public void beforeScript(Script script) {
+        ScriptSource source = script.getScriptSource();
+        scripts.put(source.getFileName(), source);
+    }
+
+    public void afterScript(Script script, Throwable result) {
+    }
+
+    public Throwable transform(Throwable exception) {
+        Throwable actualException = findDeepest(exception);
+        if (actualException == null) {
+            return exception;
+        }
+        if (actualException instanceof LocationAwareException) {
+            return actualException;
+        }
+
+        ScriptSource source = null;
+        Integer lineNumber = null;
+        Throwable target = actualException;
+
+        // todo - remove these special cases
+        if (actualException instanceof ScriptCompilationException) {
+            ScriptCompilationException scriptCompilationException = (ScriptCompilationException) actualException;
+            source = scriptCompilationException.getScriptSource();
+            lineNumber = scriptCompilationException.getLineNumber();
+        }
+        if (actualException instanceof ListenerNotificationException && actualException.getCause() != null) {
+            target = actualException.getCause();
+        }
+
+        if (source == null) {
+            for (
+                    Throwable currentException = actualException; currentException != null;
+                    currentException = currentException.getCause()) {
+                for (StackTraceElement element : currentException.getStackTrace()) {
+                    if (scripts.containsKey(element.getFileName())) {
+                        source = scripts.get(element.getFileName());
+                        lineNumber = element.getLineNumber() >= 0 ? element.getLineNumber() : null;
+                        break;
+                    }
+                }
+            }
+        }
+
+        return new LocationAwareException(actualException, target, source, lineNumber);
+    }
+
+    private Throwable findDeepest(Throwable exception) {
+        Throwable locationAware = null;
+        Throwable result = null;
+        Throwable contextMatch = null;
+        for (Throwable current = exception; current != null; current = current.getCause()) {
+            if (current instanceof LocationAwareException) {
+                locationAware = current;
+            } else if (current instanceof GradleScriptException || current instanceof TaskExecutionException) {
+                result = current;
+            } else if (contextMatch == null && current.getClass().getAnnotation(Contextual.class) != null) {
+                contextMatch = current;
+            }
+        }
+        if (locationAware != null) {
+            return locationAware;
+        } else if (result != null) {
+            return result;
+        } else {
+            return contextMatch;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
index 29b2850..5562818 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -26,43 +26,41 @@ import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.configuration.BuildConfigurer;
 import org.gradle.execution.BuildExecuter;
 import org.gradle.logging.LoggingManagerInternal;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class DefaultGradleLauncher extends GradleLauncher {
     private enum Stage {
         Configure, PopulateTaskGraph, Build
     }
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGradleLauncher.class);
-
     private final GradleInternal gradle;
     private final SettingsHandler settingsHandler;
-    private final IGradlePropertiesLoader gradlePropertiesLoader;
     private final BuildLoader buildLoader;
     private final BuildConfigurer buildConfigurer;
     private final ExceptionAnalyser exceptionAnalyser;
     private final BuildListener buildListener;
     private final InitScriptHandler initScriptHandler;
     private final LoggingManagerInternal loggingManager;
+    private final ModelConfigurationListener modelConfigurationListener;
+    private final BuildExecuter buildExecuter;
 
     /**
      * Creates a new instance.  Don't call this directly, use {@link #newInstance(org.gradle.StartParameter)} or {@link
      * #newInstance(String...)} instead.  Note that this method is package-protected to discourage it's direct use.
      */
     public DefaultGradleLauncher(GradleInternal gradle, InitScriptHandler initScriptHandler, SettingsHandler settingsHandler,
-                                 IGradlePropertiesLoader gradlePropertiesLoader, BuildLoader buildLoader,
-                                 BuildConfigurer buildConfigurer, BuildListener buildListener,
-                                 ExceptionAnalyser exceptionAnalyser, LoggingManagerInternal loggingManager) {
+                                 BuildLoader buildLoader, BuildConfigurer buildConfigurer, BuildListener buildListener,
+                                 ExceptionAnalyser exceptionAnalyser, LoggingManagerInternal loggingManager,
+                                 ModelConfigurationListener modelConfigurationListener, BuildExecuter buildExecuter) {
         this.gradle = gradle;
         this.initScriptHandler = initScriptHandler;
         this.settingsHandler = settingsHandler;
-        this.gradlePropertiesLoader = gradlePropertiesLoader;
         this.buildLoader = buildLoader;
         this.buildConfigurer = buildConfigurer;
         this.exceptionAnalyser = exceptionAnalyser;
         this.buildListener = buildListener;
         this.loggingManager = loggingManager;
+        this.modelConfigurationListener = modelConfigurationListener;
+        this.buildExecuter = buildExecuter;
     }
 
     public GradleInternal getGradle() {
@@ -130,32 +128,31 @@ public class DefaultGradleLauncher extends GradleLauncher {
         initScriptHandler.executeScripts(gradle);
 
         // Evaluate settings script
-        SettingsInternal settings = settingsHandler.findAndLoadSettings(gradle, gradlePropertiesLoader);
+        SettingsInternal settings = settingsHandler.findAndLoadSettings(gradle);
         buildListener.settingsEvaluated(settings);
 
         // Load build
-        buildLoader.load(settings.getRootProject(), gradle, gradlePropertiesLoader.getGradleProperties());
+        buildLoader.load(settings.getRootProject(), gradle);
         buildListener.projectsLoaded(gradle);
 
         // Configure build
         buildConfigurer.configure(gradle);
         buildListener.projectsEvaluated(gradle);
+        modelConfigurationListener.onConfigure(gradle);
 
         if (upTo == Stage.Configure) {
             return;
         }
 
         // Populate task graph
-        BuildExecuter executer = gradle.getStartParameter().getBuildExecuter();
-        executer.select(gradle);
+        buildExecuter.select(gradle);
 
         if (upTo == Stage.PopulateTaskGraph) {
             return;
         }
 
         // Execute build
-        LOGGER.info(String.format("Starting build for %s.", executer.getDisplayName()));
-        executer.execute();
+        buildExecuter.execute();
 
         assert upTo == Stage.Build;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
index fe8b1b3..512ace9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -18,21 +18,25 @@ package org.gradle.initialization;
 
 import org.gradle.*;
 import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.project.GlobalServicesRegistry;
-import org.gradle.api.internal.project.IProjectFactory;
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.cache.CacheRepository;
+import org.gradle.cli.CommandLineConverter;
 import org.gradle.configuration.BuildConfigurer;
+import org.gradle.execution.BuildExecuter;
+import org.gradle.initialization.layout.BuildLayoutFactory;
 import org.gradle.invocation.DefaultGradle;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.profile.ProfileListener;
-import org.gradle.util.WrapUtil;
+import org.gradle.profile.ProfileEventAdapter;
+import org.gradle.profile.ReportGeneratingProfileListener;
 
 import java.util.Arrays;
 
@@ -59,6 +63,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         // Register default loggers 
         ListenerManager listenerManager = sharedServices.get(ListenerManager.class);
         listenerManager.addListener(new BuildProgressLogger(sharedServices.get(ProgressLoggerFactory.class)));
+        listenerManager.useLogger(new DependencyResolutionLogger(sharedServices.get(ProgressLoggerFactory.class)));
 
         GradleLauncher.injectCustomFactory(this);
     }
@@ -114,34 +119,30 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
         listenerManager.addListener(tracker);
         listenerManager.addListener(new BuildCleanupListener(serviceRegistry));
 
+        listenerManager.addListener(serviceRegistry.get(ProfileEventAdapter.class));
         if (startParameter.isProfile()) {
-            listenerManager.addListener(new ProfileListener(requestMetaData.getBuildTimeClock().getStartTime()));
+            listenerManager.addListener(new ReportGeneratingProfileListener());
         }
 
-        DefaultGradle gradle = new DefaultGradle(
-                tracker.getCurrentBuild(),
-                startParameter, serviceRegistry);
+        GradleInternal gradle = serviceRegistry.get(Instantiator.class).newInstance(DefaultGradle.class, tracker.getCurrentBuild(), startParameter, serviceRegistry);
         return new DefaultGradleLauncher(
                 gradle,
                 serviceRegistry.get(InitScriptHandler.class),
                 new SettingsHandler(
-                        new EmbeddedScriptSettingsFinder(
-                                new DefaultSettingsFinder(WrapUtil.<ISettingsFileSearchStrategy>toList(
-                                        new MasterDirSettingsFinderStrategy(),
-                                        new ParentDirSettingsFinderStrategy()))),
+                        new DefaultSettingsFinder(
+                                new BuildLayoutFactory()),
                         serviceRegistry.get(SettingsProcessor.class),
                         new BuildSourceBuilder(
                                 this,
-                                serviceRegistry.get(ClassLoaderFactory.class),
+                                serviceRegistry.get(ClassLoaderRegistry.class),
                                 serviceRegistry.get(CacheRepository.class))),
-                new DefaultGradlePropertiesLoader(),
-                new BuildLoader(
-                        serviceRegistry.get(IProjectFactory.class)
-                ),
+                serviceRegistry.get(BuildLoader.class),
                 serviceRegistry.get(BuildConfigurer.class),
                 gradle.getBuildListenerBroadcaster(),
                 serviceRegistry.get(ExceptionAnalyser.class),
-                loggingManager);
+                loggingManager,
+                listenerManager.getBroadcaster(ModelConfigurationListener.class),
+                gradle.getServices().get(BuildExecuter.class));
     }
 
     public void setCommandLineConverter(
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
index 2dd11c3..bf360d8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
@@ -16,13 +16,12 @@
 package org.gradle.initialization;
 
 import org.gradle.StartParameter;
-import org.gradle.util.GUtil;
 import org.gradle.api.Project;
+import org.gradle.util.GUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -33,19 +32,27 @@ import java.util.Properties;
 public class DefaultGradlePropertiesLoader implements IGradlePropertiesLoader {
     private static Logger logger = LoggerFactory.getLogger(DefaultGradlePropertiesLoader.class);
 
-    private Map<String, String> gradleProperties = new HashMap<String, String>();
+    private Map<String, String> defaultProperties = new HashMap<String, String>();
+    private Map<String, String> overrideProperties = new HashMap<String, String>();
+    private final StartParameter startParameter;
+
+    public DefaultGradlePropertiesLoader(StartParameter startParameter) {
+        this.startParameter = startParameter;
+    }
 
-    public void loadProperties(File settingsDir, StartParameter startParameter) {
+    public void loadProperties(File settingsDir) {
         loadProperties(settingsDir, startParameter, getAllSystemProperties(), getAllEnvProperties());
     }
 
     void loadProperties(File settingsDir, StartParameter startParameter, Map<String, String> systemProperties, Map<String, String> envProperties) {
-        gradleProperties.clear();
-        addGradleProperties(new File(settingsDir, Project.GRADLE_PROPERTIES), new File(startParameter.getGradleUserHomeDir(), Project.GRADLE_PROPERTIES));
+        defaultProperties.clear();
+        overrideProperties.clear();
+        addGradleProperties(defaultProperties, new File(settingsDir, Project.GRADLE_PROPERTIES));
+        addGradleProperties(overrideProperties, new File(startParameter.getGradleUserHomeDir(), Project.GRADLE_PROPERTIES));
         setSystemProperties(startParameter.getSystemPropertiesArgs());
-        gradleProperties.putAll(getEnvProjectProperties(envProperties));
-        gradleProperties.putAll(getSystemProjectProperties(systemProperties));
-        gradleProperties.putAll(startParameter.getProjectProperties());
+        overrideProperties.putAll(getEnvProjectProperties(envProperties));
+        overrideProperties.putAll(getSystemProjectProperties(systemProperties));
+        overrideProperties.putAll(startParameter.getProjectProperties());
     }
 
     Map getAllSystemProperties() {
@@ -53,34 +60,24 @@ public class DefaultGradlePropertiesLoader implements IGradlePropertiesLoader {
     }
 
     Map<String, String> getAllEnvProperties() {
-        // The reason why we have an try-catch block here is for JDK 1.4 compatibility. We use the retrotranslator to produce
-        // a 1.4 compatible version. But the retrotranslator is not capable of translating System.getenv to 1.4.
-        // The System.getenv call is only available in 1.5. In fact 1.4 does not offer any API to read
-        // environment variables. Therefore this call leads to an exception when used with 1.4. We ignore the exception in this
-        // case and simply return an empty hashmap.
-        try {
-            return System.getenv();
-        } catch (Throwable e) {
-            logger.debug("The System.getenv() call has lead to an exception. Probably you are running on Java 1.4.", e);
-            return Collections.emptyMap();
-        }
+        return System.getenv();
     }
 
-    private void addGradleProperties(File... files) {
+    private void addGradleProperties(Map<String, String> target, File... files) {
         for (File propertyFile : files) {
             if (propertyFile.isFile()) {
                 Properties properties = GUtil.loadProperties(propertyFile);
-                gradleProperties.putAll(new HashMap(properties));
+                target.putAll(new HashMap(properties));
             }
         }
     }
 
-    public Map<String, String> getGradleProperties() {
-        return gradleProperties;
-    }
-
-    public void setGradleProperties(Map<String, String> gradleProperties) {
-        this.gradleProperties = gradleProperties;
+    public Map<String, String> mergeProperties(Map<String, String> properties) {
+        Map<String, String> result = new HashMap<String, String>();
+        result.putAll(defaultProperties);
+        result.putAll(properties);
+        result.putAll(overrideProperties);
+        return result;
     }
 
     private Map<String, String> getSystemProjectProperties(Map<String, String> systemProperties) {
@@ -107,13 +104,14 @@ public class DefaultGradlePropertiesLoader implements IGradlePropertiesLoader {
 
     private void setSystemProperties(Map<String, String> properties) {
         System.getProperties().putAll(properties);
-        addSystemPropertiesFromGradleProperties();
+        addSystemPropertiesFromGradleProperties(defaultProperties);
+        addSystemPropertiesFromGradleProperties(overrideProperties);
     }
 
-    private void addSystemPropertiesFromGradleProperties() {
-        for (String key : gradleProperties.keySet()) {
+    private void addSystemPropertiesFromGradleProperties(Map<String, String> properties) {
+        for (String key : properties.keySet()) {
             if (key.startsWith(Project.SYSTEM_PROP_PREFIX + '.')) {
-                System.setProperty(key.substring((Project.SYSTEM_PROP_PREFIX + '.').length()), gradleProperties.get(key));
+                System.setProperty(key.substring((Project.SYSTEM_PROP_PREFIX + '.').length()), properties.get(key));
             }
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultInitScriptFinder.java
deleted file mode 100644
index c926551..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultInitScriptFinder.java
+++ /dev/null
@@ -1,40 +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.initialization;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.groovy.scripts.UriScriptSource;
-
-import java.io.File;
-import java.util.List;
-import java.util.ArrayList;
-
-/**
- * Simple finder that "finds" all the init scripts that were explicitly added to the start parameters.
- */
-public class DefaultInitScriptFinder implements InitScriptFinder {
-    public List<ScriptSource> findScripts(GradleInternal gradle) {
-        List<File> scriptFiles = gradle.getStartParameter().getInitScripts();
-        List<ScriptSource> scripts = new ArrayList<ScriptSource>(scriptFiles.size());
-        for (File file : scriptFiles) {
-            scripts.add(new UriScriptSource("initialization script", file));
-        }
-
-        return scripts;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
index dfcfb45..206e40b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
@@ -17,8 +17,8 @@
 package org.gradle.initialization
 
 import org.gradle.StartParameter
-import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.api.internal.GradleInternal
+import org.gradle.groovy.scripts.ScriptSource
 
 /**
  * @author Hans Dockter
@@ -34,10 +34,10 @@ public class DefaultSettings extends BaseSettings {
     }
 
     def propertyMissing(String property) {
-        return dynamicObjectHelper.getProperty(property)
+        return dynamicObject.getProperty(property)
     }
 
     void setProperty(String name, value) {
-        dynamicObjectHelper.setProperty(name, value) 
+        dynamicObject.setProperty(name, value)
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
index 0a244cd..6889452 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
@@ -16,36 +16,21 @@
 package org.gradle.initialization;
 
 import org.gradle.StartParameter;
-import org.gradle.groovy.scripts.UriScriptSource;
-import org.gradle.groovy.scripts.StringScriptSource;
-
-import java.io.File;
-import java.util.List;
+import org.gradle.initialization.layout.BuildLayout;
+import org.gradle.initialization.layout.BuildLayoutConfiguration;
+import org.gradle.initialization.layout.BuildLayoutFactory;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultSettingsFinder implements ISettingsFinder {
-    private List<ISettingsFileSearchStrategy> settingsFileSearchStrategies;
+    private final BuildLayoutFactory layoutFactory;
 
-    public DefaultSettingsFinder(List<ISettingsFileSearchStrategy> settingsFileSearchStrategies) {
-        this.settingsFileSearchStrategies = settingsFileSearchStrategies;
+    public DefaultSettingsFinder(BuildLayoutFactory layoutFactory) {
+        this.layoutFactory = layoutFactory;
     }
 
-    public SettingsLocation find(StartParameter startParameter) {
-        File settingsFile = null;
-        for (ISettingsFileSearchStrategy settingsFileSearchStrategy : settingsFileSearchStrategies) {
-            settingsFile = settingsFileSearchStrategy.find(startParameter);
-            if (settingsFile != null) {
-                break;
-            }
-        }
-        if (settingsFile == null) {
-            return new SettingsLocation(startParameter.getCurrentDir(),
-                                       new StringScriptSource("empty settings file", ""));
-        } else {
-            return new SettingsLocation(settingsFile.getParentFile(),
-                                       new UriScriptSource("settings file", settingsFile));
-        }
+    public BuildLayout find(StartParameter startParameter) {
+        return layoutFactory.getLayoutFor(new BuildLayoutConfiguration(startParameter));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
new file mode 100644
index 0000000..5f5256b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.api.artifacts.DependencyResolutionListener;
+import org.gradle.api.artifacts.ResolvableDependencies;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+
+public class DependencyResolutionLogger implements DependencyResolutionListener {
+    private final ProgressLoggerFactory loggerFactory;
+    private ProgressLogger logger;
+
+    public DependencyResolutionLogger(ProgressLoggerFactory loggerFactory) {
+        this.loggerFactory = loggerFactory;
+    }
+
+    public void beforeResolve(ResolvableDependencies dependencies) {
+        logger = loggerFactory.newOperation(DependencyResolutionLogger.class);
+        logger.setDescription(String.format("Resolve %s", dependencies));
+        logger.setShortDescription(String.format("Resolving %s", dependencies));
+        logger.started();
+    }
+
+    public void afterResolve(ResolvableDependencies dependencies) {
+        logger.completed();
+        logger = null;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java
new file mode 100644
index 0000000..bde277b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DirectoryInitScriptFinder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class DirectoryInitScriptFinder implements InitScriptFinder {
+    protected void findScriptsInDir(File initScriptsDir, Collection<ScriptSource> scripts) {
+        if (!initScriptsDir.isDirectory()) {
+            return;
+        }
+        List<File> files = new ArrayList<File>();
+        for (File file : initScriptsDir.listFiles()) {
+            if (file.isFile() && file.getName().endsWith(".gradle")) {
+                files.add(file);
+            }
+        }
+        Collections.sort(files);
+        for (File file : files) {
+            scripts.add(new UriScriptSource("initialization script", file));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java
new file mode 100644
index 0000000..5be76a0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DistributionInitScriptFinder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.api.internal.GradleDistributionLocator;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * An {@link InitScriptFinder} that includes every *.gradle file in $gradleHome/init.d.
+ */
+public class DistributionInitScriptFinder extends DirectoryInitScriptFinder {
+    final GradleDistributionLocator locator;
+
+    public DistributionInitScriptFinder(GradleDistributionLocator locator) {
+        this.locator = locator;
+    }
+
+    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
+        File distDir = locator.getGradleHome();
+        if (distDir == null) {
+            return;
+        }
+        findScriptsInDir(new File(distDir, "init.d"), scripts);
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinder.java
deleted file mode 100644
index 9e82480..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinder.java
+++ /dev/null
@@ -1,36 +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.StartParameter;
-
-public class EmbeddedScriptSettingsFinder implements ISettingsFinder {
-    private final ISettingsFinder finder;
-
-    public EmbeddedScriptSettingsFinder(ISettingsFinder finder) {
-        this.finder = finder;
-    }
-
-    public SettingsLocation find(StartParameter startParameter) {
-        if (startParameter.getSettingsScriptSource() != null) {
-            return new SettingsLocation(startParameter.getCurrentDir(),
-                                        startParameter.getSettingsScriptSource());
-        } else {
-            return finder.find(startParameter);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java
index 7184515..2437d93 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherAction.java
@@ -18,8 +18,26 @@ package org.gradle.initialization;
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
 
+/**
+ * An object that performs some action with a {@link GradleLauncher}, and optionally produces a “result” object (e.g. the output).
+ * <p>
+ * Implementations of this are typically composed to bootstrap a build in a certain environment.
+ * <p>
+ * @see org.gradle.launcher.cli.ExecuteBuildAction
+ * @see org.gradle.tooling.internal.provider.BuildModelAction
+ */
 public interface GradleLauncherAction<T> {
+    
+    /**
+     * Something produced by the action, the meaning of which is entirely up to the implementation to define.
+     */
     T getResult();
 
+    /**
+     * Executes the action with the given launcher.
+     * <p>
+     * The state of the launcher is not defined as part of this contract, it is highly context specific. For example,
+     * it is not guaranteed that the start parameter for the launcher has been configured.
+     */
     BuildResult run(GradleLauncher launcher);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
index aefd3dc..a271e0f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/GradleLauncherFactory.java
@@ -19,7 +19,7 @@ import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
 
 /**
- * <p>A {@code GradleFactory} is responsible for creating a {@link org.gradle.GradleLauncher} instance for a build, from a {@link
+ * <p>A {@code GradleLauncherFactory} is responsible for creating a {@link org.gradle.GradleLauncher} instance for a build, from a {@link
  * org.gradle.StartParameter}.</p>
  *
  * @author Hans Dockter
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
index 39c8239..27db653 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
@@ -15,8 +15,6 @@
  */
 package org.gradle.initialization;
 
-import org.gradle.StartParameter;
-
 import java.io.File;
 import java.util.Map;
 
@@ -27,8 +25,8 @@ public interface IGradlePropertiesLoader {
     public static final String SYSTEM_PROJECT_PROPERTIES_PREFIX = "org.gradle.project.";
 
     public static final String ENV_PROJECT_PROPERTIES_PREFIX = "ORG_GRADLE_PROJECT_";
-    
-    Map<String, String> getGradleProperties();
 
-    void loadProperties(File rootDir, StartParameter startParameter);
+    Map<String, String> mergeProperties(Map<String, String> properties);
+
+    void loadProperties(File rootDir);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFileSearchStrategy.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFileSearchStrategy.java
deleted file mode 100644
index 82fdc97..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ISettingsFileSearchStrategy.java
+++ /dev/null
@@ -1,27 +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.initialization;
-
-import org.gradle.StartParameter;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public interface ISettingsFileSearchStrategy {
-    File find(StartParameter startParameter);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
index 806f11e..6289572 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
@@ -18,11 +18,11 @@ package org.gradle.initialization;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.groovy.scripts.ScriptSource;
 
-import java.util.List;
+import java.util.Collection;
 
 /**
  * Interface for objects that can find init scripts for a given build.
  */
 public interface InitScriptFinder {
-    public List<ScriptSource> findScripts(GradleInternal gradle);
+    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
index 1f6e606..d41300d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
@@ -19,6 +19,7 @@ import org.gradle.api.internal.GradleInternal;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.configuration.InitScriptProcessor;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -34,7 +35,8 @@ public class InitScriptHandler {
     }
 
     public void executeScripts(GradleInternal gradle) {
-        List<ScriptSource> scriptSources = finder.findScripts(gradle);
+        List<ScriptSource> scriptSources = new ArrayList<ScriptSource>();
+        finder.findScripts(gradle, scriptSources);
         for (ScriptSource source : scriptSources) {
             processor.process(source, gradle);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java
new file mode 100644
index 0000000..3b13750
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/InstantiatingBuildLoader.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 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.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.IProjectFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class InstantiatingBuildLoader implements BuildLoader {
+    private final IProjectFactory projectFactory;
+
+    public InstantiatingBuildLoader(IProjectFactory projectFactory) {
+        this.projectFactory = projectFactory;
+    }
+
+    /**
+     * Creates the {@link org.gradle.api.internal.GradleInternal} and {@link ProjectInternal} instances for the given root project,
+     * ready for the projects to be evaluated.
+     */
+    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle) {
+        createProjects(rootProjectDescriptor, gradle);
+        attachDefaultProject(gradle);
+    }
+
+    private void attachDefaultProject(GradleInternal gradle) {
+        File explicitProjectDir = gradle.getStartParameter().getProjectDir();
+        File explicitBuildFile = gradle.getStartParameter().getBuildFile();
+        ProjectSpec spec = explicitBuildFile != null
+                ? new BuildFileProjectSpec(explicitBuildFile)
+                : explicitProjectDir == null ? new DefaultProjectSpec(gradle.getStartParameter().getCurrentDir()) : new ProjectDirectoryProjectSpec(explicitProjectDir);
+        try {
+            gradle.setDefaultProject(spec.selectProject(gradle.getRootProject().getProjectRegistry()));
+        } catch (InvalidUserDataException e) {
+            throw new GradleException(String.format("Could not select the default project for this build. %s",
+                    e.getMessage()), e);
+        }
+    }
+
+    private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle) {
+        ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle);
+        gradle.setRootProject(rootProject);
+        addProjects(rootProject, rootProjectDescriptor, gradle);
+    }
+
+    private void addProjects(ProjectInternal parent, ProjectDescriptor parentProjectDescriptor, GradleInternal gradle) {
+        for (ProjectDescriptor childProjectDescriptor : parentProjectDescriptor.getChildren()) {
+            ProjectInternal childProject = projectFactory.createProject(childProjectDescriptor, parent, gradle);
+            addProjects(childProject, childProjectDescriptor, gradle);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategy.java b/subprojects/core/src/main/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategy.java
deleted file mode 100644
index 84b0d90..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategy.java
+++ /dev/null
@@ -1,43 +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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.initialization.Settings;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class MasterDirSettingsFinderStrategy extends AbstractSettingsFileSearchStrategyTemplate {
-    public static final String MASTER_DIR_NAME = "master";
-
-    protected File findBeyondCurrentDir(StartParameter startParameter) {
-        File searchDir = startParameter.getCurrentDir().getParentFile();
-        if (searchDir != null && startParameter.isSearchUpwards()) {
-            for (File file : searchDir.listFiles()) {
-                if (file.isDirectory() && file.getName().equals(MASTER_DIR_NAME)) {
-                    File settingsFile = new File(file, Settings.DEFAULT_SETTINGS_FILE);
-                    if (settingsFile.isFile()) {
-                        return settingsFile;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java
new file mode 100644
index 0000000..52362c6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ModelConfigurationListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.api.internal.GradleInternal;
+
+public interface ModelConfigurationListener {
+    /**
+     * Invoked when the model has been configured. This listener should not do any further configuration.
+     */
+    void onConfigure(GradleInternal model);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategy.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategy.java
deleted file mode 100644
index bca369f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategy.java
+++ /dev/null
@@ -1,39 +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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.initialization.Settings;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class ParentDirSettingsFinderStrategy extends AbstractSettingsFileSearchStrategyTemplate {
-    protected File findBeyondCurrentDir(StartParameter startParameter) {
-        File searchDir = startParameter.getCurrentDir().getParentFile();
-        while (searchDir != null && startParameter.isSearchUpwards()) {
-            File settingsFile = new File(searchDir, Settings.DEFAULT_SETTINGS_FILE);
-            if (settingsFile.isFile()) {
-                return settingsFile;
-            }
-            searchDir = searchDir.getParentFile();
-        }
-        return null;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java
deleted file mode 100644
index c502c6e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ParsedCommandLine.java
+++ /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.initialization;
-
-import org.gradle.util.GUtil;
-
-import java.io.Serializable;
-import java.util.*;
-
-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();
-            for (String optionStr : option.getOptions()) {
-                optionsByString.put(optionStr, parsedOption);
-            }
-        }
-    }
-
-    @Override
-    public String toString() {
-        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) {
-            throw new IllegalArgumentException(String.format("Option '%s' not defined.", option));
-        }
-        return parsedOption;
-    }
-
-    public List<String> getExtraArguments() {
-        return extraArguments;
-    }
-
-    void addExtraValue(String value) {
-        extraArguments.add(value);
-    }
-
-    ParsedCommandLineOption addOption(String optionStr, CommandLineOption option) {
-        ParsedCommandLineOption parsedOption = optionsByString.get(optionStr);
-        presentOptions.addAll(option.getOptions());
-        return parsedOption;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java
deleted file mode 100644
index 570d1c3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ParsedCommandLineOption.java
+++ /dev/null
@@ -1,42 +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.initialization;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ParsedCommandLineOption implements Serializable {
-    private final List<String> values = new ArrayList<String>();
-
-    public String getValue() {
-        if (values.isEmpty()) {
-            throw new IllegalStateException("Option does not have any value.");
-        }
-        if (values.size() > 1) {
-            throw new IllegalStateException("Option has multiple values.");
-        }
-        return values.get(0);
-    }
-
-    public List<String> getValues() {
-        return values;
-    }
-
-    public void addArgument(String argument) {
-        values.add(argument);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
index 6d783ac..320603a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
@@ -22,8 +22,9 @@ import org.gradle.api.internal.project.IProjectRegistry;
 import org.gradle.api.InvalidUserDataException;
 
 import java.io.File;
+import java.io.Serializable;
 
-public class ProjectDirectoryProjectSpec extends AbstractProjectSpec {
+public class ProjectDirectoryProjectSpec extends AbstractProjectSpec implements Serializable {
     private final File dir;
 
     public ProjectDirectoryProjectSpec(File dir) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java
new file mode 100644
index 0000000..6e6b30c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoader.java
@@ -0,0 +1,81 @@
+/*
+ * 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.api.Project;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.plugins.ExtraPropertiesExtension;
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectPropertySettingBuildLoader implements BuildLoader {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ProjectPropertySettingBuildLoader.class);
+
+    private final IGradlePropertiesLoader propertiesLoader;
+    private final BuildLoader buildLoader;
+
+    public ProjectPropertySettingBuildLoader(IGradlePropertiesLoader propertiesLoader, BuildLoader buildLoader) {
+        this.buildLoader = buildLoader;
+        this.propertiesLoader = propertiesLoader;
+    }
+
+    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle) {
+        buildLoader.load(rootProjectDescriptor, gradle);
+        setProjectProperties(gradle.getRootProject());
+    }
+
+    private void setProjectProperties(Project project) {
+        addPropertiesToProject(project);
+        for (Project childProject : project.getChildProjects().values()) {
+            setProjectProperties(childProject);
+        }
+    }
+
+    private void addPropertiesToProject(Project project) {
+        Properties projectProperties = new Properties();
+        File projectPropertiesFile = new File(project.getProjectDir(), Project.GRADLE_PROPERTIES);
+        LOGGER.debug("Looking for project properties from: {}", projectPropertiesFile);
+        if (projectPropertiesFile.isFile()) {
+            projectProperties = GUtil.loadProperties(projectPropertiesFile);
+            LOGGER.debug("Adding project properties (if not overwritten by user properties): {}",
+                    projectProperties.keySet());
+        } else {
+            LOGGER.debug("project property file does not exists. We continue!");
+        }
+        
+        Map<String, String> mergedProperties = propertiesLoader.mergeProperties(new HashMap(projectProperties));
+        ExtraPropertiesExtension extraProperties = new DslObject(project).getExtensions().getExtraProperties();
+        for (Map.Entry<String, String> entry: mergedProperties.entrySet()) {
+            if (project.hasProperty(entry.getKey())) {
+                project.setProperty(entry.getKey(), entry.getValue());    
+            } else {
+                extraProperties.set(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
index 6ea9511..2b3cb50 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
@@ -23,17 +23,18 @@ import java.net.URLClassLoader;
 
 public class PropertiesLoadingSettingsProcessor implements SettingsProcessor {
     private final SettingsProcessor processor;
+    private final IGradlePropertiesLoader propertiesLoader;
 
-    public PropertiesLoadingSettingsProcessor(SettingsProcessor processor) {
+    public PropertiesLoadingSettingsProcessor(SettingsProcessor processor, IGradlePropertiesLoader propertiesLoader) {
         this.processor = processor;
+        this.propertiesLoader = propertiesLoader;
     }
 
     public SettingsInternal process(GradleInternal gradle,
                                     SettingsLocation settingsLocation,
                                     URLClassLoader buildSourceClassLoader,
-                                    StartParameter startParameter,
-                                    IGradlePropertiesLoader propertiesLoader) {
-        propertiesLoader.loadProperties(settingsLocation.getSettingsDir(), startParameter);
-        return processor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter, propertiesLoader);
+                                    StartParameter startParameter) {
+        propertiesLoader.loadProperties(settingsLocation.getSettingsDir());
+        return processor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProvidedInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProvidedInitScriptFinder.java
new file mode 100644
index 0000000..757bce1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProvidedInitScriptFinder.java
@@ -0,0 +1,37 @@
+/*
+ * 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.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Simple finder that "finds" all the init scripts that were explicitly added to the start parameters.
+ */
+public class ProvidedInitScriptFinder implements InitScriptFinder {
+    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
+        List<File> scriptFiles = gradle.getStartParameter().getInitScripts();
+        for (File file : scriptFiles) {
+            scripts.add(new UriScriptSource("initialization script", file));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategy.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategy.java
deleted file mode 100644
index b1456a2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategy.java
+++ /dev/null
@@ -1,42 +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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.initialization.Settings;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class SameLevelDirSettingsFinderStrategy extends AbstractSettingsFileSearchStrategyTemplate {
-    protected File findBeyondCurrentDir(StartParameter startParameter) {
-        File parentDir = startParameter.getCurrentDir().getParentFile();
-        if (parentDir != null && startParameter.isSearchUpwards()) {
-            for (File potentialSameLevelDir : parentDir.listFiles()) {
-                if (potentialSameLevelDir.isDirectory()) {
-                    File settingsFile = new File(potentialSameLevelDir, Settings.DEFAULT_SETTINGS_FILE);
-                    if (settingsFile.isFile()) {
-                        return settingsFile;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
index e8de24b..6656eb5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
@@ -26,6 +26,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Map;
 
 
 /**
@@ -34,24 +36,26 @@ import java.net.URLClassLoader;
 public class ScriptEvaluatingSettingsProcessor implements SettingsProcessor {
     private static Logger logger = LoggerFactory.getLogger(ScriptEvaluatingSettingsProcessor.class);
 
-    private SettingsFactory settingsFactory;
-
-    private ScriptPluginFactory configurerFactory;
+    private final SettingsFactory settingsFactory;
+    private final IGradlePropertiesLoader propertiesLoader;
+    private final ScriptPluginFactory configurerFactory;
 
     public ScriptEvaluatingSettingsProcessor(ScriptPluginFactory configurerFactory,
-                                             SettingsFactory settingsFactory) {
+                                             SettingsFactory settingsFactory,
+                                             IGradlePropertiesLoader propertiesLoader) {
         this.configurerFactory = configurerFactory;
         this.settingsFactory = settingsFactory;
+        this.propertiesLoader = propertiesLoader;
     }
 
     public SettingsInternal process(GradleInternal gradle,
                                     SettingsLocation settingsLocation,
                                     URLClassLoader buildSourceClassLoader,
-                                    StartParameter startParameter,
-                                    IGradlePropertiesLoader propertiesLoader) {
+                                    StartParameter startParameter) {
         Clock settingsProcessingClock = new Clock();
+        Map<String, String> properties = propertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
         SettingsInternal settings = settingsFactory.createSettings(gradle, settingsLocation.getSettingsDir(),
-                settingsLocation.getSettingsScriptSource(), propertiesLoader.getGradleProperties(), startParameter, buildSourceClassLoader);
+                settingsLocation.getSettingsScriptSource(), properties, startParameter, buildSourceClassLoader);
         applySettingsScript(settingsLocation, buildSourceClassLoader, settings);
         logger.debug("Timing: Processing settings took: {}", settingsProcessingClock.getTime());
         return settings;
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
index 9373680..9bd2796 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
@@ -39,7 +39,8 @@ public class SettingsFactory {
                                            Map<String, String> gradleProperties, StartParameter startParameter,
                                            URLClassLoader classloader) {
         DefaultSettings settings = new DefaultSettings(gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter);
-        settings.getAdditionalProperties().putAll(gradleProperties);
+
+        settings.addDynamicProperties(gradleProperties);
         return settings;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
index e02ae98..fd88f4d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
@@ -16,11 +16,10 @@
 
 package org.gradle.initialization;
 
-import org.gradle.api.internal.SettingsInternal;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.StartParameter;
-import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
 
 import java.io.File;
 import java.net.URLClassLoader;
@@ -41,15 +40,22 @@ public class SettingsHandler {
         this.buildSourceBuilder = buildSourceBuilder;
     }
 
-    public SettingsInternal findAndLoadSettings(GradleInternal gradle, IGradlePropertiesLoader gradlePropertiesLoader) {
+    public SettingsInternal findAndLoadSettings(GradleInternal gradle) {
         StartParameter startParameter = gradle.getStartParameter();
-        SettingsInternal settings = findSettingsAndLoadIfAppropriate(gradle, startParameter, gradlePropertiesLoader);
-        if (!startParameter.getDefaultProjectSelector().containsProject(settings.getProjectRegistry())) {
+        SettingsInternal settings = findSettingsAndLoadIfAppropriate(gradle, startParameter);
+
+        File explicitProjectDir = startParameter.getProjectDir();
+        File explicitBuildFile = startParameter.getBuildFile();
+        ProjectSpec spec = explicitBuildFile != null
+                ? new BuildFileProjectSpec(explicitBuildFile)
+                : explicitProjectDir == null ? new DefaultProjectSpec(startParameter.getCurrentDir()) : new ProjectDirectoryProjectSpec(explicitProjectDir);
+
+        if (!spec.containsProject(settings.getProjectRegistry())) {
             // The settings we found did not include the desired default project. Try again with an empty settings file.
 
             StartParameter noSearchParameter = startParameter.newInstance();
-            noSearchParameter.setSettingsScriptSource(new StringScriptSource("empty settings file", ""));
-            settings = findSettingsAndLoadIfAppropriate(gradle, noSearchParameter, gradlePropertiesLoader);
+            noSearchParameter.useEmptySettings();
+            settings = findSettingsAndLoadIfAppropriate(gradle, noSearchParameter);
             if (settings == null) // not using an assert to make sure it is not disabled
             {
                 throw new InternalError("Empty settings file does not contain expected project.");
@@ -69,12 +75,11 @@ public class SettingsHandler {
 
     /**
      * Finds the settings.gradle for the given startParameter, and loads it if contains the project selected by the
-     * startParameter, or if the startParameter explicity specifies a settings script.  If the settings file is not
+     * startParameter, or if the startParameter explicitly specifies a settings script.  If the settings file is not
      * loaded (executed), then a null is returned.
      */
     private SettingsInternal findSettingsAndLoadIfAppropriate(GradleInternal gradle,
-                                                              StartParameter startParameter,
-                                                              IGradlePropertiesLoader gradlePropertiesLoader) {
+                                                              StartParameter startParameter) {
         SettingsLocation settingsLocation = findSettings(startParameter);
 
         // We found the desired settings file, now build the associated buildSrc before loading settings.  This allows
@@ -84,7 +89,7 @@ public class SettingsHandler {
                 BaseSettings.DEFAULT_BUILD_SRC_DIR));
         URLClassLoader buildSourceClassLoader = buildSourceBuilder.buildAndCreateClassLoader(buildSrcStartParameter);
 
-        return loadSettings(gradle, settingsLocation, buildSourceClassLoader, startParameter, gradlePropertiesLoader);
+        return loadSettings(gradle, settingsLocation, buildSourceClassLoader, startParameter);
     }
 
     private SettingsLocation findSettings(StartParameter startParameter) {
@@ -92,10 +97,8 @@ public class SettingsHandler {
     }
 
     private SettingsInternal loadSettings(GradleInternal gradle, SettingsLocation settingsLocation,
-                                          URLClassLoader buildSourceClassLoader, StartParameter startParameter,
-                                          IGradlePropertiesLoader gradlePropertiesLoader) {
-        return settingsProcessor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter,
-                gradlePropertiesLoader);
+                                          URLClassLoader buildSourceClassLoader, StartParameter startParameter) {
+        return settingsProcessor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter);
     }
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsLocation.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsLocation.java
index 70e001a..54de28e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsLocation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsLocation.java
@@ -16,6 +16,8 @@
 package org.gradle.initialization;
 
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
 
 import java.io.File;
 
@@ -23,15 +25,23 @@ public class SettingsLocation {
     private File settingsDir;
     private ScriptSource settingsScriptSource;
 
-    public SettingsLocation(File settingsDir, ScriptSource settingsScriptSource) {
+    public SettingsLocation(File settingsDir, File settingsFile) {
         this.settingsDir = settingsDir;
-        this.settingsScriptSource = settingsScriptSource;
+        this.settingsScriptSource = settingsFile == null
+                ? new StringScriptSource("empty settings script", "")
+                : new UriScriptSource("settings file", settingsFile);
     }
 
+    /**
+     * Returns the settings directory. Never null.
+     */
     public File getSettingsDir() {
         return settingsDir;
     }
 
+    /**
+     * Returns the settings script. Never null.
+     */
     public ScriptSource getSettingsScriptSource() {
         return settingsScriptSource;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
index 44629d0..7ea4f02 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
@@ -28,6 +28,5 @@ public interface SettingsProcessor {
     SettingsInternal process(GradleInternal gradle,
                              SettingsLocation settingsLocation,
                              URLClassLoader buildSourceClassLoader,
-                             StartParameter startParameter,
-                             IGradlePropertiesLoader propertiesLoader);
+                             StartParameter startParameter);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
index 8be4ff4..46e8ec6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
@@ -20,27 +20,16 @@ import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
 
 import java.io.File;
-import java.util.List;
-
-public class UserHomeInitScriptFinder implements InitScriptFinder {
-    public static final String DEFAULT_INIT_SCRIPT_NAME = "init.gradle";
-
-    private final InitScriptFinder finder;
-
-    public UserHomeInitScriptFinder(InitScriptFinder finder) {
-        this.finder = finder;
-    }
-
-    public List<ScriptSource> findScripts(GradleInternal gradle) {
-        List<ScriptSource> scripts = finder.findScripts(gradle);
+import java.util.Collection;
 
+public class UserHomeInitScriptFinder extends DirectoryInitScriptFinder implements InitScriptFinder {
+    public void findScripts(GradleInternal gradle, Collection<ScriptSource> scripts) {
         File userHomeDir = gradle.getStartParameter().getGradleUserHomeDir();
-        File userInitScript = new File(userHomeDir, DEFAULT_INIT_SCRIPT_NAME);
+        File userInitScript = new File(userHomeDir, "init.gradle");
         if (userInitScript.isFile()) {
             scripts.add(new UriScriptSource("initialization script", userInitScript));
         }
-
-        return scripts;
+        findScriptsInDir(new File(userHomeDir, "init.d"), scripts);
     }
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayout.java b/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayout.java
new file mode 100644
index 0000000..0f1031a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayout.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.layout;
+
+import org.gradle.initialization.SettingsLocation;
+
+import java.io.File;
+
+public class BuildLayout extends SettingsLocation {
+    private final File rootDirectory;
+
+    public BuildLayout(File rootDirectory, File settingsDir, File settingsFile) {
+        super(settingsDir, settingsFile);
+        this.rootDirectory = rootDirectory;
+    }
+
+    /**
+     * Returns the root directory of the build, is never null.
+     */
+    public File getRootDirectory() {
+        return rootDirectory;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayoutConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayoutConfiguration.java
new file mode 100644
index 0000000..2612742
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayoutConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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.layout;
+
+import org.gradle.StartParameter;
+
+import java.io.File;
+
+/**
+ * Configuration which affects the (static) layout of a build.
+ */
+public class BuildLayoutConfiguration {
+    private File currentDir;
+    private boolean searchUpwards;
+    private final File settingsFile;
+    private final boolean useEmptySettings;
+
+    public BuildLayoutConfiguration(StartParameter startParameter) {
+        currentDir = startParameter.getCurrentDir();
+        searchUpwards = startParameter.isSearchUpwards();
+        settingsFile = startParameter.getSettingsFile();
+        useEmptySettings = startParameter.isUseEmptySettings();
+    }
+
+    public File getCurrentDir() {
+        return currentDir;
+    }
+
+    public boolean isSearchUpwards() {
+        return searchUpwards;
+    }
+
+    public File getSettingsFile() {
+        return settingsFile;
+    }
+
+    public boolean isUseEmptySettings() {
+        return useEmptySettings;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayoutFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayoutFactory.java
new file mode 100644
index 0000000..ecaf71b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/layout/BuildLayoutFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 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.layout;
+
+import java.io.File;
+
+public class BuildLayoutFactory {
+    /**
+     * Determines the layout of the build, given a current directory and some other configuration.
+     */
+    public BuildLayout getLayoutFor(File currentDir, boolean searchUpwards) {
+        return getLayoutFor(currentDir, searchUpwards ? null : currentDir.getParentFile());
+    }
+
+    /**
+     * Determines the layout of the build, given a current directory and some other configuration.
+     */
+    public BuildLayout getLayoutFor(BuildLayoutConfiguration configuration) {
+        if (configuration.isUseEmptySettings()) {
+            return new BuildLayout(configuration.getCurrentDir(), configuration.getCurrentDir(), null);
+        }
+        if (configuration.getSettingsFile() != null) {
+            return new BuildLayout(configuration.getCurrentDir(), configuration.getCurrentDir(), configuration.getSettingsFile());
+        }
+
+        File currentDir = configuration.getCurrentDir();
+        boolean searchUpwards = configuration.isSearchUpwards();
+        return getLayoutFor(currentDir, searchUpwards ? null : currentDir.getParentFile());
+    }
+
+    BuildLayout getLayoutFor(File currentDir, File stopAt) {
+        File settingsFile = new File(currentDir, "settings.gradle");
+        if (settingsFile.isFile()) {
+            return layout(currentDir, currentDir, settingsFile);
+        }
+        for (File candidate = currentDir.getParentFile(); candidate != null && !candidate.equals(stopAt); candidate = candidate.getParentFile()) {
+            settingsFile = new File(candidate, "settings.gradle");
+            if (settingsFile.isFile()) {
+                return layout(candidate, candidate, settingsFile);
+            }
+            settingsFile = new File(candidate, "master/settings.gradle");
+            if (settingsFile.isFile()) {
+                return layout(candidate, settingsFile.getParentFile(), settingsFile);
+            }
+        }
+        return layout(currentDir, currentDir);
+    }
+
+    private BuildLayout layout(File rootDir, File settingsDir) {
+        return new BuildLayout(rootDir, settingsDir, null);
+    }
+
+    private BuildLayout layout(File rootDir, File settingsDir, File settingsFile) {
+        return new BuildLayout(rootDir, settingsDir, settingsFile);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java b/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
index 412ff7d..3b0a39f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
@@ -17,8 +17,11 @@
 package org.gradle.invocation;
 
 import groovy.lang.Closure;
+import org.gradle.BuildAdapter;
 import org.gradle.BuildListener;
 import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.internal.GradleDistributionLocator;
 import org.gradle.api.internal.GradleInternal;
@@ -27,6 +30,8 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ActionBroadcast;
+import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.GradleVersion;
 import org.gradle.util.MultiParentClassLoader;
@@ -44,6 +49,9 @@ public class DefaultGradle implements GradleInternal {
     private final ListenerManager listenerManager;
     private final ServiceRegistryFactory services;
     private final GradleDistributionLocator distributionLocator;
+    private final ListenerBroadcast<BuildListener> buildListenerBroadcast;
+    private final ListenerBroadcast<ProjectEvaluationListener> projectEvaluationListenerBroadcast;
+    private ActionBroadcast<Project> rootProjectActions = new ActionBroadcast<Project>();
 
     public DefaultGradle(Gradle parent, StartParameter startParameter, ServiceRegistryFactory parentRegistry) {
         this.parent = parent;
@@ -54,6 +62,15 @@ public class DefaultGradle implements GradleInternal {
         taskGraph = services.get(TaskGraphExecuter.class);
         scriptClassLoader = services.get(MultiParentClassLoader.class);
         distributionLocator = services.get(GradleDistributionLocator.class);
+        buildListenerBroadcast = listenerManager.createAnonymousBroadcaster(BuildListener.class);
+        projectEvaluationListenerBroadcast = listenerManager.createAnonymousBroadcaster(ProjectEvaluationListener.class);
+        buildListenerBroadcast.add(new BuildAdapter(){
+            @Override
+            public void projectsLoaded(Gradle gradle) {
+                rootProjectActions.execute(rootProject);
+                rootProjectActions = null;
+            }
+        });
     }
 
     @Override
@@ -82,6 +99,9 @@ public class DefaultGradle implements GradleInternal {
     }
 
     public ProjectInternal getRootProject() {
+        if (rootProject == null) {
+            throw new IllegalStateException("The root project is not yet available for " + this + ".");
+        }
         return rootProject;
     }
 
@@ -89,6 +109,23 @@ public class DefaultGradle implements GradleInternal {
         this.rootProject = rootProject;
     }
 
+    public void rootProject(Action<? super Project> action) {
+        if (rootProjectActions != null) {
+            rootProjectActions.add(action);
+        } else {
+            assert rootProject != null;
+            action.execute(rootProject);
+        }
+    }
+
+    public void allprojects(final Action<? super Project> action) {
+        rootProject(new Action<Project>() {
+            public void execute(Project project) {
+                project.allprojects(action);
+            }
+        });
+    }
+
     public ProjectInternal getDefaultProject() {
         return defaultProject;
     }
@@ -123,31 +160,31 @@ public class DefaultGradle implements GradleInternal {
     }
 
     public void beforeProject(Closure closure) {
-        listenerManager.addListener(ProjectEvaluationListener.class, "beforeEvaluate", closure);
+        projectEvaluationListenerBroadcast.add("beforeEvaluate", closure);
     }
 
     public void afterProject(Closure closure) {
-        listenerManager.addListener(ProjectEvaluationListener.class, "afterEvaluate", closure);
+        projectEvaluationListenerBroadcast.add("afterEvaluate", closure);
     }
 
     public void buildStarted(Closure closure) {
-        listenerManager.addListener(BuildListener.class, "buildStarted", closure);
+        buildListenerBroadcast.add("buildStarted", closure);
     }
 
     public void settingsEvaluated(Closure closure) {
-        listenerManager.addListener(BuildListener.class, "settingsEvaluated", closure);
+        buildListenerBroadcast.add("settingsEvaluated", closure);
     }
 
     public void projectsLoaded(Closure closure) {
-        listenerManager.addListener(BuildListener.class, "projectsLoaded", closure);
+        buildListenerBroadcast.add("projectsLoaded", closure);
     }
 
     public void projectsEvaluated(Closure closure) {
-        listenerManager.addListener(BuildListener.class, "projectsEvaluated", closure);
+        buildListenerBroadcast.add("projectsEvaluated", closure);
     }
 
     public void buildFinished(Closure closure) {
-        listenerManager.addListener(BuildListener.class, "buildFinished", closure);
+        buildListenerBroadcast.add("buildFinished", closure);
     }
 
     public void addListener(Object listener) {
@@ -163,7 +200,7 @@ public class DefaultGradle implements GradleInternal {
     }
 
     public ProjectEvaluationListener getProjectEvaluationBroadcaster() {
-        return listenerManager.getBroadcaster(ProjectEvaluationListener.class);
+        return projectEvaluationListenerBroadcast.getSource();
     }
 
     public void addBuildListener(BuildListener buildListener) {
@@ -171,7 +208,7 @@ public class DefaultGradle implements GradleInternal {
     }
 
     public BuildListener getBuildListenerBroadcaster() {
-        return listenerManager.getBroadcaster(BuildListener.class);
+        return buildListenerBroadcast.getSource();
     }
 
     public Gradle getGradle() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
old mode 100644
new mode 100755
index 210c961..61d146e
--- a/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
@@ -29,7 +29,7 @@ import java.util.concurrent.Executor;
  */
 public class AsyncListenerBroadcast<T> extends ListenerBroadcast<T> {
     public AsyncListenerBroadcast(Class<T> type, final Executor executor) {
-        super(type, new Transformer<StoppableDispatch<MethodInvocation>>() {
+        super(type, new Transformer<StoppableDispatch<MethodInvocation>, StoppableDispatch<MethodInvocation>>() {
             public StoppableDispatch<MethodInvocation> transform(StoppableDispatch<MethodInvocation> original) {
                 return new AsyncDispatch<MethodInvocation>(executor, original);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java b/subprojects/core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java
index cff261b..30e4976 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java
@@ -1,42 +1,42 @@
-/*
- * 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 org.gradle.messaging.dispatch.ContextClassLoaderDispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
-import org.gradle.messaging.dispatch.ReflectionDispatch;
-
-/**
- * Creates a proxy object which sets the context ClassLoader when invoking methods on the target object.
- *
- * @param <T>
- */
-public class ContextClassLoaderProxy<T> {
-    private final ProxyDispatchAdapter<T> adapter;
-
-    /**
-     * Creates a proxy which dispatches to the given target object.
-     */
-    public ContextClassLoaderProxy(Class<T> type, T target, ClassLoader contextClassLoader) {
-        adapter = new ProxyDispatchAdapter<T>(type, new ContextClassLoaderDispatch<MethodInvocation>(new ReflectionDispatch(target), contextClassLoader));
-    }
-
-    public T getSource() {
-        return adapter.getSource();
-    }
+/*
+ * 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 org.gradle.messaging.dispatch.ContextClassLoaderDispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+
+/**
+ * Creates a proxy object which sets the context ClassLoader when invoking methods on the target object.
+ *
+ * @param <T>
+ */
+public class ContextClassLoaderProxy<T> {
+    private final ProxyDispatchAdapter<T> adapter;
+
+    /**
+     * Creates a proxy which dispatches to the given target object.
+     */
+    public ContextClassLoaderProxy(Class<T> type, T target, ClassLoader contextClassLoader) {
+        adapter = new ProxyDispatchAdapter<T>(new ContextClassLoaderDispatch<MethodInvocation>(new ReflectionDispatch(target), contextClassLoader), type);
+    }
+
+    public T getSource() {
+        return adapter.getSource();
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/LazyCreationProxy.java b/subprojects/core/src/main/groovy/org/gradle/listener/LazyCreationProxy.java
new file mode 100644
index 0000000..71e0f84
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/LazyCreationProxy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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 org.gradle.internal.Factory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class LazyCreationProxy<T> {
+    private final T source;
+
+    public LazyCreationProxy(Class<T> type, final Factory<? extends T> factory) {
+        source = type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, new LazyInvocationHandler(factory)));
+    }
+
+    public T getSource() {
+        return source;
+    }
+
+    private static class LazyInvocationHandler implements InvocationHandler {
+        private Object target;
+        private final Factory<?> factory;
+
+        public LazyInvocationHandler(Factory<?> factory) {
+            this.factory = factory;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            if (target == null) {
+                target = factory.create();
+            }
+            try {
+                return method.invoke(target, args);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
index 33861e8..27fe1b9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
@@ -37,18 +37,18 @@ public class ListenerBroadcast<T> implements StoppableDispatch<MethodInvocation>
     private final StoppableDispatch<MethodInvocation> dispatch;
 
     public ListenerBroadcast(Class<T> type) {
-        this(type, new Transformer<StoppableDispatch<MethodInvocation>>() {
+        this(type, new Transformer<StoppableDispatch<MethodInvocation>, StoppableDispatch<MethodInvocation>>() {
             public StoppableDispatch<MethodInvocation> transform(StoppableDispatch<MethodInvocation> original) {
                 return original;
             }
         });
     }
 
-    protected ListenerBroadcast(Class<T> type, Transformer<StoppableDispatch<MethodInvocation>> transformer) {
+    protected ListenerBroadcast(Class<T> type, Transformer<StoppableDispatch<MethodInvocation>, StoppableDispatch<MethodInvocation>> transformer) {
         this.type = type;
         broadcast = new BroadcastDispatch<T>(type);
         dispatch = transformer.transform(broadcast);
-        source = new ProxyDispatchAdapter<T>(type, dispatch);
+        source = new ProxyDispatchAdapter<T>(dispatch, type);
     }
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingConfiguration.java
index 2e6a5f4..b54b5cf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingConfiguration.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingConfiguration.java
@@ -17,8 +17,11 @@ package org.gradle.logging;
 
 import org.gradle.api.logging.LogLevel;
 
-public class LoggingConfiguration {
+import java.io.Serializable;
+
+public class LoggingConfiguration implements Serializable {
     private LogLevel logLevel = LogLevel.LIFECYCLE;
+    private ShowStacktrace showStacktrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
     private boolean colorOutput = true;
 
     public LogLevel getLogLevel() {
@@ -29,11 +32,30 @@ public class LoggingConfiguration {
         this.logLevel = logLevel;
     }
 
+    /**
+     * Returns true if logging output should be displayed in color when Gradle is running in a terminal which supports
+     * color output. The default value is true.
+     *
+     * @return true if logging output should be displayed in color.
+     */
     public boolean isColorOutput() {
         return colorOutput;
     }
 
+    /**
+     * Specifies whether logging output should be displayed in color.
+     *
+     * @param colorOutput true if logging output should be displayed in color.
+     */
     public void setColorOutput(boolean colorOutput) {
         this.colorOutput = colorOutput;
     }
+
+    public ShowStacktrace getShowStacktrace() {
+        return showStacktrace;
+    }
+
+    public void setShowStacktrace(ShowStacktrace showStacktrace) {
+        this.showStacktrace = showStacktrace;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
index 04f5578..9f69bb4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
@@ -17,30 +17,32 @@
 package org.gradle.logging;
 
 import org.gradle.StartParameter;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.project.DefaultServiceRegistry;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.initialization.CommandLineConverter;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
+import org.gradle.internal.nativeplatform.TerminalDetector;
+import org.gradle.internal.nativeplatform.jna.JnaBootPathConfigurer;
+import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.logging.internal.*;
+import org.gradle.logging.internal.slf4j.Slf4jLoggingConfigurer;
 import org.gradle.util.TimeProvider;
 import org.gradle.util.TrueTimeProvider;
 
-import java.io.FileDescriptor;
-
 /**
- * A {@link org.gradle.api.internal.project.ServiceRegistry} implementation which provides the logging services.
+ * A {@link org.gradle.internal.service.ServiceRegistry} implementation which provides the logging services.
  */
 public class LoggingServiceRegistry extends DefaultServiceRegistry {
     private TextStreamOutputEventListener stdoutListener;
     private final boolean detectConsole;
+    private final boolean isEmbedded;
 
     LoggingServiceRegistry() {
-        this(true);
+        this(true, false);
     }
 
-    LoggingServiceRegistry(boolean detectConsole) {
+    LoggingServiceRegistry(boolean detectConsole, boolean isEmbedded) {
         this.detectConsole = detectConsole;
+        this.isEmbedded = isEmbedded;
         stdoutListener = new TextStreamOutputEventListener(get(OutputEventListener.class));
     }
 
@@ -48,21 +50,21 @@ public class LoggingServiceRegistry extends DefaultServiceRegistry {
      * Creates a set of logging services which are suitable to use in a command-line process.
      */
     public static LoggingServiceRegistry newCommandLineProcessLogging() {
-        return new LoggingServiceRegistry(true);
+        return new LoggingServiceRegistry(true, false);
     }
 
     /**
      * Creates a set of logging services which are suitable to use in a child process. Does not attempt to use any terminal trickery.
      */
     public static LoggingServiceRegistry newChildProcessLogging() {
-        return new LoggingServiceRegistry(false);
+        return new LoggingServiceRegistry(false, false);
     }
 
     /**
      * Creates a set of logging services which are suitable to use embedded in another application. Does not attempt to use any terminal trickery.
      */
     public static LoggingServiceRegistry newEmbeddableLogging() {
-        return new LoggingServiceRegistry(false);
+        return new LoggingServiceRegistry(false, true);
     }
 
     protected CommandLineConverter<LoggingConfiguration> createCommandLineConverter() {
@@ -72,9 +74,12 @@ public class LoggingServiceRegistry extends DefaultServiceRegistry {
     protected TimeProvider createTimeProvider() {
         return new TrueTimeProvider();
     }
-    
+
     protected StdOutLoggingSystem createStdOutLoggingSystem() {
-        return new StdOutLoggingSystem(stdoutListener, get(TimeProvider.class));
+        if (isEmbedded) {
+            return new NoOpLoggingSystem();
+        }
+        return new DefaultStdOutLoggingSystem(stdoutListener, get(TimeProvider.class));
     }
 
     protected StyledTextOutputFactory createStyledTextOutputFactory() {
@@ -82,18 +87,28 @@ public class LoggingServiceRegistry extends DefaultServiceRegistry {
     }
 
     protected StdErrLoggingSystem createStdErrLoggingSystem() {
-        return new StdErrLoggingSystem(new TextStreamOutputEventListener(get(OutputEventListener.class)), get(TimeProvider.class));
+        if (isEmbedded) {
+            return new NoOpLoggingSystem();
+        }
+        TextStreamOutputEventListener listener = new TextStreamOutputEventListener(get(OutputEventListener.class));
+        return new DefaultStdErrLoggingSystem(listener, get(TimeProvider.class));
     }
 
     protected ProgressLoggerFactory createProgressLoggerFactory() {
         return new DefaultProgressLoggerFactory(new ProgressLoggingBridge(get(OutputEventListener.class)), get(TimeProvider.class));
     }
-    
+
     protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
         OutputEventRenderer renderer = get(OutputEventRenderer.class);
-        Slf4jLoggingConfigurer slf4jConfigurer = new Slf4jLoggingConfigurer(renderer);
-        LoggingConfigurer compositeConfigurer = new DefaultLoggingConfigurer(renderer, slf4jConfigurer, new JavaUtilLoggingConfigurer());
-        return new DefaultLoggingManagerFactory(compositeConfigurer, renderer, getStdOutLoggingSystem(), getStdErrLoggingSystem());
+        if (!isEmbedded) {
+            //we want to reset and manipulate java logging only if we own the process, e.g. we're *not* embedded
+            DefaultLoggingConfigurer compositeConfigurer = new DefaultLoggingConfigurer(renderer);
+            compositeConfigurer.add(new Slf4jLoggingConfigurer(renderer));
+            compositeConfigurer.add(new JavaUtilLoggingConfigurer());
+            return new DefaultLoggingManagerFactory(compositeConfigurer, renderer, getStdOutLoggingSystem(), getStdErrLoggingSystem());
+        } else {
+            return new EmbeddedLoggingManagerFactory(renderer);
+        }
     }
 
     private LoggingSystem getStdErrLoggingSystem() {
@@ -105,11 +120,13 @@ public class LoggingServiceRegistry extends DefaultServiceRegistry {
     }
 
     protected OutputEventRenderer createOutputEventRenderer() {
-        Spec<FileDescriptor> terminalDetector;
+        TerminalDetector terminalDetector;
         if (detectConsole) {
-            terminalDetector = new TerminalDetector(StartParameter.DEFAULT_GRADLE_USER_HOME);
+            StartParameter startParameter = new StartParameter();
+            JnaBootPathConfigurer jnaConfigurer = new JnaBootPathConfigurer(startParameter.getGradleUserHomeDir());
+            terminalDetector = new TerminalDetectorFactory().create(jnaConfigurer);
         } else {
-            terminalDetector = Specs.satisfyNone();
+            terminalDetector = new NoOpTerminalDetector();
         }
         return new OutputEventRenderer(terminalDetector).addStandardOutputAndError();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/ShowStacktrace.java b/subprojects/core/src/main/groovy/org/gradle/logging/ShowStacktrace.java
new file mode 100644
index 0000000..c6f0c11
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/ShowStacktrace.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.logging;
+
+/**
+ * Specifies the detail to include in stacktraces.
+ */
+public enum ShowStacktrace {
+    INTERNAL_EXCEPTIONS, ALWAYS, ALWAYS_FULL
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java
new file mode 100644
index 0000000..1642023
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutput.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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.logging.internal;
+
+import org.gradle.internal.SystemProperties;
+
+/**
+ * A {@link org.gradle.logging.StyledTextOutput} that breaks text up into lines.
+ */
+public abstract class AbstractLineChoppingStyledTextOutput extends AbstractStyledTextOutput {
+    private final char[] eol;
+    private int seenCharsFromEol;
+
+    protected AbstractLineChoppingStyledTextOutput() {
+        eol = SystemProperties.getLineSeparator().toCharArray();
+    }
+
+    @Override
+    protected final void doAppend(String text) {
+        int max = text.length();
+        int pos = 0;
+        int start = 0;
+        while (pos < max) {
+            if (seenCharsFromEol == eol.length) {
+                doStartLine();
+                seenCharsFromEol = 0;
+            }
+            if (seenCharsFromEol < eol.length && text.charAt(pos) == eol[seenCharsFromEol]) {
+                seenCharsFromEol++;
+                pos++;
+                if (seenCharsFromEol == eol.length) {
+                    doLineText(text.substring(start, pos), true);
+                    doFinishLine();
+                    start = pos;
+                }
+            } else {
+                seenCharsFromEol = 0;
+                pos++;
+            }
+        }
+        if (pos > start) {
+            doLineText(text.substring(start, pos), false);
+        }
+    }
+
+    /**
+     * Called <em>after</em> the end-of-line text has been appended.
+     */
+    protected void doFinishLine() {
+    }
+
+    /**
+     * Called before text is about to be appended to the start of a line.
+     */
+    protected void doStartLine() {
+    }
+
+    /**
+     * Called when text is to be appended.
+     * @param text The text.
+     * @param terminatesLine true if the given text terminates a line (including the end-of-line string).
+     */
+    protected abstract void doLineText(CharSequence text, boolean terminatesLine);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractStyledTextOutput.java
index 2b52c3f..912efa7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractStyledTextOutput.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AbstractStyledTextOutput.java
@@ -16,12 +16,15 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.internal.SystemProperties;
 import org.gradle.logging.StyledTextOutput;
-import org.gradle.util.SystemProperties;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
+/**
+ * Subclasses need to implement {@link #doAppend(String)}, and optionally {@link #doStyleChange(org.gradle.logging.StyledTextOutput.Style)}.
+ */
 public abstract class AbstractStyledTextOutput implements StyledTextOutput, StandardOutputListener {
     private Style style = Style.Normal;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AnsiConsole.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AnsiConsole.java
index 2837a53..2a46a76 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/AnsiConsole.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/AnsiConsole.java
@@ -20,11 +20,10 @@ import org.apache.commons.lang.StringUtils;
 import org.fusesource.jansi.Ansi;
 import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.util.SystemProperties;
+import org.gradle.internal.SystemProperties;
 
 import java.io.Flushable;
 import java.io.IOException;
-import java.util.Iterator;
 
 public class AnsiConsole implements Console {
     private final static String EOL = SystemProperties.getLineSeparator();
@@ -193,7 +192,7 @@ public class AnsiConsole implements Console {
         }
     }
 
-    private class TextAreaImpl extends AbstractStyledTextOutput implements TextArea, Widget {
+    private class TextAreaImpl extends AbstractLineChoppingStyledTextOutput implements TextArea, Widget {
         private final Container container;
         private int width;
         boolean extraEol;
@@ -218,7 +217,7 @@ public class AnsiConsole implements Console {
         }
 
         @Override
-        protected void doAppend(final String text) {
+        protected void doLineText(final CharSequence text, final boolean terminatesLine) {
             if (text.length() == 0) {
                 return;
             }
@@ -226,83 +225,16 @@ public class AnsiConsole implements Console {
                 public void execute(Ansi ansi) {
                     ColorMap.Color color = colorMap.getColourFor(getStyle());
                     color.on(ansi);
-
-                    Iterator<String> tokenizer = new LineSplitter(text);
-                    while (tokenizer.hasNext()) {
-                        String token = tokenizer.next();
-                        if (token.equals(EOL)) {
-                            width = 0;
-                            extraEol = false;
-                        } else {
-                            width += token.length();
-                        }
-                        ansi.a(token);
+                    if (terminatesLine) {
+                        width = 0;
+                        extraEol = false;
+                    } else {
+                        width += text.length();
                     }
-
+                    ansi.a(text.toString());
                     color.off(ansi);
                 }
             });
         }
     }
-
-    private static class LineSplitter implements Iterator<String> {
-        private final CharSequence text;
-        private int start;
-        private int end;
-
-        private LineSplitter(CharSequence text) {
-            this.text = text;
-            findNext();
-        }
-
-        public boolean findNext() {
-            if (end == text.length()) {
-                start = -1;
-                return false;
-            }
-            if (startsWithEol(text, end)) {
-                start = end;
-                end = start + EOL.length();
-                return true;
-            }
-            int pos = end;
-            while (pos < text.length()) {
-                if (startsWithEol(text, pos)) {
-                    start = end;
-                    end = pos;
-                    return true;
-                }
-                pos++;
-            }
-            start = end;
-            end = text.length();
-            return true;
-        }
-
-        private boolean startsWithEol(CharSequence text, int startAt) {
-            if (startAt + EOL.length() > text.length()) {
-                return false;
-            }
-            for (int i = 0; i < EOL.length(); i++) {
-                if (EOL.charAt(i) != text.charAt(startAt + i)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        public boolean hasNext() {
-            return start >= 0;
-        }
-
-        public String next() {
-            CharSequence next = text.subSequence(start, end);
-            findNext();
-            return next.toString();
-        }
-
-        public void remove() {
-            throw new UnsupportedOperationException();
-        }
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/BufferingStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/BufferingStyledTextOutput.java
new file mode 100644
index 0000000..46580f1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/BufferingStyledTextOutput.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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.logging.internal;
+
+import org.gradle.api.Action;
+import org.gradle.logging.StyledTextOutput;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link StyledTextOutput} which buffers the content written to it, for later forwarding to another {@link StyledTextOutput} instance.
+ */
+public class BufferingStyledTextOutput extends AbstractStyledTextOutput {
+    private final List<Action<StyledTextOutput>> events = new ArrayList<Action<StyledTextOutput>>();
+    private boolean hasContent;
+
+    /**
+     * Writes the buffered contents of this output to the given target, and clears the buffer.
+     */
+    public void writeTo(StyledTextOutput output) {
+        for (Action<StyledTextOutput> event : events) {
+            event.execute(output);
+        }
+        events.clear();
+    }
+
+    @Override
+    protected void doStyleChange(final Style style) {
+        if (!events.isEmpty() && (events.get(events.size() - 1) instanceof ChangeStyleAction)) {
+            events.remove(events.size() - 1);
+        }
+        events.add(new ChangeStyleAction(style));
+    }
+
+    @Override
+    protected void doAppend(final String text) {
+        if (text.length() == 0) {
+            return;
+        }
+        hasContent = true;
+        events.add(new Action<StyledTextOutput>() {
+            public void execute(StyledTextOutput styledTextOutput) {
+                styledTextOutput.text(text);
+            }
+        });
+    }
+
+    public boolean getHasContent() {
+        return hasContent;
+    }
+
+    private static class ChangeStyleAction implements Action<StyledTextOutput> {
+        private final Style style;
+
+        public ChangeStyleAction(Style style) {
+            this.style = style;
+        }
+
+        public void execute(StyledTextOutput styledTextOutput) {
+            styledTextOutput.style(style);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultColorMap.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultColorMap.java
index b3681ec..d0a5fb1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultColorMap.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultColorMap.java
@@ -94,11 +94,6 @@ public class DefaultColorMap implements ColorMap {
 
         if (colorSpec != null) {
             if (colorSpec.equalsIgnoreCase(BOLD)) {
-                String terminalProgram = System.getenv("TERM_PROGRAM");
-                if (terminalProgram != null && terminalProgram.equals("iTerm.app")) {
-                    // iTerm displays bold as red (by default), so don't bother
-                    return noDecoration;
-                }
                 return new AttributeColor(INTENSITY_BOLD, INTENSITY_BOLD_OFF);
             }
             if (colorSpec.equalsIgnoreCase("reverse")) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingConfigurer.java
index f06d237..a608b33 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingConfigurer.java
@@ -17,21 +17,29 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 public class DefaultLoggingConfigurer implements LoggingConfigurer {
+    private final static Logger LOGGER = Logging.getLogger(DefaultLoggingConfigurer.class);
     private final List<LoggingConfigurer> configurers = new ArrayList<LoggingConfigurer>();
 
     public DefaultLoggingConfigurer(LoggingConfigurer... configurers) {
         this.configurers.addAll(Arrays.asList(configurers));
     }
 
+    public void add(LoggingConfigurer configurer) {
+        this.configurers.add(configurer);
+    }
+
     public void configure(LogLevel logLevel) {
         for (LoggingConfigurer configurer : configurers) {
             configurer.configure(logLevel);
         }
+        LOGGER.debug("Finished configuring with level: {}, configurers: {}", logLevel, configurers);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java
index fb138bc..f5a2510 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManager.java
@@ -18,9 +18,9 @@ package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
 import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-import org.gradle.messaging.concurrent.Stoppable;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -95,10 +95,6 @@ public class DefaultLoggingManager implements LoggingManagerInternal {
         return stdOutLoggingSystem.level;
     }
 
-    public boolean isStandardOutputCaptureEnabled() {
-        return getStandardOutputCaptureLevel() != null;
-    }
-
     public DefaultLoggingManager captureStandardOutput(LogLevel level) {
         stdOutLoggingSystem.setLevel(level);
         return this;
@@ -109,12 +105,6 @@ public class DefaultLoggingManager implements LoggingManagerInternal {
         return this;
     }
 
-    public DefaultLoggingManager disableStandardOutputCapture() {
-        stdOutLoggingSystem.disable();
-        stdErrLoggingSystem.disable();
-        return this;
-    }
-
     public LogLevel getStandardErrorCaptureLevel() {
         return stdErrLoggingSystem.level;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManagerFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManagerFactory.java
index f09f605..0b62934 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManagerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultLoggingManagerFactory.java
@@ -16,7 +16,7 @@
 
 package org.gradle.logging.internal;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
 public class DefaultLoggingManagerFactory implements Factory<LoggingManagerInternal> {
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java
new file mode 100644
index 0000000..507b01b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java
@@ -0,0 +1,37 @@
+/*
+ * 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.logging.internal;
+
+import org.gradle.util.TimeProvider;
+
+import java.io.PrintStream;
+
+public class DefaultStdErrLoggingSystem extends PrintStreamLoggingSystem implements StdErrLoggingSystem {
+
+    public DefaultStdErrLoggingSystem(OutputEventListener listener, TimeProvider timeProvider) {
+        super(listener, "system.err", timeProvider);
+    }
+
+    @Override
+    protected PrintStream get() {
+        return System.err;
+    }
+
+    @Override
+    protected void set(PrintStream printStream) {
+        System.setErr(printStream);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java
new file mode 100644
index 0000000..089cf3d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java
@@ -0,0 +1,37 @@
+/*
+ * 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.logging.internal;
+
+import org.gradle.util.TimeProvider;
+
+import java.io.PrintStream;
+
+public class DefaultStdOutLoggingSystem extends PrintStreamLoggingSystem implements StdOutLoggingSystem {
+
+    public DefaultStdOutLoggingSystem(OutputEventListener listener, TimeProvider timeProvider) {
+        super(listener, "system.out", timeProvider);
+    }
+
+    @Override
+    protected PrintStream get() {
+        return System.out;
+    }
+
+    @Override
+    protected void set(PrintStream printStream) {
+        System.setOut(printStream);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingManagerFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingManagerFactory.java
new file mode 100644
index 0000000..33f26ca
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingManagerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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.logging.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+
+/**
+ * by Szczepan Faber, created at: 2/14/12
+ */
+public class EmbeddedLoggingManagerFactory implements Factory<LoggingManagerInternal> {
+    private final OutputEventRenderer renderer;
+
+    public EmbeddedLoggingManagerFactory(OutputEventRenderer renderer) {
+        this.renderer = renderer;
+    }
+
+    public LoggingManagerInternal create() {
+        return new DefaultLoggingManager(new LoggingSystemAdapter(renderer),
+                new NoOpLoggingSystem(), new NoOpLoggingSystem(), renderer);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java
new file mode 100644
index 0000000..e791f36
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/EmbeddedLoggingServices.java
@@ -0,0 +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.logging.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+
+/**
+ * by Szczepan Faber, created at: 1/23/12
+ */
+public interface EmbeddedLoggingServices {
+
+    Factory<LoggingManagerInternal> getLoggingManagerFactory();
+
+    OutputEventRenderer getOutputEventRenderer();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java
new file mode 100644
index 0000000..3a7d17a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LinePrefixingStyledTextOutput.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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.logging.internal;
+
+import org.gradle.logging.StyledTextOutput;
+
+/**
+ * A {@link StyledTextOutput} which prefixes each line of text with some fixed prefix. Does not prefix the first line.
+ */
+public class LinePrefixingStyledTextOutput extends AbstractLineChoppingStyledTextOutput {
+    private final StyledTextOutput output;
+    private final CharSequence prefix;
+
+    public LinePrefixingStyledTextOutput(StyledTextOutput output, CharSequence prefix) {
+        this.output = output;
+        this.prefix = prefix;
+    }
+
+    @Override
+    protected void doLineText(CharSequence text, boolean terminatesLine) {
+        output.text(text);
+    }
+
+    @Override
+    protected void doStartLine() {
+        output.text(prefix);
+    }
+
+    @Override
+    protected void doStyleChange(Style style) {
+        output.style(style);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
index 6d77e23..3848666 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingCommandLineConverter.java
@@ -17,15 +17,18 @@ package org.gradle.logging.internal;
 
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
-import org.gradle.CommandLineArgumentException;
 import org.gradle.api.logging.LogLevel;
-import org.gradle.initialization.AbstractCommandLineConverter;
-import org.gradle.initialization.CommandLineParser;
-import org.gradle.initialization.ParsedCommandLine;
+import org.gradle.cli.AbstractCommandLineConverter;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
 import org.gradle.logging.LoggingConfiguration;
+import org.gradle.logging.ShowStacktrace;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 public class LoggingCommandLineConverter extends AbstractCommandLineConverter<LoggingConfiguration> {
     public static final String DEBUG = "d";
@@ -35,13 +38,20 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
     public static final String QUIET = "q";
     public static final String QUIET_LONG = "quiet";
     public static final String NO_COLOR = "no-color";
+    public static final String FULL_STACKTRACE = "S";
+    public static final String FULL_STACKTRACE_LONG = "full-stacktrace";
+    public static final String STACKTRACE = "s";
+    public static final String STACKTRACE_LONG = "stacktrace";
     private final BiMap<String, LogLevel> logLevelMap = HashBiMap.create();
+    private final BiMap<String, ShowStacktrace> showStacktraceMap = HashBiMap.create();
 
     public LoggingCommandLineConverter() {
         logLevelMap.put(QUIET, LogLevel.QUIET);
         logLevelMap.put(INFO, LogLevel.INFO);
         logLevelMap.put(DEBUG, LogLevel.DEBUG);
         logLevelMap.put("", LogLevel.LIFECYCLE);
+        showStacktraceMap.put(FULL_STACKTRACE, ShowStacktrace.ALWAYS_FULL);
+        showStacktraceMap.put(STACKTRACE, ShowStacktrace.ALWAYS);
     }
 
     @Override
@@ -54,9 +64,20 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
         if (commandLine.hasOption(NO_COLOR)) {
             loggingConfiguration.setColorOutput(false);
         }
+        loggingConfiguration.setShowStacktrace(getShowStacktrace(commandLine));
         return loggingConfiguration;
     }
 
+    private ShowStacktrace getShowStacktrace(ParsedCommandLine options) {
+        if (options.hasOption(FULL_STACKTRACE)) {
+            return ShowStacktrace.ALWAYS_FULL;
+        }
+        if (options.hasOption(STACKTRACE)) {
+            return ShowStacktrace.ALWAYS;
+        }
+        return ShowStacktrace.INTERNAL_EXCEPTIONS;
+    }
+
     private LogLevel getLogLevel(ParsedCommandLine options) {
         LogLevel logLevel = LogLevel.LIFECYCLE;
         if (options.hasOption(QUIET)) {
@@ -76,6 +97,8 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
         parser.option(QUIET, QUIET_LONG).hasDescription("Log errors only.");
         parser.option(INFO, INFO_LONG).hasDescription("Set log level to info.");
         parser.option(NO_COLOR).hasDescription("Do not use color in the console output.");
+        parser.option(STACKTRACE, STACKTRACE_LONG).hasDescription("Print out the stacktrace for all exceptions.");
+        parser.option(FULL_STACKTRACE, FULL_STACKTRACE_LONG).hasDescription("Print out the full (very verbose) stacktrace for all exceptions.");
     }
 
     /**
@@ -120,4 +143,44 @@ public class LoggingCommandLineConverter extends AbstractCommandLineConverter<Lo
         return Collections.unmodifiableCollection(logLevelMap.values());
     }
 
+    /**
+     * @return the set of short option strings that are used to configure log levels.
+     */
+    public Set<String> getLogLevelOptions() {
+        Set<String> options = new HashSet<String>(logLevelMap.keySet());
+        options.remove("");
+        return options;
+    }
+
+    /**
+     * This returns the stack trace level object represented by the command line argument
+     *
+     * @param commandLineArgument a single command line argument (with no '-')
+     * @return the corresponding stack trace level or null if it doesn't match any.
+     * @author mhunsicker
+     */
+    public ShowStacktrace getShowStacktrace(String commandLineArgument) {
+        ShowStacktrace showStacktrace = showStacktraceMap.get(commandLineArgument);
+        if (showStacktrace == null) {
+            return null;
+        }
+
+        return showStacktrace;
+    }
+
+    /**
+     * This returns the command line argument that represents the specified stack trace level.
+     *
+     * @param showStacktrace the stack trace level.
+     * @return the command line argument or null if this level cannot be represented on the command line.
+     * @author mhunsicker
+     */
+    public String getShowStacktraceCommandLine(ShowStacktrace showStacktrace) {
+        String commandLine = showStacktraceMap.inverse().get(showStacktrace);
+        if (commandLine == null) {
+            return null;
+        }
+
+        return commandLine;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java
new file mode 100644
index 0000000..373cb99
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/NoOpLoggingSystem.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.logging.internal;
+
+import org.gradle.api.logging.LogLevel;
+
+/**
+ * by Szczepan Faber, created at: 11/21/11
+ */
+public class NoOpLoggingSystem implements StdOutLoggingSystem, StdErrLoggingSystem, LoggingSystem {
+    public Snapshot snapshot() {
+        return dummy();
+    }
+
+    public Snapshot on(LogLevel level) {
+        return dummy();
+    }
+
+    public Snapshot off() {
+        return dummy();
+    }
+
+    public void restore(Snapshot state) {}
+
+    private Snapshot dummy() {
+        return new Snapshot() {};
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEventRenderer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEventRenderer.java
index 9ff5a3e..b9a9541 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEventRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/OutputEventRenderer.java
@@ -17,7 +17,7 @@ package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
-import org.gradle.api.specs.Spec;
+import org.gradle.internal.nativeplatform.TerminalDetector;
 import org.gradle.listener.ListenerBroadcast;
 
 import java.io.FileDescriptor;
@@ -31,12 +31,12 @@ public class OutputEventRenderer implements OutputEventListener, LoggingConfigur
     private final ListenerBroadcast<OutputEventListener> formatters = new ListenerBroadcast<OutputEventListener>(OutputEventListener.class);
     private final ListenerBroadcast<StandardOutputListener> stdoutListeners = new ListenerBroadcast<StandardOutputListener>(StandardOutputListener.class);
     private final ListenerBroadcast<StandardOutputListener> stderrListeners = new ListenerBroadcast<StandardOutputListener>(StandardOutputListener.class);
-    private final Spec<FileDescriptor> terminalDetector;
+    private final TerminalDetector terminalDetector;
     private final Object lock = new Object();
     private final DefaultColorMap colourMap = new DefaultColorMap();
     private LogLevel logLevel = LogLevel.LIFECYCLE;
 
-    public OutputEventRenderer(Spec<FileDescriptor> terminalDetector) {
+    public OutputEventRenderer(TerminalDetector terminalDetector) {
         OutputEventListener stdOutChain = onNonError(new ProgressLogEventGenerator(new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(stdoutListeners.getSource())), false));
         formatters.add(stdOutChain);
         OutputEventListener stdErrChain = onError(new ProgressLogEventGenerator(new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(stderrListeners.getSource())), false));
@@ -51,8 +51,8 @@ public class OutputEventRenderer implements OutputEventListener, LoggingConfigur
     }
 
     public OutputEventRenderer addStandardOutputAndError() {
-        boolean stdOutIsTerminal = terminalDetector.isSatisfiedBy(FileDescriptor.out);
-        boolean stdErrIsTerminal = terminalDetector.isSatisfiedBy(FileDescriptor.err);
+        boolean stdOutIsTerminal = terminalDetector.isTerminal(FileDescriptor.out);
+        boolean stdErrIsTerminal = terminalDetector.isTerminal(FileDescriptor.err);
         if (stdOutIsTerminal) {
             PrintStream outStr = org.fusesource.jansi.AnsiConsole.out();
             Console console = new AnsiConsole(outStr, outStr, colourMap);
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java
index acb2eb8..e05ed85 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/ProgressLogEventGenerator.java
@@ -16,8 +16,8 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.SystemProperties;
 import org.gradle.util.GUtil;
-import org.gradle.util.SystemProperties;
 
 import java.util.LinkedList;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/Slf4jLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/Slf4jLoggingConfigurer.java
deleted file mode 100644
index 97bc3dc..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/Slf4jLoggingConfigurer.java
+++ /dev/null
@@ -1,183 +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.logging.internal;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.PatternLayout;
-import ch.qos.logback.classic.filter.LevelFilter;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.classic.spi.ThrowableProxy;
-import ch.qos.logback.core.AppenderBase;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.filter.Filter;
-import ch.qos.logback.core.spi.FilterReply;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logging;
-import org.gradle.util.UncheckedException;
-import org.slf4j.LoggerFactory;
-
-import java.io.PrintStream;
-
-/**
- * A {@link org.gradle.logging.internal.LoggingConfigurer} implementation which configures SLF4J to route logging
- * events to a {@link org.gradle.logging.internal.OutputEventListener}.
- *
- * @author Hans Dockter
- */
-public class Slf4jLoggingConfigurer implements LoggingConfigurer {
-    private final Appender appender;
-    private LogLevel currentLevel;
-    private final PrintStream defaultStdOut;
-
-    public Slf4jLoggingConfigurer(OutputEventListener outputListener) {
-        defaultStdOut = System.out;
-        appender = new Appender(outputListener);
-    }
-
-    public void configure(LogLevel logLevel) {
-        if (currentLevel == logLevel) {
-            return;
-        }
-
-        try {
-            doConfigure(logLevel);
-        } catch (Throwable e) {
-            doFailsafeConfiguration();
-            throw UncheckedException.asUncheckedException(e);
-        }
-    }
-
-    private void doFailsafeConfiguration() {
-        // Not really failsafe, just less likely to fail
-        final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        lc.reset();
-
-        ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>() {{
-            setContext(lc);
-            setTarget("System.err");
-            setLayout(new PatternLayout() {{
-                setPattern("%msg%n%ex");
-                setContext(lc);
-                start();
-            }});
-            start();
-        }};
-
-        ch.qos.logback.classic.Logger rootLogger = lc.getLogger("ROOT");
-        rootLogger.setLevel(Level.INFO);
-        rootLogger.addAppender(consoleAppender);
-    }
-
-    private void doConfigure(LogLevel logLevel) {
-        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        ch.qos.logback.classic.Logger rootLogger;
-        if (currentLevel == null) {
-            lc.reset();
-            appender.setContext(lc);
-            rootLogger = lc.getLogger("ROOT");
-            rootLogger.addAppender(appender);
-        } else {
-            rootLogger = lc.getLogger("ROOT");
-        }
-
-        currentLevel = logLevel;
-        appender.stop();
-        appender.clearAllFilters();
-
-        switch (logLevel) {
-            case DEBUG:
-                rootLogger.setLevel(Level.DEBUG);
-                break;
-            case INFO:
-                rootLogger.setLevel(Level.INFO);
-                break;
-            case LIFECYCLE:
-                appender.addFilter(new MarkerFilter(Logging.QUIET, Logging.LIFECYCLE));
-                appender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.DENY, FilterReply.NEUTRAL));
-                rootLogger.setLevel(Level.INFO);
-                break;
-            case QUIET:
-                appender.addFilter(new MarkerFilter(Logging.QUIET));
-                appender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.DENY, FilterReply.NEUTRAL));
-                rootLogger.setLevel(Level.INFO);
-                break;
-            case WARN:
-                rootLogger.setLevel(Level.WARN);
-                break;
-            case ERROR:
-                rootLogger.setLevel(Level.ERROR);
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-
-        appender.start();
-    }
-
-    private Filter<ILoggingEvent> createLevelFilter(LoggerContext lc, Level level, FilterReply onMatch,
-                                                    FilterReply onMismatch) {
-        LevelFilter levelFilter = new LevelFilter();
-        levelFilter.setContext(lc);
-        levelFilter.setOnMatch(onMatch);
-        levelFilter.setOnMismatch(onMismatch);
-        levelFilter.setLevel(level);
-        levelFilter.start();
-        return levelFilter;
-    }
-
-    private class Appender extends AppenderBase<ILoggingEvent> {
-        private final OutputEventListener listener;
-
-        private Appender(OutputEventListener listener) {
-            this.listener = listener;
-        }
-
-        @Override
-        protected void append(ILoggingEvent event) {
-            try {
-                ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();
-                Throwable throwable = throwableProxy == null ? null : throwableProxy.getThrowable();
-                String message = event.getFormattedMessage();
-                listener.onOutput(new LogEvent(event.getTimeStamp(), event.getLoggerName(), toLogLevel(event), message, throwable));
-            } catch (Throwable t) {
-                // Give up and try stdout
-                t.printStackTrace(defaultStdOut);
-            }
-        }
-
-        private LogLevel toLogLevel(ILoggingEvent event) {
-            switch (event.getLevel().toInt()) {
-                case Level.DEBUG_INT:
-                    return LogLevel.DEBUG;
-                case Level.INFO_INT:
-                    if (event.getMarker() == Logging.LIFECYCLE) {
-                        return LogLevel.LIFECYCLE;
-                    }
-                    if (event.getMarker() == Logging.QUIET) {
-                        return LogLevel.QUIET;
-                    }
-                    return LogLevel.INFO;
-                case Level.WARN_INT:
-                    return LogLevel.WARN;
-                case Level.ERROR_INT:
-                    return LogLevel.ERROR;
-            }
-            throw new IllegalArgumentException(String.format("Cannot map SLF4j Level %s to a LogLevel", event.getLevel()));
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java
index 43ad966..ed19c67 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdErrLoggingSystem.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -13,24 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.logging.internal;
-
-import org.gradle.util.TimeProvider;
-
-import java.io.PrintStream;
 
-public class StdErrLoggingSystem extends PrintStreamLoggingSystem {
-    public StdErrLoggingSystem(OutputEventListener listener, TimeProvider timeProvider) {
-        super(listener, "system.err", timeProvider);
-    }
-
-    @Override
-    protected PrintStream get() {
-        return System.err;
-    }
+package org.gradle.logging.internal;
 
-    @Override
-    protected void set(PrintStream printStream) {
-        System.setErr(printStream);
-    }
+/**
+ * by Szczepan Faber, created at: 11/21/11
+ */
+public interface StdErrLoggingSystem extends LoggingSystem {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java
index 09d395d..7592ea3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StdOutLoggingSystem.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -13,24 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.logging.internal;
-
-import org.gradle.util.TimeProvider;
-
-import java.io.PrintStream;
 
-public class StdOutLoggingSystem extends PrintStreamLoggingSystem {
-    public StdOutLoggingSystem(OutputEventListener listener, TimeProvider timeProvider) {
-        super(listener, "system.out", timeProvider);
-    }
-
-    @Override
-    protected PrintStream get() {
-        return System.out;
-    }
+package org.gradle.logging.internal;
 
-    @Override
-    protected void set(PrintStream printStream) {
-        System.setOut(printStream);
-    }
+/**
+ * by Szczepan Faber, created at: 11/21/11
+ */
+public interface StdOutLoggingSystem extends LoggingSystem {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java
index 3e2d01a..c7c3a99 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/StyledTextOutputBackedRenderer.java
@@ -16,8 +16,8 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.SystemProperties;
 import org.gradle.logging.StyledTextOutput;
-import org.gradle.util.SystemProperties;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -61,7 +61,7 @@ public class StyledTextOutputBackedRenderer implements OutputEventListener {
         }
     }
 
-    private class OutputEventTextOutputImpl extends AbstractStyledTextOutput {
+    private class OutputEventTextOutputImpl extends AbstractLineChoppingStyledTextOutput {
         private final StyledTextOutput textOutput;
         private boolean atEndOfLine = true;
 
@@ -75,9 +75,9 @@ public class StyledTextOutputBackedRenderer implements OutputEventListener {
         }
 
         @Override
-        protected void doAppend(String text) {
-            atEndOfLine = text.length() >= EOL.length() && text.endsWith(EOL);
+        protected void doLineText(CharSequence text, boolean terminatesLine) {
             textOutput.text(text);
+            atEndOfLine = terminatesLine;
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetector.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetector.java
deleted file mode 100644
index 62fa544..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetector.java
+++ /dev/null
@@ -1,88 +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.logging.internal;
-
-import org.apache.commons.io.IOUtils;
-import org.fusesource.jansi.WindowsAnsiOutputStream;
-import org.gradle.api.GradleException;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.OperatingSystem;
-import org.gradle.util.PosixUtil;
-
-import java.io.*;
-
-public class TerminalDetector implements Spec<FileDescriptor> {
-    public TerminalDetector(File libCacheDir) {
-        // Some hackery to prevent JNA from creating a shared lib in the tmp dir, as it does not clean things up
-        File tmpDir = new File(libCacheDir, "jna");
-        tmpDir.mkdirs();
-        String libName = System.mapLibraryName("jnidispatch");
-        File libFile = new File(tmpDir, libName);
-        if (!libFile.exists()) {
-            String resourceName = "/com/sun/jna/" + OperatingSystem.current().getNativePrefix() + "/" + libName;
-            try {
-                InputStream lib = getClass().getResourceAsStream(resourceName);
-                if (lib == null) {
-                    throw new GradleException(String.format("Could not locate JNA native lib resource '%s'.", resourceName));
-                }
-                try {
-                    FileOutputStream outputStream = new FileOutputStream(libFile);
-                    try {
-                        IOUtils.copy(lib, outputStream);
-                    } finally {
-                        outputStream.close();
-                    }
-                } finally {
-                    lib.close();
-                }
-            } catch (IOException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-//        System.load(libFile.getAbsolutePath());
-        System.setProperty("jna.boot.library.path", tmpDir.getAbsolutePath());
-    }
-
-    public boolean isSatisfiedBy(FileDescriptor element) {
-
-        if (OperatingSystem.current().isWindows()) {
-            // Use Jansi's detection mechanism
-            try {
-                new WindowsAnsiOutputStream(new ByteArrayOutputStream());
-                return true;
-            } catch (IOException ignore) {
-                // Not attached to a console
-                return false;
-            }
-        }
-
-        // Use jna-posix to determine if we're connected to a terminal
-        if (!PosixUtil.current().isatty(element)) {
-            return false;
-        }
-
-        // Dumb terminal doesn't support control codes. Should really be using termcap database.
-        String term = System.getenv("TERM");
-        if (term != null && term.equals("dumb")) {
-            return false;
-        }
-
-        // Assume a terminal
-        return true;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java
new file mode 100755
index 0000000..9897a9d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.logging.internal;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.nativeplatform.NativeIntegrationUnavailableException;
+import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
+import org.gradle.internal.nativeplatform.TerminalDetector;
+import org.gradle.internal.nativeplatform.jna.JnaBootPathConfigurer;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.os.OperatingSystem;
+
+/**
+ * @author: Szczepan Faber, created at: 9/12/11
+ */
+public class TerminalDetectorFactory {
+
+    private static final Logger LOGGER = Logging.getLogger(TerminalDetectorFactory.class);
+
+    public TerminalDetector create(JnaBootPathConfigurer jnaBootPathConfigurer) {
+        try {
+            jnaBootPathConfigurer.configure();
+            return new NativeServices().get(TerminalDetector.class);
+        } catch (NativeIntegrationUnavailableException e) {
+            LOGGER.info("Unable to initialise the native integration for current platform: " + OperatingSystem.current() + ". Details: " + e.getMessage());
+            return new NoOpTerminalDetector();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/SimpleSlf4jLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/SimpleSlf4jLoggingConfigurer.java
new file mode 100644
index 0000000..8cfbeeb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/SimpleSlf4jLoggingConfigurer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.logging.internal.slf4j;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
+import org.gradle.logging.internal.LoggingConfigurer;
+import org.gradle.logging.internal.OutputEventRenderer;
+
+/**
+ * Simple configurer for slf4j, meant to be used in embedded mode,
+ * in 'safe' environment (e.g. own classloader).
+ * <p>
+ * by Szczepan Faber, created at: 1/23/12
+ */
+public class SimpleSlf4jLoggingConfigurer implements LoggingConfigurer {
+
+    public void configure(LogLevel logLevel) {
+        OutputEventRenderer renderer = new OutputEventRenderer(new NoOpTerminalDetector());
+        renderer.addStandardOutputAndError();
+        renderer.configure(logLevel);
+        new Slf4jLoggingConfigurer(renderer).configure(logLevel);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurer.java
new file mode 100644
index 0000000..d2f0769
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurer.java
@@ -0,0 +1,188 @@
+/*
+ * 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.logging.internal.slf4j;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.filter.LevelFilter;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import ch.qos.logback.core.AppenderBase;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.filter.Filter;
+import ch.qos.logback.core.spi.FilterReply;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.UncheckedException;
+import org.gradle.logging.internal.LogEvent;
+import org.gradle.logging.internal.LoggingConfigurer;
+import org.gradle.logging.internal.MarkerFilter;
+import org.gradle.logging.internal.OutputEventListener;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintStream;
+
+/**
+ * A {@link org.gradle.logging.internal.LoggingConfigurer} implementation which configures SLF4J to route logging
+ * events to a {@link org.gradle.logging.internal.OutputEventListener}.
+ *
+ * @author Hans Dockter
+ */
+public class Slf4jLoggingConfigurer implements LoggingConfigurer {
+    private final Appender appender;
+    private LogLevel currentLevel;
+    private final PrintStream defaultStdOut;
+
+    public Slf4jLoggingConfigurer(OutputEventListener outputListener) {
+        defaultStdOut = System.out;
+        appender = new Appender(outputListener);
+    }
+
+    public void configure(LogLevel logLevel) {
+        if (currentLevel == logLevel) {
+            return;
+        }
+        try {
+            doConfigure(logLevel);
+        } catch (Throwable e) {
+            doFailsafeConfiguration();
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private void doFailsafeConfiguration() {
+        // Not really failsafe, just less likely to fail
+        final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+        lc.reset();
+
+        ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>() {{
+            setContext(lc);
+            setTarget("System.err");
+            setLayout(new PatternLayout() {{
+                setPattern("%msg%n%ex");
+                setContext(lc);
+                start();
+            }});
+            start();
+        }};
+
+        ch.qos.logback.classic.Logger rootLogger = lc.getLogger("ROOT");
+        rootLogger.setLevel(Level.INFO);
+        rootLogger.addAppender(consoleAppender);
+    }
+
+    private void doConfigure(LogLevel logLevel) {
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+        ch.qos.logback.classic.Logger rootLogger;
+        if (currentLevel == null) {
+            lc.reset();
+            appender.setContext(lc);
+            rootLogger = lc.getLogger("ROOT");
+            rootLogger.addAppender(appender);
+        } else {
+            rootLogger = lc.getLogger("ROOT");
+        }
+
+        currentLevel = logLevel;
+        appender.stop();
+        appender.clearAllFilters();
+
+        switch (logLevel) {
+            case DEBUG:
+                rootLogger.setLevel(Level.DEBUG);
+                break;
+            case INFO:
+                rootLogger.setLevel(Level.INFO);
+                break;
+            case LIFECYCLE:
+                appender.addFilter(new MarkerFilter(Logging.QUIET, Logging.LIFECYCLE));
+                appender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.DENY, FilterReply.NEUTRAL));
+                rootLogger.setLevel(Level.INFO);
+                break;
+            case QUIET:
+                appender.addFilter(new MarkerFilter(Logging.QUIET));
+                appender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.DENY, FilterReply.NEUTRAL));
+                rootLogger.setLevel(Level.INFO);
+                break;
+            case WARN:
+                rootLogger.setLevel(Level.WARN);
+                break;
+            case ERROR:
+                rootLogger.setLevel(Level.ERROR);
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+
+        lc.getLogger("org.apache.http.wire").setLevel(Level.OFF);
+
+        appender.start();
+    }
+
+    private Filter<ILoggingEvent> createLevelFilter(LoggerContext lc, Level level, FilterReply onMatch,
+                                                    FilterReply onMismatch) {
+        LevelFilter levelFilter = new LevelFilter();
+        levelFilter.setContext(lc);
+        levelFilter.setOnMatch(onMatch);
+        levelFilter.setOnMismatch(onMismatch);
+        levelFilter.setLevel(level);
+        levelFilter.start();
+        return levelFilter;
+    }
+
+    private class Appender extends AppenderBase<ILoggingEvent> {
+        private final OutputEventListener listener;
+
+        private Appender(OutputEventListener listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        protected void append(ILoggingEvent event) {
+            try {
+                ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();
+                Throwable throwable = throwableProxy == null ? null : throwableProxy.getThrowable();
+                String message = event.getFormattedMessage();
+                listener.onOutput(new LogEvent(event.getTimeStamp(), event.getLoggerName(), toLogLevel(event), message, throwable));
+            } catch (Throwable t) {
+                // Give up and try stdout
+                t.printStackTrace(defaultStdOut);
+            }
+        }
+
+        private LogLevel toLogLevel(ILoggingEvent event) {
+            switch (event.getLevel().toInt()) {
+                case Level.DEBUG_INT:
+                    return LogLevel.DEBUG;
+                case Level.INFO_INT:
+                    if (event.getMarker() == Logging.LIFECYCLE) {
+                        return LogLevel.LIFECYCLE;
+                    }
+                    if (event.getMarker() == Logging.QUIET) {
+                        return LogLevel.QUIET;
+                    }
+                    return LogLevel.INFO;
+                case Level.WARN_INT:
+                    return LogLevel.WARN;
+                case Level.ERROR_INT:
+                    return LogLevel.ERROR;
+            }
+            throw new IllegalArgumentException(String.format("Cannot map SLF4j Level %s to a LogLevel", event.getLevel()));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java
index d1b671b..38e10ed 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java
@@ -16,6 +16,7 @@
 
 package org.gradle.messaging.actor;
 
+import org.gradle.internal.concurrent.ThreadSafe;
 import org.gradle.messaging.dispatch.DispatchException;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.dispatch.StoppableDispatch;
@@ -24,15 +25,25 @@ import org.gradle.messaging.dispatch.StoppableDispatch;
  * <p>An {@code Actor} dispatches method calls to a target object in a thread-safe manner. Methods are called either by
  * calling {@link org.gradle.messaging.dispatch.Dispatch#dispatch(Object)} on the actor, or using the proxy object
  * returned by {@link #getProxy(Class)}. Methods are delivered to the target object in the order they are called on the
- * actor, but are delivered to the target object by a single thread. In this way, the target object does not need to
- * perform any synchronisation.</p>
+ * actor, but are delivered to the target object by a single thread at a time. In this way, the target object does not need
+ * to perform any synchronisation.</p>
  *
- * <p>An actor delivers method calls to the target object asynchronously, so that method dispatch does not block waiting
- * for the method call to be delivered.</p>
+ * <p>An actor uses one of two modes to deliver method calls to the target object:</p>
+ *
+ * <ul>
+ * <li>Non-blocking, or asynchronous, so that method dispatch does not block waiting for the method call to be delivered or executed.
+ * In this mode, the method return value or exception is not delivered back to the dispatcher.
+ * </li>
+ *
+ * <li>Blocking, or synchronous, so that method dispatch blocks until the method call has been delivered and executed. In this mode, the
+ * method return value or exception is delivered back to the dispatcher.
+ * </li>
+ *
+ * </ul>
  *
  * <p>All implementations of this interface must be thread-safe.</p>
  */
-public interface Actor extends StoppableDispatch<MethodInvocation> {
+public interface Actor extends StoppableDispatch<MethodInvocation>, ThreadSafe {
     /**
      * Creates a proxy which delivers method calls to the target object.
      *
@@ -42,7 +53,7 @@ public interface Actor extends StoppableDispatch<MethodInvocation> {
     <T> T getProxy(Class<T> type);
 
     /**
-     * Blocks until all method calls have been delivered to the target object. Stops accepting new method calls.
+     * Stops accepting new method calls, and blocks until all method calls have been executed by the target object.
      *
      * @throws DispatchException When there were any failures dispatching method calls to the target object.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java
index 7ab9d8f..73ff0ef 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java
@@ -24,4 +24,12 @@ public interface ActorFactory {
      * @return The actor.
      */
     Actor createActor(Object target);
+
+    /**
+     * Creates a synchronous actor for the given target object.
+     *
+     * @param target The target object.
+     * @return The actor.
+     */
+    Actor createBlockingActor(Object target);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
index 5d91fac..0bfbe9d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
@@ -17,19 +17,24 @@
 package org.gradle.messaging.actor.internal;
 
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ThreadSafe;
 import org.gradle.messaging.actor.Actor;
 import org.gradle.messaging.actor.ActorFactory;
-import org.gradle.messaging.concurrent.CompositeStoppable;
 import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.Stoppable;
 import org.gradle.messaging.concurrent.StoppableExecutor;
 import org.gradle.messaging.dispatch.*;
 
 import java.util.IdentityHashMap;
 import java.util.Map;
 
+/**
+ * A basic {@link ActorFactory} implementation. Currently cannot support creating both a blocking and non-blocking actor for the same target object.
+ */
 public class DefaultActorFactory implements ActorFactory, Stoppable {
-    private final Map<Object, ActorImpl> actors = new IdentityHashMap<Object, ActorImpl>();
+    private final Map<Object, NonBlockingActor> nonBlockingActors = new IdentityHashMap<Object, NonBlockingActor>();
+    private final Map<Object, BlockingActor> blockingActors = new IdentityHashMap<Object, BlockingActor>();
     private final Object lock = new Object();
     private final ExecutorFactory executorFactory;
 
@@ -43,52 +48,107 @@ public class DefaultActorFactory implements ActorFactory, Stoppable {
     public void stop() {
         synchronized (lock) {
             try {
-                new CompositeStoppable(actors.values()).stop();
+                new CompositeStoppable().add(nonBlockingActors.values()).add(blockingActors.values()).stop();
             } finally {
-                actors.clear();
+                nonBlockingActors.clear();
             }
         }
     }
 
     public Actor createActor(Object target) {
-        if (target instanceof ActorImpl) {
-            return (ActorImpl) target;
+        if (target instanceof NonBlockingActor) {
+            return (NonBlockingActor) target;
+        }
+        synchronized (lock) {
+            if (blockingActors.containsKey(target)) {
+                throw new UnsupportedOperationException("Cannot create a non-blocking and blocking actor for the same object. This is not implemented yet.");
+            }
+            NonBlockingActor actor = nonBlockingActors.get(target);
+            if (actor == null) {
+                actor = new NonBlockingActor(target);
+                nonBlockingActors.put(target, actor);
+            }
+            return actor;
         }
+    }
+
+    public Actor createBlockingActor(Object target) {
         synchronized (lock) {
-            ActorImpl actor = actors.get(target);
+            if (nonBlockingActors.containsKey(target)) {
+                throw new UnsupportedOperationException("Cannot create a non-blocking and blocking actor for the same object. This is not implemented yet.");
+            }
+            BlockingActor actor = blockingActors.get(target);
             if (actor == null) {
-                actor = new ActorImpl(target);
-                actors.put(target, actor);
+                actor = new BlockingActor(target);
+                blockingActors.put(target, actor);
             }
             return actor;
         }
     }
 
-    private void stopped(ActorImpl actor) {
+    private void stopped(NonBlockingActor actor) {
+        synchronized (lock) {
+            nonBlockingActors.values().remove(actor);
+        }
+    }
+
+    private void stopped(BlockingActor actor) {
         synchronized (lock) {
-            actors.values().remove(actor);
+            blockingActors.values().remove(actor);
+        }
+    }
+
+    private class BlockingActor implements Actor {
+        private final Dispatch<MethodInvocation> dispatch;
+        private final Object lock = new Object();
+        private boolean stopped;
+
+        public BlockingActor(Object target) {
+            dispatch = new ReflectionDispatch(target);
+        }
+
+        public <T> T getProxy(Class<T> type) {
+            return new ProxyDispatchAdapter<T>(this, type, ThreadSafe.class).getSource();
+        }
+
+        public void stop() throws DispatchException {
+            synchronized (lock) {
+                stopped = true;
+            }
+            stopped(this);
+        }
+
+        public void dispatch(MethodInvocation message) {
+            synchronized (lock) {
+                if (stopped) {
+                    throw new IllegalStateException("This actor has been stopped.");
+                }
+                dispatch.dispatch(message);
+            }
         }
     }
 
-    private class ActorImpl implements Actor {
+    private class NonBlockingActor implements Actor {
         private final StoppableDispatch<MethodInvocation> dispatch;
         private final StoppableExecutor executor;
-        private final ExceptionTrackingListener exceptionListener;
+        private final ExceptionTrackingFailureHandler failureHandler;
 
-        public ActorImpl(Object targetObject) {
+        public NonBlockingActor(Object targetObject) {
             executor = executorFactory.create(String.format("Dispatch %s", targetObject));
-            exceptionListener = new ExceptionTrackingListener(Logging.getLogger(ActorImpl.class));
-            dispatch = new AsyncDispatch<MethodInvocation>(executor, new ExceptionTrackingDispatch<MethodInvocation>(
-                    new ReflectionDispatch(targetObject), exceptionListener));
+            failureHandler = new ExceptionTrackingFailureHandler(Logging.getLogger(NonBlockingActor.class));
+            dispatch = new AsyncDispatch<MethodInvocation>(executor,
+                    new FailureHandlingDispatch<MethodInvocation>(
+                            new ReflectionDispatch(targetObject),
+                            failureHandler));
         }
 
         public <T> T getProxy(Class<T> type) {
-            return new ProxyDispatchAdapter<T>(type, this).getSource();
+            return new ProxyDispatchAdapter<T>(this, type, ThreadSafe.class).getSource();
         }
 
         public void stop() {
             try {
-                new CompositeStoppable(dispatch, executor, exceptionListener).stop();
+                new CompositeStoppable(dispatch, executor, failureHandler).stop();
             } finally {
                 stopped(this);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
old mode 100644
new mode 100755
index e34a78c..bb0db81
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
@@ -1,30 +1,37 @@
-/*
- * 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.concurrent;
-
-/**
- * A {@link Stoppable} object whose stop process can be performed asynchronously.
- */
-public interface AsyncStoppable extends Stoppable {
-    /**
-     * <p>Requests that this stoppable commence a graceful stop. Does not block. You should call {@link
-     * Stoppable#stop} to wait for the stop process to complete.</p>
-     *
-     * <p>Generally, an {@code AsyncStoppable} should continue to complete existing work after this method has returned.
-     * It should, however, stop accepting new work.</p>
-     */
-    void requestStop();
-}
+/*
+ * 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.concurrent;
+
+import org.gradle.internal.Stoppable;
+
+/**
+ * A {@link Stoppable} object whose stop process can be performed asynchronously.
+ */
+public interface AsyncStoppable extends Stoppable {
+    /**
+     * <p>Requests that this stoppable commence a graceful stop. Does not block. You should call {@link
+     * org.gradle.internal.Stoppable#stop} to wait for the stop process to complete.</p>
+     *
+     * <p>Generally, an {@code AsyncStoppable} should continue to complete existing work after this method has returned.
+     * It should, however, stop accepting new work.</p>
+     *
+     * <p>
+     * Requesting stopping does not guarantee the stoppable actually stops.
+     * Requesting stopping means preparing for stopping; stopping accepting new work.
+     * You have to call stop at some point anyway if your intention is to completely stop the stoppable.
+     */
+    void requestStop();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/CompositeStoppable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/CompositeStoppable.java
deleted file mode 100644
index 3b4630b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/CompositeStoppable.java
+++ /dev/null
@@ -1,108 +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.messaging.concurrent;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.util.GUtil;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public class CompositeStoppable implements Stoppable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeStoppable.class);
-    private final List<Stoppable> elements = new CopyOnWriteArrayList<Stoppable>();
-
-    public CompositeStoppable() {
-    }
-
-    public CompositeStoppable(Stoppable... elements) {
-        this(Arrays.asList(elements));
-    }
-
-    public CompositeStoppable(Iterable<? extends Stoppable> elements) {
-        GUtil.addToCollection(this.elements, elements);
-    }
-
-    public CompositeStoppable(Closeable... elements) {
-        for (Closeable element : elements) {
-            add(element);
-        }
-    }
-
-    public CompositeStoppable add(Iterable<? extends Stoppable> elements) {
-        GUtil.addToCollection(this.elements, elements);
-        return this;
-    }
-
-    public CompositeStoppable add(Stoppable stoppable) {
-        elements.add(stoppable);
-        return this;
-    }
-
-    public CompositeStoppable add(Closeable closeable) {
-        elements.add(toStoppable(closeable));
-        return this;
-    }
-
-    public CompositeStoppable addCloseables(Iterable<? extends Closeable> closeables) {
-        for (Closeable closeable : closeables) {
-            add(closeable);
-        }
-        return this;
-    }
-
-    private Stoppable toStoppable(final Closeable closeable) {
-        return new Stoppable() {
-            public void stop() {
-                try {
-                    closeable.close();
-                } catch (IOException e) {
-                    throw new UncheckedIOException(e);
-                }
-            }
-        };
-    }
-
-    public void stop() {
-        Throwable failure = null;
-        try {
-            for (Stoppable element : elements) {
-                try {
-                    element.stop();
-                } catch (Throwable throwable) {
-                    if (failure == null) {
-                        failure = throwable;
-                    } else {
-                        LOGGER.error(String.format("Could not stop %s.", element), throwable);
-                    }
-                }
-            }
-        } finally {
-            elements.clear();
-        }
-
-        if (failure != null) {
-            throw UncheckedException.asUncheckedException(failure);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
index ea6bd24..f9c5ddc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
@@ -17,8 +17,11 @@
 package org.gradle.messaging.concurrent;
 
 import org.gradle.api.logging.Logging;
-import org.gradle.messaging.dispatch.ExceptionTrackingListener;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.dispatch.DispatchException;
+import org.gradle.messaging.dispatch.ExceptionTrackingFailureHandler;
 
 import java.util.Set;
 import java.util.concurrent.*;
@@ -47,12 +50,12 @@ public class DefaultExecutorFactory implements ExecutorFactory, Stoppable {
 
     private class StoppableExecutorImpl implements StoppableExecutor {
         private final ExecutorService executor;
-        private final ExceptionTrackingListener exceptionListener;
+        private final ExceptionTrackingFailureHandler failureHandler;
         private final ThreadLocal<Runnable> executing = new ThreadLocal<Runnable>();
 
         public StoppableExecutorImpl(ExecutorService executor) {
             this.executor = executor;
-            exceptionListener = new ExceptionTrackingListener(Logging.getLogger(StoppableExecutorImpl.class));
+            failureHandler = new ExceptionTrackingFailureHandler(Logging.getLogger(StoppableExecutorImpl.class));
         }
 
         public void execute(final Runnable command) {
@@ -62,7 +65,7 @@ public class DefaultExecutorFactory implements ExecutorFactory, Stoppable {
                     try {
                         command.run();
                     } catch (Throwable throwable) {
-                        exceptionListener.execute(throwable);
+                        failureHandler.dispatchFailed(command, throwable);
                     } finally {
                         executing.set(null);
                     }
@@ -92,7 +95,11 @@ public class DefaultExecutorFactory implements ExecutorFactory, Stoppable {
                 } catch (InterruptedException e) {
                     throw new UncheckedException(e);
                 }
-                exceptionListener.stop();
+                try {
+                    failureHandler.stop();
+                } catch (DispatchException e) {
+                    throw UncheckedException.throwAsUncheckedException(e.getCause());
+                }
             } finally {
                 executors.remove(this);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/Stoppable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/Stoppable.java
deleted file mode 100644
index e1c9f59..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/Stoppable.java
+++ /dev/null
@@ -1,28 +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.messaging.concurrent;
-
-/**
- * Represents an object which performs concurrent activity.
- */
-public interface Stoppable {
-    /**
-     * <p>Requests a graceful stop of this object. Blocks until all concurrent activity has been completed.</p>
-     *
-     * <p>If this object has already been stopped, this method does nothing.</p>
-     */
-    void stop();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Addressable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Addressable.java
deleted file mode 100644
index 373a3e6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Addressable.java
+++ /dev/null
@@ -1,24 +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.messaging.dispatch;
-
-import java.net.URI;
-
-public interface Addressable {
-    URI getLocalAddress();
-
-    URI getRemoteAddress();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
old mode 100644
new mode 100755
index 53cf82b..53e75ce
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
@@ -1,187 +1,193 @@
-/*
- * 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.dispatch;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.util.UncheckedException;
-
-import java.util.LinkedList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * <p>A {@link org.gradle.messaging.dispatch.Dispatch} implementation which delivers messages asynchronously. Calls to
- * {@link #dispatch} queue the message. Worker threads delivers the messages in the order they have been received to one
- * of a pool of delegate {@link org.gradle.messaging.dispatch.Dispatch} instances.</p>
- */
-public class AsyncDispatch<T> implements StoppableDispatch<T>, AsyncStoppable {
-    private enum State {
-        Init, Stopped
-    }
-
-    private static final int MAX_QUEUE_SIZE = 200;
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private final LinkedList<T> queue = new LinkedList<T>();
-    private final Executor executor;
-    private final int maxQueueSize;
-    private int dispatchers;
-    private State state;
-
-    public AsyncDispatch(Executor executor) {
-        this(executor, null, MAX_QUEUE_SIZE);
-    }
-
-    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch) {
-        this(executor, dispatch, MAX_QUEUE_SIZE);
-    }
-
-    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch, int maxQueueSize) {
-        this.executor = executor;
-        this.maxQueueSize = maxQueueSize;
-        state = State.Init;
-        if (dispatch != null) {
-            dispatchTo(dispatch);
-        }
-    }
-
-    public void dispatchTo(final Dispatch<? super T> dispatch) {
-        onDispatchThreadStart();
-        executor.execute(new Runnable() {
-            public void run() {
-                try {
-                    dispatchMessages(dispatch);
-                } finally {
-                    onDispatchThreadExit();
-                }
-            }
-        });
-    }
-
-    private void onDispatchThreadStart() {
-        lock.lock();
-        try {
-            if (state != State.Init) {
-                throw new IllegalStateException("This dispatch has been stopped.");
-            }
-            dispatchers++;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void onDispatchThreadExit() {
-        lock.lock();
-        try {
-            dispatchers--;
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void setState(State state) {
-        this.state = state;
-        condition.signalAll();
-    }
-
-    private void dispatchMessages(Dispatch<? super T> dispatch) {
-        while (true) {
-            T message = null;
-            lock.lock();
-            try {
-                while (state != State.Stopped && queue.isEmpty()) {
-                    try {
-                        condition.await();
-                    } catch (InterruptedException e) {
-                        throw new UncheckedException(e);
-                    }
-                }
-                if (!queue.isEmpty()) {
-                    message = queue.remove();
-                    condition.signalAll();
-                }
-            } finally {
-                lock.unlock();
-            }
-
-            if (message == null) {
-                // Have been stopped and nothing to deliver
-                return;
-            }
-
-            dispatch.dispatch(message);
-        }
-    }
-
-    public void dispatch(final T message) {
-        lock.lock();
-        try {
-            while (state != State.Stopped && queue.size() >= maxQueueSize) {
-                try {
-                    condition.await();
-                } catch (InterruptedException e) {
-                    throw new UncheckedException(e);
-                }
-            }
-            if (state == State.Stopped) {
-                throw new IllegalStateException("This message dispatch has been stopped.");
-            }
-            queue.add(message);
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Commences a shutdown of this dispatch.
-     */
-    public void requestStop() {
-        lock.lock();
-        try {
-            doRequestStop();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void doRequestStop() {
-        setState(State.Stopped);
-    }
-
-    public void stop() {
-        lock.lock();
-        try {
-            setState(State.Stopped);
-            while (dispatchers > 0) {
-                condition.await();
-            }
-
-            if (!queue.isEmpty()) {
-                throw new IllegalStateException(
-                        "Cannot wait for messages to be dispatched, as there are no dispatch threads running.");
-            }
-        } catch (InterruptedException e) {
-            throw new UncheckedException(e);
-        } finally {
-            lock.unlock();
-        }
-    }
-}
+/*
+ * 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.dispatch;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>A {@link org.gradle.messaging.dispatch.Dispatch} implementation which delivers messages asynchronously. Calls to
+ * {@link #dispatch} queue the message. Worker threads deliver the messages in the order they have been received to one
+ * of a pool of delegate {@link org.gradle.messaging.dispatch.Dispatch} instances.</p>
+ */
+public class AsyncDispatch<T> implements StoppableDispatch<T>, AsyncStoppable {
+    private enum State {
+        Init, Stopped
+    }
+
+    private static final int MAX_QUEUE_SIZE = 200;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final LinkedList<T> queue = new LinkedList<T>();
+    private final Executor executor;
+    private final int maxQueueSize;
+    private int dispatchers;
+    private State state;
+
+    public AsyncDispatch(Executor executor) {
+        this(executor, null, MAX_QUEUE_SIZE);
+    }
+
+    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch) {
+        this(executor, dispatch, MAX_QUEUE_SIZE);
+    }
+
+    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch, int maxQueueSize) {
+        this.executor = executor;
+        this.maxQueueSize = maxQueueSize;
+        state = State.Init;
+        if (dispatch != null) {
+            dispatchTo(dispatch);
+        }
+    }
+
+    /**
+     * Starts dispatching messages to the given handler. The handler does not need to be thread-safe.
+     */
+    public void dispatchTo(final Dispatch<? super T> dispatch) {
+        onDispatchThreadStart();
+        executor.execute(new Runnable() {
+            public void run() {
+                try {
+                    dispatchMessages(dispatch);
+                } finally {
+                    onDispatchThreadExit();
+                }
+            }
+        });
+    }
+
+    private void onDispatchThreadStart() {
+        lock.lock();
+        try {
+            if (state != State.Init) {
+                throw new IllegalStateException("This dispatch has been stopped.");
+            }
+            dispatchers++;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onDispatchThreadExit() {
+        lock.lock();
+        try {
+            dispatchers--;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        condition.signalAll();
+    }
+
+    private void dispatchMessages(Dispatch<? super T> dispatch) {
+        while (true) {
+            T message = null;
+            lock.lock();
+            try {
+                while (state != State.Stopped && queue.isEmpty()) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw new UncheckedException(e);
+                    }
+                }
+                if (!queue.isEmpty()) {
+                    message = queue.remove();
+                    condition.signalAll();
+                }
+            } finally {
+                lock.unlock();
+            }
+
+            if (message == null) {
+                // Have been stopped and nothing to deliver
+                return;
+            }
+
+            dispatch.dispatch(message);
+        }
+    }
+
+    public void dispatch(final T message) {
+        lock.lock();
+        try {
+            while (state != State.Stopped && queue.size() >= maxQueueSize) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw new UncheckedException(e);
+                }
+            }
+            if (state == State.Stopped) {
+                throw new IllegalStateException("Cannot dispatch message, as this message dispatch has been stopped. Message: " + message);
+            }
+            queue.add(message);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Commences a shutdown of this dispatch.
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        setState(State.Stopped);
+    }
+
+    /**
+     * Stops accepting new messages, and blocks until all queued messages have been dispatched.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            setState(State.Stopped);
+            while (dispatchers > 0) {
+                condition.await();
+            }
+
+            if (!queue.isEmpty()) {
+                throw new IllegalStateException(
+                        "Cannot wait for messages to be dispatched, as there are no dispatch threads running.");
+            }
+        } catch (InterruptedException e) {
+            throw new UncheckedException(e);
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
index c48b2c7..5ee35d2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
@@ -1,141 +1,204 @@
-/*
- * 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.dispatch;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.util.UncheckedException;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * <p>Receives messages asynchronously. One or more {@link Receive} instances can use used as a source of messages.
- * Messages are sent to a {@link Dispatch} </p>
- */
-public class AsyncReceive<T> implements AsyncStoppable {
-    private enum State {
-        Init, Stopping, Stopped
-    }
-
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private final Executor executor;
-    private final Dispatch<? super T> dispatch;
-    private int receivers;
-    private State state = State.Init;
-
-    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch) {
-        this.executor = executor;
-        this.dispatch = dispatch;
-    }
-
-    public void receiveFrom(final Receive<? extends T> receive) {
-        onReceiveThreadStart();
-        executor.execute(new Runnable() {
-            public void run() {
-                try {
-                    receiveMessages(receive);
-                } finally {
-                    onReceiveThreadExit();
-                }
-            }
-        });
-    }
-
-    private void onReceiveThreadStart() {
-        lock.lock();
-        try {
-            if (state != State.Init) {
-                throw new IllegalStateException("This receiver has been stopped.");
-            }
-            receivers++;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void onReceiveThreadExit() {
-        lock.lock();
-        try {
-            receivers--;
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void receiveMessages(Receive<? extends T> receive) {
-        while (true) {
-            T message = receive.receive();
-            if (message == null) {
-                return;
-            }
-
-            dispatch.dispatch(message);
-
-            lock.lock();
-            try {
-                if (state != State.Init) {
-                    return;
-                }
-            } finally {
-                lock.unlock();
-            }
-        }
-    }
-
-    private void setState(State state) {
-        this.state = state;
-        condition.signalAll();
-    }
-
-    public void requestStop() {
-        lock.lock();
-        try {
-            doRequestStop();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void doRequestStop() {
-        if (receivers > 0) {
-            setState(State.Stopping);
-        } else {
-            setState(State.Stopped);
-        }
-    }
-
-    public void stop() {
-        lock.lock();
-        try {
-            doRequestStop();
-
-            while (receivers > 0) {
-                condition.await();
-            }
-
-            setState(State.Stopped);
-        } catch (InterruptedException e) {
-            throw new UncheckedException(e);
-        } finally {
-            lock.unlock();
-        }
-    }
-}
+/*
+ * 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.dispatch;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>Receives messages asynchronously. One or more {@link Receive} instances can use used as a source of messages. Messages are sent to a {@link Dispatch} </p>
+ * 
+ * <p>It is also possible to specify an <code>onReceiversExhausted</code> Runnable callback that will be run when all of the given receivers
+ * have been exhausted of messages. However, the current implementation is flawed in that this may erroneously fire if the first receiver
+ * is exhausted before the second starts.
+ */
+public class AsyncReceive<T> implements AsyncStoppable {
+    private enum State {
+        Init, Stopping, Stopped
+    }
+
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final Executor executor;
+    private final List<Dispatch<? super T>> dispatches = new ArrayList<Dispatch<? super T>>();
+    private final Runnable onReceiversExhausted;
+    private int receivers;
+    private State state = State.Init;
+
+    public AsyncReceive(Executor executor) {
+        this(executor, (Runnable)null);
+    }
+
+    public AsyncReceive(Executor executor, Runnable onReceiversExhausted) {
+        this.executor = executor;
+        this.onReceiversExhausted = onReceiversExhausted;
+    }
+
+    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch) {
+        this(executor, dispatch, null);
+    }
+
+    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch, Runnable onReceiversExhausted) {
+        this(executor, onReceiversExhausted);
+        dispatchTo(dispatch);
+    }
+
+    /**
+     * Starts dispatching to the given dispatch. The dispatch does not need be thread-safe.
+     */
+    public void dispatchTo(final Dispatch<? super T> dispatch) {
+        lock.lock();
+        try {
+            dispatches.add(dispatch);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Starts receiving from the given receive. The receive does not need to be thread-safe.
+     */
+    public void receiveFrom(final Receive<? extends T> receive) {
+        onReceiveThreadStart();
+        executor.execute(new Runnable() {
+            public void run() {
+                try {
+                    receiveMessages(receive);
+                } finally {
+                    onReceiveThreadExit();
+                }
+            }
+        });
+    }
+
+    private void onReceiveThreadStart() {
+        lock.lock();
+        try {
+            if (state != State.Init) {
+                throw new IllegalStateException("This receiver has been stopped.");
+            }
+            receivers++;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onReceiveThreadExit() {
+        lock.lock();
+        try {
+            receivers--;
+            if (receivers == 0 && onReceiversExhausted != null) {
+                onReceiversExhausted.run();
+            }
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void receiveMessages(Receive<? extends T> receive) {
+        while (true) {
+            Dispatch<? super T> dispatch;
+            lock.lock();
+            try {
+                while (dispatches.isEmpty() && state == State.Init) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+                if (state != State.Init) {
+                    return;
+                }
+                dispatch = dispatches.remove(0);
+            } finally {
+                lock.unlock();
+            }
+
+            try {
+                T message = receive.receive();
+                if (message == null) {
+                    return;
+                }
+
+                dispatch.dispatch(message);
+            } finally {
+                lock.lock();
+                try {
+                    dispatches.add(dispatch);
+                    condition.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        condition.signalAll();
+    }
+
+    /**
+     * Stops receiving new messages.
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        if (receivers > 0) {
+            setState(State.Stopping);
+        } else {
+            setState(State.Stopped);
+        }
+    }
+
+    /**
+     * Stops receiving new messages. Blocks until all queued messages have been delivered.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            doRequestStop();
+
+            while (receivers > 0) {
+                condition.await();
+            }
+
+            setState(State.Stopped);
+        } catch (InterruptedException e) {
+            throw new UncheckedException(e);
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
old mode 100644
new mode 100755
index 6574e07..d85b5e5
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
@@ -1,139 +1,139 @@
-/*
- * 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.dispatch;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.listener.ListenerNotificationException;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class BroadcastDispatch<T> implements StoppableDispatch<MethodInvocation> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastDispatch.class);
-    private final Class<T> type;
-    private final Map<Object, Dispatch<MethodInvocation>> handlers
-            = new LinkedHashMap<Object, Dispatch<MethodInvocation>>();
-
-    public BroadcastDispatch(Class<T> type) {
-        this.type = type;
-    }
-
-    public Class<T> getType() {
-        return type;
-    }
-
-    public void add(Dispatch<MethodInvocation> dispatch) {
-        handlers.put(dispatch, dispatch);
-    }
-
-    public void add(T listener) {
-        handlers.put(listener, new ReflectionDispatch(listener));
-    }
-
-    public void add(String methodName, Closure closure) {
-        assertIsMethod(methodName);
-        handlers.put(closure, new ClosureInvocationHandler(methodName, closure));
-    }
-
-    public void add(String methodName, Action<?> action) {
-        assertIsMethod(methodName);
-        handlers.put(action, new ActionInvocationHandler(methodName, action));
-    }
-
-    private void assertIsMethod(String methodName) {
-        for (Method method : type.getMethods()) {
-            if (method.getName().equals(methodName)) {
-                return;
-            }
-        }
-        throw new IllegalArgumentException(String.format("Method %s() not found for listener type %s.", methodName,
-                type.getSimpleName()));
-    }
-
-    public void remove(Object listener) {
-        handlers.remove(listener);
-    }
-
-    private String getErrorMessage() {
-        String typeDescription = type.getSimpleName().replaceAll("(\\p{Upper})", " $1").trim().toLowerCase();
-        return String.format("Failed to notify %s.", typeDescription);
-    }
-
-    public void dispatch(MethodInvocation invocation) {
-        try {
-            ExceptionTrackingListener tracker = new ExceptionTrackingListener(LOGGER);
-            for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
-                try {
-                    handler.dispatch(invocation);
-                } catch (UncheckedException e) {
-                    tracker.execute(e.getCause());
-                } catch (Throwable t) {
-                    tracker.execute(t);
-                }
-            }
-            tracker.stop();
-        } catch (Throwable t) {
-            throw new ListenerNotificationException(getErrorMessage(), t);
-        }
-    }
-
-    public void stop() {
-    }
-
-    private class ClosureInvocationHandler implements Dispatch<MethodInvocation> {
-        private final String methodName;
-        private final Closure closure;
-
-        public ClosureInvocationHandler(String methodName, Closure closure) {
-            this.methodName = methodName;
-            this.closure = closure;
-        }
-
-        public void dispatch(MethodInvocation message) {
-            if (message.getMethod().getName().equals(methodName)) {
-                Object[] parameters = message.getArguments();
-                if (closure.getMaximumNumberOfParameters() < parameters.length) {
-                    parameters = Arrays.asList(parameters).subList(0, closure.getMaximumNumberOfParameters()).toArray();
-                }
-                closure.call(parameters);
-            }
-        }
-    }
-
-    private class ActionInvocationHandler implements Dispatch<MethodInvocation> {
-        private final String methodName;
-        private final Action action;
-
-        public ActionInvocationHandler(String methodName, Action action) {
-            this.methodName = methodName;
-            this.action = action;
-        }
-
-        public void dispatch(MethodInvocation message) {
-            if (message.getMethod().getName().equals(methodName)) {
-                action.execute(message.getArguments()[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.messaging.dispatch;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.internal.UncheckedException;
+import org.gradle.listener.ListenerNotificationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class BroadcastDispatch<T> implements StoppableDispatch<MethodInvocation> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastDispatch.class);
+    private final Class<T> type;
+    private final Map<Object, Dispatch<MethodInvocation>> handlers
+            = new LinkedHashMap<Object, Dispatch<MethodInvocation>>();
+
+    public BroadcastDispatch(Class<T> type) {
+        this.type = type;
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    public void add(Dispatch<MethodInvocation> dispatch) {
+        handlers.put(dispatch, dispatch);
+    }
+
+    public void add(T listener) {
+        handlers.put(listener, new ReflectionDispatch(listener));
+    }
+
+    public void add(String methodName, Closure closure) {
+        assertIsMethod(methodName);
+        handlers.put(closure, new ClosureInvocationHandler(methodName, closure));
+    }
+
+    public void add(String methodName, Action<?> action) {
+        assertIsMethod(methodName);
+        handlers.put(action, new ActionInvocationHandler(methodName, action));
+    }
+
+    private void assertIsMethod(String methodName) {
+        for (Method method : type.getMethods()) {
+            if (method.getName().equals(methodName)) {
+                return;
+            }
+        }
+        throw new IllegalArgumentException(String.format("Method %s() not found for listener type %s.", methodName,
+                type.getSimpleName()));
+    }
+
+    public void remove(Object listener) {
+        handlers.remove(listener);
+    }
+
+    private String getErrorMessage() {
+        String typeDescription = type.getSimpleName().replaceAll("(\\p{Upper})", " $1").trim().toLowerCase();
+        return String.format("Failed to notify %s.", typeDescription);
+    }
+
+    public void dispatch(MethodInvocation invocation) {
+        try {
+            ExceptionTrackingFailureHandler tracker = new ExceptionTrackingFailureHandler(LOGGER);
+            for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
+                try {
+                    handler.dispatch(invocation);
+                } catch (UncheckedException e) {
+                    tracker.dispatchFailed(invocation, e.getCause());
+                } catch (Throwable t) {
+                    tracker.dispatchFailed(invocation, t);
+                }
+            }
+            tracker.stop();
+        } catch (DispatchException t) {
+            throw new ListenerNotificationException(getErrorMessage(), t.getCause());
+        }
+    }
+
+    public void stop() {
+    }
+
+    private class ClosureInvocationHandler implements Dispatch<MethodInvocation> {
+        private final String methodName;
+        private final Closure closure;
+
+        public ClosureInvocationHandler(String methodName, Closure closure) {
+            this.methodName = methodName;
+            this.closure = closure;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            if (message.getMethod().getName().equals(methodName)) {
+                Object[] parameters = message.getArguments();
+                if (closure.getMaximumNumberOfParameters() < parameters.length) {
+                    parameters = Arrays.asList(parameters).subList(0, closure.getMaximumNumberOfParameters()).toArray();
+                }
+                closure.call(parameters);
+            }
+        }
+    }
+
+    private class ActionInvocationHandler implements Dispatch<MethodInvocation> {
+        private final String methodName;
+        private final Action action;
+
+        public ActionInvocationHandler(String methodName, Action action) {
+            this.methodName = methodName;
+            this.action = action;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            if (message.getMethod().getName().equals(methodName)) {
+                action.execute(message.getArguments()[0]);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DelayedReceive.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DelayedReceive.java
new file mode 100644
index 0000000..6882305
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DelayedReceive.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 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.dispatch;
+
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.TimeProvider;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.PriorityQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DelayedReceive<T> implements Stoppable, Receive<T> {
+    private final TimeProvider timeProvider;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final PriorityQueue<DelayedMessage> queue = new PriorityQueue<DelayedMessage>();
+    private boolean stopping;
+
+    public DelayedReceive(TimeProvider timeProvider) {
+        this.timeProvider = timeProvider;
+    }
+
+    public T receive() {
+        lock.lock();
+        try {
+            while (true) {
+                DelayedMessage message = queue.peek();
+                if (message == null && stopping) {
+                    return null;
+                }
+                if (message == null) {
+                    condition.await();
+                    continue;
+                }
+
+                long now = timeProvider.getCurrentTime();
+                if (message.dispatchTime > now) {
+                    condition.awaitUntil(new Date(message.dispatchTime));
+                } else {
+                    queue.poll();
+                    if (queue.isEmpty()) {
+                        condition.signalAll();
+                    }
+                    return message.message;
+                }
+            }
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Dispatches the given message after the given delay.
+     */
+    public void dispatchLater(T message, int delayValue, TimeUnit delayUnits) {
+        long dispatchTime = timeProvider.getCurrentTime() + delayUnits.toMillis(delayValue);
+        lock.lock();
+        try {
+            if (stopping) {
+                throw new IllegalStateException("This dispatch has been stopped.");
+            }
+            queue.add(new DelayedMessage(dispatchTime, message));
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Removes one instance of the given message from the queue.
+     *
+     * @return true if removed, false if not. If false is returned, the message may be currently being dispatched.
+     */
+    public boolean remove(T message) {
+        lock.lock();
+        try {
+            Iterator<DelayedMessage> iterator = queue.iterator();
+            while (iterator.hasNext()) {
+                DelayedMessage next = iterator.next();
+                if (next.message.equals(message)) {
+                    iterator.remove();
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Removes all queued messages.
+     */
+    public void clear() {
+        lock.lock();
+        try {
+            queue.clear();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Blocks until all queued messages are delivered.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            stopping = true;
+            condition.signalAll();
+            while (!queue.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private class DelayedMessage implements Comparable<DelayedMessage> {
+        private final long dispatchTime;
+        private final T message;
+
+        private DelayedMessage(long dispatchTime, T message) {
+            this.dispatchTime = dispatchTime;
+            this.message = message;
+        }
+
+        public int compareTo(DelayedMessage delayedMessage) {
+            if (dispatchTime > delayedMessage.dispatchTime) {
+                return 1;
+            } else if (dispatchTime < delayedMessage.dispatchTime) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardOnFailureDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardOnFailureDispatch.java
deleted file mode 100644
index e14a5a6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardOnFailureDispatch.java
+++ /dev/null
@@ -1,36 +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.messaging.dispatch;
-
-import org.slf4j.Logger;
-
-public class DiscardOnFailureDispatch<T> implements Dispatch<T> {
-    private final Dispatch<? super T> dispatch;
-    private final Logger logger;
-
-    public DiscardOnFailureDispatch(Dispatch<? super T> dispatch, Logger logger) {
-        this.dispatch = dispatch;
-        this.logger = logger;
-    }
-
-    public void dispatch(T message) {
-        try {
-            dispatch.dispatch(message);
-        } catch (Throwable e) {
-            logger.error(String.format("Could not dispatch message %s to %s. Discarding message.", message, dispatch), e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardingFailureHandler.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardingFailureHandler.java
new file mode 100755
index 0000000..4ce6bfa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardingFailureHandler.java
@@ -0,0 +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.dispatch;
+
+import org.slf4j.Logger;
+
+public class DiscardingFailureHandler<T> implements DispatchFailureHandler<T> {
+    private final Logger logger;
+
+    public DiscardingFailureHandler(Logger logger) {
+        this.logger = logger;
+    }
+
+    public void dispatchFailed(T message, Throwable failure) {
+        logger.error(String.format("Could not dispatch message %s. Discarding message.", message), failure);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchFailureHandler.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchFailureHandler.java
new file mode 100644
index 0000000..75104be
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchFailureHandler.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.dispatch;
+
+public interface DispatchFailureHandler<T> {
+    /**
+     * Called when a message could not be dispatched. This method can throw an exception to abort further dispatching.
+     */
+    void dispatchFailed(T message, Throwable failure);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatch.java
deleted file mode 100644
index 77a3644..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatch.java
+++ /dev/null
@@ -1,37 +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.messaging.dispatch;
-
-import org.gradle.api.Action;
-
-public class ExceptionTrackingDispatch<T> implements Dispatch<T> {
-    private final Dispatch<T> dispatch;
-    private final Action<? super DispatchException> action;
-
-    public ExceptionTrackingDispatch(Dispatch<T> dispatch, Action<? super DispatchException> action) {
-        this.dispatch = dispatch;
-        this.action = action;
-    }
-
-    public void dispatch(T message) {
-        try {
-            dispatch.dispatch(message);
-        } catch (Throwable t) {
-            action.execute(new DispatchException(String.format("Failed to dispatch message %s.", message), t));
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
new file mode 100644
index 0000000..80bbbda
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
@@ -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.messaging.dispatch;
+
+import org.gradle.internal.Stoppable;
+import org.slf4j.Logger;
+
+public class ExceptionTrackingFailureHandler implements DispatchFailureHandler<Object>, Stoppable {
+    private final Logger logger;
+    private DispatchException failure;
+
+    public ExceptionTrackingFailureHandler(Logger logger) {
+        this.logger = logger;
+    }
+
+    public void dispatchFailed(Object message, Throwable failure) {
+        if (this.failure != null) {
+            logger.error(failure.getMessage(), failure);
+        } else {
+            this.failure = new DispatchException(String.format("Could not dispatch message %s.", message), failure);
+        }
+    }
+
+    public void stop() throws DispatchException {
+        if (failure != null) {
+            try {
+                throw failure;
+            } finally {
+                failure = null;
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListener.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListener.java
deleted file mode 100644
index 26112ec..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListener.java
+++ /dev/null
@@ -1,49 +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.messaging.dispatch;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.Stoppable;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-
-public class ExceptionTrackingListener implements Action<Throwable>, Stoppable {
-    private final Logger logger;
-    private RuntimeException failure;
-
-    public ExceptionTrackingListener(Logger logger) {
-        this.logger = logger;
-    }
-
-    public void execute(Throwable failure) {
-        if (this.failure != null) {
-            logger.error(failure.getMessage(), failure);
-        } else {
-            this.failure = UncheckedException.asUncheckedException(failure);
-        }
-    }
-
-    public void stop() {
-        if (failure != null) {
-            try {
-                throw failure;
-            } finally {
-                failure = null;
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatch.java
new file mode 100644
index 0000000..abe4f01
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatch.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.dispatch;
+
+public class FailureHandlingDispatch<T> implements Dispatch<T> {
+    private final Dispatch<? super T> dispatch;
+    private final DispatchFailureHandler<? super T> handler;
+
+    public FailureHandlingDispatch(Dispatch<? super T> dispatch, DispatchFailureHandler<? super T> handler) {
+        this.dispatch = dispatch;
+        this.handler = handler;
+    }
+
+    public void dispatch(T message) {
+        try {
+            dispatch.dispatch(message);
+        } catch (Throwable throwable) {
+            handler.dispatchFailed(message, throwable);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java
index 564b4d2..9a185ed 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java
@@ -56,5 +56,10 @@ public class MethodInvocation {
     public int hashCode() {
         return method.hashCode();
     }
+
+    @Override
+    public String toString() {
+        return String.format("[MethodInvocation method: %s()]", method.getName());
+    }
 }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
old mode 100644
new mode 100755
index c0c3a54..0da70e6
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
@@ -1,79 +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.messaging.dispatch;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-
-/**
- * Adapts from interface T to a {@link Dispatch}
- *
- * @param <T>
- */
-public class ProxyDispatchAdapter<T> {
-    private final Class<T> type;
-    private final T source;
-
-    public ProxyDispatchAdapter(Class<T> type, Dispatch<? super MethodInvocation> dispatch) {
-        this.type = type;
-        source = type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type},
-                new DispatchingInvocationHandler(type, dispatch)));
-    }
-
-    public Class<T> getType() {
-        return type;
-    }
-
-    public T getSource() {
-        return source;
-    }
-
-    private static class DispatchingInvocationHandler implements InvocationHandler {
-        private final Class<?> type;
-        private final Dispatch<? super MethodInvocation> dispatch;
-
-        private DispatchingInvocationHandler(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-            this.type = type;
-            this.dispatch = dispatch;
-        }
-
-        public Object invoke(Object target, Method method, Object[] parameters) throws Throwable {
-            if (method.getName().equals("equals")) {
-                Object parameter = parameters[0];
-                if (parameter == null || !Proxy.isProxyClass(parameter.getClass())) {
-                    return false;
-                }
-                Object handler = Proxy.getInvocationHandler(parameter);
-                if (!DispatchingInvocationHandler.class.isInstance(handler)) {
-                    return false;
-                }
-
-                DispatchingInvocationHandler otherHandler = (DispatchingInvocationHandler) handler;
-                return otherHandler.type.equals(type) && otherHandler.dispatch == dispatch;
-            }
-
-            if (method.getName().equals("hashCode")) {
-                return dispatch.hashCode();
-            }
-            if (method.getName().equals("toString")) {
-                return String.format("%s broadcast", type.getSimpleName());
-            }
-            dispatch.dispatch(new MethodInvocation(method, parameters));
-            return null;
-        }
-    }
-}
+/*
+ * 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.dispatch;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapts from interface T to a {@link Dispatch}
+ *
+ * @param <T>
+ */
+public class ProxyDispatchAdapter<T> {
+    private final Class<T> type;
+    private final T source;
+
+    public ProxyDispatchAdapter(Dispatch<? super MethodInvocation> dispatch, Class<T> type, Class<?>... extraTypes) {
+        this.type = type;
+        List<Class<?>> types = new ArrayList<Class<?>>();
+        ClassLoader classLoader = type.getClassLoader();
+        types.add(type);
+        for (Class<?> extraType : extraTypes) {
+            ClassLoader candidate = extraType.getClassLoader();
+            if (candidate != classLoader && candidate != null) {
+                try {
+                    if (candidate.loadClass(type.getName()) != null) {
+                        classLoader = candidate;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                }
+            }
+            types.add(extraType);
+        }
+        source = type.cast(Proxy.newProxyInstance(classLoader, types.toArray(new Class<?>[types.size()]),
+                new DispatchingInvocationHandler(type, dispatch)));
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    public T getSource() {
+        return source;
+    }
+
+    private static class DispatchingInvocationHandler implements InvocationHandler {
+        private final Class<?> type;
+        private final Dispatch<? super MethodInvocation> dispatch;
+
+        private DispatchingInvocationHandler(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+            this.type = type;
+            this.dispatch = dispatch;
+        }
+
+        public Object invoke(Object target, Method method, Object[] parameters) throws Throwable {
+            if (method.getName().equals("equals")) {
+                Object parameter = parameters[0];
+                if (parameter == null || !Proxy.isProxyClass(parameter.getClass())) {
+                    return false;
+                }
+                Object handler = Proxy.getInvocationHandler(parameter);
+                if (!DispatchingInvocationHandler.class.isInstance(handler)) {
+                    return false;
+                }
+
+                DispatchingInvocationHandler otherHandler = (DispatchingInvocationHandler) handler;
+                return otherHandler.type.equals(type) && otherHandler.dispatch == dispatch;
+            }
+
+            if (method.getName().equals("hashCode")) {
+                return dispatch.hashCode();
+            }
+            if (method.getName().equals("toString")) {
+                return String.format("%s broadcast", type.getSimpleName());
+            }
+            dispatch.dispatch(new MethodInvocation(method, parameters));
+            return null;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/QueuingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/QueuingDispatch.java
new file mode 100644
index 0000000..2bcd480
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/QueuingDispatch.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.dispatch;
+
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Queues messages until a receiver has been connected. This class is thread-safe.
+ */
+public class QueuingDispatch<T> implements Dispatch<T>, Stoppable {
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private List<T> queue = new ArrayList<T>();
+    private Dispatch<? super T> dispatch;
+
+    /**
+     * Dispatches to the given handler. The handler does not have to be thread-safe.
+     */
+    public void dispatchTo(Dispatch<? super T> dispatch) {
+        lock.lock();
+        try {
+            this.dispatch = dispatch;
+            for (T message : queue) {
+                dispatch.dispatch(message);
+            }
+            queue = null;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void dispatch(T message) {
+        lock.lock();
+        try {
+            if (dispatch == null) {
+                queue.add(message);
+            } else {
+                dispatch.dispatch(message);
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void stop() {
+        lock.lock();
+        try {
+            while (queue != null && !queue.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java
old mode 100644
new mode 100755
index f4e75f0..a6021b9
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java
@@ -1,28 +1,28 @@
-/*
- * 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.dispatch;
-
-/**
- * A source for messages. Implementations do not have to be thread-safe.
- */
-public interface Receive<T> {
-    /**
-     * Blocks until the next message is available. Returns null when the end of the message stream has been reached.
-     *
-     * @return The next message.
-     */
-    T receive();
-}
+/*
+ * 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.dispatch;
+
+/**
+ * A source for messages. Implementations do not have to be thread-safe.
+ */
+public interface Receive<T> {
+    /**
+     * Blocks until the next message is available. Returns null when the end of the message stream has been reached.
+     *
+     * @return The next message, or null when the end of the stream has been reached.
+     */
+    T receive();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java
old mode 100644
new mode 100755
index e814fb8..0bf5b62
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java
@@ -1,42 +1,42 @@
-/*
- * 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.dispatch;
-
-import org.gradle.util.UncheckedException;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-public class ReflectionDispatch implements Dispatch<MethodInvocation> {
-    private final Object target;
-
-    public ReflectionDispatch(Object target) {
-        this.target = target;
-    }
-
-    public void dispatch(MethodInvocation message) {
-        try {
-            Method method = message.getMethod();
-            method.setAccessible(true);
-            method.invoke(target, message.getArguments());
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.asUncheckedException(e.getCause());
-        } catch (Throwable throwable) {
-            throw UncheckedException.asUncheckedException(throwable);
-        }
-    }
-}
+/*
+ * 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.dispatch;
+
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class ReflectionDispatch implements Dispatch<MethodInvocation> {
+    private final Object target;
+
+    public ReflectionDispatch(Object target) {
+        this.target = target;
+    }
+
+    public void dispatch(MethodInvocation message) {
+        try {
+            Method method = message.getMethod();
+            method.setAccessible(true);
+            method.invoke(target, message.getArguments());
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Throwable throwable) {
+            throw UncheckedException.throwAsUncheckedException(throwable);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
old mode 100644
new mode 100755
index b8f9ce4..9957149
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
@@ -1,25 +1,25 @@
-/*
- * 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.dispatch;
-
-import org.gradle.messaging.concurrent.Stoppable;
-
-public interface StoppableDispatch<T> extends Dispatch<T>, Stoppable {
-    /**
-     * Stops this dispatch. Stops accepting new events and blocks until all events have been dispatched.
-     */
-    void stop();
-}
+/*
+ * 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.dispatch;
+
+import org.gradle.internal.Stoppable;
+
+public interface StoppableDispatch<T> extends Dispatch<T>, Stoppable {
+    /**
+     * Stops this dispatch. Stops accepting new events and blocks until all events have been dispatched.
+     */
+    void stop();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ThreadSafeDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ThreadSafeDispatch.java
deleted file mode 100644
index 7432d6c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ThreadSafeDispatch.java
+++ /dev/null
@@ -1,31 +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.messaging.dispatch;
-
-public class ThreadSafeDispatch<T> implements Dispatch<T> {
-    private final Object lock = new Object();
-    private final Dispatch<T> dispatch;
-
-    public ThreadSafeDispatch(Dispatch<T> dispatch) {
-        this.dispatch = dispatch;
-    }
-
-    public void dispatch(T message) {
-        synchronized (lock) {
-            dispatch.dispatch(message);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Address.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Address.java
new file mode 100644
index 0000000..d8ab8e6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Address.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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;
+
+import java.io.Serializable;
+
+/**
+ * The address for a communication endpoint. Addresses are immutable.
+ */
+public interface Address extends Serializable {
+    /**
+     * Returns the display name for this address. Implementations should also override toString() to return the display name.
+     *
+     * @return The display name.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Addressable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Addressable.java
new file mode 100755
index 0000000..a322287
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Addressable.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+public interface Addressable {
+    Address getLocalAddress();
+
+    Address getRemoteAddress();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java
index 47cf05e..991f502 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java
@@ -16,16 +16,12 @@
 
 package org.gradle.messaging.remote;
 
-import org.gradle.messaging.dispatch.Addressable;
-
-import java.net.URI;
-
 public class ConnectEvent<T> implements Addressable {
     private final T connection;
-    private final URI localAddress;
-    private final URI remoteAddress;
+    private final Address localAddress;
+    private final Address remoteAddress;
 
-    public ConnectEvent(T connection, URI localAddress, URI remoteAddress) {
+    public ConnectEvent(T connection, Address localAddress, Address remoteAddress) {
         this.connection = connection;
         this.localAddress = localAddress;
         this.remoteAddress = remoteAddress;
@@ -35,11 +31,11 @@ public class ConnectEvent<T> implements Addressable {
         return connection;
     }
 
-    public URI getLocalAddress() {
+    public Address getLocalAddress() {
         return localAddress;
     }
 
-    public URI getRemoteAddress() {
+    public Address getRemoteAddress() {
         return remoteAddress;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java
old mode 100644
new mode 100755
index 0f33a06..413be55
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java
@@ -1,33 +1,28 @@
-/*
- * 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;
-
-import org.gradle.messaging.concurrent.Stoppable;
-
-/**
- * A {@code MessagingClient} maintains a single bi-directional uni-cast object connection with some peer.
- */
-public interface MessagingClient extends Stoppable {
-    /**
-     * Returns the connection for this client.
-     */
-    ObjectConnection getConnection();
-
-    /**
-     * Performs a graceful stop of this client. Stops the client's connection.
-     */
-    void stop();
-}
+/*
+ * 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;
+
+/**
+ * A {@code MessagingClient} maintains a single bi-directional uni-cast object connection with some peer.
+ */
+public interface MessagingClient {
+    /**
+     * Returns the connection for this client.
+     *
+     * @param address The address to connect to.
+     */
+    ObjectConnection getConnection(Address address);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java
old mode 100644
new mode 100755
index 0ff2758..c44687e
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java
@@ -1,40 +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;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.Stoppable;
-
-import java.net.URI;
-
-/**
- * A {@code MessagingServer} allows the creation of multiple bi-direction uni-cast connections with some peer.
- */
-public interface MessagingServer extends Stoppable {
-    /**
-     * Creates an endpoint which a single peer can connect to.
-     *
-     * @param action The action to execute when the connection has been established.
-     * @return The local address of the endpoint, for the peer to connect to
-     */
-    URI accept(Action<ConnectEvent<ObjectConnection>> action);
-
-    /**
-     * Performs a graceful stop of this server. Blocks until connections have been stopped.
-     */
-    void stop();
-}
+/*
+ * 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;
+
+import org.gradle.api.Action;
+
+/**
+ * A {@code MessagingServer} allows the creation of multiple bi-directional uni-cast connections with some peer.
+ */
+public interface MessagingServer {
+    /**
+     * Creates an endpoint which a single peer can connect to.
+     *
+     * @param action The action to execute when the connection has been established.
+     * @return The local address of the endpoint, for the peer to connect to.
+     */
+    Address accept(Action<ConnectEvent<ObjectConnection>> action);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
old mode 100644
new mode 100755
index 936be45..b0320df
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
@@ -1,64 +1,63 @@
-/*
- * 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;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Addressable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-
-/**
- * Manages a set of incoming and outgoing channels between 2 peers. Implementations must be thread-safe.
- */
-public interface ObjectConnection extends Addressable, AsyncStoppable {
-    /**
-     * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
-     *
-     * @param type The type
-     * @return A sink. Method calls made on this object are sent as outgoing messages.
-     */
-    <T> T addOutgoing(Class<T> type);
-
-    /**
-     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
-     * thread-safe.
-     *
-     * @param type The type.
-     * @param instance The handler instance. Incoming messages on the given type are delivered to this handler.
-     */
-    <T> void addIncoming(Class<T> type, T instance);
-
-    /**
-     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
-     * thread-safe.
-     *
-     * @param type The type.
-     * @param dispatch The handler instance. Incoming messages on the given type are delivered to this handler.
-     */
-    void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch);
-
-    /**
-     * Commences a graceful stop of this connection. Stops accepting outgoing messages. Requests that the peer stop
-     * sending incoming messages.
-     */
-    void requestStop();
-
-    /**
-     * Performs a graceful stop of this connection. Stops accepting outgoing message. Blocks until all incoming messages
-     * have been handled, and all outgoing messages have been handled by the peer.
-     */
-    void stop();
-}
+/*
+ * 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;
+
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+
+/**
+ * Manages a set of incoming and outgoing channels between 2 peers. Implementations must be thread-safe.
+ */
+public interface ObjectConnection extends Addressable, AsyncStoppable {
+    /**
+     * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
+     *
+     * @param type The type
+     * @return A sink. Method calls made on this object are sent as outgoing messages.
+     */
+    <T> T addOutgoing(Class<T> type);
+
+    /**
+     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
+     * thread-safe. Messages are delivered to the handler by a single thread.
+     *
+     * @param type The type.
+     * @param instance The handler instance. Incoming messages on the given type are delivered to this handler.
+     */
+    <T> void addIncoming(Class<T> type, T instance);
+
+    /**
+     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
+     * thread-safe. Messages are delivered to the handler by a single thread.
+     *
+     * @param type The type.
+     * @param dispatch The handler instance. Incoming messages on the given type are delivered to this handler.
+     */
+    void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch);
+
+    /**
+     * Commences a graceful stop of this connection. Stops accepting outgoing messages. Requests that the peer stop
+     * sending incoming messages.
+     */
+    void requestStop();
+
+    /**
+     * Performs a graceful stop of this connection. Stops accepting outgoing message. Blocks until all incoming messages
+     * have been handled, and all outgoing messages have been handled by the peer.
+     */
+    void stop();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnection.java
new file mode 100644
index 0000000..14a162f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnection.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch;
+
+/**
+ * <p>A messaging endpoint which allows push-style dispatch and receive.
+ *
+ * <p>Implementations must be thread-safe.
+ */
+public interface AsyncConnection<T> extends Dispatch<T> {
+    /**
+     * Dispatches a message to this connection. The implementation should not block.
+     *
+     * @param message The message.
+     */
+    void dispatch(T message);
+
+    /**
+     * Adds a handler to receive incoming messages. The handler does not need to be thread-safe. The handler should not block.
+     *
+     * @param handler The handler.
+     */
+    void dispatchTo(Dispatch<? super T> handler);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
new file mode 100644
index 0000000..df0969f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 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.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Adapts a {@link Connection} into an {@link AsyncConnection}.
+ */
+public class AsyncConnectionAdapter<T> implements AsyncConnection<T>, Stoppable {
+    private final Connection<T> connection;
+    private final AsyncReceive<T> incoming;
+    private final ProtocolStack<T> stack;
+    private final AsyncDispatch<T> outgoing;
+    private final Set<Stoppable> executors = new HashSet<Stoppable>();
+
+    public AsyncConnectionAdapter(Connection<T> connection, DispatchFailureHandler<? super T> dispatchFailureHandler, ExecutorFactory executor, Protocol<T>... protocols) {
+        this.connection = connection;
+
+        StoppableExecutor outgoingExecutor = executor.create(String.format("%s send", connection));
+        executors.add(outgoingExecutor);
+        outgoing = new AsyncDispatch<T>(outgoingExecutor);
+        outgoing.dispatchTo(new FailureHandlingDispatch<T>(connection, dispatchFailureHandler));
+
+        StoppableExecutor dispatchExecutor = executor.create(String.format("%s dispatch", connection));
+        executors.add(dispatchExecutor);
+        stack = new ProtocolStack<T>(dispatchExecutor, dispatchFailureHandler, dispatchFailureHandler, protocols);
+        stack.getBottom().dispatchTo(outgoing);
+
+        StoppableExecutor incomingExecutor = executor.create(String.format("%s receive", connection));
+        executors.add(incomingExecutor);
+        incoming = new AsyncReceive<T>(incomingExecutor);
+        incoming.dispatchTo(stack.getBottom());
+        incoming.receiveFrom(new ConnectionReceive<T>(connection));
+    }
+
+    public void dispatch(T message) {
+        stack.getTop().dispatch(message);
+    }
+
+    public void dispatchTo(Dispatch<? super T> handler) {
+        stack.getTop().dispatchTo(handler);
+    }
+
+    public void stop() {
+        new CompositeStoppable(stack, outgoing, connection, incoming).add(executors).stop();
+    }
+
+    private class ConnectionReceive<T> implements Receive<T> {
+        private final Connection<T> connection;
+
+        public ConnectionReceive(Connection<T> connection) {
+            this.connection = connection;
+        }
+
+        public T receive() {
+            T result = connection.receive();
+            if (result == null) {
+                stack.requestStop();
+            }
+            return result;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java
new file mode 100644
index 0000000..4d27b69
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.ConsumerAvailable;
+import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable;
+import org.gradle.messaging.remote.internal.protocol.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class BroadcastSendProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastSendProtocol.class);
+    private ProtocolContext<Message> context;
+    private final Set<Object> consumers = new HashSet<Object>();
+    private final List<Object> queue = new ArrayList<Object>();
+    private boolean stopping;
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof Request) {
+            Request request = (Request) message;
+            if (consumers.isEmpty()) {
+                queue.add(request.getPayload());
+            } else {
+                for (Object consumer : consumers) {
+                    context.dispatchOutgoing(new Request(consumer, request.getPayload()));
+                }
+            }
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+        }
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof ConsumerAvailable) {
+            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
+            consumers.add(consumerAvailable.getId());
+            if (!queue.isEmpty()) {
+                for (Object queued : queue) {
+                    context.dispatchOutgoing(new Request(consumerAvailable.getId(), queued));
+                }
+                queue.clear();
+                if (stopping) {
+                    LOGGER.debug("All queued outgoing messages have been dispatched. Stopping now.");
+                    context.stopped();
+                }
+            }
+        } else if (message instanceof ConsumerUnavailable) {
+            ConsumerUnavailable consumerUnavailable = (ConsumerUnavailable) message;
+            consumers.remove(consumerUnavailable.getId());
+        } else {
+            throw new IllegalArgumentException(String.format("Received unexpected incoming message: %s", message));
+        }
+    }
+
+    public void stopRequested() {
+        if (queue.isEmpty()) {
+            LOGGER.debug("No outgoing messages queued. Stopping now.");
+            context.stopped();
+            return;
+        }
+
+        LOGGER.debug("Outgoing messages queued. Stopping later.");
+        stopping = true;
+        context.stopLater();
+        context.callbackLater(5, TimeUnit.SECONDS, new Runnable() {
+            public void run() {
+                LOGGER.debug("Timeout waiting for queued messages to be dispatched. Stopping now.");
+                queue.clear();
+                context.stopped();
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BufferingProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BufferingProtocol.java
new file mode 100644
index 0000000..86c14cf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BufferingProtocol.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.MessageCredits;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedList;
+
+public class BufferingProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BufferingProtocol.class);
+    private final int maxBufferSize;
+    private final LinkedList<Message> queue = new LinkedList<Message>();
+    private ProtocolContext<Message> context;
+    private int remainingOutgoingCredits;
+    private int remainingIncomingCredits;
+    private boolean stopping;
+
+    public BufferingProtocol(int maxBufferSize) {
+        this.maxBufferSize = maxBufferSize;
+    }
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+        context.dispatchOutgoing(new MessageCredits(maxBufferSize));
+        remainingIncomingCredits = maxBufferSize;
+    }
+
+    public void handleIncoming(Message message) {
+        remainingIncomingCredits--;
+        if (remainingOutgoingCredits == 0) {
+            queue.add(message);
+        } else {
+            remainingOutgoingCredits--;
+            context.dispatchIncoming(message);
+        }
+        maybeGrantIncomingCredits();
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof MessageCredits) {
+            MessageCredits credits = (MessageCredits) message;
+            remainingOutgoingCredits += credits.getCredits();
+            while (!queue.isEmpty() && remainingOutgoingCredits > 0) {
+                remainingOutgoingCredits--;
+                context.dispatchIncoming(queue.removeFirst());
+            }
+            if (stopping && queue.isEmpty()) {
+                context.stopped();
+                return;
+            }
+            maybeGrantIncomingCredits();
+        } else {
+            context.dispatchOutgoing(message);
+        }
+    }
+
+    private void maybeGrantIncomingCredits() {
+        int minBatchSize = maxBufferSize / 2;
+        int grantablePermits = maxBufferSize - queue.size() - remainingIncomingCredits + remainingOutgoingCredits;
+        if (grantablePermits >= minBatchSize) {
+            context.dispatchOutgoing(new MessageCredits(grantablePermits));
+            remainingIncomingCredits += grantablePermits;
+        }
+    }
+
+    public void stopRequested() {
+        stopping = true;
+        if (queue.isEmpty()) {
+            context.stopped();
+        } else {
+            LOGGER.debug("Waiting for queue to empty. Stopping later.");
+            context.stopLater();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java
new file mode 100644
index 0000000..8dee930
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.ChannelAvailable;
+import org.gradle.messaging.remote.internal.protocol.ChannelUnavailable;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.LookupRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class ChannelLookupProtocol implements Protocol<DiscoveryMessage> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ChannelLookupProtocol.class);
+    private final Map<String, RequestDetails> channels = new HashMap<String, RequestDetails>();
+    private ProtocolContext<DiscoveryMessage> context;
+
+    public void start(ProtocolContext<DiscoveryMessage> context) {
+        this.context = context;
+    }
+
+    public void handleOutgoing(DiscoveryMessage message) {
+        if (message instanceof LookupRequest) {
+            LookupRequest lookupRequest = (LookupRequest) message;
+            LOGGER.info("Broadcasting lookup request: {}", lookupRequest);
+            RequestDetails request = new RequestDetails(lookupRequest);
+            channels.put(lookupRequest.getChannel(), request);
+            request.run();
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public void handleIncoming(DiscoveryMessage message) {
+        if (message instanceof ChannelAvailable) {
+            ChannelAvailable channelAvailable = (ChannelAvailable) message;
+            LOGGER.info("Channel discovered: {}", channelAvailable);
+            RequestDetails request = channels.get(channelAvailable.getChannel());
+            if (request != null) {
+                LOGGER.info("Processing request on channel: {}", request.lookupRequest);
+                request.handleResponse(channelAvailable);
+            } else {
+                LOGGER.info("No request for channel, ignoring.");
+            }
+        } else if (!(message instanceof LookupRequest) && !(message instanceof ChannelUnavailable)) {
+            // Discard
+            LOGGER.info("Received unknown discovery message - discarding: {}", message);
+        } else {
+            // Else ignore
+            LOGGER.info("Ignored incoming discovery message {}", message);
+        }
+    }
+
+    public void stopRequested() {
+        context.stopped();
+    }
+
+    private class RequestDetails implements Runnable {
+        private final LookupRequest lookupRequest;
+        ProtocolContext.Callback timeout;
+        int attempts;
+
+        public RequestDetails(LookupRequest lookupRequest) {
+            this.lookupRequest = lookupRequest;
+        }
+
+        public void handleResponse(ChannelAvailable channelAvailable) {
+            timeout.cancel();
+            context.dispatchIncoming(channelAvailable);
+        }
+
+        public void run() {
+            attempts++;
+            timeout = context.callbackLater(getTimeoutSeconds(), TimeUnit.SECONDS, this);
+            context.dispatchOutgoing(lookupRequest);
+        }
+
+        private int getTimeoutSeconds() {
+            if (attempts > 10) {
+                return 30;
+            }
+            if (attempts > 5) {
+                return 10;
+            }
+            return 1;
+        }
+
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java
deleted file mode 100644
index f4bc91a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java
+++ /dev/null
@@ -1,52 +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.messaging.remote.internal;
-
-public class ChannelMessage extends Message {
-    private final Object channel;
-    private final Object payload;
-
-    public ChannelMessage(Object channel, Object payload) {
-        this.channel = channel;
-        this.payload = payload;
-    }
-
-    public Object getChannel() {
-        return channel;
-    }
-
-    public Object getPayload() {
-        return payload;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null || !getClass().equals(obj.getClass())) {
-            return false;
-        }
-
-        ChannelMessage other = (ChannelMessage) obj;
-        return channel.equals(other.channel) && payload.equals(other.payload);
-    }
-
-    @Override
-    public int hashCode() {
-        return channel.hashCode() ^ payload.hashCode();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
deleted file mode 100644
index cd96907..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
+++ /dev/null
@@ -1,47 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class ChannelMessageMarshallingDispatch implements Dispatch<Object> {
-    private final Dispatch<Object> dispatch;
-    private final Map<Object, Integer> channels = new HashMap<Object, Integer>();
-
-    public ChannelMessageMarshallingDispatch(Dispatch<Object> dispatch) {
-        this.dispatch = dispatch;
-    }
-
-    public void dispatch(Object message) {
-        if (message instanceof ChannelMessage) {
-            ChannelMessage channelMessage = (ChannelMessage) message;
-            Object key = channelMessage.getChannel();
-            Integer id = channels.get(key);
-            if (id == null) {
-                id = channels.size();
-                channels.put(key, id);
-                dispatch.dispatch(new ChannelMetaInfo(key, id));
-            }
-            dispatch.dispatch(new ChannelMessage(id, channelMessage.getPayload()));
-        } else {
-            dispatch.dispatch(message);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
deleted file mode 100644
index 53ee640..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
+++ /dev/null
@@ -1,47 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class ChannelMessageUnmarshallingDispatch implements Dispatch<Object> {
-    private final Dispatch<Object> dispatch;
-    private final Map<Integer, Object> channels = new HashMap<Integer, Object>();
-
-    public ChannelMessageUnmarshallingDispatch(Dispatch<Object> dispatch) {
-        this.dispatch = dispatch;
-    }
-
-    public void dispatch(Object message) {
-        if (message instanceof ChannelMetaInfo) {
-            ChannelMetaInfo metaInfo = (ChannelMetaInfo) message;
-            channels.put(metaInfo.getChannelId(), metaInfo.getChannelKey());
-            return;
-        }
-        if (message instanceof ChannelMessage) {
-            ChannelMessage channelMessage = (ChannelMessage) message;
-            dispatch.dispatch(new ChannelMessage(channels.get(channelMessage.getChannel()),
-                    channelMessage.getPayload()));
-            return;
-        }
-        
-        dispatch.dispatch(message);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMetaInfo.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMetaInfo.java
deleted file mode 100644
index 618e0cb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMetaInfo.java
+++ /dev/null
@@ -1,53 +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.messaging.remote.internal;
-
-public class ChannelMetaInfo extends Message {
-    private final Object channelKey;
-    private final int channelId;
-
-    public ChannelMetaInfo(Object channelKey, int channelId) {
-        this.channelKey = channelKey;
-        this.channelId = channelId;
-    }
-
-    public int getChannelId() {
-        return channelId;
-    }
-
-    public Object getChannelKey() {
-        return channelKey;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || !getClass().equals(o.getClass())) {
-            return false;
-        }
-
-        ChannelMetaInfo other = (ChannelMetaInfo) o;
-        return other.channelKey.equals(channelKey) && other.channelId == channelId;
-    }
-
-    @Override
-    public int hashCode() {
-        return channelKey.hashCode();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java
new file mode 100644
index 0000000..8517493
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.ChannelAvailable;
+import org.gradle.messaging.remote.internal.protocol.ChannelUnavailable;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.LookupRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ChannelRegistrationProtocol implements Protocol<DiscoveryMessage> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ChannelRegistrationProtocol.class);
+    private final Map<String, ChannelAvailable> channels = new HashMap<String, ChannelAvailable>();
+    private final MessageOriginator messageOriginator;
+    private ProtocolContext<DiscoveryMessage> context;
+
+    public ChannelRegistrationProtocol(MessageOriginator messageOriginator) {
+        this.messageOriginator = messageOriginator;
+    }
+
+    public void start(ProtocolContext<DiscoveryMessage> context) {
+        this.context = context;
+    }
+
+    public void handleIncoming(DiscoveryMessage message) {
+        if (message instanceof LookupRequest) {
+            handleLookup((LookupRequest) message);
+        } else if (!(message instanceof ChannelAvailable) && !(message instanceof ChannelUnavailable)) {
+            // Discard
+            LOGGER.info("Received unexpected discovery message - discarding: {}", message);
+        } else {
+            // Else, ignore
+            LOGGER.info("Ignoring discovery message: {}", message);
+        }
+    }
+
+    public void handleOutgoing(DiscoveryMessage message) {
+        if (message instanceof ChannelAvailable) {
+            ChannelAvailable channelAvailable = (ChannelAvailable) message;
+            channels.put(channelAvailable.getChannel(), channelAvailable);
+            LOGGER.info("Channel registered. Broadcasting {}.", message);
+            context.dispatchOutgoing(message);
+        } else if (message instanceof ChannelUnavailable) {
+            ChannelUnavailable channelUnavailable = (ChannelUnavailable) message;
+            channels.remove(channelUnavailable.getChannel());
+            LOGGER.info("Channel unregistered. Broadcasting {}.", message);
+            context.dispatchOutgoing(message);
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private void handleLookup(LookupRequest request) {
+        ChannelAvailable response = channels.get(request.getChannel());
+        if (response != null) {
+            LOGGER.info("Replying to lookup request for known channel. Request: {}. Response: {}.", request, response);
+            context.dispatchOutgoing(response);
+        } else {
+            LOGGER.info("Received lookup request for unknown channel - discarding: {}", request);
+        }
+    }
+
+    public void stopRequested() {
+        try {
+            for (ChannelAvailable channelAvailable : channels.values()) {
+                context.dispatchOutgoing(new ChannelUnavailable(messageOriginator, channelAvailable.getGroup(), channelAvailable.getChannel(), channelAvailable.getAddress()));
+            }
+        } finally {
+            channels.clear();
+            context.stopped();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/CompositeAddress.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/CompositeAddress.java
new file mode 100644
index 0000000..21007a0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/CompositeAddress.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.messaging.remote.Address;
+
+public class CompositeAddress implements Address {
+    private final Address address;
+    private final Object qualifier;
+
+    public CompositeAddress(Address address, Object qualifier) {
+        this.address = address;
+        this.qualifier = qualifier;
+    }
+
+    public String getDisplayName() {
+        return String.format("%s:%s", address.getDisplayName(), qualifier);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        CompositeAddress other = (CompositeAddress) o;
+        return other.address.equals(address) && other.qualifier.equals(qualifier);
+    }
+
+    @Override
+    public int hashCode() {
+        return address.hashCode() ^ qualifier.hashCode();
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    public Object getQualifier() {
+        return qualifier;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectRequest.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectRequest.java
deleted file mode 100644
index 3dda186..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectRequest.java
+++ /dev/null
@@ -1,31 +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.messaging.remote.internal;
-
-import java.net.URI;
-
-public class ConnectRequest extends Message {
-    private final URI destinationAddress;
-
-    public ConnectRequest(URI destinationAddress) {
-        this.destinationAddress = destinationAddress;
-    }
-
-    public URI getDestinationAddress() {
-        return destinationAddress;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
old mode 100644
new mode 100755
index 3ff0b18..018a82c
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
@@ -1,24 +1,37 @@
-/*
- * 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.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.Receive;
-
-public interface Connection<T> extends Dispatch<T>, Receive<T>, AsyncStoppable {
-}
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.Receive;
+
+/**
+ * <p>A messaging endpoint which allows push-style dispatch and pull-style receive.
+ *
+ * <p>Implementations are not guaranteed to be completely thread-safe.
+ * However, the implementations:
+ * <ul>
+ * <li>should allow separate threads for dispatching and receiving, i.e. single thread that dispatches
+ * and a different single thread that receives should be perfectly safe</li>
+ * <li>should allow stopping or requesting stopping from a different thread than receiving/dispatching</li>
+ * <li>don't guarantee allowing multiple threads dispatching</li>
+ * </li>
+ * </ul>
+ */
+public interface Connection<T> extends Dispatch<T>, Receive<T>, AsyncStoppable {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
new file mode 100644
index 0000000..679e4a9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.util.IdGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIncomingBroadcast.class);
+    private final ProtocolStack<DiscoveryMessage> protocolStack;
+    private final MessageOriginator messageOriginator;
+    private final String group;
+    private final Lock lock = new ReentrantLock();
+    private final Set<String> channels = new HashSet<String>();
+    private final StoppableExecutor executor;
+    private final Address address;
+    private final MessageHub hub;
+
+    public DefaultIncomingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, IncomingConnector<Message> incomingConnector, ExecutorFactory executorFactory, IdGenerator<?> idGenerator, ClassLoader messagingClassLoader) {
+        this.messageOriginator = messageOriginator;
+        this.group = group;
+
+        executor = executorFactory.create("discovery broadcast");
+        DiscardingFailureHandler<DiscoveryMessage> failureHandler = new DiscardingFailureHandler<DiscoveryMessage>(LOGGER);
+        protocolStack = new ProtocolStack<DiscoveryMessage>(executor, failureHandler, failureHandler, new ChannelRegistrationProtocol(messageOriginator));
+        connection.dispatchTo(new GroupMessageFilter(group, protocolStack.getBottom()));
+        protocolStack.getBottom().dispatchTo(connection);
+
+        address = incomingConnector.accept(new IncomingConnectionAction(), true);
+        hub = new MessageHub("incoming broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
+
+        LOGGER.info("Created IncomingBroadcast with {}", messageOriginator);
+    }
+
+    public <T> void addIncoming(Class<T> type, T handler) {
+        String channelKey = type.getName();
+        lock.lock();
+        try {
+            if (channels.add(channelKey)) {
+                protocolStack.getTop().dispatch(new ChannelAvailable(messageOriginator, group, channelKey, address));
+            }
+        } finally {
+            lock.unlock();
+        }
+        hub.addIncoming(channelKey, new TypeCastDispatch<MethodInvocation, Object>(MethodInvocation.class, new ReflectionDispatch(handler)));
+    }
+
+    public void stop() {
+        new CompositeStoppable().add(protocolStack, hub, executor).stop();
+    }
+
+    private class IncomingConnectionAction implements Action<ConnectEvent<Connection<Message>>> {
+        public void execute(ConnectEvent<Connection<Message>> connectionConnectEvent) {
+            hub.addConnection(connectionConnectEvent.getConnection());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
new file mode 100644
index 0000000..dd17c6f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.inet.InetEndpoint;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class DefaultMessageSerializer<T> implements MessageSerializer<T> {
+    private final ClassLoader classLoader;
+
+    public DefaultMessageSerializer(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public T read(DataInputStream inputStream, InetEndpoint localAddress, InetEndpoint remoteAddress) throws Exception {
+        return (T) Message.receive(inputStream, classLoader);
+    }
+
+    public void write(T message, DataOutputStream outputStream) throws Exception {
+        Message.send(message, outputStream);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
old mode 100644
new mode 100755
index ceb66fe..b4cfa7a
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
@@ -1,40 +1,49 @@
-/*
- * 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.messaging.remote.MessagingClient;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.net.URI;
-
-public class DefaultMessagingClient implements MessagingClient {
-    private final ObjectConnection connection;
-
-    public DefaultMessagingClient(MultiChannelConnector connector, ClassLoader classLoader, URI 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);
-    }
-
-    public ObjectConnection getConnection() {
-        return connection;
-    }
-
-    public void stop() {
-        connection.stop();
-    }
-}
+/*
+ * 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.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DefaultMessagingClient implements MessagingClient, Stoppable {
+    private final Set<ObjectConnection> connections = new HashSet<ObjectConnection>();
+    private final MultiChannelConnector connector;
+    private final ClassLoader classLoader;
+
+    public DefaultMessagingClient(MultiChannelConnector connector, ClassLoader classLoader) {
+        this.connector = connector;
+        this.classLoader = classLoader;
+    }
+
+    public ObjectConnection getConnection(Address address) {
+        MultiChannelConnection<Object> connection = connector.connect(address);
+        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(connection);
+        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(connection);
+        ObjectConnection objectConnection = new DefaultObjectConnection(connection, connection, outgoing, incoming);
+        connections.add(objectConnection);
+        return objectConnection;
+    }
+
+    public void stop() {
+        new CompositeStoppable(connections).stop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
old mode 100644
new mode 100755
index 61f3609..a095887
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
@@ -1,96 +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.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.net.URI;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class DefaultMessagingServer implements MessagingServer {
-    private final MultiChannelConnector connector;
-    private final ClassLoader classLoader;
-    private final Set<ObjectConnection> connections = new CopyOnWriteArraySet<ObjectConnection>();
-
-    public DefaultMessagingServer(MultiChannelConnector connector, ClassLoader classLoader) {
-        this.connector = connector;
-        this.classLoader = classLoader;
-    }
-
-    public URI accept(final Action<ConnectEvent<ObjectConnection>> action) {
-        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Object>>>() {
-            public void execute(ConnectEvent<MultiChannelConnection<Object>> connectEvent) {
-                finishConnect(connectEvent, action);
-            }
-        });
-    }
-
-    private void finishConnect(ConnectEvent<MultiChannelConnection<Object>> connectEvent,
-                               Action<ConnectEvent<ObjectConnection>> action) {
-        MultiChannelConnection<Object> messageConnection = connectEvent.getConnection();
-        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(classLoader, messageConnection);
-        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(messageConnection);
-        AtomicReference<ObjectConnection> connectionRef = new AtomicReference<ObjectConnection>();
-        AsyncStoppable stopControl = new ConnectionAsyncStoppable(messageConnection, connectionRef);
-
-        DefaultObjectConnection connection = new DefaultObjectConnection(messageConnection, stopControl, outgoing, incoming);
-        connectionRef.set(connection);
-        connections.add(connection);
-        action.execute(new ConnectEvent<ObjectConnection>(connection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
-    }
-
-    public void stop() {
-        for (ObjectConnection connection : connections) {
-            connection.requestStop();
-        }
-        try {
-            new CompositeStoppable(connections).stop();
-        } finally {
-            connections.clear();
-        }
-    }
-
-    private class ConnectionAsyncStoppable implements AsyncStoppable {
-        private final MultiChannelConnection<Object> messageConnection;
-        private final AtomicReference<ObjectConnection> connectionRef;
-
-        public ConnectionAsyncStoppable(MultiChannelConnection<Object> messageConnection,
-                                        AtomicReference<ObjectConnection> connectionRef) {
-            this.messageConnection = messageConnection;
-            this.connectionRef = connectionRef;
-        }
-
-        public void requestStop() {
-            messageConnection.requestStop();
-        }
-
-        public void stop() {
-            try {
-                messageConnection.stop();
-            } finally {
-                connections.remove(connectionRef.get());
-            }
-        }
-    }
-}
+/*
+ * 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.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class DefaultMessagingServer implements MessagingServer, Stoppable {
+    private final MultiChannelConnector connector;
+    private final ClassLoader classLoader;
+    private final Set<ObjectConnection> connections = new CopyOnWriteArraySet<ObjectConnection>();
+
+    public DefaultMessagingServer(MultiChannelConnector connector, ClassLoader classLoader) {
+        this.connector = connector;
+        this.classLoader = classLoader;
+    }
+
+    public Address accept(final Action<ConnectEvent<ObjectConnection>> action) {
+        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Object>>>() {
+            public void execute(ConnectEvent<MultiChannelConnection<Object>> connectEvent) {
+                finishConnect(connectEvent, action);
+            }
+        });
+    }
+
+    private void finishConnect(ConnectEvent<MultiChannelConnection<Object>> connectEvent,
+                               Action<ConnectEvent<ObjectConnection>> action) {
+        MultiChannelConnection<Object> messageConnection = connectEvent.getConnection();
+        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(messageConnection);
+        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(messageConnection);
+        AtomicReference<ObjectConnection> connectionRef = new AtomicReference<ObjectConnection>();
+        AsyncStoppable stopControl = new ConnectionAsyncStoppable(messageConnection, connectionRef);
+
+        DefaultObjectConnection connection = new DefaultObjectConnection(messageConnection, stopControl, outgoing, incoming);
+        connectionRef.set(connection);
+        connections.add(connection);
+        action.execute(new ConnectEvent<ObjectConnection>(connection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
+    }
+
+    public void stop() {
+        for (ObjectConnection connection : connections) {
+            connection.requestStop();
+        }
+        try {
+            new CompositeStoppable(connections).stop();
+        } finally {
+            connections.clear();
+        }
+    }
+
+    private class ConnectionAsyncStoppable implements AsyncStoppable {
+        private final MultiChannelConnection<Object> messageConnection;
+        private final AtomicReference<ObjectConnection> connectionRef;
+
+        public ConnectionAsyncStoppable(MultiChannelConnection<Object> messageConnection,
+                                        AtomicReference<ObjectConnection> connectionRef) {
+            this.messageConnection = messageConnection;
+            this.connectionRef = connectionRef;
+        }
+
+        public void requestStop() {
+            messageConnection.requestStop();
+        }
+
+        public void stop() {
+            try {
+                messageConnection.stop();
+            } finally {
+                connections.remove(connectionRef.get());
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
old mode 100644
new mode 100755
index 1ffa534..d768a25
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
@@ -1,173 +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.messaging.remote.internal;
-
-import org.gradle.api.GradleException;
-import org.gradle.messaging.concurrent.*;
-import org.gradle.messaging.dispatch.*;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.*;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-class DefaultMultiChannelConnection implements MultiChannelConnection<Object> {
-    private final URI sourceAddress;
-    private final URI destinationAddress;
-    private final EndOfStreamDispatch outgoingDispatch;
-    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<Object> connection;
-
-    DefaultMultiChannelConnection(ExecutorFactory executorFactory, String displayName, final Connection<Object> connection, URI sourceAddress, URI destinationAddress) {
-        this.connection = connection;
-        this.executor = executorFactory.create(displayName);
-
-        this.sourceAddress = sourceAddress;
-        this.destinationAddress = destinationAddress;
-
-        // Outgoing pipeline: <source> -> <channel-mux> -> <end-of-stream-dispatch> -> <async-queue> -> <ignore-failures> -> <connection>
-        outgoingQueue = new AsyncDispatch<Object>(executor);
-        outgoingQueue.dispatchTo(wrapFailures(connection));
-        outgoingDispatch = new EndOfStreamDispatch(new ChannelMessageMarshallingDispatch(outgoingQueue));
-
-        // Incoming pipeline: <connection> -> <async-receive> -> <ignore-failures> -> <end-of-stream-filter> -> <channel-demux> -> <channel-async-queue> -> <ignore-failures> -> <handler>
-        incomingDemux = new IncomingDemultiplex();
-        incomingDispatch = new EndOfStreamFilter(incomingDemux, new Runnable() {
-            public void run() {
-                requestStop();
-            }
-        });
-        incomingReceive = new AsyncReceive<Object>(executor, wrapFailures(new ChannelMessageUnmarshallingDispatch(incomingDispatch)));
-        incomingReceive.receiveFrom(new EndOfStreamReceive(connection));
-    }
-
-    private Dispatch<Object> wrapFailures(Dispatch<Object> dispatch) {
-        return new DiscardOnFailureDispatch<Object>(dispatch, LoggerFactory.getLogger(DefaultMultiChannelConnector.class));
-    }
-
-    public URI getLocalAddress() {
-        if (sourceAddress == null) {
-            throw new UnsupportedOperationException();
-        }
-        return sourceAddress;
-    }
-
-    public URI getRemoteAddress() {
-        if (destinationAddress == null) {
-            throw new UnsupportedOperationException();
-        }
-        return destinationAddress;
-    }
-
-    public void requestStop() {
-        outgoingDispatch.stop();
-    }
-
-    public void addIncomingChannel(Object channelKey, Dispatch<Object> dispatch) {
-        incomingDemux.addIncomingChannel(channelKey, wrapFailures(dispatch));
-    }
-
-    public Dispatch<Object> addOutgoingChannel(Object channelKey) {
-        return new OutgoingMultiplex(channelKey, outgoingDispatch);
-    }
-
-    public void stop() {
-        executor.execute(new Runnable() {
-            public void run() {
-                // End-of-stream handshake
-                requestStop();
-                incomingDispatch.stop();
-
-                // Flush queues (should be empty)
-                incomingReceive.requestStop();
-                outgoingQueue.requestStop();
-                new CompositeStoppable(outgoingQueue, connection, incomingReceive, incomingDemux).stop();
-            }
-        });
-        try {
-            executor.stop(120, TimeUnit.SECONDS);
-        } catch (Throwable e) {
-            throw new GradleException("Could not stop connection.", e);
-        }
-    }
-
-    private class IncomingDemultiplex implements Dispatch<Object>, Stoppable {
-        private final Lock queueLock = new ReentrantLock();
-        private final Map<Object, AsyncDispatch<Object>> incomingQueues
-                = new HashMap<Object, AsyncDispatch<Object>>();
-
-        public void dispatch(Object message) {
-            ChannelMessage channelMessage = (ChannelMessage) message;
-            Dispatch<Object> channel = findChannel(channelMessage.getChannel());
-            channel.dispatch(channelMessage.getPayload());
-        }
-
-        public void addIncomingChannel(Object channel, Dispatch<Object> dispatch) {
-            AsyncDispatch<Object> queue = findChannel(channel);
-            queue.dispatchTo(dispatch);
-        }
-
-        private AsyncDispatch<Object> findChannel(Object channel) {
-            AsyncDispatch<Object> queue;
-            queueLock.lock();
-            try {
-                queue = incomingQueues.get(channel);
-                if (queue == null) {
-                    queue = new AsyncDispatch<Object>(executor);
-                    incomingQueues.put(channel, queue);
-                }
-            } finally {
-                queueLock.unlock();
-            }
-            return queue;
-        }
-
-        public void stop() {
-            Stoppable stopper;
-            queueLock.lock();
-            try {
-                stopper = new CompositeStoppable(incomingQueues.values());
-                incomingQueues.clear();
-            } finally {
-                queueLock.unlock();
-            }
-
-            stopper.stop();
-        }
-    }
-
-    private static class OutgoingMultiplex implements Dispatch<Object> {
-        private final Dispatch<Object> dispatch;
-        private final Object channelKey;
-
-        private OutgoingMultiplex(Object channelKey, Dispatch<Object> dispatch) {
-            this.channelKey = channelKey;
-            this.dispatch = dispatch;
-        }
-
-        public void dispatch(Object message) {
-            dispatch.dispatch(new ChannelMessage(channelKey, message));
-        }
-    }
-}
+/*
+ * 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.Address;
+
+class DefaultMultiChannelConnection implements MultiChannelConnection<Object> {
+    private final Address sourceAddress;
+    private final Address destinationAddress;
+    private final MessageHub hub;
+
+    DefaultMultiChannelConnection(MessageHub hub, Connection<Message> connection, Address sourceAddress, Address destinationAddress) {
+        this.hub = hub;
+        this.sourceAddress = sourceAddress;
+        this.destinationAddress = destinationAddress;
+
+        hub.addConnection(connection);
+    }
+
+    public Address getLocalAddress() {
+        if (sourceAddress == null) {
+            throw new UnsupportedOperationException();
+        }
+        return sourceAddress;
+    }
+
+    public Address getRemoteAddress() {
+        if (destinationAddress == null) {
+            throw new UnsupportedOperationException();
+        }
+        return destinationAddress;
+    }
+
+    public void addIncomingChannel(String channelKey, final Dispatch<Object> dispatch) {
+        hub.addIncoming(channelKey, dispatch);
+    }
+
+    public Dispatch<Object> addOutgoingChannel(String channelKey) {
+        return hub.addUnicastOutgoing(channelKey);
+    }
+
+    public void requestStop() {
+        hub.requestStop();
+    }
+
+    public void stop() {
+        requestStop();
+        hub.stop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
old mode 100644
new mode 100755
index 4c25098..b1c88f4
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
@@ -1,67 +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.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.Stoppable;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.remote.ConnectEvent;
-
-import java.net.URI;
-
-public class DefaultMultiChannelConnector implements MultiChannelConnector, Stoppable {
-    private final OutgoingConnector outgoingConnector;
-    private final ExecutorFactory executorFactory;
-    private final StoppableExecutor executorService;
-    private final HandshakeIncomingConnector incomingConnector;
-
-    public DefaultMultiChannelConnector(OutgoingConnector outgoingConnector, IncomingConnector incomingConnector,
-                                        ExecutorFactory executorFactory) {
-        this.outgoingConnector = new HandshakeOutgoingConnector(outgoingConnector);
-        this.executorFactory = executorFactory;
-        executorService = executorFactory.create("Incoming Connection Handler");
-        this.incomingConnector = new HandshakeIncomingConnector(incomingConnector, executorService);
-    }
-
-    public void stop() {
-        executorService.stop();
-    }
-
-    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<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<Object>>(channelConnection, localAddress, remoteAddress));
-    }
-
-    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);
-    }
-}
+/*
+ * 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.internal.Stoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.UUIDGenerator;
+
+import java.util.UUID;
+
+public class DefaultMultiChannelConnector implements MultiChannelConnector, Stoppable {
+    private final OutgoingConnector<Message> outgoingConnector;
+    private final ExecutorFactory executorFactory;
+    private final StoppableExecutor executorService;
+    private final HandshakeIncomingConnector incomingConnector;
+    private final IdGenerator<UUID> idGenerator = new UUIDGenerator();
+    private final ClassLoader messagingClassLoader;
+
+    public DefaultMultiChannelConnector(OutgoingConnector<Message> outgoingConnector, IncomingConnector<Message> incomingConnector,
+                                        ExecutorFactory executorFactory, ClassLoader messagingClassLoader) {
+        this.messagingClassLoader = messagingClassLoader;
+        this.outgoingConnector = new HandshakeOutgoingConnector(outgoingConnector);
+        this.executorFactory = executorFactory;
+        executorService = executorFactory.create("Incoming Connection Handler");
+        this.incomingConnector = new HandshakeIncomingConnector(incomingConnector, executorService);
+    }
+
+    public void stop() {
+        executorService.stop();
+    }
+
+    public Address accept(final Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
+        Action<ConnectEvent<Connection<Message>>> connectAction = new Action<ConnectEvent<Connection<Message>>>() {
+            public void execute(ConnectEvent<Connection<Message>> event) {
+                finishConnect(event, action);
+            }
+        };
+        return incomingConnector.accept(connectAction, false);
+    }
+
+    private void finishConnect(ConnectEvent<Connection<Message>> event,
+                               Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
+        Address localAddress = event.getLocalAddress();
+        Address remoteAddress = event.getRemoteAddress();
+        MessageHub hub = new MessageHub(String.format("Incoming Connection %s", localAddress), "message server", executorFactory, idGenerator, messagingClassLoader);
+        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(hub, event.getConnection(), localAddress, remoteAddress);
+        action.execute(new ConnectEvent<MultiChannelConnection<Object>>(channelConnection, localAddress, remoteAddress));
+    }
+
+    public MultiChannelConnection<Object> connect(Address destinationAddress) {
+        Connection<Message> connection = outgoingConnector.connect(destinationAddress);
+        MessageHub hub = new MessageHub(String.format("Outgoing Connection %s", destinationAddress), "message client", executorFactory, idGenerator, messagingClassLoader);
+        return new DefaultMultiChannelConnection(hub, connection, null, destinationAddress);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
old mode 100644
new mode 100755
index cbe7248..e1f621c
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
@@ -1,67 +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.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Addressable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.net.URI;
-
-public class DefaultObjectConnection implements ObjectConnection {
-    private final Addressable addressable;
-    private final AsyncStoppable stopControl;
-    private final OutgoingMethodInvocationHandler outgoing;
-    private final IncomingMethodInvocationHandler incoming;
-
-    public DefaultObjectConnection(Addressable addressable, AsyncStoppable stopControl,
-                                   OutgoingMethodInvocationHandler outgoing, IncomingMethodInvocationHandler incoming) {
-        this.addressable = addressable;
-        this.stopControl = stopControl;
-        this.outgoing = outgoing;
-        this.incoming = incoming;
-    }
-
-    public URI getRemoteAddress() {
-        return addressable.getRemoteAddress();
-    }
-
-    public URI getLocalAddress() {
-        return addressable.getLocalAddress();
-    }
-
-    public <T> void addIncoming(Class<T> type, T instance) {
-        incoming.addIncoming(type, instance);
-    }
-
-    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-        incoming.addIncoming(type, dispatch);
-    }
-
-    public <T> T addOutgoing(Class<T> type) {
-        return outgoing.addOutgoing(type);
-    }
-
-    public void requestStop() {
-        stopControl.requestStop();
-    }
-
-    public void stop() {
-        stopControl.stop();
-    }
-}
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.Addressable;
+import org.gradle.messaging.remote.ObjectConnection;
+
+public class DefaultObjectConnection implements ObjectConnection {
+    private final Addressable addressable;
+    private final AsyncStoppable stopControl;
+    private final OutgoingMethodInvocationHandler outgoing;
+    private final IncomingMethodInvocationHandler incoming;
+
+    public DefaultObjectConnection(Addressable addressable, AsyncStoppable stopControl,
+                                   OutgoingMethodInvocationHandler outgoing, IncomingMethodInvocationHandler incoming) {
+        this.addressable = addressable;
+        this.stopControl = stopControl;
+        this.outgoing = outgoing;
+        this.incoming = incoming;
+    }
+
+    public Address getRemoteAddress() {
+        return addressable.getRemoteAddress();
+    }
+
+    public Address getLocalAddress() {
+        return addressable.getLocalAddress();
+    }
+
+    public <T> void addIncoming(Class<T> type, T instance) {
+        incoming.addIncoming(type, instance);
+    }
+
+    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+        incoming.addIncoming(type, dispatch);
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        return outgoing.addOutgoing(type);
+    }
+
+    public void requestStop() {
+        stopControl.requestStop();
+    }
+
+    public void stop() {
+        stopControl.stop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
new file mode 100644
index 0000000..bc5bb18
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2011 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.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.DispatchFailureHandler;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.LookupRequest;
+import org.gradle.util.IdGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOutgoingBroadcast.class);
+    private final MessageOriginator messageOriginator;
+    private final String group;
+    private final OutgoingConnector<Message> outgoingConnector;
+    private final ProtocolStack<DiscoveryMessage> discoveryBroadcast;
+    private final Lock lock = new ReentrantLock();
+    private final StoppableExecutor executor;
+    private final Set<String> channels = new HashSet<String>();
+    private final Set<Address> connections = new HashSet<Address>();
+    private final MessageHub hub;
+
+    public DefaultOutgoingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, OutgoingConnector<Message> outgoingConnector, ExecutorFactory executorFactory, final IdGenerator<?> idGenerator, ClassLoader messagingClassLoader) {
+        this.messageOriginator = messageOriginator;
+        this.group = group;
+        this.outgoingConnector = outgoingConnector;
+        DispatchFailureHandler<Object> failureHandler = new DiscardingFailureHandler<Object>(LOGGER);
+
+        hub = new MessageHub("outgoing broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
+
+        executor = executorFactory.create("broadcast lookup");
+        discoveryBroadcast = new ProtocolStack<DiscoveryMessage>(executor, failureHandler, failureHandler, new ChannelLookupProtocol());
+        connection.dispatchTo(new GroupMessageFilter(group, discoveryBroadcast.getBottom()));
+        discoveryBroadcast.getBottom().dispatchTo(connection);
+        discoveryBroadcast.getTop().dispatchTo(new DiscoveryMessageDispatch());
+
+        LOGGER.info("Created OutgoingBroadcast with {}", messageOriginator);
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        String channelKey = type.getName();
+        lock.lock();
+        try {
+            if (channels.add(channelKey)) {
+                discoveryBroadcast.getTop().dispatch(new LookupRequest(messageOriginator, group, channelKey));
+            }
+        } finally {
+            lock.unlock();
+        }
+        return new ProxyDispatchAdapter<T>(hub.addMulticastOutgoing(channelKey), type).getSource();
+    }
+
+    public void stop() {
+        CompositeStoppable stoppable = new CompositeStoppable();
+        lock.lock();
+        try {
+            stoppable.add(hub, discoveryBroadcast, executor);
+        } finally {
+            connections.clear();
+            lock.unlock();
+        }
+        stoppable.stop();
+    }
+
+    private class DiscoveryMessageDispatch implements Dispatch<DiscoveryMessage> {
+        public void dispatch(DiscoveryMessage message) {
+            if (message instanceof ChannelAvailable) {
+                ChannelAvailable available = (ChannelAvailable) message;
+                Address serviceAddress = available.getAddress();
+                lock.lock();
+                try {
+                    if (!channels.contains(available.getChannel())) {
+                        return;
+                    }
+                    if (connections.contains(serviceAddress)) {
+                        return;
+                    }
+                    connections.add(serviceAddress);
+                } finally {
+                    lock.unlock();
+                }
+
+                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress);
+                hub.addConnection(syncConnection);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DelegatingConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DelegatingConnection.java
new file mode 100644
index 0000000..8ea268b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DelegatingConnection.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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;
+
+public class DelegatingConnection<T> implements Connection<T> {
+    private final Connection<T> delegate;
+
+    public DelegatingConnection(Connection<T> delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+
+    public T receive() {
+        return delegate.receive();
+    }
+
+    public void dispatch(T message) {
+        delegate.dispatch(message);
+    }
+
+    public void requestStop() {
+        delegate.requestStop();
+    }
+
+    public void stop() {
+        delegate.stop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java
new file mode 100644
index 0000000..860c3e7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * A {@code DisconnectAwareConnection} is a connection capable of executing an action when
+ * the other side of connection disconnects before the stop method is called on this connection.
+ * <p>
+ * Implementations must guarantee that the disconnect action completes before {@code null} is returned from
+ * the {@link #receive()} method. Furthermore, if a disconnect action has not yet been set the {@code receive()} method
+ * MUST NOT return {@code null} until a disconnection action is set and executed. This means that if {@link #labelonDisconnect(Runnable)} is
+ * never called on a {@code DisconnectAwareConnection}, it's {@code receive()} method will never return null as it will block indefinitely.
+ */
+public interface DisconnectAwareConnection<T> extends Connection<T> {
+
+    /**
+     * Used to specify the action to take when a disconnection is detected.
+     * <p>
+     * It is guaranteed that calling {@code receive()} on this connection will forever return {@code null} after
+     * the disconnect action has been started.
+     * <p>
+     * If this connection has an associates disconnect action at the time a disconnection is detected, it is guaranteed
+     * to be invoked <b>before</b> any call to {@code receive()} will return null.
+     * <p>
+     * If the {@code stop()} method is called on this connection before a disconnection is detected, the disconnect action
+     * will never be called.
+     * 
+     * @param disconnectAction The action to perform on disconnection, or {@code null} to remove any existing action.
+     * @return The previous disconnect action, or {@code null} if no action had been previously registered.
+     */
+    Runnable onDisconnect(Runnable disconnectAction);
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
new file mode 100644
index 0000000..6cecaa3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2011 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.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A {@link DisconnectAwareConnection} implementation that decorates an existing connection, adding disconnect awareness.
+ * <p>
+ * This implementation uses {@link EagerReceiveBuffer} internally to receive messages as fast as they are sent.
+ * The messages are then collected by {@link #receive()} as per normal.
+ * <p>
+ * NOTE: due to the use of a bounded buffer, disconnection may not be detected immediately if the internal buffer is full.
+ */
+public class DisconnectAwareConnectionDecorator<T> extends DelegatingConnection<T> implements DisconnectAwareConnection<T> {
+
+    private static final Logger LOGGER = Logging.getLogger(DisconnectAwareConnectionDecorator.class);
+    private static final int DEFAULT_BUFFER_SIZE = 200;
+
+    private final Lock actionLock = new ReentrantLock();
+    private final CountDownLatch actionSetLatch = new CountDownLatch(1);
+    private final EagerReceiveBuffer<T> receiveBuffer;
+    private Runnable disconnectAction;
+
+    private volatile boolean stopped;
+
+    public DisconnectAwareConnectionDecorator(Connection<T> connection, StoppableExecutor executor) {
+        this(connection, executor, DEFAULT_BUFFER_SIZE);
+    }
+
+    public DisconnectAwareConnectionDecorator(Connection<T> connection, StoppableExecutor executor, int bufferSize) {
+        super(connection);
+
+        // EagerReceiveBuffer guaranteest that onReceiversExhausted() will be completed before it returns null from receive(),
+        // which means we satisfy the condition of the DisconnectAwareConnection contract that disconnect handlers must complete
+        // before receive() returns null.
+
+        receiveBuffer = new EagerReceiveBuffer<T>(executor, bufferSize, connection, new Runnable() {
+            public void run() {
+                invokeDisconnectAction();
+            }
+        });
+
+        receiveBuffer.start();
+    }
+
+    public Runnable onDisconnect(Runnable disconnectAction) {
+        actionLock.lock();
+        try {
+            Runnable previous = disconnectAction;
+            this.disconnectAction = disconnectAction;
+            actionSetLatch.countDown();
+            return previous;
+        } finally {
+            actionLock.unlock();
+        }
+    }
+
+    public T receive() {
+        return receiveBuffer.receive();
+    }
+
+    private void invokeDisconnectAction() {
+        if (!stopped) {
+            try {
+                actionSetLatch.await();
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+
+            actionLock.lock();
+            try {
+                if (disconnectAction != null) {
+                    LOGGER.debug("about to invoke disconnection handler {}", disconnectAction);
+                    try {
+                        disconnectAction.run();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        LOGGER.error("disconnection handler threw exception", e);
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                    LOGGER.info("completed disconnection handler {}", disconnectAction);
+                }
+            } finally {
+                actionLock.unlock();
+            }
+        }
+    }
+
+    public void requestStop() {
+        stopped = true;
+        onDisconnect(null);
+        super.requestStop();
+    }
+
+    public void stop() {
+        stopped = true;
+        onDisconnect(null);
+        super.stop();
+        receiveBuffer.stop();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
new file mode 100644
index 0000000..bea80d3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2011 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.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.dispatch.Receive;
+import org.gradle.messaging.dispatch.AsyncReceive;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Continuously consumes from on or more receivers, serialising to an in memory buffer for synchronous consumption.
+ * <p>
+ * Messages from the same receive instance are guaranteed to always be returned from {@link #receive()} in sequence. However, no
+ * guarantee is made to deliver messages from different sources in chronological order when multiple multiple receive instances
+ * are being consumed from.
+ * <p>
+ * The buffer is bounded, the size of which is specified at construction or defaulting to {@value DEFAULT_BUFFER_SIZE}.
+ * If the buffer fills, the receive threads will block until space becomes available. If a stop is initiated while
+ * a thread is waiting for free space in the buffer after having received a message, that message will be discarded.
+ * <p>
+ * If a stop is initiated while a receive thread is waiting to receive (i.e. is blocked in a {@code receive()} call to the source),
+ * the stop will block until this returns. Therefore, it is advised to try to externally stop each of the receive instances being
+ * used by the buffer before initiating a stop on the buffer.
+ */
+public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
+
+    private enum State {
+        Init, Started, Stopping, Stopped
+    }
+
+    private static final Logger LOGGER = Logging.getLogger(EagerReceiveBuffer.class);
+    private static final int DEFAULT_BUFFER_SIZE = 200;
+
+    private final Lock lock = new ReentrantLock();
+    private final Condition notFullOrStop  = lock.newCondition();
+    private final Condition notEmptyOrNoReceivers = lock.newCondition();
+
+
+    private final StoppableExecutor executor;
+    private final int bufferSize;
+    private final Collection<Receive<T>> receivers;
+    private final Runnable onReceiversExhausted;
+    private final CountDownLatch onReceiversExhaustedFinishedLatch = new CountDownLatch(1);
+
+    private final AsyncReceive<T> asyncReceive;
+    private final LinkedList<T> queue = new LinkedList<T>();
+
+    private boolean hasActiveReceivers = true;
+    private State state = State.Init;
+
+    private static <T> Collection<Receive<T>> toReceiveCollection(Receive<T> receiver) {
+        Collection<Receive<T>> list = new ArrayList<Receive<T>>(1);
+        list.add(receiver);
+        return list;
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Receive<T> receiver) {
+        this(executor, DEFAULT_BUFFER_SIZE, toReceiveCollection(receiver), null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Receive<T> receiver, Runnable onReceiversExhausted) {
+        this(executor, DEFAULT_BUFFER_SIZE, toReceiveCollection(receiver), onReceiversExhausted);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Collection<Receive<T>> receivers) {
+        this(executor, DEFAULT_BUFFER_SIZE, receivers, null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Collection<Receive<T>> receivers, Runnable onReceiversExhausted) {
+        this(executor, DEFAULT_BUFFER_SIZE, receivers, onReceiversExhausted);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Receive<T> receiver) {
+        this(executor, bufferSize, toReceiveCollection(receiver), null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Receive<T> receiver, Runnable onReceiversExhausted) {
+        this(executor, bufferSize, toReceiveCollection(receiver), onReceiversExhausted);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Collection<Receive<T>> receivers) {
+        this(executor, bufferSize, receivers, null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, final int bufferSize, Collection<Receive<T>> receivers, final Runnable onReceiversExhausted) {
+        if (receivers.size() == 0) {
+            throw new IllegalArgumentException("eager receive buffer created with no receivers");
+        }
+
+        if (bufferSize < 1) {
+            throw new IllegalArgumentException("eager receive buffer size must be positive (value given: " + bufferSize + ")");
+        }
+
+        this.executor = executor;
+        this.bufferSize = bufferSize;
+        this.receivers = receivers;
+        this.onReceiversExhausted = onReceiversExhausted;
+
+        Dispatch<T> dispatch = new Dispatch<T>() {
+            public void dispatch(T message) {
+                lock.lock();
+                try {
+                    while (queue.size() == bufferSize && state == State.Started) {
+                        try {
+                            notFullOrStop.await();
+                        } catch (InterruptedException e) {
+                            throw UncheckedException.throwAsUncheckedException(e);
+                        }
+                    }
+
+                    queue.add(message);
+                    notEmptyOrNoReceivers.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        };
+
+        this.asyncReceive = new AsyncReceive(executor, dispatch, new Runnable() {
+            public void run() {
+                lock.lock();
+                try {
+                    hasActiveReceivers = false;
+                    if (onReceiversExhausted != null) {
+                        onReceiversExhausted.run();
+                    }
+                    notEmptyOrNoReceivers.signalAll();
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                } finally {
+                    lock.unlock();
+                    onReceiversExhaustedFinishedLatch.countDown();
+                }
+            }
+        });
+    }
+
+    /**
+     * Start consuming from the receivers given at construction.
+     *
+     * @throws IllegalStateException if already started
+     */
+    public void start() {
+        lock.lock();
+        try {
+            if (state != State.Init) {
+                throw new IllegalStateException("this eager receive buffer has already been started");
+            }
+            state = State.Started;
+
+            for (Receive<T> receiver : receivers) {
+                asyncReceive.receiveFrom(receiver);
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Receive the next message from the buffer.
+     *
+     * @return The next message or {@code null} if there are no more messages and no unexhausted receivers.
+     */
+    public T receive() {
+        lock.lock();
+        try {
+            while (queue.isEmpty() && hasActiveReceivers) {
+                try {
+                    notEmptyOrNoReceivers.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+
+            if (queue.isEmpty()) {
+                // no more messages, and all receivers are exhausted
+                assert !hasActiveReceivers;
+                return null;
+            } else {
+                T message = queue.poll();
+                assert message != null;
+                notFullOrStop.signalAll();
+                return message;
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Stops receiving new messages.
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        asyncReceive.requestStop();
+        if (hasActiveReceivers) {
+            setState(State.Stopping);
+        } else {
+            setState(State.Stopped);
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        notFullOrStop.signalAll(); // wake up any consumers waiting for space (assume it's a stopish state)
+    }
+
+    /**
+     * Stops receiving new messages. Blocks until all queued messages have been delivered.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+
+        // Have to relinquish lock at this point because the onReceiversExhausted callback that we pass to the async
+        // runnable needs to acquire the lock in order to signal the notEmptyOrNoReceivers condition. If we didn't
+        // relinquish we would have deadlock. This is harmless due to this method being idempotent.
+        try {
+            onReceiversExhaustedFinishedLatch.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        lock.lock();
+        try {
+            asyncReceive.stop();
+            setState(State.Stopped);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
deleted file mode 100644
index ff977e1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
+++ /dev/null
@@ -1,59 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.StoppableDispatch;
-
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-public class EndOfStreamDispatch implements StoppableDispatch<Object> {
-    private final Dispatch<Object> dispatch;
-    private boolean stopped;
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
-
-    public EndOfStreamDispatch(Dispatch<Object> dispatch) {
-        this.dispatch = dispatch;
-    }
-
-    public void dispatch(Object message) {
-        lock.readLock().lock();
-        try {
-            if (stopped) {
-                throw new IllegalStateException(String.format(
-                        "Cannot dispatch message %s, as this dispatch has been stopped.", message));
-            }
-            dispatch.dispatch(message);
-        } finally {
-            lock.readLock().unlock();
-        }
-    }
-
-    public void stop() {
-        lock.writeLock().lock();
-        try {
-            if (stopped) {
-                return;
-            }
-            stopped = true;
-            dispatch.dispatch(new EndOfStreamEvent());
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamEvent.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamEvent.java
deleted file mode 100644
index 4fa4f2d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamEvent.java
+++ /dev/null
@@ -1,29 +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.messaging.remote.internal;
-
-public class EndOfStreamEvent extends Message {
-    @Override
-    public boolean equals(Object obj) {
-        return obj instanceof EndOfStreamEvent;
-    }
-
-    @Override
-    public int hashCode() {
-        return 0;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
deleted file mode 100644
index 1a3713e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
+++ /dev/null
@@ -1,72 +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.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.Stoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.util.UncheckedException;
-
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-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<Object> dispatch, Runnable endOfStreamAction) {
-        this.dispatch = dispatch;
-        this.endOfStreamAction = endOfStreamAction;
-    }
-
-    public void dispatch(Object message) {
-        lock.lock();
-        try {
-            if (endOfStreamReached) {
-                throw new IllegalStateException(String.format(
-                        "Cannot dispatch message %s, as this dispatch has been stopped.", message));
-            }
-            if (message instanceof EndOfStreamEvent) {
-                endOfStreamReached = true;
-                condition.signalAll();
-                endOfStreamAction.run();
-                return;
-            }
-        } finally {
-            lock.unlock();
-        }
-        dispatch.dispatch(message);
-    }
-
-    public void stop() {
-        lock.lock();
-        try {
-            while (!endOfStreamReached) {
-                try {
-                    condition.await();
-                } catch (InterruptedException e) {
-                    throw new UncheckedException(e);
-                }
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java
deleted file mode 100644
index 7f40d09..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java
+++ /dev/null
@@ -1,43 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Receive;
-
-class EndOfStreamReceive implements Receive<Object> {
-    private final Receive<Object> receive;
-    private boolean ended;
-
-    public EndOfStreamReceive(Receive<Object> receive) {
-        this.receive = receive;
-    }
-
-    public Object receive() {
-        if (ended) {
-            return null;
-        }
-        Object message = receive.receive();
-        if (message == null) {
-            ended = true;
-            return new EndOfStreamEvent();
-        }
-        if (message instanceof EndOfStreamEvent) {
-            ended = true;
-        }
-        return message;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/GroupMessageFilter.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/GroupMessageFilter.java
new file mode 100644
index 0000000..f5ab15b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/GroupMessageFilter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.UnknownMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Filters messages for unknown groups.
+ */
+public class GroupMessageFilter implements Dispatch<DiscoveryMessage> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GroupMessageFilter.class);
+    private final Dispatch<? super DiscoveryMessage> dispatch;
+    private final String group;
+
+    public GroupMessageFilter(String group, Dispatch<? super DiscoveryMessage> dispatch) {
+        this.dispatch = dispatch;
+        this.group = group;
+    }
+
+    public void dispatch(DiscoveryMessage message) {
+        if (message instanceof UnknownMessage) {
+            LOGGER.debug("Discarding unknown message {}.", message);
+            return;
+        }
+        if (!message.getGroup().equals(group)) {
+            LOGGER.debug("Discarding message {} from unknown group {}.", message, message.getGroup());
+            return;
+        }
+        dispatch.dispatch(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
index a4f793d..d760d46 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
@@ -17,48 +17,43 @@
 package org.gradle.messaging.remote.internal;
 
 import org.gradle.api.Action;
+import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.util.UncheckedException;
+import org.gradle.messaging.remote.internal.protocol.ConnectRequest;
 
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Executor;
 
-public class HandshakeIncomingConnector implements IncomingConnector {
-    private final IncomingConnector connector;
+public class HandshakeIncomingConnector implements IncomingConnector<Message> {
+    private final IncomingConnector<Message> connector;
     private final Executor executor;
     private final Object lock = new Object();
-    private URI localAddress;
+    private Address localAddress;
     private long nextId;
-    private final Map<URI, Action<ConnectEvent<Connection<Object>>>> pendingActions = new HashMap<URI, Action<ConnectEvent<Connection<Object>>>>();
+    private final Map<Address, Action<ConnectEvent<Connection<Message>>>> pendingActions = new HashMap<Address, Action<ConnectEvent<Connection<Message>>>>();
 
-    public HandshakeIncomingConnector(IncomingConnector connector, Executor executor) {
+    public HandshakeIncomingConnector(IncomingConnector<Message> connector, Executor executor) {
         this.connector = connector;
         this.executor = executor;
     }
 
-    public URI accept(Action<ConnectEvent<Connection<Object>>> action) {
+    public Address accept(Action<ConnectEvent<Connection<Message>>> action, boolean allowRemote) {
+        assert !allowRemote;
         synchronized (lock) {
             if (localAddress == null) {
-                localAddress = connector.accept(handShakeAction());
-            }
-            
-            URI localAddress;
-            try {
-                localAddress = new URI(String.format("channel:%s!%d", this.localAddress, nextId++));
-            } catch (URISyntaxException e) {
-                throw UncheckedException.asUncheckedException(e);
+                localAddress = connector.accept(handShakeAction(), false);
             }
+
+            Address localAddress = new CompositeAddress(this.localAddress, nextId++);
             pendingActions.put(localAddress, action);
             return localAddress;
         }
     }
 
-    private Action<ConnectEvent<Connection<Object>>> handShakeAction() {
-        return new Action<ConnectEvent<Connection<Object>>>() {
-            public void execute(final ConnectEvent<Connection<Object>> connectEvent) {
+    private Action<ConnectEvent<Connection<Message>>> handShakeAction() {
+        return new Action<ConnectEvent<Connection<Message>>>() {
+            public void execute(final ConnectEvent<Connection<Message>> connectEvent) {
                 executor.execute(new Runnable() {
                     public void run() {
                         handshake(connectEvent);
@@ -68,11 +63,11 @@ public class HandshakeIncomingConnector implements IncomingConnector {
         };
     }
 
-    private void handshake(ConnectEvent<Connection<Object>> connectEvent) {
-        Connection<Object> connection = connectEvent.getConnection();
+    private void handshake(ConnectEvent<Connection<Message>> connectEvent) {
+        Connection<Message> connection = connectEvent.getConnection();
         ConnectRequest request = (ConnectRequest) connection.receive();
-        URI localAddress = request.getDestinationAddress();
-        Action<ConnectEvent<Connection<Object>>> channelConnection;
+        Address localAddress = request.getDestinationAddress();
+        Action<ConnectEvent<Connection<Message>>> channelConnection;
         synchronized (lock) {
             channelConnection = pendingActions.remove(localAddress);
         }
@@ -80,6 +75,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<Object>>(connection, localAddress, connectEvent.getRemoteAddress()));
+        channelConnection.execute(new ConnectEvent<Connection<Message>>(connection, localAddress, connectEvent.getRemoteAddress()));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
index 89b1569..57c2c14 100644
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
@@ -16,44 +16,31 @@
 
 package org.gradle.messaging.remote.internal;
 
-import org.apache.commons.lang.StringUtils;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.protocol.ConnectRequest;
 
-import java.net.URI;
-import java.net.URISyntaxException;
+public class HandshakeOutgoingConnector implements OutgoingConnector<Message> {
+    private final OutgoingConnector<Message> connector;
 
-public class HandshakeOutgoingConnector implements OutgoingConnector {
-    private final OutgoingConnector connector;
-
-    public HandshakeOutgoingConnector(OutgoingConnector connector) {
+    public HandshakeOutgoingConnector(OutgoingConnector<Message> connector) {
         this.connector = connector;
     }
 
-    public Connection<Message> connect(URI destinationAddress) {
-        if (!destinationAddress.getScheme().equals("channel")) {
-            throw new IllegalArgumentException(String.format("Cannot create a connection to destination URI with unknown scheme: %s.",
-                    destinationAddress));
+    public Connection<Message> connect(Address destinationAddress) {
+        if (!(destinationAddress instanceof CompositeAddress)) {
+            throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
         }
-        URI connectionAddress = toConnectionAddress(destinationAddress);
+        CompositeAddress compositeAddress = (CompositeAddress) destinationAddress;
+        Address connectionAddress = compositeAddress.getAddress();
         Connection<Message> connection = connector.connect(connectionAddress);
         try {
             connection.dispatch(new ConnectRequest(destinationAddress));
         } catch (Throwable e) {
             connection.stop();
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         return connection;
     }
-
-    private URI toConnectionAddress(URI destinationAddress) {
-        String content = destinationAddress.getSchemeSpecificPart();
-        URI connectionAddress;
-        try {
-            connectionAddress = new URI(StringUtils.substringBeforeLast(content, "!"));
-        } catch (URISyntaxException e) {
-            throw new UncheckedException(e);
-        }
-        return connectionAddress;
-    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingBroadcast.java
new file mode 100644
index 0000000..2aff0db
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingBroadcast.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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;
+
+public interface IncomingBroadcast {
+    <T> void addIncoming(Class<T> type, T handler);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
old mode 100644
new mode 100755
index 42e8fef..2fbc0d9
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
@@ -1,32 +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 org.gradle.api.Action;
-import org.gradle.messaging.remote.ConnectEvent;
-
-import java.net.URI;
-
-public interface IncomingConnector {
-    /**
-     * Allocates a new incoming endpoint.
-     *
-     * @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<Object>>> action);
-}
+/*
+ * 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.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+
+public interface IncomingConnector<T> {
+    /**
+     * Allocates a new incoming endpoint.
+     *
+     * @param action the action to execute on incoming connection. The supplied action is not required to be thread-safe.
+     * @param allowRemote If true, only allow connections from remote machines. If false, allow only from the local machine.
+     * @return the address of the endpoint which the connector is listening on.
+     */
+    Address accept(Action<ConnectEvent<Connection<T>>> action, boolean allowRemote);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
old mode 100644
new mode 100755
index 652b422..fe9a6f1
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
@@ -1,57 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.ReflectionDispatch;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-public class IncomingMethodInvocationHandler {
-    private final ClassLoader classLoader;
-    private final MultiChannelConnection<Object> connection;
-    private final Set<Class<?>> classes = new CopyOnWriteArraySet<Class<?>>();
-
-    public IncomingMethodInvocationHandler(ClassLoader classLoader, MultiChannelConnection<Object> connection) {
-        this.classLoader = classLoader;
-        this.connection = connection;
-    }
-
-    public <T> void addIncoming(Class<T> type, T instance) {
-        addIncoming(type, new ReflectionDispatch(instance));
-    }
-
-    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-        Set<Class<?>> incomingTypes = new HashSet<Class<?>>();
-        addInterfaces(type, incomingTypes);
-        for (Class<?> incomingType : incomingTypes) {
-            if (!classes.add(incomingType)) {
-                throw new IllegalArgumentException(String.format("A handler has already been added for type '%s'.", incomingType.getName()));
-            }
-            connection.addIncomingChannel(incomingType.getName(), new MethodInvocationUnmarshallingDispatch(dispatch, classLoader));
-        }
-    }
-
-    private void addInterfaces(Class<?> type, Set<Class<?>> superInterfaces) {
-        superInterfaces.add(type);
-        for (Class<?> superType : type.getInterfaces()) {
-            addInterfaces(superType, superInterfaces);
-        }
-    }
+/*
+ * 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class IncomingMethodInvocationHandler {
+    private final MultiChannelConnection<Object> connection;
+    private final Set<Class<?>> classes = new CopyOnWriteArraySet<Class<?>>();
+
+    public IncomingMethodInvocationHandler(MultiChannelConnection<Object> connection) {
+        this.connection = connection;
+    }
+
+    public <T> void addIncoming(Class<T> type, T instance) {
+        addIncoming(type, new ReflectionDispatch(instance));
+    }
+
+    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+        Set<Class<?>> incomingTypes = new HashSet<Class<?>>();
+        addInterfaces(type, incomingTypes);
+        for (Class<?> incomingType : incomingTypes) {
+            if (!classes.add(incomingType)) {
+                throw new IllegalArgumentException(String.format("A handler has already been added for type '%s'.", incomingType.getName()));
+            }
+            connection.addIncomingChannel(incomingType.getName(), new TypeCastDispatch<MethodInvocation, Object>(MethodInvocation.class, dispatch));
+        }
+    }
+
+    private void addInterfaces(Class<?> type, Set<Class<?>> superInterfaces) {
+        superInterfaces.add(type);
+        for (Class<?> superType : type.getInterfaces()) {
+            addInterfaces(superType, superInterfaces);
+        }
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/InputForwarder.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/InputForwarder.java
new file mode 100644
index 0000000..7b02f68
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/InputForwarder.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.DisconnectableInputStream;
+import org.gradle.util.LineBufferingOutputStream;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.AsynchronousCloseException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Asynchronously consumes from an input stream for a time,
+ * forwarding a <strong>line</strong> of input at a time to a specified action.
+ *
+ * Note that calling stop() will NOT close the source input stream.
+ */
+public class InputForwarder implements Stoppable {
+
+    private final InputStream input;
+    private final Action<String> forwardTo;
+    private final Runnable onFinish;
+    private final ExecutorFactory executorFactory;
+    private final int bufferSize;
+    private StoppableExecutor forwardingExecuter;
+    private DisconnectableInputStream disconnectableInput;
+    private LineBufferingOutputStream outputBuffer;
+    private final Lock lifecycleLock = new ReentrantLock();
+    private boolean started;
+    private boolean stopped;
+
+    public InputForwarder(InputStream input, Action<String> forwardTo, Runnable onFinish, ExecutorFactory executerFactory, int bufferSize) {
+        this.input = input;
+        this.forwardTo = forwardTo;
+        this.onFinish = onFinish;
+        this.executorFactory = executerFactory;
+        this.bufferSize = bufferSize;
+    }
+
+    public InputForwarder start() {
+        lifecycleLock.lock();
+        try {
+            if (started) {
+                throw new IllegalStateException("input forwarder has already been started");
+            }
+
+            disconnectableInput = new DisconnectableInputStream(input, executorFactory, bufferSize);
+            outputBuffer = new LineBufferingOutputStream(forwardTo, true, bufferSize);
+
+            forwardingExecuter = executorFactory.create("forward input");
+            forwardingExecuter.execute(new Runnable() {
+                public void run() {
+                    byte[] buffer = new byte[bufferSize];
+                    int readCount;
+                    try {
+                        while (true) {
+                            try {
+                                readCount = disconnectableInput.read(buffer, 0, bufferSize);
+                                if (readCount < 0) {
+                                    break;
+                                }
+                            } catch (AsynchronousCloseException e) {
+                                break;
+                            } catch (IOException e) {
+                                // Unsure what the best thing to do is here, should we forward the error?
+                                throw UncheckedException.throwAsUncheckedException(e);
+                            }
+
+                            try {
+                                outputBuffer.write(buffer, 0, readCount);
+                            } catch (IOException e) {
+                                // this shouldn't happen as outputBuffer will only throw if close has been called
+                                // and we own this object exclusively and will not have done that at this time
+                                throw UncheckedException.throwAsUncheckedException(e);
+                            }
+                        }
+                    } finally {
+                        try {
+                            outputBuffer.close(); // will flush any unterminated lines out synchronously
+                        } catch (IOException e) {
+                            throw UncheckedException.throwAsUncheckedException(e);
+                        }
+                    }
+                    
+                    onFinish.run();
+                }
+            });
+
+            started = true;
+        } finally {
+            lifecycleLock.unlock();
+        }
+        
+        return this;
+    }
+
+    public void stop() {
+        lifecycleLock.lock();
+        try {
+            if (!stopped) {
+                try {
+                    disconnectableInput.close();
+                } catch (IOException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+                
+                forwardingExecuter.stop();
+                stopped = true;
+            }
+        } finally {
+            lifecycleLock.unlock();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
old mode 100644
new mode 100755
index 0501333..190ae00
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
@@ -1,163 +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.messaging.remote.internal;
-
-import org.gradle.util.ClassLoaderObjectInputStream;
-import org.gradle.util.UncheckedException;
-
-import java.io.*;
-import java.lang.reflect.Constructor;
-
-public abstract class Message implements Serializable {
-    public static void send(Object message, OutputStream outputSteam) throws IOException {
-        ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
-        try {
-            oos.writeObject(message);
-        } finally {
-            oos.flush();
-        }
-    }
-
-    public static Object receive(InputStream inputSteam, ClassLoader classLoader)
-            throws IOException, ClassNotFoundException {
-        ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
-        return ois.readObject();
-    }
-
-    private static class ExceptionPlaceholder implements Serializable {
-        private byte[] serializedException;
-        private String type;
-        private String message;
-        private ExceptionPlaceholder cause;
-        private StackTraceElement[] stackTrace;
-
-        public ExceptionPlaceholder(final Throwable throwable) throws IOException {
-            ByteArrayOutputStream outstr = new ByteArrayOutputStream();
-            ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outstr) {
-                @Override
-                protected Object replaceObject(Object obj) throws IOException {
-                    if (obj == throwable) {
-                        return throwable;
-                    }
-                    // Don't serialize the cause - we'll serialize it separately later 
-                    if (obj == throwable.getCause()) {
-                        return new CausePlaceholder();
-                    }
-                    return super.replaceObject(obj);
-                }
-            };
-            try {
-                oos.writeObject(throwable);
-                oos.close();
-                serializedException = outstr.toByteArray();
-            } catch (NotSerializableException e) {
-                // Ignore
-            }
-
-            type = throwable.getClass().getName();
-            message = throwable.getMessage();
-            if (throwable.getCause() != null) {
-                cause = new ExceptionPlaceholder(throwable.getCause());
-            }
-            stackTrace = throwable.getStackTrace();
-        }
-
-        public Throwable read(ClassLoader classLoader) throws IOException {
-            final Throwable causeThrowable = getCause(classLoader);
-            Throwable throwable = null;
-            if (serializedException != null) {
-                try {
-                    final ExceptionReplacingObjectInputStream ois = new ExceptionReplacingObjectInputStream(new ByteArrayInputStream(serializedException), classLoader) {
-                        @Override
-                        protected Object resolveObject(Object obj) throws IOException {
-                            if (obj instanceof CausePlaceholder) {
-                                return causeThrowable;
-                            }
-                            return super.resolveObject(obj);
-                        }
-                    };
-                    throwable = (Throwable) ois.readObject();
-                } catch (ClassNotFoundException e) {
-                    // Ignore
-                } catch (InvalidClassException e) {
-                    try {
-                        Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
-                        throwable = (Throwable) constructor.newInstance(message);
-                        throwable.initCause(causeThrowable);
-                        throwable.setStackTrace(stackTrace);
-                    } catch (ClassNotFoundException e1) {
-                        // Ignore
-                    } catch (NoSuchMethodException e1) {
-                        // Ignore
-                    } catch (Throwable t) {
-                        throw UncheckedException.asUncheckedException(t);
-                    }
-                }
-            }
-
-            if (throwable == null) {
-                throwable = new PlaceholderException(String.format("%s: %s", type, message), causeThrowable);
-                throwable.setStackTrace(stackTrace);
-            }
-
-            return throwable;
-        }
-
-        private Throwable getCause(ClassLoader classLoader) throws IOException {
-            return cause != null ? cause.read(classLoader) : null;
-        }
-    }
-
-    private static class CausePlaceholder implements Serializable {
-    }
-
-    private static class TopLevelExceptionPlaceholder extends ExceptionPlaceholder {
-        private TopLevelExceptionPlaceholder(Throwable throwable) throws IOException {
-            super(throwable);
-        }
-    }
-
-    private static class ExceptionReplacingObjectOutputStream extends ObjectOutputStream {
-        public ExceptionReplacingObjectOutputStream(OutputStream outputSteam) throws IOException {
-            super(outputSteam);
-            enableReplaceObject(true);
-        }
-
-        @Override
-        protected Object replaceObject(Object obj) throws IOException {
-            if (obj instanceof Throwable) {
-                return new TopLevelExceptionPlaceholder((Throwable) obj);
-            }
-            return obj;
-        }
-    }
-
-    private static class ExceptionReplacingObjectInputStream extends ClassLoaderObjectInputStream {
-        public ExceptionReplacingObjectInputStream(InputStream inputSteam, ClassLoader classLoader) throws IOException {
-            super(inputSteam, classLoader);
-            enableResolveObject(true);
-        }
-
-        @Override
-        protected Object resolveObject(Object obj) throws IOException {
-            if (obj instanceof TopLevelExceptionPlaceholder) {
-                return ((ExceptionPlaceholder) obj).read(getClassLoader());
-            }
-            return obj;
-        }
-    }
-}
+/*
+ * 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.internal.UncheckedException;
+import org.gradle.util.ClassLoaderObjectInputStream;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+
+public abstract class Message implements Serializable {
+    public static void send(Object message, OutputStream outputSteam) throws IOException {
+        ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
+        try {
+            oos.writeObject(message);
+        } finally {
+            oos.flush();
+        }
+    }
+
+    public static Object receive(InputStream inputSteam, ClassLoader classLoader)
+            throws IOException, ClassNotFoundException {
+        ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
+        return ois.readObject();
+    }
+
+    private static class ExceptionPlaceholder implements Serializable {
+        private byte[] serializedException;
+        private String type;
+        private String message;
+        private ExceptionPlaceholder cause;
+        private StackTraceElement[] stackTrace;
+
+        public ExceptionPlaceholder(final Throwable throwable) throws IOException {
+            ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outstr) {
+                @Override
+                protected Object replaceObject(Object obj) throws IOException {
+                    if (obj == throwable) {
+                        return throwable;
+                    }
+                    // Don't serialize the cause - we'll serialize it separately later 
+                    if (obj == throwable.getCause()) {
+                        return new CausePlaceholder();
+                    }
+                    return super.replaceObject(obj);
+                }
+            };
+            try {
+                oos.writeObject(throwable);
+                oos.close();
+                serializedException = outstr.toByteArray();
+            } catch (NotSerializableException e) {
+                // Ignore
+            }
+
+            type = throwable.getClass().getName();
+            message = throwable.getMessage();
+            if (throwable.getCause() != null) {
+                cause = new ExceptionPlaceholder(throwable.getCause());
+            }
+            stackTrace = throwable.getStackTrace();
+        }
+
+        public Throwable read(ClassLoader classLoader) throws IOException {
+            final Throwable causeThrowable = getCause(classLoader);
+            Throwable throwable = null;
+            if (serializedException != null) {
+                try {
+                    final ExceptionReplacingObjectInputStream ois = new ExceptionReplacingObjectInputStream(new ByteArrayInputStream(serializedException), classLoader) {
+                        @Override
+                        protected Object resolveObject(Object obj) throws IOException {
+                            if (obj instanceof CausePlaceholder) {
+                                return causeThrowable;
+                            }
+                            return super.resolveObject(obj);
+                        }
+                    };
+                    throwable = (Throwable) ois.readObject();
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                } catch (InvalidClassException e) {
+                    try {
+                        Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
+                        throwable = (Throwable) constructor.newInstance(message);
+                        throwable.initCause(causeThrowable);
+                        throwable.setStackTrace(stackTrace);
+                    } catch (ClassNotFoundException e1) {
+                        // Ignore
+                    } catch (NoSuchMethodException e1) {
+                        // Ignore
+                    } catch (Throwable t) {
+                        throw UncheckedException.throwAsUncheckedException(t);
+                    }
+                }
+            }
+
+            if (throwable == null) {
+                throwable = new PlaceholderException(type, message, causeThrowable);
+                throwable.setStackTrace(stackTrace);
+            }
+
+            return throwable;
+        }
+
+        private Throwable getCause(ClassLoader classLoader) throws IOException {
+            return cause != null ? cause.read(classLoader) : null;
+        }
+    }
+
+    private static class CausePlaceholder implements Serializable {
+    }
+
+    private static class TopLevelExceptionPlaceholder extends ExceptionPlaceholder {
+        private TopLevelExceptionPlaceholder(Throwable throwable) throws IOException {
+            super(throwable);
+        }
+    }
+
+    private static class ExceptionReplacingObjectOutputStream extends ObjectOutputStream {
+        public ExceptionReplacingObjectOutputStream(OutputStream outputSteam) throws IOException {
+            super(outputSteam);
+            enableReplaceObject(true);
+        }
+
+        @Override
+        protected Object replaceObject(Object obj) throws IOException {
+            if (obj instanceof Throwable) {
+                return new TopLevelExceptionPlaceholder((Throwable) obj);
+            }
+            return obj;
+        }
+    }
+
+    private static class ExceptionReplacingObjectInputStream extends ClassLoaderObjectInputStream {
+        public ExceptionReplacingObjectInputStream(InputStream inputSteam, ClassLoader classLoader) throws IOException {
+            super(inputSteam, classLoader);
+            enableResolveObject(true);
+        }
+
+        @Override
+        protected Object resolveObject(Object obj) throws IOException {
+            if (obj instanceof TopLevelExceptionPlaceholder) {
+                return ((ExceptionPlaceholder) obj).read(getClassLoader());
+            }
+            return obj;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageHub.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageHub.java
new file mode 100644
index 0000000..07f87eb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageHub.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2011 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.internal.CompositeStoppable;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.DispatchFailureHandler;
+import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent;
+import org.gradle.util.IdGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class MessageHub implements AsyncStoppable {
+    private final Lock lock = new ReentrantLock();
+    private final CompositeStoppable executors = new CompositeStoppable();
+    private final CompositeStoppable connections = new CompositeStoppable();
+    private final Collection<ProtocolStack<Message>> handlers = new ArrayList<ProtocolStack<Message>>();
+    private final Collection<ProtocolStack<Message>> workers = new ArrayList<ProtocolStack<Message>>();
+    private final Map<String, ProtocolStack<Message>> outgoingUnicasts = new HashMap<String, ProtocolStack<Message>>();
+    private final Map<String, ProtocolStack<Message>> outgoingBroadcasts = new HashMap<String, ProtocolStack<Message>>();
+    private final DispatchFailureHandler<Object> failureHandler;
+    private final Router router;
+    private final String displayName;
+    private final String nodeName;
+    private final ExecutorFactory executorFactory;
+    private final IdGenerator<?> idGenerator;
+    private final ClassLoader messagingClassLoader;
+    private final StoppableExecutor incomingExecutor;
+
+    public MessageHub(String displayName, String nodeName, ExecutorFactory executorFactory, IdGenerator<?> idGenerator, ClassLoader messagingClassLoader) {
+        this.displayName = displayName;
+        this.nodeName = nodeName;
+        this.executorFactory = executorFactory;
+        this.idGenerator = idGenerator;
+        this.messagingClassLoader = messagingClassLoader;
+        failureHandler = new DiscardingFailureHandler<Object>(LoggerFactory.getLogger(MessageHub.class));
+        StoppableExecutor executor = executorFactory.create(displayName + " message router");
+        executors.add(executor);
+        router = new Router(executor, failureHandler);
+
+        incomingExecutor = executorFactory.create(displayName + " worker");
+        executors.add(incomingExecutor);
+    }
+
+    /**
+     * Adds an incoming connection. Stops the connection when finished with it.
+     */
+    public void addConnection(Connection<Message> connection) {
+        lock.lock();
+        try {
+            Connection<Message> wrapper = new EndOfStreamConnection(connection);
+            AsyncConnectionAdapter<Message> asyncConnection = new AsyncConnectionAdapter<Message>(wrapper, failureHandler, executorFactory, new RemoteDisconnectProtocol());
+            connections.add(asyncConnection);
+
+            AsyncConnection<Message> incomingEndpoint = router.createRemoteConnection();
+            incomingEndpoint.dispatchTo(new MethodInvocationMarshallingDispatch(asyncConnection));
+            asyncConnection.dispatchTo(new MethodInvocationUnmarshallingDispatch(incomingEndpoint, messagingClassLoader));
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public Dispatch<Object> addUnicastOutgoing(String channel) {
+        lock.lock();
+        try {
+            ProtocolStack<Message> outgoing = outgoingUnicasts.get(channel);
+            if (outgoing == null) {
+                Protocol<Message> unicastSendProtocol = new UnicastSendProtocol();
+                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
+                StoppableExecutor executor = executorFactory.create(displayName + " outgoing " + channel);
+                executors.add(executor);
+                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, unicastSendProtocol, sendProtocol);
+                outgoingUnicasts.put(channel, outgoing);
+
+                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
+                outgoing.getBottom().dispatchTo(outgoingEndpoint);
+                outgoingEndpoint.dispatchTo(outgoing.getBottom());
+            }
+            return new OutgoingMultiplex(channel, outgoing.getTop());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public Dispatch<Object> addMulticastOutgoing(String channel) {
+        lock.lock();
+        try {
+            ProtocolStack<Message> outgoing = outgoingBroadcasts.get(channel);
+            if (outgoing == null) {
+                Protocol<Message> broadcastProtocol = new BroadcastSendProtocol();
+                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
+                StoppableExecutor executor = executorFactory.create(displayName + " outgoing broadcast " + channel);
+                executors.add(executor);
+                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, broadcastProtocol, sendProtocol);
+                outgoingBroadcasts.put(channel, outgoing);
+
+                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
+                outgoing.getBottom().dispatchTo(outgoingEndpoint);
+                outgoingEndpoint.dispatchTo(outgoing.getBottom());
+            }
+            return new OutgoingMultiplex(channel, outgoing.getTop());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void addIncoming(final String channel, final Dispatch<Object> dispatch) {
+        lock.lock();
+        try {
+            final Object id = idGenerator.generateId();
+            Protocol<Message> workerProtocol = new WorkerProtocol(dispatch);
+            Protocol<Message> receiveProtocol = new ReceiveProtocol(id, nodeName, channel);
+
+            ProtocolStack<Message> workerStack = new ProtocolStack<Message>(incomingExecutor, failureHandler, failureHandler, workerProtocol);
+            workers.add(workerStack);
+            ProtocolStack<Message> stack = new ProtocolStack<Message>(incomingExecutor, failureHandler, failureHandler, new BufferingProtocol(200), receiveProtocol);
+            handlers.add(stack);
+
+            workerStack.getBottom().dispatchTo(stack.getTop());
+            stack.getTop().dispatchTo(workerStack.getBottom());
+
+            AsyncConnection<Message> incomingEndpoint = router.createLocalConnection();
+            stack.getBottom().dispatchTo(incomingEndpoint);
+            incomingEndpoint.dispatchTo(stack.getBottom());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void requestStop() {
+        lock.lock();
+        try {
+            for (ProtocolStack<Message> stack : outgoingUnicasts.values()) {
+                stack.requestStop();
+            }
+            for (ProtocolStack<Message> stack : outgoingBroadcasts.values()) {
+                stack.requestStop();
+            }
+            for (ProtocolStack<?> worker : workers) {
+                worker.requestStop();
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void stop() {
+        requestStop();
+
+        CompositeStoppable stoppable = new CompositeStoppable();
+        lock.lock();
+        try {
+            stoppable.add(outgoingUnicasts.values());
+            stoppable.add(outgoingBroadcasts.values());
+            stoppable.add(workers);
+            stoppable.add(handlers);
+            stoppable.add(connections);
+            stoppable.add(router);
+            stoppable.add(executors);
+        } finally {
+            outgoingUnicasts.clear();
+            outgoingBroadcasts.clear();
+            workers.clear();
+            handlers.clear();
+            lock.unlock();
+        }
+
+        stoppable.stop();
+    }
+
+    private static class EndOfStreamConnection extends DelegatingConnection<Message> {
+        private static final Logger LOGGER = LoggerFactory.getLogger(EndOfStreamConnection.class);
+        boolean incomingFinished;
+
+        private EndOfStreamConnection(Connection<Message> connection) {
+            super(connection);
+        }
+
+        @Override
+        public Message receive() {
+            if (incomingFinished) {
+                return null;
+            }
+            Message result;
+            try {
+                result = super.receive();
+            } catch (Throwable e) {
+                LOGGER.error("Could not receive message from connection. Discarding connection.", e);
+                result = null;
+            }
+            if (result instanceof EndOfStreamEvent) {
+                incomingFinished = true;
+            } else if (result == null) {
+                incomingFinished = true;
+                result = new EndOfStreamEvent();
+            }
+            return result;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageIOException.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageIOException.java
new file mode 100644
index 0000000..b952013
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageIOException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.GradleException;
+
+public class MessageIOException extends GradleException {
+    public MessageIOException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageOriginator.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageOriginator.java
new file mode 100644
index 0000000..f0e39b3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageOriginator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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;
+
+public class MessageOriginator {
+    private final Object id;
+    private final String name;
+
+    public MessageOriginator(Object id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Object getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+
+        MessageOriginator other = (MessageOriginator) o;
+        return id.equals(other.id) && name.equals(other.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[MessageOriginator name: %s, id: %s]", name, id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageSerializer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageSerializer.java
new file mode 100644
index 0000000..f4e7bb9
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageSerializer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.inet.InetEndpoint;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public interface MessageSerializer<T> {
+    T read(DataInputStream inputStream, InetEndpoint localAddress, InetEndpoint remoteAddress) throws Exception;
+
+    void write(T message, DataOutputStream outputStream) throws Exception;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessagingServices.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessagingServices.java
new file mode 100644
index 0000000..ba5050a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessagingServices.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2011 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.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.inet.*;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryProtocolSerializer;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.UUIDGenerator;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.UUID;
+
+/**
+ * A factory for a set of messaging services. Provides the following services:
+ *
+ * <ul>
+ *
+ * <li>{@link MessagingClient}</li>
+ *
+ * <li>{@link MessagingServer}</li>
+ *
+ * <li>{@link OutgoingBroadcast}</li>
+ *
+ * <li>{@link IncomingBroadcast}</li>
+ *
+ * </ul>
+ */
+public class MessagingServices extends DefaultServiceRegistry implements Stoppable {
+    private final IdGenerator<UUID> idGenerator = new UUIDGenerator();
+    private final ClassLoader messageClassLoader;
+    private final String broadcastGroup;
+    private final SocketInetAddress broadcastAddress;
+    private DefaultMessagingClient messagingClient;
+    private DefaultMultiChannelConnector multiChannelConnector;
+    private TcpIncomingConnector<Message> incomingConnector;
+    private DefaultExecutorFactory executorFactory;
+    private DefaultMessagingServer messagingServer;
+    private DefaultIncomingBroadcast incomingBroadcast;
+    private AsyncConnectionAdapter<DiscoveryMessage> multicastConnection;
+    private DefaultOutgoingBroadcast outgoingBroadcast;
+
+    public MessagingServices(ClassLoader messageClassLoader) {
+        this(messageClassLoader, "gradle");
+    }
+
+    public MessagingServices(ClassLoader messageClassLoader, String broadcastGroup) {
+        this(messageClassLoader, broadcastGroup, defaultBroadcastAddress());
+    }
+
+    public MessagingServices(ClassLoader messageClassLoader, String broadcastGroup, SocketInetAddress broadcastAddress) {
+        this.messageClassLoader = messageClassLoader;
+        this.broadcastGroup = broadcastGroup;
+        this.broadcastAddress = broadcastAddress;
+    }
+
+    private static SocketInetAddress defaultBroadcastAddress() {
+        try {
+            return new SocketInetAddress(InetAddress.getByName("233.253.17.122"), 7912);
+        } catch (UnknownHostException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private static String determineNodeName() {
+        String hostName;
+        try {
+            hostName = InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            hostName = new InetAddressFactory().findRemoteAddresses().get(0).toString();
+        }
+        return String.format("%s@%s", System.getProperty("user.name"), hostName);
+    }
+
+    public void stop() {
+        close();
+    }
+
+    @Override
+    public void close() {
+        CompositeStoppable stoppable = new CompositeStoppable();
+        stoppable.add(incomingConnector);
+        stoppable.add(messagingClient);
+        stoppable.add(messagingServer);
+        stoppable.add(multiChannelConnector);
+        stoppable.add(outgoingBroadcast);
+        stoppable.add(incomingBroadcast);
+        stoppable.add(multicastConnection);
+        stoppable.add(executorFactory);
+        stoppable.stop();
+    }
+
+    protected MessageOriginator createMessageOriginator() {
+        return new MessageOriginator(idGenerator.generateId(), determineNodeName());
+    }
+
+    protected ExecutorFactory createExecutorFactory() {
+        executorFactory = new DefaultExecutorFactory();
+        return executorFactory;
+    }
+
+    protected OutgoingConnector<Message> createOutgoingConnector() {
+        return new TcpOutgoingConnector<Message>(
+                new DefaultMessageSerializer<Message>(
+                        messageClassLoader));
+    }
+
+    protected IncomingConnector<Message> createIncomingConnector() {
+        incomingConnector = new TcpIncomingConnector<Message>(
+                get(ExecutorFactory.class),
+                new DefaultMessageSerializer<Message>(
+                        messageClassLoader),
+                new InetAddressFactory(),
+                idGenerator);
+        return incomingConnector;
+    }
+
+    protected MultiChannelConnector createMultiChannelConnector() {
+        multiChannelConnector = new DefaultMultiChannelConnector(
+                get(OutgoingConnector.class),
+                get(IncomingConnector.class),
+                get(ExecutorFactory.class),
+                messageClassLoader);
+        return multiChannelConnector;
+    }
+
+    protected MessagingClient createMessagingClient() {
+        messagingClient = new DefaultMessagingClient(
+                get(MultiChannelConnector.class),
+                messageClassLoader);
+        return messagingClient;
+    }
+
+    protected MessagingServer createMessagingServer() {
+        messagingServer = new DefaultMessagingServer(
+                get(MultiChannelConnector.class),
+                messageClassLoader);
+        return messagingServer;
+    }
+
+    protected IncomingBroadcast createIncomingBroadcast() {
+        incomingBroadcast = new DefaultIncomingBroadcast(
+                get(MessageOriginator.class),
+                broadcastGroup,
+                get(AsyncConnection.class),
+                get(IncomingConnector.class),
+                get(ExecutorFactory.class),
+                idGenerator,
+                messageClassLoader);
+        return incomingBroadcast;
+    }
+
+    protected OutgoingBroadcast createOutgoingBroadcast() {
+        outgoingBroadcast = new DefaultOutgoingBroadcast(
+                get(MessageOriginator.class),
+                broadcastGroup,
+                get(AsyncConnection.class),
+                get(OutgoingConnector.class),
+                get(ExecutorFactory.class),
+                idGenerator,
+                messageClassLoader);
+        return outgoingBroadcast;
+    }
+
+    protected AsyncConnection<DiscoveryMessage> createMulticastConnection() {
+        MulticastConnection<DiscoveryMessage> connection = new MulticastConnection<DiscoveryMessage>(broadcastAddress, new DiscoveryProtocolSerializer());
+        multicastConnection = new AsyncConnectionAdapter<DiscoveryMessage>(
+                connection,
+                new DiscardingFailureHandler<DiscoveryMessage>(LoggerFactory.getLogger(MulticastConnection.class)),
+                get(ExecutorFactory.class));
+        return multicastConnection;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
old mode 100644
new mode 100755
index 47511ae..03580ca
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
@@ -1,44 +1,60 @@
-/*
- * 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.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
-
-public class MethodInvocationMarshallingDispatch implements Dispatch<MethodInvocation> {
-    private final Dispatch<? super Message> dispatch;
-    private final Map<Method, Integer> methods = new HashMap<Method, Integer>();
-    private int nextKey;
-
-    public MethodInvocationMarshallingDispatch(Dispatch<? super Message> dispatch) {
-        this.dispatch = dispatch;
-    }
-
-    public void dispatch(MethodInvocation methodInvocation) {
-        Method method = methodInvocation.getMethod();
-        Integer key = methods.get(method);
-        if (key == null) {
-            key = nextKey++;
-            methods.put(method, key);
-            dispatch.dispatch(new MethodMetaInfo(key, method));
-        }
-        dispatch.dispatch(new RemoteMethodInvocation(key, methodInvocation.getArguments()));
-    }
-}
+/*
+ * 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.internal.protocol.MethodMetaInfo;
+import org.gradle.messaging.remote.internal.protocol.PayloadMessage;
+import org.gradle.messaging.remote.internal.protocol.RemoteMethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MethodInvocationMarshallingDispatch implements Dispatch<Message> {
+    private final Dispatch<? super Message> dispatch;
+    private final Map<Method, Integer> methods = new HashMap<Method, Integer>();
+    private int nextKey;
+
+    public MethodInvocationMarshallingDispatch(Dispatch<? super Message> dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(Message message) {
+        if (!(message instanceof PayloadMessage)) {
+            dispatch.dispatch(message);
+            return;
+        }
+
+        PayloadMessage payloadMessage = (PayloadMessage) message;
+        if (!(payloadMessage.getNestedPayload() instanceof MethodInvocation)) {
+            dispatch.dispatch(message);
+            return;
+        }
+
+        MethodInvocation methodInvocation = (MethodInvocation) payloadMessage.getNestedPayload();
+        Method method = methodInvocation.getMethod();
+        Integer key = methods.get(method);
+        if (key == null) {
+            key = nextKey++;
+            methods.put(method, key);
+            dispatch.dispatch(new MethodMetaInfo(key, method));
+        }
+        Message transformedMessage = payloadMessage.withNestedPayload(new RemoteMethodInvocation(key, methodInvocation.getArguments()));
+        dispatch.dispatch(transformedMessage);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
old mode 100644
new mode 100755
index 2171e07..dc2c762
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
@@ -1,53 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
-
-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>();
-
-    public MethodInvocationUnmarshallingDispatch(Dispatch<? super MethodInvocation> dispatch, ClassLoader classLoader) {
-        this.dispatch = dispatch;
-        this.classLoader = classLoader;
-    }
-
-    public void dispatch(Object message) {
-        if (message instanceof MethodMetaInfo) {
-            MethodMetaInfo methodMetaInfo = (MethodMetaInfo) message;
-            Method method = methodMetaInfo.findMethod(classLoader);
-            methods.put(methodMetaInfo.getKey(), method);
-        } else if (message instanceof RemoteMethodInvocation) {
-            RemoteMethodInvocation remoteMethodInvocation = (RemoteMethodInvocation) message;
-            Method method = methods.get(remoteMethodInvocation.getKey());
-            if (method == null) {
-                throw new IllegalStateException("Received a method invocation message for an unknown method.");
-            }
-            MethodInvocation methodInvocation = new MethodInvocation(method,
-                    remoteMethodInvocation.getArguments());
-            dispatch.dispatch(methodInvocation);
-        } else {
-            throw new IllegalStateException(String.format("Received an unknown message %s.", message));
-        }
-    }
-}
+/*
+ * 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.internal.protocol.MethodMetaInfo;
+import org.gradle.messaging.remote.internal.protocol.PayloadMessage;
+import org.gradle.messaging.remote.internal.protocol.RemoteMethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MethodInvocationUnmarshallingDispatch implements Dispatch<Message> {
+    private final Dispatch<? super Message> dispatch;
+    private final ClassLoader classLoader;
+    private final Map<Object, Method> methods = new HashMap<Object, Method>();
+
+    public MethodInvocationUnmarshallingDispatch(Dispatch<? super Message> dispatch, ClassLoader classLoader) {
+        this.dispatch = dispatch;
+        this.classLoader = classLoader;
+    }
+
+    public void dispatch(Message message) {
+        if (message instanceof MethodMetaInfo) {
+            MethodMetaInfo methodMetaInfo = (MethodMetaInfo) message;
+            Method method = methodMetaInfo.findMethod(classLoader);
+            methods.put(methodMetaInfo.getKey(), method);
+            return;
+        }
+        if (message instanceof PayloadMessage) {
+            PayloadMessage payloadMessage = (PayloadMessage) message;
+            if (payloadMessage.getNestedPayload() instanceof RemoteMethodInvocation) {
+                RemoteMethodInvocation remoteMethodInvocation = (RemoteMethodInvocation) payloadMessage.getNestedPayload();
+                Method method = methods.get(remoteMethodInvocation.getKey());
+                if (method == null) {
+                    throw new IllegalStateException("Received a method invocation message for an unknown method.");
+                }
+                MethodInvocation methodInvocation = new MethodInvocation(method,
+                        remoteMethodInvocation.getArguments());
+                dispatch.dispatch(payloadMessage.withNestedPayload(methodInvocation));
+                return;
+            }
+        }
+
+        dispatch.dispatch(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodMetaInfo.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodMetaInfo.java
deleted file mode 100644
index ec9483d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodMetaInfo.java
+++ /dev/null
@@ -1,114 +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.messaging.remote.internal;
-
-import org.gradle.util.UncheckedException;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-public class MethodMetaInfo extends Message {
-    private final Type type;
-    private final String methodName;
-    private final Type[] paramTypes;
-    private final Object key;
-
-    public MethodMetaInfo(Object key, Method method) {
-        this.key = key;
-        type = new Type(method.getDeclaringClass());
-        methodName = method.getName();
-        paramTypes = new Type[method.getParameterTypes().length];
-        for (int i = 0; i < method.getParameterTypes().length; i++) {
-            Class<?> paramType = method.getParameterTypes()[i];
-            paramTypes[i] = new Type(paramType);
-        }
-    }
-
-    public Object getKey() {
-        return key;
-    }
-
-    public Method findMethod(ClassLoader classLoader) {
-        try {
-            Class<?> declaringClass = this.type.load(classLoader);
-            Class<?>[] paramTypes = new Class[this.paramTypes.length];
-            for (int i = 0; i < this.paramTypes.length; i++) {
-                Type paramType = this.paramTypes[i];
-                paramTypes[i] = paramType.load(classLoader);
-            }
-            return declaringClass.getMethod(methodName, paramTypes);
-        } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null || obj.getClass() != getClass()) {
-            return false;
-        }
-
-        MethodMetaInfo other = (MethodMetaInfo) obj;
-        if (!key.equals(other.key)) {
-            return false;
-        }
-        if (!type.equals(other.type)) {
-            return false;
-        }
-        if (!methodName.equals(other.methodName)) {
-            return false;
-        }
-        return Arrays.equals(paramTypes, other.paramTypes);
-    }
-
-    @Override
-    public int hashCode() {
-        return key.hashCode();
-    }
-
-    private static class Type implements Serializable {
-        private String typeName;
-        private Class<?> type;
-
-        public Type(Class<?> type) {
-            this.typeName = type.getName();
-            if (type.isPrimitive()) {
-                this.type = type;
-            }
-        }
-
-        Class<?> load(ClassLoader classLoader) throws ClassNotFoundException {
-            if (type != null) {
-                return type;
-            }
-            return classLoader.loadClass(typeName);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            return ((Type) obj).typeName.equals(typeName);
-        }
-
-        @Override
-        public int hashCode() {
-            return typeName.hashCode();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java
old mode 100644
new mode 100755
index 3c6de05..44375d9
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java
@@ -1,46 +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.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Addressable;
-import org.gradle.messaging.dispatch.Dispatch;
-
-public interface MultiChannelConnection<T> extends Addressable, AsyncStoppable {
-    /**
-     * Adds a destination for outgoing messages on the given channel. The returned value is thread-safe.
-     */
-    Dispatch<T> addOutgoingChannel(Object channelKey);
-
-    /**
-     * Adds a handler for incoming messages on the given channel. The given dispatch is only ever used by a single
-     * thread at any given time.
-     */
-    void addIncomingChannel(Object channelKey, Dispatch<T> dispatch);
-
-    /**
-     * Commences graceful stop of this connection. Stops accepting any more outgoing messages, and requests that the
-     * peer stop sending incoming messages.
-     */
-    void requestStop();
-
-    /**
-     * Performs a graceful stop of this connection. Blocks until all dispatched incoming messages have been handled, and
-     * all outgoing messages have been delivered.
-     */
-    void stop();
-}
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.remote.Addressable;
+import org.gradle.messaging.dispatch.Dispatch;
+
+public interface MultiChannelConnection<T> extends Addressable, AsyncStoppable {
+    /**
+     * Adds a destination for outgoing messages on the given channel. The returned value is thread-safe.
+     */
+    Dispatch<T> addOutgoingChannel(String channelKey);
+
+    /**
+     * Adds a handler for incoming messages on the given channel. The given dispatch is only ever used by a single
+     * thread at any given time.
+     */
+    void addIncomingChannel(String channelKey, Dispatch<T> dispatch);
+
+    /**
+     * Commences graceful stop of this connection. Stops accepting any more outgoing messages, and requests that the
+     * peer stop sending incoming messages.
+     */
+    void requestStop();
+
+    /**
+     * Performs a graceful stop of this connection. Blocks until all dispatched incoming messages have been handled, and
+     * all outgoing messages have been delivered.
+     */
+    void stop();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
old mode 100644
new mode 100755
index 28c0f1f..5698229
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
@@ -1,28 +1,27 @@
-/*
- * 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<Object>>> action);
-
-    MultiChannelConnection<Object> 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.messaging.remote.internal;
+
+import org.gradle.api.Action;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+
+public interface MultiChannelConnector {
+    Address accept(Action<ConnectEvent<MultiChannelConnection<Object>>> action);
+
+    MultiChannelConnection<Object> connect(Address destinationAddress);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingBroadcast.java
new file mode 100644
index 0000000..2ca8abb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingBroadcast.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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;
+
+public interface OutgoingBroadcast {
+    <T> T addOutgoing(Class<T> type);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
old mode 100644
new mode 100755
index a0a575a..2379a6f
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
@@ -1,26 +1,27 @@
-/*
- * 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.
-     * @throws org.gradle.messaging.remote.internal.ConnectException when there is nothing listening on the remote URI
-     */
-    <T> Connection<T> connect(URI destinationUri) throws ConnectException;
-}
+/*
+ * 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.messaging.remote.Address;
+
+public interface OutgoingConnector<T> {
+    /**
+     * Creates a connection to the given address.
+     *
+     * @throws ConnectException when there is nothing listening on the remote address
+     */
+    Connection<T> connect(Address destinationAddress) throws ConnectException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
old mode 100644
new mode 100755
index cedd291..65068e7
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
@@ -1,47 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
-import org.gradle.messaging.dispatch.ThreadSafeDispatch;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class OutgoingMethodInvocationHandler {
-    private final Map<Class<?>, ProxyDispatchAdapter<?>> outgoing = new ConcurrentHashMap<Class<?>, ProxyDispatchAdapter<?>>();
-    private final MultiChannelConnection<Object> connection;
-
-    public OutgoingMethodInvocationHandler(MultiChannelConnection<Object> connection) {
-        this.connection = connection;
-    }
-
-    public <T> T addOutgoing(Class<T> type) {
-        ProxyDispatchAdapter<?> existing = outgoing.get(type);
-        if (existing != null) {
-            return type.cast(outgoing.get(type).getSource());
-        }
-
-        Dispatch<MethodInvocation> dispatch = new ThreadSafeDispatch<MethodInvocation>(
-                new MethodInvocationMarshallingDispatch(connection.addOutgoingChannel(type.getName())));
-        ProxyDispatchAdapter<T> adapter = new ProxyDispatchAdapter<T>(type, dispatch);
-        outgoing.put(type, adapter);
-        return adapter.getSource();
-    }
+/*
+ * 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class OutgoingMethodInvocationHandler {
+    private final Map<Class<?>, ProxyDispatchAdapter<?>> outgoing = new ConcurrentHashMap<Class<?>, ProxyDispatchAdapter<?>>();
+    private final MultiChannelConnection<Object> connection;
+
+    public OutgoingMethodInvocationHandler(MultiChannelConnection<Object> connection) {
+        this.connection = connection;
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        ProxyDispatchAdapter<?> existing = outgoing.get(type);
+        if (existing != null) {
+            return type.cast(existing.getSource());
+        }
+
+        Dispatch<Object> dispatch = connection.addOutgoingChannel(type.getName());
+        ProxyDispatchAdapter<T> adapter = new ProxyDispatchAdapter<T>(dispatch, type);
+        outgoing.put(type, adapter);
+        return adapter.getSource();
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMultiplex.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMultiplex.java
new file mode 100644
index 0000000..c0eeaa3
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMultiplex.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.protocol.Request;
+
+class OutgoingMultiplex implements Dispatch<Object> {
+    private final Dispatch<? super Message> dispatch;
+    private final String channelKey;
+
+    OutgoingMultiplex(String channelKey, Dispatch<? super Message> dispatch) {
+        this.channelKey = channelKey;
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(Object message) {
+        dispatch.dispatch(new Request(channelKey, message));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java
old mode 100644
new mode 100755
index 2376233..7d64769
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.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;
-
-/**
- * A {@code PlaceholderException} is used when an exception cannot be serialized or deserialized.
- */
-public class PlaceholderException extends RuntimeException {
-    public PlaceholderException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
+/*
+ * 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;
+
+/**
+ * A {@code PlaceholderException} is used when an exception cannot be serialized or deserialized.
+ */
+public class PlaceholderException extends RuntimeException {
+    private final String exceptionClassName;
+    
+    public PlaceholderException(String exceptionClassName, String message, Throwable cause) {
+        super(message, cause);
+        this.exceptionClassName = exceptionClassName;
+    }
+    
+    public String toString() {
+        return String.format("%s: %s", exceptionClassName, getMessage());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Protocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Protocol.java
new file mode 100644
index 0000000..009a9de
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Protocol.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * <p>A protocol implementation. A protocol is a stage in a bi-directional messaging pipeline. It can receive incoming and outgoing messages.
+ * In response to these messages, it can dispatch incoming and outgoing messages. It can also register callbacks to be executed later, to allow
+ * timeouts and periodic behaviour to be implemented.
+ *
+ * <p>All methods on protocol are called from a single thread at a time, so implementations do not have to use any locking for their state. The method
+ * implementations should not block.
+ *
+ * @param <T> The message type.
+ */
+public interface Protocol<T> {
+    /**
+     * Called to initialise the protocol. The supplied context can be later used to dispatch incoming and outgoing messages.
+     *
+     * @param context The context.
+     */
+    void start(ProtocolContext<T> context);
+
+    /**
+     * Handles an outgoing message. The context can be used to dispatch incoming and outgoing messages, as required.
+     */
+    void handleOutgoing(T message);
+
+    /**
+     * Handles an incoming message. The context can be used to dispatch incoming and outgoing messages, as required.
+     */
+    void handleIncoming(T message);
+
+    /**
+     * Requests that this protocol initiate its stop messages. The protocol can call {@link ProtocolContext#stopLater()} to defer stop until some
+     * messages are received. In which case, it should later call {@link ProtocolContext#stopped()} to indicate it has finished.
+     *
+     * If the protocol does not call stopLater(), it is assumed to have stopped when this method returns.
+     */
+    void stopRequested();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolContext.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolContext.java
new file mode 100644
index 0000000..a359fd2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolContext.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.util.concurrent.TimeUnit;
+
+/**
+ * Used by {@link Protocol} implementations to send incoming and outgoing messages.
+ *
+ * @param <T> The message type.
+ */
+public interface ProtocolContext<T> {
+    void dispatchOutgoing(T message);
+
+    void dispatchIncoming(T message);
+
+    Callback callbackLater(int delay, TimeUnit delayUnits, Runnable action);
+
+    void stopLater();
+
+    void stopped();
+
+    interface Callback {
+        void cancel();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolStack.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolStack.java
new file mode 100644
index 0000000..137b699
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolStack.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2011 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.internal.CompositeStoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.*;
+import org.gradle.util.TrueTimeProvider;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ProtocolStack<T> implements AsyncStoppable {
+    private final AsyncDispatch<Runnable> workQueue;
+    private final QueuingDispatch<T> incomingQueue = new QueuingDispatch<T>();
+    private final QueuingDispatch<T> outgoingQueue = new QueuingDispatch<T>();
+    private final AsyncReceive<Runnable> receiver;
+    private final DelayedReceive<Runnable> callbackQueue;
+    private final LinkedList<Stage> stack = new LinkedList<Stage>();
+    private final LinkedList<Runnable> contextQueue = new LinkedList<Runnable>();
+    private final DispatchFailureHandler<? super T> outgoingDispatchFailureHandler;
+    private final DispatchFailureHandler<? super T> incomingDispatchFailureHandler;
+    private final CountDownLatch protocolsStopped;
+    private final AtomicBoolean stopRequested = new AtomicBoolean();
+    private final AsyncConnection<T> bottomConnection;
+    private final AsyncConnection<T> topConnection;
+
+    public ProtocolStack(Executor executor, DispatchFailureHandler<? super T> outgoingDispatchFailureHandler, DispatchFailureHandler<? super T> incomingDispatchFailureHandler,
+                         Protocol<T>... protocols) {
+        this.outgoingDispatchFailureHandler = outgoingDispatchFailureHandler;
+        this.incomingDispatchFailureHandler = incomingDispatchFailureHandler;
+        this.callbackQueue = new DelayedReceive<Runnable>(new TrueTimeProvider());
+        protocolsStopped = new CountDownLatch(protocols.length);
+
+        //Start work queue
+        workQueue = new AsyncDispatch<Runnable>(executor);
+        workQueue.dispatchTo(new ExecuteRunnable());
+
+        stack.add(new TopStage());
+        for (Protocol<T> protocol : protocols) {
+            stack.add(new ProtocolStage(protocol));
+        }
+        stack.add(new BottomStage());
+        for (int i = 0; i < stack.size(); i++) {
+            Stage context = stack.get(i);
+            Stage outgoingStage = i == stack.size() - 1 ? null : stack.get(i + 1);
+            Stage incomingStage = i == 0 ? null : stack.get(i - 1);
+            context.attach(outgoingStage, incomingStage);
+        }
+
+        // Wire up callback queue
+        receiver = new AsyncReceive<Runnable>(executor);
+        receiver.dispatchTo(workQueue);
+        receiver.receiveFrom(callbackQueue);
+
+        bottomConnection = new BottomConnection();
+        topConnection = new TopConnection();
+
+        // Start each protocol from bottom to top
+        workQueue.dispatch(new Runnable() {
+            public void run() {
+                for (int i = stack.size() - 1; i >= 0; i--) {
+                    Stage context = stack.get(i);
+                    context.start();
+                }
+            }
+        });
+    }
+
+    public AsyncConnection<T> getBottom() {
+        return bottomConnection;
+    }
+
+    public AsyncConnection<T> getTop() {
+        return topConnection;
+    }
+
+    public void requestStop() {
+        if (!stopRequested.getAndSet(true)) {
+            workQueue.dispatch(new Runnable() {
+                public void run() {
+                    stack.getFirst().requestStop();
+                }
+            });
+        }
+    }
+
+    public void stop() {
+        requestStop();
+        try {
+            protocolsStopped.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        callbackQueue.clear();
+        new CompositeStoppable(callbackQueue, receiver, workQueue, incomingQueue, outgoingQueue).stop();
+    }
+
+    private class ExecuteRunnable implements Dispatch<Runnable> {
+        public void dispatch(Runnable message) {
+            contextQueue.add(message);
+            while (!contextQueue.isEmpty()) {
+                contextQueue.removeFirst().run();
+            }
+        }
+    }
+
+    private abstract class Stage {
+        protected Stage outgoing;
+        protected Stage incoming;
+
+        public void attach(Stage outgoing, Stage incoming) {
+            this.outgoing = outgoing;
+            this.incoming = incoming;
+        }
+
+        public void start() {
+        }
+
+        public void handleIncoming(T message) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void handleOutgoing(T message) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void requestStop() {
+        }
+    }
+
+    private enum StageState { Init, StopRequested, StopPending, Stopped }
+
+    private class ProtocolStage extends Stage implements ProtocolContext<T> {
+        private final Protocol<T> protocol;
+        private StageState state = StageState.Init;
+
+        private ProtocolStage(Protocol<T> protocol) {
+            this.protocol = protocol;
+        }
+
+        @Override
+        public void start() {
+            protocol.start(this);
+        }
+
+        @Override
+        public void handleIncoming(T message) {
+            try {
+                protocol.handleIncoming(message);
+            } catch (Throwable throwable) {
+                incomingDispatchFailureHandler.dispatchFailed(message, throwable);
+            }
+        }
+
+        @Override
+        public void handleOutgoing(T message) {
+            try {
+                protocol.handleOutgoing(message);
+            } catch (Throwable throwable) {
+                outgoingDispatchFailureHandler.dispatchFailed(message, throwable);
+            }
+        }
+
+        public void dispatchIncoming(final T message) {
+            contextQueue.add(new Runnable() {
+                public void run() {
+                    incoming.handleIncoming(message);
+                }
+            });
+        }
+
+        public void dispatchOutgoing(final T message) {
+            contextQueue.add(new Runnable() {
+                public void run() {
+                    outgoing.handleOutgoing(message);
+                }
+            });
+        }
+
+        public Callback callbackLater(int delay, TimeUnit delayUnits, Runnable action) {
+            DefaultCallback callback = new DefaultCallback(action);
+            callbackQueue.dispatchLater(callback, delay, delayUnits);
+            return callback;
+        }
+
+        public void stopped() {
+            if (state == StageState.Init) {
+                throw new IllegalStateException(String.format("Cannot stop when in %s state.", state));
+            }
+            if (state != StageState.Stopped) {
+                state = StageState.Stopped;
+                protocolsStopped.countDown();
+                contextQueue.add(new Runnable() {
+                    public void run() {
+                        outgoing.requestStop();
+                    }
+                });
+            }
+        }
+
+        public void stopLater() {
+            if (state == StageState.Init || state == StageState.Stopped) {
+                throw new IllegalStateException(String.format("Cannot stop later when in %s state.", state));
+            }
+            state = StageState.StopPending;
+        }
+
+        @Override
+        public void requestStop() {
+            assert state == StageState.Init;
+            state = StageState.StopRequested;
+            protocol.stopRequested();
+            if (state == StageState.StopRequested) {
+                stopped();
+            }
+        }
+
+        private class DefaultCallback implements Runnable, ProtocolContext.Callback {
+            final Runnable action;
+            boolean cancelled;
+
+            private DefaultCallback(Runnable action) {
+                this.action = action;
+            }
+
+            public void cancel() {
+                cancelled = true;
+                callbackQueue.remove(this);
+            }
+
+            public void run() {
+                if (!cancelled && state != StageState.Stopped) {
+                    action.run();
+                }
+            }
+        }
+    }
+
+    private class TopStage extends Stage {
+        @Override
+        public void handleIncoming(T message) {
+            incomingQueue.dispatch(message);
+        }
+
+        @Override
+        public void handleOutgoing(T message) {
+            outgoing.handleOutgoing(message);
+        }
+
+        @Override
+        public void requestStop() {
+            outgoing.requestStop();
+        }
+    }
+
+    private class BottomStage extends Stage {
+        @Override
+        public void handleIncoming(T message) {
+            incoming.handleIncoming(message);
+        }
+
+        @Override
+        public void handleOutgoing(T message) {
+            outgoingQueue.dispatch(message);
+        }
+    }
+
+    private class BottomConnection implements AsyncConnection<T> {
+        public void dispatchTo(Dispatch<? super T> handler) {
+            outgoingQueue.dispatchTo(new FailureHandlingDispatch<T>(handler, outgoingDispatchFailureHandler));
+        }
+
+        public void dispatch(final T message) {
+            workQueue.dispatch(new Runnable() {
+                @Override
+                public String toString() {
+                    return String.format("incoming %s", message);
+                }
+
+                public void run() {
+                    stack.getLast().handleIncoming(message);
+                }
+            });
+        }
+    }
+
+    private class TopConnection implements AsyncConnection<T> {
+        public void dispatchTo(Dispatch<? super T> handler) {
+            incomingQueue.dispatchTo(new FailureHandlingDispatch<T>(handler, incomingDispatchFailureHandler));
+        }
+
+        public void dispatch(final T message) {
+            workQueue.dispatch(new Runnable() {
+                @Override
+                public String toString() {
+                    return String.format("outgoing %s", message);
+                }
+
+                public void run() {
+                    stack.getFirst().handleOutgoing(message);
+                }
+            });
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ReceiveProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ReceiveProtocol.java
new file mode 100644
index 0000000..d13d874
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ReceiveProtocol.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class ReceiveProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ReceiveProtocol.class);
+    private final Object id;
+    private final String displayName;
+    private final String channelKey;
+    private final Set<Object> producers = new HashSet<Object>();
+    private ProtocolContext<Message> context;
+    private boolean stopping;
+
+    public ReceiveProtocol(Object id, String displayName, String channelKey) {
+        this.id = id;
+        this.displayName = displayName;
+        this.channelKey = channelKey;
+    }
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+        LOGGER.debug("Starting receiver {}.", id);
+        context.dispatchOutgoing(new ConsumerAvailable(id, displayName, channelKey));
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof ProducerReady) {
+            LOGGER.debug("Producer ready: {}", message);
+            ProducerReady producerReady = (ProducerReady) message;
+            producers.add(producerReady.getProducerId());
+            context.dispatchOutgoing(new ConsumerReady(id, producerReady.getProducerId()));
+        } else if (message instanceof ProducerStopped) {
+            LOGGER.debug("Producer stopped: {}", message);
+            ProducerStopped producerStopped = (ProducerStopped) message;
+            context.dispatchOutgoing(new ConsumerStopped(id, producerStopped.getProducerId()));
+            removeProducer(producerStopped.getProducerId());
+        } else if (message instanceof ProducerUnavailable) {
+            LOGGER.debug("Producer unavailable: {}", message);
+            ProducerUnavailable producerUnavailable = (ProducerUnavailable) message;
+            removeProducer(producerUnavailable.getId());
+        } else if (message instanceof ProducerAvailable) {
+            // Ignore these broadcasts
+            return;
+        } else if (message instanceof Request) {
+            context.dispatchIncoming(message);
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
+        }
+    }
+
+    private void removeProducer(Object producerId) {
+        producers.remove(producerId);
+        if (stopping && producers.isEmpty()) {
+            LOGGER.debug("All producers finished. Stopping now.");
+            allProducersFinished();
+        }
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof WorkerStopping) {
+            workerStopped();
+        } else if (message instanceof MessageCredits) {
+            LOGGER.debug("Discarding {}.", message);
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+        }
+    }
+
+    private void workerStopped() {
+        stopping = true;
+        if (producers.isEmpty()) {
+            LOGGER.debug("No producers. Stopping now.");
+            allProducersFinished();
+            return;
+        }
+
+        LOGGER.debug("Waiting for producers to finish. Stopping later. Producers: {}", producers);
+        for (Object producer : producers) {
+            context.dispatchOutgoing(new ConsumerStopping(id, producer));
+        }
+    }
+
+    private void allProducersFinished() {
+        context.dispatchOutgoing(new ConsumerUnavailable(id));
+        context.dispatchIncoming(new EndOfStreamEvent());
+    }
+
+    public void stopRequested() {
+        assert stopping;
+        context.stopped();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java
new file mode 100644
index 0000000..c727430
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.EndOfStreamEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RemoteDisconnectProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteDisconnectProtocol.class);
+    private ProtocolContext<Message> context;
+    private boolean stopping;
+    private boolean outgoingStopped;
+    private boolean finished;
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+    }
+
+    public void handleIncoming(Message message) {
+        context.dispatchIncoming(message);
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof EndOfStreamEvent) {
+            if (stopping) {
+                context.stopped();
+                return;
+            }
+            context.dispatchOutgoing(message);
+            outgoingStopped = true;
+            finished = true;
+        } else if (!outgoingStopped) {
+            context.dispatchOutgoing(message);
+        } else {
+            LOGGER.debug("Discarding outgoing message {} as output has been finished.", message);
+        }
+    }
+
+    public void stopRequested() {
+        if (finished) {
+            context.stopped();
+            return;
+        }
+
+        if (!outgoingStopped) {
+            context.dispatchOutgoing(new EndOfStreamEvent());
+            outgoingStopped = true;
+        }
+        context.stopLater();
+        stopping = true;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocation.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocation.java
deleted file mode 100644
index 87e3490..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocation.java
+++ /dev/null
@@ -1,54 +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.messaging.remote.internal;
-
-import java.util.Arrays;
-
-public class RemoteMethodInvocation extends Message {
-    private final Object key;
-    private final Object[] arguments;
-
-    public RemoteMethodInvocation(Object key, Object[] arguments) {
-        this.key = key;
-        this.arguments = arguments;
-    }
-
-    public Object getKey() {
-        return key;
-    }
-
-    public Object[] getArguments() {
-        return arguments;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-
-        RemoteMethodInvocation other = (RemoteMethodInvocation) obj;
-        return key.equals(other.key) && Arrays.equals(arguments, other.arguments);
-    }
-
-    @Override
-    public int hashCode() {
-        return key.hashCode();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Router.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Router.java
new file mode 100644
index 0000000..aa3bf04
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Router.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2011 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.internal.Stoppable;
+import org.gradle.messaging.dispatch.AsyncDispatch;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.DispatchFailureHandler;
+import org.gradle.messaging.dispatch.QueuingDispatch;
+import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent;
+import org.gradle.messaging.remote.internal.protocol.RoutableMessage;
+import org.gradle.messaging.remote.internal.protocol.RouteAvailableMessage;
+import org.gradle.messaging.remote.internal.protocol.RouteUnavailableMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+public class Router implements Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Router.class);
+    private final AsyncDispatch<Runnable> workQueue;
+    private final Group localConnections = new LocalGroup();
+    private final Group remoteConnections = new RemoteGroup();
+    private final DispatchFailureHandler<? super Message> failureHandler;
+
+    public Router(Executor executor, DispatchFailureHandler<? super Message> failureHandler) {
+        this.failureHandler = failureHandler;
+        localConnections.peer = remoteConnections;
+        remoteConnections.peer = localConnections;
+        workQueue = new AsyncDispatch<Runnable>(executor);
+        workQueue.dispatchTo(new Dispatch<Runnable>() {
+            public void dispatch(Runnable message) {
+                message.run();
+            }
+        });
+    }
+
+    public AsyncConnection<Message> createLocalConnection() {
+        return new Endpoint(localConnections);
+    }
+
+    public AsyncConnection<Message> createRemoteConnection() {
+        return new Endpoint(remoteConnections);
+    }
+
+    public void stop() {
+        workQueue.stop();
+    }
+
+    private class Endpoint implements AsyncConnection<Message> {
+        private QueuingDispatch<Message> handler = new QueuingDispatch<Message>();
+        final Group owner;
+        final Group peerGroup;
+        final Set<Route> routes = new HashSet<Route>();
+
+        protected Endpoint(Group owner) {
+            this.owner = owner;
+            this.peerGroup = owner.peer;
+            owner.addEndpoint(this);
+        }
+
+        public void dispatchTo(final Dispatch<? super Message> handler) {
+            workQueue.dispatch(new Runnable() {
+                public void run() {
+                    Endpoint.this.handler.dispatchTo(handler);
+                }
+            });
+        }
+
+        public void dispatch(final Message message) {
+            workQueue.dispatch(new Runnable() {
+                public void run() {
+                    try {
+                        if (message instanceof RouteAvailableMessage) {
+                            RouteAvailableMessage routeAvailableMessage = (RouteAvailableMessage) message;
+                            LOGGER.debug("Received route available. Message: {}", routeAvailableMessage);
+                            Route route = new Route(routeAvailableMessage.getId(), Endpoint.this, routeAvailableMessage);
+                            routes.add(route);
+                            owner.addRoute(route);
+                        } else if (message instanceof RouteUnavailableMessage) {
+                            RouteUnavailableMessage routeUnavailableMessage = (RouteUnavailableMessage) message;
+                            LOGGER.debug("Received route unavailable. Message: {}", routeUnavailableMessage);
+                            Route route = owner.removeRoute(routeUnavailableMessage.getId());
+                            routes.remove(route);
+                        } else if (message instanceof RoutableMessage) {
+                            RoutableMessage routableMessage = (RoutableMessage) message;
+                            peerGroup.send(routableMessage.getDestination(), message);
+                        } else if (message instanceof EndOfStreamEvent) {
+                            for (Route route : routes) {
+                                LOGGER.debug("Removing route {} due to end of stream.", route.id);
+                                owner.removeRoute(route.id);
+                            }
+                            owner.removeEndpoint(Endpoint.this);
+                            // Ping the message back
+                            dispatchIncoming(message);
+                        } else {
+                            throw new UnsupportedOperationException(String.format("Received message which cannot be routed: %s.", message));
+                        }
+                    } catch (Throwable throwable) {
+                        failureHandler.dispatchFailed(message, throwable);
+                    }
+                }
+            });
+        }
+
+        void dispatchIncoming(Message message) {
+            handler.dispatch(message);
+        }
+    }
+
+    private static class Route {
+        final Object id;
+        final RouteAvailableMessage announcement;
+        final Endpoint destination;
+        final Set<Route> targets = new HashSet<Route>();
+
+        private Route(Object id, Endpoint destination, RouteAvailableMessage announcement) {
+            this.id = id;
+            this.destination = destination;
+            this.announcement = announcement;
+        }
+
+        public void connectTo(Route target) {
+            targets.add(target);
+            target.destination.dispatchIncoming((Message) announcement);
+        }
+    }
+
+    private static class Group {
+        private final Map<Object, Route> routes = new HashMap<Object, Route>();
+        private final Set<Endpoint> endpoints = new HashSet<Endpoint>();
+        Group peer;
+
+        public void addEndpoint(Endpoint endpoint) {
+            endpoints.add(endpoint);
+        }
+
+        public void addRoute(Route route) {
+            routes.put(route.id, route);
+        }
+
+        public Route removeRoute(Object routeId) {
+            return routes.remove(routeId);
+        }
+
+        public void send(Object routeId, Message message) {
+            routes.get(routeId).destination.dispatchIncoming(message);
+        }
+
+        public void removeEndpoint(Endpoint endpoint) {
+            endpoints.remove(endpoint);
+        }
+    }
+
+    private static class LocalGroup extends Group {
+        @Override
+        public void addRoute(Route route) {
+            super.addRoute(route);
+            for (Endpoint endpoint : peer.endpoints) {
+                endpoint.dispatchIncoming((Message) route.announcement);
+            }
+            for (Route targetRoute : peer.routes.values()) {
+                if (route.announcement.acceptIncoming(targetRoute.announcement)) {
+                    targetRoute.connectTo(route);
+                }
+            }
+        }
+
+        @Override
+        public Route removeRoute(Object routeId) {
+            Route route = super.removeRoute(routeId);
+            Message unavailableMessage = (Message) route.announcement.getUnavailableMessage();
+            for (Endpoint endpoint : peer.endpoints) {
+                endpoint.dispatchIncoming(unavailableMessage);
+            }
+            for (Route targetRoute : peer.routes.values()) {
+                targetRoute.targets.remove(route);
+            }
+            return route;
+        }
+
+        @Override
+        public void removeEndpoint(Endpoint endpoint) {
+            super.removeEndpoint(endpoint);
+            for (Route route : peer.routes.values()) {
+                route.targets.removeAll(endpoint.routes);
+            }
+        }
+    }
+
+    private static class RemoteGroup extends Group {
+        @Override
+        public void addEndpoint(Endpoint endpoint) {
+            super.addEndpoint(endpoint);
+            for (Route route : peer.routes.values()) {
+                endpoint.dispatchIncoming((Message) route.announcement);
+            }
+        }
+
+        @Override
+        public void addRoute(Route route) {
+            super.addRoute(route);
+            for (Route targetRoute : peer.routes.values()) {
+                if (targetRoute.announcement.acceptIncoming(route.announcement)) {
+                    route.connectTo(targetRoute);
+                }
+            }
+        }
+
+        @Override
+        public Route removeRoute(Object routeId) {
+            Route route = super.removeRoute(routeId);
+            for (Route target : route.targets) {
+                Message unavailableMessage = (Message) route.announcement.getUnavailableMessage();
+                target.destination.dispatchIncoming(unavailableMessage);
+            }
+            route.targets.clear();
+            return route;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SendProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SendProtocol.java
new file mode 100644
index 0000000..5f7abc6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SendProtocol.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class SendProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SendProtocol.class);
+    private final String channelKey;
+    private final Object id;
+    private final String displayName;
+    private ProtocolContext<Message> context;
+    private boolean stopping;
+    private final Map<Object, ConsumerAvailable> pending = new HashMap<Object, ConsumerAvailable>();
+    private final Set<Object> consumers = new HashSet<Object>();
+
+    public SendProtocol(Object id, String displayName, String channelKey) {
+        this.channelKey = channelKey;
+        this.id = id;
+        this.displayName = displayName;
+    }
+
+    public void start(ProtocolContext<Message> context) {
+        LOGGER.debug("Starting producer {}", id);
+        this.context = context;
+        context.dispatchOutgoing(new ProducerAvailable(id, displayName, channelKey));
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof ConsumerAvailable) {
+            LOGGER.debug("Consumer available: {}", message);
+            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
+            pending.put(consumerAvailable.getId(), consumerAvailable);
+            consumers.add(consumerAvailable.getId());
+            context.dispatchOutgoing(new ProducerReady(id, consumerAvailable.getId()));
+        } else if (message instanceof ConsumerReady) {
+            LOGGER.debug("Consumer ready: {}", message);
+            ConsumerReady consumerReady = (ConsumerReady) message;
+            context.dispatchIncoming(pending.remove(consumerReady.getConsumerId()));
+        } else if (message instanceof ConsumerStopping) {
+            LOGGER.debug("Consumer stopping: {}", message);
+            ConsumerStopping consumerStopping = (ConsumerStopping) message;
+            context.dispatchIncoming(new ConsumerUnavailable(consumerStopping.getConsumerId()));
+            context.dispatchOutgoing(new ProducerStopped(id, consumerStopping.getConsumerId()));
+        } else if (message instanceof ConsumerStopped) {
+            LOGGER.debug("Consumer stopped: {}", message);
+            ConsumerStopped consumerStopped = (ConsumerStopped) message;
+            consumers.remove(consumerStopped.getConsumerId());
+            maybeStop();
+        } else if (message instanceof ConsumerUnavailable) {
+            LOGGER.debug("Consumer unavailable: {}", message);
+            ConsumerUnavailable consumerUnavailable = (ConsumerUnavailable) message;
+            consumers.remove(consumerUnavailable.getId());
+            if (pending.remove(consumerUnavailable.getId()) == null) {
+                context.dispatchIncoming(new ConsumerUnavailable(consumerUnavailable.getId()));
+            }
+            maybeStop();
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
+        }
+    }
+
+    private void maybeStop() {
+        if (consumers.isEmpty() && stopping) {
+            LOGGER.debug("All consumers stopped. Stopping now.");
+            context.dispatchOutgoing(new ProducerUnavailable(id));
+            context.stopped();
+        }
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof RoutableMessage) {
+            RoutableMessage routableMessage = (RoutableMessage) message;
+            if (!consumers.contains(routableMessage.getDestination())) {
+                throw new IllegalStateException(String.format("Message to unexpected destination dispatched: %s", message));
+            }
+            context.dispatchOutgoing(message);
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+        }
+    }
+
+    public void stopRequested() {
+        stopping = true;
+        if (consumers.isEmpty()) {
+            maybeStop();
+            return;
+        }
+
+        LOGGER.debug("Waiting for consumers to stop: {}", consumers);
+        context.stopLater();
+        for (Object consumerId : consumers) {
+            context.dispatchOutgoing(new ProducerStopped(id, consumerId));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
deleted file mode 100644
index 4a0c68d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
+++ /dev/null
@@ -1,208 +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.messaging.remote.internal;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-
-public class SocketConnection<T> implements Connection<T> {
-    private final SocketChannel socket;
-    private final Object localAddress;
-    private final Object remoteAddress;
-    private final ClassLoader classLoader;
-    private final InputStream instr;
-    private final OutputStream outstr;
-
-    public SocketConnection(SocketChannel socket, Object localAddress, Object remoteAddress, ClassLoader classLoader) {
-        this.socket = socket;
-        this.localAddress = localAddress;
-        this.remoteAddress = remoteAddress;
-        this.classLoader = classLoader;
-        try {
-            // NOTE: we use non-blocking IO as there is no reliable way when using blocking IO to shutdown reads while
-            // keeping writes active. For example, Socket.shutdownInput() does not work on Windows.
-            socket.configureBlocking(false);
-            outstr = new SocketOutputStream(socket);
-            instr = new SocketInputStream(socket);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return String.format("socket connection at %s with %s", localAddress, remoteAddress);
-    }
-
-    public T receive() {
-        try {
-            return (T) Message.receive(instr, classLoader);
-        } catch (Exception e) {
-            if (isEndOfStream(e)) {
-                return null;
-            }
-            throw new GradleException(String.format("Could not read message from '%s'.", remoteAddress), e);
-        }
-    }
-
-    private boolean isEndOfStream(Exception e) {
-        if (e instanceof EOFException) {
-            return true;
-        }
-        if (e instanceof IOException && e.getMessage() != null && e.getMessage().equals("An existing connection was forcibly closed by the remote host")) {
-            return true;
-        }
-        return false;
-    }
-
-    public void dispatch(T message) {
-        try {
-            Message.send(message, outstr);
-            outstr.flush();
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not write message to '%s'.", remoteAddress), e);
-        }
-    }
-
-    public void requestStop() {
-        new CompositeStoppable(instr).stop();
-    }
-
-    public void stop() {
-        new CompositeStoppable(instr, outstr, socket).stop();
-    }
-
-    private static class SocketInputStream extends InputStream {
-        private final Selector selector;
-        private final ByteBuffer buffer;
-        private final SocketChannel socket;
-        private final byte[] readBuffer = new byte[1];
-
-        public SocketInputStream(SocketChannel socket) throws IOException {
-            this.socket = socket;
-            selector = Selector.open();
-            socket.register(selector, SelectionKey.OP_READ);
-            buffer = ByteBuffer.allocateDirect(4096);
-            buffer.limit(0);
-        }
-
-        @Override
-        public int read() throws IOException {
-            int nread = read(readBuffer, 0, 1);
-            if (nread <= 0) {
-                return nread;
-            }
-            return readBuffer[0];
-        }
-
-        @Override
-        public int read(byte[] dest, int offset, int max) throws IOException {
-            if (max == 0) {
-                return 0;
-            }
-
-            if (buffer.remaining() == 0) {
-                selector.select();
-                if (!selector.isOpen()) {
-                    return -1;
-                }
-
-                buffer.clear();
-                int nread = socket.read(buffer);
-                buffer.flip();
-
-                if (nread < 0) {
-                    return -1;
-                }
-            }
-
-            int count = Math.min(buffer.remaining(), max);
-            buffer.get(dest, offset, count);
-            return count;
-        }
-
-        @Override
-        public void close() throws IOException {
-            selector.close();
-        }
-    }
-
-    private static class SocketOutputStream extends OutputStream {
-        private final Selector selector;
-        private final SocketChannel socket;
-        private final ByteBuffer buffer;
-        private final byte[] writeBuffer = new byte[1];
-
-        public SocketOutputStream(SocketChannel socket) throws IOException {
-            this.socket = socket;
-            selector = Selector.open();
-            socket.register(selector, SelectionKey.OP_WRITE);
-            buffer = ByteBuffer.allocateDirect(4096);
-        }
-
-        @Override
-        public void write(int b) throws IOException {
-            writeBuffer[0] = (byte) b;
-            write(writeBuffer);
-        }
-
-        @Override
-        public void write(byte[] src, int offset, int max) throws IOException {
-            int remaining = max;
-            int currentPos = offset;
-            while (remaining > 0) {
-                int count = Math.min(remaining, buffer.remaining());
-                if (count > 0) {
-                    buffer.put(src, currentPos, count);
-                    remaining -= count;
-                    currentPos += count;
-                }
-                if (buffer.remaining() == 0) {
-                    flush();
-                }
-            }
-        }
-
-        @Override
-        public void flush() throws IOException {
-            buffer.flip();
-            while (buffer.remaining() > 0) {
-                selector.select();
-                if (!selector.isOpen()) {
-                    throw new EOFException();
-                }
-                socket.write(buffer);
-            }
-            buffer.clear();
-        }
-
-        @Override
-        public void close() throws IOException {
-            selector.close();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SynchronizedDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SynchronizedDispatch.java
new file mode 100644
index 0000000..c219185
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SynchronizedDispatch.java
@@ -0,0 +1,61 @@
+/*
+ * 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.messaging.remote.internal;
+
+import org.gradle.api.internal.Operation;
+import org.gradle.api.internal.concurrent.Synchronizer;
+
+/**
+ * Connection decorator that synchronizes dispatching.
+ * <p>
+ * by Szczepan Faber, created at: 2/27/12
+ */
+public class SynchronizedDispatch<T> implements Connection<T> {
+    
+    private final Synchronizer sync = new Synchronizer();
+    private final Connection<T> delegate;
+
+    public SynchronizedDispatch(Connection<T> delegate) {
+        this.delegate = delegate;
+    }
+    
+    public void requestStop() {
+        delegate.requestStop();
+    }
+
+    public void dispatch(final T message) {
+        sync.synchronize(new Operation() {
+            public void execute() {
+                delegate.dispatch(message);
+            }
+        });
+    }
+
+    public T receive() {
+        //in case one wants to synchronize this method,
+        //bear in mind that it is blocking so it cannot share the same lock as others
+        return delegate.receive();
+    }
+
+    public void stop() {
+        delegate.stop();
+    }
+
+    public String toString() {
+        return delegate.toString();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
deleted file mode 100644
index cc50fc0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
+++ /dev/null
@@ -1,115 +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.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.util.UncheckedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public class TcpIncomingConnector implements IncomingConnector, AsyncStoppable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(TcpIncomingConnector.class);
-    private final StoppableExecutor executor;
-    private final ClassLoader classLoader;
-    private final List<InetAddress> localAddresses;
-    private final List<ServerSocketChannel> serverSockets = new CopyOnWriteArrayList<ServerSocketChannel>();
-
-    public TcpIncomingConnector(ExecutorFactory executorFactory, ClassLoader classLoader) {
-        this.executor = executorFactory.create("Incoming TCP Connector");
-        this.classLoader = classLoader;
-
-        localAddresses = TcpOutgoingConnector.findLocalAddresses();
-    }
-
-    public URI accept(Action<ConnectEvent<Connection<Object>>> action) {
-        ServerSocketChannel serverSocket;
-        URI localAddress;
-        try {
-            serverSocket = ServerSocketChannel.open();
-            serverSockets.add(serverSocket);
-            serverSocket.socket().bind(new InetSocketAddress(0));
-            localAddress = new URI(String.format("tcp://localhost:%d", serverSocket.socket().getLocalPort()));
-            LOGGER.debug("Listening on {}.", localAddress);
-        } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-
-        executor.execute(new Receiver(serverSocket, localAddress, action));
-        return localAddress;
-    }
-
-    public void requestStop() {
-        new CompositeStoppable().addCloseables(serverSockets).stop();
-    }
-
-    public void stop() {
-        requestStop();
-        executor.stop();
-    }
-
-    private class Receiver implements Runnable {
-        private final ServerSocketChannel serverSocket;
-        private final URI localAddress;
-        private final Action<ConnectEvent<Connection<Object>>> action;
-
-        public Receiver(ServerSocketChannel serverSocket, URI localAddress, Action<ConnectEvent<Connection<Object>>> action) {
-            this.serverSocket = serverSocket;
-            this.localAddress = localAddress;
-            this.action = action;
-        }
-
-        public void run() {
-            try {
-                try {
-                    while (true) {
-                        SocketChannel socket = serverSocket.accept();
-                        InetSocketAddress remoteAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
-                        if (!localAddresses.contains(remoteAddress.getAddress())) {
-                            LOGGER.error("Cannot accept connection from remote address {}.", remoteAddress.getAddress());
-                            socket.close();
-                            continue;
-                        }
-                        URI remoteUri = new URI(String.format("tcp://localhost:%d", remoteAddress.getPort()));
-                        LOGGER.debug("Accepted connection from {}.", remoteUri);
-                        action.execute(new ConnectEvent<Connection<Object>>(new SocketConnection<Object>(socket, localAddress, remoteUri, classLoader), localAddress, remoteUri));
-                    }
-                } catch (ClosedChannelException e) {
-                    // Ignore
-                } catch (Exception e) {
-                    LOGGER.error("Could not accept remote connection.", e);
-                }
-            } finally {
-                new CompositeStoppable(serverSocket).stop();
-                serverSockets.remove(serverSocket);
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
deleted file mode 100644
index 17e5137..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
+++ /dev/null
@@ -1,55 +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.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.MessagingClient;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.net.URI;
-
-/**
- * A {@link org.gradle.messaging.remote.MessagingClient} which uses a single TCP connection with a server.
- */
-public class TcpMessagingClient implements MessagingClient {
-    private final DefaultMultiChannelConnector connector;
-    private final DefaultMessagingClient client;
-    private final DefaultExecutorFactory executorFactory;
-
-    public TcpMessagingClient(ClassLoader messagingClassLoader, URI serverAddress) {
-        executorFactory = new DefaultExecutorFactory();
-        connector = new DefaultMultiChannelConnector(new TcpOutgoingConnector(messagingClassLoader), new NoOpIncomingConnector(), executorFactory);
-        client = new DefaultMessagingClient(connector, messagingClassLoader, serverAddress);
-    }
-
-    public ObjectConnection getConnection() {
-        return client.getConnection();
-    }
-
-    public void stop() {
-        new CompositeStoppable(client, connector, executorFactory).stop();
-    }
-
-    private static class NoOpIncomingConnector implements IncomingConnector {
-        public URI accept(Action<ConnectEvent<Connection<Object>>> action) {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingServer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingServer.java
deleted file mode 100644
index 15743e5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingServer.java
+++ /dev/null
@@ -1,58 +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.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.messaging.concurrent.CompositeStoppable;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.net.URI;
-
-/**
- * A {@link org.gradle.messaging.remote.MessagingServer} implementation which uses a single incoming TCP port for all peers.
- */
-public class TcpMessagingServer implements MessagingServer {
-    private final TcpIncomingConnector incomingConnector;
-    private final DefaultMultiChannelConnector connector;
-    private final DefaultMessagingServer server;
-    private final DefaultExecutorFactory executorFactory;
-
-    public TcpMessagingServer(ClassLoader messageClassLoader) {
-        executorFactory = new DefaultExecutorFactory();
-        incomingConnector = new TcpIncomingConnector(executorFactory, messageClassLoader);
-        connector = new DefaultMultiChannelConnector(new NoOpOutgoingConnector(), incomingConnector, executorFactory);
-        server = new DefaultMessagingServer(connector, messageClassLoader);
-    }
-
-    public URI accept(Action<ConnectEvent<ObjectConnection>> action) {
-        return server.accept(action);
-    }
-
-    public void stop() {
-        incomingConnector.requestStop();
-        new CompositeStoppable(server, connector, incomingConnector, executorFactory).stop();
-    }
-
-    private static class NoOpOutgoingConnector implements OutgoingConnector {
-        public Connection<Message> connect(URI destinationUri) {
-            throw new UnsupportedOperationException();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java
deleted file mode 100644
index 8b167c5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java
+++ /dev/null
@@ -1,100 +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.messaging.remote.internal;
-
-import org.gradle.api.GradleException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.*;
-import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-
-public class TcpOutgoingConnector implements OutgoingConnector {
-    private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
-    private ClassLoader classLoader;
-
-    public TcpOutgoingConnector(ClassLoader classLoader) {
-        this.classLoader = classLoader;
-    }
-
-    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));
-        }
-
-        // Find all loop back addresses. Not all of them are necessarily reachable (eg when socket option IPV6_V6ONLY
-        // is on - the default for debian and others), so we will try each of them until we can connect
-        List<InetAddress> loopBackAddresses = findLocalAddresses();
-
-        // Now try each address
-        try {
-            SocketException lastFailure = null;
-            for (InetAddress address : loopBackAddresses) {
-                LOGGER.debug("Trying to connect to address {}.", address);
-                SocketChannel socketChannel;
-                try {
-                    socketChannel = SocketChannel.open(new InetSocketAddress(address, destinationUri.getPort()));
-                } catch (SocketException e) {
-                    LOGGER.debug("Cannot connect to address {}, skipping.", address);
-                    lastFailure = e;
-                    continue;
-                }
-                LOGGER.debug("Connected to address {}.", address);
-                URI localAddress = new URI(String.format("tcp://localhost:%d", socketChannel.socket().getLocalPort()));
-                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);
-        }
-    }
-
-    /**
-     * Never returns an empty list.
-     */
-    static List<InetAddress> findLocalAddresses() {
-        try {
-            List<InetAddress> addresses = new ArrayList<InetAddress>();
-            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
-            while (interfaces.hasMoreElements()) {
-                NetworkInterface networkInterface = interfaces.nextElement();
-                Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
-                while (candidates.hasMoreElements()) {
-                    InetAddress inetAddress = candidates.nextElement();
-                    if (inetAddress.isLoopbackAddress()) {
-                        addresses.add(inetAddress);
-                    }
-                }
-            }
-            if (addresses.isEmpty()) {
-                addresses.add(InetAddress.getByName(null));
-            }
-            LOGGER.debug("Found loop-back addresses: {}.", addresses);
-            return addresses;
-        } catch (Exception e) {
-            throw new GradleException("Could not determine the local loop-back addresses.", e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TypeCastDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TypeCastDispatch.java
new file mode 100644
index 0000000..4b06f1b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TypeCastDispatch.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch;
+
+class TypeCastDispatch<S, T> implements Dispatch<T> {
+    private final Class<S> type;
+    private final Dispatch<? super S> dispatch;
+
+    TypeCastDispatch(Class<S> type, Dispatch<? super S> dispatch) {
+        this.type = type;
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(T message) {
+        dispatch.dispatch(type.cast(message));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocol.java
new file mode 100644
index 0000000..885bbdd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocol.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.ConsumerAvailable;
+import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable;
+import org.gradle.messaging.remote.internal.protocol.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UnicastSendProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(UnicastSendProtocol.class);
+    private static final Object BROKEN_CONSUMER = new Object();
+    private final List<Object> queue = new ArrayList<Object>();
+    private String consumerDisplayName;
+    private Object consumerId;
+    private ProtocolContext<Message> context;
+    private boolean stopping;
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof ConsumerAvailable) {
+            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
+            LOGGER.debug("Consumer available: {}", consumerAvailable);
+            consumerId = consumerAvailable.getId();
+            consumerDisplayName = consumerAvailable.getDisplayName();
+            for (Object queued : queue) {
+                context.dispatchOutgoing(new Request(consumerId, queued));
+            }
+            queue.clear();
+            if (stopping) {
+                LOGGER.debug("Queued messages dispatched. Stopping now.");
+                context.stopped();
+            }
+        } else if (message instanceof ConsumerUnavailable) {
+            consumerId = BROKEN_CONSUMER;
+        } else {
+            throw new IllegalArgumentException(String.format("Received unexpected incoming message: %s", message));
+        }
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof Request) {
+            Request request = (Request) message;
+            if (consumerId == null) {
+                queue.add(request.getPayload());
+            } else if (consumerId == BROKEN_CONSUMER) {
+                LOGGER.warn("Discarding message {}, as {} is no longer available.", message, consumerDisplayName);
+            } else {
+                context.dispatchOutgoing(new Request(consumerId, request.getPayload()));
+            }
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+        }
+    }
+
+    public void stopRequested() {
+        if (queue.isEmpty()) {
+            context.stopped();
+            return;
+        }
+
+        LOGGER.debug("Waiting for outgoing messages to be dispatched to a consumer.");
+        stopping = true;
+        context.stopLater();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/WorkerProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/WorkerProtocol.java
new file mode 100644
index 0000000..3ac16eb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/WorkerProtocol.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch;
+import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent;
+import org.gradle.messaging.remote.internal.protocol.MessageCredits;
+import org.gradle.messaging.remote.internal.protocol.Request;
+import org.gradle.messaging.remote.internal.protocol.WorkerStopping;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Adapts a worker {@link Dispatch} to a messaging protocol.
+ *
+ * Note: this protocol blocks while messages are being dispatched. This means sending and receiving are stuck for the whole stack. Generally, this protocol should live in its own stack.
+ */
+public class WorkerProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(WorkerProtocol.class);
+    private final Dispatch<Object> worker;
+    private ProtocolContext<Message> context;
+
+    public WorkerProtocol(Dispatch<Object> worker) {
+        this.worker = worker;
+    }
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+        context.dispatchOutgoing(new MessageCredits(1));
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof EndOfStreamEvent) {
+            LOGGER.debug("Received worker stopped: {}", message);
+            context.stopped();
+        } else if (message instanceof Request) {
+            Request request = (Request) message;
+            LOGGER.debug("Dispatching request to worker: {}", message);
+            try {
+                worker.dispatch(request.getPayload());
+            } finally {
+                context.dispatchOutgoing(new MessageCredits(1));
+            }
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
+        }
+    }
+
+    public void handleOutgoing(Message message) {
+        throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+    }
+
+    public void stopRequested() {
+        context.dispatchOutgoing(new WorkerStopping());
+        context.stopLater();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
new file mode 100644
index 0000000..9a04f14
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.inet;
+
+import org.gradle.api.GradleException;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+public class InetAddressFactory {
+    /**
+     * Locates the local (loopback) addresses for this machine. Never returns an empty list.
+     */
+    public List<InetAddress> findLocalAddresses() {
+        try {
+            List<InetAddress> addresses = new ArrayList<InetAddress>();
+            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+            while (interfaces.hasMoreElements()) {
+                NetworkInterface networkInterface = interfaces.nextElement();
+                Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
+                while (candidates.hasMoreElements()) {
+                    InetAddress candidate = candidates.nextElement();
+                    if (candidate.isLoopbackAddress()) {
+                        addresses.add(candidate);
+                    }
+                }
+            }
+            if (addresses.isEmpty()) {
+                addresses.add(InetAddress.getByName(null));
+            }
+            return addresses;
+        } catch (Exception e) {
+            throw new GradleException("Could not determine the local IP addresses for this machine.", e);
+        }
+    }
+
+    /**
+     * Locates the remote (non-loopback) addresses for this machine. Never returns an empty list.
+     */
+    public List<InetAddress> findRemoteAddresses() {
+        try {
+            List<InetAddress> addresses = new ArrayList<InetAddress>();
+            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+            while (interfaces.hasMoreElements()) {
+                NetworkInterface networkInterface = interfaces.nextElement();
+                Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
+                while (candidates.hasMoreElements()) {
+                    InetAddress candidate = candidates.nextElement();
+                    if (!candidate.isLoopbackAddress()) {
+                        addresses.add(candidate);
+                    }
+                }
+            }
+            if (addresses.isEmpty()) {
+                addresses.add(InetAddress.getLocalHost());
+            }
+            return addresses;
+        } catch (Exception e) {
+            throw new GradleException("Could not determine the remote IP addresses for this machine.", e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetEndpoint.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetEndpoint.java
new file mode 100644
index 0000000..ebe4bac
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetEndpoint.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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.inet;
+
+import org.gradle.messaging.remote.Address;
+
+import java.net.InetAddress;
+import java.util.List;
+
+/**
+ * A Inet-based route. Has a port and a set of potential inet addresses.
+ */
+public interface InetEndpoint extends Address {
+    int getPort();
+
+    List<InetAddress> getCandidates();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java
new file mode 100644
index 0000000..01f8a8d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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.inet;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MultiChoiceAddress implements InetEndpoint {
+    private final Object canonicalAddress;
+    private final int port;
+    private final List<InetAddress> candidates;
+
+    public MultiChoiceAddress(Object canonicalAddress, int port, List<InetAddress> candidates) {
+        this.canonicalAddress = canonicalAddress;
+        this.port = port;
+        this.candidates = new ArrayList<InetAddress>(candidates);
+    }
+
+    public String getDisplayName() {
+        return String.format("[%s port:%s, addresses:%s]", canonicalAddress, port, candidates);
+    }
+
+    public Object getCanonicalAddress() {
+        return canonicalAddress;
+    }
+
+    public List<InetAddress> getCandidates() {
+        return candidates;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        MultiChoiceAddress other = (MultiChoiceAddress) o;
+        return other.canonicalAddress.equals(canonicalAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        return canonicalAddress.hashCode();
+    }
+
+    public MultiChoiceAddress addAddresses(Iterable<InetAddress> candidates) {
+        return new MultiChoiceAddress(canonicalAddress, port, Lists.newArrayList(Iterables.concat(candidates, this.candidates)));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MulticastConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
new file mode 100644
index 0000000..c1fee6a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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.inet;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageIOException;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+
+import java.io.*;
+import java.net.DatagramPacket;
+import java.net.MulticastSocket;
+import java.net.SocketException;
+
+public class MulticastConnection<T> implements Connection<T> {
+    private static final int MAX_MESSAGE_SIZE = 32*1024;
+    private final MulticastSocket socket;
+    private final SocketInetAddress address;
+    private final MessageSerializer<T> serializer;
+    private final SocketInetAddress localAddress;
+
+    public MulticastConnection(SocketInetAddress address, MessageSerializer<T> serializer) {
+        this.address = address;
+        this.serializer = serializer;
+        try {
+            socket = new MulticastSocket(address.getPort());
+            socket.joinGroup(address.getAddress());
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        localAddress = new SocketInetAddress(socket.getInetAddress(), socket.getLocalPort());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("multicast connection %s", address);
+    }
+
+    public void dispatch(T message) {
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
+            serializer.write(message, dataOutputStream);
+            dataOutputStream.close();
+            byte[] buffer = outputStream.toByteArray();
+            socket.send(new DatagramPacket(buffer, buffer.length, address.getAddress(), address.getPort()));
+        } catch (Exception e) {
+            throw new MessageIOException(String.format("Could not write multi-cast message on %s.", address), e);
+        }
+    }
+
+    public T receive() {
+        try {
+            byte[] buffer = new byte[MAX_MESSAGE_SIZE];
+            DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
+            socket.receive(packet);
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength());
+            DataInputStream dataInputStream = new DataInputStream(inputStream);
+            return serializer.read(dataInputStream, localAddress, new SocketInetAddress(packet.getAddress(), packet.getPort()));
+        } catch (SocketException e) {
+            // Assume closed
+            return null;
+        } catch (Exception e) {
+            throw new MessageIOException(String.format("Could not receive multi-cast message on %s", address), e);
+        }
+    }
+
+    public void requestStop() {
+        socket.close();
+    }
+
+    public void stop() {
+        requestStop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketConnection.java
new file mode 100755
index 0000000..b19ed92
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketConnection.java
@@ -0,0 +1,233 @@
+/*
+ * 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.inet;
+
+import com.google.common.base.Objects;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageIOException;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+
+import java.io.*;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedSelectorException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+
+public class SocketConnection<T> implements Connection<T> {
+    private final SocketChannel socket;
+    private final SocketInetAddress localAddress;
+    private final SocketInetAddress remoteAddress;
+    private final MessageSerializer<T> serializer;
+    private final DataInputStream instr;
+    private final DataOutputStream outstr;
+
+    public SocketConnection(SocketChannel socket, MessageSerializer<T> serializer) {
+        this.socket = socket;
+        this.serializer = serializer;
+        try {
+            // NOTE: we use non-blocking IO as there is no reliable way when using blocking IO to shutdown reads while
+            // keeping writes active. For example, Socket.shutdownInput() does not work on Windows.
+            socket.configureBlocking(false);
+            outstr = new DataOutputStream(new SocketOutputStream(socket));
+            instr = new DataInputStream(new SocketInputStream(socket));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        InetSocketAddress localSocketAddress = (InetSocketAddress) socket.socket().getLocalSocketAddress();
+        localAddress = new SocketInetAddress(localSocketAddress.getAddress(), localSocketAddress.getPort());
+        InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
+        remoteAddress = new SocketInetAddress(remoteSocketAddress.getAddress(), remoteSocketAddress.getPort());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("socket connection at %s with %s", localAddress, remoteAddress);
+    }
+
+    public Address getLocalAddress() {
+        return localAddress;
+    }
+
+    public Address getRemoteAddress() {
+        return remoteAddress;
+    }
+
+    public T receive() {
+        try {
+            return serializer.read(instr, localAddress, remoteAddress);
+        } catch (Exception e) {
+            if (isEndOfStream(e)) {
+                return null;
+            }
+            throw new MessageIOException(String.format("Could not read message from '%s'.", remoteAddress), e);
+        }
+    }
+
+    private boolean isEndOfStream(Exception e) {
+        if (e instanceof EOFException) {
+            return true;
+        }
+        if (e instanceof IOException) {
+            if (Objects.equal(e.getMessage(), "An existing connection was forcibly closed by the remote host")) {
+                return true;
+            }
+            if (Objects.equal(e.getMessage(), "An established connection was aborted by the software in your host machine")) {
+                return true;
+            }
+            if (Objects.equal(e.getMessage(), "Connection reset by peer")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void dispatch(T message) {
+        try {
+            serializer.write(message, outstr);
+            outstr.flush();
+        } catch (Exception e) {
+            throw new MessageIOException(String.format("Could not write message %s to '%s'.", message, remoteAddress), e);
+        }
+    }
+
+    public void requestStop() {
+        new CompositeStoppable(instr).stop();
+    }
+
+    public void stop() {
+        new CompositeStoppable(instr, outstr, socket).stop();
+    }
+
+    private static class SocketInputStream extends InputStream {
+        private final Selector selector;
+        private final ByteBuffer buffer;
+        private final SocketChannel socket;
+        private final byte[] readBuffer = new byte[1];
+
+        public SocketInputStream(SocketChannel socket) throws IOException {
+            this.socket = socket;
+            selector = Selector.open();
+            socket.register(selector, SelectionKey.OP_READ);
+            buffer = ByteBuffer.allocateDirect(4096);
+            buffer.limit(0);
+        }
+
+        @Override
+        public int read() throws IOException {
+            int nread = read(readBuffer, 0, 1);
+            if (nread <= 0) {
+                return nread;
+            }
+            return readBuffer[0];
+        }
+
+        @Override
+        public int read(byte[] dest, int offset, int max) throws IOException {
+            if (max == 0) {
+                return 0;
+            }
+
+            if (buffer.remaining() == 0) {
+                try {
+                    selector.select();
+                } catch (ClosedSelectorException e) {
+                    return -1;
+                }
+                if (!selector.isOpen()) {
+                    return -1;
+                }
+
+                buffer.clear();
+                int nread = socket.read(buffer);
+                buffer.flip();
+
+                if (nread < 0) {
+                    return -1;
+                }
+            }
+
+            int count = Math.min(buffer.remaining(), max);
+            buffer.get(dest, offset, count);
+            return count;
+        }
+
+        @Override
+        public void close() throws IOException {
+            selector.close();
+        }
+    }
+
+    private static class SocketOutputStream extends OutputStream {
+        private final Selector selector;
+        private final SocketChannel socket;
+        private final ByteBuffer buffer;
+        private final byte[] writeBuffer = new byte[1];
+
+        public SocketOutputStream(SocketChannel socket) throws IOException {
+            this.socket = socket;
+            selector = Selector.open();
+            socket.register(selector, SelectionKey.OP_WRITE);
+            buffer = ByteBuffer.allocateDirect(4096);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            writeBuffer[0] = (byte) b;
+            write(writeBuffer);
+        }
+
+        @Override
+        public void write(byte[] src, int offset, int max) throws IOException {
+            int remaining = max;
+            int currentPos = offset;
+            while (remaining > 0) {
+                int count = Math.min(remaining, buffer.remaining());
+                if (count > 0) {
+                    buffer.put(src, currentPos, count);
+                    remaining -= count;
+                    currentPos += count;
+                }
+                if (buffer.remaining() == 0) {
+                    flush();
+                }
+            }
+        }
+
+        @Override
+        public void flush() throws IOException {
+            buffer.flip();
+            while (buffer.remaining() > 0) {
+                selector.select();
+                if (!selector.isOpen()) {
+                    throw new EOFException();
+                }
+                socket.write(buffer);
+            }
+            buffer.clear();
+        }
+
+        @Override
+        public void close() throws IOException {
+            selector.close();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java
new file mode 100644
index 0000000..e600dff
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.inet;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.List;
+
+public class SocketInetAddress implements InetEndpoint {
+    private final InetAddress address;
+    private final int port;
+
+    public SocketInetAddress(InetAddress address, int port) {
+        this.address = address;
+        this.port = port;
+    }
+
+    public String getDisplayName() {
+        return String.format("%s:%s", address, port);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        SocketInetAddress other = (SocketInetAddress) o;
+        return other.address.equals(address) && other.port == port;
+    }
+
+    @Override
+    public int hashCode() {
+        return address.hashCode() ^ port;
+    }
+
+    public List<InetAddress> getCandidates() {
+        return Collections.singletonList(address);
+    }
+
+    public InetAddress getAddress() {
+        return address;
+    }
+
+    public int getPort() {
+        return port;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
new file mode 100755
index 0000000..084b0a6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
@@ -0,0 +1,131 @@
+/*
+ * 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.inet;
+
+import org.gradle.api.Action;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.IncomingConnector;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.util.IdGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TcpIncomingConnector.class);
+    private final StoppableExecutor executor;
+    private final MessageSerializer<T> serializer;
+    private final IdGenerator<?> idGenerator;
+    private final List<InetAddress> localAddresses;
+    private final List<InetAddress> remoteAddresses;
+    private final List<ServerSocketChannel> serverSockets = new CopyOnWriteArrayList<ServerSocketChannel>();
+
+    public TcpIncomingConnector(ExecutorFactory executorFactory, MessageSerializer<T> serializer, InetAddressFactory addressFactory, IdGenerator<?> idGenerator) {
+        this.serializer = serializer;
+        this.idGenerator = idGenerator;
+        this.executor = executorFactory.create("Incoming TCP Connector");
+
+        localAddresses = addressFactory.findLocalAddresses();
+        remoteAddresses = addressFactory.findRemoteAddresses();
+    }
+
+    public Address accept(Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
+        ServerSocketChannel serverSocket;
+        int localPort;
+        try {
+            serverSocket = ServerSocketChannel.open();
+            serverSockets.add(serverSocket);
+            serverSocket.socket().bind(new InetSocketAddress(0));
+            localPort = serverSocket.socket().getLocalPort();
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        Object id = idGenerator.generateId();
+        List<InetAddress> addresses = allowRemote ? remoteAddresses : localAddresses;
+        Address address = new MultiChoiceAddress(id, localPort, addresses);
+        LOGGER.debug("Listening on {}.", address);
+
+        executor.execute(new Receiver(serverSocket, action, allowRemote));
+        return address;
+    }
+
+    public void requestStop() {
+        new CompositeStoppable().add(serverSockets).stop();
+    }
+
+    public void stop() {
+        requestStop();
+        executor.stop();
+    }
+
+    private class Receiver implements Runnable {
+        private final ServerSocketChannel serverSocket;
+        private final Action<ConnectEvent<Connection<T>>> action;
+        private final boolean allowRemote;
+
+        public Receiver(ServerSocketChannel serverSocket, Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
+            this.serverSocket = serverSocket;
+            this.action = action;
+            this.allowRemote = allowRemote;
+        }
+
+        public void run() {
+            try {
+                try {
+                    while (true) {
+                        SocketChannel socket = serverSocket.accept();
+                        InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
+                        if (!allowRemote && !localAddresses.contains(remoteSocketAddress.getAddress())) {
+                            LOGGER.error("Cannot accept connection from remote address {}.", remoteSocketAddress.getAddress());
+                            socket.close();
+                            continue;
+                        }
+
+                        SocketConnection<T> connection = new SocketConnection<T>(socket, serializer);
+                        Address localAddress = connection.getLocalAddress();
+                        Address remoteAddress = connection.getRemoteAddress();
+
+                        LOGGER.debug("Accepted connection from {} to {}.", remoteAddress, localAddress);
+                        action.execute(new ConnectEvent<Connection<T>>(connection, localAddress, remoteAddress));
+                    }
+                } catch (ClosedChannelException e) {
+                    // Ignore
+                } catch (Exception e) {
+                    LOGGER.error("Could not accept remote connection.", e);
+                }
+            } finally {
+                new CompositeStoppable(serverSocket).stop();
+                serverSockets.remove(serverSocket);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
new file mode 100755
index 0000000..d0ade1e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
@@ -0,0 +1,78 @@
+/*
+ * 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.inet;
+
+import org.gradle.api.GradleException;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.ConnectException;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.OutgoingConnector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+public class TcpOutgoingConnector<T> implements OutgoingConnector<T> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
+    private final MessageSerializer<T> serializer;
+
+    public TcpOutgoingConnector(MessageSerializer<T> serializer) {
+        this.serializer = serializer;
+    }
+
+    public Connection<T> connect(Address destinationAddress) {
+        if (!(destinationAddress instanceof InetEndpoint)) {
+            throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
+        }
+        InetEndpoint address = (InetEndpoint) destinationAddress;
+        LOGGER.debug("Attempting to connect to {}.", address);
+
+        // Try each address in turn. Not all of them are necessarily reachable (eg when socket option IPV6_V6ONLY
+        // is on - the default for debian and others), so we will try each of them until we can connect
+        List<InetAddress> candidateAddresses = address.getCandidates();
+
+        // Now try each address
+        try {
+            SocketException lastFailure = null;
+            for (InetAddress candidate : candidateAddresses) {
+                LOGGER.debug("Trying to connect to address {}.", candidate);
+                SocketChannel socketChannel;
+                try {
+                    socketChannel = SocketChannel.open(new InetSocketAddress(candidate, address.getPort()));
+                } catch (SocketException e) {
+                    LOGGER.debug("Cannot connect to address {}, skipping.", candidate);
+                    lastFailure = e;
+                    continue;
+                }
+                LOGGER.debug("Connected to address {}.", candidate);
+                return new SocketConnection<T>(socketChannel, serializer);
+            }
+            throw lastFailure;
+        } catch (java.net.ConnectException e) {
+            throw new ConnectException(String.format("Could not connect to server %s. Tried addresses: %s.",
+                    destinationAddress, candidateAddresses), e);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not connect to server %s. Tried addresses: %s.",
+                    destinationAddress, candidateAddresses), e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java
new file mode 100644
index 0000000..d2fbcdb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public abstract class AbstractPayloadMessage extends Message implements PayloadMessage {
+    public Object getNestedPayload() {
+        Object payload = getPayload();
+        if (payload instanceof PayloadMessage) {
+            PayloadMessage payloadMessage = (PayloadMessage) payload;
+            return payloadMessage.getNestedPayload();
+        }
+        return payload;
+    }
+
+    public Message withNestedPayload(Object payload) {
+        Object oldPayload = getPayload();
+        if (oldPayload instanceof PayloadMessage) {
+            PayloadMessage payloadMessage = (PayloadMessage) oldPayload;
+            return withPayload(payloadMessage.withNestedPayload(payload));
+        }
+        return withPayload(payload);
+    }
+
+    public abstract Object getPayload();
+
+    public abstract Message withPayload(Object payload);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java
new file mode 100644
index 0000000..e513514
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.MessageOriginator;
+
+public class ChannelAvailable extends DiscoveryMessage {
+    private final String channel;
+    private final Address address;
+
+    public ChannelAvailable(MessageOriginator originator, String group, String channel, Address address) {
+        super(originator, group);
+        this.channel = channel;
+        this.address = address;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!super.equals(o)) {
+            return false;
+        }
+        ChannelAvailable other = (ChannelAvailable) o;
+        return channel.equals(other.channel) && address.equals(other.address);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ channel.hashCode() ^ address.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[ChannelAvailable channel: %s, address: %s, source: %s]", channel, address, getOriginator());
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java
new file mode 100644
index 0000000..d80a8ee
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.MessageOriginator;
+
+public class ChannelUnavailable extends DiscoveryMessage {
+    private final String channel;
+    private final Address address;
+
+    public ChannelUnavailable(MessageOriginator originator, String group, String channel, Address address) {
+        super(originator, group);
+        this.channel = channel;
+        this.address = address;
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        ChannelUnavailable other = (ChannelUnavailable) o;
+        return channel.equals(other.channel) && address.equals(other.address);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ channel.hashCode() ^ address.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[ChannelUnavailable channel: %s, address: %s, source: %s]", channel, address, getOriginator());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
new file mode 100755
index 0000000..a6e8667
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
@@ -0,0 +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.protocol;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.Message;
+
+public class ConnectRequest extends Message {
+    private final Address destinationAddress;
+
+    public ConnectRequest(Address destinationAddress) {
+        this.destinationAddress = destinationAddress;
+    }
+
+    public Address getDestinationAddress() {
+        return destinationAddress;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java
new file mode 100644
index 0000000..8befb6b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ConsumerAvailable extends ParticipantAvailable {
+    public ConsumerAvailable(Object id, String displayName, String channelKey) {
+        super(id, displayName, channelKey);
+    }
+
+    public RouteUnavailableMessage getUnavailableMessage() {
+        return new ConsumerUnavailable(getId());
+    }
+
+    public boolean acceptIncoming(RouteAvailableMessage message) {
+        if (message instanceof ProducerAvailable) {
+            ProducerAvailable producerAvailable = (ProducerAvailable) message;
+            return producerAvailable.getChannelKey().equals(getChannelKey());
+        }
+        return false;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java
new file mode 100644
index 0000000..6a46f1c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public abstract class ConsumerMessage extends Message implements RoutableMessage {
+    protected final Object consumerId;
+    protected final Object producerId;
+
+    public ConsumerMessage(Object consumerId, Object producerId) {
+        this.producerId = producerId;
+        this.consumerId = consumerId;
+    }
+
+    public Object getConsumerId() {
+        return consumerId;
+    }
+
+    public Object getProducerId() {
+        return producerId;
+    }
+
+    public Object getDestination() {
+        return producerId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ConsumerMessage other = (ConsumerMessage) o;
+        return consumerId.equals(other.consumerId) && producerId.equals(other.producerId);
+    }
+
+    @Override
+    public int hashCode() {
+        return consumerId.hashCode() ^ producerId.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s, consumerId: %s, producerId: %s]", getClass().getSimpleName(), consumerId, producerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java
new file mode 100644
index 0000000..6904eaf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ConsumerReady extends ConsumerMessage {
+    public ConsumerReady(Object consumerId, Object producerId) {
+        super(consumerId, producerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java
new file mode 100644
index 0000000..ffae984
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ConsumerStopped extends ConsumerMessage {
+    public ConsumerStopped(Object consumerId, Object producerId) {
+        super(consumerId, producerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java
new file mode 100644
index 0000000..fe397a0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ConsumerStopping extends ConsumerMessage {
+    public ConsumerStopping(Object consumerId, Object producerId) {
+        super(consumerId, producerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java
new file mode 100644
index 0000000..88e4c6c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ConsumerUnavailable extends ParticipantUnavailable {
+    public ConsumerUnavailable(Object id) {
+        super(id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
new file mode 100644
index 0000000..3b3507b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.MessageOriginator;
+
+import java.io.Serializable;
+
+public class DiscoveryMessage implements Serializable {
+    private final MessageOriginator originator;
+    private final String group;
+
+    public DiscoveryMessage(MessageOriginator originator, String group) {
+        this.originator = originator;
+        this.group = group;
+    }
+
+    public MessageOriginator getOriginator() {
+        return originator;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+
+        DiscoveryMessage other = (DiscoveryMessage) o;
+        return originator.equals(other.originator) && group.equals(other.group);
+    }
+
+    @Override
+    public int hashCode() {
+        return group.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
new file mode 100644
index 0000000..c1cc23b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.MessageOriginator;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.inet.InetEndpoint;
+import org.gradle.messaging.remote.internal.inet.MultiChoiceAddress;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class DiscoveryProtocolSerializer implements MessageSerializer<DiscoveryMessage> {
+    public static final byte PROTOCOL_VERSION = 1;
+    public static final byte LOOKUP_REQUEST = 1;
+    public static final byte CHANNEL_AVAILABLE = 2;
+    public static final byte CHANNEL_UNAVAILABLE = 3;
+
+    public DiscoveryMessage read(DataInputStream inputStream, InetEndpoint localAddress, InetEndpoint remoteAddress) throws Exception {
+        byte protocolVersion = inputStream.readByte();
+        if (protocolVersion != PROTOCOL_VERSION) {
+            return new UnknownMessage(String.format("unknown protocol version %s", protocolVersion));
+        }
+        byte messageType = inputStream.readByte();
+        switch (messageType) {
+            case LOOKUP_REQUEST:
+                return readLookupRequest(inputStream);
+            case CHANNEL_AVAILABLE:
+                return readChannelAvailable(inputStream, remoteAddress);
+            case CHANNEL_UNAVAILABLE:
+                return readChannelUnavailable(inputStream);
+        }
+        return new UnknownMessage(String.format("unknown message type %s", messageType));
+    }
+
+    private DiscoveryMessage readChannelUnavailable(DataInputStream inputStream) throws IOException {
+        MessageOriginator originator = readMessageOriginator(inputStream);
+        String group = inputStream.readUTF();
+        String channel = inputStream.readUTF();
+        Address address = readAddress(inputStream);
+        return new ChannelUnavailable(originator, group, channel, address);
+    }
+
+    private DiscoveryMessage readChannelAvailable(DataInputStream inputStream, InetEndpoint remoteAddress) throws IOException {
+        MessageOriginator originator = readMessageOriginator(inputStream);
+        String group = inputStream.readUTF();
+        String channel = inputStream.readUTF();
+        MultiChoiceAddress address = readAddress(inputStream);
+        address = address.addAddresses(remoteAddress.getCandidates());
+        return new ChannelAvailable(originator, group, channel, address);
+    }
+
+    private DiscoveryMessage readLookupRequest(DataInputStream inputStream) throws IOException {
+        MessageOriginator originator = readMessageOriginator(inputStream);
+        String group = inputStream.readUTF();
+        String channel = inputStream.readUTF();
+        return new LookupRequest(originator, group, channel);
+    }
+
+    private MessageOriginator readMessageOriginator(DataInputStream inputStream) throws IOException {
+        UUID uuid = readUUID(inputStream);
+        String name = inputStream.readUTF();
+        return new MessageOriginator(uuid, name);
+    }
+
+    private MultiChoiceAddress readAddress(DataInputStream inputStream) throws IOException {
+        UUID uuid = readUUID(inputStream);
+        int port = inputStream.readInt();
+        int addressCount = inputStream.readInt();
+        List<InetAddress> addresses = new ArrayList<InetAddress>();
+        for (int i = 0; i < addressCount; i++) {
+            int length = inputStream.readInt();
+            byte[] binAddress = new byte[length];
+            inputStream.readFully(binAddress);
+            InetAddress inetAddress = InetAddress.getByAddress(binAddress);
+            addresses.add(inetAddress);
+        }
+        return new MultiChoiceAddress(uuid, port, addresses);
+    }
+
+    private UUID readUUID(DataInputStream inputStream) throws IOException {
+        long mostSigUuidBits = inputStream.readLong();
+        long leastSigUuidBits = inputStream.readLong();
+        return new UUID(mostSigUuidBits, leastSigUuidBits);
+    }
+
+    public void write(DiscoveryMessage message, DataOutputStream outputStream) throws Exception {
+        outputStream.writeByte(PROTOCOL_VERSION);
+        if (message instanceof LookupRequest) {
+            writeLookupRequest(outputStream, (LookupRequest) message);
+        } else if (message instanceof ChannelAvailable) {
+            writeChannelAvailable(outputStream, (ChannelAvailable) message);
+        } else if (message instanceof ChannelUnavailable) {
+            writeChannelUnavailable(outputStream, (ChannelUnavailable) message);
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private void writeChannelUnavailable(DataOutputStream outputStream, ChannelUnavailable channelUnavailable) throws IOException {
+        outputStream.writeByte(CHANNEL_UNAVAILABLE);
+        writeMessageOriginator(outputStream, channelUnavailable.getOriginator());
+        outputStream.writeUTF(channelUnavailable.getGroup());
+        outputStream.writeUTF(channelUnavailable.getChannel());
+        writeAddress(outputStream, (MultiChoiceAddress) channelUnavailable.getAddress());
+    }
+
+    private void writeChannelAvailable(DataOutputStream outputStream, ChannelAvailable channelAvailable) throws IOException {
+        outputStream.writeByte(CHANNEL_AVAILABLE);
+        writeMessageOriginator(outputStream, channelAvailable.getOriginator());
+        outputStream.writeUTF(channelAvailable.getGroup());
+        outputStream.writeUTF(channelAvailable.getChannel());
+        writeAddress(outputStream, (MultiChoiceAddress) channelAvailable.getAddress());
+    }
+
+    private void writeLookupRequest(DataOutputStream outputStream, LookupRequest request) throws IOException {
+        outputStream.writeByte(LOOKUP_REQUEST);
+        writeMessageOriginator(outputStream, request.getOriginator());
+        outputStream.writeUTF(request.getGroup());
+        outputStream.writeUTF(request.getChannel());
+    }
+
+    private void writeMessageOriginator(DataOutputStream outputStream, MessageOriginator messageOriginator) throws IOException {
+        writeUUID(outputStream, messageOriginator.getId());
+        outputStream.writeUTF(messageOriginator.getName());
+    }
+
+    private void writeAddress(DataOutputStream outputStream, MultiChoiceAddress address) throws IOException {
+        writeUUID(outputStream, address.getCanonicalAddress());
+        outputStream.writeInt(address.getPort());
+        outputStream.writeInt(address.getCandidates().size());
+        for (InetAddress inetAddress : address.getCandidates()) {
+            byte[] binAddress = inetAddress.getAddress();
+            outputStream.writeInt(binAddress.length);
+            outputStream.write(binAddress);
+        }
+    }
+
+    private void writeUUID(DataOutputStream outputStream, Object id) throws IOException {
+        UUID uuid = (UUID) id;
+        outputStream.writeLong(uuid.getMostSignificantBits());
+        outputStream.writeLong(uuid.getLeastSignificantBits());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java
new file mode 100755
index 0000000..8059f7f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java
@@ -0,0 +1,20 @@
+/*
+ * 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.protocol;
+
+public class EndOfStreamEvent extends StatelessMessage {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/LookupRequest.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/LookupRequest.java
new file mode 100644
index 0000000..7a2f3e6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/LookupRequest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.MessageOriginator;
+
+public class LookupRequest extends DiscoveryMessage {
+    private final String channel;
+
+    public LookupRequest(MessageOriginator originator, String group, String channel) {
+        super(originator, group);
+        this.channel = channel;
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        LookupRequest other = (LookupRequest) o;
+        return channel.equals(other.channel);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ channel.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[LookupRequest channel: %s, source: %s]", channel, getOriginator());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MessageCredits.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MessageCredits.java
new file mode 100644
index 0000000..c447bfd
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MessageCredits.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public class MessageCredits extends Message {
+    private final int credits;
+
+    public MessageCredits(int credits) {
+        this.credits = credits;
+    }
+
+    public int getCredits() {
+        return credits;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+
+        MessageCredits other = (MessageCredits) o;
+        return credits == other.credits;
+    }
+
+    @Override
+    public int hashCode() {
+        return credits;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[MessageCredits credits: %d]", credits);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
new file mode 100755
index 0000000..3d0d1f8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
@@ -0,0 +1,115 @@
+/*
+ * 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.protocol;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.remote.internal.Message;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+public class MethodMetaInfo extends Message {
+    private final Type type;
+    private final String methodName;
+    private final Type[] paramTypes;
+    private final Object key;
+
+    public MethodMetaInfo(Object key, Method method) {
+        this.key = key;
+        type = new Type(method.getDeclaringClass());
+        methodName = method.getName();
+        paramTypes = new Type[method.getParameterTypes().length];
+        for (int i = 0; i < method.getParameterTypes().length; i++) {
+            Class<?> paramType = method.getParameterTypes()[i];
+            paramTypes[i] = new Type(paramType);
+        }
+    }
+
+    public Object getKey() {
+        return key;
+    }
+
+    public Method findMethod(ClassLoader classLoader) {
+        try {
+            Class<?> declaringClass = this.type.load(classLoader);
+            Class<?>[] paramTypes = new Class[this.paramTypes.length];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                Type paramType = this.paramTypes[i];
+                paramTypes[i] = paramType.load(classLoader);
+            }
+            return declaringClass.getMethod(methodName, paramTypes);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+
+        MethodMetaInfo other = (MethodMetaInfo) obj;
+        if (!key.equals(other.key)) {
+            return false;
+        }
+        if (!type.equals(other.type)) {
+            return false;
+        }
+        if (!methodName.equals(other.methodName)) {
+            return false;
+        }
+        return Arrays.equals(paramTypes, other.paramTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+
+    private static class Type implements Serializable {
+        private String typeName;
+        private Class<?> type;
+
+        public Type(Class<?> type) {
+            this.typeName = type.getName();
+            if (type.isPrimitive()) {
+                this.type = type;
+            }
+        }
+
+        Class<?> load(ClassLoader classLoader) throws ClassNotFoundException {
+            if (type != null) {
+                return type;
+            }
+            return classLoader.loadClass(typeName);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return ((Type) obj).typeName.equals(typeName);
+        }
+
+        @Override
+        public int hashCode() {
+            return typeName.hashCode();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java
new file mode 100644
index 0000000..3399118
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public abstract class ParticipantAvailable extends Message implements RouteAvailableMessage {
+    private final String channelKey;
+    private final Object id;
+    private final String displayName;
+
+    public ParticipantAvailable(Object id, String displayName, String channelKey) {
+        this.id = id;
+        this.displayName = displayName;
+        this.channelKey = channelKey;
+    }
+
+    public Object getId() {
+        return id;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getChannelKey() {
+        return channelKey;
+    }
+
+    public Object getSource() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s id: %s, displayName: %s, channel: %s]", getClass().getSimpleName(), id, displayName, channelKey);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ParticipantAvailable other = (ParticipantAvailable) o;
+        return id.equals(other.id) && displayName.equals(other.displayName) && channelKey.equals(other.channelKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode() ^ displayName.hashCode() ^ channelKey.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java
new file mode 100644
index 0000000..06965e1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public class ParticipantUnavailable extends Message implements RouteUnavailableMessage {
+    private final Object id;
+
+    public ParticipantUnavailable(Object id) {
+        this.id = id;
+    }
+
+    public Object getId() {
+        return id;
+    }
+
+    public Object getSource() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s id: %s]", getClass().getSimpleName(), id);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ParticipantUnavailable other = (ParticipantUnavailable) o;
+        return id.equals(other.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java
new file mode 100644
index 0000000..45db402
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public interface PayloadMessage {
+    Object getNestedPayload();
+
+    Message withNestedPayload(Object payload);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java
new file mode 100644
index 0000000..513dd65
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ProducerAvailable extends ParticipantAvailable {
+    public ProducerAvailable(Object id, String displayName, String channelKey) {
+        super(id, displayName, channelKey);
+    }
+
+    public RouteUnavailableMessage getUnavailableMessage() {
+        return new ProducerUnavailable(getId());
+    }
+
+    public boolean acceptIncoming(RouteAvailableMessage message) {
+        if (message instanceof ConsumerAvailable) {
+            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
+            return consumerAvailable.getChannelKey().equals(getChannelKey());
+        }
+        return false;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java
new file mode 100644
index 0000000..8ad71d2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public abstract class ProducerMessage extends Message implements RoutableMessage {
+    protected final Object producerId;
+    protected final Object consumerId;
+
+    public ProducerMessage(Object producerId, Object consumerId) {
+        this.consumerId = consumerId;
+        this.producerId = producerId;
+    }
+
+    public Object getConsumerId() {
+        return consumerId;
+    }
+
+    public Object getProducerId() {
+        return producerId;
+    }
+
+    public Object getDestination() {
+        return consumerId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ProducerMessage other = (ProducerMessage) o;
+        return consumerId.equals(other.consumerId) && producerId.equals(other.producerId);
+    }
+
+    @Override
+    public int hashCode() {
+        return consumerId.hashCode() ^ producerId.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s producerId: %s, consumerId: %s", getClass().getSimpleName(), producerId, consumerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerReady.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerReady.java
new file mode 100644
index 0000000..175cfc7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerReady.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ProducerReady extends ProducerMessage {
+    public ProducerReady(Object producerId, Object consumerId) {
+        super(producerId, consumerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java
new file mode 100644
index 0000000..0bd2dd1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ProducerStopped extends ProducerMessage {
+    public ProducerStopped(Object producerId, Object consumerId) {
+        super(producerId, consumerId);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java
new file mode 100644
index 0000000..29b9828
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class ProducerUnavailable extends ParticipantUnavailable {
+    public ProducerUnavailable(Object id) {
+        super(id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
new file mode 100755
index 0000000..71ea1cc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
@@ -0,0 +1,56 @@
+/*
+ * 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+import java.util.Arrays;
+
+public class RemoteMethodInvocation extends Message {
+    private final Object key;
+    private final Object[] arguments;
+
+    public RemoteMethodInvocation(Object key, Object[] arguments) {
+        this.key = key;
+        this.arguments = arguments;
+    }
+
+    public Object getKey() {
+        return key;
+    }
+
+    public Object[] getArguments() {
+        return arguments;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        RemoteMethodInvocation other = (RemoteMethodInvocation) obj;
+        return key.equals(other.key) && Arrays.equals(arguments, other.arguments);
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/Request.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/Request.java
new file mode 100644
index 0000000..0b9d48c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/Request.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public class Request extends AbstractPayloadMessage implements RoutableMessage, PayloadMessage {
+    private final Object consumerId;
+    private final Object payload;
+
+    public Request(Object consumerId, Object payload) {
+        this.consumerId = consumerId;
+        this.payload = payload;
+    }
+
+    public Object getDestination() {
+        return consumerId;
+    }
+
+    public Object getPayload() {
+        return payload;
+    }
+
+    public Message withPayload(Object payload) {
+        return new Request(consumerId, payload);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[Request consumer: %s, payload: %s]", consumerId, payload);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+
+        Request other = (Request) o;
+        return consumerId.equals(other.consumerId) && payload.equals(other.payload);
+    }
+
+    @Override
+    public int hashCode() {
+        return consumerId.hashCode() ^ payload.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java
new file mode 100644
index 0000000..5fd2d0e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public interface RoutableMessage {
+    /**
+     * Returns the destination for this message.
+     */
+    Object getDestination();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java
new file mode 100644
index 0000000..d02b748
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public interface RouteAvailableMessage {
+    /**
+     * Returns the route id.
+     */
+    Object getId();
+
+    /**
+     * Returns the corresponding unavailable message for this message.
+     */
+    RouteUnavailableMessage getUnavailableMessage();
+
+    /**
+     * Returns true if the given route should be broadcast to this route.
+     */
+    boolean acceptIncoming(RouteAvailableMessage message);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java
new file mode 100644
index 0000000..d162c1e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public interface RouteUnavailableMessage {
+    Object getId();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java
new file mode 100644
index 0000000..461e0aa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public class StatelessMessage extends Message {
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return getClass().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s]", getClass().getSimpleName());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java
new file mode 100644
index 0000000..bf67362
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class UnknownMessage extends DiscoveryMessage {
+    private final String description;
+
+    public UnknownMessage(String description) {
+        super(null, null);
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return description;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java
new file mode 100644
index 0000000..0f8c458
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+public class WorkerStopped extends Message {
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return 17;
+    }
+
+    @Override
+    public String toString() {
+        return "[WorkerStopped]";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java
new file mode 100644
index 0000000..0e9085d
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2011 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.protocol;
+
+public class WorkerStopping extends StatelessMessage {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/JavaForkOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/JavaForkOptions.java
old mode 100644
new mode 100755
index 42d24b2..58ce512
--- a/subprojects/core/src/main/groovy/org/gradle/process/JavaForkOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/JavaForkOptions.java
@@ -1,182 +1,214 @@
-/*
- * 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.process;
-
-import org.gradle.api.file.FileCollection;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * <p>Specifies the options to use to fork a Java process.</p>
- */
-public interface JavaForkOptions extends ProcessForkOptions {
-    /**
-     * Returns the system properties which will be used for the process.
-     *
-     * @return The system properties. Returns an empty map when there are no system properties.
-     */
-    Map<String, Object> getSystemProperties();
-
-    /**
-     * Sets the system properties to use for the process.
-     *
-     * @param properties The system properties. Must not be null.
-     */
-    void setSystemProperties(Map<String, ?> properties);
-
-    /**
-     * Adds some system properties to use for the process.
-     *
-     * @param properties The system properties. Must not be null.
-     * @return this
-     */
-    JavaForkOptions systemProperties(Map<String, ?> properties);
-
-    /**
-     * Adds a system property to use for the process.
-     *
-     * @param name The name of the property
-     * @param value The value for the property. May be null.
-     * @return this
-     */
-    JavaForkOptions systemProperty(String name, Object value);
-
-    /**
-     * Returns the maximum heap size for the process, if any.
-     *
-     * @return The maximum heap size. Returns null if the default maximum heap size should be used.
-     */
-    String getMaxHeapSize();
-
-    /**
-     * Sets the maximum heap size for the process.
-     *
-     * @param heapSize The heap size. Use null for the default maximum heap size.
-     */
-    void setMaxHeapSize(String heapSize);
-
-    /**
-     * Returns the extra arguments to use to launch the JVM for the process. Does not include system properties and the
-     * maximum heap size.
-     *
-     * @return The arguments. Returns an empty list if there are no arguments.
-     */
-    List<String> getJvmArgs();
-
-    /**
-     * Sets the extra arguments to use to launch the JVM for the process. System properties and maximum heap size are
-     * updated.
-     *
-     * @param arguments The arguments. Must not be null.
-     */
-    void setJvmArgs(Iterable<?> arguments);
-
-    /**
-     * Adds some arguments to use to launch the JVM for the process.
-     *
-     * @param arguments The arguments. Must not be null.
-     * @return this
-     */
-    JavaForkOptions jvmArgs(Iterable<?> arguments);
-
-    /**
-     * Adds some arguments to use to launch the JVM for the process.
-     *
-     * @param arguments The arguments.
-     * @return this
-     */
-    JavaForkOptions jvmArgs(Object... arguments);
-
-    /**
-     * Returns the bootstrap classpath to use for the process. The default bootstrap classpath for the JVM is used when
-     * this classpath is empty.
-     *
-     * @return The bootstrap classpath. Never returns null.
-     */
-    FileCollection getBootstrapClasspath();
-
-    /**
-     * Sets the bootstrap classpath to use for the process. Set to an empty classpath to use the default bootstrap
-     * classpath for the specified JVM.
-     *
-     * @param classpath The classpath. Must not be null. Can be empty.
-     */
-    void setBootstrapClasspath(FileCollection classpath);
-
-    /**
-     * Adds the given values to the end of the bootstrap classpath for the process.
-     *
-     * @param classpath The classpath.
-     * @return this
-     */
-    JavaForkOptions bootstrapClasspath(Object... classpath);
-
-    /**
-     * Returns true if assertions are enabled for the process.
-     *
-     * @return true if assertions are enabled, false if disabled
-     */
-    boolean getEnableAssertions();
-
-    /**
-     * Enable or disable assertions for the process.
-     *
-     * @param enabled true to enable assertions, false to disable.
-     */
-    void setEnableAssertions(boolean enabled);
-
-    /**
-     * Returns true if debugging is enabled for the process. When enabled, the process is started suspended and
-     * listening on port 5005.
-     *
-     * @return true when debugging is enabled, false to disable.
-     */
-    boolean getDebug();
-
-    /**
-     * Enable or disable debugging for the process. When enabled, the process is started suspended and listening on port
-     * 5005.
-     *
-     * @param enabled true to enable debugging, false to disable.
-     */
-    void setDebug(boolean enabled);
-
-    /**
-     * Returns the full set of arguments to use to launch the JVM for the process. This includes arguments to define
-     * system properties, the maximum heap size, and the bootstrap classpath.
-     *
-     * @return The arguments. Returns an empty list if there are no arguments.
-     */
-    List<String> getAllJvmArgs();
-
-    /**
-     * Sets the full set of arguments to use to launch the JVM for the process. Overwrites any previously set system
-     * properties, maximum heap size, assertions, and bootstrap classpath.
-     *
-     * @param arguments The arguments. Must not be null.
-     */
-    void setAllJvmArgs(Iterable<?> arguments);
-
-    /**
-     * Copies these options to the given options.
-     *
-     * @param options The target options.
-     * @return this
-     */
-    JavaForkOptions copyTo(JavaForkOptions options);
-}
+/*
+ * 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.process;
+
+import org.gradle.api.file.FileCollection;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>Specifies the options to use to fork a Java process.</p>
+ */
+public interface JavaForkOptions extends ProcessForkOptions {
+    /**
+     * Returns the system properties which will be used for the process.
+     *
+     * @return The system properties. Returns an empty map when there are no system properties.
+     */
+    Map<String, Object> getSystemProperties();
+
+    /**
+     * Sets the system properties to use for the process.
+     *
+     * @param properties The system properties. Must not be null.
+     */
+    void setSystemProperties(Map<String, ?> properties);
+
+    /**
+     * Adds some system properties to use for the process.
+     *
+     * @param properties The system properties. Must not be null.
+     * @return this
+     */
+    JavaForkOptions systemProperties(Map<String, ?> properties);
+
+    /**
+     * Adds a system property to use for the process.
+     *
+     * @param name The name of the property
+     * @param value The value for the property. May be null.
+     * @return this
+     */
+    JavaForkOptions systemProperty(String name, Object value);
+
+    /**
+     * Returns the default character encoding to use.
+     * 
+     * @return The default character encoding. Returns null if the {@link java.nio.charset.Charset#defaultCharset() default character encoding of this JVM} should be used.
+     */
+     String getDefaultCharacterEncoding();
+
+    /**
+     * Sets the default character encoding to use.
+     *
+     * Note: Many JVM implementations support the setting of this attribute via system property on startup (namely, the {@code file.encoding} property). For JVMs
+     * where this is the case, setting the {@code file.encoding} property via {@link #setSystemProperties(java.util.Map)} or similar will have no effect as
+     * this value will be overridden by the value specified by {@link #getDefaultCharacterEncoding()}.
+     *
+     * @param defaultCharacterEncoding The default character encoding. Use null to use {@link java.nio.charset.Charset#defaultCharset() this JVM's default charset}
+     */
+     void setDefaultCharacterEncoding(String defaultCharacterEncoding);
+    
+    /**
+     * Returns the minimum heap size for the process, if any.
+     *
+     * @return The minimum heap size. Returns null if the default minimum heap size should be used.
+     */
+    String getMinHeapSize();
+
+    /**
+     * Sets the minimum heap size for the process.
+     *
+     * @param heapSize The minimum heap size. Use null for the default minimum heap size.
+     */
+    void setMinHeapSize(String heapSize);
+
+    /**
+     * Returns the maximum heap size for the process, if any.
+     *
+     * @return The maximum heap size. Returns null if the default maximum heap size should be used.
+     */
+    String getMaxHeapSize();
+
+    /**
+     * Sets the maximum heap size for the process.
+     *
+     * @param heapSize The heap size. Use null for the default maximum heap size.
+     */
+    void setMaxHeapSize(String heapSize);
+
+    /**
+     * Returns the extra arguments to use to launch the JVM for the process. Does not include system properties and the
+     * minimum/maximum heap size.
+     *
+     * @return The arguments. Returns an empty list if there are no arguments.
+     */
+    List<String> getJvmArgs();
+
+    /**
+     * Sets the extra arguments to use to launch the JVM for the process. System properties
+     * and minimum/maximum heap size are updated.
+     *
+     * @param arguments The arguments. Must not be null.
+     */
+    void setJvmArgs(Iterable<?> arguments);
+
+    /**
+     * Adds some arguments to use to launch the JVM for the process.
+     *
+     * @param arguments The arguments. Must not be null.
+     * @return this
+     */
+    JavaForkOptions jvmArgs(Iterable<?> arguments);
+
+    /**
+     * Adds some arguments to use to launch the JVM for the process.
+     *
+     * @param arguments The arguments.
+     * @return this
+     */
+    JavaForkOptions jvmArgs(Object... arguments);
+
+    /**
+     * Returns the bootstrap classpath to use for the process. The default bootstrap classpath for the JVM is used when
+     * this classpath is empty.
+     *
+     * @return The bootstrap classpath. Never returns null.
+     */
+    FileCollection getBootstrapClasspath();
+
+    /**
+     * Sets the bootstrap classpath to use for the process. Set to an empty classpath to use the default bootstrap
+     * classpath for the specified JVM.
+     *
+     * @param classpath The classpath. Must not be null. Can be empty.
+     */
+    void setBootstrapClasspath(FileCollection classpath);
+
+    /**
+     * Adds the given values to the end of the bootstrap classpath for the process.
+     *
+     * @param classpath The classpath.
+     * @return this
+     */
+    JavaForkOptions bootstrapClasspath(Object... classpath);
+
+    /**
+     * Returns true if assertions are enabled for the process.
+     *
+     * @return true if assertions are enabled, false if disabled
+     */
+    boolean getEnableAssertions();
+
+    /**
+     * Enable or disable assertions for the process.
+     *
+     * @param enabled true to enable assertions, false to disable.
+     */
+    void setEnableAssertions(boolean enabled);
+
+    /**
+     * Returns true if debugging is enabled for the process. When enabled, the process is started suspended and
+     * listening on port 5005.
+     *
+     * @return true when debugging is enabled, false to disable.
+     */
+    boolean getDebug();
+
+    /**
+     * Enable or disable debugging for the process. When enabled, the process is started suspended and listening on port
+     * 5005.
+     *
+     * @param enabled true to enable debugging, false to disable.
+     */
+    void setDebug(boolean enabled);
+
+    /**
+     * Returns the full set of arguments to use to launch the JVM for the process. This includes arguments to define
+     * system properties, the minimum/maximum heap size, and the bootstrap classpath.
+     *
+     * @return The arguments. Returns an empty list if there are no arguments.
+     */
+    List<String> getAllJvmArgs();
+
+    /**
+     * Sets the full set of arguments to use to launch the JVM for the process. Overwrites any previously set system
+     * properties, minimum/maximum heap size, assertions, and bootstrap classpath.
+     *
+     * @param arguments The arguments. Must not be null.
+     */
+    void setAllJvmArgs(Iterable<?> arguments);
+
+    /**
+     * Copies these options to the given options.
+     *
+     * @param options The target options.
+     * @return this
+     */
+    JavaForkOptions copyTo(JavaForkOptions options);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/ProcessForkOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/ProcessForkOptions.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
index 717e949..dbf3347 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
@@ -15,16 +15,15 @@
  */
 package org.gradle.process.internal;
 
+import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.process.BaseExecSpec;
-import org.gradle.util.GUtil;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -40,14 +39,17 @@ public abstract class AbstractExecHandleBuilder extends DefaultProcessForkOption
 
     public AbstractExecHandleBuilder(FileResolver fileResolver) {
         super(fileResolver);
-        standardOutput = System.out;
-        errorOutput = System.err;
+        standardOutput = new CloseShieldOutputStream(System.out);
+        errorOutput = new CloseShieldOutputStream(System.err);
     }
 
     public abstract List<String> getAllArguments();
 
     public List<String> getCommandLine() {
-        return GUtil.addLists(Collections.singleton(getExecutable()), getAllArguments());
+        List<String> commandLine = new ArrayList<String>();
+        commandLine.add(getExecutable());
+        commandLine.addAll(getAllArguments());
+        return commandLine;
     }
 
     public AbstractExecHandleBuilder setStandardInput(InputStream inputStream) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
index 77998e0..2370d52 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
@@ -19,18 +19,21 @@ package org.gradle.process.internal;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.UncheckedException;
 import org.gradle.listener.AsyncListenerBroadcast;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.messaging.concurrent.DefaultExecutorFactory;
 import org.gradle.messaging.concurrent.StoppableExecutor;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.shutdown.ShutdownHookActionRegister;
-import org.gradle.util.UncheckedException;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -215,7 +218,7 @@ public class DefaultExecHandle implements ExecHandle {
             lock.unlock();
         }
 
-        LOGGER.debug("Process finished for {}.", displayName);
+        LOGGER.debug("Process finished (code: {}) for {}.", exitCode, displayName);
 
         broadcast.getSource().executionFinished(this, result);
         broadcast.stop();
@@ -223,6 +226,7 @@ public class DefaultExecHandle implements ExecHandle {
     }
 
     public ExecHandle start() {
+        ProcessParentingInitializer.intitialize();
         lock.lock();
         try {
             if (!stateIn(ExecHandleState.INIT)) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java
old mode 100644
new mode 100755
index e44fb33..d695604
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java
@@ -18,26 +18,15 @@ package org.gradle.process.internal;
 
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.JavaForkOptions;
-import org.gradle.util.Jvm;
 
-import java.io.File;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 
 public class DefaultJavaForkOptions extends DefaultProcessForkOptions implements JavaForkOptions {
-    private final Pattern sysPropPattern = Pattern.compile("-D(.+?)=(.*)");
-    private final Pattern noArgSysPropPattern = Pattern.compile("-D([^=]+)");
-    private final Pattern maxHeapPattern = Pattern.compile("-Xmx(.+)");
-    private final Pattern bootstrapPattern = Pattern.compile("-Xbootclasspath:(.+)");
-    private final List<Object> extraJvmArgs = new ArrayList<Object>();
-    private final Map<String, Object> systemProperties = new TreeMap<String, Object>();
-    private FileCollection bootstrapClasspath;
-    private String maxHeapSize;
-    private boolean assertionsEnabled;
-    private boolean debug;
+    private final JvmOptions options;
 
     public DefaultJavaForkOptions(FileResolver resolver) {
         this(resolver, Jvm.current());
@@ -45,112 +34,28 @@ public class DefaultJavaForkOptions extends DefaultProcessForkOptions implements
 
     public DefaultJavaForkOptions(FileResolver resolver, Jvm jvm) {
         super(resolver);
-        this.bootstrapClasspath = new DefaultConfigurableFileCollection(resolver, null);
+        options = new JvmOptions(resolver);
         setExecutable(jvm.getJavaExecutable());
     }
 
     public List<String> getAllJvmArgs() {
-        List<String> args = new ArrayList<String>();
-        args.addAll(getJvmArgs());
-        for (Map.Entry<String, Object> entry : getSystemProperties().entrySet()) {
-            if (entry.getValue() != null) {
-                args.add(String.format("-D%s=%s", entry.getKey(), entry.getValue().toString()));
-            } else {
-                args.add(String.format("-D%s", entry.getKey()));
-            }
-        }
-        if (maxHeapSize != null) {
-            args.add(String.format("-Xmx%s", maxHeapSize));
-        }
-        FileCollection bootstrapClasspath = getBootstrapClasspath();
-        if (!bootstrapClasspath.isEmpty()) {
-            args.add(String.format("-Xbootclasspath:%s", bootstrapClasspath.getAsPath()));
-        }
-        if (assertionsEnabled) {
-            args.add("-ea");
-        }
-        if (debug) {
-            args.add("-Xdebug");
-            args.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
-        }
-        return args;
+        return options.getAllJvmArgs();
     }
 
     public void setAllJvmArgs(Iterable<?> arguments) {
-        systemProperties.clear();
-        maxHeapSize = null;
-        extraJvmArgs.clear();
-        assertionsEnabled = false;
-        jvmArgs(arguments);
+        options.setAllJvmArgs(arguments);
     }
 
     public List<String> getJvmArgs() {
-        List<String> args = new ArrayList<String>();
-        for (Object extraJvmArg : extraJvmArgs) {
-            args.add(extraJvmArg.toString());
-        }
-        return args;
+        return options.getJvmArgs();
     }
 
     public void setJvmArgs(Iterable<?> arguments) {
-        extraJvmArgs.clear();
-        jvmArgs(arguments);
+        options.setJvmArgs(arguments);
     }
 
     public JavaForkOptions jvmArgs(Iterable<?> arguments) {
-        for (Object argument : arguments) {
-            String argStr = argument.toString();
-            Matcher matcher = sysPropPattern.matcher(argStr);
-            if (matcher.matches()) {
-                systemProperties.put(matcher.group(1), matcher.group(2));
-                continue;
-            }
-            matcher = noArgSysPropPattern.matcher(argStr);
-            if (matcher.matches()) {
-                systemProperties.put(matcher.group(1), null);
-                continue;
-            }
-            matcher = maxHeapPattern.matcher(argStr);
-            if (matcher.matches()) {
-                maxHeapSize = matcher.group(1);
-                continue;
-            }
-            matcher = bootstrapPattern.matcher(argStr);
-            if (matcher.matches()) {
-                setBootstrapClasspath(getResolver().resolveFiles((Object[]) matcher.group(1).split(Pattern.quote(File.pathSeparator))));
-                continue;
-            }
-            if (argStr.equals("-ea") || argStr.equals("-enableassertions")) {
-                assertionsEnabled = true;
-                continue;
-            }
-            if (argStr.equals("-da") || argStr.equals("-disableassertions")) {
-                assertionsEnabled = false;
-                continue;
-            }
-
-            extraJvmArgs.add(argument);
-        }
-
-        boolean xdebugFound = false;
-        boolean xrunjdwpFound = false;
-        Set<Object> matches = new HashSet<Object>();
-        for (Object extraJvmArg : extraJvmArgs) {
-            if (extraJvmArg.toString().equals("-Xdebug")) {
-                xdebugFound = true;
-                matches.add(extraJvmArg);
-            } else if (extraJvmArg.toString().equals("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005")) {
-                xrunjdwpFound = true;
-                matches.add(extraJvmArg);
-            }
-        }
-        if (xdebugFound && xrunjdwpFound) {
-            debug = true;
-            extraJvmArgs.removeAll(matches);
-        } else {
-            debug = false;
-        }
-
+        options.jvmArgs(arguments);
         return this;
     }
 
@@ -160,69 +65,79 @@ public class DefaultJavaForkOptions extends DefaultProcessForkOptions implements
     }
 
     public Map<String, Object> getSystemProperties() {
-        return systemProperties;
+        return options.getSystemProperties();
     }
 
     public void setSystemProperties(Map<String, ?> properties) {
-        systemProperties.clear();
-        systemProperties.putAll(properties);
+        options.setSystemProperties(properties);
     }
 
     public JavaForkOptions systemProperties(Map<String, ?> properties) {
-        systemProperties.putAll(properties);
+        options.systemProperties(properties);
         return this;
     }
 
     public JavaForkOptions systemProperty(String name, Object value) {
-        systemProperties.put(name, value);
+        options.systemProperty(name, value);
         return this;
     }
 
     public FileCollection getBootstrapClasspath() {
-        return bootstrapClasspath;
+        return options.getBootstrapClasspath();
     }
 
     public void setBootstrapClasspath(FileCollection classpath) {
-        this.bootstrapClasspath = classpath;
+        options.setBootstrapClasspath(classpath);
     }
 
     public JavaForkOptions bootstrapClasspath(Object... classpath) {
-        this.bootstrapClasspath = this.bootstrapClasspath.plus(getResolver().resolveFiles(classpath));
+        options.bootstrapClasspath(classpath);
         return this;
     }
 
+    public String getMinHeapSize() {
+        return options.getMinHeapSize();
+    }
+
+    public void setMinHeapSize(String heapSize) {
+        options.setMinHeapSize(heapSize);
+    }
+
     public String getMaxHeapSize() {
-        return maxHeapSize;
+        return options.getMaxHeapSize();
     }
 
     public void setMaxHeapSize(String heapSize) {
-        this.maxHeapSize = heapSize;
+        options.setMaxHeapSize(heapSize);
+    }
+
+    public String getDefaultCharacterEncoding() {
+        return options.getDefaultCharacterEncoding();
+    }
+
+    public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        options.setDefaultCharacterEncoding(defaultCharacterEncoding);
     }
 
     public boolean getEnableAssertions() {
-        return assertionsEnabled;
+        return options.getEnableAssertions();
     }
 
     public void setEnableAssertions(boolean enabled) {
-        assertionsEnabled = enabled;
+        options.setEnableAssertions(enabled);
     }
 
     public boolean getDebug() {
-        return debug;
+        return options.getDebug();
     }
 
     public void setDebug(boolean enabled) {
-        debug = enabled;
+        options.setDebug(enabled);
     }
 
     public JavaForkOptions copyTo(JavaForkOptions target) {
         super.copyTo(target);
-        target.setJvmArgs(extraJvmArgs);
-        target.setSystemProperties(systemProperties);
-        target.setMaxHeapSize(maxHeapSize);
-        target.setBootstrapClasspath(bootstrapClasspath);
-        target.setEnableAssertions(assertionsEnabled);
-        target.setDebug(debug);
+        options.copyTo(target);
         return this;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
old mode 100644
new mode 100755
index b3b3544..dc93079
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
@@ -17,8 +17,8 @@ package org.gradle.process.internal;
 
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.FileSource;
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.ProcessForkOptions;
-import org.gradle.util.Jvm;
 
 import java.io.File;
 import java.util.HashMap;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
index ba555fd..8dd43ee 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
@@ -17,10 +17,10 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.Action;
+import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.ExecResult;
-import org.gradle.util.UncheckedException;
 
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
@@ -114,11 +114,11 @@ public class DefaultWorkerProcess implements WorkerProcess {
                         throw new ExecException(String.format("Timeout waiting for %s to connect.", execHandle));
                     }
                 } catch (InterruptedException e) {
-                    throw UncheckedException.asUncheckedException(e);
+                    throw UncheckedException.throwAsUncheckedException(e);
                 }
             }
             if (processFailure != null) {
-                throw UncheckedException.asUncheckedException(processFailure);
+                throw UncheckedException.throwAsUncheckedException(processFailure);
             }
             if (connection == null) {
                 throw new ExecException(String.format("Never received a connection from %s.", execHandle));
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
index d7e4329..ac899d5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
@@ -17,9 +17,10 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.Factory;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingServer;
 import org.gradle.process.internal.child.ApplicationClassesInIsolatedClassLoaderWorkerFactory;
 import org.gradle.process.internal.child.ApplicationClassesInSystemClassLoaderWorkerFactory;
@@ -32,10 +33,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayInputStream;
-import java.net.URI;
 import java.net.URL;
 import java.util.List;
-import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 
 public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder> {
@@ -73,8 +72,8 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
                 throw new IllegalStateException("No worker action specified for this worker process.");
             }
 
-            final DefaultWorkerProcess workerProcess = new DefaultWorkerProcess(120, TimeUnit.SECONDS);
-            URI localAddress = server.accept(workerProcess.getConnectAction());
+            DefaultWorkerProcess workerProcess = new DefaultWorkerProcess(120, TimeUnit.SECONDS);
+            Address localAddress = server.accept(workerProcess.getConnectAction());
 
             // Build configuration for GradleWorkerMain
             List<URL> implementationClassPath = ClasspathUtil.getClasspath(getWorker().getClass().getClassLoader());
@@ -89,16 +88,14 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
                 workerFactory = new ApplicationClassesInIsolatedClassLoaderWorkerFactory(id, displayName, this,
                         implementationClassPath, localAddress, classPathRegistry);
             }
-            Callable<?> workerMain = workerFactory.create();
-            byte[] config = GUtil.serialize(workerMain);
 
             LOGGER.debug("Creating {}", displayName);
             LOGGER.debug("Using application classpath {}", getApplicationClasspath());
             LOGGER.debug("Using implementation classpath {}", implementationClassPath);
 
             JavaExecHandleBuilder javaCommand = getJavaCommand();
-            javaCommand.classpath(workerFactory.getSystemClasspath());
-            javaCommand.setStandardInput(new ByteArrayInputStream(config));
+            attachStdInContent(workerFactory, javaCommand);
+            workerFactory.prepareJavaCommand(javaCommand);
             javaCommand.setDisplayName(displayName);
             ExecHandle execHandle = javaCommand.build();
 
@@ -106,5 +103,10 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
 
             return workerProcess;
         }
+
+        private void attachStdInContent(WorkerFactory workerFactory, JavaExecHandleBuilder javaCommand) {
+            ByteArrayInputStream stdinContent = new ByteArrayInputStream(GUtil.serialize(workerFactory.create()));
+            javaCommand.setStandardInput(stdinContent);
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
index 741509c..95eab48 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
@@ -42,6 +42,8 @@ public interface ExecHandle {
      */
     ExecHandle start();
 
+    ExecHandleState getState();
+
     void abort();
 
     ExecResult waitForFinish();
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
index ba2cf14..ca4ecf1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
@@ -16,6 +16,7 @@
 
 package org.gradle.process.internal;
 
+import com.google.common.collect.Lists;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.process.ExecSpec;
@@ -45,7 +46,7 @@ public class ExecHandleBuilder extends AbstractExecHandleBuilder implements Exec
     }
 
     public ExecSpec commandLine(Iterable<?> args) {
-        List<Object> argsList = GUtil.addLists(args);
+        List<Object> argsList = Lists.newArrayList(args);
         executable(argsList.get(0));
         setArgs(argsList.subList(1, argsList.size()));
         return this;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
index 76efd76..e29a56b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
@@ -18,7 +18,7 @@ package org.gradle.process.internal;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.internal.CompositeStoppable;
 
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -55,4 +55,9 @@ public class ExecOutputHandleRunner implements Runnable {
             LOGGER.error(String.format("Could not %s.", displayName), t);
         }
     }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java
index ec4e155..64f21e3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java
@@ -106,6 +106,22 @@ public class JavaExecHandleBuilder extends AbstractExecHandleBuilder implements
         return this;
     }
 
+    public String getMinHeapSize() {
+        return javaOptions.getMinHeapSize();
+    }
+
+    public void setMinHeapSize(String heapSize) {
+        javaOptions.setMinHeapSize(heapSize);
+    }
+
+    public String getDefaultCharacterEncoding() {
+        return javaOptions.getDefaultCharacterEncoding();
+    }
+
+    public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        javaOptions.setDefaultCharacterEncoding(defaultCharacterEncoding);
+    }
+
     public String getMaxHeapSize() {
         return javaOptions.getMaxHeapSize();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
new file mode 100644
index 0000000..a5812aa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2011 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.process.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.process.JavaForkOptions;
+import org.gradle.util.internal.ArgumentsSplitter;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JvmOptions {
+    private static final Pattern SYS_PROP_PATTERN = Pattern.compile("-D(.+?)=(.*)");
+    private static final Pattern DEFAULT_ENCODING_PATTERN = Pattern.compile("-Dfile\\Q.\\Eencoding=(.*)");
+    private static final Pattern NO_ARG_SYS_PROP_PATTERN = Pattern.compile("-D([^=]+)");
+    private static final Pattern MIN_HEAP_PATTERN = Pattern.compile("-Xms(.+)");
+    private static final Pattern MAX_HEAP_PATTERN = Pattern.compile("-Xmx(.+)");
+    private static final Pattern BOOTSTRAP_PATTERN = Pattern.compile("-Xbootclasspath:(.+)");
+
+    private final List<Object> extraJvmArgs = new ArrayList<Object>();
+    private final Map<String, Object> systemProperties = new TreeMap<String, Object>();
+    private DefaultConfigurableFileCollection bootstrapClasspath;
+    private String minHeapSize;
+    private String maxHeapSize;
+    private boolean assertionsEnabled;
+    private boolean debug;
+    private String defaultCharacterEncoding;
+
+    public JvmOptions(FileResolver resolver) {
+        this.bootstrapClasspath = new DefaultConfigurableFileCollection(resolver, null);
+    }
+
+    /**
+     * @return all jvm args including system properties
+     */
+    public List<String> getAllJvmArgs() {
+        List<String> args = new LinkedList<String>();
+        for (Map.Entry<String, Object> entry : getSystemProperties().entrySet()) {
+            if (entry.getValue() != null) {
+                args.add(String.format("-D%s=%s", entry.getKey(), entry.getValue().toString()));
+            } else {
+                args.add(String.format("-D%s", entry.getKey()));
+            }
+        }
+
+        // We have to add these after the system properties so they can override any system properties
+        // (identical properties later in the command line override earlier ones)
+        args.addAll(getAllImmutableJvmArgs());
+
+        return args;
+    }
+
+    /**
+     * @return all immutable jvm args. It excludes most system properties.
+     * Only implicitly immutable system properties like "file.encoding" are included.
+     * The result is a subset of options returned by {@link #getAllJvmArgs()}
+     */
+    public List<String> getAllImmutableJvmArgs() {
+        List<String> args = new ArrayList<String>();
+        args.addAll(getJvmArgs());
+        args.addAll(getManagedJvmArgs());
+        return args;
+    }
+
+    /**
+     * @return the list of jvm args we manage explicitly, for example, max heaps size or file encoding.
+     *          The result is a subset of options returned by {@link #getAllImmutableJvmArgs()}
+     */
+    public List<String> getManagedJvmArgs() {
+        List<String> args = new ArrayList<String>();
+        if (minHeapSize != null) {
+            args.add(String.format("-Xms%s", minHeapSize));
+        }
+        if (maxHeapSize != null) {
+            args.add(String.format("-Xmx%s", maxHeapSize));
+        }
+        FileCollection bootstrapClasspath = getBootstrapClasspath();
+        if (!bootstrapClasspath.isEmpty()) {
+            args.add(String.format("-Xbootclasspath:%s", bootstrapClasspath.getAsPath()));
+        }
+
+        // This is implemented as a system property, but doesn't really function like one
+        // So we include it in this “no system property” set.
+        addDefaultEncodingJvmArg(args);
+
+        if (assertionsEnabled) {
+            args.add("-ea");
+        }
+        if (debug) {
+            args.add("-Xdebug");
+            args.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
+        }
+        return args;
+    }
+
+    public void setAllJvmArgs(Iterable<?> arguments) {
+        systemProperties.clear();
+        minHeapSize = null;
+        maxHeapSize = null;
+        extraJvmArgs.clear();
+        assertionsEnabled = false;
+        jvmArgs(arguments);
+    }
+
+    public List<String> getJvmArgs() {
+        List<String> args = new ArrayList<String>();
+        for (Object extraJvmArg : extraJvmArgs) {
+            args.add(extraJvmArg.toString());
+        }
+        return args;
+    }
+
+    public void setJvmArgs(Iterable<?> arguments) {
+        extraJvmArgs.clear();
+        jvmArgs(arguments);
+    }
+
+    public void jvmArgs(Iterable<?> arguments) {
+        for (Object argument : arguments) {
+            String argStr = argument.toString();
+
+            Matcher matcher = DEFAULT_ENCODING_PATTERN.matcher(argStr);
+            if (matcher.matches()) {
+                defaultCharacterEncoding = matcher.group(1);
+                continue;
+            }
+            matcher = SYS_PROP_PATTERN.matcher(argStr);
+            if (matcher.matches()) {
+                systemProperties.put(matcher.group(1), matcher.group(2));
+                continue;
+            }
+            matcher = NO_ARG_SYS_PROP_PATTERN.matcher(argStr);
+            if (matcher.matches()) {
+                systemProperties.put(matcher.group(1), "");
+                continue;
+            }
+            matcher = MIN_HEAP_PATTERN.matcher(argStr);
+            if (matcher.matches()) {
+                minHeapSize = matcher.group(1);
+                continue;
+            }
+            matcher = MAX_HEAP_PATTERN.matcher(argStr);
+            if (matcher.matches()) {
+                maxHeapSize = matcher.group(1);
+                continue;
+            }
+            matcher = BOOTSTRAP_PATTERN.matcher(argStr);
+            if (matcher.matches()) {
+                setBootstrapClasspath(matcher.group(1).split(Pattern.quote(File.pathSeparator)));
+                continue;
+            }
+            if (argStr.equals("-ea") || argStr.equals("-enableassertions")) {
+                assertionsEnabled = true;
+                continue;
+            }
+            if (argStr.equals("-da") || argStr.equals("-disableassertions")) {
+                assertionsEnabled = false;
+                continue;
+            }
+
+            extraJvmArgs.add(argument);
+        }
+
+        boolean xdebugFound = false;
+        boolean xrunjdwpFound = false;
+        Set<Object> matches = new HashSet<Object>();
+        for (Object extraJvmArg : extraJvmArgs) {
+            if (extraJvmArg.toString().equals("-Xdebug")) {
+                xdebugFound = true;
+                matches.add(extraJvmArg);
+            } else if (extraJvmArg.toString().equals("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005")) {
+                xrunjdwpFound = true;
+                matches.add(extraJvmArg);
+            }
+        }
+        if (xdebugFound && xrunjdwpFound) {
+            debug = true;
+            extraJvmArgs.removeAll(matches);
+        } else {
+            debug = false;
+        }
+    }
+
+    public void jvmArgs(Object... arguments) {
+        jvmArgs(Arrays.asList(arguments));
+    }
+
+    public Map<String, Object> getSystemProperties() {
+        return systemProperties;
+    }
+
+    public void setSystemProperties(Map<String, ?> properties) {
+        systemProperties.clear();
+        systemProperties.putAll(properties);
+    }
+
+    public void systemProperties(Map<String, ?> properties) {
+        systemProperties.putAll(properties);
+    }
+
+    public void systemProperty(String name, Object value) {
+        systemProperties.put(name, value);
+    }
+
+    public FileCollection getBootstrapClasspath() {
+        return bootstrapClasspath;
+    }
+
+    public void setBootstrapClasspath(FileCollection classpath) {
+        this.bootstrapClasspath.setFrom(classpath);
+    }
+
+    public void setBootstrapClasspath(Object... classpath) {
+        this.bootstrapClasspath.setFrom(classpath);
+    }
+
+    public void bootstrapClasspath(Object... classpath) {
+        this.bootstrapClasspath.from(classpath);
+    }
+
+    public String getMinHeapSize() {
+        return minHeapSize;
+    }
+
+    public void setMinHeapSize(String heapSize) {
+        this.minHeapSize = heapSize;
+    }
+
+    public String getMaxHeapSize() {
+        return maxHeapSize;
+    }
+
+    public void setMaxHeapSize(String heapSize) {
+        this.maxHeapSize = heapSize;
+    }
+
+    public String getDefaultCharacterEncoding() {
+        return defaultCharacterEncoding;
+    }
+
+    public String getEffectiveDefaultCharacterEncoding() {
+        if (defaultCharacterEncoding != null) {
+            return defaultCharacterEncoding;
+        } else {
+            return Charset.defaultCharset().name();
+        }
+    }
+
+    private void addDefaultEncodingJvmArg(List<String> jvmArgs) {
+        // The “file.encoding” system property is not part of the JVM standard, but both the
+        // Sun and IBM JVMs support this system property. We should at some point abstract this
+        // behind the Jvm class.
+        jvmArgs.add(String.format("-Dfile.encoding=%s", getEffectiveDefaultCharacterEncoding()));
+    }
+
+    public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        this.defaultCharacterEncoding = defaultCharacterEncoding;
+    }
+
+    public boolean getEnableAssertions() {
+        return assertionsEnabled;
+    }
+
+    public void setEnableAssertions(boolean enabled) {
+        assertionsEnabled = enabled;
+    }
+
+    public boolean getDebug() {
+        return debug;
+    }
+
+    public void setDebug(boolean enabled) {
+        debug = enabled;
+    }
+
+    public void copyTo(JavaForkOptions target) {
+        target.setJvmArgs(extraJvmArgs);
+        target.setSystemProperties(systemProperties);
+        target.setMinHeapSize(minHeapSize);
+        target.setMaxHeapSize(maxHeapSize);
+        target.setBootstrapClasspath(bootstrapClasspath);
+        target.setEnableAssertions(assertionsEnabled);
+        target.setDebug(debug);
+    }
+
+    public static List<String> fromString(String input) {
+        return ArgumentsSplitter.split(input);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
new file mode 100644
index 0000000..1f3d0db
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
@@ -0,0 +1,58 @@
+/*
+ * 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.process.internal;
+
+import org.gradle.api.internal.Operation;
+import org.gradle.api.internal.concurrent.Synchronizer;
+import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
+import org.gradle.internal.os.OperatingSystem;
+
+/**
+ * Initializes for a well behaved parent process.
+ * <p>
+ * by Szczepan Faber, created at: 3/2/12
+ */
+public class ProcessParentingInitializer {
+
+    private static boolean initialized;
+    private static Synchronizer synchronizer = new Synchronizer();
+
+    /**
+     * Initializes the current process so that it can be a well behaving parent.
+     * <p>
+     * If the process has be already initialized then the method does nothing.
+     */
+    public static void intitialize() {
+        if (initialized) {
+            return;
+        }
+        synchronizer.synchronize(new Operation() {
+            public void execute() {
+                if (initialized) {
+                    return;
+                }
+                //even if initialization fails we don't want it to re-run
+                initialized = true;
+
+                //make sure the the children will be fully detached on windows:
+                if (OperatingSystem.current().isWindows()) {
+                    new WindowsHandlesManipulator().uninheritStandardStreams();
+                }
+            }
+        });
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
index 8ea090a..f5f4ef7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
@@ -17,15 +17,15 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
+import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingClient;
 import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.messaging.remote.internal.TcpMessagingClient;
+import org.gradle.messaging.remote.internal.MessagingServices;
 import org.gradle.process.internal.WorkerProcessContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.Serializable;
-import java.net.URI;
 
 /**
  * <p>The final stage of worker start-up. Takes care of executing the worker action.</p>
@@ -37,10 +37,10 @@ public class ActionExecutionWorker implements Action<WorkerContext>, Serializabl
     private final Action<WorkerProcessContext> action;
     private final Object workerId;
     private final String displayName;
-    private final URI serverAddress;
+    private final Address serverAddress;
 
     public ActionExecutionWorker(Action<WorkerProcessContext> action, Object workerId, String displayName,
-                                 URI serverAddress) {
+                                 Address serverAddress) {
         this.action = action;
         this.workerId = workerId;
         this.displayName = displayName;
@@ -48,12 +48,14 @@ public class ActionExecutionWorker implements Action<WorkerContext>, Serializabl
     }
 
     public void execute(final WorkerContext workerContext) {
-        final MessagingClient client = createClient();
+        MessagingServices messagingServices = createClient();
+        final MessagingClient client = messagingServices.get(MessagingClient.class);
+        final ObjectConnection clientConnection = client.getConnection(serverAddress);
         try {
             LOGGER.debug("Starting {}.", displayName);
             WorkerProcessContext context = new WorkerProcessContext() {
                 public ObjectConnection getServerConnection() {
-                    return client.getConnection();
+                    return clientConnection;
                 }
 
                 public ClassLoader getApplicationClassLoader() {
@@ -79,11 +81,11 @@ public class ActionExecutionWorker implements Action<WorkerContext>, Serializabl
             LOGGER.debug("Completed {}.", displayName);
         } finally {
             LOGGER.debug("Stopping client connection.");
-            client.stop();
+            messagingServices.stop();
         }
     }
 
-    MessagingClient createClient() {
-        return new TcpMessagingClient(getClass().getClassLoader(), serverAddress);
+    MessagingServices createClient() {
+        return new MessagingServices(getClass().getClassLoader());
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
index e35a803..09a10a5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
@@ -17,10 +17,11 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.messaging.remote.Address;
+import org.gradle.process.JavaExecSpec;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.util.GFileUtils;
 
-import java.io.File;
 import java.net.URI;
 import java.net.URL;
 import java.util.Collection;
@@ -32,19 +33,19 @@ import java.util.concurrent.Callable;
  *
  * <p>Class loader hierarchy:</p>
  * <pre>
- *                              bootstrap
- *                                 |
- *                   +-------------+------------+
- *                   |                          |
- *                 system                   application
- *  (ImplementationClassLoaderWorker, logging)           |
- *                   |                          |
- *                filter                     filter
- *              (logging)               (shared packages)
- *                   |                         |
- *                   +-------------+-----------+
- *                                 |
- *                          implementation
+ *                              jvm bootstrap
+ *                                   |
+ *                   +---------------+----------------+
+ *                   |                                |
+ *               jvm system                       application
+ *  (ImplementationClassLoaderWorker, logging)        |
+ *                   |                                |
+ *                filter                           filter
+ *              (logging)                     (shared packages)
+ *                   |                                |
+ *                   +--------------+-----------------+
+ *                                  |
+ *                            implementation
  *                (ActionExecutionWorker + action implementation)
  * </pre>
  *
@@ -54,11 +55,11 @@ public class ApplicationClassesInIsolatedClassLoaderWorkerFactory implements Wor
     private final String displayName;
     private final WorkerProcessBuilder processBuilder;
     private final Collection<URL> implementationClassPath;
-    private final URI serverAddress;
+    private final Address serverAddress;
     private final ClassPathRegistry classPathRegistry;
 
     public ApplicationClassesInIsolatedClassLoaderWorkerFactory(Object workerId, String displayName, WorkerProcessBuilder processBuilder,
-                                            Collection<URL> implementationClassPath, URI serverAddress,
+                                            Collection<URL> implementationClassPath, Address serverAddress,
                                             ClassPathRegistry classPathRegistry) {
         this.workerId = workerId;
         this.displayName = displayName;
@@ -68,12 +69,12 @@ public class ApplicationClassesInIsolatedClassLoaderWorkerFactory implements Wor
         this.classPathRegistry = classPathRegistry;
     }
 
-    public Collection<File> getSystemClasspath() {
-        return classPathRegistry.getClassPathFiles("WORKER_PROCESS");
+    public void prepareJavaCommand(JavaExecSpec execSpec) {
+        execSpec.classpath(classPathRegistry.getClassPath("WORKER_PROCESS").getAsFiles());
     }
 
     public Callable<?> create() {
-        List<URL> applicationClassPath = GFileUtils.toURLs(processBuilder.getApplicationClasspath());
+        List<URI> applicationClassPath = GFileUtils.toURIs(processBuilder.getApplicationClasspath());
         ActionExecutionWorker injectedWorker = new ActionExecutionWorker(processBuilder.getWorker(), workerId,
                 displayName, serverAddress);
         ImplementationClassLoaderWorker worker = new ImplementationClassLoaderWorker(processBuilder.getLogLevel(),
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
index 2da8180..f402b76 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
@@ -16,15 +16,18 @@
 
 package org.gradle.process.internal.child;
 
+import com.google.common.io.ByteStreams;
+import com.google.common.io.InputSupplier;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.messaging.remote.Address;
+import org.gradle.process.JavaExecSpec;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.process.internal.launcher.BootstrapClassLoaderWorker;
 import org.gradle.util.GUtil;
 
-import java.io.File;
-import java.net.URI;
+import java.io.*;
 import java.net.URL;
-import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Callable;
 
@@ -33,11 +36,11 @@ import java.util.concurrent.Callable;
  *
  * <p>Class loader hierarchy:</p>
  * <pre>
- *                              bootstrap
+ *                            jvm bootstrap
  *                                 |
  *                +----------------+--------------+
  *                |                               |
- *              system                      worker bootstrap
+ *            jvm system                      worker bootstrap
  *  (GradleWorkerMain, application) (SystemApplicationClassLoaderWorker, logging)
  *                |                   (ImplementationClassLoaderWorker)
  *                |                               |
@@ -49,19 +52,18 @@ import java.util.concurrent.Callable;
  *                          implementation
  *         (ActionExecutionWorker + worker action implementation)
  * </pre>
- *
  */
 public class ApplicationClassesInSystemClassLoaderWorkerFactory implements WorkerFactory {
     private final Object workerId;
     private final String displayName;
     private final WorkerProcessBuilder processBuilder;
     private final List<URL> implementationClassPath;
-    private final URI serverAddress;
+    private final Address serverAddress;
     private final ClassPathRegistry classPathRegistry;
 
     public ApplicationClassesInSystemClassLoaderWorkerFactory(Object workerId, String displayName, WorkerProcessBuilder processBuilder,
-                                          List<URL> implementationClassPath, URI serverAddress,
-                                          ClassPathRegistry classPathRegistry) {
+                                                              List<URL> implementationClassPath, Address serverAddress,
+                                                              ClassPathRegistry classPathRegistry) {
         this.workerId = workerId;
         this.displayName = displayName;
         this.processBuilder = processBuilder;
@@ -70,8 +72,31 @@ public class ApplicationClassesInSystemClassLoaderWorkerFactory implements Worke
         this.classPathRegistry = classPathRegistry;
     }
 
-    public Collection<File> getSystemClasspath() {
-        return classPathRegistry.getClassPathFiles("WORKER_MAIN");
+    public void prepareJavaCommand(JavaExecSpec execSpec) {
+        execSpec.classpath(classPathRegistry.getClassPath("WORKER_MAIN").getAsFiles());
+        Object requestedSecurityManager = execSpec.getSystemProperties().get("java.security.manager");
+        if (requestedSecurityManager != null) {
+            execSpec.systemProperty("org.gradle.security.manager", requestedSecurityManager);
+        }
+        execSpec.systemProperty("java.security.manager", BootstrapSecurityManager.class.getName());
+        try {
+            ByteArrayOutputStream stdin = new ByteArrayOutputStream();
+            DataOutputStream outstr = new DataOutputStream(stdin);
+            outstr.writeInt(processBuilder.getApplicationClasspath().size());
+            for (File file : processBuilder.getApplicationClasspath()) {
+                outstr.writeUTF(file.getAbsolutePath());
+            }
+            outstr.close();
+            final InputStream originalStdin = execSpec.getStandardInput();
+            InputStream input = ByteStreams.join(ByteStreams.newInputStreamSupplier(stdin.toByteArray()), new InputSupplier<InputStream>() {
+                public InputStream getInput() throws IOException {
+                    return originalStdin;
+                }
+            }).getInput();
+            execSpec.setStandardInput(input);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
     }
 
     public Callable<?> create() {
@@ -81,6 +106,7 @@ public class ApplicationClassesInSystemClassLoaderWorkerFactory implements Worke
                 implementationClassPath, injectedWorker);
         byte[] serializedWorker = GUtil.serialize(worker);
 
-        return new BootstrapClassLoaderWorker(classPathRegistry.getClassPath("WORKER_PROCESS"), processBuilder.getApplicationClasspath(), serializedWorker);
+        return new BootstrapClassLoaderWorker(classPathRegistry.getClassPath("WORKER_PROCESS").getAsURLs(), serializedWorker);
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java
new file mode 100644
index 0000000..5985a4c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012 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.process.internal.child;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.Permission;
+
+/**
+ * Used to bootstrap the system classpath.
+ */
+public class BootstrapSecurityManager extends SecurityManager {
+    private boolean initialised;
+    private final URLClassLoader target;
+
+    public BootstrapSecurityManager() {
+        this(null);
+    }
+
+    BootstrapSecurityManager(URLClassLoader target) {
+        this.target = target;
+    }
+
+    @Override
+    public void checkPermission(Permission permission) {
+        synchronized (this) {
+            if (initialised) {
+                return;
+            }
+            if (System.in == null) {
+                // Still starting up
+                return;
+            }
+
+            initialised = true;
+        }
+
+        System.clearProperty("java.security.manager");
+        System.setSecurityManager(null);
+
+        URLClassLoader systemClassLoader = target != null ? target : (URLClassLoader) getClass().getClassLoader();
+        try {
+            Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+            addUrlMethod.setAccessible(true);
+
+            DataInputStream inputStream = new DataInputStream(System.in);
+            int count = inputStream.readInt();
+            StringBuilder classpathStr = new StringBuilder();
+            for (int i = 0; i < count; i++) {
+                String entry = inputStream.readUTF();
+                File file = new File(entry);
+                addUrlMethod.invoke(systemClassLoader, file.toURI().toURL());
+                if (i > 0) {
+                    classpathStr.append(File.pathSeparator);
+                }
+                classpathStr.append(file.toString());
+            }
+            System.setProperty("java.class.path", classpathStr.toString());
+        } catch (Exception e) {
+            throw new RuntimeException("Could not initialise system classpath.", e);
+        }
+
+        String secManagerType = System.getProperty("org.gradle.security.manager");
+        if (secManagerType != null) {
+            System.setProperty("java.security.manager", secManagerType);
+            SecurityManager securityManager;
+            try {
+                Class<?> aClass = systemClassLoader.loadClass(secManagerType);
+                securityManager = (SecurityManager) aClass.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Could not create an instance of '" + secManagerType + "' specified for system SecurityManager.", e);
+            }
+            System.setSecurityManager(securityManager);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
index 1774873..3d20816 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
@@ -18,6 +18,7 @@ package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.UncheckedException;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.util.*;
@@ -58,7 +59,7 @@ public class ImplementationClassLoaderWorker implements Action<WorkerContext>, S
 
         ClassLoader applicationClassLoader = workerContext.getApplicationClassLoader();
         FilteringClassLoader filteredApplication = new FilteringClassLoader(applicationClassLoader);
-        ObservableUrlClassLoader implementationClassLoader = createImplementationClassLoader(filteredWorkerClassLoader,
+        MutableURLClassLoader implementationClassLoader = createImplementationClassLoader(filteredWorkerClassLoader,
                 filteredApplication);
 
         // Configure classpaths
@@ -74,7 +75,7 @@ public class ImplementationClassLoaderWorker implements Action<WorkerContext>, S
                     implementationClassLoader);
             action = (Action<WorkerContext>) instr.readObject();
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
         action.execute(workerContext);
     }
@@ -83,7 +84,7 @@ public class ImplementationClassLoaderWorker implements Action<WorkerContext>, S
         return LoggingServiceRegistry.newChildProcessLogging().newInstance(LoggingManagerInternal.class);
     }
 
-    ObservableUrlClassLoader createImplementationClassLoader(ClassLoader system, ClassLoader application) {
-        return new ObservableUrlClassLoader(new MultiParentClassLoader(application, system));
+    MutableURLClassLoader createImplementationClassLoader(ClassLoader system, ClassLoader application) {
+        return new MutableURLClassLoader(new MultiParentClassLoader(application, system));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
index 3ad70d3..a069bb7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
@@ -17,10 +17,10 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
-import org.gradle.util.ObservableUrlClassLoader;
+import org.gradle.util.DefaultClassLoaderFactory;
 
 import java.io.Serializable;
-import java.net.URL;
+import java.net.URI;
 import java.util.Collection;
 import java.util.concurrent.Callable;
 
@@ -29,15 +29,15 @@ import java.util.concurrent.Callable;
  */
 public class IsolatedApplicationClassLoaderWorker implements Callable<Void>, Serializable {
     private final Action<WorkerContext> worker;
-    private final Collection<URL> applicationClassPath;
+    private final Collection<URI> applicationClassPath;
 
-    public IsolatedApplicationClassLoaderWorker(Collection<URL> applicationClassPath, Action<WorkerContext> worker) {
+    public IsolatedApplicationClassLoaderWorker(Collection<URI> applicationClassPath, Action<WorkerContext> worker) {
         this.applicationClassPath = applicationClassPath;
         this.worker = worker;
     }
 
     public Void call() throws Exception {
-        final ObservableUrlClassLoader applicationClassLoader = createApplicationClassLoader();
+        final ClassLoader applicationClassLoader = createApplicationClassLoader();
 
         WorkerContext context = new WorkerContext() {
             public ClassLoader getApplicationClassLoader() {
@@ -50,7 +50,7 @@ public class IsolatedApplicationClassLoaderWorker implements Callable<Void>, Ser
         return null;
     }
 
-    private ObservableUrlClassLoader createApplicationClassLoader() {
-        return new ObservableUrlClassLoader(ClassLoader.getSystemClassLoader().getParent(), applicationClassPath);
+    private ClassLoader createApplicationClassLoader() {
+        return new DefaultClassLoaderFactory().createIsolatedClassLoader(applicationClassPath);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
index 8fef9ab..8b7147d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
@@ -18,44 +18,32 @@ package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
 import org.gradle.util.ClassLoaderObjectInputStream;
-import org.gradle.util.ClasspathUtil;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
 
 import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.net.URLClassLoader;
-import java.util.Collection;
 import java.util.concurrent.Callable;
 
 /**
  * <p>Stage 3 of the start-up for a worker process with the application classes loaded in the system ClassLoader. Takes
  * care of adding the application classes to the system ClassLoader and then invoking the next stage of start-up.</p>
+ * TODO:DAZ No longer adds application classes to system ClassLoader: is this required at all?
  *
  * <p> Instantiated in the worker bootstrap ClassLoader and invoked from {@link org.gradle.process.internal.launcher.BootstrapClassLoaderWorker}.
  * See {@link ApplicationClassesInSystemClassLoaderWorkerFactory} for details.</p>
  */
 public class SystemApplicationClassLoaderWorker implements Callable<Void> {
     private final byte[] serializedWorker;
-    private final Collection<File> applicationClassPath;
 
-    public SystemApplicationClassLoaderWorker(Collection<File> applicationClassPath, byte[] serializedWorker) {
-        this.applicationClassPath = applicationClassPath;
+    public SystemApplicationClassLoaderWorker(byte[] serializedWorker) {
         this.serializedWorker = serializedWorker;
     }
 
     public Void call() throws Exception {
-        final ClassLoader applicationClassLoader = ClassLoader.getSystemClassLoader();
-        ClasspathUtil.addUrl((URLClassLoader) applicationClassLoader, GFileUtils.toURLs(applicationClassPath));
-        System.setProperty("java.class.path", GUtil.join(applicationClassPath, File.pathSeparator));
-
-        ClassLoaderObjectInputStream instr = new ClassLoaderObjectInputStream(new ByteArrayInputStream(
-                serializedWorker), getClass().getClassLoader());
+        ClassLoaderObjectInputStream instr = new ClassLoaderObjectInputStream(new ByteArrayInputStream(serializedWorker), getClass().getClassLoader());
         final Action<WorkerContext> action = (Action<WorkerContext>) instr.readObject();
 
         action.execute(new WorkerContext() {
             public ClassLoader getApplicationClassLoader() {
-                return applicationClassLoader;
+                return ClassLoader.getSystemClassLoader();
             }
         });
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.java
index 1bc9fda..c2cad59 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.java
@@ -16,12 +16,18 @@
 
 package org.gradle.process.internal.child;
 
-import java.io.File;
-import java.util.Collection;
+import org.gradle.process.JavaExecSpec;
+
 import java.util.concurrent.Callable;
 
 public interface WorkerFactory {
+    /**
+     * Creates the worker action to be serialized across to the child process.
+     */
     Callable<?> create();
 
-    Collection<File> getSystemClasspath();
+    /**
+     * Configures the Java command that will be used to launch the child process.
+     */
+    void prepareJavaCommand(JavaExecSpec execSpec);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
index 720e287..4d6ce45 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
@@ -16,50 +16,71 @@
 
 package org.gradle.process.internal.child;
 
-import org.gradle.api.internal.AbstractClassPathProvider;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClassPathProvider;
+import org.gradle.api.internal.classpath.ModuleRegistry;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentCache;
 import org.gradle.process.internal.launcher.BootstrapClassLoaderWorker;
 import org.gradle.process.internal.launcher.GradleWorkerMain;
+import org.gradle.util.ClassPath;
+import org.gradle.util.DefaultClassPath;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
 
-public class WorkerProcessClassPathProvider extends AbstractClassPathProvider {
+public class WorkerProcessClassPathProvider implements ClassPathProvider {
     private final CacheRepository cacheRepository;
+    private final ModuleRegistry moduleRegistry;
     private final Object lock = new Object();
-    private Set<File> workerClassPath;
+    private ClassPath workerClassPath;
 
-    public WorkerProcessClassPathProvider(CacheRepository cacheRepository) {
+    public WorkerProcessClassPathProvider(CacheRepository cacheRepository, ModuleRegistry moduleRegistry) {
         this.cacheRepository = cacheRepository;
-        add("WORKER_PROCESS", toPatterns("gradle-core", "slf4j-api", "logback-classic", "logback-core", "jul-to-slf4j", "jansi", "jna", "jna-posix"));
+        this.moduleRegistry = moduleRegistry;
     }
 
-    public Set<File> findClassPath(String name) {
-        if (!name.equals("WORKER_MAIN")) {
-            return super.findClassPath(name);
+    public ClassPath findClassPath(String name) {
+        if (name.equals("WORKER_PROCESS")) {
+            // TODO - split out a logging project and use its classpath, instead of hardcoding logging dependencies here
+            ClassPath classpath = new DefaultClassPath();
+            classpath = classpath.plus(moduleRegistry.getModule("gradle-base-services").getImplementationClasspath());
+            classpath = classpath.plus(moduleRegistry.getModule("gradle-core").getImplementationClasspath());
+            classpath = classpath.plus(moduleRegistry.getModule("gradle-cli").getImplementationClasspath());
+            classpath = classpath.plus(moduleRegistry.getModule("gradle-native").getImplementationClasspath());
+            classpath = classpath.plus(moduleRegistry.getExternalModule("slf4j-api").getClasspath());
+            classpath = classpath.plus(moduleRegistry.getExternalModule("logback-classic").getClasspath());
+            classpath = classpath.plus(moduleRegistry.getExternalModule("logback-core").getClasspath());
+            classpath = classpath.plus(moduleRegistry.getExternalModule("jul-to-slf4j").getClasspath());
+            return classpath;
+        }
+        if (name.equals("WORKER_MAIN")) {
+            synchronized (lock) {
+                if (workerClassPath == null) {
+                    PersistentCache cache = cacheRepository.cache("workerMain").withInitializer(new CacheInitializer()).open();
+                    workerClassPath = new DefaultClassPath(classesDir(cache));
+                }
+                return workerClassPath;
+            }
         }
 
-        synchronized (lock) {
-            if (workerClassPath == null) {
-                PersistentCache cache = cacheRepository.cache("workerMain").open();
-                File classesDir = new File(cache.getBaseDir(), "classes");
-                if (!cache.isValid()) {
-                    for (Class<?> aClass : Arrays.asList(GradleWorkerMain.class, BootstrapClassLoaderWorker.class)) {
-                        String fileName = aClass.getName().replace('.', '/') + ".class";
-                        GFileUtils.copyURLToFile(WorkerProcessClassPathProvider.class.getClassLoader().getResource(fileName),
-                                new File(classesDir, fileName));
-                    }
+        return null;
+    }
 
-                    cache.markValid();
-                }
+    private static File classesDir(PersistentCache cache) {
+        return new File(cache.getBaseDir(), "classes");
+    }
 
-                workerClassPath = Collections.singleton(classesDir);
+    private static class CacheInitializer implements Action<PersistentCache> {
+        public void execute(PersistentCache cache) {
+            File classesDir = classesDir(cache);
+            for (Class<?> aClass : Arrays.asList(GradleWorkerMain.class, BootstrapClassLoaderWorker.class, BootstrapSecurityManager.class)) {
+                String fileName = aClass.getName().replace('.', '/') + ".class";
+                GFileUtils.copyURLToFile(WorkerProcessClassPathProvider.class.getClassLoader().getResource(fileName),
+                        new File(classesDir, fileName));
             }
-            return workerClassPath;
         }
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java
index 254bd95..9811f35 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java
@@ -16,7 +16,6 @@
 
 package org.gradle.process.internal.launcher;
 
-import java.io.File;
 import java.io.Serializable;
 import java.net.URL;
 import java.net.URLClassLoader;
@@ -32,23 +31,18 @@ import java.util.concurrent.Callable;
  */
 public class BootstrapClassLoaderWorker implements Callable<Void>, Serializable {
     private final Collection<URL> bootstrapClasspath;
-    private final Collection<File> applicationClasspath;
     private final byte[] serializedWorker;
 
-    public BootstrapClassLoaderWorker(Collection<URL> bootstrapClasspath, Collection<File> applicationClasspath,
-                                      byte[] serializedWorker) {
+    public BootstrapClassLoaderWorker(Collection<URL> bootstrapClasspath, byte[] serializedWorker) {
         this.bootstrapClasspath = bootstrapClasspath;
-        this.applicationClasspath = applicationClasspath;
         this.serializedWorker = serializedWorker;
     }
 
     public Void call() throws Exception {
         URL[] bootstrapUrls = bootstrapClasspath.toArray(new URL[bootstrapClasspath.size()]);
         URLClassLoader classLoader = new URLClassLoader(bootstrapUrls, ClassLoader.getSystemClassLoader().getParent());
-        Class<? extends Callable> workerClass = classLoader.loadClass(
-                "org.gradle.process.internal.child.SystemApplicationClassLoaderWorker").asSubclass(Callable.class);
-        Callable<Void> main = workerClass.getConstructor(Collection.class, byte[].class).newInstance(
-                applicationClasspath, serializedWorker);
+        Class<? extends Callable> workerClass = classLoader.loadClass("org.gradle.process.internal.child.SystemApplicationClassLoaderWorker").asSubclass(Callable.class);
+        Callable<Void> main = workerClass.getConstructor(byte[].class).newInstance(serializedWorker);
         return main.call();
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java
index dc5601e..d574371 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/BuildProfile.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2011 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.
@@ -16,9 +16,13 @@
 package org.gradle.profile;
 
 import org.gradle.api.Project;
+import org.gradle.api.artifacts.ResolvableDependencies;
 import org.gradle.api.invocation.Gradle;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Root container for profile information about a build.  This includes summary
@@ -36,14 +40,16 @@ import java.util.*;
  * </ul>
  */
 public class BuildProfile {
-    private Gradle gradle;
-    Map<Project, ProjectProfile> projects = new HashMap<Project, ProjectProfile>();
-    long profilingStarted;
-    long buildStarted;
-    long settingsEvaluated;
-    long projectsLoaded;
-    long projectsEvaluated;
-    long buildFinished;
+    private final Gradle gradle;
+    private final Map<Project, ProjectProfile> projects = new LinkedHashMap<Project, ProjectProfile>();
+    private final Map<String, DependencyResolveProfile> dependencySets = new LinkedHashMap<String, DependencyResolveProfile>();
+    private long profilingStarted;
+    private long buildStarted;
+    private long settingsEvaluated;
+    private long projectsLoaded;
+    private long projectsEvaluated;
+    private long buildFinished;
+    private boolean successful;
 
     public BuildProfile(Gradle gradle) {
         this.gradle = gradle;
@@ -53,6 +59,10 @@ public class BuildProfile {
         return gradle;
     }
 
+    public long getBuildStarted() {
+        return buildStarted;
+    }
+
     /**
      * Get a description of the tasks passed to gradle as targets from the command line
      * @return
@@ -93,6 +103,27 @@ public class BuildProfile {
         return new ArrayList<ProjectProfile>(projects.values());
     }
 
+    public CompositeOperation<Operation> getProjectConfiguration() {
+        List<Operation> operations = new ArrayList<Operation>();
+        for (ProjectProfile projectProfile : projects.values()) {
+            operations.add(projectProfile.getEvaluation());
+        }
+        return new CompositeOperation<Operation>(operations);
+    }
+
+    public DependencyResolveProfile getDependencySetProfile(ResolvableDependencies dependencySet) {
+        DependencyResolveProfile profile = dependencySets.get(dependencySet.getPath());
+        if (profile == null) {
+            profile = new DependencyResolveProfile(dependencySet);
+            dependencySets.put(dependencySet.getPath(), profile);
+        }
+        return profile;
+    }
+
+    public CompositeOperation<DependencyResolveProfile> getDependencySets() {
+        return new CompositeOperation<DependencyResolveProfile>(dependencySets.values());
+    }
+
     /**
      * Should be set with a time as soon as possible after startup.
      * @param profilingStarted
@@ -159,7 +190,7 @@ public class BuildProfile {
      * @return
      */
     public long getElapsedTotal() { 
-    return buildFinished - profilingStarted;
+        return buildFinished - profilingStarted;
     }
 
     /**
@@ -202,8 +233,16 @@ public class BuildProfile {
     public long getElapsedTotalExecutionTime() {
         long result = 0;
         for (ProjectProfile projectProfile : projects.values()) {
-            result += projectProfile.getElapsedTaskExecution();
+            result += projectProfile.getTasks().getElapsedTime();
         }
         return result;
     }
+
+    public boolean isSuccessful() {
+        return successful;
+    }
+
+    public void setSuccessful(boolean successful) {
+        this.successful = successful;
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java b/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java
new file mode 100644
index 0000000..6965bd4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/CompositeOperation.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.profile;
+
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An operation made up of other operations of type T.
+ */
+public class CompositeOperation<T extends Operation> extends Operation implements Iterable<T> {
+    private List<T> children = new ArrayList<T>();
+
+    public CompositeOperation(Iterable<? extends T> children) {
+        this.children = Lists.newArrayList(children);
+    }
+
+    public List<T> getOperations() {
+        return children;
+    }
+
+    public Iterator<T> iterator() {
+        return children.iterator();
+    }
+
+    @Override
+    long getElapsedTime() {
+        long sum = 0;
+        for (T child : children) {
+            sum += child.getElapsedTime();
+        }
+        return sum;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java b/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java
new file mode 100644
index 0000000..c0b342c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ContinuousOperation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.profile;
+
+/**
+ * A continuous operation with a start and finish time.
+ */
+public class ContinuousOperation extends Operation {
+    private long start;
+    private long finish;
+
+    public void setStart(long start) {
+        this.start = start;
+    }
+
+    public void setFinish(long finish) {
+        this.finish = finish;
+    }
+
+    public long getStartTime() {
+        return start;
+    }
+
+    public long getElapsedTime() {
+        return finish - start;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/DependencyResolveProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/DependencyResolveProfile.java
new file mode 100644
index 0000000..8c703a5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/DependencyResolveProfile.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.profile;
+
+import org.gradle.api.artifacts.ResolvableDependencies;
+
+public class DependencyResolveProfile extends ContinuousOperation {
+    private final ResolvableDependencies dependencySet;
+
+    public DependencyResolveProfile(ResolvableDependencies dependencySet) {
+        this.dependencySet = dependencySet;
+    }
+
+    public String getPath() {
+        return dependencySet.getPath();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ElapsedTimeFormatter.java b/subprojects/core/src/main/groovy/org/gradle/profile/ElapsedTimeFormatter.java
deleted file mode 100644
index 1a82898..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ElapsedTimeFormatter.java
+++ /dev/null
@@ -1,54 +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.profile;
-
-import java.text.DecimalFormat;
-
-public class ElapsedTimeFormatter {
-    private static final DecimalFormat SECONDS_FORMAT = new DecimalFormat("0.000");
-    private static final DecimalFormat SECONDS_FORMAT2 = new DecimalFormat("00.000");
-    private static final DecimalFormat HMFORMAT = new DecimalFormat("0");
-    private static final DecimalFormat HMFORMAT2 = new DecimalFormat("00");
-
-    public String format(long elapsed) {
-        long hours = elapsed / (1000L * 60 * 60);
-        long minutes = (elapsed / (1000L * 60)) % 60;
-        float seconds = (float) ((elapsed % 60000L) / 1000.0);
-        StringBuffer result = new StringBuffer();
-
-        if (hours > 0) {
-            result.append(HMFORMAT.format(hours));
-            result.append(":");
-        }
-
-        if (minutes > 0) {
-            if (hours > 0) {
-                result.append(HMFORMAT2.format(minutes));
-            } else {
-                result.append(HMFORMAT.format(minutes));
-            }
-            result.append(":");
-        }
-
-        if (hours > 0 || minutes > 0) {
-            result.append(SECONDS_FORMAT2.format(seconds));
-        } else {
-            result.append(SECONDS_FORMAT.format(seconds));
-        }
-
-        return result.toString();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.groovy b/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.groovy
index c9c59c2..f2f7d45 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.groovy
@@ -16,40 +16,27 @@
 package org.gradle.profile
 
 import java.text.SimpleDateFormat
-import org.gradle.api.UncheckedIOException
-import groovy.text.SimpleTemplateEngine
 
+import groovy.text.SimpleTemplateEngine
+import org.gradle.reporting.TextReportRenderer
+import org.gradle.reporting.DurationFormatter
 
-class HTMLProfileReport {
-    private BuildProfile profile;
+class HTMLProfileReport extends TextReportRenderer<BuildProfile> {
     private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd - HH:mm:ss");
-    private static final ElapsedTimeFormatter TIME_FORMAT = new ElapsedTimeFormatter();
+    private static final DurationFormatter DURATION_FORMAT = new DurationFormatter()
 
-    public HTMLProfileReport(BuildProfile profile) {
-        this.profile = profile
-    }
-
-    public void writeTo(PrintWriter out) {
+    protected void writeTo(BuildProfile profile, Writer out) {
+        def templateStream = getClass().getResourceAsStream('/org/gradle/profile/ProfileTemplate.html')
+        def template
         try {
-            InputStream templateStream = getClass().getResourceAsStream('/org/gradle/profile/ProfileTemplate.html')
             SimpleTemplateEngine engine = new SimpleTemplateEngine()
-            def binding = ['build': profile, 'time': TIME_FORMAT, 'date':DATE_FORMAT]
-            def result = engine.createTemplate(new InputStreamReader(templateStream)).make(binding)
-            result.writeTo(out)
-        }
-        finally {
-            out.close();
+            template = engine.createTemplate(new InputStreamReader(templateStream))
+        } finally {
+            templateStream.close()
         }
-    }
 
-    public void writeTo(File file) {
-        try {
-            FileOutputStream out = new FileOutputStream(file);
-            writeTo(new PrintWriter(new BufferedOutputStream(out)));
-        } catch (FileNotFoundException e) {
-            throw new UncheckedIOException(e);
-        }
+        def binding = ['build': profile, 'time': DURATION_FORMAT, 'date': DATE_FORMAT]
+        def result = template.make(binding)
+        result.writeTo(out)
     }
-
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java b/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java
new file mode 100644
index 0000000..591b71e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/Operation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.profile;
+
+/**
+ * A general operation.
+ */
+public abstract class Operation {
+    /**
+     * Returns the total elapsed execution time of this operation in millis.
+     */
+    abstract long getElapsedTime();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
new file mode 100644
index 0000000..5e9ed4f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012 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.profile;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.api.Project;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.ProjectState;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.DependencyResolutionListener;
+import org.gradle.api.artifacts.ResolvableDependencies;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.initialization.BuildRequestMetaData;
+import org.gradle.util.TimeProvider;
+
+/**
+ * Adapts various events to build a {@link BuildProfile} model, and then notifies a {@link ReportGeneratingProfileListener} when the model is ready.
+ */
+public class ProfileEventAdapter implements BuildListener, ProjectEvaluationListener, TaskExecutionListener, DependencyResolutionListener {
+    private final BuildRequestMetaData buildMetaData;
+    private final TimeProvider timeProvider;
+    private final ProfileListener listener;
+    private BuildProfile buildProfile;
+
+    public ProfileEventAdapter(BuildRequestMetaData buildMetaData, TimeProvider timeProvider, ProfileListener listener) {
+        this.buildMetaData = buildMetaData;
+        this.timeProvider = timeProvider;
+        this.listener = listener;
+    }
+
+    // BuildListener
+    public void buildStarted(Gradle gradle) {
+        buildProfile = new BuildProfile(gradle);
+        buildProfile.setBuildStarted(timeProvider.getCurrentTime());
+        buildProfile.setProfilingStarted(buildMetaData.getBuildTimeClock().getStartTime());
+    }
+
+    public void settingsEvaluated(Settings settings) {
+        buildProfile.setSettingsEvaluated(timeProvider.getCurrentTime());
+    }
+
+    public void projectsLoaded(Gradle gradle) {
+        buildProfile.setProjectsLoaded(timeProvider.getCurrentTime());
+    }
+
+    public void projectsEvaluated(Gradle gradle) {
+        buildProfile.setProjectsEvaluated(timeProvider.getCurrentTime());
+    }
+
+    public void buildFinished(BuildResult result) {
+        buildProfile.setBuildFinished(timeProvider.getCurrentTime());
+        buildProfile.setSuccessful(result.getFailure() == null);
+        try {
+            listener.buildFinished(buildProfile);
+        } finally {
+            buildProfile = null;
+        }
+    }
+
+    // ProjectEvaluationListener
+    public void beforeEvaluate(Project project) {
+        buildProfile.getProjectProfile(project).getEvaluation().setStart(System.currentTimeMillis());
+    }
+
+    public void afterEvaluate(Project project, ProjectState state) {
+        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
+        projectProfile.getEvaluation().setFinish(timeProvider.getCurrentTime());
+        projectProfile.setState(state);
+    }
+
+    // TaskExecutionListener
+    public void beforeExecute(Task task) {
+        Project project = task.getProject();
+        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
+        projectProfile.getTaskProfile(task).setStart(timeProvider.getCurrentTime());
+    }
+
+    public void afterExecute(Task task, TaskState state) {
+        Project project = task.getProject();
+        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
+        TaskExecution taskExecution = projectProfile.getTaskProfile(task);
+        taskExecution.setFinish(timeProvider.getCurrentTime());
+        taskExecution.setState(state);
+    }
+
+    // DependencyResolutionListener
+    public void beforeResolve(ResolvableDependencies dependencies) {
+        DependencyResolveProfile profile = buildProfile.getDependencySetProfile(dependencies);
+        profile.setStart(timeProvider.getCurrentTime());
+    }
+
+    public void afterResolve(ResolvableDependencies dependencies) {
+        DependencyResolveProfile profile = buildProfile.getDependencySetProfile(dependencies);
+        profile.setFinish(timeProvider.getCurrentTime());
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileListener.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileListener.java
index 42fe01b..29ccd2f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileListener.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileListener.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2012 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.
@@ -15,85 +15,6 @@
  */
 package org.gradle.profile;
 
-import org.gradle.BuildListener;
-import org.gradle.BuildResult;
-import org.gradle.api.*;
-import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.initialization.Settings;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.api.tasks.TaskState;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class ProfileListener implements BuildListener, ProjectEvaluationListener, TaskExecutionListener {
-    private BuildProfile buildProfile;
-    private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
-    private long profileStarted;
-
-    public ProfileListener(long profileStarted) {
-        this.profileStarted = profileStarted;
-    }
-
-    // BuildListener
-    public void buildStarted(Gradle gradle) {
-        buildProfile = new BuildProfile(gradle);
-        buildProfile.setBuildStarted(System.currentTimeMillis());
-        buildProfile.setProfilingStarted(profileStarted);
-    }
-
-    public void settingsEvaluated(Settings settings) {
-        buildProfile.setSettingsEvaluated(System.currentTimeMillis());
-    }
-
-    public void projectsLoaded(Gradle gradle) {
-        buildProfile.setProjectsLoaded(System.currentTimeMillis());
-    }
-
-    public void projectsEvaluated(Gradle gradle) {
-        buildProfile.setProjectsEvaluated(System.currentTimeMillis());
-    }
-
-    public void buildFinished(BuildResult result) {
-        buildProfile.setBuildFinished(System.currentTimeMillis());
-
-        HTMLProfileReport report = new HTMLProfileReport(buildProfile);
-        File file = new File(result.getGradle().getRootProject().getBuildDir(), "reports/profile/profile-" + FILE_DATE_FORMAT.format(new Date(profileStarted)) + ".html");
-        file.getParentFile().mkdirs();
-        try {
-            file.createNewFile();
-            report.writeTo(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    // ProjectEvaluationListener
-    public void beforeEvaluate(Project project) {
-        buildProfile.getProjectProfile(project).setBeforeEvaluate(System.currentTimeMillis());
-    }
-
-    public void afterEvaluate(Project project, ProjectState state) {
-        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
-        projectProfile.setAfterEvaluate(System.currentTimeMillis());
-        projectProfile.setState(state);
-    }
-
-    // TaskExecutionListener
-    public void beforeExecute(Task task) {
-        Project project = task.getProject();
-        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
-        projectProfile.getTaskProfile(task).setStart(System.currentTimeMillis());
-    }
-
-    public void afterExecute(Task task, TaskState state) {
-        Project project = task.getProject();
-        ProjectProfile projectProfile = buildProfile.getProjectProfile(project);
-        TaskProfile taskProfile = projectProfile.getTaskProfile(task);
-        taskProfile.setFinish(System.currentTimeMillis());
-        taskProfile.setState(state);
-    }
+public interface ProfileListener {
+    void buildFinished(BuildProfile result);
 }
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
new file mode 100644
index 0000000..a958cb7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.profile;
+
+import org.gradle.reporting.DomReportRenderer;
+import org.gradle.reporting.HtmlReportRenderer;
+import org.gradle.reporting.TabbedPageRenderer;
+import org.gradle.reporting.TextDomReportRenderer;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+public class ProfileReportRenderer {
+    public void writeTo(BuildProfile buildProfile, File file) {
+        HtmlReportRenderer renderer = new HtmlReportRenderer();
+        renderer.requireResource(getClass().getResource("/org/gradle/reporting/base-style.css"));
+        renderer.requireResource(getClass().getResource("/org/gradle/reporting/report.js"));
+        renderer.requireResource(getClass().getResource("/org/gradle/reporting/css3-pie-1.0beta3.htc"));
+        renderer.requireResource(getClass().getResource("style.css"));
+        renderer.renderer(new ProfilePageRenderer()).writeTo(buildProfile, file);
+    }
+
+    private static class ProfilePageRenderer extends TabbedPageRenderer<BuildProfile> {
+        static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd - HH:mm:ss");
+
+        @Override
+        protected String getTitle() {
+            return "Profile report";
+        }
+
+        @Override
+        protected DomReportRenderer<BuildProfile> getHeaderRenderer() {
+            return new DomReportRenderer<BuildProfile>() {
+                @Override
+                public void render(BuildProfile model, Element parent) {
+                    Element header = appendWithId(parent, "div", "header");
+                    appendWithText(header, "p", String.format("Profiled with tasks: %s", model.getTaskDescription()));
+                    appendWithText(header, "p", String.format("Run on: %s", DATE_FORMAT.format(model.getBuildStarted())));
+                }
+            };
+        }
+
+        @Override
+        protected DomReportRenderer<BuildProfile> getContentRenderer() {
+            return new TextDomReportRenderer<BuildProfile>(new HTMLProfileReport());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
index 1144144..7f95477 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
@@ -19,16 +19,17 @@ import org.gradle.api.Project;
 import org.gradle.api.ProjectState;
 import org.gradle.api.Task;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 
 public class ProjectProfile {
-    private Project project;
-    private long beforeEvaluate;
-    private long afterEvaluate;
+    private final Project project;
     private ProjectState state;
-    private HashMap<Task, TaskProfile> tasks = new HashMap<Task, TaskProfile>();
+    private HashMap<Task, TaskExecution> tasks = new HashMap<Task, TaskExecution>();
+    private final ContinuousOperation evaluation = new ContinuousOperation() {
+        public String getPath() {
+            return project.getPath();
+        }
+    };
 
     public ProjectProfile(Project project) {
         this.project = project;
@@ -36,53 +37,39 @@ public class ProjectProfile {
 
     /**
      * Gets the task profiling container for the specified task.
-     * @param task
-     * @return
      */
-    public TaskProfile getTaskProfile(Task task) {
-        TaskProfile result = tasks.get(task);
+    public TaskExecution getTaskProfile(Task task) {
+        TaskExecution result = tasks.get(task);
         if (result == null) {
-            result = new TaskProfile(task);
+            result = new TaskExecution(task);
             tasks.put(task, result);
         }
         return result;
     }
 
     /**
-     * Gets the list of task profiling containers.
-     * @return
+     * Returns the task executions for this project.
      */
-    public List<TaskProfile> getTaskProfiles() {
-        return new ArrayList<TaskProfile>(tasks.values());
+    public CompositeOperation<TaskExecution> getTasks() {
+        return new CompositeOperation<TaskExecution>(tasks.values());
     }
 
     /**
      * Get the String project path.
-     * @return
      */
     public String getPath() {
         return project.getPath();
     }
 
     /**
-     * Should be set with a timestamp right before project evaluation begins.
-     * @param beforeEvaluate
-     */
-    public void setBeforeEvaluate(long beforeEvaluate) {
-        this.beforeEvaluate = beforeEvaluate;
-    }
-
-    /**
-     * Should be set with a timestamp right after proejct evaluation finishes.
-     * @param afterEvaluate
+     * Returns the evaluation time of this project.
      */
-    public void setAfterEvaluate(long afterEvaluate) {
-        this.afterEvaluate = afterEvaluate;
+    public ContinuousOperation getEvaluation() {
+        return evaluation;
     }
 
     /**
      * Gets the state of the project after evaluation finishes.
-     * @return
      */
     public ProjectState getState() {
         return state;
@@ -91,25 +78,4 @@ public class ProjectProfile {
     public void setState(ProjectState state) {
         this.state = state;
     }
-
-    /**
-     * Get the elapsed time (in mSec) for project evaluation (configuration phase).
-     * @return
-     */
-    public long getElapsedEvaluation() {
-        return afterEvaluate - beforeEvaluate;
-    }
-
-    /**
-     * Get the elapsed time (in mSec) for execution of all tasks.
-     * @return
-     */
-    public long getElapsedTaskExecution() {
-        long result = 0;
-        for (TaskProfile taskProfile : tasks.values()) {
-            result += taskProfile.getElapsedExecution();
-        }
-        return result;
-    }
 }
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ReportGeneratingProfileListener.java b/subprojects/core/src/main/groovy/org/gradle/profile/ReportGeneratingProfileListener.java
new file mode 100644
index 0000000..602ea25
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ReportGeneratingProfileListener.java
@@ -0,0 +1,40 @@
+/*
+ * 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.profile;
+
+import org.gradle.BuildAdapter;
+import org.gradle.api.invocation.Gradle;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class ReportGeneratingProfileListener extends BuildAdapter implements ProfileListener {
+    private static final SimpleDateFormat FILE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
+    private File buildDir;
+
+    @Override
+    public void projectsEvaluated(Gradle gradle) {
+        buildDir = gradle.getRootProject().getBuildDir();
+    }
+
+    public void buildFinished(BuildProfile buildProfile) {
+        ProfileReportRenderer renderer = new ProfileReportRenderer();
+        File file = new File(buildDir, "reports/profile/profile-" + FILE_DATE_FORMAT.format(new Date(buildProfile.getBuildStarted())) + ".html");
+        renderer.writeTo(buildProfile, file);
+    }
+}
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java b/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java
new file mode 100644
index 0000000..e322a82
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/TaskExecution.java
@@ -0,0 +1,48 @@
+/*
+ * 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.profile;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskState;
+
+/**
+ * Container for task profiling information.
+ * This includes timestamps around task execution and the resulting TaskState.
+ */
+public class TaskExecution extends ContinuousOperation {
+    private final Task task;
+    private TaskState state;
+
+    public TaskExecution(Task task) {
+        this.task = task;
+    }
+
+    /**
+     * Gets the string task path.
+     * @return
+     */
+    public String getPath() {
+        return task.getPath();
+    }
+
+    public TaskState getState() {
+        return state;
+    }
+
+    public void setState(TaskState state) {
+        this.state = state;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/TaskProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/TaskProfile.java
deleted file mode 100644
index 2a5055a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/profile/TaskProfile.java
+++ /dev/null
@@ -1,74 +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.profile;
-
-import org.gradle.api.Task;
-import org.gradle.api.tasks.TaskState;
-
-/**
- * Container for task profiling information.
- * This includes timestamps around task execution and the resulting TaskState.
- */
-public class TaskProfile {
-    private Task task;
-    private long start;
-    private long finish;
-    private TaskState state;
-
-    public TaskProfile(Task task) {
-        this.task = task;
-    }
-
-    /**
-     * Gets the string task path.
-     * @return
-     */
-    public String getPath() {
-        return task.getPath();
-    }
-
-    /**
-     * Should be called with a time right before task execution begins.
-     * @param start
-     */
-    public void setStart(long start) {
-        this.start = start;
-    }
-
-    /**
-     * Should be called with a time right after task execution finishes.
-     * @param finish
-     */
-    public void setFinish(long finish) {
-        this.finish = finish;
-    }
-
-    /**
-     * Gets the elapsed task execution time (in mSec).
-     * @return
-     */
-    public long getElapsedExecution() {
-        return finish - start;
-    }
-
-    public TaskState getState() {
-        return state;
-    }
-
-    public void setState(TaskState state) {
-        this.state = state;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java
new file mode 100644
index 0000000..06b0747
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.w3c.dom.Element;
+
+public class CodePanelRenderer extends DomReportRenderer<String> {
+    @Override
+    public void render(String text, Element parent) {
+        // Wrap in a <span>, to work around CSS problem in IE
+        Element span = append(parent, "span");
+        span.setAttribute("class", "code");
+        appendWithText(span, "pre", text);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/DomReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/DomReportRenderer.java
new file mode 100644
index 0000000..c478bf8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/DomReportRenderer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.w3c.dom.Element;
+
+public abstract class DomReportRenderer<T> {
+    /**
+     * Renders the report for the given model as children of the given DOM element.
+     */
+    public abstract void render(T model, Element parent);
+
+    protected Element append(Element parent, String name) {
+        Element element = parent.getOwnerDocument().createElement(name);
+        parent.appendChild(element);
+        return element;
+    }
+
+    protected Element appendWithId(Element parent, String name, String id) {
+        Element element = parent.getOwnerDocument().createElement(name);
+        parent.appendChild(element);
+        element.setAttribute("id", id);
+        return element;
+    }
+
+    protected Element appendWithText(Element parent, String name, Object textContent) {
+        Element element = parent.getOwnerDocument().createElement(name);
+        parent.appendChild(element);
+        appendText(element, textContent);
+        return element;
+    }
+
+    protected void appendText(Element element, Object textContent) {
+        element.appendChild(element.getOwnerDocument().createTextNode(textContent.toString()));
+    }
+
+    protected Element appendLink(Element parent, String href, Object textContent) {
+        Element element = appendWithText(parent, "a", textContent);
+        element.setAttribute("href", href);
+        return element;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/DurationFormatter.java b/subprojects/core/src/main/groovy/org/gradle/reporting/DurationFormatter.java
new file mode 100644
index 0000000..f74da55
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/DurationFormatter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import java.math.BigDecimal;
+
+public class DurationFormatter {
+    public static final int MILLIS_PER_SECOND = 1000;
+    public static final int MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
+    public static final int MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
+    public static final int MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
+    
+    public String format(long duration) {
+        if (duration == 0) {
+            return "0s";
+        }
+
+        StringBuilder result = new StringBuilder();
+
+        long days = duration / MILLIS_PER_DAY;
+        duration = duration % MILLIS_PER_DAY;
+        if (days > 0) {
+            result.append(days);
+            result.append("d");
+        }
+        long hours = duration / MILLIS_PER_HOUR;
+        duration = duration % MILLIS_PER_HOUR;
+        if (hours > 0 || result.length() > 0) {
+            result.append(hours);
+            result.append("h");
+        }
+        long minutes = duration / MILLIS_PER_MINUTE;
+        duration = duration % MILLIS_PER_MINUTE;
+        if (minutes > 0 || result.length() > 0) {
+            result.append(minutes);
+            result.append("m");
+        }
+        int secondsScale = result.length() > 0 ? 2 : 3;
+        result.append(BigDecimal.valueOf(duration).divide(BigDecimal.valueOf(MILLIS_PER_SECOND)).setScale(secondsScale, BigDecimal.ROUND_HALF_UP));
+        result.append("s");
+        return result.toString();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
new file mode 100644
index 0000000..d717748
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.internal.SystemProperties;
+import org.gradle.util.GFileUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.*;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+
+public class HtmlReportRenderer {
+    private DocumentBuilder documentBuilder;
+    private Transformer transformer;
+    private final Set<URL> resources = new HashSet<URL>();
+
+    public void requireResource(URL resource) {
+        resources.add(resource);
+    }
+
+    public <T> TextReportRenderer<T> renderer(final DomReportRenderer<T> renderer) {
+        return renderer(new TextReportRenderer<T>() {
+            @Override
+            protected void writeTo(T model, Writer writer) throws Exception {
+                if (documentBuilder == null) {
+                    documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+                }
+                Document document = documentBuilder.newDocument();
+
+                Element html = document.createElement("html");
+                document.appendChild(html);
+                renderer.render(model, html);
+
+                if (transformer == null) {
+                    TransformerFactory factory = TransformerFactory.newInstance();
+                    transformer = factory.newTransformer();
+                    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+                    transformer.setOutputProperty(OutputKeys.METHOD, "html");
+                    transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "text/html");
+                }
+
+                writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
+                writer.write(SystemProperties.getLineSeparator());
+                transformer.transform(new DOMSource(document), new StreamResult(writer));
+            }
+        });
+    }
+
+    public <T> TextReportRenderer<T> renderer(final TextReportRenderer<T> renderer) {
+        return new TextReportRenderer<T>() {
+            @Override
+            protected void writeTo(T model, Writer out) throws Exception {
+                renderer.writeTo(model, out);
+            }
+
+            @Override
+            public void writeTo(T model, File file) {
+                super.writeTo(model, file);
+                for (URL resource : resources) {
+                    String name = StringUtils.substringAfterLast(resource.getPath(), "/");
+                    File destFile = new File(file.getParentFile(), name);
+                    if (!destFile.exists()) {
+                        GFileUtils.copyURLToFile(resource, destFile);
+                    }
+                }
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
new file mode 100644
index 0000000..be536eb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.gradle.util.GradleVersion;
+import org.w3c.dom.Element;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+public abstract class TabbedPageRenderer<T> extends DomReportRenderer<T> {
+    private T model;
+
+    protected T getModel() {
+        return model;
+    }
+
+    protected abstract String getTitle();
+
+    protected String getPageTitle() {
+        return getTitle();
+    }
+
+    protected abstract DomReportRenderer<T> getHeaderRenderer();
+
+    protected abstract DomReportRenderer<T> getContentRenderer();
+
+    @Override
+    public void render(T model, Element parent) {
+        this.model = model;
+
+        // <head>
+        Element head = append(parent, "head");
+        appendWithText(head, "title", getPageTitle());
+        Element link = append(head, "link");
+        link.setAttribute("rel", "stylesheet");
+        link.setAttribute("href", "base-style.css");
+        link.setAttribute("type", "text/css");
+        link = append(head, "link");
+        link.setAttribute("rel", "stylesheet");
+        link.setAttribute("href", "style.css");
+        link.setAttribute("type", "text/css");
+        Element script = append(head, "script");
+        script.setAttribute("src", "report.js");
+        script.setAttribute("type", "text/javascript");
+
+        // <body>
+        Element body = append(parent, "body");
+
+        // Content
+        Element content = appendWithId(body, "div", "content");
+        appendWithText(content, "h1", getTitle());
+        getHeaderRenderer().render(model, content);
+        getContentRenderer().render(model, content);
+
+        // Footer
+        Element footer = appendWithId(content, "div", "footer");
+        Element footerText = append(footer, "p");
+        appendText(footerText, "Generated by ");
+        appendLink(footerText, "http://www.gradle.org/", String.format("Gradle %s", GradleVersion.current().getVersion()));
+        appendText(footerText, String.format(" at %s", DateFormat.getDateTimeInstance().format(new Date())));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java
new file mode 100644
index 0000000..744f01b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TabsRenderer<T> extends DomReportRenderer<T> {
+    private final List<TabDefinition> tabs = new ArrayList<TabDefinition>();
+
+    public void add(String title, DomReportRenderer<T> contentRenderer) {
+        tabs.add(new TabDefinition(title, contentRenderer));
+    }
+
+    public void clear() {
+        tabs.clear();
+    }
+
+    @Override
+    public void render(T model, Element parent) {
+        Element tabs = appendWithId(parent, "div", "tabs");
+        Element ul = append(tabs, "ul");
+        ul.setAttribute("class", "tabLinks");
+        for (int i = 0; i < this.tabs.size(); i++) {
+            TabDefinition tab = this.tabs.get(i);
+            Element li = append(ul, "li");
+            Element a = appendWithText(li, "a", tab.title);
+            String tabId = String.format("tab%s", i);
+            a.setAttribute("href", "#" + tabId);
+            Element tabDiv = appendWithId(tabs, "div", tabId);
+            tabDiv.setAttribute("class", "tab");
+            appendWithText(tabDiv, "h2", tab.title);
+            tab.renderer.render(model, tabDiv);
+        }
+    }
+
+    private class TabDefinition {
+        final String title;
+        final DomReportRenderer<T> renderer;
+
+        private TabDefinition(String title, DomReportRenderer<T> renderer) {
+            this.title = title;
+            this.renderer = renderer;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TextDomReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TextDomReportRenderer.java
new file mode 100644
index 0000000..cb6e3f0
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TextDomReportRenderer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.gradle.internal.UncheckedException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+
+public class TextDomReportRenderer<T> extends DomReportRenderer<T> {
+    private final TextReportRenderer<T> renderer;
+
+    public TextDomReportRenderer(TextReportRenderer<T> renderer) {
+        this.renderer = renderer;
+    }
+
+    @Override
+    public void render(T model, Element parent) {
+        try {
+            StringWriter writer = new StringWriter();
+            renderer.writeTo(model, writer);
+            Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(writer.toString().getBytes()));
+            NodeList children = document.getDocumentElement().getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node adopted = parent.getOwnerDocument().importNode(children.item(i), true);
+                parent.appendChild(adopted);
+            }
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
new file mode 100644
index 0000000..aeef3ca
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.gradle.internal.UncheckedException;
+
+import java.io.*;
+
+public abstract class TextReportRenderer<T> {
+    /**
+     * Renders the report for the given model to a writer.
+     */
+    protected abstract void writeTo(T model, Writer out) throws Exception;
+
+    /**
+     * Renders the report for the given model to a file.
+     */
+    public void writeTo(T model, File file) {
+        try {
+            file.getParentFile().mkdirs();
+            Writer writer = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file)), "utf-8");
+            try {
+                writeTo(model, writer);
+            } finally {
+                writer.close();
+            }
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
index 7ac7f7c..fd5362c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
@@ -15,22 +15,10 @@
  */
 package org.gradle.testfixtures;
 
-import org.gradle.StartParameter;
 import org.gradle.api.Project;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.IProjectFactory;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ServiceRegistryFactory;
-import org.gradle.initialization.DefaultProjectDescriptor;
-import org.gradle.initialization.DefaultProjectDescriptorRegistry;
-import org.gradle.invocation.DefaultGradle;
-import org.gradle.testfixtures.internal.GlobalTestServices;
-import org.gradle.testfixtures.internal.TestTopLevelBuildServiceRegistry;
-import org.gradle.util.GFileUtils;
+import org.gradle.testfixtures.internal.ProjectBuilderImpl;
 
 import java.io.File;
-import java.io.IOException;
 
 /**
  * <p>Creates dummy instances of {@link org.gradle.api.Project} which you can use in testing custom task and plugin
@@ -51,8 +39,11 @@ import java.io.IOException;
  * <p>You can reuse a builder to create multiple {@code Project} instances.</p>
  */
 public class ProjectBuilder {
-    private static final GlobalTestServices GLOBAL_SERVICES = new GlobalTestServices();
+
     private File projectDir;
+    private String name = "test";
+    private Project parent;
+    private ProjectBuilderImpl impl = new ProjectBuilderImpl();
 
     /**
      * Creates a project builder.
@@ -67,10 +58,32 @@ public class ProjectBuilder {
      * Specifies the project directory for the project to build.
      *
      * @param dir The project directory
-     * @return A new ProjectBuilder.
+     * @return The builder
      */
     public ProjectBuilder withProjectDir(File dir) {
-        projectDir = GFileUtils.canonicalise(dir);
+        projectDir = dir;
+        return this;
+    }
+
+    /**
+     * Specifies the name for the project
+     *
+     * @param name project name
+     * @return The builder
+     */
+    public ProjectBuilder withName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    /**
+     * Specifies the parent project. Use it to create multi-module projects.
+     *
+     * @param parent parent project
+     * @return The builder
+     */
+    public ProjectBuilder withParent(Project parent) {
+        this.parent = parent;
         return this;
     }
 
@@ -80,32 +93,9 @@ public class ProjectBuilder {
      * @return The project
      */
     public Project build() {
-        if (projectDir == null) {
-            try {
-                projectDir = GFileUtils.canonicalise(File.createTempFile("gradle", "projectDir"));
-                projectDir.delete();
-                projectDir.mkdir();
-                projectDir.deleteOnExit();
-            } catch (IOException e) {
-                throw new UncheckedIOException(e);
-            }
+        if (parent != null) {
+            return impl.createChildProject(name, parent, projectDir);
         }
-
-        final File homeDir = new File(projectDir, "gradleHome");
-
-        StartParameter startParameter = new StartParameter();
-        startParameter.setGradleUserHomeDir(new File(projectDir, "userHome"));
-
-        ServiceRegistryFactory topLevelRegistry = new TestTopLevelBuildServiceRegistry(GLOBAL_SERVICES, startParameter, homeDir);
-        GradleInternal gradle = new DefaultGradle(null, startParameter, topLevelRegistry);
-
-        DefaultProjectDescriptor projectDescriptor = new DefaultProjectDescriptor(null, "test", projectDir, new DefaultProjectDescriptorRegistry());
-        ProjectInternal project = topLevelRegistry.get(IProjectFactory.class).createProject(projectDescriptor, null, gradle);
-
-        gradle.setRootProject(project);
-        gradle.setDefaultProject(project);
-
-        return project;
+        return impl.createProject(name, projectDir);
     }
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
index 8af68d1..cfc430b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
@@ -15,16 +15,9 @@
  */
 package org.gradle.testfixtures.internal;
 
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.DefaultClassPathRegistry;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.project.DefaultIsolatedAntBuilder;
-import org.gradle.api.internal.project.DefaultServiceRegistry;
-import org.gradle.api.internal.project.IsolatedAntBuilder;
-import org.gradle.cache.AutoCloseCacheFactory;
-import org.gradle.cache.CacheFactory;
-import org.gradle.initialization.ClassLoaderFactory;
-import org.gradle.initialization.DefaultClassLoaderFactory;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.api.internal.project.GlobalServicesRegistry;
 import org.gradle.listener.DefaultListenerManager;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.LoggingManagerInternal;
@@ -36,41 +29,28 @@ import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.logging.internal.ProgressListener;
 import org.gradle.util.TrueTimeProvider;
 
-public class GlobalTestServices extends DefaultServiceRegistry {
-    protected ListenerManager createListenerManager() {
-        return new DefaultListenerManager();
+public class GlobalTestServices extends GlobalServicesRegistry {
+    public GlobalTestServices() {
+        super(new TestLoggingServices());
     }
 
-    protected ClassPathRegistry createClassPathRegistry() {
-        return new DefaultClassPathRegistry();
-    }
-
-    protected ClassLoaderFactory createClassLoaderFactory() {
-        return new DefaultClassLoaderFactory(get(ClassPathRegistry.class));
-    }
-
-    protected CacheFactory createCacheFactory() {
-        return new AutoCloseCacheFactory(new InMemoryCacheFactory());
-    }
+    private static class TestLoggingServices extends DefaultServiceRegistry {
+        final ListenerManager listenerManager = new DefaultListenerManager();
 
-    protected ProgressLoggerFactory createProgressLoggerFactory() {
-        return new DefaultProgressLoggerFactory(get(ListenerManager.class).getBroadcaster(ProgressListener.class), new TrueTimeProvider());
-    }
+        protected ProgressLoggerFactory createProgressLoggerFactory() {
+            return new DefaultProgressLoggerFactory(listenerManager.getBroadcaster(ProgressListener.class), new TrueTimeProvider());
+        }
 
-    protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
-        return new Factory<LoggingManagerInternal>() {
-            public LoggingManagerInternal create() {
-                return new NoOpLoggingManager();
-            }
-        };
-    }
+        protected Factory<LoggingManagerInternal> createLoggingManagerFactory() {
+            return new Factory<LoggingManagerInternal>() {
+                public LoggingManagerInternal create() {
+                    return new NoOpLoggingManager();
+                }
+            };
+        }
 
-    protected StyledTextOutputFactory createStyledTextOutputFactory() {
-        return new DefaultStyledTextOutputFactory(get(ListenerManager.class).getBroadcaster(OutputEventListener.class), new TrueTimeProvider());
+        protected StyledTextOutputFactory createStyledTextOutputFactory() {
+            return new DefaultStyledTextOutputFactory(listenerManager.getBroadcaster(OutputEventListener.class), new TrueTimeProvider());
+        }
     }
-
-    protected IsolatedAntBuilder createIsolatedAntBuilder() {
-        return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class));
-    }
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
index fc455f5..36cd074 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
@@ -16,41 +16,88 @@
 package org.gradle.testfixtures.internal;
 
 import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.changedetection.InMemoryIndexedCache;
 import org.gradle.cache.*;
+import org.gradle.cache.internal.*;
 
 import java.io.File;
+import java.util.Collections;
 import java.util.Map;
 
 public class InMemoryCacheFactory implements CacheFactory {
-    public void close(PersistentCache cache) {
+    public PersistentCache openStore(File storeDir, String displayName, FileLockManager.LockMode lockMode, Action<? super PersistentCache> initializer) throws CacheOpenException {
+        return open(storeDir, displayName, CacheUsage.ON, null, Collections.<String, Object>emptyMap(), lockMode, initializer);
     }
 
-    public PersistentCache open(final File cacheDir, CacheUsage usage, Map<String, ?> properties) {
+    public PersistentCache open(File cacheDir, String displayName, CacheUsage usage, CacheValidator cacheValidator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Action<? super PersistentCache> initializer) {
         cacheDir.mkdirs();
-        return new PersistentCache() {
-            public File getBaseDir() {
-                return cacheDir;
-            }
+        InMemoryCache cache = new InMemoryCache(cacheDir);
+        if (initializer != null) {
+            initializer.execute(cache);
+        }
+        return cache;
+    }
 
-            public boolean isValid() {
-                return false;
-            }
+    public <K, V> PersistentIndexedCache<K, V> openIndexedCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Serializer<V> serializer) {
+        return new InMemoryIndexedCache<K, V>();
+    }
 
-            public void markValid() {
-            }
+    public <E> PersistentStateCache<E> openStateCache(File cacheDir, CacheUsage usage, CacheValidator validator, Map<String, ?> properties, FileLockManager.LockMode lockMode, Serializer<E> serializer) {
+        cacheDir.mkdirs();
+        return new SimpleStateCache<E>(new File(cacheDir, "state.bin"), new NoOpFileLock(), new DefaultSerializer<E>());
+    }
 
-            public <K, V> PersistentIndexedCache<K, V> openIndexedCache(Serializer<V> serializer) {
-                return new InMemoryIndexedCache<K, V>();
-            }
+    private static class NoOpFileLock extends AbstractFileAccess {
+        public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
+            return action.create();
+        }
 
-            public <K, V> PersistentIndexedCache<K, V> openIndexedCache() {
-                return new InMemoryIndexedCache<K, V>();
-            }
+        public void writeToFile(Runnable action) throws LockTimeoutException {
+            action.run();
+        }
 
-            public <T> PersistentStateCache<T> openStateCache() {
-                return new SimpleStateCache<T>(this, new DefaultSerializer<T>());
+        public void close() {
+        }
+    }
+
+    private static class InMemoryCache implements PersistentCache {
+        private final File cacheDir;
+
+        public InMemoryCache(File cacheDir) {
+            this.cacheDir = cacheDir;
+        }
+
+        public File getBaseDir() {
+            return cacheDir;
+        }
+
+        public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
+            return new InMemoryIndexedCache<K, V>();
+        }
+
+        public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
+            return new InMemoryIndexedCache<K, V>();
+        }
+
+        public <T> T useCache(String operationDisplayName, Factory<? extends T> action) {
+            // The contract of useCache() means we have to provide some basic synchronization.
+            synchronized (this) {
+                return action.create();
             }
-        };
+        }
+
+        public void useCache(String operationDisplayName, Runnable action) {
+            action.run();
+        }
+
+        public <T> T longRunningOperation(String operationDisplayName, Factory<? extends T> action) {
+            return action.create();
+        }
+
+        public void longRunningOperation(String operationDisplayName, Runnable action) {
+            action.run();
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/NoOpLoggingManager.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/NoOpLoggingManager.java
index e26982d..5ebcbba 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/NoOpLoggingManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/NoOpLoggingManager.java
@@ -16,7 +16,6 @@
 package org.gradle.testfixtures.internal;
 
 import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.internal.OutputEventListener;
@@ -30,15 +29,6 @@ public class NoOpLoggingManager implements LoggingManagerInternal {
         return this;
     }
 
-    public LoggingManager disableStandardOutputCapture() {
-        stdoutLevel = null;
-        return this;
-    }
-
-    public boolean isStandardOutputCaptureEnabled() {
-        return stdoutLevel != null;
-    }
-
     public LogLevel getStandardOutputCaptureLevel() {
         return stdoutLevel;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
new file mode 100644
index 0000000..51a8bb2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/ProjectBuilderImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 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.testfixtures.internal;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Project;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.internal.project.IProjectFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ServiceRegistryFactory;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.initialization.DefaultProjectDescriptor;
+import org.gradle.initialization.DefaultProjectDescriptorRegistry;
+import org.gradle.invocation.DefaultGradle;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+
+/**
+ * by Szczepan Faber, created at: 10/1/11
+ */
+public class ProjectBuilderImpl {
+    private static final GlobalTestServices GLOBAL_SERVICES = new GlobalTestServices();
+    private static final AsmBackedClassGenerator CLASS_GENERATOR = new AsmBackedClassGenerator();
+
+    public Project createChildProject(String name, Project parent, File projectDir) {
+        ProjectInternal parentProject = (ProjectInternal) parent;
+        DefaultProject project = CLASS_GENERATOR.newInstance(
+                DefaultProject.class,
+                name,
+                parentProject,
+                (projectDir != null) ? projectDir.getAbsoluteFile() : new File(parentProject.getProjectDir(), name),
+                new StringScriptSource("test build file", null),
+                parentProject.getGradle(),
+                parentProject.getGradle().getServices()
+        );
+        parentProject.addChildProject(project);
+        parentProject.getProjectRegistry().addProject(project);
+        return project;
+    }
+
+    public Project createProject(String name, File inputProjectDir) {
+        File projectDir = prepareProjectDir(inputProjectDir);
+
+        final File homeDir = new File(projectDir, "gradleHome");
+
+        StartParameter startParameter = new StartParameter();
+        startParameter.setGradleUserHomeDir(new File(projectDir, "userHome"));
+
+        ServiceRegistryFactory topLevelRegistry = new TestTopLevelBuildServiceRegistry(GLOBAL_SERVICES, startParameter, homeDir);
+        GradleInternal gradle = new DefaultGradle(null, startParameter, topLevelRegistry);
+
+        DefaultProjectDescriptor projectDescriptor = new DefaultProjectDescriptor(null, name, projectDir, new DefaultProjectDescriptorRegistry());
+        ProjectInternal project = topLevelRegistry.get(IProjectFactory.class).createProject(projectDescriptor, null, gradle);
+
+        gradle.setRootProject(project);
+        gradle.setDefaultProject(project);
+
+        gradle.getScriptClassLoader().addParent(getClass().getClassLoader());
+
+        return project;
+    }
+
+    public File prepareProjectDir(File projectDir) {
+        if (projectDir == null) {
+            TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
+            projectDir = temporaryFileProvider.createTemporaryDirectory("gradle", "projectDir");
+            // TODO deleteOnExit won't clean up non-empty directories (and it leaks memory for long-running processes).
+            projectDir.deleteOnExit();
+        } else {
+            projectDir = GFileUtils.canonicalise(projectDir);
+        }
+        return projectDir;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestTopLevelBuildServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestTopLevelBuildServiceRegistry.java
index a896d8c..bfc3716 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestTopLevelBuildServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestTopLevelBuildServiceRegistry.java
@@ -17,8 +17,9 @@ package org.gradle.testfixtures.internal;
 
 import org.gradle.StartParameter;
 import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
+import org.gradle.cache.internal.CacheFactory;
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.BuildClientMetaData;
 
@@ -36,6 +37,11 @@ public class TestTopLevelBuildServiceRegistry extends TopLevelBuildServiceRegist
         return new GradleLauncherMetaData();
     }
 
+    @Override
+    protected CacheFactory createCacheFactory() {
+        return new InMemoryCacheFactory();
+    }
+
     protected GradleDistributionLocator createGradleDistributionLocator() {
         return new GradleDistributionLocator() {
             public File getGradleHome() {
@@ -43,5 +49,4 @@ public class TestTopLevelBuildServiceRegistry extends TopLevelBuildServiceRegist
             }
         };
     }
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java b/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
new file mode 100644
index 0000000..af37faa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/AvailablePortFinder.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2011 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.util;
+
+import net.jcip.annotations.ThreadSafe;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Finds currently available server ports within a certain port range. Code originally taken from Apache MINA.
+ *
+ * <em>Note:</em> If possible, it's preferable to let the party creating the server socket select the port (e.g. with <tt>new ServerSocket(0)</tt>) and then query it for the port chosen. With this
+ * class, there is always a risk that someone else grabs the port between the time it is returned from <tt>getNextAvailable()</tt> and the time the socket is created.
+ *
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ * @see <a href="http://www.iana.org/assignments/port-numbers">IANA.org</a>
+ */
+ at ThreadSafe
+public class AvailablePortFinder {
+    private static final int MIN_PRIVATE_PORT = 49152;
+    private static final int MAX_PRIVATE_PORT = 65535;
+
+    private final Lock lock = new ReentrantLock();
+    private final int startPort;
+    private int current;
+
+    /**
+     * Creates a port finder that operates on private ports.
+     *
+     * @return a port finder that operates on private ports
+     */
+    public static AvailablePortFinder createPrivate() {
+        return new AvailablePortFinder();
+    }
+
+    public AvailablePortFinder() {
+        startPort = new Random().nextInt(MAX_PRIVATE_PORT - MIN_PRIVATE_PORT) + MIN_PRIVATE_PORT;
+        current = startPort;
+    }
+
+    /**
+     * Gets the next available port.
+     *
+     * <p>Tries to avoid returning the same port on successive invocations (but it may happen if no other available ports are found).
+     *
+     * @return the next available port
+     * @throws NoSuchElementException if no available port is found
+     */
+    public int getNextAvailable() {
+        lock.lock();
+        try {
+            while (true) {
+                if (current >= MAX_PRIVATE_PORT) {
+                    current = MIN_PRIVATE_PORT;
+                } else {
+                    current++;
+                }
+                if (current == startPort) {
+                    throw new NoSuchElementException("Could not find an available port within port range.");
+                }
+                int candidate = current;
+                if (available(candidate)) {
+                    return candidate;
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Checks to see if a specific port is available.
+     *
+     * @param port the port to check for availability
+     * @return <tt>true</tt> if the port is available, <tt>false</tt> otherwise
+     */
+    private boolean available(int port) {
+        try {
+            ServerSocket ss = new ServerSocket(port);
+            try {
+                ss.setReuseAddress(true);
+            } finally {
+                ss.close();
+            }
+            DatagramSocket ds = new DatagramSocket(port);
+            try {
+                ds.setReuseAddress(true);
+            } finally {
+                ds.close();
+            }
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java b/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
new file mode 100644
index 0000000..11b2f60
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.util;
+
+public class BuildCommencedTimeProvider implements TimeProvider {
+    private final long fixedTime = System.currentTimeMillis();
+
+    public long getCurrentTime() {
+        return fixedTime;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderBackedClasspathSource.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderBackedClasspathSource.java
new file mode 100644
index 0000000..a83a233
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderBackedClasspathSource.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.util;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class ClassLoaderBackedClasspathSource implements ClasspathSource {
+    private final ClassLoader classLoader;
+
+    public ClassLoaderBackedClasspathSource(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public void collectClasspath(Collection<? super URL> classpath) {
+        ClassLoader stopAt = ClassLoader.getSystemClassLoader() == null ? null : ClassLoader.getSystemClassLoader().getParent();
+        for (ClassLoader cl = classLoader; cl != null && cl != stopAt; cl = cl.getParent()) {
+            if (cl instanceof ClasspathSource) {
+                ClasspathSource classpathSource = (ClasspathSource) cl;
+                classpathSource.collectClasspath(classpath);
+                break;
+            }
+            if (cl instanceof URLClassLoader) {
+                classpath.addAll(Arrays.asList(((URLClassLoader) cl).getURLs()));
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java
new file mode 100644
index 0000000..806cea7
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.util;
+
+import java.net.URI;
+
+public interface ClassLoaderFactory {
+    /**
+     * Creates a ClassLoader implementation which has only the classes from the specified URIs and the Java API visible.
+     */
+    ClassLoader createIsolatedClassLoader(ClassPath classPath);
+
+    /**
+     * Creates a ClassLoader implementation which has only the classes from the specified URIs and the Java API visible.
+     */
+    ClassLoader createIsolatedClassLoader(Iterable<URI> uris);
+
+    /**
+     * Creates a ClassLoader implementation which has, by default, only the classes from the Java API visible, but which can allow access
+     * to selected classes from the given parent ClassLoader.
+     *
+     * @param parent the parent ClassLoader
+     * @return The ClassLoader
+     */
+    FilteringClassLoader createFilteringClassLoader(ClassLoader parent);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassPath.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassPath.java
new file mode 100644
index 0000000..c6146b5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ClassPath.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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.util;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+
+/**
+ * An immutable classpath.
+ */
+public interface ClassPath {
+    boolean isEmpty();
+
+    Collection<URI> getAsURIs();
+
+    Collection<File> getAsFiles();
+
+    Collection<URL> getAsURLs();
+
+    URL[] getAsURLArray();
+
+    ClassPath plus(Collection<File> classPath);
+    
+    ClassPath plus(ClassPath classPath);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathSource.java b/subprojects/core/src/main/groovy/org/gradle/util/ClasspathSource.java
new file mode 100644
index 0000000..c47980f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ClasspathSource.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.util;
+
+import java.net.URL;
+import java.util.Collection;
+
+public interface ClasspathSource {
+    void collectClasspath(Collection<? super URL> classpath);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java
index 1fddb43..a80bec4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ClasspathUtil.java
@@ -16,11 +16,16 @@
 
 package org.gradle.util;
 
+import org.gradle.api.GradleException;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
 import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -41,12 +46,53 @@ public class ClasspathUtil {
 
     public static List<URL> getClasspath(ClassLoader classLoader) {
         List<URL> implementationClassPath = new ArrayList<URL>();
-        ClassLoader stopAt = ClassLoader.getSystemClassLoader() == null ? null : ClassLoader.getSystemClassLoader().getParent();
-        for (ClassLoader cl = classLoader; cl != null && cl != stopAt; cl = cl.getParent()) {
-            if (cl instanceof URLClassLoader) {
-                implementationClassPath.addAll(Arrays.asList(((URLClassLoader) cl).getURLs()));
+        new ClassLoaderBackedClasspathSource(classLoader).collectClasspath(implementationClassPath);
+        return implementationClassPath;
+    }
+
+    public static File getClasspathForClass(Class<?> targetClass) {
+        URI location;
+        try {
+            location = targetClass.getProtectionDomain().getCodeSource().getLocation().toURI();
+        } catch (URISyntaxException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        if (!location.getScheme().equals("file")) {
+            throw new GradleException(String.format("Cannot determine classpath for %s from codebase '%s'.", targetClass.getName(), location));
+        }
+        return new File(location.getPath());
+    }
+
+    public static File getClasspathForResource(ClassLoader classLoader, String name) {
+        if (classLoader == null) {
+            return getClasspathForResource(ClassLoader.getSystemResource(name), name);
+        } else {
+            return getClasspathForResource(classLoader.getResource(name), name);
+        }
+    }
+
+    public static File getClasspathForResource(URL resource, String name) {
+        URI location;
+        try {
+            location = resource.toURI();
+            String path = location.getPath();
+            if (location.getScheme().equals("file")) {
+                assert path.endsWith("/" + name);
+                return new File(path.substring(0, path.length() - (name.length() + 1)));
+            } else if (location.getScheme().equals("jar")) {
+                String schemeSpecificPart = location.getRawSchemeSpecificPart();
+                int pos = schemeSpecificPart.indexOf("!");
+                if (pos > 0) {
+                    assert schemeSpecificPart.substring(pos + 1).equals("/" + name);
+                    URI jarFile = new URI(schemeSpecificPart.substring(0, pos));
+                    if (jarFile.getScheme().equals("file")) {
+                        return new File(jarFile.getPath());
+                    }
+                }
             }
+        } catch (URISyntaxException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
         }
-        return implementationClassPath;
+        throw new GradleException(String.format("Cannot determine classpath for resource '%s' from location '%s'.", name, location));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
new file mode 100644
index 0000000..a112970
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 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.util;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.Transformer;
+
+import java.util.*;
+
+public abstract class CollectionUtils {
+
+    public static <T> T findFirst(Iterable<T> source, Spec<? super T> filter) {
+        for (T item : source) {
+            if (filter.isSatisfiedBy(item)) {
+                return item;
+            }
+        }
+
+        return null;
+    }
+    
+    public static <T> Set<T> filter(Set<T> set, Spec<? super T> filter) {
+        return filter(set, new LinkedHashSet<T>(), filter);
+    }
+
+    public static <T> List<T> filter(List<T> list, Spec<? super T> filter) {
+        return filter(list, new LinkedList<T>(), filter);
+    }
+
+    public static <T, C extends Collection<T>> C filter(Iterable<T> source, C destination, Spec<? super T> filter) {
+        for (T item : source) {
+             if (filter.isSatisfiedBy(item)) {
+                 destination.add(item);
+             }
+         }
+         return destination;
+    }
+
+    public static <R, I> List<R> collect(List<? extends I> list, Transformer<R, I> transformer) {
+        return collect(list, new ArrayList<R>(list.size()), transformer);
+    }
+
+    public static <R, I> Set<R> collect(Set<? extends I> set, Transformer<R, I> transformer) {
+        return collect(set, new HashSet<R>(), transformer);
+    }
+
+    public static <R, I, C extends Collection<R>> C collect(Iterable<? extends I> source, C destination, Transformer<R, I> transformer) {
+        for (I item : source) {
+            destination.add(transformer.transform(item));
+        }
+        return destination;
+    }
+
+    public static List<String> toStringList(Iterable<?> iterable) {
+        List<String> result = Lists.newArrayList();
+        for (Object elem : iterable) {
+            result.add(elem.toString());
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
index 8e9052d..567d581 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
@@ -18,48 +18,130 @@ package org.gradle.util;
 
 import groovy.lang.Closure;
 import groovy.lang.MissingMethodException;
-import groovy.lang.MissingPropertyException;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.DynamicObjectUtil;
 
+import java.util.Collection;
 import java.util.Map;
 
+import static org.apache.commons.collections.CollectionUtils.subtract;
+
 /**
  * @author Hans Dockter
  */
 public class ConfigureUtil {
 
-    public static <T> T configure(Closure configureClosure, T delegate) {
-        return configure(configureClosure, delegate, Closure.DELEGATE_FIRST);
-    }
-
     public static <T> T configureByMap(Map<String, ?> properties, T delegate) {
+        DynamicObject dynamicObject = DynamicObjectUtil.asDynamicObject(delegate);
+
         for (Map.Entry<String, ?> entry : properties.entrySet()) {
-            try {
-                ReflectionUtil.setProperty(delegate, entry.getKey(), entry.getValue());
-            } catch (MissingPropertyException e) {
-                // Try as a method
+            String name = entry.getKey();
+            Object value = entry.getValue();
+
+            if (dynamicObject.hasProperty(name)) {
+                dynamicObject.setProperty(name, value);
+            } else {
                 try {
-                    ReflectionUtil.invoke(delegate, entry.getKey(), new Object[]{entry.getValue()});
-                } catch (MissingMethodException mme) {
-                    // Throw the original MPE
-                    throw e;
+                    dynamicObject.invokeMethod(name, value);
+                } catch (MissingMethodException e) {
+                    dynamicObject.setProperty(name, value);
                 }
             }
         }
+
         return delegate;
     }
 
-    private static <T> T configure(Closure configureClosure, T delegate, int resolveStrategy) {
+    public static <T> T configureByMap(Map<String, ?> properties, T delegate, Collection<String> mandatoryKeys) {
+        Collection missingKeys = subtract(mandatoryKeys, properties.keySet());
+        if(!missingKeys.isEmpty()) {
+            throw new IncompleteInputException("Input configuration map does not contain following mandatory keys: " + missingKeys, missingKeys);
+        }
+        return configureByMap(properties, delegate);
+    }
+
+    public static class IncompleteInputException extends RuntimeException {
+        private final Collection missingKeys;
+
+        public IncompleteInputException(String message, Collection missingKeys) {
+            super(message);
+            this.missingKeys = missingKeys;
+        }
+
+        public Collection getMissingKeys() {
+            return missingKeys;
+        }
+    }
+
+    /**
+     * <p>Configures {@code delegate} with {@code configureClosure}, via the {@link Configurable} interface if necessary.</p>
+     * 
+     * <p>If {@code delegate} does not implement {@link Configurable} interface, it is set as the delegate of a clone of 
+     * {@code configureClosure} with a resolve strategy of {@code DELEGATE_FIRST}.</p>
+     * 
+     * <p>If {@code delegate} does implement the {@link Configurable} interface, the {@code configureClosure} will be passed to
+     * {@code delegate}'s {@link Configurable#configure(Closure)} method.</p>
+     * 
+     * @param configureClosure The configuration closure
+     * @param delegate The object to be configured
+     * @return The delegate param
+     */
+    public static <T> T configure(Closure configureClosure, T delegate) {
+        return configure(configureClosure, delegate, Closure.DELEGATE_FIRST, true);
+    }
+
+    /**
+     * <p>Configures {@code delegate} with {@code configureClosure}, via the {@link Configurable} interface if necessary.</p>
+     * 
+     * <p>If {@code delegate} does not implement {@link Configurable} interface, it is set as the delegate of a clone of 
+     * {@code configureClosure} with a resolve strategy of {@code DELEGATE_FIRST}.</p>
+     * 
+     * <p>If {@code delegate} does implement the {@link Configurable} interface, the {@code configureClosure} will be passed to
+     * {@code delegate}'s {@link Configurable#configure(Closure)} method. However, if {@code configureableAware} is false then
+     * {@code delegate} will be treated like it does not implement the configurable interface.</p>
+     * 
+     * @param configureClosure The configuration closure
+     * @param delegate The object to be configured
+     * @param configureableAware Whether or not to use the {@link Configurable} interface to configure the object if possible
+     * @return The delegate param
+     */
+    public static <T> T configure(Closure configureClosure, T delegate, boolean configureableAware) {
+        return configure(configureClosure, delegate, Closure.DELEGATE_FIRST, configureableAware);
+    }
+
+    /**
+     * <p>Configures {@code delegate} with {@code configureClosure}, ignoring the {@link Configurable} interface.</p>
+     * 
+     * <p>{@code delegate} is set as the delegate of a clone of {@code configureClosure} with a resolve strategy 
+     * of the {@code resolveStrategy} param.</p>
+     * 
+     * @param configureClosure The configuration closure
+     * @param delegate The object to be configured
+     * @param resolveStrategy The resolution strategy to use for the configuration closure
+     * @return The delegate param
+     */
+    public static <T> T configure(Closure configureClosure, T delegate, int resolveStrategy) {
+        return configure(configureClosure, delegate, resolveStrategy, false);
+    }
+
+    private static <T> T configure(Closure configureClosure, T delegate, int resolveStrategy, boolean configureableAware) {
         if (configureClosure == null) {
             return delegate;
         }
-        Closure copy = (Closure) configureClosure.clone();
-        copy.setResolveStrategy(resolveStrategy);
-        copy.setDelegate(delegate);
-        if (copy.getMaximumNumberOfParameters() == 0) {
-            copy.call();
+
+        if (configureableAware && delegate instanceof Configurable) {
+            ((Configurable)delegate).configure(configureClosure);
         } else {
-            copy.call(delegate);
+            Closure copy = (Closure) configureClosure.clone();
+            copy.setResolveStrategy(resolveStrategy);
+            copy.setDelegate(delegate);
+            if (copy.getMaximumNumberOfParameters() == 0) {
+                copy.call();
+            } else {
+                copy.call(delegate);
+            }
         }
+
         return delegate;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
new file mode 100644
index 0000000..abda632
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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.util;
+
+import org.gradle.internal.UncheckedException;
+
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.List;
+
+public class DefaultClassLoaderFactory implements ClassLoaderFactory {
+    public ClassLoader createIsolatedClassLoader(ClassPath classPath) {
+        return createIsolatedClassLoader(classPath.getAsURIs());
+    }
+
+    public ClassLoader createIsolatedClassLoader(Iterable<URI> uris) {
+        List<URL> classpath = GFileUtils.urisToUrls(uris);
+
+        // This piece of ugliness copies the JAXP (ie XML API) provider, if any, from the system ClassLoader. Here's why:
+        //
+        // 1. When looking for a provider, JAXP looks for a service resource in the context ClassLoader, which is our isolated ClassLoader. If our classpath above does not contain a
+        //    provider, this returns null. If it does contain a provider, JAXP extracts the classname from the service resource.
+        // 2. If not found, JAXP looks for a service resource in the system ClassLoader. This happens to include all the application classes specified on the classpath. If the application
+        //    classpath does not contain a provider, this returns null. If it does contain a provider, JAXP extracts the implementation classname from the service resource.
+        // 3. If not found, JAXP uses a default classname
+        // 4. JAXP attempts to load the provider using the context ClassLoader. which is our isolated ClassLoader. This is fine if the classname came from step 1 or 3. It blows up if the
+        //    classname came from step 2.
+        //
+        // So, as a workaround, locate and include the JAXP provider jar in the classpath for our isolated ClassLoader.
+        //
+        // Note that in practise, this is only triggered when running in our tests
+
+        if (needJaxpImpl()) {
+            try {
+                classpath.add(ClasspathUtil.getClasspathForResource(ClassLoader.getSystemClassLoader(), "META-INF/services/javax.xml.parsers.SAXParserFactory").toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        return new URLClassLoader(classpath.toArray(new URL[classpath.size()]), ClassLoader.getSystemClassLoader().getParent());
+    }
+
+    public FilteringClassLoader createFilteringClassLoader(ClassLoader parent) {
+        // See the comment for {@link #createIsolatedClassLoader} above
+        FilteringClassLoader classLoader = new FilteringClassLoader(parent);
+        if (needJaxpImpl()) {
+            ServiceLocator locator = new ServiceLocator(ClassLoader.getSystemClassLoader());
+            makeServiceVisible(locator, classLoader, SAXParserFactory.class);
+            makeServiceVisible(locator, classLoader, DocumentBuilderFactory.class);
+            makeServiceVisible(locator, classLoader, DatatypeFactory.class);
+        }
+        return classLoader;
+    }
+
+    private void makeServiceVisible(ServiceLocator locator, FilteringClassLoader classLoader, Class<?> serviceType) {
+        classLoader.allowClass(locator.getFactory(serviceType).getImplementationClass());
+        classLoader.allowResource("META-INF/services/" + serviceType.getName());
+    }
+
+    private boolean needJaxpImpl() {
+        return ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory") != null;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassPath.java b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassPath.java
new file mode 100644
index 0000000..bba4217
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassPath.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 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.util;
+
+import java.io.File;
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class DefaultClassPath implements ClassPath, Serializable {
+    private final List<File> files;
+
+    public DefaultClassPath(Iterable<File> files) {
+        this.files = new ArrayList<File>();
+        for (File file : files) {
+            this.files.add(file);
+        }
+    }
+    
+    public DefaultClassPath(File... files) {
+        this(Arrays.asList(files));
+    }
+
+    public boolean isEmpty() {
+        return files.isEmpty();
+    }
+
+    public Collection<URI> getAsURIs() {
+        return GFileUtils.toURIs(files);
+    }
+
+    public Collection<File> getAsFiles() {
+        return files;
+    }
+
+    public URL[] getAsURLArray() {
+        return GFileUtils.toURLArray(files);
+    }
+
+    public Collection<URL> getAsURLs() {
+        return GFileUtils.toURLs(files);
+    }
+
+    public ClassPath plus(ClassPath other) {
+        if (files.isEmpty()) {
+            return other;
+        }
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new DefaultClassPath(concat(files, other.getAsFiles()));
+    }
+
+    public ClassPath plus(Collection<File> other) {
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new DefaultClassPath(concat(files, other));
+    }
+
+    private Iterable<File> concat(List<File> files1, Collection<File> files2) {
+        List<File> result = new ArrayList<File>();
+        result.addAll(files1);
+        result.addAll(files2);
+        return result;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java b/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
old mode 100644
new mode 100755
index a34804e..4dcc075
--- a/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
@@ -1,42 +1,176 @@
-/*
- * 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.util;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-public class DeprecationLogger {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DeprecationLogger.class);
-    private static final Set<String> METHODS = new CopyOnWriteArraySet<String>();
-
-    public static void nagUser(String methodName, String replacement) {
-        if (METHODS.add(methodName)) {
-            LOGGER.warn(String.format(
-                    "The %s method is deprecated and will be removed in the next version of Gradle. You should use the %s method instead.",
-                    methodName, replacement));
-        }
-    }
-
-    public static void nagUser(String methodName) {
-        if (METHODS.add(methodName)) {
-            LOGGER.warn(String.format("The %s method is deprecated and will be removed in the next version of Gradle.",
-                    methodName));
-        }
-    }
-}
+/*
+ * 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.util;
+
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.runtime.StackTraceUtils;
+import org.gradle.internal.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class DeprecationLogger {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DeprecationLogger.class);
+    private static final Set<String> PLUGINS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> TASKS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> METHODS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> DYNAMIC_PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> NAMED_PARAMETERS = Collections.synchronizedSet(new HashSet<String>());
+    
+    private static final ThreadLocal<Boolean> ENABLED = new ThreadLocal<Boolean>() {
+        @Override
+        protected Boolean initialValue() {
+            return true;
+        }
+    };
+
+    private static final ThreadLocal<Boolean> LOG_TRACE = new ThreadLocal<Boolean>() {
+        @Override
+        protected Boolean initialValue() {
+            return false;
+        }
+    };
+
+    public static final String ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME = "org.gradle.deprecation.trace";
+
+    public static void reset() {
+        PLUGINS.clear();
+        METHODS.clear();
+        PROPERTIES.clear();
+        NAMED_PARAMETERS.clear();
+        DYNAMIC_PROPERTIES.clear();
+    }
+
+    public static void nagUserOfReplacedPlugin(String pluginName, String replacement) {
+        if (isEnabled() && PLUGINS.add(pluginName)) {
+            LOGGER.warn(String.format(
+                    "The %s plugin has been deprecated and will be removed in the next version of Gradle. Please use the %s plugin instead.",
+                    pluginName, replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedTask(String taskName, String replacement) {
+        if (isEnabled() && TASKS.add(taskName)) {
+            LOGGER.warn(String.format(
+                    "The %s task has been deprecated and will be removed in the next version of Gradle. Please use the %s instead.",
+                    taskName, replacement));
+            logTraceIfNecessary();
+        }
+    }
+    
+    public static void nagUserOfReplacedMethod(String methodName, String replacement) {
+        if (isEnabled() && METHODS.add(methodName)) {
+            LOGGER.warn(String.format(
+                    "The %s method has been deprecated and will be removed in the next version of Gradle. Please use the %s method instead.",
+                    methodName, replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedProperty(String propertyName, String replacement) {
+        if (isEnabled() && PROPERTIES.add(propertyName)) {
+            LOGGER.warn(String.format(
+                    "The %s property has been deprecated and will be removed in the next version of Gradle. Please use the %s property instead.",
+                    propertyName, replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfDiscontinuedMethod(String methodName) {
+        if (isEnabled() && METHODS.add(methodName)) {
+            LOGGER.warn(String.format("The %s method has been deprecated and will be removed in the next version of Gradle.",
+                    methodName));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfDiscontinuedProperty(String propertyName, String advice) {
+        if (isEnabled() && PROPERTIES.add(propertyName)) {
+            LOGGER.warn(String.format("The %s property has been deprecated and will be removed in the next version of Gradle. %s",
+                    propertyName, advice));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedNamedParameter(String parameterName, String replacement) {
+        if (isEnabled() && NAMED_PARAMETERS.add(parameterName)) {
+            LOGGER.warn(String.format(
+                    "The %s named parameter has been deprecated and will be removed in the next version of Gradle. Please use the %s named parameter instead.",
+                    parameterName, replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserWith(String message) {
+        if (isEnabled() && METHODS.add(message)) {
+            LOGGER.warn(message);
+            logTraceIfNecessary();
+        }
+    }
+    
+    public static <T> T whileDisabled(Factory<T> factory) {
+        ENABLED.set(false);
+        try {
+            return factory.create();
+        } finally {
+            ENABLED.set(true);
+        }
+    }
+
+    private static boolean isTraceLoggingEnabled() {
+        return Boolean.getBoolean(ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME) || LOG_TRACE.get();
+    }
+
+    private static void logTraceIfNecessary() {
+        if (isTraceLoggingEnabled()) {
+            StackTraceElement[] stack = StackTraceUtils.sanitize(new Exception()).getStackTrace();
+            for (StackTraceElement frame : stack) {
+                if (!frame.getClassName().startsWith(DeprecationLogger.class.getName())) {
+                    LOGGER.warn("    {}", frame.toString());
+                }
+            }
+        }
+    }
+
+    private static boolean isEnabled() {
+        return ENABLED.get();
+    }
+    
+    public static void setLogTrace(boolean flag) {
+        LOG_TRACE.set(flag);
+    }
+
+    public static void nagUserAboutDynamicProperty(String propertyName, Object target, Object value) {
+        if (!isEnabled()) {
+            return;
+        }
+        nagUserWith("Dynamic properties are deprecated: http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html");
+
+        String propertyWithClass = target.getClass().getName() + "." + propertyName;
+        if (DYNAMIC_PROPERTIES.add(propertyWithClass)) {
+            String propertyWithTarget = String.format("\"%s\" on \"%s\"", propertyName, target);
+            String theValue = (value==null)? "null" : StringUtils.abbreviate(value.toString(), 25);
+            nagUserWith(String.format("Deprecated dynamic property: %s, value: \"%s\".", propertyWithTarget, theValue));
+        } else {
+            nagUserWith(String.format("Deprecated dynamic property \"%s\" created in multiple locations.", propertyName));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java b/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
index 7b0770e..d2e805b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.util;
 
+import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.concurrent.ExecutorFactory;
 
 import java.io.IOException;
@@ -99,7 +100,7 @@ public class DisconnectableInputStream extends BulkReadInputStream {
                     } finally {
                         lock.unlock();
                     }
-                    throw UncheckedException.asUncheckedException(throwable);
+                    throw UncheckedException.throwAsUncheckedException(throwable);
                 }
             }
         });
@@ -126,7 +127,7 @@ public class DisconnectableInputStream extends BulkReadInputStream {
             assert writePos >= readPos;
             condition.signalAll();
         } catch (InterruptedException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         } finally {
             lock.unlock();
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java b/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java
index 67f6344..140195a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DistributionLocator.java
@@ -15,32 +15,33 @@
  */
 package org.gradle.util;
 
+import org.gradle.internal.UncheckedException;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
 public class DistributionLocator {
-    private static final String ARTIFACTORY_RELEASE_REPOSITORY = "http://repo.gradle.org/gradle/distributions";
-    private static final String ARTIFACTORY_SNAPSHOT_REPOSITORY = "http://repo.gradle.org/gradle/distributions/gradle-snapshots";
-    private static final String CODEHAUS_RELEASE_REPOSITORY = "http://dist.codehaus.org/gradle";
-    private static final String CODEHAUS_SNAPSHOT_REPOSITORY = "http://snapshots.dist.codehaus.org/gradle";
+    private static final String RELEASE_REPOSITORY = "http://services.gradle.org/distributions";
+    private static final String SNAPSHOT_REPOSITORY = "http://services.gradle.org/distributions-snapshots";
 
-    public String getDistributionFor(GradleVersion version) {
+    public URI getDistributionFor(GradleVersion version) {
         return getDistribution(getDistributionRepository(version), version, "gradle", "bin");
     }
 
-    public String getDistributionRepository(GradleVersion version) {
-        if (version.compareTo(GradleVersion.version("0.9")) >= 0) {
-            if (version.isSnapshot()) {
-                return ARTIFACTORY_SNAPSHOT_REPOSITORY;
-            }
-            return ARTIFACTORY_RELEASE_REPOSITORY;
+    private String getDistributionRepository(GradleVersion version) {
+        if (version.isSnapshot()) {
+            return SNAPSHOT_REPOSITORY;
         } else {
-            if (version.isSnapshot()) {
-                return CODEHAUS_SNAPSHOT_REPOSITORY;
-            }
-            return CODEHAUS_RELEASE_REPOSITORY;
+            return RELEASE_REPOSITORY;
         }
     }
 
-    public String getDistribution(String repositoryUrl, GradleVersion version, String archiveName,
-                                  String archiveClassifier) {
-        return String.format("%s/%s-%s-%s.zip", repositoryUrl, archiveName, version.getVersion(), archiveClassifier);
+    private URI getDistribution(String repositoryUrl, GradleVersion version, String archiveName,
+                                   String archiveClassifier) {
+        try {
+            return new URI(String.format("%s/%s-%s-%s.zip", repositoryUrl, archiveName, version.getVersion(), archiveClassifier));
+        } catch (URISyntaxException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
index 852f2fb..71f515f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
@@ -23,16 +23,17 @@ import java.security.PrivilegedAction;
 import java.util.*;
 
 /**
- * A ClassLoader which hides all non-system classes, packages and resources. Allows certain non-system packages and
- * classes to be declared as visible. By default, only the Java system classes, packages and resources are visible.
+ * A ClassLoader which hides all non-system classes, packages and resources. Allows certain non-system packages and classes to be declared as visible. By default, only the Java system classes,
+ * packages and resources are visible.
  */
 public class FilteringClassLoader extends ClassLoader {
     private static final Set<ClassLoader> SYSTEM_CLASS_LOADERS = new HashSet<ClassLoader>();
     private static final ClassLoader EXT_CLASS_LOADER;
-    private static final Set<Package> SYSTEM_PACKAGES = new HashSet<Package>();
+    private static final Set<String> SYSTEM_PACKAGES = new HashSet<String>();
     private final Set<String> packageNames = new HashSet<String>();
     private final Set<String> packagePrefixes = new HashSet<String>();
     private final Set<String> resourcePrefixes = new HashSet<String>();
+    private final Set<String> resourceNames = new HashSet<String>();
     private final Set<String> classNames = new HashSet<String>();
 
     static {
@@ -40,9 +41,11 @@ public class FilteringClassLoader extends ClassLoader {
         for (ClassLoader cl = EXT_CLASS_LOADER; cl != null; cl = cl.getParent()) {
             SYSTEM_CLASS_LOADERS.add(cl);
         }
-        JavaMethod<ClassLoader, Package[]> method = new JavaMethod<ClassLoader, Package[]>(ClassLoader.class,
-                Package[].class, "getPackages");
-        SYSTEM_PACKAGES.addAll(Arrays.asList((Package[]) method.invoke(EXT_CLASS_LOADER)));
+        JavaMethod<ClassLoader, Package[]> method = JavaMethod.create(ClassLoader.class, Package[].class, "getPackages");
+        Package[] systemPackages = method.invoke(EXT_CLASS_LOADER);
+        for (Package p : systemPackages) {
+            SYSTEM_PACKAGES.add(p.getName());
+        }
     }
 
     public FilteringClassLoader(ClassLoader parent) {
@@ -109,6 +112,9 @@ public class FilteringClassLoader extends ClassLoader {
     }
 
     private boolean allowed(String resourceName) {
+        if (resourceNames.contains(resourceName)) {
+            return true;
+        }
         for (String resourcePrefix : resourcePrefixes) {
             if (resourceName.startsWith(resourcePrefix)) {
                 return true;
@@ -118,13 +124,11 @@ public class FilteringClassLoader extends ClassLoader {
     }
 
     private boolean allowed(Package p) {
-        if (SYSTEM_PACKAGES.contains(p)) {
+        if (SYSTEM_PACKAGES.contains(p.getName())) {
             return true;
         }
-        for (String packageName : packageNames) {
-            if (p.getName().equals(packageName)) {
-                return true;
-            }
+        if (packageNames.contains(p.getName())) {
+            return true;
         }
         for (String packagePrefix : packagePrefixes) {
             if (p.getName().startsWith(packagePrefix)) {
@@ -156,7 +160,7 @@ public class FilteringClassLoader extends ClassLoader {
     }
 
     /**
-     * Marks a package and all its sub-packages as visible.
+     * Marks a package and all its sub-packages as visible. Also makes resources in those packages visible.
      *
      * @param packageName The package name
      */
@@ -174,4 +178,22 @@ public class FilteringClassLoader extends ClassLoader {
     public void allowClass(Class<?> aClass) {
         classNames.add(aClass.getName());
     }
+
+    /**
+     * Marks all resources with the given prefix as visible.
+     *
+     * @param resourcePrefix The resource prefix.
+     */
+    public void allowResources(String resourcePrefix) {
+        resourcePrefixes.add(resourcePrefix + "/");
+    }
+
+    /**
+     * Marks a single resource as visible
+     *
+     * @param resourceName The resource name
+     */
+    public void allowResource(String resourceName) {
+        resourceNames.add(resourceName);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
index 35f3df3..60298e4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
@@ -16,12 +16,15 @@
 package org.gradle.util;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.LineIterator;
 import org.apache.commons.io.filefilter.IOFileFilter;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.util.internal.LimitedDescription;
 
 import java.io.*;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.util.*;
 import java.util.zip.Checksum;
@@ -118,6 +121,14 @@ public class GFileUtils {
         return paths;
     }
 
+    public static List<URI> toURIs(Iterable<File> files) {
+        List<URI> urls = new ArrayList<URI>();
+        for (File file : files) {
+            urls.add(file.toURI());
+        }
+        return urls;
+    }
+
     public static List<URL> toURLs(Iterable<File> files) {
         List<URL> urls = new ArrayList<URL>();
         for (File file : files) {
@@ -130,6 +141,18 @@ public class GFileUtils {
         return urls;
     }
 
+    public static List<URL> urisToUrls(Iterable<URI> uris) {
+        List<URL> urls = new ArrayList<URL>();
+        for (URI uri : uris) {
+            try {
+                urls.add(uri.toURL());
+            } catch (MalformedURLException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+        return urls;
+    }
+
     public static URL[] toURLArray(Collection<File> files) {
         return toURLs(files.toArray(new File[files.size()]));
     }
@@ -291,6 +314,40 @@ public class GFileUtils {
         }
     }
 
+    public static class TailReadingException extends RuntimeException {
+        public TailReadingException(Throwable throwable) {
+            super(throwable);
+        }
+    }
+
+    /**
+     * @param file to read from tail
+     * @param maxLines max lines to read
+     * @return tail content
+     * @throws org.gradle.util.GFileUtils.TailReadingException when reading failed
+     */
+    public static String tail(File file, int maxLines) throws TailReadingException {
+        BufferedReader reader = null;
+        FileReader fileReader = null;
+        try {
+            fileReader = new FileReader(file);
+            reader = new BufferedReader(fileReader);
+
+            LimitedDescription description = new LimitedDescription(maxLines);
+            String line = reader.readLine();
+            while (line != null) {
+                description.append(line);
+                line = reader.readLine();
+            }
+            return description.toString();
+        } catch (Exception e) {
+            throw new TailReadingException(e);
+        } finally {
+            IOUtils.closeQuietly(fileReader);
+            IOUtils.closeQuietly(reader);
+        }
+    }
+
     public static LineIterator lineIterator(File file, String encoding) {
         try {
             return FileUtils.lineIterator(file, encoding);
@@ -536,4 +593,14 @@ public class GFileUtils {
             return true;
         }
     }
+
+    /**
+     * Creates a directory and any unexisting parent directories. Throws an
+     * UncheckedIOException if it fails to do so.
+     */
+    public static void createDirectory(File directory) {
+        if (!directory.exists() && !directory.mkdirs()) {
+            throw new UncheckedIOException("Failed to create directory " + directory);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
index cbf3ba3..4a6d9d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
@@ -21,36 +21,50 @@ import org.gradle.api.UncheckedIOException;
 
 import java.io.*;
 import java.net.URL;
+import java.net.URLConnection;
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+
 /**
  * @author Hans Dockter
  */
 public class GUtil {
     public static <T extends Collection> T flatten(Object[] elements, T addTo, boolean flattenMaps) {
-        return flatten(Arrays.asList(elements), addTo, flattenMaps);
+        return flatten(asList(elements), addTo, flattenMaps);
     }
 
     public static <T extends Collection> T flatten(Object[] elements, T addTo) {
-        return flatten(Arrays.asList(elements), addTo);
+        return flatten(asList(elements), addTo);
     }
 
     public static <T extends Collection> T flatten(Collection elements, T addTo) {
         return flatten(elements, addTo, true);
     }
 
-    public static <T extends Collection> T flatten(Collection elements, T addTo, boolean flattenMaps) {
+    public static <T extends Collection> T flattenElements(Object... elements) {
+        Collection<T> out = new LinkedList<T>();
+        flatten(elements, out, true);
+        return (T) out;
+    }
+
+    public static <T extends Collection> T flatten(Collection elements, T addTo, boolean flattenMapsAndArrays) {
+        return flatten(elements, addTo, flattenMapsAndArrays, flattenMapsAndArrays);
+    }
+
+    public static <T extends Collection> T flatten(Collection elements, T addTo, boolean flattenMaps, boolean flattenArrays) {
         Iterator iter = elements.iterator();
         while (iter.hasNext()) {
             Object element = iter.next();
             if (element instanceof Collection) {
-                flatten((Collection) element, addTo, flattenMaps);
+                flatten((Collection) element, addTo, flattenMaps, flattenArrays);
             } else if ((element instanceof Map) && flattenMaps) {
-                flatten(((Map) element).values(), addTo, flattenMaps);
-            } else if ((element.getClass().isArray()) && flattenMaps) {
-                flatten(Arrays.asList((Object[]) element), addTo, flattenMaps);
+                flatten(((Map) element).values(), addTo, flattenMaps, flattenArrays);
+            } else if ((element.getClass().isArray()) && flattenArrays) {
+                flatten(asList((Object[]) element), addTo, flattenMaps, flattenArrays);
             } else {
                 addTo.add(element);
             }
@@ -58,8 +72,30 @@ public class GUtil {
         return addTo;
     }
 
-    public static List flatten(Collection elements, boolean flattenMaps) {
-        return flatten(elements, new ArrayList(), flattenMaps);
+    /**
+     * Flattens input collections (including arrays *but* not maps).
+     * If input is not a collection wraps it in a collection and returns it.
+     * @param input any object
+     * @return collection of flattened input or single input wrapped in a collection.
+     */
+    public static Collection collectionize(Object input) {
+        if (input == null) {
+            return emptyList();
+        } else if (input instanceof Collection) {
+            Collection out = new LinkedList();
+            flatten((Collection) input, out, false, true);
+            return out;
+        } else if (input.getClass().isArray()) {
+            Collection out = new LinkedList();
+            flatten(asList((Object[]) input), out, false, true);
+            return out;
+        } else {
+            return asList(input);
+        }
+    }
+
+    public static List flatten(Collection elements, boolean flattenMapsAndArrays) {
+        return flatten(elements, new ArrayList(), flattenMapsAndArrays);
     }
 
     public static List flatten(Collection elements) {
@@ -86,7 +122,7 @@ public class GUtil {
     }
 
     public static String join(Object[] self, String separator) {
-        return join(Arrays.asList(self), separator);
+        return join(asList(self), separator);
     }
 
     public static List<String> prefix(String prefix, Collection<String> strings) {
@@ -113,14 +149,6 @@ public class GUtil {
         return isTrue(object) ? object : defaultValue;
     }
 
-    public static <T> Set<T> addSets(Iterable<? extends T>... sets) {
-        return addToCollection(new HashSet<T>(), sets);
-    }
-
-    public static <T> List<T> addLists(Iterable<? extends T>... lists) {
-        return addToCollection(new ArrayList<T>(), lists);
-    }
-
     public static <V, T extends Collection<? super V>> T addToCollection(T dest, Iterable<? extends V>... srcs) {
         for (Iterable<? extends V> src : srcs) {
             for (V v : src) {
@@ -170,7 +198,9 @@ public class GUtil {
 
     public static Properties loadProperties(URL url) {
         try {
-            return loadProperties(url.openStream());
+            URLConnection uc = url.openConnection();
+            uc.setUseCaches(false);
+            return loadProperties(uc.getInputStream());
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
@@ -258,7 +288,7 @@ public class GUtil {
         return toWords(string, ' ');
     }
 
-    private static String toWords(CharSequence string, char separator) {
+    public static String toWords(CharSequence string, char separator) {
         if (string == null) {
             return null;
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
index 868da52..0e12aa0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
@@ -19,11 +19,24 @@ package org.gradle.util;
 import groovy.lang.GroovySystem;
 import org.apache.ivy.Ivy;
 import org.apache.tools.ant.Main;
+import org.gradle.api.GradleException;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Properties;
+import java.util.Date;
+import java.util.TimeZone;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -32,9 +45,6 @@ import java.util.regex.Pattern;
  * @author Russel Winder
  */
 public class GradleVersion implements Comparable<GradleVersion> {
-    private final static String BUILD_TIME = "buildTime";
-    private final static String VERSION = "version";
-    private final static String FILE_NAME = "/org/gradle/version.properties";
     public final static String URL = "http://www.gradle.org";
     private final static Pattern VERSION_PATTERN = Pattern.compile("(\\d+(\\.\\d+)+)(-(\\p{Alpha}+)-(\\d+[a-z]?))?(-(\\d{14}[-+]\\d{4}))?");
 
@@ -43,24 +53,44 @@ public class GradleVersion implements Comparable<GradleVersion> {
     private final Long snapshot;
     private final String versionPart;
     private final Stage stage;
+    private static final GradleVersion CURRENT;
 
-    public static GradleVersion current() {
-        return new GradleVersion(GUtil.loadProperties(GradleVersion.class.getResourceAsStream(FILE_NAME)));
+    public static final String RESOURCE_NAME = "/org/gradle/releases.xml";
+
+    static {
+        URL resource = GradleVersion.class.getResource(RESOURCE_NAME);
+        Document document;
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            InputStream inputStream = resource.openStream();
+            try {
+                document = builder.parse(inputStream);
+            } finally {
+                inputStream.close();
+            }
+            NodeList currentElements = document.getDocumentElement().getElementsByTagName("current");
+            if (currentElements.getLength() != 1) {
+                throw new GradleException(String.format("Expected to find 1 <current> element, found %s.", currentElements.getLength()));
+            }
+            Element currentRelease = (Element) currentElements.item(0);
+            CURRENT = new GradleVersion(currentRelease.getAttribute("version"), new SimpleDateFormat("yyyyMMddHHmmssZ").parse(currentRelease.getAttribute("build-time")));
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not load version details from resource '%s'.", resource), e);
+        }
     }
 
-    public static GradleVersion version(String version) {
-        return new GradleVersion(properties(version));
+    public static GradleVersion current() {
+        return CURRENT;
     }
 
-    private static Properties properties(String version) {
-        Properties properties = new Properties();
-        properties.setProperty(VERSION, version);
-        return properties;
+    public static GradleVersion version(String version) {
+        return new GradleVersion(version, null);
     }
 
-    private GradleVersion(Properties properties) {
-        version = properties.getProperty(VERSION);
-        buildTime = properties.getProperty(BUILD_TIME);
+    private GradleVersion(String version, Date buildTime) {
+        this.version = version;
+        this.buildTime = buildTime == null ? null : formatBuildTime(buildTime);
         Matcher matcher = VERSION_PATTERN.matcher(version);
         if (!matcher.matches()) {
             throw new InvalidUserDataException(String.format("Unexpected Gradle version '%s'.", version));
@@ -86,13 +116,19 @@ public class GradleVersion implements Comparable<GradleVersion> {
             try {
                 snapshot = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(matcher.group(7)).getTime();
             } catch (ParseException e) {
-                throw UncheckedException.asUncheckedException(e);
+                throw UncheckedException.throwAsUncheckedException(e);
             }
         } else {
             snapshot = null;
         }
     }
 
+    private String formatBuildTime(Date buildTime) {
+        DateFormat format = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);
+        format.setTimeZone(TimeZone.getTimeZone("UTC"));
+        return format.format(buildTime);
+    }
+
     @Override
     public String toString() {
         return String.format("Gradle %s", version);
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/HashUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/HashUtil.java
deleted file mode 100644
index e596238..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/HashUtil.java
+++ /dev/null
@@ -1,69 +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.util;
-
-import org.gradle.api.UncheckedIOException;
-
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.io.File;
-import java.io.InputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-
-/**
- * @author Hans Dockter
- */
-public class HashUtil {
-    public static String createHash(String scriptText) {
-        MessageDigest messageDigest;
-        try {
-            messageDigest = MessageDigest.getInstance("MD5");
-        } catch (NoSuchAlgorithmException e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-        messageDigest.update(scriptText.getBytes());
-        return new BigInteger(1, messageDigest.digest()).toString(32);
-    }
-
-    public static byte[] createHash(File file) {
-        MessageDigest messageDigest;
-        try {
-            messageDigest = MessageDigest.getInstance("MD5");
-        } catch (NoSuchAlgorithmException e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-        try {
-            byte[] buffer = new byte[4096];
-            InputStream instr = new FileInputStream(file);
-            try {
-                while (true) {
-                    int nread = instr.read(buffer);
-                    if (nread < 0) {
-                        break;
-                    }
-                    messageDigest.update(buffer, 0, nread);
-                }
-            } finally {
-                instr.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        return messageDigest.digest();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java b/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
index fd4b9dc..fb5d7a7 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
@@ -26,13 +26,13 @@ public class JavaMethod<T, R> {
     private final Method method;
     private final Class<R> returnType;
 
-    public JavaMethod(Class<T> target, Class<R> returnType, String name, Class<?> ... paramTypes) {
+    private JavaMethod(Class<T> target, Class<R> returnType, String name, Class<?>... paramTypes) {
         this.returnType = returnType;
         method = findMethod(target, name, paramTypes);
         method.setAccessible(true);
     }
 
-    public JavaMethod(Class<T> target, Class<R> returnType, Method method) {
+    private JavaMethod(Class<T> target, Class<R> returnType, Method method) {
         this.returnType = returnType;
         this.method = method;
         method.setAccessible(true);
@@ -64,4 +64,12 @@ public class JavaMethod<T, R> {
             throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), e);
         }
     }
+
+    public static <T, R> JavaMethod<T, R> create(Class<T> target, Class<R> returnType, String name, Class<?>... paramTypes) {
+        return new JavaMethod<T, R>(target, returnType, name, paramTypes);
+    }
+
+    public static <T, R> JavaMethod<T, R> create(Class<T> target, Class<R> returnType, Method method) {
+        return new JavaMethod<T, R>(target, returnType, method);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/JavaReflectionUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/JavaReflectionUtil.java
new file mode 100644
index 0000000..d4f5178
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/JavaReflectionUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.util;
+
+import org.apache.commons.lang.reflect.FieldUtils;
+import org.gradle.internal.UncheckedException;
+
+public class JavaReflectionUtil {
+    // TODO: use setter instead of field
+    public static void setProperty(Object target, String property, Object value) {
+        try {
+            FieldUtils.writeDeclaredField(target, property, value, true);
+        } catch (IllegalAccessException e) {
+            throw new UncheckedException(e);
+        }
+    }
+
+    // TODO: use getter instead of field
+    public static Object getProperty(Object target, String property) {
+        try {
+            return FieldUtils.readDeclaredField(target, property, true);
+        } catch (IllegalAccessException e) {
+            throw new UncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
index 1932538..d8f2d19 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2012 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.
@@ -16,103 +16,82 @@
 
 package org.gradle.util;
 
-import org.apache.tools.ant.util.JavaEnvUtils;
+import org.gradle.internal.jvm.JavaHomeException;
+import org.gradle.internal.jvm.JavaInfo;
 
 import java.io.File;
-import java.util.HashMap;
 import java.util.Map;
 
-public class Jvm {
-    private final OperatingSystem os;
+ at Deprecated
+public class Jvm implements JavaInfo {
+    private final org.gradle.internal.jvm.Jvm jvm;
+
+    public Jvm(org.gradle.internal.jvm.Jvm current) {
+        this.jvm = current;
+    }
 
     public static Jvm current() {
-        String vendor = System.getProperty("java.vm.vendor");
-        if (vendor.toLowerCase().startsWith("apple inc.")) {
-            return new AppleJvm(OperatingSystem.current());
-        }
-        return new Jvm(OperatingSystem.current());
+        DeprecationLogger.nagUserWith("The class org.gradle.util.Jvm has been deprecated and will be removed in the next version of Gradle.");
+        return new Jvm(org.gradle.internal.jvm.Jvm.current());
     }
 
-    Jvm(OperatingSystem os) {
-        this.os = os;
+    public File getJavaExecutable() throws JavaHomeException {
+        return jvm.getJavaExecutable();
     }
 
-    @Override
-    public String toString() {
-        return String.format("%s (%s %s)", System.getProperty("java.version"), System.getProperty("java.vm.vendor"), System.getProperty("java.vm.version"));
+    public File getJavadocExecutable() throws JavaHomeException {
+        return jvm.getJavadocExecutable();
     }
 
-    public File getJavaExecutable() {
-        return new File(JavaEnvUtils.getJdkExecutable("java"));
+    public File getExecutable(String name) throws JavaHomeException {
+        return jvm.getExecutable(name);
     }
 
-    public File getJavadocExecutable() {
-        return new File(JavaEnvUtils.getJdkExecutable("javadoc"));
+    public boolean isJava5() {
+        return jvm.isJava5();
     }
 
-    public File getJpsExecutable() {
-        return new File(JavaEnvUtils.getJdkExecutable("jps"));
+    public boolean isJava6() {
+        return jvm.isJava6();
+    }
+
+    public boolean isJava7() {
+        return jvm.isJava7();
     }
 
     public boolean isJava5Compatible() {
-        return System.getProperty("java.version").startsWith("1.5") || isJava6Compatible();
+        return jvm.isJava5Compatible();
     }
 
     public boolean isJava6Compatible() {
-        return System.getProperty("java.version").startsWith("1.6");
+        return jvm.isJava6Compatible();
     }
 
     public File getJavaHome() {
-        File toolsJar = getToolsJar();
-        return toolsJar == null ? getDefaultJavaHome() : toolsJar.getParentFile().getParentFile();
+        return jvm.getJavaHome();
     }
 
-    private File getDefaultJavaHome() {
-        return GFileUtils.canonicalise(new File(System.getProperty("java.home")));
+    public File getRuntimeJar() {
+        return jvm.getRuntimeJar();
     }
 
     public File getToolsJar() {
-        File javaHome = getDefaultJavaHome();
-        File toolsJar = new File(javaHome, "lib/tools.jar");
-        if (toolsJar.exists()) {
-            return toolsJar;
-        }
-        if (javaHome.getName().equalsIgnoreCase("jre")) {
-            javaHome = javaHome.getParentFile();
-            toolsJar = new File(javaHome, "lib/tools.jar");
-            if (toolsJar.exists()) {
-                return toolsJar;
-            }
-        }
-        if (javaHome.getName().matches("jre\\d+") && os.isWindows()) {
-            javaHome = new File(javaHome.getParentFile(), String.format("jdk%s", System.getProperty("java.version")));
-            toolsJar = new File(javaHome, "lib/tools.jar");
-            if (toolsJar.exists()) {
-                return toolsJar;
-            }
-        }
-        return null;
+        return jvm.getToolsJar();
     }
 
     public Map<String, ?> getInheritableEnvironmentVariables(Map<String, ?> envVars) {
-        return envVars;
-    }
-
-    static class AppleJvm extends Jvm {
-        AppleJvm(OperatingSystem os) {
-            super(os);
-        }
-
-        @Override
-        public Map<String, ?> getInheritableEnvironmentVariables(Map<String, ?> envVars) {
-            Map<String, Object> vars = new HashMap<String, Object>();
-            for (Map.Entry<String, ?> entry : envVars.entrySet()) {
-                if (entry.getKey().matches("APP_NAME_\\d+") || entry.getKey().matches("JAVA_MAIN_CLASS_\\d+")) {
-                    continue;
-                }
-                vars.put(entry.getKey(), entry.getValue());
-            }
-            return vars;
-        }
+        return jvm.getInheritableEnvironmentVariables(envVars);
+    }
+
+    public boolean getSupportsAppleScript() {
+        return jvm.getSupportsAppleScript();
+    }
+
+    public boolean isIbmJvm() {
+        return jvm.isIbmJvm();
+    }
+    
+    public String toString(){
+        return jvm.toString();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java b/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
index d541d87..cbe746a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
@@ -16,6 +16,7 @@
 package org.gradle.util;
 
 import org.gradle.api.Action;
+import org.gradle.internal.SystemProperties;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java
index e1767d7..d86da52 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java
@@ -23,22 +23,28 @@ import java.util.concurrent.CopyOnWriteArrayList;
 /**
  * A {@code ClassLoader} which delegates to multiple parent ClassLoaders.
  */
-public class MultiParentClassLoader extends ClassLoader {
+public class MultiParentClassLoader extends ClassLoader implements ClasspathSource {
     private final List<ClassLoader> parents;
     private final JavaMethod<ClassLoader, Package[]> getPackagesMethod;
     private final JavaMethod<ClassLoader, Package> getPackageMethod;
 
     public MultiParentClassLoader(ClassLoader... parents) {
-        super(parents.length == 0 ? null : parents[0]);
+        super(null);
         this.parents = new CopyOnWriteArrayList<ClassLoader>(Arrays.asList(parents));
-        getPackagesMethod = new JavaMethod<ClassLoader, Package[]>(ClassLoader.class, Package[].class, "getPackages");
-        getPackageMethod = new JavaMethod<ClassLoader, Package>(ClassLoader.class, Package.class, "getPackage", String.class);
+        getPackagesMethod = JavaMethod.create(ClassLoader.class, Package[].class, "getPackages");
+        getPackageMethod = JavaMethod.create(ClassLoader.class, Package.class, "getPackage", String.class);
     }
 
     public void addParent(ClassLoader parent) {
         parents.add(parent);
     }
 
+    public void collectClasspath(Collection<? super URL> classpath) {
+        for (ClassLoader parent : parents) {
+            new ClassLoaderBackedClasspathSource(parent).collectClasspath(classpath);
+        }
+    }
+
     @Override
     protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
         for (ClassLoader parent : parents) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java
new file mode 100755
index 0000000..364a1eb
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.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.util;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+
+public class MutableURLClassLoader extends URLClassLoader {
+    public MutableURLClassLoader(ClassLoader parent, URL... urls) {
+        super(urls, parent);
+    }
+
+    public MutableURLClassLoader(ClassLoader parent, Collection<URL> urls) {
+        super(urls.toArray(new URL[urls.size()]), parent);
+    }
+
+    public MutableURLClassLoader(ClassLoader parent, ClassPath classPath) {
+        super(classPath.getAsURLArray(), parent);
+    }
+
+    @Override
+    public void addURL(URL url) {
+        super.addURL(url);
+    }
+
+    public void addURLs(Iterable<URL> urls) {
+        for (URL url : urls) {
+            addURL(url);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java
deleted file mode 100644
index f5c83c8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java
+++ /dev/null
@@ -1,52 +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.util;
-
-import org.gradle.api.Action;
-import org.gradle.listener.ActionBroadcast;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Collection;
-
-public class ObservableUrlClassLoader extends URLClassLoader {
-    private final ActionBroadcast<ObservableUrlClassLoader> broadcast = new ActionBroadcast<ObservableUrlClassLoader>();
-
-    public ObservableUrlClassLoader(ClassLoader parent, URL... urls) {
-        super(urls, parent);
-    }
-
-    public ObservableUrlClassLoader(ClassLoader parent, Collection<URL> urls) {
-        super(urls.toArray(new URL[urls.size()]), parent);
-    }
-
-    public void whenUrlAdded(Action<? super ObservableUrlClassLoader> action) {
-        broadcast.add(action);
-    }
-
-    @Override
-    public void addURL(URL url) {
-        super.addURL(url);
-        broadcast.execute(this);
-    }
-
-    public void addURLs(Iterable<URL> urls) {
-        for (URL url : urls) {
-            addURL(url);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/OperatingSystem.java b/subprojects/core/src/main/groovy/org/gradle/util/OperatingSystem.java
deleted file mode 100644
index 948af91..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/OperatingSystem.java
+++ /dev/null
@@ -1,120 +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.util;
-
-public class OperatingSystem {
-    private static final OperatingSystem WINDOWS = new OperatingSystem() {
-        @Override
-        public boolean isCaseSensitiveFileSystem() {
-            return false;
-        }
-
-        @Override
-        public boolean isWindows() {
-            return true;
-        }
-
-        @Override
-        public String getScriptName(String scriptPath) {
-            if (scriptPath.toLowerCase().endsWith(".bat")) {
-                return scriptPath;
-            }
-            return scriptPath + ".bat";
-        }
-
-        @Override
-        public String getNativePrefix() {
-            String arch = System.getProperty("os.arch");
-            if ("i386".equals(arch)) {
-                arch = "x86";
-            }
-            return  "win32-" + arch;
-        }
-    };
-
-    private static final OperatingSystem OS_X = new OperatingSystem() {
-        @Override
-        public boolean isCaseSensitiveFileSystem() {
-            return false;
-        }
-
-        @Override
-        public String getNativePrefix() {
-            return "darwin";
-        }
-    };
-
-    private static final OperatingSystem OTHER = new OperatingSystem();
-    private static final OperatingSystem CURRENT;
-
-    static {
-        String osName = System.getProperty("os.name").toLowerCase();
-        if (osName.contains("windows")) {
-            CURRENT = WINDOWS;
-        } else if (osName.contains("mac os x")) {
-            CURRENT = OS_X;
-        } else {
-            CURRENT = OTHER;
-        }
-    }
-
-    public static OperatingSystem current() {
-        return CURRENT;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s %s %s", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
-    }
-
-    public boolean isWindows() {
-        return false;
-    }
-
-    public boolean isUnix() {
-        // Not quite true
-        return !isWindows();
-    }
-
-    public boolean isCaseSensitiveFileSystem() {
-        return true;
-    }
-
-    public String getScriptName(String scriptPath) {
-        return scriptPath;
-    }
-
-    public String getNativePrefix() {
-        String name = System.getProperty("os.name");
-        String arch = System.getProperty("os.arch");
-        String osPrefix = name.toLowerCase();
-        if ("x86".equals(arch)) {
-            arch = "i386";
-        }
-        if ("x86_64".equals(arch)) {
-            arch = "amd64";
-        }
-        if ("powerpc".equals(arch)) {
-            arch = "ppc";
-        }
-        int space = osPrefix.indexOf(" ");
-        if (space != -1) {
-            osPrefix = osPrefix.substring(0, space);
-        }
-        osPrefix += "-" + arch;
-        return osPrefix;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/PosixUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/PosixUtil.java
deleted file mode 100644
index 96c90a8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/PosixUtil.java
+++ /dev/null
@@ -1,75 +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.util;
-
-import org.jruby.ext.posix.POSIX;
-import org.jruby.ext.posix.POSIXFactory;
-import org.jruby.ext.posix.POSIXHandler;
-
-import java.io.File;
-import java.io.InputStream;
-import java.io.PrintStream;
-
-public class PosixUtil {
-    private static final POSIX POSIX = POSIXFactory.getPOSIX(new POSIXHandlerImpl(), true);
-
-    public static POSIX current() {
-        return POSIX;
-    }
-    
-    private static class POSIXHandlerImpl implements POSIXHandler {
-        public void error(POSIX.ERRORS errors, String message) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void unimplementedError(String message) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void warn(WARNING_ID warningId, String message, Object... objects) {
-        }
-
-        public boolean isVerbose() {
-            return false;
-        }
-
-        public File getCurrentWorkingDirectory() {
-            throw new UnsupportedOperationException();
-        }
-
-        public String[] getEnv() {
-            throw new UnsupportedOperationException();
-        }
-
-        public InputStream getInputStream() {
-            return System.in;
-        }
-
-        public PrintStream getOutputStream() {
-            return System.out;
-        }
-
-        public int getPID() {
-            throw new UnsupportedOperationException();
-        }
-
-        public PrintStream getErrorStream() {
-            return System.err;
-        }
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy b/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
index cdbe9ac..e27684d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
@@ -19,23 +19,47 @@ package org.gradle.util
  * @author Hans Dockter
  */
 class ReflectionUtil {
-    public static Object invoke(Object object, String method, Object ... params) {
-        return object.invokeMethod(method, params)
+    static Object invoke(Object object, String method, Object... params) {
+        object.invokeMethod(method, params)
     }
 
-    static <T> T newInstance(Class cl, Object ... args) {
-        return cl.newInstance(args)
+    static Object getProperty(Object object, String property) {
+        object."$property"
     }
 
-    static Object getProperty(def object, String property) {
-        object.getProperty(property)
+    static void setProperty(Object object, String property, Object value) {
+        object."$property" = value
     }
 
-    static void setProperty(def object, String property, Object value) {
-        object."$property" = value
+    static boolean hasProperty(Object object, String property) {
+        object.metaClass.hasProperty(object, property) != null
+    }
+
+    static boolean isClassAvailable(String className) {
+        try {
+            ReflectionUtil.classLoader.loadClass(className)
+            return true
+        } catch (ClassNotFoundException e) {
+            return false
+        }
     }
 
-    static boolean hasProperty(def object, String property) {
-        return object.metaClass.hasProperty(object, property)
+    static Class<?> getWrapperTypeForPrimitiveType(Class<?> type) {
+        if (type == Boolean.TYPE) {
+            return Boolean.class;
+        } else if (type == Long.TYPE) {
+            return Long.class;
+        } else if (type == Integer.TYPE) {
+            return Integer.class;
+        } else if (type == Short.TYPE) {
+            return Short.class;
+        } else if (type == Byte.TYPE) {
+            return Byte.class;
+        } else if (type == Float.TYPE) {
+            return Float.class;
+        } else if (type == Double.TYPE) {
+            return Double.class;
+        }
+        throw new IllegalArgumentException(String.format("Don't know how wrapper type for primitive type %s.", type));
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ServiceLocator.java b/subprojects/core/src/main/groovy/org/gradle/util/ServiceLocator.java
new file mode 100644
index 0000000..00f847c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ServiceLocator.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2011 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.util;
+
+import org.gradle.api.internal.DirectInstantiator;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.UnknownServiceException;
+import org.gradle.api.internal.Instantiator;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Uses the Jar service resource specification to locate service implementations.
+ */
+public class ServiceLocator implements ServiceRegistry {
+    private final ClassLoader classLoader;
+    private final Map<Class<?>, Object> implementations = new ConcurrentHashMap<Class<?>, Object>();
+
+    public ServiceLocator(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public <T> T get(Class<T> serviceType) throws UnknownServiceException {
+        synchronized (implementations) {
+            T implementation = serviceType.cast(implementations.get(serviceType));
+            if (implementation == null) {
+                implementation = getFactory(serviceType).create();
+                implementations.put(serviceType, implementation);
+            }
+            return implementation;
+        }
+    }
+
+    public <T> ServiceFactory<T> getFactory(final Class<T> serviceType) throws UnknownServiceException {
+        ServiceFactory<T> factory = findFactory(serviceType);
+        if (factory == null) {
+            throw new UnknownServiceException(serviceType, String.format("Could not find meta-data resource 'META-INF/services/%s' for service '%s'.", serviceType.getName(), serviceType.getName()));
+        }
+        return factory;
+    }
+
+    /**
+     * Locates a factory for a given service. Returns null when no service implementation is available.
+     */
+    public <T> ServiceFactory<T> findFactory(Class<T> serviceType) {
+        Class<? extends T> implementationClass = findServiceImplementationClass(serviceType);
+        if (implementationClass == null) {
+            return null;
+        }
+        return new ServiceFactory<T>(serviceType, implementationClass);
+    }
+
+    public <T> T newInstance(Class<T> type) throws UnknownServiceException {
+        return getFactory(type).create();
+    }
+
+    <T> Class<? extends T> findServiceImplementationClass(Class<T> serviceType) {
+        String implementationClassName;
+        try {
+            implementationClassName = findServiceImplementationClassName(serviceType);
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not determine implementation class for service '%s'.", serviceType.getName()), e);
+        }
+        if (implementationClassName == null) {
+            return null;
+        }
+        try {
+            Class<?> implClass = classLoader.loadClass(implementationClassName);
+            if (!serviceType.isAssignableFrom(implClass)) {
+                throw new RuntimeException(String.format("Implementation class '%s' is not assignable to service class '%s'.", implementationClassName, serviceType.getName()));
+            }
+            return implClass.asSubclass(serviceType);
+        } catch (Throwable t) {
+            throw new RuntimeException(String.format("Could not load implementation class '%s' for service '%s'.", implementationClassName, serviceType.getName()), t);
+        }
+    }
+
+    private String findServiceImplementationClassName(Class<?> serviceType) throws IOException {
+        String resourceName = "META-INF/services/" + serviceType.getName();
+        URL resource = classLoader.getResource(resourceName);
+        if (resource == null) {
+            return null;
+        }
+
+        InputStream inputStream = resource.openStream();
+        try {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                line = line.replaceAll("#.*", "").trim();
+                if (line.length() > 0) {
+                    return line;
+                }
+            }
+        } finally {
+            inputStream.close();
+        }
+        throw new RuntimeException(String.format("No implementation class for service '%s' specified in resource '%s'.", serviceType.getName(), resource));
+    }
+
+    public static class ServiceFactory<T> implements Factory<T> {
+        private final Class<T> serviceType;
+        private final Class<? extends T> implementationClass;
+
+        public ServiceFactory(Class<T> serviceType, Class<? extends T> implementationClass) {
+            this.serviceType = serviceType;
+            this.implementationClass = implementationClass;
+        }
+
+        public Class<? extends T> getImplementationClass() {
+            return implementationClass;
+        }
+
+        public T create() {
+            return newInstance();
+        }
+
+        public T newInstance(Object... params) {
+            Instantiator instantiator = new DirectInstantiator();
+            try {
+                return instantiator.newInstance(implementationClass, params);
+            } catch (Throwable t) {
+                throw new RuntimeException(String.format("Could not create an implementation of service '%s'.", serviceType.getName()), t);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/StdinSwapper.java b/subprojects/core/src/main/groovy/org/gradle/util/StdinSwapper.java
new file mode 100644
index 0000000..910175b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/StdinSwapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.util;
+
+import org.gradle.api.Action;
+import java.util.concurrent.Callable;
+
+import java.io.InputStream;
+
+public class StdinSwapper extends Swapper<InputStream> {
+
+    public StdinSwapper() {
+        super(
+            new Callable<InputStream>() {
+                public InputStream call() {
+                    return System.in;
+                }
+            },
+            new Action<InputStream>() {
+                public void execute(InputStream newValue) {
+                    System.setIn(newValue);
+                }
+            }
+        );
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/StdoutSwapper.java b/subprojects/core/src/main/groovy/org/gradle/util/StdoutSwapper.java
new file mode 100644
index 0000000..5118100
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/StdoutSwapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.util;
+
+import org.gradle.api.Action;
+import java.util.concurrent.Callable;
+
+import java.io.PrintStream;
+
+public class StdoutSwapper extends Swapper<PrintStream> {
+
+    public StdoutSwapper() {
+        super(
+            new Callable<PrintStream>() {
+                public PrintStream call() {
+                    return System.out;
+                }
+            },
+            new Action<PrintStream>() {
+                public void execute(PrintStream newValue) {
+                    System.setOut(newValue);
+                }
+            }
+        );
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Swapper.java b/subprojects/core/src/main/groovy/org/gradle/util/Swapper.java
new file mode 100644
index 0000000..975f60e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Swapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.util;
+
+import org.gradle.api.Action;
+import java.util.concurrent.Callable;
+
+/**
+ * Generic utility for temporarily changing something.
+ */
+public class Swapper<T> {
+
+    private final Callable<? extends T> getter;
+    private final Action<? super T> setter;
+
+    public Swapper(Callable<? extends T> getter, Action<? super T> setter) {
+        this.getter = getter;
+        this.setter = setter;
+    }
+
+    public <Y extends T, N> N swap(Y value, Callable<N> whileSwapped) throws Exception {
+        T orginalValue = getter.call();
+        setter.execute(value);
+        try {
+            return whileSwapped.call();
+        } finally {
+            setter.execute(orginalValue);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/SystemProperties.java b/subprojects/core/src/main/groovy/org/gradle/util/SystemProperties.java
deleted file mode 100644
index a7155d7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/SystemProperties.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2011 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.util;
-
-/**
- * Provides access to frequently used system properties.
- */
-public class SystemProperties {
-    public static String getLineSeparator() {
-        return System.getProperty("line.separator");
-    }
-
-    public static String getJavaIoTmpDir() {
-        return System.getProperty("java.io.tmpdir");
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
index 493f064..5c6cc32 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
@@ -16,6 +16,9 @@
 
 package org.gradle.util;
 
+import org.apache.commons.lang.StringEscapeUtils;
+import org.gradle.internal.SystemProperties;
+
 public class TextUtil {
     /**
      * Returns the line separator for Windows.
@@ -42,13 +45,34 @@ public class TextUtil {
      * Converts all line separators in the specified string to the specified line separator.
      */
     public static String convertLineSeparators(String str, String sep) {
-        return str.replaceAll("\r\n|\r|\n", sep);
+        return str == null ? null : str.replaceAll("\r\n|\r|\n", sep);
     }
 
     /**
      * Converts all line separators in the specified string to the the platform's line separator.
      */
     public static String toPlatformLineSeparators(String str) {
-        return convertLineSeparators(str, getPlatformLineSeparator());
+        return str == null ? null : convertLineSeparators(str, getPlatformLineSeparator());
+    }
+
+    /**
+     * <p>Escapes the toString() representation of {@code obj} for use in a literal string.</p>
+     *
+     * <p>This is useful for interpolating variables into script strings, as well as in other situations.</p>
+     */
+    public static String escapeString(Object obj) {
+        return obj == null ? null : StringEscapeUtils.escapeJava(obj.toString());
+    }
+
+    /**
+     * Tells whether the specified string contains any whitespace characters.
+     */
+    public static boolean containsWhitespace(String str) {
+        for (int i = 0; i < str.length(); i++) {
+            if (Character.isWhitespace(str.charAt(i))) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TreeVisitor.java b/subprojects/core/src/main/groovy/org/gradle/util/TreeVisitor.java
new file mode 100644
index 0000000..3a7e620
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/TreeVisitor.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.util;
+
+/**
+ * Visits a tree with nodes of type T.
+ */
+public class TreeVisitor<T> {
+    /**
+     * Visits a node of the tree.
+     */
+    public void node(T node) {
+    }
+
+    /**
+     * Starts visiting the children of the most recently visited node.
+     */
+    public void startChildren() {
+    }
+
+    /**
+     * Finishes visiting the children of the most recently started node.
+     */
+    public void endChildren() {
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/UUIDGenerator.java b/subprojects/core/src/main/groovy/org/gradle/util/UUIDGenerator.java
new file mode 100644
index 0000000..6c6f7d6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/UUIDGenerator.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.util;
+
+import java.util.UUID;
+
+public class UUIDGenerator implements IdGenerator<UUID> {
+    public UUID generateId() {
+        return UUID.randomUUID();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/UncheckedException.java b/subprojects/core/src/main/groovy/org/gradle/util/UncheckedException.java
deleted file mode 100644
index f6b48ad..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/UncheckedException.java
+++ /dev/null
@@ -1,33 +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.util;
-
-/**
- * Wraps a checked exception. Carries no other context.
- */
-public final class UncheckedException extends RuntimeException {
-    public UncheckedException(Throwable cause) {
-        super(cause);
-    }
-
-    public static RuntimeException asUncheckedException(Throwable t) {
-        if (t instanceof RuntimeException) {
-            return (RuntimeException) t;
-        }
-        return new UncheckedException(t);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/WrapUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/WrapUtil.java
index a59c062..84588e6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/WrapUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/WrapUtil.java
@@ -15,6 +15,9 @@
  */
 package org.gradle.util;
 
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+
 import java.util.*;
 
 /**
@@ -32,6 +35,13 @@ public class WrapUtil {
     }
 
     /**
+     * Wraps the given items in a mutable domain object set.
+     */
+    public static <T> DomainObjectSet<T> toDomainObjectSet(Class<T> type, T... items) {
+        return new DefaultDomainObjectSet<T>(type, toSet(items));
+    }
+
+    /**
      * Wraps the given items in a mutable ordered set.
      */
     public static <T> Set<T> toLinkedSet(T... items) {
@@ -117,4 +127,8 @@ public class WrapUtil {
     public static <T> T[] toArray(T... items) {
         return items;
     }
+    
+    public static <T> Set<T> asSet(Collection<T> c) {
+        return new LinkedHashSet<T>(c);
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashUtil.java
new file mode 100644
index 0000000..ea6db61
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashUtil.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util.hash;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.UncheckedException;
+
+import java.io.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HashUtil {
+    public static HashValue createHash(String scriptText, String algorithm) {
+        MessageDigest messageDigest = createMessageDigest(algorithm);
+        messageDigest.update(scriptText.getBytes());
+        return new HashValue(messageDigest.digest());
+    }
+
+    public static HashValue createHash(File file, String algorithm) {
+        try {
+            return createHash(new FileInputStream(file), algorithm);
+        } catch (FileNotFoundException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static HashValue createHash(InputStream instr, String algorithm) {
+        MessageDigest messageDigest = createMessageDigest(algorithm);
+        try {
+            byte[] buffer = new byte[4096];
+            try {
+                while (true) {
+                    int nread = instr.read(buffer);
+                    if (nread < 0) {
+                        break;
+                    }
+                    messageDigest.update(buffer, 0, nread);
+                }
+            } finally {
+                instr.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return new HashValue(messageDigest.digest());
+    }
+
+    private static MessageDigest createMessageDigest(String algorithm) {
+        try {
+            return MessageDigest.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public static String createCompactMD5(String scriptText) {
+        return createHash(scriptText, "MD5").asCompactString();
+    }
+
+    public static HashValue sha1(byte[] bytes) {
+        return createHash(new ByteArrayInputStream(bytes), "SHA1");
+    }
+
+    public static HashValue sha1(InputStream inputStream) {
+        return createHash(inputStream, "SHA1");
+    }
+
+    public static HashValue sha1(File file) {
+        return createHash(file, "SHA1");
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
new file mode 100644
index 0000000..379be6e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/hash/HashValue.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.util.hash;
+
+import java.math.BigInteger;
+
+public class HashValue {
+    private final BigInteger digest;
+
+    public HashValue(byte[] digest) {
+        this.digest = new BigInteger(1, digest);
+    }
+    
+    public HashValue(String hexString) {
+        this.digest = new BigInteger(hexString, 16);
+    }
+    
+    public static HashValue parse(String inputString) {
+        if (inputString == null || inputString.length() == 0) {
+            return null;
+        }
+        return new HashValue(parseInput(inputString));
+    }
+    
+    private static String parseInput(String inputString) {
+        if (inputString == null) {
+            return null;
+        }
+        String cleaned = inputString.trim().toLowerCase();
+        int spaceIndex = cleaned.indexOf(' ');
+        if (spaceIndex != -1) {
+            String firstPart = cleaned.substring(0, spaceIndex);
+            if (firstPart.startsWith("md") || firstPart.startsWith("sha")) {
+                cleaned = cleaned.substring(cleaned.lastIndexOf(' ') + 1);
+            } else if (firstPart.endsWith(":")) {
+                cleaned = cleaned.substring(spaceIndex + 1).replace(" ", "");
+            } else {
+                cleaned = cleaned.substring(0, spaceIndex);
+            }
+        }
+        return cleaned;
+    }
+
+    public String asCompactString() {
+        return digest.toString(32);
+    }
+    
+    public String asHexString() {
+        return digest.toString(16);        
+    }
+
+    public byte[] asByteArray() {
+        return digest.toByteArray();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof HashValue)) {
+            return false;
+        }
+
+        HashValue otherHashValue = (HashValue) other;
+        return digest.equals(otherHashValue.digest);
+    }
+
+    @Override
+    public int hashCode() {
+        return digest.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/internal/ArgumentsSplitter.java b/subprojects/core/src/main/groovy/org/gradle/util/internal/ArgumentsSplitter.java
new file mode 100644
index 0000000..134a35a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/internal/ArgumentsSplitter.java
@@ -0,0 +1,63 @@
+/*
+ * 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.util.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ArgumentsSplitter {
+
+    /**
+     * Splits the arguments string (for example, a program command line) into a collection.
+     * Only supports space-delimited and/or quoted command line arguments. This currently does not handle escaping characters such as quotes.
+     *
+     * @param arguments the arguments, for example command line args.
+     * @return separate command line arguments.
+     */
+    public static List<String> split(String arguments) {
+        List<String> commandLineArguments = new ArrayList<String>();
+
+        Character currentQuote = null;
+        StringBuilder currentOption = new StringBuilder();
+        boolean hasOption = false;
+
+        for (int index = 0; index < arguments.length(); index++) {
+            char c = arguments.charAt(index);
+            if (currentQuote == null && Character.isWhitespace(c)) {
+                if (hasOption) {
+                    commandLineArguments.add(currentOption.toString());
+                    hasOption = false;
+                    currentOption.setLength(0);
+                }
+            } else if (currentQuote == null && (c == '"' || c == '\'')) {
+                currentQuote = c;
+                hasOption = true;
+            } else if (currentQuote != null && c == currentQuote) {
+                currentQuote = null;
+            } else {
+                currentOption.append(c);
+                hasOption = true;
+            }
+        }
+
+        if (hasOption) {
+            commandLineArguments.add(currentOption.toString());
+        }
+
+        return commandLineArguments;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java b/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java
new file mode 100644
index 0000000..06a7a8a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/internal/LimitedDescription.java
@@ -0,0 +1,60 @@
+/*
+ * 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.util.internal;
+
+import com.google.common.collect.Lists;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Discards old entries when current count is over the limit.
+ * <p>
+ * by Szczepan Faber, created at: 2/28/12
+ */
+public class LimitedDescription {
+
+    private final LinkedList content;
+    private final int maxItems;
+
+    public LimitedDescription(int maxItems) {
+        this.maxItems = maxItems;
+        this.content = new LinkedList();
+    }
+
+    public LimitedDescription append(String line) {
+        content.add(0, line);
+        if (content.size() > maxItems) {
+            content.removeLast();
+        }
+        return this;
+    }
+    
+    public String toString() {
+        if (content.size() == 0) {
+            return "<<empty>>";
+        }
+
+        StringBuilder out = new StringBuilder();
+        List reversed = Lists.reverse(content);
+        for (Object item : reversed) {
+            out.append(item).append("\n");
+        }
+
+        return out.toString();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt b/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
index af4bd51..9dc6cfb 100644
--- a/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
+++ b/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
@@ -7,6 +7,7 @@ import org.gradle.api.artifacts.maven.*
 import org.gradle.api.artifacts.specs.*
 import org.gradle.api.execution.*
 import org.gradle.api.file.*
+import org.gradle.api.resources.*
 import org.gradle.api.initialization.*
 import org.gradle.api.invocation.*
 import org.gradle.api.java.archives.*
@@ -14,7 +15,9 @@ import org.gradle.api.logging.*
 import org.gradle.api.plugins.*
 import org.gradle.plugins.ide.eclipse.*
 import org.gradle.plugins.ide.idea.*
+import org.gradle.plugins.jetty.*
 import org.gradle.api.plugins.quality.*
+import org.gradle.api.plugins.announce.*
 import org.gradle.api.specs.*
 import org.gradle.api.tasks.*
 import org.gradle.api.tasks.bundling.*
diff --git a/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html b/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html
index a37550e..5c3531b 100644
--- a/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html
+++ b/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html
@@ -1,130 +1,115 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
-    <title>Profile</title>
-    <style type="text/css">
-        .date { text-align:right; }
-        td { vertical-align:top;}
-        .et { text-align:right; }
-        table table { border: solid black 1px;}
-        table table tr:nth-child(even) { background-color: #ddf; }
-        td {
-            vertical-align:top;
-            padding-left:5px;
-            padding-right:5px;
-            white-space:nowrap;
-        }
-        h1 {
-            font-size:100%;
-            font-family:sans-serif;
-        }
-        .indentPath { padding-left: 3em; }
-        .heading {
-            text-align:center;
-            font-family:sans-serif;
-            font-weight:bold;
-        }
-    </style>
-
-</head>
 <body>
-    <div class="header">
-        <span></span>
-        <span class="date"></span>
+<div id="tabs">
+    <ul class="tabLinks">
+        <li><a href="#tab0">Summary</a></li>
+        <li><a href="#tab1">Configuration</a></li>
+        <li><a href="#tab2">Dependency Resolution</a></li>
+        <li><a href="#tab3">Task Execution</a></li>
+    </ul>
+    <div class="tab" id="tab0">
+        <h2>Summary</h2>
+        <table>
+            <thead><tr><th>Description</th><th class="numeric">Duration</th></tr></thead>
+            <tr>
+                <td>Total Build Time</td>
+                <td class="numeric">${time.format(build.elapsedTotal)}</td>
+            </tr>
+            <tr>
+                <td>Startup</td>
+                <td class="numeric">${time.format(build.elapsedStartup)}</td>
+            </tr>
+            <tr>
+                <td>Settings and BuildSrc</td>
+                <td class="numeric">${time.format(build.elapsedSettings)}</td>
+            </tr>
+            <tr>
+                <td>Loading Projects</td>
+                <td class="numeric">${time.format(build.elapsedProjectsLoading)}</td>
+            </tr>
+            <tr>
+                <td>Configuring Projects</td>
+                <td class="numeric">${time.format(build.elapsedProjectsEvaluated)}</td>
+            </tr>
+            <tr>
+                <td>Task Execution</td>
+                <td class="numeric">${time.format(build.elapsedTotalExecutionTime)}</td>
+            </tr>
+        </table>
     </div>
-    <table>
-        <tr>
-            <td>Profiled with tasks: ${build.taskDescription}</td>
-            <td> </td>
-            <td class="date">Run on: ${date.format(build.buildStarted)}</td>
-        </tr>
-        <tr>
-            <td>
-                <div id="summary">
-                    <div class="heading">Summary</div>
-                    <table cellpadding="0" cellspacing="0">
-                        <tr>
-                            <td>Total Build Time</td>
-                            <td class="et">${time.format(build.elapsedTotal)}</td>
-                        </tr>
-                        <tr>
-                            <td>Startup</td>
-                            <td class="et">${time.format(build.elapsedStartup)}</td>
-                        </tr>
-                        <tr>
-                            <td>Settings and BuildSrc</td>
-                            <td class="et">${time.format(build.elapsedSettings)}</td>
-                        </tr>
-                        <tr>
-                            <td>Loading Projects</td>
-                            <td class="et">${time.format(build.elapsedProjectsLoading)}</td>
-                        </tr>
-                        <tr>
-                            <td>Configuring Projects</td>
-                            <td class="et">${time.format(build.elapsedProjectsEvaluated)}</td>
-                        </tr>
-                        <tr>
-                            <td>Total Task Execution</td>
-                            <td class="et">${time.format(build.elapsedTotalExecutionTime)}</td>
-                        </tr>
-                    </table>
-                </div>
-            </td>
-            <td>
-                <div id="config">
-                    <div class="heading">Configuration</div>
-                    <table cellpadding="0" cellspacing="0">
-                        <%
-                            def projects = build.projects
-                            projects.sort { it. elapsedEvaluation }
-                            projects = projects.reverse()
-
-                            for (def project : projects) {
-                        %>
-                        <tr>
-                            <td>$project.path</td>
-                            <td class="et">${time.format(project.elapsedEvaluation)}</td>
-                        </tr>
-                        <% } %>
-                    </table>
-                </div>
-            </td>
-            <td>
-                <div id="execution">
-                    <div class="heading">Task Execution</div>
-                    <table cellpadding="0" cellspacing="0">
-                        <%
-                            projects.sort { it.elapsedTaskExecution }
-                            projects = projects.reverse()
-                            for (def project : projects) {
-                        %>
-                        <tr>
-                            <td>$project.path</td>
-                            <td class="et">${time.format(project.elapsedTaskExecution)}</td>
-                            <td>(total)</td>
-                        </tr>
-                        <%
-                            def profiles = project.taskProfiles
-                            profiles.sort { it.elapsedExecution }
-                            profiles = profiles.reverse()
-                            for (def task : profiles) {
-                        %>
-                        <tr>
-                            <td class="indentPath">${task.path}</td>
-                            <td class="et">${time.format(task.elapsedExecution)}</td>
-                            <td><%= task.state.getSkipped() ? task.state.skipMessage : (task.state.didWork ? ' ' : 'Did No Work')%></td>
-                        </tr>
-                        <%
-                            }
-                        %>
-                        <% } %>
-                    </table>
-                </div>
-
-            </td>
-        </tr>
-    </table>
+    <div class="tab" id="tab1">
+        <h2>Configuration</h2>
+        <table>
+            <thead><tr><th>Project</th><th class="numeric">Duration</th></tr></thead>
+            <tr>
+                <td>All projects</td>
+                <td class="numeric">${time.format(build.projectConfiguration.elapsedTime)}</td>
+            </tr>
+            <%
+            def projects = build.projectConfiguration.operations
+            projects.sort { it.elapsedTime }
+            projects = projects.reverse()
 
+            for (def project : projects) {
+            %>
+            <tr>
+                <td>$project.path</td>
+                <td class="numeric">${time.format(project.elapsedTime)}</td>
+            </tr>
+            <% } %>
+        </table>
+    </div>
+    <div class="tab" id="tab2">
+        <h2>Dependency Resolution</h2>
+        <table>
+            <thead><tr><th>Dependencies</th><th class="numeric">Duration</th></tr></thead>
+            <tr>
+                <td>All dependencies</td>
+                <td class="numeric">${time.format(build.dependencySets.elapsedTime)}</td>
+            </tr>
+            <%
+            def dependencySets = build.dependencySets.operations
+            dependencySets.sort { it.elapsedTime }
+            dependencySets = dependencySets.reverse()
+            for (def dependencySet : dependencySets) {
+            %>
+            <tr>
+                <td>$dependencySet.path</td>
+                <td class="numeric">${time.format(dependencySet.elapsedTime)}</td>
+            </tr>
+            <% } %>
+        </table>
+    </div>
+    <div class="tab" id="tab3">
+        <h2>Task Execution</h2>
+        <table>
+            <thead><tr><th>Task</th><th class="numeric">Duration</th><th>Result</th></tr></thead>
+            <%
+            projects = build.projects
+            projects.sort { it.tasks.elapsedTime }
+            projects = projects.reverse()
+            for (def project : projects) {
+            %>
+            <tr>
+                <td>Project $project.path</td>
+                <td class="numeric">${time.format(project.tasks.elapsedTime)}</td>
+                <td>(total)</td>
+            </tr>
+            <%
+            def profiles = project.tasks.operations
+            profiles.sort { it.elapsedTime }
+            profiles = profiles.reverse()
+            for (def task : profiles) {
+            %>
+            <tr>
+                <td class="indentPath">${task.path}</td>
+                <td class="numeric">${time.format(task.elapsedTime)}</td>
+                <td><%= task.state.getSkipped() ? task.state.skipMessage : (task.state.didWork ? '' : 'Did No Work')%></td>
+            </tr>
+            <%
+            }
+            %>
+            <% } %>
+        </table>
+    </div>
+</div>
 </body>
-</html>
\ No newline at end of file
diff --git a/subprojects/core/src/main/resources/org/gradle/profile/style.css b/subprojects/core/src/main/resources/org/gradle/profile/style.css
new file mode 100644
index 0000000..c4a4239
--- /dev/null
+++ b/subprojects/core/src/main/resources/org/gradle/profile/style.css
@@ -0,0 +1,4 @@
+
+div.tab td.indentPath {
+    padding-left: 3em;
+}
diff --git a/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css b/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css
new file mode 100644
index 0000000..e09a387
--- /dev/null
+++ b/subprojects/core/src/main/resources/org/gradle/reporting/base-style.css
@@ -0,0 +1,162 @@
+
+body {
+    margin: 0;
+    padding: 0;
+    font-family: sans-serif;
+    font-size: 12pt;
+}
+
+body, a, a:visited {
+    color: #303030;
+}
+
+#content {
+    padding-left: 50px;
+    padding-right: 50px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+}
+
+#content h1 {
+    font-size: 160%;
+    margin-bottom: 10px;
+}
+
+#footer {
+    margin-top: 100px;
+    font-size: 80%;
+    white-space: nowrap;
+}
+
+#footer, #footer a {
+    color: #a0a0a0;
+}
+
+ul {
+    margin-left: 0;
+}
+
+h1, h2, h3 {
+    white-space: nowrap;
+}
+
+h2 {
+    font-size: 120%;
+}
+
+ul.tabLinks {
+    padding-left: 0;
+    padding-top: 10px;
+    padding-bottom: 10px;
+    overflow: auto;
+    min-width: 800px;
+    width: auto !important;
+    width: 800px;
+}
+
+ul.tabLinks li {
+    float: left;
+    height: 100%;
+    list-style: none;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-top: 5px;
+    padding-bottom: 5px;
+    margin-bottom: 0;
+    -moz-border-radius: 7px;
+    border-radius: 7px;
+    margin-right: 25px;
+    border: solid 1px #d4d4d4;
+    background-color: #f0f0f0;
+    behavior: url(css3-pie-1.0beta3.htc);
+}
+
+ul.tabLinks li:hover {
+    background-color: #fafafa;
+}
+
+ul.tabLinks li.selected {
+    background-color: #c5f0f5;
+    border-color: #c5f0f5;
+}
+
+ul.tabLinks a {
+    font-size: 120%;
+    display: block;
+    outline: none;
+    text-decoration: none;
+    margin: 0;
+    padding: 0;
+}
+
+ul.tabLinks li h2 {
+    margin: 0;
+    padding: 0;
+}
+
+div.tab {
+}
+
+div.selected {
+    display: block;
+}
+
+div.deselected {
+    display: none;
+}
+
+div.tab table {
+    min-width: 350px;
+    width: auto !important;
+    width: 350px;
+    border-collapse: collapse;
+}
+
+div.tab th, div.tab table {
+    border-bottom: solid #d0d0d0 1px;
+}
+
+div.tab th {
+    text-align: left;
+    white-space: nowrap;
+    padding-left: 6em;
+}
+
+div.tab th:first-child {
+    padding-left: 0;
+}
+
+div.tab td {
+    white-space: nowrap;
+    padding-left: 6em;
+    padding-top: 5px;
+    padding-bottom: 5px;
+}
+
+div.tab td:first-child {
+    padding-left: 0;
+}
+
+div.tab td.numeric, div.tab th.numeric {
+    text-align: right;
+}
+
+span.code {
+    display: inline-block;
+    margin-top: 0em;
+    margin-bottom: 1em;
+}
+
+span.code pre {
+    font-size: 11pt;
+    padding-top: 10px;
+    padding-bottom: 10px;
+    padding-left: 10px;
+    padding-right: 10px;
+    margin: 0;
+    background-color: #f7f7f7;
+    border: solid 1px #d0d0d0;
+    min-width: 700px;
+    width: auto !important;
+    width: 700px;
+}
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/report.js b/subprojects/core/src/main/resources/org/gradle/reporting/report.js
similarity index 100%
rename from subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/report.js
rename to subprojects/core/src/main/resources/org/gradle/reporting/report.js
diff --git a/subprojects/core/src/releases.xml b/subprojects/core/src/releases.xml
new file mode 100644
index 0000000..62ae528
--- /dev/null
+++ b/subprojects/core/src/releases.xml
@@ -0,0 +1,25 @@
+<releases>
+  <next version="1.0"/>
+  <current version="${version}" build-time="${buildTime}" type="${releaseType}"/>
+  <release version="1.0-rc-3" build-time="20120430015152+0200"/>
+  <release version="1.0-rc-2" build-time="20120425015237+0200"/>
+  <release version="1.0-rc-1" build-time="20120411121324+0100"/>
+  <release version="1.0-milestone-9" build-time="20120313171009+0100"/>
+  <release version="1.0-milestone-8a" build-time="20120220185357+0100"/>
+  <release version="1.0-milestone-8" build-time="20120214022451+0100"/>
+  <release version="1.0-milestone-7" build-time="20120105102443+0000"/>
+  <release version="1.0-milestone-6" build-time="20111117065412+0100"/>
+  <release version="1.0-milestone-5" build-time="20111025055608+0200"/>
+  <release version="1.0-milestone-4" build-time="20110728103822+0200" status="broken"/>
+  <release version="1.0-milestone-3" build-time="20110425174011+1000"/>
+  <release version="1.0-milestone-2" build-time="20110407163255+1000"/>
+  <release version="1.0-milestone-1" build-time="20110227141320+1100"/>
+  <release version="0.9.2" build-time="20110123133421+1100"/>
+  <release version="0.9.1" build-time="20110102114057+1100"/>
+  <release version="0.9" build-time="20101219125006+1100"/>
+  <release version="0.9-rc-3" build-time="20101120131750+1100"/>
+  <release version="0.9-rc-2" build-time="20101027082405+1100"/>
+  <release version="0.9-rc-1" build-time="20100804080433+1100"/>
+  <release version="0.8" build-time="20090928140159+0200"/>
+  <release version="0.7" build-time="20090720085013+0200"/>
+</releases>
diff --git a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
index 64c2ce3..b426ecb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
@@ -15,27 +15,30 @@
  */
 package org.gradle
 
-import org.gradle.StartParameter.ShowStacktrace
 import org.gradle.api.GradleException
-import org.gradle.api.LocationAwareException
+import org.gradle.api.internal.LocationAwareException
 import org.gradle.api.logging.LogLevel
 import org.gradle.execution.TaskSelectionException
 import org.gradle.initialization.BuildClientMetaData
+import org.gradle.logging.LoggingConfiguration
+import org.gradle.logging.ShowStacktrace
 import org.gradle.logging.StyledTextOutputFactory
 import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.util.TreeVisitor
 import spock.lang.Specification
-import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.api.internal.MultiCauseException
+import org.gradle.api.internal.AbstractMultiCauseException
 
 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, clientMetaData)
+    final LoggingConfiguration configuration = new LoggingConfiguration()
+    final BuildExceptionReporter reporter = new BuildExceptionReporter(factory, configuration, clientMetaData)
 
     def setup() {
-        _ * factory.create(BuildExceptionReporter.class, LogLevel.ERROR) >> output
-        _ * clientMetaData.describeCommand(!null, !null) >> { args -> args[0].append("[gradle ${args[1].join(' ')}]")}
+        factory.create(BuildExceptionReporter.class, LogLevel.ERROR) >> output
+        clientMetaData.describeCommand(!null, !null) >> { args -> args[0].append("[gradle ${args[1].join(' ')}]")}
     }
 
     def doesNothingWheBuildIsSuccessful() {
@@ -53,7 +56,7 @@ class BuildExceptionReporterTest extends Specification {
 {failure}FAILURE: {normal}{failure}Build aborted because of an internal error.{normal}
 
 * What went wrong:
-Build aborted because of an unexpected internal error. Please file an issue at: http://www.gradle.org.
+Build aborted because of an unexpected internal error. Please file an issue at: http://forums.gradle.org.
 
 * Try:
 Run with {userinput}--debug{normal} option to get additional debug info.
@@ -109,7 +112,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 
 * What went wrong:
 <message>
-Cause: <cause>
+{info}> {normal}<cause>
 
 * Try:
 Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
@@ -117,7 +120,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
     }
 
     def reportsLocationAwareExceptionWithMultipleCauses() {
-        Throwable exception = exception("<location>", "<message>", new RuntimeException("<outer>"), new RuntimeException("<cause>"));
+        Throwable exception = exception("<location>", "<message>", new RuntimeException("<cause1>"), new RuntimeException("<cause2>"));
 
         expect:
         reporter.buildFinished(result(exception))
@@ -129,8 +132,34 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 
 * What went wrong:
 <message>
-Cause: <outer>
-Cause: <cause>
+{info}> {normal}<cause1>
+{info}> {normal}<cause2>
+
+* Try:
+Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
+'''
+    }
+
+    def reportsLocationAwareExceptionWithMultipleNestedCauses() {
+        def cause1 = nested("<cause1>", new RuntimeException("<cause1.1>"), new RuntimeException("<cause1.2>"))
+        def cause2 = nested("<cause2>", new RuntimeException("<cause2.1>"))
+        Throwable exception = exception("<location>", "<message>", cause1, cause2);
+
+        expect:
+        reporter.buildFinished(result(exception))
+        output.value == '''
+{failure}FAILURE: {normal}{failure}Build failed with an exception.{normal}
+
+* Where:
+<location>
+
+* What went wrong:
+<message>
+{info}> {normal}<cause1>
+   {info}> {normal}<cause1.1>
+   {info}> {normal}<cause1.2>
+{info}> {normal}<cause2>
+   {info}> {normal}<cause2.1>
 
 * Try:
 Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
@@ -150,7 +179,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 
 * What went wrong:
 <message>
-Cause: java.lang.RuntimeException (no error message)
+{info}> {normal}java.lang.RuntimeException (no error message)
 
 * Try:
 Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
@@ -158,7 +187,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
     }
 
     def showsStacktraceOfCauseOfLocationAwareException() {
-        startParameter.showStacktrace = ShowStacktrace.ALWAYS
+        configuration.showStacktrace = ShowStacktrace.ALWAYS
 
         Throwable exception = exception("<location>", "<message>", new GradleException('<failure>'))
 
@@ -172,7 +201,7 @@ Run with {userinput}--stacktrace{normal} option to get the stack trace. Run with
 
 * What went wrong:
 <message>
-Cause: <failure>
+{info}> {normal}<failure>
 
 * Try:
 Run with {userinput}--info{normal} or {userinput}--debug{normal} option to get more log output.
@@ -200,7 +229,7 @@ Run {userinput}[gradle tasks]{normal} to get a list of available tasks.
     }
 
     def reportsBuildFailureWhenShowStacktraceEnabled() {
-        startParameter.showStacktrace = ShowStacktrace.ALWAYS
+        configuration.showStacktrace = ShowStacktrace.ALWAYS
 
         GradleException exception = new GradleException('<message>')
 
@@ -222,7 +251,7 @@ org.gradle.api.GradleException: <message>
     }
 
     def reportsBuildFailureWhenShowFullStacktraceEnabled() {
-        startParameter.showStacktrace = ShowStacktrace.ALWAYS_FULL
+        configuration.showStacktrace = ShowStacktrace.ALWAYS_FULL
 
         GradleException exception = new GradleException('<message>')
 
@@ -244,7 +273,7 @@ org.gradle.api.GradleException: <message>
     }
 
     def reportsBuildFailureWhenDebugLoggingEnabled() {
-        startParameter.logLevel = LogLevel.DEBUG
+        configuration.logLevel = LogLevel.DEBUG
 
         GradleException exception = new GradleException('<message>')
 
@@ -264,22 +293,40 @@ org.gradle.api.GradleException: <message>
 
     def result(Throwable failure) {
         BuildResult result = Mock()
-        _ * result.failure >> failure
+        result.failure >> failure
         result
     }
-
-    def exception(final String location, final String message, final Throwable... causes) {
-        TestException exception = Mock()
-        _ * exception.location >> location
-        _ * exception.originalMessage >> message
-        _ * exception.reportableCauses >> (causes as List)
-        _ * exception.cause >> causes[0]
+    
+    def nested(String message, Throwable... causes) {
+        return new TestException(message, causes)
+    }
+    
+    def exception(String location, String message, Throwable... causes) {
+        LocationAwareException exception = Mock()
+        exception.location >> location
+        exception.originalMessage >> message
+        exception.cause >> causes[0]
+        exception.visitReportableCauses(!null) >> { TreeVisitor visitor ->
+            visitor.node(exception)
+            visitor.startChildren()
+            causes.each { 
+                visitor.node(it) 
+                if (it instanceof TestException) {
+                    visitor.startChildren()
+                    it.causes.each { child ->
+                        visitor.node(child)
+                    }
+                    visitor.endChildren()
+                }
+            }
+            visitor.endChildren()
+        }
         exception
     }
 }
 
-public abstract class TestException extends LocationAwareException {
-    TestException(Throwable cause, ScriptSource source, Integer lineNumber) {
-        super(cause, source, lineNumber)
+class TestException extends AbstractMultiCauseException {
+    TestException(String message, Throwable... causes) {
+        super(message, causes)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
index 6cb1c99..ee8ee06 100644
--- a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -16,26 +16,14 @@
 
 package org.gradle
 
-import static org.gradle.util.Matchers.*
-
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction
 import org.gradle.api.logging.LogLevel
-import org.gradle.execution.BuildExecuter
-import org.gradle.execution.DefaultBuildExecuter
-import org.gradle.execution.DryRunBuildExecuter
-import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.groovy.scripts.StringScriptSource
-import org.gradle.groovy.scripts.UriScriptSource
-import org.gradle.initialization.BuildFileProjectSpec
-import org.gradle.initialization.DefaultProjectSpec
-import org.gradle.initialization.ProjectDirectoryProjectSpec
-import org.gradle.initialization.ProjectSpec
+import org.gradle.util.SetSystemProperties
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.junit.Test
+import static org.gradle.util.Matchers.*
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.util.SetSystemProperties
 
 /**
  * @author Hans Dockter
@@ -51,7 +39,7 @@ class StartParameterTest {
         testObj.settingsFile = 'settingsfile' as File
         testObj.buildFile = 'buildfile' as File
         testObj.taskNames = ['a']
-        testObj.projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(true)
+        testObj.buildProjectDependencies = true
         testObj.currentDir = new File('a')
         testObj.searchUpwards = false
         testObj.projectProperties = [a: 'a']
@@ -61,6 +49,11 @@ class StartParameterTest {
         testObj.cacheUsage = CacheUsage.ON
         testObj.logLevel = LogLevel.WARN
         testObj.colorOutput = false
+        testObj.continueOnFailure = true
+        testObj.refreshOptions = RefreshOptions.fromCommandLineOptions(['dependencies'])
+        testObj.rerunTasks = true;
+        testObj.refreshDependencies = true;
+        testObj.recompileScripts = true;
 
         StartParameter startParameter = testObj.newInstance()
         assertEquals(testObj, startParameter)
@@ -72,7 +65,7 @@ class StartParameterTest {
         assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
 
         assertThat(parameter.buildFile, nullValue())
-        assertThat(parameter.settingsScriptSource, nullValue())
+        assertThat(parameter.settingsFile, nullValue())
 
         assertThat(parameter.logLevel, equalTo(LogLevel.LIFECYCLE))
         assertTrue(parameter.colorOutput)
@@ -80,9 +73,13 @@ class StartParameterTest {
         assertThat(parameter.excludedTaskNames, isEmpty())
         assertThat(parameter.projectProperties, isEmptyMap())
         assertThat(parameter.systemPropertiesArgs, isEmptyMap())
-        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
-        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(parameter.currentDir)))
         assertFalse(parameter.dryRun)
+        assertFalse(parameter.continueOnFailure)
+        assertThat(parameter.refreshOptions, equalTo(RefreshOptions.NONE))
+        assertThat(parameter.rerunTasks, equalTo(false))
+        assertThat(parameter.recompileScripts, equalTo(false))
+        assertFalse(parameter.refreshDependencies)
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testDefaultWithGradleUserHomeSystemProp() {
@@ -98,7 +95,7 @@ class StartParameterTest {
         parameter.currentDir = dir
 
         assertThat(parameter.currentDir, equalTo(dir.canonicalFile))
-        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(dir.canonicalFile)))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetBuildFile() {
@@ -108,7 +105,7 @@ class StartParameterTest {
 
         assertThat(parameter.buildFile, equalTo(file.canonicalFile))
         assertThat(parameter.currentDir, equalTo(file.canonicalFile.parentFile))
-        assertThat(parameter.defaultProjectSelector, reflectionEquals(new BuildFileProjectSpec(file.canonicalFile)))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetNullBuildFile() {
@@ -118,8 +115,8 @@ class StartParameterTest {
 
         assertThat(parameter.buildFile, nullValue())
         assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
-        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(parameter.currentDir)))
         assertThat(parameter.initScripts, equalTo(Collections.emptyList()))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetProjectDir() {
@@ -128,7 +125,7 @@ class StartParameterTest {
         parameter.projectDir = file
 
         assertThat(parameter.currentDir, equalTo(file.canonicalFile))
-        assertThat(parameter.defaultProjectSelector, reflectionEquals(new ProjectDirectoryProjectSpec(file.canonicalFile)))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetNullProjectDir() {
@@ -137,7 +134,7 @@ class StartParameterTest {
         parameter.projectDir = null
 
         assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
-        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(parameter.currentDir)))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetSettingsFile() {
@@ -146,75 +143,31 @@ class StartParameterTest {
         parameter.settingsFile = file
 
         assertThat(parameter.currentDir, equalTo(file.canonicalFile.parentFile))
-        assertThat(parameter.settingsScriptSource, instanceOf(UriScriptSource.class))
-        assertThat(parameter.settingsScriptSource.resource.file, equalTo(file.canonicalFile))
+        assertThat(parameter.settingsFile, equalTo(file.canonicalFile))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetNullSettingsFile() {
         StartParameter parameter = new StartParameter()
         parameter.settingsFile = null
 
-        assertThat(parameter.settingsScriptSource, nullValue())
-    }
-
-    @Test public void testSetSettingsScriptSource() {
-        StartParameter parameter = new StartParameter()
-        parameter.settingsFile = new File('settings file')
-
-        ScriptSource scriptSource = {} as ScriptSource
-
-        parameter.settingsScriptSource = scriptSource
-
-        assertThat(parameter.settingsScriptSource, sameInstance(scriptSource))
-    }
-
-    @Test public void testSetTaskNames() {
-        StartParameter parameter = new StartParameter()
-        parameter.taskNames = ['a', 'b']
-        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
-        assertThat(parameter.buildExecuter.delegate.names, equalTo(['a', 'b']))
+        assertThat(parameter.settingsFile, nullValue())
+        assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetTaskNamesUsesDefaultExecuter() {
-        StartParameter parameter = new StartParameter()
-
-        parameter.setBuildExecuter({} as BuildExecuter)
-        parameter.taskNames = []
-        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
-    }
-
-    @Test public void testSetExcludedTaskNames() {
-        StartParameter parameter = new StartParameter()
-        parameter.excludedTaskNames = ['a', 'b']
-        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
-        assertThat(parameter.buildExecuter.excludedTaskNames, equalTo(['a', 'b'] as Set))
-    }
-
-    @Test public void testUseEmbeddedBuildFile() {
+    @Test public void testUseEmptySettingsScript() {
         StartParameter parameter = new StartParameter();
-        parameter.useEmbeddedBuildFile("<content>")
-        assertThat(parameter.buildScriptSource, instanceOf(StringScriptSource.class))
-        assertThat(parameter.buildScriptSource.resource.text, equalTo("<content>"))
-        assertThat(parameter.settingsScriptSource, instanceOf(StringScriptSource.class))
-        assertThat(parameter.settingsScriptSource.resource.text, equalTo(""))
+        parameter.useEmptySettings()
+        assertThat(parameter.settingsFile, nullValue())
         assertThat(parameter.searchUpwards, equalTo(false))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testSetNullUserHomeDir() {
         StartParameter parameter = new StartParameter()
         parameter.gradleUserHomeDir = null
         assertThat(parameter.gradleUserHomeDir, equalTo(StartParameter.DEFAULT_GRADLE_USER_HOME))
-    }
-
-    @Test public void testWrapsExecuterWhenDryRunIsTrue() {
-        StartParameter parameter = new StartParameter()
-        def originalExecuter = [:] as BuildExecuter
-        parameter.buildExecuter = originalExecuter
-        parameter.dryRun = true
-        assertThat(parameter.buildExecuter, instanceOf(DryRunBuildExecuter))
-        assertThat(parameter.buildExecuter.delegate, sameInstance(originalExecuter))
-        parameter.dryRun = false
-        assertThat(parameter.buildExecuter, sameInstance(originalExecuter))
+        assertThat(parameter, isSerializable())
     }
 
     @Test public void testNewBuild() {
@@ -232,8 +185,13 @@ class StartParameterTest {
         parameter.settingsFile = new File("settings file")
         parameter.taskNames = ['task1']
         parameter.excludedTaskNames = ['excluded1']
-        parameter.defaultProjectSelector = [:] as ProjectSpec
         parameter.dryRun = true
+        parameter.continueOnFailure = true
+        parameter.recompileScripts = true
+        parameter.rerunTasks = true
+        parameter.refreshDependencies = true
+
+        assertThat(parameter, isSerializable())
 
         StartParameter newParameter = parameter.newBuild();
 
@@ -243,12 +201,33 @@ class StartParameterTest {
         assertThat(newParameter.cacheUsage, equalTo(parameter.cacheUsage));
         assertThat(newParameter.logLevel, equalTo(parameter.logLevel));
         assertThat(newParameter.colorOutput, equalTo(parameter.colorOutput));
+        assertThat(newParameter.continueOnFailure, equalTo(parameter.continueOnFailure))
+        assertThat(newParameter.refreshDependencies, equalTo(parameter.refreshDependencies))
+        assertThat(newParameter.rerunTasks, equalTo(parameter.rerunTasks))
+        assertThat(newParameter.recompileScripts, equalTo(parameter.recompileScripts))
 
         assertThat(newParameter.buildFile, nullValue())
         assertThat(newParameter.taskNames, isEmpty())
         assertThat(newParameter.excludedTaskNames, isEmpty())
         assertThat(newParameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
-        assertThat(newParameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(newParameter.currentDir)))
         assertFalse(newParameter.dryRun)
+        assertThat(newParameter, isSerializable())
+    }
+    
+    void testMergingSystemProperties() {
+        def p = { args = null ->
+            def sp = new StartParameter()
+            if (args) {
+                sp.systemPropertyArgs = args
+            }
+            sp.mergedSystemProperties.sort()
+        }
+        
+        System.properties.clear()
+        System.properties.a = "1"
+    
+        assert p(b: "2") == [a: "1", b: "2"]
+        assert p(a: "2", b: "3") == [a: "2", b: "3"]
+        assert p() == [a: "1"]
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java
index 9c5a288..7083219 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java
@@ -15,10 +15,13 @@
  */
 package org.gradle.api;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import org.junit.Test;
 
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
 public class JavaVersionTest {
     @Test
     public void toStringReturnsVersion() {
@@ -83,6 +86,11 @@ public class JavaVersionTest {
         conversionFails(2.0);
         conversionFails(4.2);
     }
+    
+    @Test
+    public void currentReturnsJvmVersion() {
+        assertThat(JavaVersion.current(), equalTo(JavaVersion.toVersion(System.getProperty("java.version"))));
+    }
 
     @Test
     public void convertsNullToNull() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/artifacts/PublishInstructionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/artifacts/PublishInstructionTest.java
deleted file mode 100644
index 549a850..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/artifacts/PublishInstructionTest.java
+++ /dev/null
@@ -1,71 +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.artifacts;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.gradle.api.InvalidUserDataException;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class PublishInstructionTest {
-    @Test
-    public void initWithNoArgs() {
-        PublishInstruction publishInstruction = new PublishInstruction();
-        assertThat(publishInstruction.isUploadDescriptor(), equalTo(false));
-        assertThat(publishInstruction.getDescriptorDestination(), equalTo(null));
-    }
-
-    @Test
-    public void initWithUploadTrueAndFileNotNull() {
-        File someFile = new File("somePath");
-        PublishInstruction publishInstruction = new PublishInstruction(true, someFile);
-        assertThat(publishInstruction.isUploadDescriptor(), equalTo(true));
-        assertThat(publishInstruction.getDescriptorDestination(), equalTo(someFile));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void initWithUploadFalseAndFileNotNullShouldThrowInvalidUserDataEx() {
-        new PublishInstruction(false, new File("somePath"));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void initWithUploadTrueAndFileNullShouldThrowInvalidUserDataEx() {
-        new PublishInstruction(true, null);
-    }
-
-    @Test
-    public void initWithUploadFalseAndFileNull() {
-        PublishInstruction publishInstruction = new PublishInstruction(false, null);
-        assertThat(publishInstruction.isUploadDescriptor(), equalTo(false));
-        assertThat(publishInstruction.getDescriptorDestination(), nullValue());
-    }
-
-    @Test
-    public void equalityAndHash() {
-        assertThat(new PublishInstruction(false, null), equalTo(new PublishInstruction(false, null)));
-        assertThat(new PublishInstruction(true, new File("somePath")), equalTo(new PublishInstruction(true, new File("somePath"))));
-        assertThat(new PublishInstruction(true, new File("somePath")), not(equalTo(new PublishInstruction(true, new File("someOtherPath")))));
-
-        assertThat(new PublishInstruction(true, new File("somePath")).hashCode(),
-                equalTo(new PublishInstruction(true, new File("somePath")).hashCode()));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java b/subprojects/core/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
deleted file mode 100644
index 812e8b3..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
+++ /dev/null
@@ -1,55 +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.artifacts.maven;
-
-import static org.junit.Assert.*;
-import org.junit.Before;
-import org.junit.Test;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.util.HelperUtil;
-
-/**
- * @author Hans Dockter
- */
-public class Conf2ScopeMappingTest {
-    private Conf2ScopeMapping conf2ScopeMapping;
-    private static final String TEST_SCOPE = "somescope";
-    private static final Integer TEST_PRIORITY = 10;
-    private static final Configuration TEST_CONF = HelperUtil.createConfiguration("someconf");
-
-    @Before
-    public void setUp() {
-        conf2ScopeMapping = new Conf2ScopeMapping(TEST_PRIORITY, TEST_CONF, TEST_SCOPE);
-    }
-
-    @Test
-    public void init() {
-        assertEquals(TEST_PRIORITY, conf2ScopeMapping.getPriority());
-        assertEquals(TEST_CONF, conf2ScopeMapping.getConfiguration());
-        assertEquals(TEST_SCOPE, conf2ScopeMapping.getScope());
-    }
-
-    @Test
-    public void equality() {
-        assertTrue(conf2ScopeMapping.equals(new Conf2ScopeMapping(TEST_PRIORITY, TEST_CONF, TEST_SCOPE)));
-        assertFalse(conf2ScopeMapping.equals(new Conf2ScopeMapping(TEST_PRIORITY + 10, TEST_CONF, TEST_SCOPE)));
-    }
-
-    @Test
-    public void hashcode() {
-        assertEquals(conf2ScopeMapping.hashCode(), new Conf2ScopeMapping(TEST_PRIORITY, TEST_CONF, TEST_SCOPE).hashCode());
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/artifacts/specs/TypeTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/artifacts/specs/TypeTest.groovy
deleted file mode 100644
index d430320..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/artifacts/specs/TypeTest.groovy
+++ /dev/null
@@ -1,38 +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.artifacts.specs
-
-import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.api.artifacts.ExternalDependency
-import org.gradle.api.artifacts.Dependency
-
-import spock.lang.Specification
-
-class TypeTest extends Specification {
-    def "EXTERNAL matches external dependencies"() {
-        expect:
-        Type.EXTERNAL.isSatisfiedBy(Mock(ExternalDependency))
-        !Type.EXTERNAL.isSatisfiedBy(Mock(ProjectDependency))
-        !Type.EXTERNAL.isSatisfiedBy(Mock(Dependency))
-    }
-
-    def "PROJECT matches project dependencies"() {
-        expect:
-        Type.PROJECT.isSatisfiedBy(Mock(ProjectDependency))
-        !Type.PROJECT.isSatisfiedBy(Mock(ExternalDependency))
-        !Type.PROJECT.isSatisfiedBy(Mock(Dependency))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/file/FileCollectionSymlinkTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/file/FileCollectionSymlinkTest.groovy
new file mode 100644
index 0000000..bc5896e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/file/FileCollectionSymlinkTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.file
+
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.api.Project
+
+import spock.lang.Specification
+import spock.lang.Shared
+import spock.lang.Unroll
+
+// cannot use org.gradle.util.Resources here because the files it returns have already been canonicalized
+class FileCollectionSymlinkTest extends Specification {
+    @Shared Project project = new ProjectBuilder().build()
+
+    @Shared File baseDir = getTestFile("symlinks")
+    @Shared File file = new File(baseDir, "file")
+    @Shared File symlink = new File(baseDir, "symlink")
+    @Shared File symlinked = new File(baseDir, "symlinked")
+
+    @Unroll("#desc can handle symlinks")
+    def "file collection can handle symlinks"() {
+        expect:
+        fileCollection.contains(file)
+        fileCollection.contains(symlink)
+        fileCollection.contains(symlinked)
+        fileCollection.files == [file, symlink, symlinked] as Set
+
+        (fileCollection - project.files(symlink)).files == [file, symlinked] as Set
+
+        where:
+        desc                 | fileCollection
+        "project.files()"    | project.files(file, symlink, symlinked)
+        "project.fileTree()" | project.fileTree(baseDir)
+    }
+
+    private static File getTestFile(String name) {
+        new File(FileCollectionSymlinkTest.getResource(name).toURI().path)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy b/subprojects/core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy
index 9847032..518622b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy
@@ -15,17 +15,18 @@
  */
 package org.gradle.api.file
 
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
 import org.gradle.api.internal.file.collections.MinimalFileTree
 import org.gradle.api.internal.file.collections.FileTreeAdapter
 
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
 class FileVisitorUtil {
-    static def assertCanStopVisiting(MinimalFileTree tree) {
+    static void assertCanStopVisiting(MinimalFileTree tree) {
         assertCanStopVisiting(new FileTreeAdapter(tree))
     }
 
-    static def assertCanStopVisiting(FileTree tree) {
+    static void assertCanStopVisiting(FileTree tree) {
         boolean found = false
         FileVisitor visitor = [
                 visitFile: {FileVisitDetails details ->
@@ -42,11 +43,11 @@ class FileVisitorUtil {
         assertTrue(found)
     }
 
-    static def assertVisits(MinimalFileTree tree, Iterable<String> expectedFiles, Iterable<String> expectedDirs) {
+    static void assertVisits(MinimalFileTree tree, Iterable<String> expectedFiles, Iterable<String> expectedDirs) {
         assertVisits(new FileTreeAdapter(tree), expectedFiles, expectedDirs)
     }
 
-    static def assertVisits(FileTree tree, Iterable<String> expectedFiles, Iterable<String> expectedDirs) {
+    static void assertVisits(FileTree tree, Iterable<String> expectedFiles, Iterable<String> expectedDirs) {
         Set files = [] as Set
         Set dirs = [] as Set
         FileVisitor visitor = [
@@ -56,7 +57,6 @@ class FileVisitorUtil {
                     }
                     assertTrue(files.add(details.relativePath.pathString))
                     assertTrue(details.relativePath.isFile())
-                    assertEquals(details.file.lastModified(), details.lastModified)
                     assertTrue(details.file.file)
                     ByteArrayOutputStream outstr = new ByteArrayOutputStream()
                     details.copyTo(outstr)
@@ -68,7 +68,6 @@ class FileVisitorUtil {
                     }
                     assertTrue(dirs.add(details.relativePath.pathString))
                     assertFalse(details.relativePath.isFile())
-                    assertEquals(details.file.lastModified(), details.lastModified)
                     assertTrue(details.file.directory)
                 }
         ] as FileVisitor
@@ -86,7 +85,7 @@ class FileVisitorUtil {
         assertThat(files, equalTo(expectedFiles + expectedDirs as Set))
     }
 
-    static def assertVisits(FileTree tree, Map<String, File> files) {
+    static void assertVisits(FileTree tree, Map<String, File> files) {
         Map<String, File> visited = [:]
         FileVisitor visitor = [
                 visitFile: {FileVisitDetails details ->
@@ -100,4 +99,16 @@ class FileVisitorUtil {
 
         assertThat(visited, equalTo(files))
     }
+
+    static void assertVisitsPermissions(MinimalFileTree tree, Map<String, Integer> filesWithPermissions) {
+        assertVisitsPermissions(new FileTreeAdapter(tree), filesWithPermissions)
+    }
+
+    static void assertVisitsPermissions(FileTree tree, Map<String, Integer> filesWithPermissions) {
+        def visited = [:]
+        tree.visit {
+            visited[it.name] = it.mode
+        }
+        assertThat(visited, equalTo(filesWithPermissions))
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
old mode 100644
new mode 100755
index 40867c5..b9a73ba
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
@@ -17,14 +17,16 @@ package org.gradle.api.internal;
 
 import groovy.lang.Closure;
 import groovy.lang.GroovyObject;
-import groovy.lang.GroovyRuntimeException;
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.plugins.Convention;
-import org.gradle.api.tasks.ConventionValue;
-import org.gradle.util.GUtil;
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.api.plugins.ExtensionContainer;
 import org.junit.Before;
 import org.junit.Test;
+import spock.lang.Issue;
 
 import java.util.*;
 import java.util.concurrent.Callable;
@@ -36,15 +38,17 @@ import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
+import static org.gradle.api.internal.AbstractClassGeneratorTestGroovy.*;
+
 public abstract class AbstractClassGeneratorTest {
-    private ClassGenerator generator;
+    private AbstractClassGenerator generator;
 
     @Before
     public void setUp() {
         generator = createGenerator();
     }
 
-    protected abstract ClassGenerator createGenerator();
+    protected abstract AbstractClassGenerator createGenerator();
 
     @Test
     public void mixesInConventionAwareInterface() throws Exception {
@@ -57,8 +61,6 @@ public abstract class AbstractClassGeneratorTest {
         assertThat(conventionAware.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
         conventionAware.getConventionMapping().map("prop", TEST_CLOSURE);
         ConventionMapping mapping = new ConventionAwareBean();
-        conventionAware.setConventionMapping(mapping);
-        assertThat(conventionAware.getConventionMapping(), sameInstance(mapping));
     }
 
     @Test
@@ -66,12 +68,24 @@ public abstract class AbstractClassGeneratorTest {
         Class<? extends Bean> generatedClass = generator.generate(Bean.class);
         assertTrue(DynamicObjectAware.class.isAssignableFrom(generatedClass));
         Bean bean = generatedClass.newInstance();
-        ((DynamicObjectAware) bean).getAsDynamicObject().setProperty("prop", "value");
+        DynamicObjectAware dynamicBean = (DynamicObjectAware) bean;
+
+        dynamicBean.getAsDynamicObject().setProperty("prop", "value");
         assertThat(bean.getProp(), equalTo("value"));
         assertThat(bean.doStuff("some value"), equalTo("{some value}"));
     }
 
     @Test
+    public void mixesInExtensionAwareInterface() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(ExtensionAware.class.isAssignableFrom(generatedClass));
+        Bean bean = generatedClass.newInstance();
+        ExtensionAware dynamicBean = (ExtensionAware) bean;
+
+        assertThat(dynamicBean.getExtensions(), notNullValue());
+    }
+
+    @Test
     public void mixesInGroovyObjectInterface() throws Exception {
         Class<? extends Bean> generatedClass = generator.generate(Bean.class);
         assertTrue(GroovyObject.class.isAssignableFrom(generatedClass));
@@ -91,6 +105,12 @@ public abstract class AbstractClassGeneratorTest {
     }
 
     @Test
+    public void doesNotDecorateAlreadyDecoratedClass() {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertSame(generatedClass, generator.generate(generatedClass));
+    }
+
+    @Test
     public void overridesPublicConstructors() throws Exception {
         Class<? extends Bean> generatedClass = generator.generate(BeanWithConstructor.class);
         Bean bean = generatedClass.getConstructor(String.class).newInstance("value");
@@ -108,6 +128,9 @@ public abstract class AbstractClassGeneratorTest {
 
         bean = generator.newInstance(BeanWithConstructor.class);
         assertThat(bean.getProp(), equalTo("default value"));
+
+        bean = generator.newInstance(BeanWithConstructor.class, 127);
+        assertThat(bean.getProp(), equalTo("127"));
     }
 
     @Test
@@ -122,7 +145,7 @@ public abstract class AbstractClassGeneratorTest {
         try {
             generator.newInstance(Bean.class, "arg1", 2);
             fail();
-        } catch (GroovyRuntimeException e) {
+        } catch (IllegalArgumentException e) {
             // expected
         }
 
@@ -150,8 +173,8 @@ public abstract class AbstractClassGeneratorTest {
 
         assertThat(bean.getProp(), nullValue());
 
-        conventionAware.getConventionMapping().map("prop", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.getConventionMapping().map("prop", new Callable<String>() {
+            public String call() {
                 return "conventionValue";
             }
         });
@@ -166,6 +189,41 @@ public abstract class AbstractClassGeneratorTest {
     }
 
     @Test
+    @Issue("GRADLE-2163")
+    public void appliesConventionMappingToGroovyBoolean() throws Exception {
+        BeanWithGroovyBoolean bean = generator.generate(BeanWithGroovyBoolean.class).newInstance();
+
+        assertTrue(bean instanceof IConventionAware);
+        assertThat(bean.getSmallB(), equalTo(false));
+        assertThat(bean.getBigB(), nullValue());
+
+        IConventionAware conventionAware = (IConventionAware) bean;
+
+        conventionAware.getConventionMapping().map("smallB", new Callable<Object>() {
+            public Object call() throws Exception {
+                return true;
+            }
+        });
+
+        assertThat(bean.isSmallB(), equalTo(true));
+        assertThat(bean.getSmallB(), equalTo(true));
+
+        bean.setSmallB(false);
+        assertThat(bean.isSmallB(), equalTo(false));
+        assertThat(bean.getSmallB(), equalTo(false));
+
+        conventionAware.getConventionMapping().map("bigB", new Callable<Object>() {
+            public Object call() throws Exception {
+                return Boolean.TRUE;
+            }
+        });
+
+        assertThat(bean.getBigB(), equalTo(Boolean.TRUE));
+        bean.setBigB(Boolean.FALSE);
+        assertThat(bean.getBigB(), equalTo(Boolean.FALSE));
+    }
+
+    @Test
     public void appliesConventionMappingToCollectionGetter() throws Exception {
         Class<? extends CollectionBean> generatedClass = generator.generate(CollectionBean.class);
         CollectionBean bean = generatedClass.newInstance();
@@ -174,8 +232,8 @@ public abstract class AbstractClassGeneratorTest {
 
         assertThat(bean.getProp(), isEmpty());
 
-        conventionAware.getConventionMapping().map("prop", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
+            public Object call() {
                 return conventionValue;
             }
         });
@@ -185,8 +243,8 @@ public abstract class AbstractClassGeneratorTest {
         bean.setProp(toList("other"));
         assertThat(bean.getProp(), equalTo(toList("other")));
 
-        bean.setProp(Collections.EMPTY_LIST);
-        assertThat(bean.getProp(), equalTo(Collections.EMPTY_LIST));
+        bean.setProp(Collections.<String>emptyList());
+        assertThat(bean.getProp(), equalTo(Collections.<String>emptyList()));
 
         bean.setProp(null);
         assertThat(bean.getProp(), nullValue());
@@ -200,6 +258,18 @@ public abstract class AbstractClassGeneratorTest {
         assertThat(bean.getBooleanProperty(), equalTo(false));
         assertThat(bean.getLongProperty(), equalTo(12L));
         assertThat(bean.setReturnValueProperty("p"), sameInstance(bean));
+
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("booleanProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                return true;
+            }
+        });
+
+        assertThat(bean.getBooleanProperty(), equalTo(true));
+
+        bean.setBooleanProperty(false);
+        assertThat(bean.getBooleanProperty(), equalTo(false));
     }
 
     @Test
@@ -217,27 +287,26 @@ public abstract class AbstractClassGeneratorTest {
     public void doesNotOverrideMethodsFromSuperclassesMarkedWithAnnotation() throws Exception {
         BeanSubClass bean = generator.generate(BeanSubClass.class).newInstance();
         IConventionAware conventionAware = (IConventionAware) bean;
-        conventionAware.getConventionMapping().map(GUtil.map(
-                "property", new ConventionValue(){
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        throw new UnsupportedOperationException();
-                    }
-                },
-                "interfaceProperty", new ConventionValue(){
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        throw new UnsupportedOperationException();
-                    }
-                },
-                "overriddenProperty", new ConventionValue(){
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return "conventionValue";
-                    }
-                },
-                "otherProperty", new ConventionValue(){
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return "conventionValue";
-                    }
-                }));
+        conventionAware.getConventionMapping().map("property", new Callable<Object>() {
+            public Object call() throws Exception {
+                throw new UnsupportedOperationException();
+            }
+        });
+        conventionAware.getConventionMapping().map("interfaceProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                throw new UnsupportedOperationException();
+            }
+        });
+        conventionAware.getConventionMapping().map("overriddenProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "conventionValue";
+            }
+        });
+        conventionAware.getConventionMapping().map("otherProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "conventionValue";
+            }
+        });
         assertEquals(null, bean.getProperty());
         assertEquals(null, bean.getInterfaceProperty());
         assertEquals("conventionValue", bean.getOverriddenProperty());
@@ -258,10 +327,7 @@ public abstract class AbstractClassGeneratorTest {
     public void doesNotOverrideMethodsFromDynamicObjectAwareInterface() throws Exception {
         DynamicObjectAwareBean bean = generator.generate(DynamicObjectAwareBean.class).newInstance();
         assertThat(bean.getConvention(), sameInstance(bean.conv));
-        Convention newConvention = new DefaultConvention();
-        bean.setConvention(newConvention);
-        assertThat(bean.getConvention(), sameInstance(newConvention));
-        assertThat(bean.getAsDynamicObject(), sameInstance((DynamicObject) newConvention));
+        assertThat(bean.getAsDynamicObject(), sameInstance(bean.conv.getExtensionsAsDynamicObject()));
     }
 
     @Test
@@ -280,24 +346,11 @@ public abstract class AbstractClassGeneratorTest {
     }
 
     @Test
-    public void usesSameConventionForDynamicObjectAndConventionMappings() throws Exception {
-        Bean bean = generator.generate(Bean.class).newInstance();
-        IConventionAware conventionAware = (IConventionAware) bean;
-        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
-        assertThat(dynamicObjectAware.getConvention(), sameInstance(conventionAware.getConventionMapping().getConvention()));
-
-        Convention newConvention = new DefaultConvention();
-        dynamicObjectAware.setConvention(newConvention);
-        assertThat(dynamicObjectAware.getConvention(), sameInstance(newConvention));
-        assertThat(conventionAware.getConventionMapping().getConvention(), sameInstance(newConvention));
-    }
-
-    @Test
     public void canAddDynamicPropertiesAndMethodsToJavaObject() throws Exception {
         Bean bean = generator.generate(Bean.class).newInstance();
         DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
         ConventionObject conventionObject = new ConventionObject();
-        dynamicObjectAware.getConvention().getPlugins().put("plugin", conventionObject);
+        new DslObject(dynamicObjectAware).getConvention().getPlugins().put("plugin", conventionObject);
 
         call("{ it.conventionProperty = 'value' }", bean);
         assertThat(conventionObject.getConventionProperty(), equalTo("value"));
@@ -312,7 +365,7 @@ public abstract class AbstractClassGeneratorTest {
         TestDecoratedGroovyBean bean = generator.generate(TestDecoratedGroovyBean.class).newInstance();
         DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
         ConventionObject conventionObject = new ConventionObject();
-        dynamicObjectAware.getConvention().getPlugins().put("plugin", conventionObject);
+        new DslObject(dynamicObjectAware).getConvention().getPlugins().put("plugin", conventionObject);
 
         call("{ it.conventionProperty = 'value' }", bean);
         assertThat(conventionObject.getConventionProperty(), equalTo("value"));
@@ -343,7 +396,7 @@ public abstract class AbstractClassGeneratorTest {
     }
 
     @Test
-    public void usesExistingDynamicObject() throws Exception {
+    public void usesExistingGetAsDynamicObjectMethod() throws Exception {
         DynamicObjectBean bean = generator.generate(DynamicObjectBean.class).newInstance();
 
         call("{ it.prop = 'value' }", bean);
@@ -356,6 +409,123 @@ public abstract class AbstractClassGeneratorTest {
         assertThat(call("{ it.dynamicProp }", bean), equalTo((Object) "value"));
     }
 
+    @Test
+    public void constructorCanCallGetter() throws Exception {
+        BeanUsesPropertiesInConstructor bean = generator.newInstance(BeanUsesPropertiesInConstructor.class);
+
+        assertThat(bean.name, equalTo("default-name"));
+    }
+
+    @Test
+    public void mixesInSetValueMethodForProperty() throws Exception {
+        BeanWithVariousGettersAndSetters bean = generator.generate(BeanWithVariousGettersAndSetters.class).newInstance();
+
+        call("{ it.prop 'value'}", bean);
+        assertThat(bean.getProp(), equalTo("value"));
+
+        call("{ it.finalGetter 'another'}", bean);
+        assertThat(bean.getFinalGetter(), equalTo("another"));
+
+        call("{ it.writeOnly 12}", bean);
+        assertThat(bean.writeOnly, equalTo(12));
+
+        call("{ it.primitive 12}", bean);
+        assertThat(bean.getPrimitive(), equalTo(12));
+    }
+
+    @Test
+    public void doesNotUseConventionValueOnceSetValueMethodHasBeenCalled() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "[default]";
+            }
+        });
+
+        assertThat(bean.getProp(), equalTo("[default]"));
+
+        call("{ it.prop 'value'}", bean);
+        assertThat(bean.getProp(), equalTo("value"));
+    }
+
+    @Test
+    public void doesNotMixInSetValueMethodForReadOnlyProperty() throws Exception {
+        BeanWithReadOnlyProperties bean = generator.generate(BeanWithReadOnlyProperties.class).newInstance();
+
+        try {
+            call("{ it.prop 'value'}", bean);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMethod(), equalTo("prop"));
+        }
+    }
+
+    @Test
+    public void doesNotMixInSetValueMethodForMultiValueProperty() throws Exception {
+        CollectionBean bean = generator.generate(CollectionBean.class).newInstance();
+
+        try {
+            call("{ def val = ['value']; it.prop val}", bean);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMethod(), equalTo("prop"));
+        }
+    }
+
+    @Test
+    public void overridesExistingSetValueMethod() throws Exception {
+        BeanWithDslMethods bean = generator.generate(BeanWithDslMethods.class).newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "[default]";
+            }
+        });
+
+        assertThat(bean.getProp(), equalTo("[default]"));
+
+        assertThat(call("{ it.prop 'value'}", bean), sameInstance((Object) bean));
+        assertThat(bean.getProp(), equalTo("[value]"));
+
+        assertThat(call("{ it.prop 1.2}", bean), sameInstance((Object) bean));
+        assertThat(bean.getProp(), equalTo("<1.2>"));
+
+        assertThat(call("{ it.prop 1}", bean), nullValue());
+        assertThat(bean.getProp(), equalTo("<1>"));
+
+        // failing, seems to be that set method override doesn't work for iterables - GRADLE-2097
+        //assertThat(call("{ bean, list -> bean.things(list) }", bean, new LinkedList<Object>()), nullValue());
+        //assertThat(bean.getThings().size(), equalTo(0));
+
+        //assertThat(call("{ bean -> bean.things([1,2,3]) }", bean), nullValue());
+        //assertThat(bean.getThings().size(), equalTo(3));
+
+        //FileCollection files = ProjectBuilder.builder().build().files();
+        //assertThat(call("{ bean, fc -> bean.files fc}", bean, files), nullValue());
+        //assertThat(bean.getFiles(), sameInstance(files));
+    }
+
+    @Test
+    public void mixesInClosureOverloadForActionMethod() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        bean.prop = "value";
+
+        call("{def value; it.doStuff { value = it }; assert value == \'value\' }", bean);
+    }
+
+    @Test
+    public void doesNotOverrideExistingClosureOverload() throws IllegalAccessException, InstantiationException {
+        BeanWithDslMethods bean = generator.generate(BeanWithDslMethods.class).newInstance();
+        bean.prop = "value";
+
+        assertThat(call("{def value; it.doStuff { value = it }; return value }", bean), equalTo((Object) "[value]"));
+    }
+
+    @Test public void generatesDslObjectCompatibleObject() throws Exception {
+        new DslObject(generator.generate(Bean.class).newInstance());
+    }
+
     public static class Bean {
         private String prop;
 
@@ -370,6 +540,16 @@ public abstract class AbstractClassGeneratorTest {
         public String doStuff(String value) {
             return "{" + value + "}";
         }
+
+        public void doStuff(Action<String> action) {
+            action.execute(getProp());
+        }
+    }
+
+    public static class BeanWithReadOnlyProperties {
+        public String getProp() {
+            return "value";
+        }
     }
 
     public static class CollectionBean {
@@ -392,28 +572,70 @@ public abstract class AbstractClassGeneratorTest {
         public BeanWithConstructor(String value) {
             setProp(value);
         }
+
+        public BeanWithConstructor(int value) {
+            setProp(String.valueOf(value));
+        }
     }
 
-    public static class ConventionAwareBean extends Bean implements IConventionAware, ConventionMapping {
-        Map<String, ConventionValue> mapping = new HashMap<String, ConventionValue>();
+    public static class BeanWithDslMethods extends Bean {
+        private String prop;
+        private FileCollection files;
+        private List<Object> things;
 
-        public Convention getConvention() {
-            throw new UnsupportedOperationException();
+        public String getProp() {
+            return prop;
         }
 
-        public void setConvention(Convention convention) {
-            throw new UnsupportedOperationException();
+        public void setProp(String prop) {
+            this.prop = prop;
         }
 
-        public ConventionMapping map(Map<String, ? extends ConventionValue> properties) {
+        public FileCollection getFiles() {
+            return files;
+        }
+
+        public void setFiles(FileCollection files) {
+            this.files = files;
+        }
+
+        public List<Object> getThings() {
+            return things;
+        }
+
+        public void setThings(List<Object> things) {
+            this.things = things;
+        }
+
+        public BeanWithDslMethods prop(String property) {
+            this.prop = String.format("[%s]", property);
+            return this;
+        }
+
+        public BeanWithDslMethods prop(Object property) {
+            this.prop = String.format("<%s>", property);
+            return this;
+        }
+
+        public void prop(int property) {
+            this.prop = String.format("<%s>", property);
+        }
+
+        public void doStuff(Closure cl) {
+            cl.call(String.format("[%s]", getProp()));
+        }
+    }
+
+    public static class ConventionAwareBean extends Bean implements IConventionAware, ConventionMapping {
+        public Convention getConvention() {
             throw new UnsupportedOperationException();
         }
 
-        public MappedProperty map(String propertyName, Closure value) {
+        public void setConvention(Convention convention) {
             throw new UnsupportedOperationException();
         }
 
-        public MappedProperty map(String propertyName, ConventionValue value) {
+        public MappedProperty map(String propertyName, Closure value) {
             throw new UnsupportedOperationException();
         }
 
@@ -423,7 +645,7 @@ public abstract class AbstractClassGeneratorTest {
 
         public <T> T getConventionValue(T actualValue, String propertyName) {
             if (actualValue instanceof String) {
-                return (T)("[" + actualValue + "]");
+                return (T) ("[" + actualValue + "]");
             } else {
                 throw new UnsupportedOperationException();
             }
@@ -443,18 +665,18 @@ public abstract class AbstractClassGeneratorTest {
     }
 
     public static class DynamicObjectAwareBean extends Bean implements DynamicObjectAware {
-        Convention conv = new DefaultConvention();
+        Convention conv = new ExtensibleDynamicObject(this, ThreadGlobalInstantiator.getOrCreate()).getConvention();
 
         public Convention getConvention() {
             return conv;
         }
 
-        public void setConvention(Convention convention) {
-            this.conv = convention;
+        public ExtensionContainer getExtensions() {
+            return conv;
         }
 
         public DynamicObject getAsDynamicObject() {
-            return conv;
+            return conv.getExtensionsAsDynamicObject();
         }
     }
 
@@ -475,12 +697,14 @@ public abstract class AbstractClassGeneratorTest {
     }
 
     public static class BeanWithVariousPropertyTypes {
+        private boolean b;
+
         public String[] getArrayProperty() {
             return new String[1];
         }
 
         public boolean getBooleanProperty() {
-            return false;
+            return b;
         }
 
         public long getLongProperty() {
@@ -490,10 +714,41 @@ public abstract class AbstractClassGeneratorTest {
         public String getReturnValueProperty() {
             return "value";
         }
-        
+
         public BeanWithVariousPropertyTypes setReturnValueProperty(String val) {
             return this;
         }
+
+        public void setBooleanProperty(boolean b) {
+            this.b = b;
+        }
+    }
+
+    public static class BeanWithVariousGettersAndSetters extends Bean {
+        private int primitive;
+        private boolean bool;
+        private String finalGetter;
+        private Integer writeOnly;
+
+        public int getPrimitive() {
+            return primitive;
+        }
+
+        public void setPrimitive(int primitive) {
+            this.primitive = primitive;
+        }
+
+        public final String getFinalGetter() {
+            return finalGetter;
+        }
+
+        public void setFinalGetter(String value) {
+            finalGetter = value;
+        }
+
+        public void setWriteOnly(Integer value) {
+            writeOnly = value;
+        }
     }
 
     public interface SomeType {
@@ -541,6 +796,18 @@ public abstract class AbstractClassGeneratorTest {
         }
     }
 
+    public static class BeanUsesPropertiesInConstructor {
+        final String name;
+
+        public BeanUsesPropertiesInConstructor() {
+            name = getName();
+        }
+
+        public String getName() {
+            return "default-name";
+        }
+    }
+
     public static class UnconstructableBean {
         static UnsupportedOperationException failure = new UnsupportedOperationException();
 
@@ -553,5 +820,6 @@ public abstract class AbstractClassGeneratorTest {
         abstract void implementMe();
     }
 
-    private static class PrivateBean {}
+    private static class PrivateBean {
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTestGroovy.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTestGroovy.groovy
new file mode 100644
index 0000000..26c24dd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTestGroovy.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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
+
+class AbstractClassGeneratorTestGroovy {
+
+    public static class BeanWithGroovyBoolean {
+        boolean smallB
+        Boolean bigB
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy
index 633ee4f..368a354 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractMultiCauseExceptionTest.groovy
@@ -35,7 +35,28 @@ class AbstractMultiCauseExceptionTest extends Specification {
         failure.cause == null
         failure.causes == []
     }
-    
+
+    def canUseInitCauseToProvideCause() {
+        def cause1 = new RuntimeException()
+        def failure = new TestMultiCauseException('message', [])
+        failure.initCause(cause1)
+
+        expect:
+        failure.cause == cause1
+        failure.causes == [cause1]
+    }
+
+    def canUseInitCausesToProvideMultipleCause() {
+        def cause1 = new RuntimeException()
+        def cause2 = new RuntimeException()
+        def failure = new TestMultiCauseException('message', [])
+        failure.initCauses([cause1, cause2])
+
+        expect:
+        failure.cause == cause1
+        failure.causes == [cause1, cause2]
+    }
+
     def printStackTraceWithMultipleCauses() {
         RuntimeException cause1 = new RuntimeException('cause1')
         RuntimeException cause2 = new RuntimeException('cause2')
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
new file mode 100644
index 0000000..d0b93e8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
@@ -0,0 +1,196 @@
+/*
+ * 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
+
+import org.junit.Ignore
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
+
+class AbstractNamedDomainObjectContainerTest {
+    private final Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+    private final AbstractNamedDomainObjectContainer container = instantiator.newInstance(TestContainer.class, instantiator)
+
+    @Test
+    public void isDynamicObjectAware() {
+        assertThat(container, instanceOf(DynamicObjectAware));
+    }
+
+    @Test
+    public void canAddObjectWithName() {
+        container.create('obj')
+        assertThat(container.getByName('obj'), equalTo(['obj']))
+    }
+
+    @Test
+    public void canAddAndConfigureAnObjectWithName() {
+        container.create('obj') {
+            add(1)
+            add('value')
+        }
+        assertThat(container.getByName('obj'), equalTo(['obj', 1, 'value']))
+    }
+
+    @Test
+    public void failsToAddObjectWhenObjectWithSameNameAlreadyInContainer() {
+        container.create('obj')
+
+        try {
+            container.create('obj')
+            fail()
+        } catch (org.gradle.api.InvalidUserDataException e) {
+            assertThat(e.message, equalTo('Cannot add a TestObject with name \'obj\' as a TestObject with that name already exists.'))
+        }
+    }
+
+    @Test
+    public void canConfigureExistingObject() {
+        container.create('list1')
+        container.configure {
+            list1 { add(1) }
+        }
+        assertThat(container.list1, equalTo(['list1', 1]))
+    }
+
+    @Test
+    public void propagatesNestedMissingMethodException() {
+        container.create('list1')
+        try {
+            container.configure {
+                list1 { unknown { anotherUnknown(2) } }
+            }
+        } catch (groovy.lang.MissingMethodException e) {
+            assertThat(e.method, equalTo('unknown'))
+            assertThat(e.type, equalTo(TestObject))
+        }
+    }
+
+    @Test
+    public void propagatesMethodInvocationException() {
+        RuntimeException failure = new RuntimeException()
+        try {
+            container.configure {
+                list1 { throw failure }
+            }
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+
+    @Test
+    public void implicitlyAddsAnObjectWhenContainerIsBeingConfigured() {
+        container.configure {
+            list1
+            list2 { add(1) }
+        }
+        assertThat(container.list1, equalTo(['list1']))
+        assertThat(container.list2, equalTo(['list2', 1]))
+    }
+
+    @Test
+    public void canReferToPropertiesAndMethodsOfOwner() {
+        new DynamicOwner().configure(container)
+        assertThat(container.asMap.keySet(), equalTo(['list1', 'list2'] as Set))
+        assertThat(container.list1, equalTo(['list1', 'dynamicProp', 'ownerProp', 'ownerMethod', 'dynamicMethod', 'dynamicMethod', 1, 'prop', 'testObjectDynamicMethod']))
+        assertThat(container.list1.prop, equalTo('prop'))
+        assertThat(container.list2, equalTo(['list2', container.list1]))
+    }
+
+    @Test @Ignore
+    public void canUseAnItemCalledMainInAScript() {
+        Script script = new GroovyShell().parse("""import org.gradle.util.ConfigureUtil
+            c.configure {
+                run
+                main { add(1) }
+            }
+
+""")
+        script.getBinding().setProperty("c", container)
+        script.run()
+
+        assertThat(container.run, equalTo(['run']))
+        assertThat(container.main, equalTo(['main', 1]))
+    }
+}
+
+class DynamicOwner {
+    Map values = [:]
+
+    def ownerMethod(String value) {
+        return value
+    }
+    
+    def getOwnerProp() {
+        return 'ownerProp'
+    }
+
+    def propertyMissing(String name) {
+        if (name == 'dynamicProp') {
+            return values[name]
+        }
+        throw new MissingPropertyException("fail")
+    }
+
+    def propertyMissing(String name, Object value) {
+        if (name == 'dynamicProp') {
+            values[name] = value
+            return
+        }
+        throw new MissingPropertyException("fail")
+    }
+
+    def methodMissing(String name, Object params) {
+        if (name == 'dynamicMethod') {
+            return 'dynamicMethod'
+        }
+        throw new groovy.lang.MissingMethodException(name, getClass(), params)
+    }
+
+    def configure(def container) {
+        container.configure {
+            list1 {
+                // owner properties and methods - owner is a DynamicOwner
+                dynamicProp = 'dynamicProp'
+                add dynamicProp
+                add ownerProp
+                add ownerMethod('ownerMethod')
+                add dynamicMethod('a', 'b', 'c')
+                add dynamicMethod { doesntGetEvaluated }
+                // delegate properties and methods - delegate is a TestObject
+                add owner.size()
+                prop = 'prop'
+                add prop
+                testObjectDynamicMethod { doesntGetEvaluated }
+            }
+            list2 {
+                add list1
+            }
+        }
+    }
+}
+
+class TestObject extends ArrayList<String> {
+    def String prop
+    String name
+    def methodMissing(String name, Object params) {
+        if (name == 'testObjectDynamicMethod') {
+            add(name)
+            return name
+        }
+        throw new groovy.lang.MissingMethodException(name, getClass(), params)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
index e34fde5..a9bfb63 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal;
 
 public class AsmBackedClassGeneratorTest extends AbstractClassGeneratorTest {
     @Override
-    protected ClassGenerator createGenerator() {
+    protected AsmBackedClassGenerator createGenerator() {
         return new AsmBackedClassGenerator();
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerTest.groovy
deleted file mode 100644
index dbdfcd2..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerTest.groovy
+++ /dev/null
@@ -1,189 +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
-
-import org.junit.Test
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import org.junit.Ignore
-
-class AutoCreateDomainObjectContainerTest {
-    private final AutoCreateDomainObjectContainer container = new AsmBackedClassGenerator().newInstance(TestContainer.class)
-
-    @Test
-    public void canAddObjectWithName() {
-        container.add('obj')
-        assertThat(container.getByName('obj'), equalTo(['obj']))
-    }
-
-    @Test
-    public void canAddAndConfigureAnObjectWithName() {
-        container.add('obj') {
-            add(1)
-            add('value')
-        }
-        assertThat(container.getByName('obj'), equalTo(['obj', 1, 'value']))
-    }
-
-    @Test
-    public void failsToAddObjectWhenObjectWithSameNameAlreadyInContainer() {
-        container.add('obj')
-
-        try {
-            container.add('obj')
-            fail()
-        } catch (org.gradle.api.InvalidUserDataException e) {
-            assertThat(e.message, equalTo('Cannot add TestObject \'obj\' as a TestObject with that name already exists.'))
-        }
-    }
-
-    @Test
-    public void canConfigureExistingObject() {
-        container.add('list1')
-        container.configure {
-            list1 { add(1) }
-        }
-        assertThat(container.list1, equalTo(['list1', 1]))
-    }
-
-    @Test
-    public void propagatesNestedMissingMethodException() {
-        container.add('list1')
-        try {
-            container.configure {
-                list1 { unknown { anotherUnknown(2) } }
-            }
-        } catch (groovy.lang.MissingMethodException e) {
-            assertThat(e.method, equalTo('unknown'))
-            assertThat(e.type, equalTo(TestObject))
-        }
-    }
-
-    @Test
-    public void propagatesMethodInvocationException() {
-        RuntimeException failure = new RuntimeException()
-        try {
-            container.configure {
-                list1 { throw failure }
-            }
-        } catch (RuntimeException e) {
-            assertThat(e, sameInstance(failure))
-        }
-    }
-
-    @Test
-    public void implicitlyAddsAnObjectWhenContainerIsBeingConfigured() {
-        container.configure {
-            list1
-            list2 { add(1) }
-        }
-        assertThat(container.list1, equalTo(['list1']))
-        assertThat(container.list2, equalTo(['list2', 1]))
-    }
-
-    @Test
-    public void canReferToPropertiesAndMethodsOfOwner() {
-        new DynamicOwner().configure(container)
-        assertThat(container.asMap.keySet(), equalTo(['list1', 'list2'] as Set))
-        assertThat(container.list1, equalTo(['list1', 'dynamicProp', 'ownerProp', 'ownerMethod', 'dynamicMethod', 'dynamicMethod', 1, 'prop', 'testObjectDynamicMethod']))
-        assertThat(container.list1.prop, equalTo('prop'))
-        assertThat(container.list2, equalTo(['list2', container.list1]))
-    }
-
-    @Test @Ignore
-    public void canUseAnItemCalledMainInAScript() {
-        Script script = new GroovyShell().parse("""import org.gradle.util.ConfigureUtil
-            c.configure {
-                run
-                main { add(1) }
-            }
-
-""")
-        script.getBinding().setProperty("c", container)
-        script.run()
-
-        assertThat(container.run, equalTo(['run']))
-        assertThat(container.main, equalTo(['main', 1]))
-    }
-}
-
-class DynamicOwner {
-    Map values = [:]
-
-    def ownerMethod(String value) {
-        return value
-    }
-    
-    def getOwnerProp() {
-        return 'ownerProp'
-    }
-
-    def propertyMissing(String name) {
-        if (name == 'dynamicProp') {
-            return values[name]
-        }
-        throw new MissingPropertyException("fail")
-    }
-
-    def propertyMissing(String name, Object value) {
-        if (name == 'dynamicProp') {
-            values[name] = value
-            return
-        }
-        throw new MissingPropertyException("fail")
-    }
-
-    def methodMissing(String name, Object params) {
-        if (name == 'dynamicMethod') {
-            return 'dynamicMethod'
-        }
-        throw new groovy.lang.MissingMethodException(name, getClass(), params)
-    }
-
-    def configure(def container) {
-        container.configure {
-            list1 {
-                // owner properties and methods - owner is a DynamicOwner
-                dynamicProp = 'dynamicProp'
-                add dynamicProp
-                add ownerProp
-                add ownerMethod('ownerMethod')
-                add dynamicMethod('a', 'b', 'c')
-                add dynamicMethod { doesntGetEvaluated }
-                // delegate properties and methods - delegate is a TestObject
-                add all.size()
-                prop = 'prop'
-                add prop
-                testObjectDynamicMethod { doesntGetEvaluated }
-            }
-            list2 {
-                add list1
-            }
-        }
-    }
-}
-
-class TestObject extends ArrayList<String> {
-    def String prop
-
-    def methodMissing(String name, Object params) {
-        if (name == 'testObjectDynamicMethod') {
-            add(name)
-            return name
-        }
-        throw new groovy.lang.MissingMethodException(name, getClass(), params)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
index fbdbedf..a9c4c5a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
@@ -38,8 +38,10 @@ public class ChainingTransformerTest {
 
     @Test
     public void passesObjectToEachTransformerInTurn() {
-        final Transformer<String> transformerA = context.mock(Transformer.class, "transformerA");
-        final Transformer<String> transformerB = context.mock(Transformer.class, "transformerB");
+        @SuppressWarnings("unchecked")
+        final Transformer<String, String> transformerA = context.mock(Transformer.class, "transformerA");
+        @SuppressWarnings("unchecked")
+        final Transformer<String, String> transformerB = context.mock(Transformer.class, "transformerB");
 
         context.checking(new Expectations(){{
             one(transformerA).transform("original");
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy
new file mode 100644
index 0000000..3b53a6f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+
+class ClassGeneratorBackedInstantiatorTest extends Specification {
+    final ClassGenerator classGenerator = Mock()
+    final Instantiator target = Mock()
+    final ClassGeneratorBackedInstantiator instantiator = new ClassGeneratorBackedInstantiator(classGenerator, target)
+
+    def "decorates class and instantiates instance"() {
+        when:
+        def result = instantiator.newInstance(CharSequence.class, "test")
+
+        then:
+        result == "[test]"
+        1 * classGenerator.generate(CharSequence.class) >> String.class
+        1 * target.newInstance(String.class, "test") >> "[test]"
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy
new file mode 100644
index 0000000..379a394
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/CompositeDomainObjectSetTest.groovy
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.api.DomainObjectCollection
+
+import spock.lang.*
+
+class CompositeDomainObjectSetTest extends Specification {
+
+    Class type = String
+
+    protected collection(Object... entries) {
+        def collection = new DefaultDomainObjectCollection(type, new LinkedList())
+        entries.each { collection.add(it) }
+        collection
+    }
+
+    protected composite(DomainObjectCollection... collections) {
+        new CompositeDomainObjectSet(type, *collections)
+    }
+
+    def "empty composite contains no elements"() {
+        expect:
+        composite().empty
+    }
+
+    def "composite containing one collection"() {
+        expect:
+        composite(collection("a", "b")).toList() == ["a", "b"]
+    }
+
+    def "composite containing two collections"() {
+        expect:
+        composite(collection("a", "b"), collection("c", "d")).toList() == ["a", "b", "c", "d"]
+    }
+
+    def "combined collection contains additions and removals"() {
+        given:
+        def component1 = collection("a", "b")
+        def component2 = collection("c", "d")
+        def composite = composite(component1, component2)
+
+        expect:
+        composite.toList() == ["a", "b", "c", "d"]
+
+        when:
+        component1.add("e")
+        component2.remove("d")
+
+        then:
+        composite.toList() == ["a", "b", "e", "c"]
+    }
+
+    def "all action called for all existing items"() {
+        given:
+        def composite = composite(collection("a", "b"), collection("c", "d"))
+
+        when:
+        def calledFor = []
+        composite.all { calledFor << it }
+
+        then:
+        calledFor == ["a", "b", "c", "d"]
+    }
+
+    def "all callback called when items added to component of composite"() {
+        given:
+        def component1 = collection("a")
+        def component2 = collection("b")
+        def composite = composite(component1, component2)
+
+        when:
+        def calledFor = []
+        composite.all { calledFor << it }
+
+        then:
+        calledFor == ["a", "b"]
+
+        when:
+        component1.add("c")
+        component2.add("d")
+
+        then:
+        calledFor == ["a", "b", "c", "d"]
+    }
+
+    def "added callback called when items added to component of composite"() {
+        given:
+        def component1 = collection("a")
+        def component2 = collection("b")
+        def composite = composite(component1, component2)
+
+        when:
+        def calledFor = []
+        composite.whenObjectAdded { calledFor << it }
+
+        then:
+        calledFor == []
+
+        when:
+        component1.add("c")
+        component2.add("d")
+
+        then:
+        calledFor == ["c", "d"]
+    }
+
+    def "all callback called when component added to composite"() {
+        given:
+        def component1 = collection("a", "b")
+        def component2 = collection("c", "d")
+        def composite = composite(component1)
+
+        when:
+        def calledFor = []
+        composite.all { calledFor << it }
+
+        then:
+        calledFor == ["a", "b"]
+
+        when:
+        composite.addCollection(component2)
+
+        then:
+        calledFor == ["a", "b", "c", "d"]
+    }
+
+    def "removed callback called when removed from composite"() {
+        given:
+        def component1 = collection("a", "b")
+        def component2 = collection("c", "d")
+        def composite = composite(component1, component2)
+
+        when:
+        def calledFor = []
+        composite.whenObjectRemoved { calledFor << it }
+
+        then:
+        calledFor == []
+
+        when:
+        component1.remove("b")
+        component2.remove("d")
+
+        then:
+        calledFor == ["b", "d"]
+    }
+
+    def "filtered collection is live"() {
+        given:
+        def component1 = collection("a", "j")
+        def component2 = collection("b", "k")
+        def composite = composite(component1, component2)
+        def filtered = composite.matching { it > "d" }
+
+        expect:
+        filtered.toList() == ["j", "k"]
+
+        when:
+        component1.add("c")
+        component1.add("l")
+        component2.add("d")
+        component2.add("m")
+
+        then:
+        filtered.toList() == ["j", "l", "k", "m"]
+
+        when:
+        component1.remove("c")
+        component1.remove("l")
+        component2.remove("d")
+        component2.remove("m")
+
+        then:
+        filtered.toList() == ["j", "k"]
+
+        when:
+        composite.addCollection collection("c", "e")
+
+        then:
+        filtered.toList() == ["j", "k", "e"]
+    }
+
+    def "filtered collection callbacks live"() {
+        given:
+        def component1 = collection("a", "j")
+        def component2 = collection("b", "k")
+        def composite = composite(component1, component2)
+        def filtered = composite.matching { it > "d" }
+        def calledFor = []
+
+        when:
+        filtered.all { calledFor << it }
+
+        then:
+        calledFor == ["j", "k"]
+
+        when:
+        calledFor.clear()
+        component1.add("c")
+        component1.add("l")
+        component2.add("d")
+        component2.add("m")
+
+        then:
+        calledFor == ["l", "m"]
+
+        when:
+        calledFor.clear()
+
+        and:
+        filtered.whenObjectRemoved { calledFor << it }
+        component1.remove("c")
+        component1.remove("l")
+        component2.remove("d")
+        component2.remove("m")
+
+        then:
+        calledFor == ["l", "m"]
+
+        when:
+        calledFor.clear()
+        composite.addCollection collection("c", "e")
+
+        then:
+        calledFor == ["e"]
+    }
+
+    def "callbacks called for composite of composites"() {
+        given:
+        def component1 = collection("a")
+        def component2 = collection("b")
+        def component3 = collection("c")
+        def component4 = collection("d")
+
+        def composite1 = composite(component1, component2)
+        def composite2 = composite(component3, component4)
+
+        def superComposite = composite(composite1, composite2)
+
+        def calledFor = []
+
+        expect:
+        superComposite.toList() == ["a", "b", "c", "d"]
+
+        when:
+        superComposite.all { calledFor << it }
+
+        then:
+        calledFor == ["a", "b", "c", "d"]
+
+        when:
+        calledFor.clear()
+        component1.add("j")
+        component2.add("k")
+        component3.add("l")
+        component4.add("m")
+
+        then:
+        calledFor == ["j", "k", "l", "m"]
+
+        and:
+        superComposite.toList() == ["a", "j", "b", "k", "c", "l", "d", "m"]
+
+        when:
+        superComposite.whenObjectRemoved { calledFor << it }
+
+        and:
+        calledFor.clear()
+        component1.remove("j")
+        component2.remove("k")
+        component3.remove("l")
+        component4.remove("m")
+
+        then:
+        calledFor == ["j", "k", "l", "m"]
+
+        and:
+        superComposite.toList() == ["a", "b", "c", "d"]
+    }
+
+    def "filtered composite of composites is live"() {
+        given:
+        def component1 = collection("a")
+        def component2 = collection("b")
+        def component3 = collection("j")
+        def component4 = collection("k")
+
+        def composite1 = composite(component1, component2)
+        def composite2 = composite(component3, component4)
+
+        def superComposite = composite(composite1, composite2)
+        def filtered = superComposite.matching { it < "g" }
+
+        expect:
+        filtered.toList() == ["a", "b"]
+
+        when:
+        component1.add("j")
+        component2.add("k")
+        component3.add("c")
+        component4.add("d")
+
+        then:
+        filtered.toList() == ["a", "b", "c", "d"]
+
+        when:
+        component1.remove("j")
+        component2.remove("k")
+        component3.remove("c")
+        component4.remove("d")
+
+        then:
+        filtered.toList() == ["a", "b"]
+    }
+
+    def "duplicates in composite are flattened"() {
+        expect:
+        composite(collection("a", "b"), collection("b", "c"), collection("c", "d"))*.toString() == ["a", "b", "c", "d"]
+    }
+
+    def "add notifications are only fired for new in composite"() {
+        given:
+        def component1 = collection("a")
+        def component2 = collection("b")
+        def composite = composite(component1, component2)
+        def calledFor = []
+
+        when:
+        composite.whenObjectAdded { calledFor << it }
+
+        and:
+        component1 << "a" << "c"
+        component2 << "a" << "d"
+
+        then:
+        calledFor == ["c", "d"]
+    }
+
+    @IgnoreRest
+    def "all notifications are only fired once for each in composite"() {
+        given:
+        def component1 = collection("a")
+        def component2 = collection("a", "b")
+        def composite = composite(component1, component2)
+        def calledFor = []
+
+        when:
+        composite.all { calledFor << it }
+
+        then:
+        calledFor == ["a", "b"]
+
+        when:
+        component1 << "a" << "c"
+        component2 << "a" << "d"
+
+        then:
+        calledFor == ["a", "b", "c", "d"]
+    }
+
+    def "remove notifications are only fired for new in composite"() {
+        given:
+        def component1 = collection("a", "b")
+        def component2 = collection("a", "b", "c")
+        def composite = composite(component1, component2)
+        def calledFor = []
+
+        when:
+        composite.whenObjectRemoved { calledFor << it }
+
+        and:
+        component1.remove("a")
+        component2.remove("b")
+        component2.remove("c")
+
+        then:
+        calledFor == ["c"]
+    }
+
+    def "composite is immutable"() {
+        when:
+        composite(collection("a")).add("b")
+
+        then:
+        thrown UnsupportedOperationException
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
index dacea3c..4cd4991 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
@@ -17,22 +17,22 @@
 package org.gradle.api.internal;
 
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.plugins.DefaultConvention;
-import org.gradle.api.plugins.Convention;
-import org.gradle.api.tasks.ConventionValue;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.TestTask;
-import org.gradle.util.WrapUtil;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Test;
 
-import static java.util.Collections.*;
 import java.util.List;
 import java.util.concurrent.Callable;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+import static org.gradle.util.WrapUtil.toList;
+import static org.gradle.util.WrapUtil.toMap;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.*;
+
 /**
  * @author Hans Dockter
  */
@@ -43,39 +43,19 @@ public class ConventionAwareHelperTest {
 
     @Before public void setUp() {
         testTask = HelperUtil.createTask(TestTask.class);
-        conventionAware = new ConventionAwareHelper(testTask, new DefaultConvention());
-    }
-
-    @Test public void canMapPropertiesUsingConventionValue() {
-        final List expectedList1 = toList("a");
-        final List expectedList2 = toList("b");
-        assertNotNull(conventionAware.map("list1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                assertSame(conventionAware.getConvention(), convention);
-                assertSame(testTask, conventionAwareObject);
-                return expectedList1;
-            }
-        }));
-        assertNotNull(conventionAware.map("list2", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return expectedList2;
-            }
-        }));
-
-        assertSame(expectedList1, conventionAware.getConventionValue("list1"));
-        assertSame(expectedList2, conventionAware.getConventionValue("list2"));
+        conventionAware = new ConventionAwareHelper(testTask);
     }
 
     @Test
     public void canMapPropertiesUsingClosure() {
         conventionAware.map("list1", HelperUtil.toClosure("{ ['a'] }"));
-        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList("a")));
+        assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList("a")));
 
         conventionAware.map("list1", HelperUtil.toClosure("{ convention -> [convention] }"));
-        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList(conventionAware.getConvention())));
+        assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList(conventionAware.getConvention())));
 
         conventionAware.map("list1", HelperUtil.toClosure("{ convention, object -> [convention, object] }"));
-        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList(conventionAware.getConvention(), testTask)));
+        assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList(conventionAware.getConvention(), testTask)));
     }
 
     @Test
@@ -87,80 +67,83 @@ public class ConventionAwareHelperTest {
         };
 
         conventionAware.map("list1", callable);
-        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList("a")));
+        assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList("a")));
     }
     
     @Test
     public void canSetMappingUsingDynamicProperty() {
         HelperUtil.call("{ it.list1 = { ['a'] } }", conventionAware);
-        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList("a")));
+        assertThat(conventionAware.getConventionValue(null, "list1", false), equalTo((Object) toList("a")));
     }
     
     @Test (expected = InvalidUserDataException.class) public void cannotMapUnknownProperty() {
-        conventionAware.map(WrapUtil.<String, ConventionValue>toMap("unknownProp", null));
+        conventionAware.map("unknownProp", new Callable<Object>() {
+            public Object call() throws Exception {
+                throw new UnsupportedOperationException();
+            }
+        });
     }
 
     @Test public void canOverwriteProperties() {
         final List conventionList1 = toList("a");
-        conventionAware.map("list1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.map("list1", new Callable<Object>() {
+            public Object call() {
                 return conventionList1;
             }
         });
-        assertSame(conventionList1, conventionAware.getConventionValue("list1"));
+        assertSame(conventionList1, conventionAware.getConventionValue(null, "list1", false));
         List expectedList1 = toList("b");
-        testTask.setList1(expectedList1);
-        assertSame(expectedList1, conventionAware.getConventionValue("list1"));
+        assertSame(expectedList1, conventionAware.getConventionValue(expectedList1, "list1", true));
     }
 
     @Test public void canEnableCachingOfPropertyValue() {
-        conventionAware.map("list1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.map("list1", new Callable<Object>() {
+            public Object call() {
                 return toList("a");
             }
         }).cache();
-        assertSame(conventionAware.getConventionValue("list1"), conventionAware.getConventionValue("list1"));
+        assertSame(conventionAware.getConventionValue(null, "list1", false), conventionAware.getConventionValue(null, "list1", false));
     }
 
     @Test public void notCachesPropertyValuesByDefault() {
-        ConventionMapping.MappedProperty property = conventionAware.map("list1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.map("list1", new Callable<Object>() {
+            public Object call() {
                 return toList("a");
             }
         });
 
-        Object value1 = conventionAware.getConventionValue("list1");
-        Object value2 = conventionAware.getConventionValue("list1");
+        Object value1 = conventionAware.getConventionValue(null, "list1", false);
+        Object value2 = conventionAware.getConventionValue(null, "list1", false);
         assertEquals(value1, value2);
         assertNotSame(value1, value2);
     }
 
     @Test public void doesNotUseMappingWhenExplicitValueProvided() {
-        conventionAware.map("list1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.map("list1", new Callable<Object>() {
+            public Object call() {
                 throw new UnsupportedOperationException();
             }
         });
 
-        List<Object> value = toList();
+        List<Object> value = emptyList();
         assertThat(conventionAware.getConventionValue(value, "list1", true), sameInstance(value));
     }
-    
+
     @Test public void usesConventionValueForEmptyCollection() {
-        conventionAware.map("list1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.map("list1", new Callable<Object>() {
+            public Object call() {
                 return toList("a");
             }
         });
-        assertThat(conventionAware.getConventionValue(toList(), "list1"), equalTo((Object) toList("a")));
+        assertThat(conventionAware.getConventionValue(emptyList(), "list1", false), equalTo((Object) toList("a")));
     }
 
     @Test public void usesConventionValueForEmptyMap() {
-        conventionAware.map("map1", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        conventionAware.map("map1", new Callable<Object>() {
+            public Object call() {
                 return toMap("a", "b");
             }
         });
-        assertThat(conventionAware.getConventionValue(emptyMap(), "map1"), equalTo((Object) toMap("a", "b")));
+        assertThat(conventionAware.getConventionValue(emptyMap(), "map1", false), equalTo((Object) toMap("a", "b")));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultAutoCreateDomainObjectContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultAutoCreateDomainObjectContainerSpec.groovy
deleted file mode 100644
index de9aba5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultAutoCreateDomainObjectContainerSpec.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2011 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
-
-import spock.lang.Specification
-import org.gradle.api.NamedDomainObjectFactory
-
-class DefaultAutoCreateDomainObjectContainerSpec extends Specification {
-    final NamedDomainObjectFactory<String> factory = Mock()
-    final ClassGenerator classGenerator = Mock()
-
-    def usesFactoryToCreateContainerElements() {
-        def container = new DefaultAutoCreateDomainObjectContainer<String>(String.class, classGenerator, factory)
-
-        when:
-        def result = container.add('a')
-
-        then:
-        result == 'element a'
-        1 * factory.create('a') >> 'element a'
-        0 * _._
-    }
-
-    def usesPublicConstructorWhenNoFactorySupplied() {
-        def container = new DefaultAutoCreateDomainObjectContainer<String>(String.class, classGenerator)
-
-        when:
-        def result = container.add('a')
-
-        then:
-        result == 'a'
-        0 * _._
-    }
-
-    def usesClosureToCreateContainerElements() {
-        def cl = { name -> "element $name" as String }
-        def container = new DefaultAutoCreateDomainObjectContainer<String>(String.class, classGenerator, cl)
-
-        when:
-        def result = container.add('a')
-
-        then:
-        result == 'element a'
-        0 * _._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy
new file mode 100644
index 0000000..37659f1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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
+
+import spock.lang.Specification
+import org.gradle.util.ClassPath
+
+class DefaultClassPathRegistryTest extends Specification {
+    final ClassPathProvider provider1 = Mock()
+    final ClassPathProvider provider2 = Mock()
+    final DefaultClassPathRegistry registry = new DefaultClassPathRegistry(provider1, provider2)
+
+    def "fails for unknown classpath"() {
+        given:
+        provider1.findClassPath(_) >> null
+        provider2.findClassPath(_) >> null
+
+        when:
+        registry.getClassPath("name")
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == 'unknown classpath \'name\' requested.'
+    }
+
+    def "delegates to providers to find classpath"() {
+        def classpath = Mock(ClassPath)
+
+        given:
+        provider1.findClassPath(_) >> null
+        provider2.findClassPath("name") >> classpath
+
+        expect:
+        registry.getClassPath("name") == classpath
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java
new file mode 100755
index 0000000..5408f9c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectCollectionTest.java
@@ -0,0 +1,585 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TestClosure;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultDomainObjectCollectionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final DefaultDomainObjectCollection<CharSequence> container = new DefaultDomainObjectCollection<CharSequence>(CharSequence.class, new LinkedHashSet<CharSequence>());
+
+    @Test
+    public void canGetAllDomainObjectsForEmptyCollection() {
+        assertTrue(container.isEmpty());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsOrderedByOrderAdded() {
+        container.add("b");
+        container.add("a");
+        container.add("c");
+
+        assertThat(toList(container), equalTo(toList((CharSequence) "b", "a", "c")));
+    }
+
+    @Test
+    public void canIterateOverEmptyCollection() {
+        Iterator<CharSequence> iterator = container.iterator();
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canIterateOverDomainObjectsOrderedByOrderAdded() {
+        container.add("b");
+        container.add("a");
+        container.add("c");
+
+        Iterator<CharSequence> iterator = container.iterator();
+        assertThat(iterator.next(), equalTo((CharSequence) "b"));
+        assertThat(iterator.next(), equalTo((CharSequence) "a"));
+        assertThat(iterator.next(), equalTo((CharSequence) "c"));
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canGetAllMatchingDomainObjectsOrderedByOrderAdded() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+
+        container.add("a");
+        container.add("b");
+        container.add("c");
+
+        assertThat(toList(container.matching(spec)), equalTo(toList((CharSequence) "a", "c")));
+    }
+
+    @Test
+    public void getAllMatchingDomainObjectsReturnsEmptySetWhenNoMatches() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return false;
+            }
+        };
+
+        container.add("a");
+
+        assertTrue(container.matching(spec).isEmpty());
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichMeetSpec() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+        TestClosure testClosure = new TestClosure() {
+            public Object call(Object param) {
+                return !param.equals("b");    
+            }
+        };
+
+        container.add("a");
+        container.add("b");
+        container.add("c");
+
+        assertThat(toList(container.matching(spec)), equalTo(toList((CharSequence) "a", "c")));
+        assertThat(toList(container.matching(HelperUtil.toClosure(testClosure))), equalTo(toList((CharSequence) "a", "c")));
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichHaveType() {
+        container.add("c");
+        container.add("a");
+        container.add(new StringBuffer("b"));
+
+        assertThat(toList(container.withType(CharSequence.class)), equalTo(toList(container)));
+        assertThat(toList(container.withType(String.class)), equalTo(toList("c", "a")));
+    }
+
+    // @Test
+    // public void canExecuteActionForAllElementsInATypeFilteredCollection() {
+    //     final Action<CharSequence> action = context.mock(Action.class);
+    // 
+    //     container.add("c");
+    //     container.add(new StringBuffer("b"));
+    // 
+    //     context.checking(new Expectations(){{
+    //         one(action).execute("c");
+    //         one(action).execute("a");
+    //     }});
+    // 
+    //     container.withType(String.class, action);
+    //     container.add("a");
+    // }
+
+    @Test
+    public void canExecuteClosureForAllElementsInATypeFilteredCollection() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        container.add("c");
+        container.add(new StringBuffer("b"));
+
+        context.checking(new Expectations(){{
+            one(closure).call("c");
+            one(closure).call("a");
+        }});
+        
+        container.withType(String.class, HelperUtil.toClosure(closure));
+        container.add("a");
+    }
+
+    @Test
+    public void filteredCollectionIsLive() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("a");
+            }
+        };
+
+        container.add("a");
+
+        DomainObjectCollection<CharSequence> filteredCollection = container.matching(spec);
+        assertTrue(filteredCollection.isEmpty());
+
+        container.add("b");
+        container.add("c");
+
+        assertThat(toList(filteredCollection), equalTo(toList((CharSequence) "b", "c")));
+    }
+
+    @Test
+    public void filteredCollectionExecutesActionWhenMatchingObjectAdded() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(action);
+
+        container.add("a");
+        container.add("b");
+    }
+
+    @Test
+    public void filteredCollectionExecutesClosureWhenMatchingObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
+
+        container.add("a");
+        container.add("b");
+    }
+
+    @Test
+    public void canChainFilteredCollections() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+        Spec<String> spec2 = new Spec<String>() {
+            public boolean isSatisfiedBy(String element) {
+                return !element.equals("c");
+            }
+        };
+
+        container.add("a");
+        container.add("b");
+        container.add("c");
+        container.add(new StringBuffer("d"));
+
+        DomainObjectCollection<String> collection = container.matching(spec).withType(String.class).matching(spec2);
+        assertThat(toList(collection), equalTo(toList("a")));
+    }
+
+    @Test
+    public void findAllRetainsIterationOrder() {
+        container.add("a");
+        container.add("b");
+        container.add("c");
+
+        Collection<CharSequence> collection = container.findAll(HelperUtil.toClosure("{ it != 'b' }"));
+        assertThat(collection, instanceOf(List.class));
+        assertThat(collection, equalTo((Collection) toList("a", "c")));
+    }
+
+    @Test
+    public void findAllDoesNotReturnALiveCollection() {
+        container.add("a");
+        container.add("b");
+        container.add("c");
+
+        Collection<CharSequence> collection = container.findAll(HelperUtil.toClosure("{ it != 'b' }"));
+
+        container.add("d");
+        assertThat(collection, equalTo((Collection) toList("a", "c")));
+    }
+    
+    @Test
+    public void callsActionWhenObjectAdded() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        container.whenObjectAdded(action);
+        container.add("a");
+    }
+
+    @Test
+    public void callsClosureWithNewObjectAsParameterWhenObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.whenObjectAdded(HelperUtil.toClosure(closure));
+        container.add("a");
+    }
+
+    @Test
+    public void callsClosureWithRemovedObjectAsParameterWhenObjectRemoved() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        container.add("a");
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.whenObjectRemoved(HelperUtil.toClosure(closure));
+        container.remove("a");
+    }
+
+    @Test
+    public void callsClosureWithNewObjectAsDelegateWhenObjectAdded() {
+        container.whenObjectAdded(HelperUtil.toClosure("{ assert delegate == 'a' }"));
+        container.add("a");
+    }
+
+    @Test
+    public void callsRemoveActionWhenObjectRemoved() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+        final String original = "a";
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(sameInstance(original)));
+        }});
+
+        container.whenObjectRemoved(action);
+        container.add(original);
+        assertTrue(container.remove(original));
+    }
+
+    @Test
+    public void callsRemoveActionWhenObjectRemovedUsingIterator() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        container.whenObjectRemoved(action);
+        container.add("a");
+        container.add("b");
+
+        Iterator<CharSequence> iterator = container.iterator();
+        iterator.next();
+        iterator.next();
+
+        context.checking(new Expectations() {{
+            one(action).execute("b");
+        }});
+
+        iterator.remove();
+    }
+
+    @Test
+    public void allCallsActionForEachExistingObject() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        container.add("a");
+        container.all(action);
+    }
+
+    @Test
+    public void allCallsClosureForEachExistingObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.add("a");
+        container.all(HelperUtil.toClosure(closure));
+    }
+
+    @Test
+    public void allCallsActionForEachNewObject() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        container.all(action);
+        container.add("a");
+    }
+
+    @Test
+    public void allCallsClosureForEachNewObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.all(HelperUtil.toClosure(closure));
+        container.add("a");
+    }
+
+    @Test
+    public void allCallsClosureWithObjectAsDelegate() {
+        container.all(HelperUtil.toClosure(" { assert delegate == 'a' } "));
+        container.add("a");
+    }
+
+    @Test
+    public void allCallsActionForEachNewObjectAddedByTheAction() {
+        @SuppressWarnings("unchecked")
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+            will(new org.jmock.api.Action() {
+                public Object invoke(Invocation invocation) throws Throwable {
+                    container.add("c");
+                    return null;
+                }
+
+                public void describeTo(Description description) {
+                    description.appendText("add 'c'");
+                }
+            });
+            one(action).execute("b");
+            one(action).execute("c");
+        }});
+
+        container.add("a");
+        container.add("b");
+        container.all(action);
+    }
+
+    @Test
+    public void callsVetoActionBeforeObjectIsAdded() {
+        final Runnable action = context.mock(Runnable.class);
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        container.add("a");
+    }
+
+    @Test
+    public void objectIsNotAddedWhenVetoActionThrowsAnException() {
+        final Runnable action = context.mock(Runnable.class);
+        final RuntimeException failure = new RuntimeException();
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+            will(throwException(failure));
+        }});
+
+        try {
+            container.add("a");
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+
+        assertThat(container, not(hasItem((CharSequence) "a")));
+    }
+
+    @Test
+    public void callsVetoActionOnceBeforeCollectionIsAdded() {
+        final Runnable action = context.mock(Runnable.class);
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        container.addAll(toList("a", "b"));
+    }
+
+    @Test
+    public void callsVetoActionBeforeObjectIsRemoved() {
+        final Runnable action = context.mock(Runnable.class);
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        container.remove("a");
+    }
+
+    @Test
+    public void callsVetoActionBeforeObjectIsRemovedUsingIterator() {
+        final Runnable action = context.mock(Runnable.class);
+
+        container.add("a");
+        container.beforeChange(action);
+
+        Iterator<CharSequence> iterator = container.iterator();
+        iterator.next();
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        iterator.remove();
+    }
+
+    @Test
+    public void objectIsNotRemovedWhenVetoActionThrowsAnException() {
+        final Runnable action = context.mock(Runnable.class);
+        final RuntimeException failure = new RuntimeException();
+        container.add("a");
+        container.beforeChange(action);
+        
+        context.checking(new Expectations() {{
+            one(action).run();
+            will(throwException(failure));
+        }});
+
+        try {
+            container.remove("a");
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+
+        assertThat(container, hasItem((CharSequence) "a"));
+    }
+
+    @Test
+    public void callsVetoActionBeforeCollectionIsCleared() {
+        final Runnable action = context.mock(Runnable.class);
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        container.clear();
+    }
+
+    @Test
+    public void callsVetoActionOnceBeforeCollectionIsRemoved() {
+        final Runnable action = context.mock(Runnable.class);
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        container.removeAll(toList("a", "b"));
+    }
+
+    @Test
+    public void callsVetoActionOnceBeforeCollectionIsIntersected() {
+        final Runnable action = context.mock(Runnable.class);
+        container.add("a");
+        container.add("b");
+        container.beforeChange(action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+
+        container.retainAll(toList());
+    }
+
+    @Test
+    public void canRemoveAndMaintainOrder() {
+        container.add("b");
+        container.add("a");
+        container.add("c");
+
+        assertTrue(container.remove("a"));
+        assertThat(toList(container), equalTo(toList((CharSequence) "b", "c")));
+    }
+
+    @Test
+    public void canRemoveNonExistentObject() {
+        assertFalse(container.remove("a"));
+    }
+    
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectContainerTest.java
deleted file mode 100644
index 0be7f99..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectContainerTest.java
+++ /dev/null
@@ -1,370 +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;
-
-import org.gradle.api.Action;
-import org.gradle.api.DomainObjectCollection;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.TestClosure;
-import org.hamcrest.Description;
-import org.jmock.Expectations;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Iterator;
-
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultDomainObjectContainerTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final DefaultDomainObjectContainer<CharSequence> container = new DefaultDomainObjectContainer<CharSequence>(CharSequence.class);
-
-    @Test
-    public void canGetAllDomainObjectsForEmptyCollection() {
-        assertTrue(container.getAll().isEmpty());
-    }
-
-    @Test
-    public void canGetAllDomainObjectsOrderedByOrderAdded() {
-        container.addObject("b");
-        container.addObject("a");
-        container.addObject("c");
-
-        assertThat(container.getAll(), equalTo(toLinkedSet((CharSequence) "b", "a", "c")));
-    }
-
-    @Test
-    public void canIterateOverEmptyCollection() {
-        Iterator<CharSequence> iterator = container.iterator();
-        assertFalse(iterator.hasNext());
-    }
-
-    @Test
-    public void canIterateOverDomainObjectsOrderedByOrderAdded() {
-        container.addObject("b");
-        container.addObject("a");
-        container.addObject("c");
-
-        Iterator<CharSequence> iterator = container.iterator();
-        assertThat(iterator.next(), equalTo((CharSequence) "b"));
-        assertThat(iterator.next(), equalTo((CharSequence) "a"));
-        assertThat(iterator.next(), equalTo((CharSequence) "c"));
-        assertFalse(iterator.hasNext());
-    }
-
-    @Test
-    public void canGetAllMatchingDomainObjectsOrderedByOrderAdded() {
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return !element.equals("b");
-            }
-        };
-
-        container.addObject("a");
-        container.addObject("b");
-        container.addObject("c");
-
-        assertThat(container.findAll(spec), equalTo(toLinkedSet((CharSequence) "a", "c")));
-    }
-
-    @Test
-    public void getAllMatchingDomainObjectsReturnsEmptySetWhenNoMatches() {
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return false;
-            }
-        };
-
-        container.addObject("a");
-
-        assertTrue(container.findAll(spec).isEmpty());
-    }
-
-    @Test
-    public void canGetFilteredCollectionContainingAllObjectsWhichMeetSpec() {
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return !element.equals("b");
-            }
-        };
-        TestClosure testClosure = new TestClosure() {
-            public Object call(Object param) {
-                return !param.equals("b");    
-            }
-        };
-
-        container.addObject("a");
-        container.addObject("b");
-        container.addObject("c");
-
-        assertThat(container.matching(spec).getAll(), equalTo(toLinkedSet((CharSequence) "a", "c")));
-        assertThat(container.matching(HelperUtil.toClosure(testClosure)).getAll(), equalTo(toLinkedSet((CharSequence) "a", "c")));
-    }
-
-    @Test
-    public void canGetFilteredCollectionContainingAllObjectsWhichHaveType() {
-        container.addObject("c");
-        container.addObject("a");
-        container.addObject(new StringBuffer("b"));
-
-        assertThat(container.withType(CharSequence.class).getAll(), equalTo(container.getAll()));
-        assertThat(container.withType(String.class).getAll(), equalTo(toLinkedSet("c", "a")));
-    }
-
-    @Test
-    public void canExecuteActionForAllElementsInATypeFilteredCollection() {
-        final Action<CharSequence> action = context.mock(Action.class);
-
-        container.addObject("c");
-        container.addObject(new StringBuffer("b"));
-
-        context.checking(new Expectations(){{
-            one(action).execute("c");
-            one(action).execute("a");
-        }});
-
-        container.withType(String.class, action);
-        container.addObject("a");
-    }
-
-    @Test
-    public void canExecuteClosureForAllElementsInATypeFilteredCollection() {
-        final TestClosure closure = context.mock(TestClosure.class);
-
-        container.addObject("c");
-        container.addObject(new StringBuffer("b"));
-
-        context.checking(new Expectations(){{
-            one(closure).call("c");
-            one(closure).call("a");
-        }});
-        
-        container.withType(String.class, HelperUtil.toClosure(closure));
-        container.addObject("a");
-    }
-
-    @Test
-    public void filteredCollectionIsLive() {
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return !element.equals("a");
-            }
-        };
-
-        container.addObject("a");
-
-        DomainObjectCollection<CharSequence> filteredCollection = container.matching(spec);
-        assertTrue(filteredCollection.getAll().isEmpty());
-
-        container.addObject("b");
-        container.addObject("c");
-
-        assertThat(filteredCollection.getAll(), equalTo(toLinkedSet((CharSequence) "b", "c")));
-    }
-
-    @Test
-    public void filteredCollectionExecutesActionWhenMatchingObjectAdded() {
-        final Action<CharSequence> action = context.mock(Action.class);
-
-        context.checking(new Expectations() {{
-            one(action).execute("a");
-        }});
-
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return !element.equals("b");
-            }
-        };
-
-        container.matching(spec).whenObjectAdded(action);
-
-        container.addObject("a");
-        container.addObject("b");
-    }
-
-    @Test
-    public void filteredCollectionExecutesClosureWhenMatchingObjectAdded() {
-        final TestClosure closure = context.mock(TestClosure.class);
-
-        context.checking(new Expectations() {{
-            one(closure).call("a");
-        }});
-
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return !element.equals("b");
-            }
-        };
-
-        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
-
-        container.addObject("a");
-        container.addObject("b");
-    }
-
-    @Test
-    public void canChainFilteredCollections() {
-        Spec<CharSequence> spec = new Spec<CharSequence>() {
-            public boolean isSatisfiedBy(CharSequence element) {
-                return !element.equals("b");
-            }
-        };
-        Spec<String> spec2 = new Spec<String>() {
-            public boolean isSatisfiedBy(String element) {
-                return !element.equals("c");
-            }
-        };
-
-        container.addObject("a");
-        container.addObject("b");
-        container.addObject("c");
-        container.addObject(new StringBuffer("d"));
-
-        DomainObjectCollection<String> collection = container.matching(spec).withType(String.class).matching(spec2);
-        assertThat(collection.getAll(), equalTo(toSet("a")));
-    }
-
-    @Test
-    public void callsActionWhenObjectAdded() {
-        final Action<CharSequence> action = context.mock(Action.class);
-
-        context.checking(new Expectations() {{
-            one(action).execute("a");
-        }});
-
-        container.whenObjectAdded(action);
-        container.addObject("a");
-    }
-
-    @Test
-    public void callsClosureWithNewObjectAsParameterWhenObjectAdded() {
-        final TestClosure closure = context.mock(TestClosure.class);
-
-        context.checking(new Expectations() {{
-            one(closure).call("a");
-        }});
-
-        container.whenObjectAdded(HelperUtil.toClosure(closure));
-        container.addObject("a");
-    }
-
-    @Test
-    public void callsClosureWithNewObjectAsDelegateWhenObjectAdded() {
-        container.whenObjectAdded(HelperUtil.toClosure("{ assert delegate == 'a' }"));
-        container.addObject("a");
-    }
-
-    @Test
-    public void callsActionWhenObjectRemoved() {
-        final Action<CharSequence> action = context.mock(Action.class);
-        final String original = "a";
-
-        context.checking(new Expectations() {{
-            one(action).execute(with(sameInstance(original)));
-        }});
-
-        container.whenObjectRemoved(action);
-        container.addObject(original);
-        container.addObject("a");
-    }
-
-    @Test
-    public void allCallsActionForEachExistingObject() {
-        final Action<CharSequence> action = context.mock(Action.class);
-
-        context.checking(new Expectations() {{
-            one(action).execute("a");
-        }});
-
-        container.addObject("a");
-        container.all(action);
-    }
-
-    @Test
-    public void allCallsClosureForEachExistingObject() {
-        final TestClosure closure = context.mock(TestClosure.class);
-
-        context.checking(new Expectations() {{
-            one(closure).call("a");
-        }});
-
-        container.addObject("a");
-        container.all(HelperUtil.toClosure(closure));
-    }
-
-    @Test
-    public void allCallsActionForEachNewObject() {
-        final Action<CharSequence> action = context.mock(Action.class);
-
-        context.checking(new Expectations() {{
-            one(action).execute("a");
-        }});
-
-        container.all(action);
-        container.addObject("a");
-    }
-
-    @Test
-    public void allCallsClosureForEachNewObject() {
-        final TestClosure closure = context.mock(TestClosure.class);
-
-        context.checking(new Expectations() {{
-            one(closure).call("a");
-        }});
-
-        container.all(HelperUtil.toClosure(closure));
-        container.addObject("a");
-    }
-
-    @Test
-    public void allCallsClosureWithObjectAsDelegate() {
-        container.all(HelperUtil.toClosure(" { assert delegate == 'a' } "));
-        container.addObject("a");
-    }
-
-    @Test
-    public void allCallsActionForEachNewObjectAddedByTheAction() {
-        final Action<CharSequence> action = context.mock(Action.class);
-
-        context.checking(new Expectations() {{
-            one(action).execute("a");
-            will(new org.jmock.api.Action() {
-                public Object invoke(Invocation invocation) throws Throwable {
-                    container.addObject("c");
-                    return null;
-                }
-
-                public void describeTo(Description description) {
-                    description.appendText("add 'c'");
-                }
-            });
-            one(action).execute("b");
-            one(action).execute("c");
-        }});
-
-        container.addObject("a");
-        container.addObject("b");
-        container.all(action);
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectSetTest.groovy
new file mode 100644
index 0000000..d723972
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectSetTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+
+class DefaultDomainObjectSetTest extends Specification {
+    def "findAll() filters elements and retains iteration order"() {
+        def set = new DefaultDomainObjectSet<String>(String.class)
+        set.add("a")
+        set.add("b")
+        set.add("c")
+        set.add("d")
+
+        expect:
+        set.findAll { it != "c" } == ["a", "b", "d"] as LinkedHashSet
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainerTest.java
deleted file mode 100644
index 0b64cad..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainerTest.java
+++ /dev/null
@@ -1,664 +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;
-
-import groovy.lang.Closure;
-import groovy.lang.MissingPropertyException;
-import org.gradle.api.Action;
-import org.gradle.api.DomainObjectCollection;
-import org.gradle.api.Rule;
-import org.gradle.api.UnknownDomainObjectException;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.util.*;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Iterator;
-
-import static org.gradle.util.HelperUtil.*;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultNamedDomainObjectContainerTest {
-    private final ClassGenerator classGenerator = new AsmBackedClassGenerator();
-    private final DefaultNamedDomainObjectContainer<Bean> container = classGenerator.newInstance(DefaultNamedDomainObjectContainer.class, Bean.class, classGenerator);
-    private final JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void usesTypeNameToGenerateDisplayName() {
-        assertThat(container.getTypeDisplayName(), equalTo("Bean"));
-        assertThat(container.getDisplayName(), equalTo("Bean container"));
-    }
-
-    @Test
-    public void canGetAllDomainObjectsForEmptyContainer() {
-        assertTrue(container.getAll().isEmpty());
-    }
-
-    @Test
-    public void canGetAllDomainObjectsOrderedByName() {
-        Bean bean1 = new Bean();
-        Bean bean2 = new Bean();
-        Bean bean3 = new Bean();
-
-        container.addObject("b", bean2);
-        container.addObject("a", bean1);
-        container.addObject("c", bean3);
-
-        assertThat(container.getAll(), equalTo(toLinkedSet(bean1, bean2, bean3)));
-    }
-
-    @Test
-    public void canIterateOverEmptyContainer() {
-        Iterator<Bean> iterator = container.iterator();
-        assertFalse(iterator.hasNext());
-    }
-
-    @Test
-    public void canIterateOverDomainObjectsOrderedByName() {
-        Bean bean1 = new Bean();
-        Bean bean2 = new Bean();
-        Bean bean3 = new Bean();
-
-        container.addObject("b", bean2);
-        container.addObject("a", bean1);
-        container.addObject("c", bean3);
-
-        Iterator<Bean> iterator = container.iterator();
-        assertThat(iterator.next(), sameInstance(bean1));
-        assertThat(iterator.next(), sameInstance(bean2));
-        assertThat(iterator.next(), sameInstance(bean3));
-        assertFalse(iterator.hasNext());
-    }
-
-    @Test
-    public void canGetAllDomainObjectsAsMapForEmptyContainer() {
-        assertTrue(container.getAsMap().isEmpty());
-    }
-
-    @Test
-    public void canGetAllDomainObjectsAsMap() {
-        Bean bean1 = new Bean();
-        Bean bean2 = new Bean();
-        Bean bean3 = new Bean();
-
-        container.addObject("b", bean2);
-        container.addObject("a", bean1);
-        container.addObject("c", bean3);
-
-        assertThat(container.getAsMap(), equalTo(GUtil.map("a", bean1, "b", bean2, "c", bean3)));
-    }
-
-    @Test
-    public void canGetAllMatchingDomainObjectsOrderedByName() {
-        Bean bean1 = new Bean();
-        final Bean bean2 = new Bean();
-        Bean bean3 = new Bean();
-
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element == bean2;
-            }
-        };
-
-        container.addObject("a", bean1);
-        container.addObject("b", bean2);
-        container.addObject("c", bean3);
-
-        assertThat(container.findAll(spec), equalTo(toLinkedSet(bean2)));
-    }
-
-    @Test
-    public void getAllMatchingDomainObjectsReturnsEmptySetWhenNoMatches() {
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return false;
-            }
-        };
-
-        container.addObject("a", new Bean());
-
-        assertTrue(container.findAll(spec).isEmpty());
-    }
-
-    @Test
-    public void canGetFilteredCollectionContainingAllObjectsWhichMeetSpec() {
-        final Bean bean1 = new Bean();
-        Bean bean2 = new Bean();
-        Bean bean3 = new Bean();
-
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element != bean1;
-            }
-        };
-
-        TestClosure testClosure = new TestClosure() {
-            public Object call(Object param) {
-                return param != bean1;
-            }
-        };
-
-        container.addObject("a", bean1);
-        container.addObject("b", bean2);
-        container.addObject("c", bean3);
-
-        assertThat(container.matching(spec).getAll(), equalTo(toLinkedSet(bean2, bean3)));
-        assertThat(container.matching(HelperUtil.toClosure(testClosure)).getAll(), equalTo(toLinkedSet(bean2, bean3)));
-        assertThat(container.matching(spec).findByName("a"), nullValue());
-        assertThat(container.matching(spec).findByName("b"), sameInstance(bean2));
-    }
-
-    @Test
-    public void canGetFilteredCollectionContainingAllObjectsWhichHaveType() {
-        class OtherBean extends Bean {
-        }
-        Bean bean1 = new Bean();
-        OtherBean bean2 = new OtherBean();
-        Bean bean3 = new Bean();
-
-        container.addObject("c", bean3);
-        container.addObject("a", bean1);
-        container.addObject("b", bean2);
-
-        assertThat(container.withType(Bean.class).getAll(), equalTo(toLinkedSet(bean1, bean2, bean3)));
-        assertThat(container.withType(OtherBean.class).getAll(), equalTo(toLinkedSet(bean2)));
-        assertThat(container.withType(OtherBean.class).findByName("a"), nullValue());
-        assertThat(container.withType(OtherBean.class).findByName("b"), sameInstance(bean2));
-    }
-
-    @Test
-    public void canExecuteActionForAllElementsInATypeFilteredCollection() {
-        class OtherBean extends Bean {
-        }
-        final Action<OtherBean> action = context.mock(Action.class);
-        Bean bean1 = new Bean();
-        final OtherBean bean2 = new OtherBean();
-
-        container.addObject("a", bean1);
-        container.addObject("b", bean2);
-
-        context.checking(new Expectations(){{
-            one(action).execute(bean2);
-        }});
-
-        container.withType(OtherBean.class, action);
-    }
-
-    @Test
-    public void canExecuteClosureForAllElementsInATypeFilteredCollection() {
-        class OtherBean extends Bean {
-        }
-        final TestClosure closure = context.mock(TestClosure.class);
-        Bean bean1 = new Bean();
-        final OtherBean bean2 = new OtherBean();
-
-        container.addObject("a", bean1);
-        container.addObject("b", bean2);
-
-        context.checking(new Expectations(){{
-            one(closure).call(bean2);
-        }});
-
-        container.withType(OtherBean.class, HelperUtil.toClosure(closure));
-    }
-
-    @Test
-    public void filteredCollectionIsLive() {
-        final Bean bean1 = new Bean();
-        Bean bean2 = new Bean();
-        Bean bean3 = new Bean();
-        Bean bean4 = new Bean();
-
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element != bean1;
-            }
-        };
-
-        container.addObject("a", bean1);
-
-        DomainObjectCollection<Bean> filteredCollection = container.matching(spec);
-        assertTrue(filteredCollection.getAll().isEmpty());
-
-        container.addObject("b", bean2);
-        container.addObject("c", bean3);
-
-        assertThat(filteredCollection.getAll(), equalTo(toLinkedSet(bean2, bean3)));
-
-        container.addObject("c", bean4);
-
-        assertThat(filteredCollection.getAll(), equalTo(toLinkedSet(bean2, bean4)));
-    }
-
-    @Test
-    public void filteredCollectionExecutesActionWhenMatchingObjectAdded() {
-        final Action<Bean> action = context.mock(Action.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(action).execute(bean);
-        }});
-
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element == bean;
-            }
-        };
-
-        container.matching(spec).whenObjectAdded(action);
-
-        container.addObject("bean", bean);
-        container.addObject("bean2", new Bean());
-    }
-
-    @Test
-    public void filteredCollectionExecutesClosureWhenMatchingObjectAdded() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(closure).call(bean);
-        }});
-
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element == bean;
-            }
-        };
-
-        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
-
-        container.addObject("bean", bean);
-        container.addObject("bean2", new Bean());
-    }
-
-    @Test
-    public void canChainFilteredCollections() {
-        final Bean bean = new Bean();
-        final Bean bean2 = new Bean();
-        final Bean bean3 = new Bean();
-
-        Spec<Bean> spec = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element != bean;
-            }
-        };
-        Spec<Bean> spec2 = new Spec<Bean>() {
-            public boolean isSatisfiedBy(Bean element) {
-                return element != bean2;
-            }
-        };
-
-        container.addObject("a", bean);
-        container.addObject("b", bean2);
-        container.addObject("c", bean3);
-
-        DomainObjectCollection<Bean> collection = container.matching(spec).matching(spec2);
-        assertThat(collection.getAll(), equalTo(toSet(bean3)));
-    }
-
-    @Test
-    public void canGetDomainObjectByName() {
-        Bean bean = new Bean();
-        container.addObject("a", bean);
-
-        assertThat(container.getByName("a"), sameInstance(bean));
-        assertThat(container.getAt("a"), sameInstance(bean));
-    }
-
-    @Test
-    public void getDomainObjectByNameFailsForUnknownDomainObject() {
-        try {
-            container.getByName("unknown");
-            fail();
-        } catch (UnknownDomainObjectException e) {
-            assertThat(e.getMessage(), equalTo("Bean with name 'unknown' not found."));
-        }
-    }
-
-    @Test
-    public void getDomainObjectInvokesRuleForUnknownDomainObject() {
-        Bean bean = new Bean();
-        addRuleFor(bean);
-
-        assertThat(container.getByName("bean"), sameInstance(bean));
-    }
-
-    @Test
-    public void canConfigureDomainObjectByName() {
-        Bean bean = new Bean();
-        container.addObject("a", bean);
-
-        assertThat(container.getByName("a", toClosure("{ beanProperty = 'hi' }")), sameInstance(bean));
-        assertThat(bean.getBeanProperty(), equalTo("hi"));
-    }
-
-    @Test
-    public void configureDomainObjectInvokesRuleForUnknownDomainObject() {
-        Bean bean = new Bean();
-        addRuleFor(bean);
-
-        assertThat(container.getByName("bean", toClosure("{ beanProperty = 'hi' }")), sameInstance(bean));
-        assertThat(bean.getBeanProperty(), equalTo("hi"));
-    }
-
-    @Test
-    public void canFindDomainObjectByName() {
-        Bean bean = new Bean();
-        container.addObject("a", bean);
-
-        assertThat(container.findByName("a"), sameInstance(bean));
-    }
-
-    @Test
-    public void findDomainObjectByNameReturnsNullForUnknownDomainObject() {
-        assertThat(container.findByName("a"), nullValue());
-    }
-
-    @Test
-    public void findDomainObjectByNameInvokesRulesForUnknownDomainObject() {
-        Bean bean = new Bean();
-        addRuleFor(bean);
-
-        assertThat(container.findByName("bean"), sameInstance(bean));
-    }
-
-    @Test
-    public void findDomainObjectByNameInvokesNestedRulesOnlyOnceForUnknownDomainObject() {
-        final Bean bean1 = new Bean();
-        final Bean bean2 = new Bean();
-        container.addRule(new Rule() {
-            public String getDescription() {
-                return "rule1";
-            }
-
-            public void apply(String domainObjectName) {
-                if (domainObjectName.equals("bean1")) {
-                    container.addObject("bean1", bean1);
-                }
-            }
-        });
-        container.addRule(new Rule() {
-            private boolean applyHasBeenCalled;
-
-            public String getDescription() {
-                return "rule2";
-            }
-
-            public void apply(String domainObjectName) {
-                if (domainObjectName.equals("bean2")) {
-                    assertThat(applyHasBeenCalled, equalTo(false));
-                    container.findByName("bean1");
-                    container.findByName("bean2");
-                    container.addObject("bean2", bean2);
-                    applyHasBeenCalled = true;
-                }
-            }
-        });
-        container.findByName("bean2");
-        assertThat(container.getAll(), equalTo(WrapUtil.toSet(bean1, bean2)));
-    }
-
-    @Test
-    public void callsActionWhenObjectAdded() {
-        final Action<Bean> action = context.mock(Action.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(action).execute(bean);
-        }});
-
-        container.whenObjectAdded(action);
-        container.addObject("bean", bean);
-    }
-
-    @Test
-    public void callsClosureWhenObjectAdded() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(closure).call(bean);
-        }});
-
-        container.whenObjectAdded(HelperUtil.toClosure(closure));
-        container.addObject("bean", bean);
-    }
-
-    @Test
-    public void callsActionWhenObjectRemoved() {
-        final Action<Bean> action = context.mock(Action.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(action).execute(bean);
-        }});
-
-        container.whenObjectRemoved(action);
-        container.addObject("bean", bean);
-        container.addObject("bean", new Bean());
-    }
-
-    @Test
-    public void allCallsActionForEachExistingObject() {
-        final Action<Bean> action = context.mock(Action.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(action).execute(bean);
-        }});
-
-        container.addObject("bean", bean);
-        container.all(action);
-    }
-
-    @Test
-    public void allCallsClosureForEachExistingObject() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(closure).call(bean);
-        }});
-
-        container.addObject("bean", bean);
-        container.all(HelperUtil.toClosure(closure));
-    }
-
-    @Test
-    public void allCallsActionForEachNewObject() {
-        final Action<Bean> action = context.mock(Action.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(action).execute(bean);
-        }});
-
-        container.all(action);
-        container.addObject("bean", bean);
-    }
-
-    @Test
-    public void allCallsClosureForEachNewObject() {
-        final TestClosure closure = context.mock(TestClosure.class);
-        final Bean bean = new Bean();
-
-        context.checking(new Expectations() {{
-            one(closure).call(bean);
-        }});
-
-        container.all(HelperUtil.toClosure(closure));
-        container.addObject("bean", bean);
-    }
-
-    @Test
-    public void eachObjectIsAvailableAsADynamicProperty() {
-        Bean bean = new Bean();
-        container.addObject("child", bean);
-        assertTrue(container.getAsDynamicObject().hasProperty("child"));
-        assertThat(container.getAsDynamicObject().getProperty("child"), sameInstance((Object) bean));
-        assertThat(container.getAsDynamicObject().getProperties().get("child"), sameInstance((Object) bean));
-        assertThat(call("{ it.child }", container), sameInstance((Object) bean));
-        assertThat(call("{ it.child }", container.withType(Bean.class)), sameInstance((Object) bean));
-        assertThat(call("{ it.child }", container.matching(Specs.satisfyAll())), sameInstance((Object) bean));
-    }
-
-    @Test
-    public void eachObjectIsAvailableUsingAnIndex() {
-        Bean bean = new Bean();
-        container.addObject("child", bean);
-        assertThat(call("{ it['child'] }", container), sameInstance((Object) bean));
-    }
-
-    @Test
-    public void cannotGetUnknownProperty() {
-        assertFalse(container.getAsDynamicObject().hasProperty("unknown"));
-
-        try {
-            container.getAsDynamicObject().getProperty("unknown");
-            fail();
-        } catch (MissingPropertyException e) {
-            // expected
-        }
-    }
-
-    @Test
-    public void dynamicPropertyAccessInvokesRulesForUnknownDomainObject() {
-        Bean bean = new Bean();
-        addRuleFor(bean);
-
-        assertTrue(container.getAsDynamicObject().hasProperty("bean"));
-        assertThat(container.getAsDynamicObject().getProperty("bean"), sameInstance((Object) bean));
-    }
-
-    @Test
-    public void eachObjectIsAvailableAsConfigureMethod() {
-        Bean bean = new Bean();
-        container.addObject("child", bean);
-
-        Closure closure = toClosure("{ beanProperty = 'value' }");
-        assertTrue(container.getAsDynamicObject().hasMethod("child", closure));
-        container.getAsDynamicObject().invokeMethod("child", closure);
-        assertThat(bean.getBeanProperty(), equalTo("value"));
-
-        call("{ it.child { beanProperty = 'value 2' } }", container);
-        assertThat(bean.getBeanProperty(), equalTo("value 2"));
-
-        call("{ it.invokeMethod('child') { beanProperty = 'value 3' } }", container);
-        assertThat(bean.getBeanProperty(), equalTo("value 3"));
-    }
-
-    @Test
-    public void canUseDynamicPropertiesAndMethodsInsideConfigureClosures() {
-        Bean bean = new Bean();
-        container.addObject("child", bean);
-        container.addObject("aProp", bean);
-        container.addObject("a", bean);
-        container.addObject("withType", bean);
-        container.addObject("allObjects", bean);
-
-        ConfigureUtil.configure(toClosure("{ child.beanProperty = 'value 1' }"), container);
-        assertThat(bean.getBeanProperty(), equalTo("value 1"));
-
-        ConfigureUtil.configure(toClosure("{ child { beanProperty = 'value 2' } }"), container);
-        assertThat(bean.getBeanProperty(), equalTo("value 2"));
-
-        ConfigureUtil.configure(toClosure("{ aProp.beanProperty = 'value 3' }"), container);
-        assertThat(bean.getBeanProperty(), equalTo("value 3"));
-
-        ConfigureUtil.configure(toClosure("{ a.beanProperty = 'value 4' }"), container);
-        assertThat(bean.getBeanProperty(), equalTo("value 4"));
-
-        // Try with an element with the same name as a method
-        ConfigureUtil.configure(toClosure("{ withType.beanProperty = 'value 6' }"), container);
-        assertThat(bean.getBeanProperty(), equalTo("value 6"));
-
-        ConfigureUtil.configure(toClosure("{ withType { beanProperty = 'value 6' } }"), container);
-        assertThat(bean.getBeanProperty(), equalTo("value 6"));
-    }
-
-    @Test
-    public void cannotInvokeUnknownMethod() {
-        container.addObject("child", new Bean());
-
-        assertMethodUnknown("unknown");
-        assertMethodUnknown("unknown", toClosure("{ }"));
-        assertMethodUnknown("child");
-        assertMethodUnknown("child", "not a closure");
-        assertMethodUnknown("child", toClosure("{ }"), "something else");
-    }
-
-    private void assertMethodUnknown(String name, Object... arguments) {
-        assertFalse(container.getAsDynamicObject().hasMethod(name, arguments));
-        try {
-            container.getAsDynamicObject().invokeMethod(name, arguments);
-            fail();
-        } catch (groovy.lang.MissingMethodException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void configureMethodInvokesRuleForUnknownDomainObject() {
-        Bean bean = new Bean();
-        addRuleFor(bean);
-
-        assertTrue(container.getAsDynamicObject().hasMethod("bean", toClosure("{ }")));
-    }
-
-    @Test
-    public void addRuleByClosure() {
-        String testPropertyKey = "org.gradle.test.addRuleByClosure";
-        String expectedTaskName = "someTaskName";
-        Closure ruleClosure = HelperUtil.toClosure(String.format("{ taskName -> System.setProperty('%s', '%s') }",
-                testPropertyKey, expectedTaskName));
-        container.addRule("description", ruleClosure);
-        container.getRules().get(0).apply(expectedTaskName);
-        assertThat(System.getProperty(testPropertyKey), equalTo(expectedTaskName));
-        System.getProperties().remove(testPropertyKey);
-    }
-
-    private void addRuleFor(final Bean bean) {
-        container.addRule(new Rule() {
-            public String getDescription() {
-                throw new UnsupportedOperationException();
-            }
-
-            public void apply(String taskName) {
-                container.addObject(taskName, bean);
-            }
-        });
-    }
-
-    private class Bean {
-        private String beanProperty;
-
-        public String getBeanProperty() {
-            return beanProperty;
-        }
-
-        public void setBeanProperty(String beanProperty) {
-            this.beanProperty = beanProperty;
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy
new file mode 100644
index 0000000..04db660
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+import org.gradle.api.Namer
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+
+class DefaultNamedDomainObjectListTest extends Specification {
+    final Namer<Object> toStringNamer = new Namer<Object>() {
+        String determineName(Object object) {
+            return object.toString()
+        }
+    }
+    final DefaultNamedDomainObjectList<String> list = new DefaultNamedDomainObjectList<String>(String, new DirectInstantiator(), toStringNamer)
+
+    def "can add element at given index"() {
+        given:
+        list.add('b')
+
+        when:
+        list.add(0, 'a')
+        list.add(2, 'c')
+
+        then:
+        list == ['a', 'b', 'c']
+    }
+
+    def "fires events when element is added at index"() {
+        Action<String> action = Mock()
+
+        given:
+        list.all(action)
+
+        when:
+        list.add(0, 'a')
+        list.add(0, 'b')
+
+        then:
+        1 * action.execute('a')
+        1 * action.execute('b')
+        0 * action._
+    }
+
+    def "cannot add duplicate element by adding element at given index"() {
+        given:
+        list.add('a')
+
+        when:
+        list.add(1, 'a')
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot add a String with name 'a' as a String with that name already exists."
+        list == ['a']
+    }
+
+    def "can add collection at given index"() {
+        when:
+        list.addAll(0, ['a', 'b'])
+
+        then:
+        list == ['a', 'b']
+
+        when:
+        list.addAll(1, ['c', 'd'])
+
+        then:
+        list == ['a', 'c', 'd', 'b']
+    }
+
+    def "fires events when elements are added"() {
+        Action<String> action = Mock()
+
+        given:
+        list.all(action)
+
+        when:
+        list.addAll(0, ['a', 'b'])
+
+        then:
+        1 * action.execute('a')
+        1 * action.execute('b')
+        0 * action._
+    }
+
+    def "ignores duplicate elements when adding collection at given index"() {
+        given:
+        list.add('a')
+
+        when:
+        list.addAll(1, ['b', 'a', 'c', 'b'])
+
+        then:
+        list == ['a', 'b', 'c']
+    }
+
+    def "can get element at given index"() {
+        given:
+        list.add("a")
+        list.add("b")
+        list.add("c")
+
+        expect:
+        list.get(0) == "a"
+        list.get(1) == "b"
+    }
+
+    def "can set element at given index"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+
+        when:
+        def result = list.set(1, 'd')
+
+        then:
+        result == 'b'
+        list == ['a', 'd', 'c']
+    }
+
+    def "cannot add duplicate element by setting element at given index"() {
+        given:
+        list.addAll(['a', 'b'])
+
+        when:
+        list.set(1, 'a')
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot add a String with name 'a' as a String with that name already exists."
+        list == ['a', 'b']
+    }
+
+    def "fires events when element is replaced"() {
+        Action<String> addAction = Mock()
+        Action<String> removeAction = Mock()
+
+        given:
+        list.add('a')
+        list.all(addAction)
+        list.whenObjectRemoved(removeAction)
+
+        when:
+        list.set(0, 'b')
+
+        then:
+        1 * removeAction.execute('a')
+        1 * addAction.execute('b')
+        0 * removeAction._
+        0 * addAction._
+    }
+
+    def "can remove element at given index"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+
+        when:
+        def result = list.remove(1)
+
+        then:
+        result == 'b'
+        list == ['a', 'c']
+    }
+
+    def "fires events when element is removed from given index"() {
+        Action<String> action = Mock()
+
+        given:
+        list.add('a')
+        list.whenObjectRemoved(action)
+
+        when:
+        list.remove(0)
+
+        then:
+        1 * action.execute('a')
+        0 * action._
+    }
+
+    def "can find index of domain object"() {
+        given:
+        list.addAll(['a', 'b', 'a'])
+
+        expect:
+        list.indexOf('a') == 0
+        list.indexOf('other') == -1
+    }
+
+    def "can find last index of domain object"() {
+        given:
+        list.addAll(['a', 'b', 'a'])
+
+        expect:
+        list.lastIndexOf('a') == 2
+        list.lastIndexOf('other') == -1
+    }
+
+    def "can iterate over elements using ListIterator"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+
+        expect:
+        def iter = list.listIterator()
+        iter.hasNext()
+        iter.next() == 'a'
+        iter.hasNext()
+        iter.next() == 'b'
+        iter.hasNext()
+        iter.next() == 'c'
+        !iter.hasNext()
+    }
+
+    def "can remove element using ListIterator"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+        def iterator = list.listIterator()
+
+        when:
+        iterator.next()
+        iterator.remove()
+
+        then:
+        iterator.next() == 'b'
+        list == ['b', 'c']
+    }
+
+    def "fires event when element removed using ListIterator"() {
+        given:
+        Action<String> action = Mock()
+        list.addAll(['a', 'b', 'c'])
+        list.whenObjectRemoved(action)
+
+        when:
+        def iterator = list.listIterator()
+        iterator.next()
+        iterator.remove()
+
+        then:
+        1 * action.execute('a')
+        0 * action._
+    }
+
+    def "can set element using ListIterator"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+        def iterator = list.listIterator()
+
+        when:
+        iterator.next()
+        iterator.set('d')
+
+        then:
+        iterator.next() == 'b'
+        list == ['d', 'b', 'c']
+    }
+
+    def "fires events when element replaced using ListIterator"() {
+        given:
+        Action<String> addAction = Mock()
+        Action<String> removeAction = Mock()
+        list.addAll(['a', 'b', 'c'])
+        list.whenObjectAdded(addAction)
+        list.whenObjectRemoved(removeAction)
+
+        when:
+        def iterator = list.listIterator()
+        iterator.next()
+        iterator.set('d')
+
+        then:
+        1 * removeAction.execute('a')
+        1 * addAction.execute('d')
+        0 * removeAction._
+        0 * addAction._
+    }
+
+    def "cannot add duplicate element by replacing using ListIterator"() {
+        given:
+        list.addAll(['a', 'b'])
+
+        when:
+        def iterator = list.listIterator()
+        iterator.next()
+        iterator.set('b')
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot add a String with name 'b' as a String with that name already exists."
+        list == ['a', 'b']
+    }
+
+    def "can add element using ListIterator"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+        def iterator = list.listIterator()
+
+        when:
+        iterator.next()
+        iterator.add('d')
+
+        then:
+        iterator.next() == 'b'
+        list == ['a', 'd', 'b', 'c']
+    }
+
+    def "fires event when element added using ListIterator"() {
+        given:
+        Action<String> action = Mock()
+        list.addAll(['a', 'b', 'c'])
+        list.whenObjectAdded(action)
+
+        when:
+        def iterator = list.listIterator()
+        iterator.next()
+        iterator.add('d')
+
+        then:
+        1 * action.execute('d')
+        0 * action._
+    }
+
+    def "cannot add duplicate element by adding using ListIterator"() {
+        given:
+        list.addAll(['a', 'b'])
+
+        when:
+        def iterator = list.listIterator()
+        iterator.next()
+        iterator.add('b')
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "Cannot add a String with name 'b' as a String with that name already exists."
+        list == ['a', 'b']
+    }
+
+    def "can iterate over elements from given position using ListIterator"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+
+        expect:
+        def iter = list.listIterator(1)
+        iter.hasNext()
+        iter.hasPrevious()
+        iter.next() == 'b'
+        iter.hasNext()
+        iter.next() == 'c'
+        !iter.hasNext()
+    }
+
+    def "can get sublist of elements"() {
+        given:
+        list.addAll(['a', 'b', 'c'])
+
+        expect:
+        list.subList(1, 3) == ['b', 'c']
+    }
+
+    def "cannot mutate sublist"() {
+        when:
+        list.subList(0, 0).clear()
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    def "can find all elements that match closure"() {
+        given:
+        list.addAll(["a", "b", "c"])
+
+        expect:
+        list.findAll { it != "b" } == ["a", "c"]
+    }
+
+    def "can get filtered element by index"() {
+        given:
+        list.addAll(["a", "b", "c"])
+
+        expect:
+        list.matching { it != "b" }.get(0) == "a"
+        list.matching { it != "b" }.get(1) == "c"
+
+        when:
+        list.matching { it != "b" }.get(43)
+
+        then:
+        IndexOutOfBoundsException e = thrown()
+    }
+
+    def "can get index of filtered element"() {
+        given:
+        list.addAll(["a", "b", "c"])
+
+        expect:
+        list.matching { it != "b" }.indexOf("a") == 0
+        list.matching { it != "b" }.indexOf("c") == 1
+        list.matching { it != "b" }.indexOf("b") == -1
+        list.matching { it != "b" }.indexOf("z") == -1
+    }
+
+    def "can get last index of filtered element"() {
+        given:
+        list.addAll(["a", "b", "c"])
+
+        expect:
+        list.matching { it != "b" }.lastIndexOf("a") == 0
+        list.matching { it != "b" }.lastIndexOf("c") == 1
+        list.matching { it != "b" }.lastIndexOf("b") == -1
+        list.matching { it != "b" }.lastIndexOf("z") == -1
+    }
+
+    def "can get ListIterator over filtered elements"() {
+        given:
+        list.addAll(["a", "b", "c"])
+
+        when:
+        def iter = list.matching { it != "b" }.listIterator()
+
+        then:
+        iter.hasNext()
+        iter.nextIndex() == 0
+        iter.next() == "a"
+        iter.nextIndex() == 1
+        iter.next() == "c"
+        !iter.hasNext()
+        iter.nextIndex() == 2
+    }
+
+    def "can get ListIterator over filtered elements starting at given index"() {
+        given:
+        list.addAll(["a", "b", "c"])
+
+        when:
+        def iter = list.matching { it != "b" }.listIterator(1)
+
+        then:
+        iter.hasNext()
+        iter.nextIndex() == 1
+        iter.next() == "c"
+        !iter.hasNext()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
new file mode 100644
index 0000000..ab695fc
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
@@ -0,0 +1,747 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.*;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TestClosure;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Iterator;
+
+import static org.gradle.util.HelperUtil.call;
+import static org.gradle.util.HelperUtil.toClosure;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+
+ at RunWith(JMock.class)
+public class DefaultNamedDomainObjectSetTest {
+    private final Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator());
+    private final Namer<Bean> namer = new Namer<Bean>() { public String determineName(Bean bean) { return bean.name; } };
+    @SuppressWarnings("unchecked")
+    private final DefaultNamedDomainObjectSet<Bean> container = instantiator.newInstance(DefaultNamedDomainObjectSet.class, Bean.class, instantiator, namer);
+    private final JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void usesTypeNameToGenerateDisplayName() {
+        assertThat(container.getTypeDisplayName(), equalTo("Bean"));
+        assertThat(container.getDisplayName(), equalTo("Bean set"));
+    }
+
+    @Test
+    public void canGetAllDomainObjectsForEmptyContainer() {
+        assertTrue(container.isEmpty());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsOrderedByName() {
+        Bean bean1 = new Bean("a");
+        Bean bean2 = new Bean("b");
+        Bean bean3 = new Bean("c");
+    
+        container.add(bean2);
+        container.add(bean1);
+        container.add(bean3);
+    
+        assertThat(toList(container), equalTo(toList(bean1, bean2, bean3)));
+    }
+
+    @Test
+    public void canIterateOverEmptyContainer() {
+        Iterator<Bean> iterator = container.iterator();
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canIterateOverDomainObjectsOrderedByName() {
+        Bean bean1 = new Bean("a");
+        Bean bean2 = new Bean("b");
+        Bean bean3 = new Bean("c");
+    
+        container.add(bean2);
+        container.add(bean1);
+        container.add(bean3);
+    
+        Iterator<Bean> iterator = container.iterator();
+        assertThat(iterator.next(), sameInstance(bean1));
+        assertThat(iterator.next(), sameInstance(bean2));
+        assertThat(iterator.next(), sameInstance(bean3));
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsAsMapForEmptyContainer() {
+        assertTrue(container.getAsMap().isEmpty());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsAsMap() {
+        Bean bean1 = new Bean("a");
+        Bean bean2 = new Bean("b");
+        Bean bean3 = new Bean("c");
+
+        container.add(bean2);
+        container.add(bean1);
+        container.add(bean3);
+
+        assertThat(container.getAsMap(), equalTo(GUtil.map("a", bean1, "b", bean2, "c", bean3)));
+    }
+
+    @Test
+    public void canGetAllMatchingDomainObjectsOrderedByName() {
+        Bean bean1 = new Bean("a");
+        final Bean bean2 = new Bean("b");
+        Bean bean3 = new Bean("c");
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element == bean2;
+            }
+        };
+
+        container.add(bean1);
+        container.add(bean2);
+        container.add(bean3);
+
+        assertThat(toList(container.matching(spec)), equalTo(toList(bean2)));
+    }
+
+    @Test
+    public void getAllMatchingDomainObjectsReturnsEmptySetWhenNoMatches() {
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return false;
+            }
+        };
+
+        container.add(new Bean("a"));
+
+        assertTrue(container.matching(spec).isEmpty());
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichMeetSpec() {
+        final Bean bean1 = new Bean("a");
+        Bean bean2 = new Bean("b");
+        Bean bean3 = new Bean("c");
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean1;
+            }
+        };
+
+        TestClosure testClosure = new TestClosure() {
+            public Object call(Object param) {
+                return param != bean1;
+            }
+        };
+
+        container.add(bean1);
+        container.add(bean2);
+        container.add(bean3);
+
+        assertThat(toList(container.matching(spec)), equalTo(toList(bean2, bean3)));
+        assertThat(toList(container.matching(HelperUtil.toClosure(testClosure))), equalTo(toList(bean2, bean3)));
+        assertThat(container.matching(spec).findByName("a"), nullValue());
+        assertThat(container.matching(spec).findByName("b"), sameInstance(bean2));
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichHaveType() {
+        class OtherBean extends Bean {
+            public OtherBean(String name) {
+                super(name);
+            }
+        }
+        Bean bean1 = new Bean("a");
+        OtherBean bean2 = new OtherBean("b");
+        Bean bean3 = new Bean("c");
+
+        container.add(bean1);
+        container.add(bean2);
+        container.add(bean3);
+
+        assertThat(toList(container.withType(Bean.class)), equalTo(toList(bean1, bean2, bean3)));
+        assertThat(toList(container.withType(OtherBean.class)), equalTo(toList(bean2)));
+        assertThat(container.withType(OtherBean.class).findByName("a"), nullValue());
+        assertThat(container.withType(OtherBean.class).findByName("b"), sameInstance(bean2));
+    }
+
+    @Test
+    public void canExecuteActionForAllElementsInATypeFilteredCollection() {
+        class OtherBean extends Bean {
+            public OtherBean(String name) {
+                super(name);
+            }
+            public OtherBean() {}
+        }
+        final Action<OtherBean> action = context.mock(Action.class);
+        Bean bean1 = new Bean("b1");
+        final OtherBean bean2 = new OtherBean("b2");
+
+        container.add(bean1);
+        container.add(bean2);
+
+        context.checking(new Expectations(){{
+            one(action).execute(bean2);
+        }});
+
+        container.withType(OtherBean.class, action);
+    }
+
+    @Test
+    public void canExecuteClosureForAllElementsInATypeFilteredCollection() {
+        class OtherBean extends Bean {
+            public OtherBean(String name) {
+                super(name);
+            }
+            public OtherBean() {}
+        }
+        final TestClosure closure = context.mock(TestClosure.class);
+        Bean bean1 = new Bean("b1");
+        final OtherBean bean2 = new OtherBean("b2");
+
+        container.add(bean1);
+        container.add(bean2);
+
+        context.checking(new Expectations(){{
+            one(closure).call(bean2);
+        }});
+
+        container.withType(OtherBean.class, HelperUtil.toClosure(closure));
+    }
+
+    @Test
+    public void filteredCollectionIsLive() {
+        final Bean bean1 = new Bean("a");
+        Bean bean2 = new Bean("b");
+        Bean bean3 = new Bean("c");
+        Bean bean4 = new Bean("d");
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean1;
+            }
+        };
+
+        container.add(bean1);
+
+        DomainObjectCollection<Bean> filteredCollection = container.matching(spec);
+        assertTrue(filteredCollection.isEmpty());
+
+        container.add(bean2);
+        container.add(bean3);
+
+        assertThat(toList(filteredCollection), equalTo(toList(bean2, bean3)));
+
+        container.add(bean4);
+
+        assertThat(toList(filteredCollection), equalTo(toList(bean2, bean3, bean4)));
+
+        assertThat(container.removeByName("b"), sameInstance(bean2));
+
+        assertThat(toList(filteredCollection), equalTo(toList(bean3, bean4)));
+
+    }
+
+    @Test
+    public void filteredCollectionExecutesActionWhenMatchingObjectAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element == bean;
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(action);
+
+        container.add(bean);
+        container.add(new Bean());
+    }
+
+    @Test
+    public void filteredCollectionExecutesClosureWhenMatchingObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element == bean;
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
+
+        container.add(bean);
+        container.add(new Bean());
+    }
+
+    @Test
+    public void canChainFilteredCollections() {
+        final Bean bean = new Bean("b1");
+        final Bean bean2 = new Bean("b2");
+        final Bean bean3 = new Bean("b3");
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean;
+            }
+        };
+        Spec<Bean> spec2 = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean2;
+            }
+        };
+
+        container.add(bean);
+        container.add(bean2);
+        container.add(bean3);
+
+        DomainObjectCollection<Bean> collection = container.matching(spec).matching(spec2);
+        assertThat(toList(collection), equalTo(toList(bean3)));
+    }
+
+    @Test
+    public void canGetDomainObjectByName() {
+        Bean bean = new Bean("a");
+        container.add(bean);
+
+        assertThat(container.getByName("a"), sameInstance(bean));
+        assertThat(container.getAt("a"), sameInstance(bean));
+    }
+
+    @Test
+    public void getDomainObjectByNameFailsForUnknownDomainObject() {
+        try {
+            container.getByName("unknown");
+            fail();
+        } catch (UnknownDomainObjectException e) {
+            assertThat(e.getMessage(), equalTo("Bean with name 'unknown' not found."));
+        }
+    }
+
+    @Test
+    public void getDomainObjectInvokesRuleForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertThat(container.getByName("bean"), sameInstance(bean));
+    }
+
+    @Test
+    public void canConfigureDomainObjectByName() {
+        Bean bean = new Bean("a");
+        container.add(bean);
+
+        assertThat(container.getByName("a", toClosure("{ beanProperty = 'hi' }")), sameInstance(bean));
+        assertThat(bean.getBeanProperty(), equalTo("hi"));
+    }
+
+    @Test
+    public void configureDomainObjectInvokesRuleForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertThat(container.getByName("bean", toClosure("{ beanProperty = 'hi' }")), sameInstance(bean));
+        assertThat(bean.getBeanProperty(), equalTo("hi"));
+    }
+
+    @Test
+    public void canFindDomainObjectByName() {
+        Bean bean = new Bean("a");
+        container.add(bean);
+
+        assertThat(container.findByName("a"), sameInstance(bean));
+    }
+
+    @Test
+    public void findDomainObjectByNameReturnsNullForUnknownDomainObject() {
+        assertThat(container.findByName("a"), nullValue());
+    }
+
+    @Test
+    public void findDomainObjectByNameInvokesRulesForUnknownDomainObject() {
+        Bean bean = new Bean("bean");
+        addRuleFor(bean);
+
+        assertThat(container.findByName("bean"), sameInstance(bean));
+    }
+
+    @Test
+    public void findDomainObjectByNameInvokesNestedRulesOnlyOnceForUnknownDomainObject() {
+        final Bean bean1 = new Bean("bean1");
+        final Bean bean2 = new Bean("bean2");
+        container.addRule(new Rule() {
+            public String getDescription() {
+                return "rule1";
+            }
+
+            public void apply(String domainObjectName) {
+                if (domainObjectName.equals("bean1")) {
+                    container.add(bean1);
+                }
+            }
+        });
+        container.addRule(new Rule() {
+            private boolean applyHasBeenCalled;
+
+            public String getDescription() {
+                return "rule2";
+            }
+
+            public void apply(String domainObjectName) {
+                if (domainObjectName.equals("bean2")) {
+                    assertThat(applyHasBeenCalled, equalTo(false));
+                    container.findByName("bean1");
+                    container.findByName("bean2");
+                    container.add(bean2);
+                    applyHasBeenCalled = true;
+                }
+            }
+        });
+        container.findByName("bean2");
+        assertThat(toList(container), equalTo(toList(bean1, bean2)));
+    }
+
+    @Test
+    public void callsActionWhenObjectAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.whenObjectAdded(action);
+        container.add(bean);
+    }
+
+    @Test
+    public void callsClosureWhenObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        container.whenObjectAdded(HelperUtil.toClosure(closure));
+        container.add(bean);
+    }
+
+    @Test
+    public void doesNotCallActionWhenDuplicateObjectAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        container.add(bean);
+
+        container.whenObjectAdded(action);
+        container.add(bean);
+    }
+
+    @Test
+    public void callsActionWhenObjectsAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+        final Bean bean2 = new Bean("other");
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+            one(action).execute(bean2);
+        }});
+
+        container.whenObjectAdded(action);
+        container.addAll(toList(bean, bean2));
+    }
+
+    @Test
+    public void doesNotCallActionWhenDuplicateObjectsAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+        final Bean bean2 = new Bean("other");
+
+        container.add(bean);
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean2);
+        }});
+
+        container.whenObjectAdded(action);
+        container.addAll(toList(bean, bean2));
+    }
+
+    @Test
+    public void callsActionWhenObjectRemoved() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.whenObjectRemoved(action);
+        container.add(bean);
+        container.removeByName("bean");
+    }
+
+    @Test
+    public void doesNotCallActionWhenUnknownObjectRemoved() {
+        final Action<Bean> action = context.mock(Action.class);
+
+        container.whenObjectRemoved(action);
+        container.remove(new Bean());
+    }
+
+    @Test
+    public void allCallsActionForEachExistingObject() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.add(bean);
+        container.all(action);
+    }
+
+    @Test
+    public void allCallsClosureForEachExistingObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        container.add(bean);
+        container.all(HelperUtil.toClosure(closure));
+    }
+
+    @Test
+    public void allCallsActionForEachNewObject() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.all(action);
+        container.add(bean);
+    }
+
+    @Test
+    public void allCallsClosureForEachNewObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        container.all(HelperUtil.toClosure(closure));
+        container.add(bean);
+    }
+
+    @Test
+    public void eachObjectIsAvailableAsADynamicProperty() {
+        Bean bean = new Bean("child");
+        container.add(bean);
+        assertTrue(container.withType(Bean.class).findByName("child") != null);
+        assertTrue(container.getAsDynamicObject().hasProperty("child"));
+        assertThat(container.getAsDynamicObject().getProperty("child"), sameInstance((Object) bean));
+        assertThat(container.getAsDynamicObject().getProperties().get("child"), sameInstance((Object) bean));
+        assertThat(call("{ it.child }", container), sameInstance((Object) bean));
+        assertThat(call("{ it.child }", container.withType(Bean.class)), sameInstance((Object) bean));
+        assertThat(call("{ it.child }", container.matching(Specs.satisfyAll())), sameInstance((Object) bean));
+    }
+
+    @Test
+    public void eachObjectIsAvailableUsingAnIndex() {
+        Bean bean = new Bean("child");
+        container.add(bean);
+        assertThat(call("{ it['child'] }", container), sameInstance((Object) bean));
+    }
+
+    @Test
+    public void cannotGetUnknownProperty() {
+        assertFalse(container.getAsDynamicObject().hasProperty("unknown"));
+
+        try {
+            container.getAsDynamicObject().getProperty("unknown");
+            fail();
+        } catch (MissingPropertyException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void dynamicPropertyAccessInvokesRulesForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertTrue(container.getAsDynamicObject().hasProperty("bean"));
+        assertThat(container.getAsDynamicObject().getProperty("bean"), sameInstance((Object) bean));
+    }
+
+    @Test
+    public void eachObjectIsAvailableAsConfigureMethod() {
+        Bean bean = new Bean("child");
+        container.add(bean);
+
+        Closure closure = toClosure("{ beanProperty = 'value' }");
+        assertTrue(container.getAsDynamicObject().hasMethod("child", closure));
+        container.getAsDynamicObject().invokeMethod("child", closure);
+        assertThat(bean.getBeanProperty(), equalTo("value"));
+
+        call("{ it.child { beanProperty = 'value 2' } }", container);
+        assertThat(bean.getBeanProperty(), equalTo("value 2"));
+
+        call("{ it.invokeMethod('child') { beanProperty = 'value 3' } }", container);
+        assertThat(bean.getBeanProperty(), equalTo("value 3"));
+    }
+
+    @Test
+    public void canUseDynamicPropertiesAndMethodsInsideConfigureClosures() {
+        Bean bean = new Bean("child");
+        container.add(bean);
+        container.add(bean);
+        container.add(bean);
+        container.add(bean);
+        container.add(bean);
+
+        ConfigureUtil.configure(toClosure("{ child.beanProperty = 'value 1' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 1"));
+
+        ConfigureUtil.configure(toClosure("{ child { beanProperty = 'value 2' } }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 2"));
+
+        ConfigureUtil.configure(toClosure("{ child.beanProperty = 'value 3' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 3"));
+
+        ConfigureUtil.configure(toClosure("{ child.beanProperty = 'value 4' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 4"));
+
+        Bean withType = new Bean("withType");
+        container.add(withType);
+        
+        // Try with an element with the same name as a method
+        ConfigureUtil.configure(toClosure("{ withType.beanProperty = 'value 6' }"), container);
+        assertThat(withType.getBeanProperty(), equalTo("value 6"));
+
+        ConfigureUtil.configure(toClosure("{ withType { beanProperty = 'value 6' } }"), container);
+        assertThat(withType.getBeanProperty(), equalTo("value 6"));
+    }
+
+    @Test
+    public void cannotInvokeUnknownMethod() {
+        container.add(new Bean("child"));
+
+        assertMethodUnknown("unknown");
+        assertMethodUnknown("unknown", toClosure("{ }"));
+        assertMethodUnknown("child");
+        assertMethodUnknown("child", "not a closure");
+        assertMethodUnknown("child", toClosure("{ }"), "something else");
+    }
+
+    private void assertMethodUnknown(String name, Object... arguments) {
+        assertFalse(container.getAsDynamicObject().hasMethod(name, arguments));
+        try {
+            container.getAsDynamicObject().invokeMethod(name, arguments);
+            fail();
+        } catch (groovy.lang.MissingMethodException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void configureMethodInvokesRuleForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertTrue(container.getAsDynamicObject().hasMethod("bean", toClosure("{ }")));
+    }
+
+    @Test
+    public void addRuleByClosure() {
+        String testPropertyKey = "org.gradle.test.addRuleByClosure";
+        String expectedTaskName = "someTaskName";
+        Closure ruleClosure = HelperUtil.toClosure(String.format("{ taskName -> System.setProperty('%s', '%s') }",
+                testPropertyKey, expectedTaskName));
+        container.addRule("description", ruleClosure);
+        container.getRules().get(0).apply(expectedTaskName);
+        assertThat(System.getProperty(testPropertyKey), equalTo(expectedTaskName));
+        System.getProperties().remove(testPropertyKey);
+    }
+
+    private void addRuleFor(final Bean bean) {
+        container.addRule(new Rule() {
+            public String getDescription() {
+                throw new UnsupportedOperationException();
+            }
+
+            public void apply(String taskName) {
+                container.add(bean);
+            }
+        });
+    }
+
+    private class Bean {
+        public final String name;
+        private String beanProperty;
+
+        public Bean() {
+            this("bean");
+        }
+
+        public Bean(String name) {
+            this.name = name;
+        }
+
+        public String getBeanProperty() {
+            return beanProperty;
+        }
+
+        public void setBeanProperty(String beanProperty) {
+            this.beanProperty = beanProperty;
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
index ddf0139..2dc58d0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
@@ -16,21 +16,25 @@
 
 package org.gradle.api.internal
 
+import java.util.concurrent.Callable
+import org.gradle.api.Action
 import org.gradle.api.DefaultTask
+import org.gradle.api.Task
 import org.gradle.api.tasks.AbstractTaskTest
-import org.gradle.util.WrapUtil
+import org.gradle.api.tasks.TaskExecutionException
+import org.gradle.listener.ListenerManager
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
-import static org.gradle.util.Matchers.*
+import static org.gradle.util.Matchers.isEmpty
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import java.util.concurrent.Callable
-import org.gradle.listener.ListenerManager
 
 /**
  * @author Hans Dockter
  */
 class DefaultTaskTest extends AbstractTaskTest {
+    ClassLoader cl
     DefaultTask defaultTask
 
     Object testCustomPropValue;
@@ -39,6 +43,11 @@ class DefaultTaskTest extends AbstractTaskTest {
         super.setUp()
         testCustomPropValue = new Object()
         defaultTask = createTask(DefaultTask.class)
+        cl = Thread.currentThread().contextClassLoader
+    }
+
+    @After public void cleanup() {
+        Thread.currentThread().contextClassLoader = cl
     }
 
     AbstractTask getTask() {
@@ -60,35 +69,129 @@ class DefaultTaskTest extends AbstractTaskTest {
         assertThat(task.name, equalTo(TEST_TASK_NAME))
     }
 
-    @Test public void testDoFirstWithClosureDelegatesToTask() {
-        Closure testAction = {}
+    @Test
+    public void testConfigure() {
+        Closure action1 = { Task t -> }
+        assertSame(task, task.configure {
+            doFirst(action1)
+        });
+        assertEquals(1, task.actions.size())
+    }
+
+    @Test
+    public void testDoFirstAddsActionToTheStartOfActionsList() {
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+
+        assertSame(defaultTask, defaultTask.doFirst(action1));
+        assertEquals(1, defaultTask.actions.size());
+
+        assertSame(defaultTask, defaultTask.doFirst(action2));
+        assertEquals(2, defaultTask.actions.size());
+
+        assertSame(action2, defaultTask.actions[0].action)
+        assertSame(action1, defaultTask.actions[1].action)
+    }
+
+    @Test
+    public void testDoLastAddsActionToTheEndOfActionsList() {
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+
+        assertSame(defaultTask, defaultTask.doLast(action1));
+        assertEquals(1, defaultTask.actions.size());
+
+        assertSame(defaultTask, defaultTask.doLast(action2));
+        assertEquals(2, defaultTask.actions.size());
+
+        assertSame(action1, defaultTask.actions[0].action)
+        assertSame(action2, defaultTask.actions[1].action)
+    }
+
+    @Test public void testSetsContextClassLoaderWhenExecutingAction() {
+        Action<Task> testAction = context.mock(Action)
+        context.checking {
+            one(testAction).execute(defaultTask)
+            will {
+                assert Thread.currentThread().contextClassLoader == testAction.getClass().classLoader
+            }
+        }
+
+        defaultTask.doFirst(testAction)
+
+        Thread.currentThread().contextClassLoader = new ClassLoader(getClass().classLoader) {}
+        defaultTask.actions[0].execute(defaultTask)
+    }
+
+    @Test public void testClosureActionDelegatesToTask() {
+        Closure testAction = {
+            assert delegate == defaultTask
+            assert resolveStrategy == Closure.DELEGATE_FIRST
+        }
         defaultTask.doFirst(testAction)
-        assertSame(defaultTask, testAction.delegate)
-        assertEquals(Closure.DELEGATE_FIRST, testAction.getResolveStrategy())
+        defaultTask.actions[0].execute(defaultTask)
+    }
+
+    @Test public void testSetsContextClassLoaderWhenRunningClosureAction() {
+        Closure testAction = {
+            assert Thread.currentThread().contextClassLoader == getClass().classLoader
+        }
+
+        defaultTask.doFirst(testAction)
+
+        Thread.currentThread().contextClassLoader = new ClassLoader(getClass().classLoader) {}
+        defaultTask.actions[0].execute(defaultTask)
+    }
+
+    @Test public void testDoFirstWithClosureAddsActionToTheStartOfActionsList() {
+        Closure testAction1 = { }
+        Closure testAction2 = { }
+        Closure testAction3 = { }
+        defaultTask.doLast(testAction1)
+        defaultTask.doLast(testAction2)
+        defaultTask.doLast(testAction3)
+
+        assertSame(defaultTask.actions[0].closure, testAction1)
+        assertSame(defaultTask.actions[1].closure, testAction2)
+        assertSame(defaultTask.actions[2].closure, testAction3)
     }
 
-    @Test public void testDoFirstWithClosure() {
-        List<Integer> executed = new ArrayList<Integer>();
-        Closure testAction1 = { executed.add(1) }
-        Closure testAction2 = {-> executed.add(2) }
-        Closure testAction3 = {task -> executed.add(3) }
+    @Test public void testDoLastWithClosureAddsActionToTheEndOfActionsList() {
+        Closure testAction1 = { }
+        Closure testAction2 = { }
+        Closure testAction3 = { }
         defaultTask.doFirst(testAction1)
         defaultTask.doFirst(testAction2)
         defaultTask.doFirst(testAction3)
-        defaultTask.execute()
-        assertEquals(executed, WrapUtil.toList(3, 2, 1))
+
+        assertSame(defaultTask.actions[0].closure, testAction3)
+        assertSame(defaultTask.actions[1].closure, testAction2)
+        assertSame(defaultTask.actions[2].closure, testAction1)
     }
 
-    @Test
-    void getAdditonalProperties() {
-        defaultTask.additionalProperties.customProp = testCustomPropValue
-        assertSame(testCustomPropValue, defaultTask."customProp")
+    @Test public void testExecuteThrowsExecutionFailure() {
+        def failure = new RuntimeException()
+        defaultTask.doFirst { throw failure }
+
+        try {
+            defaultTask.execute()
+            fail()
+        } catch (TaskExecutionException e) {
+            assertThat(e.cause, sameInstance(failure))
+        }
+
+        assertThat(defaultTask.state.failure, instanceOf(TaskExecutionException))
+        assertThat(defaultTask.state.failure.cause, sameInstance(failure))
     }
 
-    @Test
-    void setAdditonalProperties() {
-        defaultTask."customProp" = testCustomPropValue
-        assertSame(testCustomPropValue, defaultTask.additionalProperties.customProp)
+    @Test public void testExecuteWithoutThrowingTaskFailureThrowsExecutionFailure() {
+        def failure = new RuntimeException()
+        defaultTask.doFirst { throw failure }
+
+        defaultTask.executeWithoutThrowingTaskFailure()
+
+        assertThat(defaultTask.state.failure, instanceOf(TaskExecutionException))
+        assertThat(defaultTask.state.failure.cause, sameInstance(failure))
     }
 
     @Test
@@ -107,13 +210,6 @@ class DefaultTaskTest extends AbstractTaskTest {
         assertEquals(defaultTask.conventionMethod('a', 'b').toString(), "a.b")
     }
 
-    @Test
-    void getProperty() {
-        defaultTask.additionalProperties.customProp = testCustomPropValue
-        assertSame(testCustomPropValue, defaultTask.property("customProp"))
-        assertSame(AbstractTaskTest.TEST_TASK_NAME, defaultTask.property("name"))
-    }
-
     @Test(expected = MissingPropertyException)
     void accessNonExistingProperty() {
         defaultTask."unknownProp"
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy
new file mode 100644
index 0000000..bc34cf8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.api.internal.classpath.Module
+import org.gradle.api.internal.classpath.ModuleRegistry
+import spock.lang.Specification
+import org.gradle.api.internal.classpath.PluginModuleRegistry
+import org.gradle.util.DefaultClassPath
+
+class DependencyClassPathProviderTest extends Specification {
+    final ModuleRegistry moduleRegistry = Mock()
+    final PluginModuleRegistry pluginModuleRegistry = Mock()
+    final DependencyClassPathProvider provider = new DependencyClassPathProvider(moduleRegistry, pluginModuleRegistry)
+
+    def "uses modules to determine gradle API classpath"() {
+        when:
+        def classpath = provider.findClassPath("GRADLE_API")
+
+        then:
+        classpath.asFiles.collect{it.name} == ["gradle-core-runtime", "gradle-cli-runtime", "gradle-core-impl-runtime", "gradle-tooling-api-impl", "plugin1-runtime", "plugin2-runtime"]
+
+        and:
+        1 * moduleRegistry.getModule("gradle-core") >> module("gradle-core", module("gradle-cli"))
+        1 * moduleRegistry.getModule("gradle-core-impl") >> module("gradle-core-impl")
+        1 * moduleRegistry.getModule("gradle-tooling-api") >> module("gradle-tooling-api")
+        1 * pluginModuleRegistry.getPluginModules() >> ([module("plugin1"), module("plugin2")] as LinkedHashSet)
+    }
+
+    def module(String name, Module ... requiredModules) {
+        Module module = Mock()
+        _ * module.classpath >> new DefaultClassPath(new File("$name-runtime"))
+        _ * module.implementationClasspath >> new DefaultClassPath(new File("$name-impl"))
+        _ * module.allRequiredModules >> (([module] + (requiredModules as List)) as LinkedHashSet)
+        return module
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DirectInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DirectInstantiatorTest.groovy
new file mode 100644
index 0000000..89c28f8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DirectInstantiatorTest.groovy
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+
+import org.gradle.internal.UncheckedException
+
+class DirectInstantiatorTest extends Specification {
+    final DirectInstantiator instantiator = new DirectInstantiator()
+
+    def "creates instance with constructor parameters"() {
+        CharSequence param = Mock()
+
+        expect:
+        def result = instantiator.newInstance(SomeType, param)
+        result instanceof SomeType
+        result.result == param
+    }
+
+    def "creates instance with subtypes of constructor parameters"() {
+        expect:
+        def result = instantiator.newInstance(SomeType, "param")
+        result instanceof SomeType
+        result.result == "param"
+    }
+
+    def "creates instance with null constructor parameters"() {
+        expect:
+        def result = instantiator.newInstance(SomeType, [null] as Object[])
+        result instanceof SomeType
+        result.result == null
+    }
+
+    def "uses constructor which matches parameter types"() {
+        expect:
+        def stringResult = instantiator.newInstance(SomeTypeWithMultipleConstructors, "param")
+        stringResult instanceof SomeTypeWithMultipleConstructors
+        stringResult.result == "param"
+
+        and:
+        def numberResult = instantiator.newInstance(SomeTypeWithMultipleConstructors, 5)
+        numberResult instanceof SomeTypeWithMultipleConstructors
+        numberResult.result == 5
+    }
+
+    def "unboxes constructor parameters"() {
+        expect:
+        def result = instantiator.newInstance(SomeTypeWithPrimitiveTypes, true)
+        result instanceof SomeTypeWithPrimitiveTypes
+        result.result
+    }
+
+    def "fails when target class has ambiguous constructor"() {
+        when:
+        instantiator.newInstance(TypeWithAmbiguousConstructor, "param")
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [param]."
+
+        when:
+        instantiator.newInstance(TypeWithAmbiguousConstructor, true)
+
+        then:
+        e = thrown()
+        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [true]."
+    }
+
+    def "fails when target class has no matching public constructor"() {
+        def param = new Object()
+
+        when:
+        instantiator.newInstance(SomeType, param)
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [${param}]."
+
+        when:
+        instantiator.newInstance(SomeType, "a", "b")
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [a, b]."
+
+        when:
+        instantiator.newInstance(SomeType, false)
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [false]."
+
+        when:
+
+        instantiator.newInstance(SomeTypeWithPrimitiveTypes, "a")
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [a]."
+
+        when:
+
+        instantiator.newInstance(SomeTypeWithPrimitiveTypes, 22)
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [22]."
+
+        when:
+
+        instantiator.newInstance(SomeTypeWithPrimitiveTypes, [null] as Object[])
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [null]."
+    }
+
+    def "rethrows unchecked exception thrown by constructor"() {
+        when:
+        instantiator.newInstance(BrokenType, false)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == 'broken'
+    }
+
+    def "wraps checked exception thrown by constructor"() {
+        when:
+        instantiator.newInstance(BrokenType, true)
+
+        then:
+        UncheckedException e = thrown()
+        e.cause.message == 'broken'
+    }
+}
+
+class SomeType {
+    final Object result
+
+    SomeType(CharSequence result) {
+        this.result = result
+    }
+}
+
+class BrokenType {
+    BrokenType(Boolean checked) {
+        throw checked ? new Exception("broken") : new RuntimeException("broken")
+    }
+}
+
+class SomeTypeWithMultipleConstructors {
+    final Object result
+
+    SomeTypeWithMultipleConstructors(CharSequence result) {
+        this.result = result
+    }
+
+    SomeTypeWithMultipleConstructors(Number result) {
+        this.result = result
+    }
+}
+
+class SomeTypeWithPrimitiveTypes {
+    final Object result
+
+    SomeTypeWithPrimitiveTypes(boolean result) {
+        this.result = result
+    }
+}
+
+class TypeWithAmbiguousConstructor {
+    TypeWithAmbiguousConstructor(CharSequence param) {
+    }
+
+    TypeWithAmbiguousConstructor(String param) {
+    }
+
+    TypeWithAmbiguousConstructor(Boolean param) {
+    }
+
+    TypeWithAmbiguousConstructor(boolean param) {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
new file mode 100644
index 0000000..e2d40a1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DocumentationRegistryTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 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
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+class DocumentationRegistryTest extends Specification {
+    @Rule TemporaryFolder tmpDir
+    final GradleDistributionLocator locator = Mock()
+    final DocumentationRegistry registry = new DocumentationRegistry(locator)
+
+    def "points users at the local user guide when target page is present in distribution"() {
+        def distDir = tmpDir.createDir("home")
+        distDir.createFile("docs/userguide/userguide.html")
+        def daemonPage = distDir.createFile("docs/userguide/gradle_daemon.html")
+
+        given:
+        _ * locator.gradleHome >> distDir
+
+        expect:
+        registry.getDocumentationFor('gradle_daemon') == daemonPage.absolutePath
+    }
+
+    def "fails when local user guide is present in distribution but target page not found"() {
+        def distDir = tmpDir.createDir("home")
+        distDir.createFile("docs/userguide/userguide.html")
+        def expectedPage = distDir.file("docs/userguide/gradle_daemon.html")
+
+        given:
+        _ * locator.gradleHome >> distDir
+
+        when:
+        registry.getDocumentationFor('gradle_daemon')
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "User guide page '${expectedPage}' not found."
+    }
+
+    def "points users at the remote user guide when user guide not present in distribution"() {
+        def distDir = tmpDir.createDir("home")
+
+        given:
+        _ * locator.gradleHome >> distDir
+
+        expect:
+        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/current/userguide/gradle_daemon.html"
+    }
+
+    def "points users at the remote user guide when no distribution"() {
+        given:
+        _ * locator.gradleHome >> null
+
+        expect:
+        registry.getDocumentationFor('gradle_daemon') == "http://gradle.org/docs/current/userguide/gradle_daemon.html"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTest.java
deleted file mode 100644
index 94e94fd..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTest.java
+++ /dev/null
@@ -1,837 +0,0 @@
-/*
- * Copyright 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;
-
-import groovy.lang.*;
-import groovy.lang.MissingMethodException;
-import org.gradle.api.plugins.Convention;
-import org.gradle.api.internal.plugins.DefaultConvention;
-import org.gradle.util.HelperUtil;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-import java.util.Map;
-
-public class DynamicObjectHelperTest {
-    @Test
-    public void hasPropertiesDefinedByClass() {
-        Bean bean = new Bean();
-        assertTrue(bean.hasProperty("readWriteProperty"));
-        assertTrue(bean.hasProperty("readOnlyProperty"));
-        assertTrue(bean.hasProperty("writeOnlyProperty"));
-    }
-
-    @Test
-    public void canGetAndSetClassProperty() {
-        Bean bean = new Bean();
-        bean.setReadWriteProperty("value");
-
-        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
-
-        bean.setProperty("readWriteProperty", "new value");
-
-        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "new value"));
-        assertThat(bean.getReadWriteProperty(), equalTo((Object) "new value"));
-    }
-
-    @Test
-    public void canGetReadOnlyClassProperty() {
-        Bean bean = new Bean();
-        bean.doSetReadOnlyProperty("value");
-
-        assertThat(bean.getProperty("readOnlyProperty"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void cannotSetReadOnlyClassProperty() {
-        Bean bean = new Bean();
-
-        try {
-            bean.setProperty("readOnlyProperty", "value");
-            fail();
-        } catch (ReadOnlyPropertyException e) {
-            assertThat(e.getMessage(), equalTo("Cannot set the value of read-only property 'readOnlyProperty' on <bean>."));
-        }
-    }
-    
-    @Test
-    public void canSetWriteOnlyClassProperty() {
-        Bean bean = new Bean();
-        bean.setProperty("writeOnlyProperty", "value");
-        assertThat(bean.doGetWriteOnlyProperty(), equalTo("value"));
-    }
-
-    @Test
-    public void cannotGetWriteOnlyClassProperty() {
-        Bean bean = new Bean();
-
-        try {
-            bean.getProperty("writeOnlyProperty");
-            fail();
-        } catch (GroovyRuntimeException e) {
-            assertThat(e.getMessage(), equalTo("Cannot get the value of write-only property 'writeOnlyProperty' on <bean>."));
-        }
-    }
-
-    @Test
-    public void canSetPropertyWhenGetterAndSetterHaveDifferentTypes() {
-        Bean bean = new Bean();
-
-        bean.setProperty("differentTypesProperty", "91");
-        assertThat(bean.getProperty("differentTypesProperty"), equalTo((Object) 91));
-    }
-
-    @Test
-    public void groovyObjectHasPropertiesDefinedByClassMetaInfo() {
-        GroovyBean bean = new GroovyBean();
-        assertTrue(bean.hasProperty("groovyProperty"));
-        assertTrue(bean.hasProperty("dynamicGroovyProperty"));
-    }
-
-    @Test
-    public void groovyObjectHasPropertiesInheritedFromSuperClass() {
-        GroovyBean bean = new GroovyBean();
-        assertTrue(bean.hasProperty("readWriteProperty"));
-        assertTrue(bean.hasProperty("readOnlyProperty"));
-        assertTrue(bean.hasProperty("writeOnlyProperty"));
-    }
-
-    @Test
-    public void canGetAndSetGroovyObjectClassProperty() {
-        GroovyBean bean = new GroovyBean();
-        bean.setGroovyProperty("value");
-
-        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "value"));
-
-        bean.setProperty("groovyProperty", "new value");
-
-        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "new value"));
-        assertThat(bean.getGroovyProperty(), equalTo((Object) "new value"));
-    }
-
-    @Test
-    public void canGetAndSetGroovyDynamicProperty() {
-        GroovyBean bean = new GroovyBean();
-
-        assertThat(bean.getProperty("dynamicGroovyProperty"), equalTo(null));
-
-        bean.setProperty("dynamicGroovyProperty", "new value");
-
-        assertThat(bean.getProperty("dynamicGroovyProperty"), equalTo((Object) "new value"));
-    }
-
-    @Test
-    public void canGetButNotSetPropertiesOnJavaObjectFromGroovy() {
-        DynamicObjectHelperTestHelper.assertCanGetProperties(new Bean());
-    }
-    
-    @Test
-    public void canGetAndSetPropertiesOnGroovyObjectFromGroovy() {
-        DynamicObjectHelperTestHelper.assertCanGetAndSetProperties(new GroovyBean());
-    }
-
-    @Test
-    public void canGetAndSetPropertiesOnGroovyObjectFromJava() {
-        assertCanGetAndSetProperties(new GroovyBean());
-    }
-
-    @Test
-    public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromJava() {
-        assertCanGetAndSetProperties(new DynamicBean());
-    }
-
-    private void assertCanGetAndSetProperties(DynamicObject bean) {
-        bean.setProperty("readWriteProperty", "value");
-        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
-        bean.setProperty("groovyProperty", "value");
-        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "value"));
-        bean.setProperty("additional", "value");
-        assertThat(bean.getProperty("additional"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromGroovy() {
-        DynamicObjectHelperTestHelper.assertCanGetAndSetProperties(new DynamicBean());
-    }
-
-    @Test
-    public void hasPropertyDefinedByConventionObject() {
-        Bean bean = new Bean();
-        Convention convention = new DefaultConvention();
-
-        assertFalse(bean.hasProperty("conventionProperty"));
-
-        bean.setConvention(convention);
-        assertFalse(bean.hasProperty("conventionProperty"));
-
-        convention.getPlugins().put("test", new ConventionBean());
-        assertTrue(bean.hasProperty("conventionProperty"));
-    }
-
-    @Test
-    public void canGetAndSetPropertyDefinedByConventionObject() {
-        Bean bean = new Bean();
-        Convention convention = new DefaultConvention();
-        bean.setConvention(convention);
-        ConventionBean conventionBean = new ConventionBean();
-        convention.getPlugins().put("test", conventionBean);
-
-        conventionBean.setConventionProperty("value");
-
-        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
-
-        bean.setProperty("conventionProperty", "new value");
-
-        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "new value"));
-        assertThat(conventionBean.getConventionProperty(), equalTo((Object) "new value"));
-    }
-
-    @Test
-    public void hasPropertyDefinedByParent() {
-        Bean parent = new Bean();
-        parent.setProperty("parentProperty", "value");
-
-        Bean bean = new Bean();
-        assertFalse(bean.hasProperty("parentProperty"));
-
-        bean.setParent(parent);
-        assertTrue(bean.hasProperty("parentProperty"));
-    }
-
-    @Test
-    public void canGetPropertyDefinedByParent() {
-        Bean parent = new Bean();
-        parent.setProperty("parentProperty", "value");
-
-        Bean bean = new Bean();
-        bean.setParent(parent);
-
-        assertThat(bean.getProperty("parentProperty"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void cannotSetPropertyDefinedByParent() {
-        Bean parent = new Bean();
-
-        Bean bean = new Bean();
-        bean.setParent(parent);
-        bean.setProperty("parentProperty", "value");
-
-        assertFalse(parent.hasProperty("parentProperty"));
-    }
-
-    @Test
-    public void hasAdditionalProperty() {
-        Bean bean = new Bean();
-
-        assertFalse(bean.hasProperty("additional"));
-
-        bean.setProperty("additional", "value");
-        assertTrue(bean.hasProperty("additional"));
-
-        bean.setProperty("additional", null);
-        assertTrue(bean.hasProperty("additional"));
-    }
-
-    @Test
-    public void canGetAndSetAdditionalProperty() {
-        Bean bean = new Bean();
-
-        bean.setProperty("additional", "value");
-        assertThat(bean.getProperty("additional"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void canGetAndSetPropertyDefinedByAdditionalObject() {
-        Bean otherObject = new Bean();
-        otherObject.setProperty("otherObject", "value");
-
-        Bean bean = new Bean();
-        bean.helper.addObject(otherObject, DynamicObjectHelper.Location.BeforeConvention);
-
-        assertTrue(bean.hasProperty("otherObject"));
-        assertThat(bean.getProperty("otherObject"), equalTo((Object) "value"));
-        bean.setProperty("otherObject", "new value");
-
-        assertThat(otherObject.getProperty("otherObject"), equalTo((Object) "new value"));
-    }
-    
-    @Test
-    public void classPropertyTakesPrecedenceOverAdditionalProperty() {
-        Bean bean = new Bean();
-        bean.setReadWriteProperty("value");
-        bean.helper.getAdditionalProperties().put("readWriteProperty", "additional");
-
-        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
-
-        bean.setProperty("readWriteProperty", "new value");
-
-        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "new value"));
-        assertThat(bean.getReadWriteProperty(), equalTo((Object) "new value"));
-        assertThat(bean.helper.getAdditionalProperties().get("readWriteProperty"), equalTo((Object) "additional"));
-    }
-
-    @Test
-    public void additionalPropertyTakesPrecedenceOverConventionProperty() {
-        Bean bean = new Bean();
-        bean.setProperty("conventionProperty", "value");
-
-        Convention convention = new DefaultConvention();
-        bean.setConvention(convention);
-        ConventionBean conventionBean = new ConventionBean();
-        convention.getPlugins().put("test", conventionBean);
-
-        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
-
-        bean.setProperty("conventionProperty", "new value");
-
-        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "new value"));
-        assertThat(bean.helper.getAdditionalProperties().get("conventionProperty"), equalTo((Object) "new value"));
-        assertThat(conventionBean.getConventionProperty(), nullValue());
-    }
-
-    @Test
-    public void conventionPropertyTakesPrecedenceOverParentProperty() {
-        Bean parent = new Bean();
-        parent.setProperty("conventionProperty", "parent");
-
-        Bean bean = new Bean();
-        bean.setParent(parent);
-
-        Convention convention = new DefaultConvention();
-        bean.setConvention(convention);
-        ConventionBean conventionBean = new ConventionBean();
-        conventionBean.setConventionProperty("value");
-        convention.getPlugins().put("test", conventionBean);
-
-        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void canGetAllProperties() {
-        Bean parent = new Bean();
-        parent.setProperty("parentProperty", "parentProperty");
-        parent.setReadWriteProperty("ignore me");
-        parent.doSetReadOnlyProperty("ignore me");
-        Convention parentConvention = new DefaultConvention();
-        parentConvention.getPlugins().put("parent", new ConventionBean());
-        parent.setConvention(parentConvention);
-
-        GroovyBean bean = new GroovyBean();
-        bean.setProperty("additional", "additional");
-        bean.setReadWriteProperty("readWriteProperty");
-        bean.doSetReadOnlyProperty("readOnlyProperty");
-        bean.setGroovyProperty("groovyProperty");
-        Convention convention = new DefaultConvention();
-        ConventionBean conventionBean = new ConventionBean();
-        conventionBean.setConventionProperty("conventionProperty");
-        convention.getPlugins().put("bean", conventionBean);
-        bean.setConvention(convention);
-        bean.setParent(parent);
-
-        Map<String, Object> properties = bean.getProperties();
-        assertThat(properties.get("properties"), sameInstance((Object) properties));
-        assertThat(properties.get("readWriteProperty"), equalTo((Object) "readWriteProperty"));
-        assertThat(properties.get("readOnlyProperty"), equalTo((Object) "readOnlyProperty"));
-        assertThat(properties.get("parentProperty"), equalTo((Object) "parentProperty"));
-        assertThat(properties.get("additional"), equalTo((Object) "additional"));
-        assertThat(properties.get("groovyProperty"), equalTo((Object) "groovyProperty"));
-        assertThat(properties.get("groovyDynamicProperty"), equalTo(null));
-        assertThat(properties.get("conventionProperty"), equalTo((Object) "conventionProperty"));
-    }
-
-    @Test
-    public void canGetAllPropertiesFromGroovy() {
-        DynamicObjectHelperTestHelper.assertCanGetAllProperties(new Bean());
-        DynamicObjectHelperTestHelper.assertCanGetAllProperties(new GroovyBean());
-        DynamicObjectHelperTestHelper.assertCanGetAllProperties(new DynamicBean());
-    }
-
-    @Test
-    public void getPropertyFailsForUnknownProperty() {
-        Bean bean = new Bean();
-
-        try {
-            bean.getProperty("unknown");
-            fail();
-        } catch (MissingPropertyException e) {
-            assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
-        }
-
-        bean.setParent(new Bean(){
-            @Override
-            public String toString() {
-                return "<parent>";
-            }
-        });
-
-        try {
-            bean.getProperty("unknown");
-            fail();
-        } catch (MissingPropertyException e) {
-            assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
-        }
-    }
-
-    @Test
-    public void additionalPropertyWithNullValueIsNotTreatedAsUnknown() {
-        Bean bean = new Bean();
-        bean.setProperty("additional", null);
-        assertThat(bean.getProperty("additional"), nullValue());
-    }
-
-    @Test
-    public void canInvokeMethodDefinedByClass() {
-        Bean bean = new Bean();
-        assertTrue(bean.hasMethod("javaMethod", "a", "b"));
-        assertThat(bean.invokeMethod("javaMethod", "a", "b"), equalTo((Object) "java:a.b"));
-    }
-
-    @Test
-    public void canInvokeMethodDefinedByMetaClass() {
-        Bean bean = new GroovyBean();
-
-        assertTrue(bean.hasMethod("groovyMethod", "a", "b"));
-        assertThat(bean.invokeMethod("groovyMethod", "a", "b"), equalTo((Object) "groovy:a.b"));
-
-        assertTrue(bean.hasMethod("dynamicGroovyMethod", "a", "b"));
-        assertThat(bean.invokeMethod("dynamicGroovyMethod", "a", "b"), equalTo((Object) "dynamicGroovy:a.b"));
-    }
-
-    @Test
-    public void canInvokeMethodDefinedByScriptObject() {
-        Bean bean = new Bean();
-        Script script = HelperUtil.createScript("def scriptMethod(a, b) { \"script:$a.$b\" } ");
-        bean.helper.addObject(new BeanDynamicObject(script), DynamicObjectHelper.Location.BeforeConvention);
-
-        assertTrue(bean.hasMethod("scriptMethod", "a", "b"));
-        assertThat(bean.invokeMethod("scriptMethod", "a", "b").toString(), equalTo((Object) "script:a.b"));
-    }
-
-    @Test
-    public void canInvokeMethodDefinedByConvention() {
-        Bean bean = new Bean();
-        Convention convention = new DefaultConvention();
-        convention.getPlugins().put("bean", new ConventionBean());
-
-        assertFalse(bean.hasMethod("conventionMethod", "a", "b"));
-
-        bean.setConvention(convention);
-
-        assertTrue(bean.hasMethod("conventionMethod", "a", "b"));
-        assertThat(bean.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
-    }
-
-    @Test
-    public void canInvokeMethodDefinedByParent() {
-        Bean parent = new Bean() {
-            public String parentMethod(String a, String b) {
-                return String.format("parent:%s.%s", a, b);
-            }
-        };
-        Bean bean = new Bean();
-
-        assertFalse(bean.hasMethod("parentMethod", "a", "b"));
-        
-        bean.setParent(parent);
-
-        assertTrue(bean.hasMethod("parentMethod", "a", "b"));
-        assertThat(bean.invokeMethod("parentMethod", "a", "b"), equalTo((Object) "parent:a.b"));
-    }
-
-    @Test
-    public void canInvokeMethodsOnJavaObjectFromGroovy() {
-        Bean bean = new Bean();
-        Convention convention = new DefaultConvention();
-        bean.setConvention(convention);
-        convention.getPlugins().put("bean", new ConventionBean());
-        DynamicObjectHelperTestHelper.assertCanCallMethods(bean);
-    }
-
-    @Test
-    public void canInvokeMethodsOnGroovyObjectFromGroovy() {
-        GroovyBean bean = new GroovyBean();
-        Convention convention = new DefaultConvention();
-        bean.setConvention(convention);
-        convention.getPlugins().put("bean", new ConventionBean());
-        DynamicObjectHelperTestHelper.assertCanCallMethods(bean);
-    }
-
-    @Test
-    public void canInvokeMethodsOnJavaSubClassOfGroovyObjectFromGroovy() {
-        DynamicBean bean = new DynamicBean();
-        Convention convention = new DefaultConvention();
-        bean.setConvention(convention);
-        convention.getPlugins().put("bean", new ConventionBean());
-        DynamicObjectHelperTestHelper.assertCanCallMethods(bean);
-    }
-
-    @Test
-    public void canInvokeClosurePropertyAsAMethod() {
-        Bean bean = new Bean();
-        bean.setProperty("someMethod", HelperUtil.toClosure("{ param -> param.toLowerCase() }"));
-        assertThat(bean.invokeMethod("someMethod", "Param"), equalTo((Object) "param"));
-    }
-    
-    @Test
-    public void invokeMethodFailsForUnknownMethod() {
-        Bean bean = new Bean();
-        try {
-            bean.invokeMethod("unknown", "a", 12);
-            fail();
-        } catch (MissingMethodException e) {
-            assertThat(e.getMessage(), equalTo("Could not find method unknown() for arguments [a, 12] on <bean>."));
-        }
-    }
-
-    @Test
-    public void propagatesGetPropertyException() {
-        final RuntimeException failure = new RuntimeException();
-        Bean bean = new Bean() {
-            String getFailure() {
-                throw failure;
-            }
-        };
-
-        try {
-            bean.getProperty("failure");
-            fail();
-        } catch (Exception e) {
-            assertThat(e, sameInstance((Exception) failure));
-        }
-    }
-
-    @Test
-    public void propagatesSetPropertyException() {
-        final RuntimeException failure = new RuntimeException();
-        Bean bean = new Bean() {
-            void setFailure(String value) {
-                throw failure;
-            }
-        };
-
-        try {
-            bean.setProperty("failure", "a");
-            fail();
-        } catch (Exception e) {
-            assertThat(e, sameInstance((Exception) failure));
-        }
-    }
-
-    @Test
-    public void propagatesInvokeMethodException() {
-        final RuntimeException failure = new RuntimeException();
-        Bean bean = new Bean() {
-            void failure() {
-                throw failure;
-            }
-        };
-
-        try {
-            bean.invokeMethod("failure");
-            fail();
-        } catch (Exception e) {
-            assertThat(e, sameInstance((Exception) failure));
-        }
-    }
-
-    @Test
-    public void additionalPropertiesAreInherited() {
-        Bean bean = new Bean();
-        bean.setProperty("additional", "value");
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasProperty("additional"));
-        assertThat(inherited.getProperty("additional"), equalTo((Object) "value"));
-        assertThat(inherited.getProperties().get("additional"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void inheritedAdditionalPropertiesTrackChanges() {
-        Bean bean = new Bean();
-
-        DynamicObject inherited = bean.getInheritable();
-        assertFalse(inherited.hasProperty("additional"));
-
-        bean.setProperty("additional", "value");
-        assertTrue(inherited.hasProperty("additional"));
-        assertThat(inherited.getProperty("additional"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void additionalObjectPropertiesAreInherited() {
-        Bean other = new Bean();
-        other.setProperty("other", "value");
-        Bean bean = new Bean();
-        bean.helper.addObject(other, DynamicObjectHelper.Location.BeforeConvention);
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasProperty("other"));
-        assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
-        assertThat(inherited.getProperties().get("other"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void inheritedAdditionalObjectPropertiesTrackChanges() {
-        Bean other = new Bean();
-        other.setProperty("other", "value");
-        Bean bean = new Bean();
-
-        DynamicObject inherited = bean.getInheritable();
-        assertFalse(inherited.hasProperty("other"));
-
-        bean.helper.addObject(other, DynamicObjectHelper.Location.BeforeConvention);
-
-        assertTrue(inherited.hasProperty("other"));
-        assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void conventionPropertiesAreInherited() {
-        Bean bean = new Bean();
-        Convention convention = new DefaultConvention();
-        ConventionBean conventionBean = new ConventionBean();
-        conventionBean.setConventionProperty("value");
-        convention.getPlugins().put("convention", conventionBean);
-        bean.setConvention(convention);
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasProperty("conventionProperty"));
-        assertThat(inherited.getProperty("conventionProperty"), equalTo((Object) "value"));
-        assertThat(inherited.getProperties().get("conventionProperty"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void inheritedConventionPropertiesTrackChanges() {
-        Bean bean = new Bean();
-
-        DynamicObject inherited = bean.getInheritable();
-        assertFalse(inherited.hasProperty("conventionProperty"));
-
-        Convention convention = new DefaultConvention();
-        ConventionBean conventionBean = new ConventionBean();
-        conventionBean.setConventionProperty("value");
-        convention.getPlugins().put("convention", conventionBean);
-        bean.setConvention(convention);
-
-        assertTrue(inherited.hasProperty("conventionProperty"));
-        assertThat(inherited.getProperty("conventionProperty"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void parentPropertiesAreInherited() {
-        Bean parent = new Bean();
-        parent.setProperty("parentProperty", "value");
-        Bean bean = new Bean();
-        bean.setParent(parent);
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasProperty("parentProperty"));
-        assertThat(inherited.getProperty("parentProperty"), equalTo((Object) "value"));
-        assertThat(inherited.getProperties().get("parentProperty"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void otherPropertiesAreNotInherited() {
-        Bean bean = new Bean();
-        assertTrue(bean.hasProperty("readWriteProperty"));
-
-        DynamicObject inherited = bean.getInheritable();
-        assertFalse(inherited.hasProperty("readWriteProperty"));
-        assertFalse(inherited.getProperties().containsKey("readWriteProperty"));
-    }
-
-    @Test
-    public void cannotSetInheritedProperties() {
-        Bean bean = new Bean();
-        bean.setProperty("additional", "value");
-
-        DynamicObject inherited = bean.getInheritable();
-        try {
-            inherited.setProperty("additional", "new value");
-            fail();
-        } catch (MissingPropertyException e) {
-            assertThat(e.getMessage(), equalTo("Could not find property 'additional' inherited from <bean>."));
-        }
-    }
-
-    @Test
-    public void conventionMethodsAreInherited() {
-        Bean bean = new Bean();
-        Convention convention = new DefaultConvention();
-        convention.getPlugins().put("convention", new ConventionBean());
-        bean.setConvention(convention);
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
-        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
-    }
-
-    @Test
-    public void additionalObjectMethodsAreInherited() {
-        Bean other = new Bean();
-        Convention convention = new DefaultConvention();
-        convention.getPlugins().put("convention", new ConventionBean());
-        other.setConvention(convention);
-
-        Bean bean = new Bean();
-        bean.helper.addObject(other, DynamicObjectHelper.Location.BeforeConvention);
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
-        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
-    }
-
-    @Test
-    public void parentMethodsAreInherited() {
-        Bean parent = new Bean();
-        Convention convention = new DefaultConvention();
-        convention.getPlugins().put("convention", new ConventionBean());
-        parent.setConvention(convention);
-        Bean bean = new Bean();
-        bean.setParent(parent);
-
-        DynamicObject inherited = bean.getInheritable();
-        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
-        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
-    }
-
-    @Test
-    public void otherMethodsAreNotInherited() {
-        Bean bean = new Bean();
-        assertTrue(bean.hasMethod("javaMethod", "a", "b"));
-
-        DynamicObject inherited = bean.getInheritable();
-        assertFalse(inherited.hasMethod("javaMethod", "a", "b"));
-    }
-    
-    public static class Bean implements DynamicObject {
-        private String readWriteProperty;
-        private String readOnlyProperty;
-        private String writeOnlyProperty;
-        private Integer differentTypesProperty;
-        final DynamicObjectHelper helper;
-
-        public Bean() {
-            helper = new DynamicObjectHelper(this);
-        }
-
-        @Override
-        public String toString() {
-            return "<bean>";
-        }
-
-        public void setConvention(Convention convention) {
-            helper.setConvention(convention);
-        }
-
-        public void setParent(DynamicObject parent) {
-            helper.setParent(parent);
-        }
-
-        public String getReadOnlyProperty() {
-            return readOnlyProperty;
-        }
-
-        public void doSetReadOnlyProperty(String readOnlyProperty) {
-            this.readOnlyProperty = readOnlyProperty;
-        }
-
-        public String doGetWriteOnlyProperty() {
-            return writeOnlyProperty;
-        }
-
-        public void setWriteOnlyProperty(String writeOnlyProperty) {
-            this.writeOnlyProperty = writeOnlyProperty;
-        }
-
-        public String getReadWriteProperty() {
-            return readWriteProperty;
-        }
-
-        public void setReadWriteProperty(String property) {
-            this.readWriteProperty = property;
-        }
-
-        public Integer getDifferentTypesProperty() {
-            return differentTypesProperty;
-        }
-
-        public void setDifferentTypesProperty(Object differentTypesProperty) {
-            this.differentTypesProperty = Integer.parseInt(differentTypesProperty.toString());
-        }
-
-        public String javaMethod(String a, String b) {
-            return String.format("java:%s.%s", a, b);
-        }
-        
-        public Object getProperty(String name) {
-            return helper.getProperty(name);
-        }
-
-        public boolean hasProperty(String name) {
-            return helper.hasProperty(name);
-        }
-
-        public void setProperty(String name, Object value) {
-            helper.setProperty(name, value);
-        }
-
-        public Map<String, Object> getProperties() {
-            return helper.getProperties();
-        }
-
-        public boolean hasMethod(String name, Object... arguments) {
-            return helper.hasMethod(name, arguments);
-        }
-
-        public Object invokeMethod(String name, Object... arguments) {
-            return helper.invokeMethod(name, arguments);
-        }
-
-        public Object methodMissing(String name, Object params) {
-            return helper.invokeMethod(name, (Object[]) params);
-        }
-
-        public Object propertyMissing(String name) {
-            return getProperty(name);
-        }
-
-        public DynamicObject getInheritable() {
-            return helper.getInheritable();
-        }
-    }
-
-    private static class DynamicBean extends GroovyBean {
-    }
-
-    private static class ConventionBean {
-        private String conventionProperty;
-
-        public String getConventionProperty() {
-            return conventionProperty;
-        }
-
-        public void setConventionProperty(String conventionProperty) {
-            this.conventionProperty = conventionProperty;
-        }
-
-        public String conventionMethod(String a, String b) {
-            return String.format("convention:%s.%s", a, b);
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTestHelper.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTestHelper.groovy
deleted file mode 100644
index 2f95345..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTestHelper.groovy
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 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
-
-import static org.junit.Assert.*
-
-public class DynamicObjectHelperTestHelper {
-    public static void assertCanGetAllProperties (DynamicObjectHelperTest.Bean bean) {
-        bean.readWriteProperty = 'readWrite'
-        bean.setProperty('additional', 'additional')
-        assertEquals(bean.getProperties().readWriteProperty, 'readWrite')
-        assertEquals(bean.getProperties().additional, 'additional')
-    }
-
-    public static void assertCanGetProperties (DynamicObjectHelperTest.Bean bean) {
-        bean.readWriteProperty = 'value'
-        assertEquals(bean.readWriteProperty, 'value')
-
-        bean.doSetReadOnlyProperty('value')
-        assertEquals(bean.readOnlyProperty, 'value')
-
-        bean.helper.additionalProperties.additional = 'value'
-        assertEquals(bean.additional, 'value')
-
-        bean.setProperty 'another', 'value'
-        assertEquals(bean.another, 'value')
-    }
-    
-    public static void assertCanGetAndSetProperties (DynamicObjectHelperTest.Bean bean) {
-        bean.readWriteProperty = 'value'
-        assertEquals(bean.readWriteProperty, 'value')
-
-        bean.doSetReadOnlyProperty('value')
-        assertEquals(bean.readOnlyProperty, 'value')
-
-        bean.additional = 'value'
-        assertEquals(bean.additional, 'value')
-
-        bean.setProperty 'another', 'value'
-        assertEquals(bean.another, 'value')
-    }
-
-    public static void assertCanCallMethods (DynamicObjectHelperTest.Bean bean) {
-        assertEquals(bean.javaMethod('a', 'b'), 'java:a.b')
-        assertTrue(bean.hasMethod('conventionMethod', 'a', 'b'))
-        assertEquals(bean.conventionMethod('a', 'b'), 'convention:a.b')
-    }
-}
-
-public class DynamicBean extends DynamicObjectHelperTest.Bean {
-    def propertyMissing(String name) {
-        super.getProperty(name)
-    }
-
-//    def methodMissing(String name, params) {
-//        super.methodMissing(name, params)
-//    }
-
-    void setProperty(String name, Object value) {
-        super.setProperty(name, value)
-    }
-}
-
-public class GroovyBean extends DynamicBean {
-    String groovyProperty
-
-    def GroovyBean() {
-        Map values = [:]
-        ExpandoMetaClass metaClass = new ExpandoMetaClass(GroovyBean.class, false)
-        metaClass.getDynamicGroovyProperty << {-> values.dynamicGroovyProperty }
-        metaClass.setDynamicGroovyProperty << {value -> values.dynamicGroovyProperty = value}
-        metaClass.dynamicGroovyMethod << {a, b -> "dynamicGroovy:$a.$b".toString() }
-        metaClass.initialize()
-        setMetaClass(metaClass)
-    }
-
-    def groovyMethod(a, b) {
-        "groovy:$a.$b".toString()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTest.java
new file mode 100644
index 0000000..0fcc898
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTest.java
@@ -0,0 +1,865 @@
+/*
+ * Copyright 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;
+
+import groovy.lang.*;
+import groovy.lang.MissingMethodException;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.api.plugins.Convention;
+import org.gradle.testfixtures.ProjectBuilder;
+import org.gradle.util.HelperUtil;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class ExtensibleDynamicObjectTest {
+    @Test
+    public void hasPropertiesDefinedByClass() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasProperty("readWriteProperty"));
+        assertTrue(bean.hasProperty("readOnlyProperty"));
+        assertTrue(bean.hasProperty("writeOnlyProperty"));
+    }
+
+    @Test
+    public void canGetAndSetClassProperty() {
+        Bean bean = new Bean();
+        bean.setReadWriteProperty("value");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("readWriteProperty", "new value");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "new value"));
+        assertThat(bean.getReadWriteProperty(), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void canGetReadOnlyClassProperty() {
+        Bean bean = new Bean();
+        bean.doSetReadOnlyProperty("value");
+
+        assertThat(bean.getProperty("readOnlyProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void cannotSetReadOnlyClassProperty() {
+        Bean bean = new Bean();
+
+        try {
+            bean.setProperty("readOnlyProperty", "value");
+            fail();
+        } catch (ReadOnlyPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Cannot set the value of read-only property 'readOnlyProperty' on <bean>."));
+        }
+    }
+    
+    @Test
+    public void canSetWriteOnlyClassProperty() {
+        Bean bean = new Bean();
+        bean.setProperty("writeOnlyProperty", "value");
+        assertThat(bean.doGetWriteOnlyProperty(), equalTo("value"));
+    }
+
+    @Test
+    public void cannotGetWriteOnlyClassProperty() {
+        Bean bean = new Bean();
+
+        try {
+            bean.getProperty("writeOnlyProperty");
+            fail();
+        } catch (GroovyRuntimeException e) {
+            assertThat(e.getMessage(), equalTo("Cannot get the value of write-only property 'writeOnlyProperty' on <bean>."));
+        }
+    }
+
+    @Test
+    public void canSetPropertyWhenGetterAndSetterHaveDifferentTypes() {
+        Bean bean = new Bean();
+
+        bean.setProperty("differentTypesProperty", "91");
+        assertThat(bean.getProperty("differentTypesProperty"), equalTo((Object) 91));
+    }
+
+    @Test
+    public void groovyObjectHasPropertiesDefinedByClassMetaInfo() {
+        GroovyBean bean = new GroovyBean();
+        assertTrue(bean.hasProperty("groovyProperty"));
+        assertTrue(bean.hasProperty("dynamicGroovyProperty"));
+    }
+
+    @Test
+    public void groovyObjectHasPropertiesInheritedFromSuperClass() {
+        GroovyBean bean = new GroovyBean();
+        assertTrue(bean.hasProperty("readWriteProperty"));
+        assertTrue(bean.hasProperty("readOnlyProperty"));
+        assertTrue(bean.hasProperty("writeOnlyProperty"));
+    }
+
+    @Test
+    public void canGetAndSetGroovyObjectClassProperty() {
+        GroovyBean bean = new GroovyBean();
+        bean.setGroovyProperty("value");
+
+        assertThat(((Bean)bean).getProperty("groovyProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("groovyProperty", "new value");
+
+        assertThat(((Bean)bean).getProperty("groovyProperty"), equalTo((Object) "new value"));
+        assertThat(bean.getGroovyProperty(), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void canGetAndSetGroovyDynamicProperty() {
+        GroovyBean bean = new GroovyBean();
+
+        assertThat(((Bean)bean).getProperty("dynamicGroovyProperty"), equalTo(null));
+
+        bean.setProperty("dynamicGroovyProperty", "new value");
+
+        assertThat(((Bean)bean).getProperty("dynamicGroovyProperty"), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void canGetButNotSetPropertiesOnJavaObjectFromGroovy() {
+        ExtensibleDynamicObjectTestHelper.assertCanGetProperties(new Bean());
+    }
+    
+    @Test
+    public void canGetAndSetPropertiesOnGroovyObjectFromGroovy() {
+        ExtensibleDynamicObjectTestHelper.assertCanGetAndSetProperties(new GroovyBean());
+    }
+
+    @Test
+    public void canGetAndSetPropertiesOnGroovyObjectFromJava() {
+        assertCanGetAndSetProperties(new GroovyBean());
+    }
+
+    @Test
+    public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromJava() {
+        assertCanGetAndSetProperties(new DynamicBean());
+    }
+
+    private void assertCanGetAndSetProperties(DynamicObject bean) {
+        bean.setProperty("readWriteProperty", "value");
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
+        bean.setProperty("groovyProperty", "value");
+        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "value"));
+        bean.setProperty("additional", "value");
+        assertThat(bean.getProperty("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromGroovy() {
+        ExtensibleDynamicObjectTestHelper.assertCanGetAndSetProperties(new DynamicBean());
+    }
+
+    @Test
+    public void hasPropertyDefinedByConventionObject() {
+        Bean bean = new Bean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+
+        assertFalse(bean.hasProperty("conventionProperty"));
+        assertFalse(bean.hasProperty("conventionProperty"));
+
+        convention.getPlugins().put("test", new ConventionBean());
+        assertTrue(bean.hasProperty("conventionProperty"));
+    }
+
+    @Test
+    public void canGetAndSetPropertyDefinedByConventionObject() {
+        Bean bean = new Bean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        convention.getPlugins().put("test", conventionBean);
+
+        conventionBean.setConventionProperty("value");
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("conventionProperty", "new value");
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "new value"));
+        assertThat(conventionBean.getConventionProperty(), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void hasPropertyDefinedByParent() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "value");
+
+        Bean bean = new Bean();
+        assertFalse(bean.hasProperty("parentProperty"));
+
+        bean.setParent(parent);
+        assertTrue(bean.hasProperty("parentProperty"));
+    }
+
+    @Test
+    public void canGetPropertyDefinedByParent() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "value");
+
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        assertThat(bean.getProperty("parentProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void cannotSetPropertyDefinedByParent() {
+        Bean parent = new Bean();
+
+        Bean bean = new Bean();
+        bean.setParent(parent);
+        bean.setProperty("parentProperty", "value");
+
+        assertFalse(parent.hasProperty("parentProperty"));
+    }
+
+    @Test
+    public void hasAdditionalProperty() {
+        Bean bean = new Bean();
+
+        assertFalse(bean.hasProperty("additional"));
+
+        bean.setProperty("additional", "value");
+        assertTrue(bean.hasProperty("additional"));
+
+        bean.setProperty("additional", null);
+        assertTrue(bean.hasProperty("additional"));
+    }
+
+    @Test
+    public void canGetAndSetAdditionalProperty() {
+        Bean bean = new Bean();
+
+        bean.setProperty("additional", "value");
+        assertThat(bean.getProperty("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canGetAndSetPropertyDefinedByAdditionalObject() {
+        Bean otherObject = new Bean();
+        otherObject.setProperty("otherObject", "value");
+
+        Bean bean = new Bean();
+        bean.extensibleDynamicObject.addObject(otherObject, ExtensibleDynamicObject.Location.BeforeConvention);
+
+        assertTrue(bean.hasProperty("otherObject"));
+        assertThat(bean.getProperty("otherObject"), equalTo((Object) "value"));
+        bean.setProperty("otherObject", "new value");
+
+        assertThat(otherObject.getProperty("otherObject"), equalTo((Object) "new value"));
+    }
+    
+    @Test
+    public void classPropertyTakesPrecedenceOverAdditionalProperty() {
+        Bean bean = new Bean();
+        bean.setReadWriteProperty("value");
+        bean.extensibleDynamicObject.getDynamicProperties().set("readWriteProperty", "additional");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("readWriteProperty", "new value");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "new value"));
+        assertThat(bean.getReadWriteProperty(), equalTo((Object) "new value"));
+        assertThat(bean.extensibleDynamicObject.getDynamicProperties().get("readWriteProperty"), equalTo((Object) "additional"));
+    }
+
+    @Test
+    public void additionalPropertyTakesPrecedenceOverConventionProperty() {
+        Bean bean = new Bean();
+        bean.setProperty("conventionProperty", "value");
+
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        convention.getPlugins().put("test", conventionBean);
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("conventionProperty", "new value");
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "new value"));
+        assertThat(bean.extensibleDynamicObject.getDynamicProperties().get("conventionProperty"), equalTo((Object) "new value"));
+        assertThat(conventionBean.getConventionProperty(), nullValue());
+    }
+
+    @Test
+    public void conventionPropertyTakesPrecedenceOverParentProperty() {
+        Bean parent = new Bean();
+        parent.setProperty("conventionProperty", "parent");
+
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("value");
+        convention.getPlugins().put("test", conventionBean);
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canGetAllProperties() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "parentProperty");
+        parent.setReadWriteProperty("ignore me");
+        parent.doSetReadOnlyProperty("ignore me");
+        Convention parentConvention = parent.extensibleDynamicObject.getConvention();
+        parentConvention.getPlugins().put("parent", new ConventionBean());
+
+        GroovyBean bean = new GroovyBean();
+        bean.setProperty("additional", "additional");
+        bean.setReadWriteProperty("readWriteProperty");
+        bean.doSetReadOnlyProperty("readOnlyProperty");
+        bean.setGroovyProperty("groovyProperty");
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("conventionProperty");
+        convention.getPlugins().put("bean", conventionBean);
+        bean.setParent(parent);
+
+        Map<String, Object> properties = bean.getProperties();
+        assertThat(properties.get("properties"), sameInstance((Object) properties));
+        assertThat(properties.get("readWriteProperty"), equalTo((Object) "readWriteProperty"));
+        assertThat(properties.get("readOnlyProperty"), equalTo((Object) "readOnlyProperty"));
+        assertThat(properties.get("parentProperty"), equalTo((Object) "parentProperty"));
+        assertThat(properties.get("additional"), equalTo((Object) "additional"));
+        assertThat(properties.get("groovyProperty"), equalTo((Object) "groovyProperty"));
+        assertThat(properties.get("groovyDynamicProperty"), equalTo(null));
+        assertThat(properties.get("conventionProperty"), equalTo((Object) "conventionProperty"));
+    }
+
+    @Test
+    public void canGetAllPropertiesFromGroovy() {
+        ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new Bean());
+        ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new GroovyBean());
+        ExtensibleDynamicObjectTestHelper.assertCanGetAllProperties(new DynamicBean());
+    }
+
+    @Test
+    public void getPropertyFailsForUnknownProperty() {
+        Bean bean = new Bean();
+
+        try {
+            bean.getProperty("unknown");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
+        }
+
+        bean.setParent(new Bean(){
+            @Override
+            public String toString() {
+                return "<parent>";
+            }
+        });
+
+        try {
+            bean.getProperty("unknown");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
+        }
+    }
+
+    @Test
+    public void additionalPropertyWithNullValueIsNotTreatedAsUnknown() {
+        Bean bean = new Bean();
+        bean.setProperty("additional", null);
+        assertThat(bean.getProperty("additional"), nullValue());
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByClass() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasMethod("javaMethod", "a", "b"));
+        assertThat(bean.invokeMethod("javaMethod", "a", "b"), equalTo((Object) "java:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByMetaClass() {
+        Bean bean = new GroovyBean();
+
+        assertTrue(bean.hasMethod("groovyMethod", "a", "b"));
+        assertThat(bean.invokeMethod("groovyMethod", "a", "b"), equalTo((Object) "groovy:a.b"));
+
+        assertTrue(bean.hasMethod("dynamicGroovyMethod", "a", "b"));
+        assertThat(bean.invokeMethod("dynamicGroovyMethod", "a", "b"), equalTo((Object) "dynamicGroovy:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByScriptObject() {
+        Bean bean = new Bean();
+        Script script = HelperUtil.createScript("def scriptMethod(a, b) { \"script:$a.$b\" } ");
+        bean.extensibleDynamicObject.addObject(new BeanDynamicObject(script), ExtensibleDynamicObject.Location.BeforeConvention);
+
+        assertTrue(bean.hasMethod("scriptMethod", "a", "b"));
+        assertThat(bean.invokeMethod("scriptMethod", "a", "b").toString(), equalTo((Object) "script:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByConvention() {
+        Bean bean = new Bean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+
+        assertFalse(bean.hasMethod("conventionMethod", "a", "b"));
+
+        convention.getPlugins().put("bean", new ConventionBean());
+        assertTrue(bean.hasMethod("conventionMethod", "a", "b"));
+        assertThat(bean.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByParent() {
+        Bean parent = new Bean() {
+            public String parentMethod(String a, String b) {
+                return String.format("parent:%s.%s", a, b);
+            }
+        };
+        Bean bean = new Bean();
+
+        assertFalse(bean.hasMethod("parentMethod", "a", "b"));
+        
+        bean.setParent(parent);
+
+        assertTrue(bean.hasMethod("parentMethod", "a", "b"));
+        assertThat(bean.invokeMethod("parentMethod", "a", "b"), equalTo((Object) "parent:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodsOnJavaObjectFromGroovy() {
+        Bean bean = new Bean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        convention.getPlugins().put("bean", new ConventionBean());
+        ExtensibleDynamicObjectTestHelper.assertCanCallMethods(bean);
+    }
+
+    @Test
+    public void canInvokeMethodsOnGroovyObjectFromGroovy() {
+        GroovyBean bean = new GroovyBean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        convention.getPlugins().put("bean", new ConventionBean());
+        ExtensibleDynamicObjectTestHelper.assertCanCallMethods(bean);
+    }
+
+    @Test
+    public void canInvokeMethodsOnJavaSubClassOfGroovyObjectFromGroovy() {
+        DynamicBean bean = new DynamicBean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        convention.getPlugins().put("bean", new ConventionBean());
+        ExtensibleDynamicObjectTestHelper.assertCanCallMethods(bean);
+    }
+
+    @Test
+    public void canInvokeClosurePropertyAsAMethod() {
+        Bean bean = new Bean();
+        bean.setProperty("someMethod", HelperUtil.toClosure("{ param -> param.toLowerCase() }"));
+        assertThat(bean.invokeMethod("someMethod", "Param"), equalTo((Object) "param"));
+    }
+    
+    @Test
+    public void invokeMethodFailsForUnknownMethod() {
+        Bean bean = new Bean();
+        try {
+            bean.invokeMethod("unknown", "a", 12);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMessage(), equalTo("Could not find method unknown() for arguments [a, 12] on <bean>."));
+        }
+    }
+
+    @Test
+    public void propagatesGetPropertyException() {
+        final RuntimeException failure = new RuntimeException();
+        Bean bean = new Bean() {
+            String getFailure() {
+                throw failure;
+            }
+        };
+
+        try {
+            bean.getProperty("failure");
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance((Exception) failure));
+        }
+    }
+
+    @Test
+    public void propagatesSetPropertyException() {
+        final RuntimeException failure = new RuntimeException();
+        Bean bean = new Bean() {
+            void setFailure(String value) {
+                throw failure;
+            }
+        };
+
+        try {
+            bean.setProperty("failure", "a");
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance((Exception) failure));
+        }
+    }
+
+    @Test
+    public void propagatesInvokeMethodException() {
+        final RuntimeException failure = new RuntimeException();
+        Bean bean = new Bean() {
+            void failure() {
+                throw failure;
+            }
+        };
+
+        try {
+            bean.invokeMethod("failure");
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance((Exception) failure));
+        }
+    }
+
+    @Test
+    public void additionalPropertiesAreInherited() {
+        Bean bean = new Bean();
+        bean.setProperty("additional", "value");
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("additional"));
+        assertThat(inherited.getProperty("additional"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void inheritedAdditionalPropertiesTrackChanges() {
+        Bean bean = new Bean();
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("additional"));
+
+        bean.setProperty("additional", "value");
+        assertTrue(inherited.hasProperty("additional"));
+        assertThat(inherited.getProperty("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void additionalObjectPropertiesAreInherited() {
+        Bean other = new Bean();
+        other.setProperty("other", "value");
+        Bean bean = new Bean();
+        bean.extensibleDynamicObject.addObject(other, ExtensibleDynamicObject.Location.BeforeConvention);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("other"));
+        assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("other"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void inheritedAdditionalObjectPropertiesTrackChanges() {
+        Bean other = new Bean();
+        other.setProperty("other", "value");
+        Bean bean = new Bean();
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("other"));
+
+        bean.extensibleDynamicObject.addObject(other, ExtensibleDynamicObject.Location.BeforeConvention);
+
+        assertTrue(inherited.hasProperty("other"));
+        assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void conventionPropertiesAreInherited() {
+        Bean bean = new Bean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("value");
+        convention.getPlugins().put("convention", conventionBean);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("conventionProperty"));
+        assertThat(inherited.getProperty("conventionProperty"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("conventionProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void inheritedConventionPropertiesTrackChanges() {
+        Bean bean = new Bean();
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("conventionProperty"));
+
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("value");
+        convention.getPlugins().put("convention", conventionBean);
+
+        assertTrue(inherited.hasProperty("conventionProperty"));
+        assertThat(inherited.getProperty("conventionProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void parentPropertiesAreInherited() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "value");
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("parentProperty"));
+        assertThat(inherited.getProperty("parentProperty"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("parentProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void otherPropertiesAreNotInherited() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasProperty("readWriteProperty"));
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("readWriteProperty"));
+        assertFalse(inherited.getProperties().containsKey("readWriteProperty"));
+    }
+
+    @Test
+    public void cannotSetInheritedProperties() {
+        Bean bean = new Bean();
+        bean.setProperty("additional", "value");
+
+        DynamicObject inherited = bean.getInheritable();
+        try {
+            inherited.setProperty("additional", "new value");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'additional' inherited from <bean>."));
+        }
+    }
+
+    @Test
+    public void conventionMethodsAreInherited() {
+        Bean bean = new Bean();
+        Convention convention = bean.extensibleDynamicObject.getConvention();
+        convention.getPlugins().put("convention", new ConventionBean());
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
+        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void additionalObjectMethodsAreInherited() {
+        Bean other = new Bean();
+        Convention convention = other.extensibleDynamicObject.getConvention();
+        convention.getPlugins().put("convention", new ConventionBean());
+
+        Bean bean = new Bean();
+        bean.extensibleDynamicObject.addObject(other, ExtensibleDynamicObject.Location.BeforeConvention);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
+        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void parentMethodsAreInherited() {
+        Bean parent = new Bean();
+        Convention convention = parent.extensibleDynamicObject.getConvention();
+        convention.getPlugins().put("convention", new ConventionBean());
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
+        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void otherMethodsAreNotInherited() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasMethod("javaMethod", "a", "b"));
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasMethod("javaMethod", "a", "b"));
+    }
+    
+    @Test
+    public void canGetObjectAsDynamicObject() {
+        Bean bean = new Bean();
+        assertThat(DynamicObjectUtil.asDynamicObject(bean), sameInstance((DynamicObject) bean));
+
+        AbstractProject project = (AbstractProject)ProjectBuilder.builder().build();
+        assertThat(DynamicObjectUtil.asDynamicObject(project), sameInstance(project.getAsDynamicObject()));
+
+        assertThat(DynamicObjectUtil.asDynamicObject(new Object()), instanceOf(DynamicObject.class));
+    }
+
+    @Test
+    public void canGetAndSetGroovyDynamicProperties() {
+        DynamicObject object = new BeanDynamicObject(new DynamicGroovyBean());
+        object.setProperty("foo", "bar");
+        assertThat((String)object.getProperty("foo"), equalTo("bar"));
+
+        try {
+            object.getProperty("additional");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("No such property: additional for class: org.gradle.api.internal.DynamicGroovyBean"));
+        }
+
+        try {
+            object.setProperty("additional", "foo");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("No such property: additional for class: org.gradle.api.internal.DynamicGroovyBean"));
+        }
+    }
+
+    @Test
+    public void canCallGroovyDynamicMethods() {
+        DynamicObject object = new BeanDynamicObject(new DynamicGroovyBean());
+        Integer doubled = (Integer)object.invokeMethod("bar", 1);
+        assertThat(doubled, equalTo(2));
+
+        try {
+            object.invokeMethod("xxx", 1, 2, 3);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMessage(), startsWith("No signature of method: org.gradle.api.internal.DynamicGroovyBean.xxx() is applicable for argument types: (java.lang.Integer, java.lang.Integer, java.lang.Integer) values: [1, 2, 3]\nPossible solutions:"));
+        }
+    }
+
+    public static class Bean implements DynamicObject {
+        private String readWriteProperty;
+        private String readOnlyProperty;
+        private String writeOnlyProperty;
+        private Integer differentTypesProperty;
+        final ExtensibleDynamicObject extensibleDynamicObject;
+        private Convention convention;
+
+        public Bean() {
+            extensibleDynamicObject = new ExtensibleDynamicObject(this, ThreadGlobalInstantiator.getOrCreate());
+        }
+
+        @Override
+        public String toString() {
+            return "<bean>";
+        }
+
+        public void setParent(DynamicObject parent) {
+            extensibleDynamicObject.setParent(parent);
+        }
+
+        public String getReadOnlyProperty() {
+            return readOnlyProperty;
+        }
+
+        public void doSetReadOnlyProperty(String readOnlyProperty) {
+            this.readOnlyProperty = readOnlyProperty;
+        }
+
+        public String doGetWriteOnlyProperty() {
+            return writeOnlyProperty;
+        }
+
+        public void setWriteOnlyProperty(String writeOnlyProperty) {
+            this.writeOnlyProperty = writeOnlyProperty;
+        }
+
+        public String getReadWriteProperty() {
+            return readWriteProperty;
+        }
+
+        public void setReadWriteProperty(String property) {
+            this.readWriteProperty = property;
+        }
+
+        public Integer getDifferentTypesProperty() {
+            return differentTypesProperty;
+        }
+
+        public void setDifferentTypesProperty(Object differentTypesProperty) {
+            this.differentTypesProperty = Integer.parseInt(differentTypesProperty.toString());
+        }
+
+        public String javaMethod(String a, String b) {
+            return String.format("java:%s.%s", a, b);
+        }
+        
+        public Object getProperty(String name) {
+            return extensibleDynamicObject.getProperty(name);
+        }
+
+        public boolean hasProperty(String name) {
+            return extensibleDynamicObject.hasProperty(name);
+        }
+
+        public void setProperty(String name, Object value) {
+            extensibleDynamicObject.setProperty(name, value);
+        }
+
+        public Map<String, Object> getProperties() {
+            return extensibleDynamicObject.getProperties();
+        }
+
+        public boolean hasMethod(String name, Object... arguments) {
+            return extensibleDynamicObject.hasMethod(name, arguments);
+        }
+
+        public Object invokeMethod(String name, Object... arguments) {
+            return extensibleDynamicObject.invokeMethod(name, arguments);
+        }
+
+        public Object methodMissing(String name, Object params) {
+            return extensibleDynamicObject.invokeMethod(name, (Object[]) params);
+        }
+
+        public Object propertyMissing(String name) {
+            return getProperty(name);
+        }
+
+        public DynamicObject getInheritable() {
+            return extensibleDynamicObject.getInheritable();
+        }
+    }
+
+    private static class DynamicBean extends GroovyBean {
+    }
+
+    private static class ConventionBean {
+        private String conventionProperty;
+
+        public String getConventionProperty() {
+            return conventionProperty;
+        }
+
+        public void setConventionProperty(String conventionProperty) {
+            this.conventionProperty = conventionProperty;
+        }
+
+        public String conventionMethod(String a, String b) {
+            return String.format("convention:%s.%s", a, b);
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTestHelper.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTestHelper.groovy
new file mode 100644
index 0000000..8094bfb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ExtensibleDynamicObjectTestHelper.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright 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
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
+
+public class ExtensibleDynamicObjectTestHelper {
+    public static void assertCanGetAllProperties (ExtensibleDynamicObjectTest.Bean bean) {
+        bean.readWriteProperty = 'readWrite'
+        bean.setProperty('additional', 'additional')
+        assertEquals(bean.getProperties().readWriteProperty, 'readWrite')
+        assertEquals(bean.getProperties().additional, 'additional')
+    }
+
+    public static void assertCanGetProperties (ExtensibleDynamicObjectTest.Bean bean) {
+        bean.readWriteProperty = 'value'
+        assertEquals(bean.readWriteProperty, 'value')
+
+        bean.doSetReadOnlyProperty('value')
+        assertEquals(bean.readOnlyProperty, 'value')
+
+        bean.extensibleDynamicObject.dynamicProperties.set('additional', 'value')
+        assertEquals(bean.additional, 'value')
+
+        bean.setProperty 'another', 'value'
+        assertEquals(bean.another, 'value')
+    }
+    
+    public static void assertCanGetAndSetProperties (ExtensibleDynamicObjectTest.Bean bean) {
+        bean.readWriteProperty = 'value'
+        assertEquals(bean.readWriteProperty, 'value')
+
+        bean.doSetReadOnlyProperty('value')
+        assertEquals(bean.readOnlyProperty, 'value')
+
+        bean.additional = 'value'
+        assertEquals(bean.additional, 'value')
+
+        bean.setProperty 'another', 'value'
+        assertEquals(bean.another, 'value')
+    }
+
+    public static void assertCanCallMethods (ExtensibleDynamicObjectTest.Bean bean) {
+        assertEquals(bean.javaMethod('a', 'b'), 'java:a.b')
+        assertTrue(bean.hasMethod('conventionMethod', 'a', 'b'))
+        assertEquals(bean.conventionMethod('a', 'b'), 'convention:a.b')
+    }
+}
+
+public class DynamicBean extends ExtensibleDynamicObjectTest.Bean {
+    def propertyMissing(String name) {
+        super.getProperty(name)
+    }
+
+//    def methodMissing(String name, params) {
+//        super.methodMissing(name, params)
+//    }
+
+    void setProperty(String name, Object value) {
+        super.setProperty(name, value)
+    }
+}
+
+public class GroovyBean extends DynamicBean {
+    String groovyProperty
+
+    def GroovyBean() {
+        Map values = [:]
+        ExpandoMetaClass metaClass = new ExpandoMetaClass(GroovyBean.class, false)
+        metaClass.getDynamicGroovyProperty << {-> values.dynamicGroovyProperty }
+        metaClass.setDynamicGroovyProperty << {value -> values.dynamicGroovyProperty = value}
+        metaClass.dynamicGroovyMethod << {a, b -> "dynamicGroovy:$a.$b".toString() }
+        metaClass.initialize()
+        setMetaClass(metaClass)
+    }
+
+    def groovyMethod(a, b) {
+        "groovy:$a.$b".toString()
+    }
+}
+
+class DynamicGroovyBean {
+    
+    private holder = null;
+    
+    Map called = [:]
+    
+    def propertyMissing(String name) {
+        if (name == "foo") {
+            return holder;
+        } else {
+            throw new MissingPropertyException(name, getClass())
+        }
+    }
+
+    def propertyMissing(String name, value) {
+        if (name == "foo") {
+            holder = value;
+        } else {
+            throw new MissingPropertyException(name, getClass())
+        }
+
+    }
+    
+    def methodMissing(String name, args) {
+        if (name == "bar") {
+            args[0] * 2   
+        } else {
+            throw new groovy.lang.MissingMethodException(name, getClass(), args)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
new file mode 100644
index 0000000..1a2a7d7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2011 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
+
+import org.gradle.api.Named
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.api.Namer
+import spock.lang.Specification
+
+class FactoryNamedDomainObjectContainerSpec extends Specification {
+    final NamedDomainObjectFactory<String> factory = Mock()
+    final Instantiator instantiator = Mock()
+    final namer = { it } as Namer
+    
+    def usesFactoryToCreateContainerElements() {
+        def container = new FactoryNamedDomainObjectContainer<String>(String.class, instantiator, namer, factory)
+
+        when:
+        def result = container.create('a')
+
+        then:
+        result == 'element a'
+        1 * factory.create('a') >> 'element a'
+        0 * _._
+    }
+
+    def usesPublicConstructorWhenNoFactorySupplied() {
+        def container = new FactoryNamedDomainObjectContainer<String>(String.class, instantiator, namer)
+
+        when:
+        def result = container.create('a')
+
+        then:
+        result == 'a'
+        0 * _._
+    }
+
+    def usesClosureToCreateContainerElements() {
+        def cl = { name -> "element $name" as String }
+        def container = new FactoryNamedDomainObjectContainer<String>(String.class, instantiator, namer, cl)
+
+        when:
+        def result = container.create('a')
+
+        then:
+        result == 'element a'
+        0 * _._
+    }
+    
+    // Tests for reflective instantiation
+    
+    def type
+    def extraArgs = []
+    def name = "test"
+    
+    protected getInstance() {
+        getInstance(name)
+    }
+
+    protected getInstance(name) {
+        new FactoryNamedDomainObjectContainer(type, instantiator, new ReflectiveNamedDomainObjectFactory(type, *extraArgs)).create(name)
+    }
+
+    static class JustName implements Named {
+        String name
+
+        JustName(String name) {
+            this.name = name
+        }
+    }
+
+    def "can create instance with just name constructor"() {
+        given:
+        type = JustName
+
+        expect:
+        instance.name == name
+    }
+
+    def "specifying extra args that the type can't handle produces exception"() {
+        given:
+        type = JustName
+        extraArgs = [1, 2]
+
+        when:
+        getInstance()
+
+        then:
+        thrown IllegalArgumentException
+    }
+
+    static class NoConstructor {}
+
+    def "type with no name constructor produces exception"() {
+        given:
+        type = NoConstructor
+
+        when:
+        getInstance()
+
+        then:
+        thrown IllegalArgumentException
+    }
+
+    static class ExtraArgs implements Named {
+        String name
+        int arg1
+        int arg2
+
+        ExtraArgs(name, arg1, arg2) {
+            this.name = name
+            this.arg1 = arg1
+            this.arg2 = arg2
+        }
+    }
+
+    def "can supply extra args"() {
+        given:
+        type = ExtraArgs
+        extraArgs = [1, 2]
+
+        when:
+        def instance = getInstance()
+
+        then:
+        instance.name == name
+        instance.arg1 == 1
+        instance.arg2 == 2
+    }
+    
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/FilteredActionSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/FilteredActionSpec.groovy
new file mode 100644
index 0000000..20a6b6c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/FilteredActionSpec.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.*
+
+import org.gradle.api.Action;
+import org.gradle.api.specs.Spec;
+
+class FilteredActionSpec extends Specification {
+
+    protected Spec s(Closure spec) {
+        spec as Spec
+    }
+
+    protected a(Closure action) {
+        action as Action
+    }
+
+    protected fa(Spec spec, Action action) {
+        new FilteredAction(spec, action)
+    }
+    
+    def "filtered action fires for matching"() {
+        given:
+        def called = false
+        def spec = s { true }
+        def action = fa(spec, a { called = true })
+        
+        expect:
+        spec.isSatisfiedBy "object"
+        
+        when:
+        action.execute "object"
+        
+        then:
+        called == true
+    }
+
+    def "filtered action doesnt fire for not matching"() {
+        given:
+        def called = false
+        def spec = s { false }
+        def action = fa(spec, a { called = true })
+        
+        expect:
+        !spec.isSatisfiedBy("object")
+        
+        when:
+        action.execute "object"
+        
+        then:
+        called == false
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGeneratorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGeneratorTest.java
deleted file mode 100644
index 778bbeb..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGeneratorTest.java
+++ /dev/null
@@ -1,23 +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;
-
-public class GroovySourceGenerationBackedClassGeneratorTest extends AbstractClassGeneratorTest {
-    @Override
-    protected ClassGenerator createGenerator() {
-        return new GroovySourceGenerationBackedClassGenerator();
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
new file mode 100644
index 0000000..b11f726
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/LocationAwareExceptionTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2012 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
+
+import spock.lang.Specification
+import org.gradle.util.TreeVisitor
+
+class LocationAwareExceptionTest extends Specification {
+    def "visit reportable causes does not visit direct cause"() {
+        TreeVisitor visitor = Mock()
+        def cause = new RuntimeException()
+        def e = new LocationAwareException(cause, cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+        0 * visitor._
+        
+        and:
+        e.reportableCauses == []
+    }
+
+    def "visit reportable causes visits indirect cause"() {
+        TreeVisitor visitor = Mock()
+        def childCause = new RuntimeException()
+        def cause = new RuntimeException(childCause)
+        def e = new LocationAwareException(cause, cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        1 * visitor.startChildren()
+
+        and:
+        1 * visitor.node(childCause)
+
+        and:
+        1 * visitor.endChildren()
+        0 * visitor._
+        
+        and:
+        e.reportableCauses == [childCause]
+    }
+
+    def "visit reportable causes visits causes of contextual exception"() {
+        TreeVisitor visitor = Mock()
+        def childCause = new RuntimeException()
+        def cause = new TestContextualException(childCause)
+        def e = new LocationAwareException(cause, cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        1 * visitor.startChildren()
+
+        and:
+        1 * visitor.node(childCause)
+
+        and:
+        1 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause]
+    }
+
+    def "visit reportable causes visits causes of multi-cause exception"() {
+        TreeVisitor visitor = Mock()
+        def childCause1 = new RuntimeException()
+        def childCause2 = new RuntimeException()
+        def cause = new AbstractMultiCauseException("broken", childCause1, childCause2)
+        def e = new LocationAwareException(cause, cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        1 * visitor.startChildren()
+
+        and:
+        1 * visitor.node(childCause1)
+
+        and:
+        1 * visitor.node(childCause2)
+
+        and:
+        1 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause1, childCause2]
+    }
+
+    def "visit reportable causes visits causes recursively"() {
+        TreeVisitor visitor = Mock()
+        def ignored = new RuntimeException()
+        def childCause1 = new RuntimeException(ignored)
+        def childCause2 = new RuntimeException()
+        def childCause3 = new TestContextualException(childCause2)
+        def childCause4 = new TestContextualException(childCause3)
+        def cause = new AbstractMultiCauseException("broken", childCause1, childCause4)
+        def e = new LocationAwareException(cause, cause, null, 100)
+
+        when:
+        e.visitReportableCauses(visitor)
+
+        then:
+        1 * visitor.node(e)
+
+        and:
+        3 * visitor.startChildren()
+        1 * visitor.node(childCause1)
+        1 * visitor.node(childCause4)
+        1 * visitor.node(childCause3)
+        1 * visitor.node(childCause2)
+        3 * visitor.endChildren()
+        0 * visitor._
+
+        and:
+        e.reportableCauses == [childCause1, childCause4, childCause3, childCause2]
+    }
+
+    @Contextual
+    class TestContextualException extends RuntimeException {
+        TestContextualException(Throwable throwable) {
+            super(throwable)
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/MapBackedDynamicObjectTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/MapBackedDynamicObjectTest.java
deleted file mode 100644
index 02ecc01..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/MapBackedDynamicObjectTest.java
+++ /dev/null
@@ -1,51 +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;
-
-import org.junit.Test;
-
-import java.util.Map;
-
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-public class MapBackedDynamicObjectTest {
-    private final MapBackedDynamicObject object = new MapBackedDynamicObject(null);
-
-    @Test
-    public void hasNoPropertiesByDefault() {
-        assertTrue(object.getProperties().isEmpty());
-        assertFalse(object.hasProperty("someProp"));
-    }
-
-    @Test
-    public void canAddAProperty() {
-        object.setProperty("someProp", "value");
-        assertThat(object.getProperties(), equalTo((Map) toMap("someProp", (Object) "value")));
-        assertTrue(object.hasProperty("someProp"));
-        assertThat(object.getProperty("someProp"), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void canChangeAPropertyValue() {
-        object.setProperty("someProp", "value");
-        assertThat(object.getProperty("someProp"), equalTo((Object) "value"));
-
-        object.setProperty("someProp", "new value");
-        assertThat(object.getProperty("someProp"), equalTo((Object) "new value"));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
new file mode 100644
index 0000000..2a0a5e3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.*
+
+class NestedConfigureAutoCreateNamedDomainObjectContainerSpec extends Specification {
+
+    def instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+
+    static class Container extends FactoryNamedDomainObjectContainer {
+        String parentName
+        String name
+        Container(String parentName, String name, Closure factory) {
+            super(Object, new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator()), new DynamicPropertyNamer(), factory)
+            this.parentName = parentName
+            this.name = name
+        }
+    }
+
+    def "can nest auto creation configure closures"() {
+        given:
+        def parent = instantiator.newInstance(Container, "top", "parent", { name1 ->
+            instantiator.newInstance(Container, "parent", name1, { name2 ->
+                instantiator.newInstance(Container, name1, name2, { name3 ->
+                    [parentName: name2, name: name3]
+                })
+            })
+        })
+
+        when:
+        parent.configure {
+            c1 {
+                c1c1 {
+                    m1 {
+                        prop = "c1c1m1"
+                    }
+                    m2 {
+                        prop = "c1c1m2"
+                    }
+                }
+                c1c2 {
+                    m1 {
+                        prop = "c1c2m1"
+                    }
+                }
+            }
+            c2 {
+                c2c1 {
+                    m1 {
+                        prop = "c2c1m1"
+                    }
+                }
+            }
+        }
+
+        then:
+        parent.c1.c1c1.m1.prop == "c1c1m1"
+        parent.c1.c1c1.m2.prop == "c1c1m2"
+        parent.c1.c1c2.m1.prop == "c1c2m1"
+        parent.c2.c2c1.m1.prop == "c2c1m1"
+    }
+
+    def "configure like method for object that doesn't support it produces error"() {
+        given:
+        def parent = instantiator.newInstance(Container, "top", "parent", { name1 ->
+            instantiator.newInstance(Container, "parent", name1, { name2 ->
+                [parent: name1, name: name2]
+            })
+        })
+
+        when:
+        parent.configure {
+            c1 {
+                m1 {
+                    prop = "c1c1m1"
+                    
+                    // Should throw mme because map doesn't have this method
+                    somethingThatDoesntExist {
+
+                    }
+                }
+            }
+        }
+
+
+        then:
+        def e = thrown(MissingMethodException)
+        e.method == "somethingThatDoesntExist"
+        parent.c1.m1.prop == "c1c1m1"
+        
+        // make sure the somethingThatDoesntExist() call didn't resolve against any of the parent containers, creating an entry
+        parent.size() == 1
+        parent.c1.size() == 1
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy
new file mode 100644
index 0000000..2c017c8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/PropertiesTransformerTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 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
+
+import org.gradle.api.Action
+import spock.lang.Specification
+
+class PropertiesTransformerTest extends Specification {
+    final PropertiesTransformer transformer = new PropertiesTransformer()
+    
+    def 'returns original when no action specified'() {
+        given:
+        Map map = [test:'value']
+        Properties original = props(map)
+        Properties test = props(map)
+        when:
+        def result = transformer.transform(original)
+        then:
+        result == test
+    }
+    
+    def 'action can access properties'() {
+        given:
+        Action<Properties> action = Mock()
+        transformer.addAction(action)
+        Properties original = props(removed:'value', changed:'old')
+        when:
+        def result = transformer.transform(original)
+        then:
+        action.execute(_) >> { Properties props ->
+            props.remove('removed')
+            props.changed = 'new'
+            props.added = 'value'
+        }
+        props(changed:'new', added:'value') == result
+    }
+    
+    def 'can use closure as action'() {
+        given:
+        transformer.addAction { Properties props ->
+            props.added = 'value'
+        }
+        when:
+        def result = transformer.transform(new Properties())
+        then:
+        props(added:'value') == result
+    }
+    
+    def 'can chain actions'() {
+        given:
+        transformer.addAction { Properties props ->
+            props.remove('removed')
+        }
+        transformer.addAction { Properties props ->
+            props.changed = 'new'
+        }
+        transformer.addAction { Properties props ->
+            props.added = 'value'
+        }
+        Properties original = props(removed:'value', changed:'old')
+        when:
+        def result = transformer.transform(original)
+        then:
+        props(changed:'new', added:'value') == result
+    }
+    
+    def 'can transform to an OutputStream'() {
+        given:
+        transformer.addAction { Properties props ->
+            props.added = 'value'
+        }
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream()
+
+        when:
+        transformer.transform(new Properties(), outstr)
+
+        then:
+        def result = new Properties()
+        result.load(new ByteArrayInputStream(outstr.toByteArray()))
+        result == [added: 'value']
+    }
+    
+    Properties props(Map map) {
+        Properties props = new Properties()
+        props.putAll(map)
+        return props
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
old mode 100644
new mode 100755
index 02796e7..1f64c17
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
@@ -1,29 +1,30 @@
-/*
- * 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;
-
-public class TestContainer extends AutoCreateDomainObjectContainer<TestObject> {
-
-    public TestContainer(ClassGenerator classGenerator) {
-        super(TestObject.class, classGenerator);
-    }
-
-    protected TestObject create(String name) {
-        TestObject testObject = new TestObject();
-        testObject.add(name);
-        return testObject;
-    }
+/*
+ * 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;
+
+public class TestContainer extends AbstractNamedDomainObjectContainer<TestObject> {
+
+    public TestContainer(Instantiator instantiator) {
+        super(TestObject.class, instantiator, new DynamicPropertyNamer());
+    }
+
+    protected TestObject doCreate(String name) {
+        TestObject testObject = new TestObject();
+        testObject.setName(name);
+        testObject.add(name);
+        return testObject;
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy
index 88c0d0c..ecb82d5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/XmlTransformerTest.groovy
@@ -15,10 +15,10 @@
  */
 package org.gradle.api.internal
 
-import spock.lang.Specification
-import org.gradle.api.artifacts.maven.XmlProvider
 import org.gradle.api.Action
+import org.gradle.api.XmlProvider
 import org.gradle.util.TextUtil
+import spock.lang.Specification
 
 class XmlTransformerTest extends Specification {
     final XmlTransformer transformer = new XmlTransformer()
@@ -178,6 +178,71 @@ class XmlTransformerTest extends Specification {
         looksLike "<root>\n\t<child>\n\t\t<grandchild/>\n\t</child>\n</root>\n", result
     }
 
+    def "can add DOCTYPE along with nodes"() {
+        transformer.addAction { it.asNode().appendNode('someChild') }
+        transformer.addAction {
+            def s = it.asString()
+            s.insert(s.indexOf("?>") + 2, '\n<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN" "http://java.sun.com/dtd/application_1_3.dtd">')
+        }
+
+        when:
+        def result = transformer.transform("<root></root>")
+
+        then:
+        looksLike "<!DOCTYPE application PUBLIC \"-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN\" \"http://java.sun.com/dtd/application_1_3.dtd\">\n<root>\n  <someChild/>\n</root>\n", result
+    }
+
+    def "can specify DOCTYPE when using DomNode"() {
+        StringWriter writer = new StringWriter()
+        def node = new DomNode('root')
+        node.publicId = 'public-id'
+        node.systemId = 'system-id'
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        looksLike '''<!DOCTYPE root PUBLIC "public-id" "system-id">
+<root/>
+''', writer.toString()
+    }
+
+    def "DOCTYPE is preserved when transformed as a Node"() {
+        StringWriter writer = new StringWriter()
+        def node = new DomNode('root')
+        node.publicId = 'public-id'
+        node.systemId = 'system-id'
+        transformer.addAction { it.asNode().appendNode('someChild') }
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        looksLike '''<!DOCTYPE root PUBLIC "public-id" "system-id">
+<root>
+  <someChild/>
+</root>
+''', writer.toString()
+    }
+
+    def "DOCTYPE is preserved when transformed as a DOM element"() {
+        StringWriter writer = new StringWriter()
+        def node = new DomNode('root')
+        node.publicId = 'public-id'
+        node.systemId = getClass().getResource("xml-transformer-test.dtd")
+        transformer.addAction { it.asElement().appendChild(it.asElement().ownerDocument.createElement('someChild')) }
+
+        when:
+        transformer.transform(node, writer)
+
+        then:
+        looksLike """<!DOCTYPE root PUBLIC "public-id" "${node.getSystemId()}">
+<root>
+  <someChild/>
+</root>
+""", writer.toString()
+    }
+
     def "indentation correct when writing out DOM element (only) if indenting with spaces"() {
         transformer.indentation = expected
         transformer.addAction { XmlProvider provider ->
@@ -198,7 +263,11 @@ class XmlTransformerTest extends Specification {
     }
 
     private void looksLike(String expected, String actual) {
-        assert actual == TextUtil.toPlatformLineSeparators(addXmlDeclaration(expected))
+        assert removeTrailingWhitespace(actual) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators(addXmlDeclaration(expected)))
+    }
+
+    private String removeTrailingWhitespace(String value) {
+        return value.replaceFirst('(?s)\\s+$', "")
     }
 
     private String addXmlDeclaration(String value) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy
index 186972c..47b996a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy
@@ -24,7 +24,7 @@ class CachingDependencyResolveContextTest extends Specification {
     private final CachingDependencyResolveContext context = new CachingDependencyResolveContext(true)
 
     def resolvesAFileCollection() {
-        DependencyInternal dependency = Mock()
+        ResolvableDependency dependency = Mock()
         FileCollection fileCollection = Mock()
 
         when:
@@ -38,8 +38,8 @@ class CachingDependencyResolveContextTest extends Specification {
     }
 
     def resolvesADependencyInternal() {
-        DependencyInternal dependency = Mock()
-        DependencyInternal otherDependency = Mock()
+        ResolvableDependency dependency = Mock()
+        ResolvableDependency otherDependency = Mock()
         FileCollection fileCollection = Mock()
 
         when:
@@ -54,7 +54,7 @@ class CachingDependencyResolveContextTest extends Specification {
     }
 
     def failsToResolveAnyOtherType() {
-        DependencyInternal dependency = Mock()
+        ResolvableDependency dependency = Mock()
 
         when:
         context.add(dependency)
@@ -67,8 +67,8 @@ class CachingDependencyResolveContextTest extends Specification {
     }
 
     def handlesCyclesBetweenDependencies() {
-        DependencyInternal dependency = Mock()
-        DependencyInternal otherDependency = Mock()
+        ResolvableDependency dependency = Mock()
+        ResolvableDependency otherDependency = Mock()
         FileCollection fileCollection = Mock()
 
         when:
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
new file mode 100644
index 0000000..58860b1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
@@ -0,0 +1,246 @@
+/*
+ * 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.api.internal.artifacts
+
+import org.apache.ivy.plugins.resolver.DependencyResolver
+import org.apache.ivy.plugins.resolver.FileSystemResolver
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.ArtifactRepositoryContainer
+import org.gradle.api.artifacts.UnknownRepositoryException
+import org.gradle.api.artifacts.repositories.ArtifactRepository
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock)
+class DefaultArtifactRepositoryContainerTest {
+    static final String TEST_REPO_NAME = 'reponame'
+
+    DefaultArtifactRepositoryContainer resolverContainer
+
+    def expectedUserDescription
+    def expectedUserDescription2
+    def expectedUserDescription3
+    String expectedName
+    String expectedName2
+    String expectedName3
+
+    FileSystemResolver expectedResolver
+    FileSystemResolver expectedResolver2
+    FileSystemResolver expectedResolver3
+
+    ResolverFactory resolverFactoryMock;
+
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    ArtifactRepositoryContainer createResolverContainer() {
+        return new DefaultArtifactRepositoryContainer(resolverFactoryMock, context.mock(Instantiator.class))
+    }
+
+    @Before public void setUp() {
+        expectedUserDescription = 'somedescription'
+        expectedUserDescription2 = 'somedescription2'
+        expectedUserDescription3 = 'somedescription3'
+        expectedName = 'somename'
+        expectedName2 = 'somename2'
+        expectedName3 = 'somename3'
+        expectedResolver = new FileSystemResolver()
+        expectedResolver2 = new FileSystemResolver()
+        expectedResolver3 = new FileSystemResolver()
+        expectedResolver.name = expectedName
+        expectedResolver2.name = expectedName2
+        expectedResolver3.name = expectedName3
+        resolverFactoryMock = context.mock(ResolverFactory)
+        ArtifactRepository repo1 = context.mock(TestArtifactRepository)
+        ArtifactRepository repo2 = context.mock(TestArtifactRepository)
+        ArtifactRepository repo3 = context.mock(TestArtifactRepository)
+        context.checking {
+            allowing(resolverFactoryMock).createRepository(expectedUserDescription); will(returnValue(repo1))
+            allowing(resolverFactoryMock).createRepository(expectedUserDescription2); will(returnValue(repo2))
+            allowing(resolverFactoryMock).createRepository(expectedUserDescription3); will(returnValue(repo3))
+            allowing(repo1).createResolver(); will(returnValue(expectedResolver))
+            allowing(repo2).createResolver(); will(returnValue(expectedResolver2))
+            allowing(repo3).createResolver(); will(returnValue(expectedResolver3))
+        }
+        resolverContainer = createResolverContainer()
+    }
+
+    @Test public void testAddResolver() {
+        assert resolverContainer.addLast(expectedUserDescription).is(expectedResolver)
+        assert resolverContainer.findByName(expectedName) != null
+        resolverContainer.addLast(expectedUserDescription2)
+        assertEquals([expectedResolver, expectedResolver2], resolverContainer.resolvers)
+    }
+
+    @Test public void testCannotAddResolverWithDuplicateName() {
+        [expectedResolver, expectedResolver2]*.name = 'resolver'
+        resolverContainer.addLast(expectedUserDescription)
+
+        try {
+            resolverContainer.addLast(expectedUserDescription2)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Cannot add a repository with name 'resolver' as a repository with that name already exists."))
+        }
+    }
+
+    @Test public void testAddResolverWithClosure() {
+        def expectedConfigureValue = 'testvalue'
+        Closure configureClosure = {transactional = expectedConfigureValue}
+        assertThat(resolverContainer.addLast(expectedUserDescription, configureClosure), sameInstance(expectedResolver))
+        assertThat(resolverContainer.findByName(expectedName), notNullValue())
+        assert expectedResolver.transactional == expectedConfigureValue
+    }
+
+    @Test public void testAddBefore() {
+        resolverContainer.addLast(expectedUserDescription)
+        assert resolverContainer.addBefore(expectedUserDescription2, expectedName).is(expectedResolver2)
+        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
+    }
+
+    @Test public void testAddAfter() {
+        resolverContainer.addLast(expectedUserDescription)
+        assert resolverContainer.addAfter(expectedUserDescription2, expectedName).is(expectedResolver2)
+        resolverContainer.addAfter(expectedUserDescription3, expectedName)
+        assertEquals([expectedResolver, expectedResolver3, expectedResolver2], resolverContainer.resolvers)
+    }
+
+    @Test(expected = UnknownRepositoryException) public void testAddBeforeWithUnknownResolver() {
+        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
+    }
+
+    @Test(expected = UnknownRepositoryException) public void testAddAfterWithUnknownResolver() {
+        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
+    }
+
+    @Test public void testAddFirst() {
+        ArtifactRepository repository1 = context.mock(ArtifactRepository)
+        ArtifactRepository repository2 = context.mock(ArtifactRepository)
+
+        context.checking {
+            allowing(repository1).getName(); will(returnValue("1"))
+            allowing(repository2).getName(); will(returnValue("2"))
+        }
+
+        resolverContainer.addFirst(repository1)
+        resolverContainer.addFirst(repository2)
+
+        assert resolverContainer == [repository2, repository1]
+        assert resolverContainer.collect { it } == [repository2, repository1]
+        assert resolverContainer.matching { true } == [repository2, repository1]
+        assert resolverContainer.matching { true }.collect { it } == [repository2, repository1]
+    }
+
+    @Test public void testAddLast() {
+        ArtifactRepository repository1 = context.mock(ArtifactRepository)
+        ArtifactRepository repository2 = context.mock(ArtifactRepository)
+
+        context.checking {
+            allowing(repository1).getName(); will(returnValue('repo1'))
+            allowing(repository2).getName(); will(returnValue('repo2'))
+        }
+        
+        resolverContainer.addLast(repository1)
+        resolverContainer.addLast(repository2)
+
+        assert resolverContainer == [repository1, repository2]
+    }
+    
+    @Test public void testAddFirstUsingUserDescription() {
+        assert resolverContainer.addFirst(expectedUserDescription).is(expectedResolver)
+        resolverContainer.addFirst(expectedUserDescription2)
+        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
+    }
+
+    @Test public void testAddLastUsingUserDescription() {
+        assert resolverContainer.addLast(expectedUserDescription).is(expectedResolver)
+        resolverContainer.addLast(expectedUserDescription2)
+        assertEquals([expectedResolver, expectedResolver2], resolverContainer.resolvers)
+    }
+
+    @Test
+    public void testAddWithUnnamedResolver() {
+        expectedResolver.name = null
+        assert resolverContainer.addLast(expectedUserDescription).is(expectedResolver)
+        assert expectedResolver.name == 'repository'
+    }
+
+    @Test
+    public void testGetThrowsExceptionForUnknownResolver() {
+        try {
+            resolverContainer.getByName('unknown')
+            fail()
+        } catch (UnknownRepositoryException e) {
+            assertThat(e.message, equalTo("Repository with name 'unknown' not found."))
+        }
+    }
+
+    @Test
+    public void notificationsAreFiredWhenRepositoryIsAdded() {
+        Action<DependencyResolver> action = context.mock(Action.class)
+        ArtifactRepository repository = context.mock(ArtifactRepository)
+
+        context.checking {
+            ignoring(repository)
+            one(action).execute(repository)
+        }
+
+        resolverContainer.all(action)
+        resolverContainer.add(repository)
+    }
+
+    @Test
+    public void notificationsAreFiredWhenRepositoryIsAddedToTheHead() {
+        Action<DependencyResolver> action = context.mock(Action.class)
+        ArtifactRepository repository = context.mock(ArtifactRepository)
+
+        context.checking {
+            ignoring(repository)
+            one(action).execute(repository)
+        }
+
+        resolverContainer.all(action)
+        resolverContainer.addFirst(repository)
+    }
+
+    @Test
+    public void notificationsAreFiredWhenRepositoryIsAddedToTheTail() {
+        Action<DependencyResolver> action = context.mock(Action.class)
+        ArtifactRepository repository = context.mock(ArtifactRepository)
+
+        context.checking {
+            ignoring(repository)
+            one(action).execute(repository)
+        }
+
+        resolverContainer.all(action)
+        resolverContainer.addLast(repository)
+    }
+}
+
+interface TestArtifactRepository extends ArtifactRepository, ArtifactRepositoryInternal {
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactoryTest.java
deleted file mode 100644
index 5bcc435..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactoryTest.java
+++ /dev/null
@@ -1,83 +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.api.internal.artifacts;
-
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.api.internal.artifacts.ivyservice.*;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.sameInstance;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultConfigurationContainerFactoryTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void testCreate() {
-        ResolverProvider resolverProviderDummy = context.mock(ResolverProvider.class);
-        final DependencyMetaDataProvider dependencyMetaDataProviderStub = context.mock(DependencyMetaDataProvider.class);
-
-        HashMap clientModuleRegistry = new HashMap();
-        SettingsConverter settingsConverter = context.mock(SettingsConverter.class);
-        ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "resolve");
-        ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publish");
-        ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "file");
-        IvyFactory ivyFactory = context.mock(IvyFactory.class);
-        IvyDependencyResolver ivyDependencyResolver = context.mock(IvyDependencyResolver.class);
-        IvyDependencyPublisher ivyDependencyPublisher = context.mock(IvyDependencyPublisher.class);
-        ClassGenerator classGenerator = new AsmBackedClassGenerator();
-        DefaultConfigurationContainer configurationContainer = (DefaultConfigurationContainer)
-                new DefaultConfigurationContainerFactory(clientModuleRegistry, settingsConverter,
-                        resolveModuleDescriptorConverter, publishModuleDescriptorConverter,
-                        fileModuleDescriptorConverter, ivyFactory,
-                        ivyDependencyResolver, ivyDependencyPublisher, classGenerator).createConfigurationContainer(resolverProviderDummy,
-                        dependencyMetaDataProviderStub, context.mock(DomainObjectContext.class));
-
-        assertThat(configurationContainer.getIvyService(), instanceOf(ErrorHandlingIvyService.class));
-        ErrorHandlingIvyService errorHandlingService = (ErrorHandlingIvyService) configurationContainer.getIvyService();
-
-        assertThat(errorHandlingService.getIvyService(), instanceOf(ShortcircuitEmptyConfigsIvyService.class));
-        ShortcircuitEmptyConfigsIvyService service = (ShortcircuitEmptyConfigsIvyService) errorHandlingService.getIvyService();
-
-        assertThat(service.getIvyService(), instanceOf(DefaultIvyService.class));
-        DefaultIvyService defaultIvyService = (DefaultIvyService) service.getIvyService();
-        assertThat(defaultIvyService.getMetaDataProvider(), sameInstance(dependencyMetaDataProviderStub));
-        assertThat(defaultIvyService.getResolverProvider(), sameInstance(resolverProviderDummy));
-        assertThat((HashMap) defaultIvyService.getClientModuleRegistry(), sameInstance(clientModuleRegistry));
-        assertThat(defaultIvyService.getSettingsConverter(), sameInstance(settingsConverter));
-        assertThat(defaultIvyService.getResolveModuleDescriptorConverter(), sameInstance(resolveModuleDescriptorConverter));
-        assertThat(defaultIvyService.getPublishModuleDescriptorConverter(), sameInstance(publishModuleDescriptorConverter));
-        assertThat(defaultIvyService.getFileModuleDescriptorConverter(), sameInstance(fileModuleDescriptorConverter));
-        assertThat(defaultIvyService.getIvyFactory(), sameInstance(ivyFactory));
-        assertThat(defaultIvyService.getDependencyResolver(), sameInstance(ivyDependencyResolver));
-        assertThat(defaultIvyService.getDependencyPublisher(), sameInstance(ivyDependencyPublisher));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencySetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencySetTest.groovy
new file mode 100644
index 0000000..d569319
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencySetTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+import org.gradle.api.artifacts.SelfResolvingDependency
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+
+class DefaultDependencySetTest extends Specification {
+    final DefaultDomainObjectSet<Dependency> store = new DefaultDomainObjectSet<Dependency>(Dependency)
+    final DefaultDependencySet set = new DefaultDependencySet('dependencies', store)
+
+    def "set is built by the union of tasks that build the self-resolving dependencies in the set"() {
+        SelfResolvingDependency dep1 = Mock()
+        SelfResolvingDependency dep2 = Mock()
+        Task task1 = Mock()
+        Task task2 = Mock()
+        Task task3 = Mock()
+        Dependency ignored = Mock()
+
+        given:
+        store.add(dep1)
+        store.add(dep2)
+        store.add(ignored)
+        builtBy(dep1, task1, task2)
+        builtBy(dep2, task1, task3)
+
+        expect:
+        set.buildDependencies.getDependencies(null) == [task1, task2, task3] as Set
+    }
+
+    def builtBy(SelfResolvingDependency dependency, Task... tasks) {
+        TaskDependency taskDependency = Mock()
+        _ * dependency.buildDependencies >> taskDependency
+        _ * taskDependency.getDependencies(_) >> (tasks as Set)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java
new file mode 100644
index 0000000..48d2981
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainerTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.util.WrapUtil;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleContainerTest {
+    @Test
+    public void testInit() {
+        assertThat(new DefaultExcludeRuleContainer().getRules().size(), equalTo(0));
+    }
+
+    @Test
+    public void testInitWithRules() {
+        Set<ExcludeRule> sourceExcludeRules = new HashSet<ExcludeRule>();
+        sourceExcludeRules.add(new DefaultExcludeRule("aGroup", null));
+        DefaultExcludeRuleContainer defaultExcludeRuleContainer = new DefaultExcludeRuleContainer(sourceExcludeRules);
+        assertThat(defaultExcludeRuleContainer.getRules(), equalTo(sourceExcludeRules));
+        assertThat(defaultExcludeRuleContainer.getRules(), not(sameInstance(sourceExcludeRules)));
+    }
+
+    @Test
+    public void testAdd() {
+        DefaultExcludeRuleContainer excludeRuleContainer = new DefaultExcludeRuleContainer();
+        Map<String, String> excludeRuleArgs1 = WrapUtil.toMap("group", "value1");
+        Map<String, String> excludeRuleArgs2 = WrapUtil.toMap("module", "value2");
+        excludeRuleContainer.add(excludeRuleArgs1);
+        excludeRuleContainer.add(excludeRuleArgs2);
+        assertThat(excludeRuleContainer.getRules().size(), equalTo(2));
+        assertExcludeRuleContainerHasCorrectExcludeRules(excludeRuleContainer.getRules(), excludeRuleArgs1, excludeRuleArgs2);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void testInvalidExcludeDefinitionThrowsInvalidUserDataException() {
+        DefaultExcludeRuleContainer excludeRuleContainer = new DefaultExcludeRuleContainer();
+        Map<String, String> excludeRuleArgs1 = WrapUtil.toMap("group", "value1");
+        Map<String, String> excludeRuleArgs2 = WrapUtil.toMap("invalidkey2", "value2");
+        excludeRuleContainer.add(excludeRuleArgs1);
+        excludeRuleContainer.add(excludeRuleArgs2);
+    }
+
+    private void assertExcludeRuleContainerHasCorrectExcludeRules(Set<ExcludeRule> excludeRules, Map... excludeRuleArgs) {
+        List<Map> foundRules = new ArrayList<Map>();
+        for (ExcludeRule excludeRule : excludeRules) {
+            for (Map excludeRuleArg : excludeRuleArgs) {
+                if (matchingExcludeRule(excludeRule, excludeRuleArg)) {
+                    foundRules.add(excludeRuleArg);
+                    continue;
+                }
+            }
+        }
+        assertThat(Arrays.asList(excludeRuleArgs), equalTo(foundRules));
+    }
+
+    private boolean matchingExcludeRule(ExcludeRule excludeRule, Map excludeRuleArg) {
+        final DefaultExcludeRule expectedExcludeRule = new DefaultExcludeRule((String) excludeRuleArg.get(ExcludeRule.GROUP_KEY), (String) excludeRuleArg.get(ExcludeRule.MODULE_KEY));
+        return excludeRule.equals(expectedExcludeRule);
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierTest.groovy
new file mode 100755
index 0000000..00a327c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionIdentifierTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+import static org.gradle.util.Matchers.strictlyEqual
+
+class DefaultModuleVersionIdentifierTest extends Specification {
+    def "has useful toString()"() {
+        def module = new DefaultModuleVersionIdentifier("group", "module", "version")
+
+        expect:
+        module.toString().contains("group: group, module: module, version: version")
+    }
+
+    def "ids are equal when group, module and version are equal"() {
+        def module = new DefaultModuleVersionIdentifier("group", "module", "version")
+        def same = new DefaultModuleVersionIdentifier("group", "module", "version")
+        def differentGroup = new DefaultModuleVersionIdentifier("other", "module", "version")
+        def differentModule = new DefaultModuleVersionIdentifier("group", "other", "version")
+        def differentVersion = new DefaultModuleVersionIdentifier("group", "module", "other")
+
+        expect:
+        module strictlyEqual(same)
+        module != differentGroup
+        module != differentModule
+        module != differentVersion
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultPublishArtifactSetTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultPublishArtifactSetTest.groovy
new file mode 100644
index 0000000..763a6cb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultPublishArtifactSetTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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
+
+import spock.lang.Specification
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+
+class DefaultPublishArtifactSetTest extends Specification {
+    final store = new DefaultDomainObjectSet<PublishArtifact>(PublishArtifact)
+    final set = new DefaultPublishArtifactSet('artifacts', store)
+
+    def "set is built by the union of the tasks that build the publish artifacts"() {
+        PublishArtifact artifact1 = Mock()
+        PublishArtifact artifact2 = Mock()
+        Task task1 = Mock()
+        Task task2 = Mock()
+        Task task3 = Mock()
+
+        given:
+        store.add(artifact1)
+        store.add(artifact2)
+        builtBy(artifact1, task1, task2)
+        builtBy(artifact2, task1, task3)
+
+        expect:
+        set.buildDependencies.getDependencies(null) == [task1, task2, task3] as Set
+    }
+
+    def "file collection contains the files from each publish artifact"() {
+        PublishArtifact artifact1 = Mock()
+        PublishArtifact artifact2 = Mock()
+        File file1 = Mock()
+        File file2 = Mock()
+
+        given:
+        store.add(artifact1)
+        store.add(artifact2)
+        _ * artifact1.file >> file1
+        _ * artifact2.file >> file2
+
+        expect:
+        set.files.files == [file1, file2] as Set
+    }
+
+    def "files are built by the union of the tasks that build the publish artifacts"() {
+        PublishArtifact artifact1 = Mock()
+        PublishArtifact artifact2 = Mock()
+        Task task1 = Mock()
+        Task task2 = Mock()
+        Task task3 = Mock()
+
+        given:
+        store.add(artifact1)
+        store.add(artifact2)
+        builtBy(artifact1, task1, task2)
+        builtBy(artifact2, task1, task3)
+
+        expect:
+        set.files.buildDependencies.getDependencies(null) == [task1, task2, task3] as Set
+    }
+
+    def builtBy(PublishArtifact artifact, Task... tasks) {
+        TaskDependency taskDependency = Mock()
+        _ * artifact.buildDependencies >> taskDependency
+        _ * taskDependency.getDependencies(_) >> (tasks as Set)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.java
deleted file mode 100644
index be4addf..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.java
+++ /dev/null
@@ -1,102 +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;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.report.ArtifactDownloadReport;
-import org.apache.ivy.core.resolve.DownloadOptions;
-import org.apache.ivy.core.resolve.ResolveEngine;
-import org.gradle.api.artifacts.ResolvedDependency;
-import static org.hamcrest.Matchers.*;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultResolvedArtifactTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    @Test
-    public void init() {
-        String someName = "someName";
-        String someType = "someType";
-        String someExtension = "someExtension";
-        File someFile = new File("someFile");
-        DefaultResolvedArtifact resolvedArtifact = createResolvedArtifact(context, 
-                someName, someType, someExtension, someFile);
-        assertThat(resolvedArtifact.getResolvedDependency(), nullValue());
-        assertThat(resolvedArtifact.getName(), equalTo(someName));
-        assertThat(resolvedArtifact.getType(), equalTo(someType));
-        assertThat(resolvedArtifact.getExtension(), equalTo(someExtension));
-        assertThatFileIsReturnedAndCached(someFile, resolvedArtifact);
-        assertThat(resolvedArtifact.getVersion(), nullValue());
-        assertThat(resolvedArtifact.getDependencyName(), nullValue());
-    }
-
-    private void assertThatFileIsReturnedAndCached(File someFile, DefaultResolvedArtifact resolvedArtifact) {
-        assertThat(resolvedArtifact.getFile(), equalTo(someFile));
-
-        // The resolve engine mock would complain if the file is not cached.
-        assertThat(resolvedArtifact.getFile(), equalTo(someFile));
-    }
-
-    @Test
-    public void setResolvedDependency() {
-        final String someVersion = "someVersion";
-        final String someDependencyName = "someDependencyName";
-        final ResolvedDependency resolvedDependencyStub = context.mock(ResolvedDependency.class);
-        context.checking(new Expectations() {{
-            allowing(resolvedDependencyStub).getModuleVersion();
-            will(returnValue(someVersion));
-            allowing(resolvedDependencyStub).getModuleName();
-            will(returnValue(someDependencyName));
-        }});
-        DefaultResolvedArtifact resolvedArtifact = createResolvedArtifact(context, "someName", "someType", "someExtension", new File("someFile"));
-        resolvedArtifact.setResolvedDependency(resolvedDependencyStub);
-        assertThat(resolvedArtifact.getResolvedDependency(), sameInstance(resolvedDependencyStub));
-        assertThat(resolvedArtifact.getVersion(), equalTo(someVersion));
-        assertThat(resolvedArtifact.getDependencyName(), equalTo(someDependencyName));
-    }
-
-    public static DefaultResolvedArtifact createResolvedArtifact(Mockery context, final String name, final String type, final String extension, File file) {
-        final Artifact artifactStub = context.mock(Artifact.class, "artifact" + name);
-        context.checking(new Expectations() {{
-            allowing(artifactStub).getName();
-            will(returnValue(name));
-            allowing(artifactStub).getType();
-            will(returnValue(type));
-            allowing(artifactStub).getExt();
-            will(returnValue(extension));
-        }});
-        final ResolveEngine resolveEngineMock = context.mock(ResolveEngine.class, "engine" + name);
-        final ArtifactDownloadReport artifactDownloadReport = new ArtifactDownloadReport(artifactStub);
-        artifactDownloadReport.setLocalFile(file);
-        context.checking(new Expectations() {{
-            one(resolveEngineMock).download(with(equal(artifactStub)), with(any(DownloadOptions.class)));
-            will(returnValue(artifactDownloadReport));
-        }});
-        return new DefaultResolvedArtifact(artifactStub, resolveEngineMock);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
deleted file mode 100644
index 4cff2f5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
+++ /dev/null
@@ -1,203 +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.api.internal.artifacts;
-
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.util.GUtil;
-import org.gradle.util.WrapUtil;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.Set;
-
-import static org.gradle.util.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultResolvedDependencyTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    @Test
-    public void init() {
-        String someGroup = "someGroup";
-        String someName = "someName";
-        String someVersion = "someVersion";
-        String someConfiguration = "someConfiguration";
-        Set<ResolvedArtifact> someArtifacts = WrapUtil.toSet(createArtifact("someName"));
-        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency(someGroup, someName, someVersion, someConfiguration,
-                someArtifacts);
-        assertThat(resolvedDependency.getName(), equalTo(someGroup + ":" + someName + ":" + someVersion));
-        assertThat(resolvedDependency.getModuleGroup(), equalTo(someGroup));
-        assertThat(resolvedDependency.getModuleName(), equalTo(someName));
-        assertThat(resolvedDependency.getModuleVersion(), equalTo(someVersion));
-        assertThat(resolvedDependency.getConfiguration(), equalTo(someConfiguration));
-        assertThat(resolvedDependency.getModuleArtifacts(), equalTo(someArtifacts));
-        assertThat(resolvedDependency.getChildren(), equalTo(Collections.<ResolvedDependency>emptySet()));
-        assertThat(resolvedDependency.getParents(), equalTo(Collections.<ResolvedDependency>emptySet()));
-    }
-
-    @Test
-    public void getAllModuleArtifacts() {
-        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.<ResolvedArtifact>toSet(createArtifact("moduleArtifact"));
-        Set<ResolvedArtifact> someChildModuleArtifacts = WrapUtil.<ResolvedArtifact>toSet(createArtifact("childModuleArtifact"));
-        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration",
-                someModuleArtifacts);
-        resolvedDependency.getChildren().add(new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration",
-                someChildModuleArtifacts));
-        assertThat(resolvedDependency.getAllModuleArtifacts(), equalTo(GUtil.addSets(someChildModuleArtifacts, someModuleArtifacts)));
-    }
-
-    @Test
-    public void getParentArtifacts() {
-        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someResolvedArtifact"));
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(someModuleArtifacts);
-
-        Set<ResolvedArtifact> parent1SpecificArtifacts = WrapUtil.toSet(createArtifact("parent1Specific"));
-        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
-
-        Set<ResolvedArtifact> parent2SpecificArtifacts = WrapUtil.toSet(createArtifact("parent2Specific"));
-        DefaultResolvedDependency parentResolvedDependency2 = createAndAddParent("parent2", resolvedDependency, parent2SpecificArtifacts);
-
-        assertThat(resolvedDependency.getParentArtifacts(parentResolvedDependency1), equalTo(parent1SpecificArtifacts));
-        assertThat(resolvedDependency.getParentArtifacts(parentResolvedDependency2), equalTo(parent2SpecificArtifacts));
-    }
-
-    private ResolvedArtifact createArtifact(String name) {
-        return DefaultResolvedArtifactTest.createResolvedArtifact(context, name, "someType", "someExt", new File("pathTo" + name));
-    }
-
-    private DefaultResolvedDependency createResolvedDependency(Set<ResolvedArtifact> moduleArtifacts) {
-        return new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration",
-                moduleArtifacts);
-    }
-
-    @Test
-    public void getArtifacts() {
-        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(someModuleArtifacts);
-
-        Set<ResolvedArtifact> parent1SpecificArtifacts = WrapUtil.toSet(createArtifact("parent1Specific"));
-        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
-
-        assertThat(resolvedDependency.getArtifacts(parentResolvedDependency1), equalTo(GUtil.addSets(someModuleArtifacts, parent1SpecificArtifacts)));
-    }
-
-    @Test
-    public void getArtifactsWithParentWithoutParentArtifacts() {
-        Set<ResolvedArtifact> moduleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(moduleArtifacts);
-
-        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration",
-                Collections.<ResolvedArtifact>emptySet());
-        resolvedDependency.getParents().add(parent);
-        assertThat(resolvedDependency.getArtifacts(parent), equalTo(moduleArtifacts));
-    }
-
-    @Test
-    public void getParentArtifactsWithParentWithoutParentArtifacts() {
-        Set<ResolvedArtifact> moduleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(moduleArtifacts);
-
-        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration",
-                Collections.<ResolvedArtifact>emptySet());
-        resolvedDependency.getParents().add(parent);
-        assertThat(resolvedDependency.getParentArtifacts(parent), equalTo(Collections.<ResolvedArtifact>emptySet()));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void getParentArtifactsWithUnknownParent() {
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(Collections.<ResolvedArtifact>emptySet());
-        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration",
-                Collections.<ResolvedArtifact>emptySet());
-        assertThat(resolvedDependency.getParentArtifacts(unknownParent),
-                equalTo(Collections.<ResolvedArtifact>emptySet()));
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void getArtifactsWithUnknownParent() {
-        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(Collections.<ResolvedArtifact>emptySet());
-
-        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration",
-                Collections.<ResolvedArtifact>emptySet());
-        assertThat(resolvedDependency.getParentArtifacts(unknownParent),
-                equalTo(someModuleArtifacts));
-    }
-
-    @Test
-    public void getAllArtifacts() {
-        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
-        Set<ResolvedArtifact> someChildModuleArtifacts = WrapUtil.toSet(createArtifact("someChildModuleResolvedArtifact"));
-        DefaultResolvedDependency resolvedDependency = createResolvedDependency(someModuleArtifacts);
-
-        Set<ResolvedArtifact> parent1SpecificArtifacts = WrapUtil.toSet(createArtifact("parent1Specific"));
-        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
-
-        createAndAddParent("parent2", resolvedDependency, WrapUtil.toSet(createArtifact("parent2Specific")));
-
-        DefaultResolvedDependency child = new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration",
-                someChildModuleArtifacts);
-        resolvedDependency.getChildren().add(child);
-
-        Set<ResolvedArtifact> childParent1SpecificArtifacts = WrapUtil.toSet(createArtifact("childParent1Specific"));
-        createAndAddParent("childParent1", child, childParent1SpecificArtifacts);
-
-        Set<ResolvedArtifact> childParent2SpecificArtifacts = WrapUtil.toSet(createArtifact("childParent2Specific"));
-        createAndAddParent("childParent2", child, childParent2SpecificArtifacts);
-
-        assertThat(resolvedDependency.getAllArtifacts(parentResolvedDependency1),
-                equalTo(GUtil.addSets(someModuleArtifacts, parent1SpecificArtifacts, someChildModuleArtifacts, childParent1SpecificArtifacts, childParent2SpecificArtifacts)));
-    }
-
-    @Test
-    public void equalsAndHashCode() {
-        DefaultResolvedDependency dependency = new DefaultResolvedDependency("group", "name", "version", "config",
-                Collections.EMPTY_SET);
-        DefaultResolvedDependency same = new DefaultResolvedDependency("group", "name", "version", "config", Collections.EMPTY_SET);
-        DefaultResolvedDependency differentGroup = new DefaultResolvedDependency("other", "name", "version", "config",
-                Collections.EMPTY_SET);
-        DefaultResolvedDependency differentName = new DefaultResolvedDependency("group", "other", "version", "config",
-                Collections.EMPTY_SET);
-        DefaultResolvedDependency differentVersion = new DefaultResolvedDependency("group", "name", "other", "config",
-                Collections.EMPTY_SET);
-        DefaultResolvedDependency differentConfiguration = new DefaultResolvedDependency("group", "name", "version",
-                "other", Collections.EMPTY_SET);
-
-        assertThat(dependency, strictlyEqual(same));
-        assertThat(dependency, not(equalTo(differentGroup)));
-        assertThat(dependency, not(equalTo(differentName)));
-        assertThat(dependency, not(equalTo(differentVersion)));
-        assertThat(dependency, not(equalTo(differentConfiguration)));
-    }
-
-    private DefaultResolvedDependency createAndAddParent(String parentName, DefaultResolvedDependency resolvedDependency, Set<ResolvedArtifact> parentSpecificArtifacts) {
-        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", parentName, "someVersion", "someConfiguration",
-                Collections.<ResolvedArtifact>emptySet());
-        resolvedDependency.getParents().add(parent);
-        resolvedDependency.addParentSpecificArtifacts(parent, parentSpecificArtifacts);
-        return parent;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainerTest.groovy
deleted file mode 100644
index 583b93f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainerTest.groovy
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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.api.internal.artifacts
-
-import org.apache.ivy.core.cache.DefaultRepositoryCacheManager
-import org.apache.ivy.core.cache.RepositoryCacheManager
-import org.apache.ivy.plugins.resolver.DependencyResolver
-import org.apache.ivy.plugins.resolver.FileSystemResolver
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.ResolverContainer
-import org.gradle.api.artifacts.UnknownRepositoryException
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer
-import org.gradle.api.artifacts.maven.MavenResolver
-import org.gradle.api.internal.ClassGenerator
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.JUnit4GroovyMockery
-import org.junit.Before
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-/**
- * @author Hans Dockter
- */
-class DefaultResolverContainerTest {
-    static final String TEST_REPO_NAME = 'reponame'
-
-    DefaultResolverContainer resolverContainer
-
-    RepositoryCacheManager dummyCacheManager = new DefaultRepositoryCacheManager()
-    def expectedUserDescription
-    def expectedUserDescription2
-    def expectedUserDescription3
-    String expectedName
-    String expectedName2
-    String expectedName3
-
-    FileSystemResolver expectedResolver
-    FileSystemResolver expectedResolver2
-    FileSystemResolver expectedResolver3
-
-    ResolverFactory resolverFactoryMock;
-
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
-    ResolverContainer createResolverContainer() {
-        return new DefaultResolverContainer(resolverFactoryMock, context.mock(ClassGenerator.class))
-    }
-
-    @Before public void setUp() {
-        expectedUserDescription = 'somedescription'
-        expectedUserDescription2 = 'somedescription2'
-        expectedUserDescription3 = 'somedescription3'
-        expectedName = 'somename'
-        expectedName2 = 'somename2'
-        expectedName3 = 'somename3'
-        expectedResolver = new FileSystemResolver()
-        expectedResolver2 = new FileSystemResolver()
-        expectedResolver3 = new FileSystemResolver()
-        expectedResolver.name = expectedName
-        expectedResolver2.name = expectedName2
-        expectedResolver3.name = expectedName3
-        resolverFactoryMock = context.mock(ResolverFactory)
-        context.checking {
-            allowing(resolverFactoryMock).createResolver(expectedUserDescription); will(returnValue(expectedResolver))
-            allowing(resolverFactoryMock).createResolver(expectedUserDescription2); will(returnValue(expectedResolver2))
-            allowing(resolverFactoryMock).createResolver(expectedUserDescription3); will(returnValue(expectedResolver3))
-        }
-        resolverContainer = createResolverContainer()
-    }
-
-    @Test public void testAddResolver() {
-        assert resolverContainer.add(expectedUserDescription).is(expectedResolver)
-        assert resolverContainer.findByName(expectedName).is(expectedResolver)
-        resolverContainer.add(expectedUserDescription2)
-        assertEquals([expectedResolver, expectedResolver2], resolverContainer.resolvers)
-    }
-
-    @Test public void testCannotAddResolverWithDuplicateName() {
-        [expectedResolver, expectedResolver2]*.name = 'resolver'
-        resolverContainer.add(expectedUserDescription)
-
-        try {
-            resolverContainer.add(expectedUserDescription2)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("Cannot add a resolver with name 'resolver' as a resolver with that name already exists."))
-        }
-    }
-
-    @Test public void testAddResolverWithClosure() {
-        def expectedConfigureValue = 'testvalue'
-        Closure configureClosure = {transactional = expectedConfigureValue}
-        assertThat(resolverContainer.add(expectedUserDescription, configureClosure), sameInstance(expectedResolver))
-        assertThat(resolverContainer.findByName(expectedName), sameInstance(expectedResolver))
-        assert expectedResolver.transactional == expectedConfigureValue
-    }
-
-    @Test public void testAddBefore() {
-        resolverContainer.add(expectedUserDescription)
-        assert resolverContainer.addBefore(expectedUserDescription2, expectedName).is(expectedResolver2)
-        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
-    }
-
-    @Test public void testAddAfter() {
-        resolverContainer.add(expectedUserDescription)
-        assert resolverContainer.addAfter(expectedUserDescription2, expectedName).is(expectedResolver2)
-        resolverContainer.addAfter(expectedUserDescription3, expectedName)
-        assertEquals([expectedResolver, expectedResolver3, expectedResolver2], resolverContainer.resolvers)
-    }
-
-    @Test(expected = InvalidUserDataException) public void testAddWithNullUserDescription() {
-        resolverContainer.add(null)
-    }
-
-    @Test(expected = InvalidUserDataException) public void testAddFirstWithNullUserDescription() {
-        resolverContainer.addFirst(null)
-    }
-
-    @Test(expected = InvalidUserDataException) public void testAddBeforeWithNullUserDescription() {
-        resolverContainer.addBefore(null, expectedName)
-    }
-
-    @Test(expected = InvalidUserDataException) public void testAddBeforeWithUnknownResolver() {
-        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
-    }
-
-    @Test(expected = InvalidUserDataException) public void testAddAfterWithNullUserDescription() {
-        resolverContainer.addAfter(null, expectedName)
-    }
-
-    @Test(expected = InvalidUserDataException) public void testAddAfterWithUnknownResolver() {
-        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
-    }
-
-    @Test public void testAddFirst() {
-        assert resolverContainer.addFirst(expectedUserDescription).is(expectedResolver)
-        resolverContainer.addFirst(expectedUserDescription2)
-        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
-    }
-
-    @Test(expected = InvalidUserDataException)
-    public void testAddWithUnnamedResolver() {
-        expectedResolver.name = null
-        resolverContainer.add(expectedUserDescription).is(expectedResolver)
-    }
-
-    @Test
-    public void testGetThrowsExceptionForUnknownResolver() {
-        try {
-            resolverContainer.getByName('unknown')
-            fail()
-        } catch (UnknownRepositoryException e) {
-            assertThat(e.message, equalTo("Repository with name 'unknown' not found."))
-        }
-    }
-
-    @Test
-    public void createMavenUploader() {
-        assertSame(prepareMavenDeployerTests(), resolverContainer.createMavenDeployer(DefaultResolverContainerTest.TEST_REPO_NAME));
-    }
-
-    @Test
-    public void createMavenInstaller() {
-        assertSame(prepareMavenInstallerTests(), resolverContainer.createMavenInstaller(DefaultResolverContainerTest.TEST_REPO_NAME));
-    }
-
-    protected GroovyMavenDeployer prepareMavenDeployerTests() {
-        prepareMavenResolverTests(GroovyMavenDeployer, "createMavenDeployer")
-    }
-
-    protected DependencyResolver prepareMavenInstallerTests() {
-        prepareMavenResolverTests(MavenResolver, "createMavenInstaller")
-    }
-
-    protected DependencyResolver prepareMavenResolverTests(Class resolverType, String createMethod) {
-        File testPomDir = new File("pomdir");
-        ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class)
-        Conf2ScopeMappingContainer conf2ScopeMappingContainer = context.mock(Conf2ScopeMappingContainer.class)
-        FileResolver fileResolver = context.mock(FileResolver.class)
-        resolverContainer.setMavenPomDir(testPomDir)
-        resolverContainer.setConfigurationContainer(configurationContainer)
-        resolverContainer.setMavenScopeMappings(conf2ScopeMappingContainer)
-        resolverContainer.setFileResolver(fileResolver)
-        DependencyResolver expectedResolver = context.mock(resolverType)
-        context.checking {
-            allowing(expectedResolver).getName(); will(returnValue(DefaultResolverContainerTest.TEST_REPO_NAME))
-            allowing(resolverFactoryMock)."$createMethod"(
-                    withParam(any(String)),
-                    withParam(same(resolverContainer)),
-                    withParam(same(configurationContainer)),
-                    withParam(same(conf2ScopeMappingContainer)),
-                    withParam(same(fileResolver)));
-            will(returnValue(expectedResolver))
-            allowing(resolverFactoryMock).createResolver(expectedResolver);
-            will(returnValue(expectedResolver))
-        }
-        expectedResolver
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParserTest.groovy
new file mode 100644
index 0000000..7014a95
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ExcludeRuleNotationParserTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * 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
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.ExcludeRule
+import org.gradle.util.WrapUtil
+import spock.lang.Specification
+
+class ExcludeRuleNotationParserTest extends Specification {
+    def parser = new ExcludeRuleNotationParser<DefaultExcludeRule>();
+
+    def "with group"() {
+        when:
+        ExcludeRule d = parser.parseNotation([group: 'aGroup']);
+        then:
+        d != null;
+        d instanceof DefaultExcludeRule
+        d.group == 'aGroup'
+        !d.module
+    }
+
+    def "with module"() {
+        when:
+        ExcludeRule d = parser.parseNotation([module: 'aModule']);
+        then:
+        d != null;
+        d instanceof DefaultExcludeRule
+        d.module == 'aModule'
+        !d.group
+    }
+
+    def "with group and module"() {
+        when:
+        ExcludeRule d = parser.parseNotation([group: 'aGroup', module: 'aModule']);
+        then:
+        d != null;
+        d instanceof DefaultExcludeRule
+        d.group == 'aGroup'
+        d.module == 'aModule'
+    }
+
+    def "with no group and no module InvalidUserDataException is thrown"() {
+        when:
+        parser.parseNotation([invalidKey1: 'aGroup', invalidKey2: 'aModule']);
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "throw exception with a valid group but invalid second key"() {
+        when:
+        parser.parseNotation([group: 'aGroup', invalidKey1:"invalidValue"]);
+        then:
+        thrown(MissingPropertyException)
+    }
+
+    def "checkValidExcludeRuleMap is true if group or module is defined"() {
+        expect:
+        parser.parseNotation(WrapUtil.toMap(ExcludeRule.GROUP_KEY, "aGroup"));
+        parser.parseNotation(WrapUtil.toMap(ExcludeRule.MODULE_KEY, "aModule"));
+
+        when:
+        parser.parseNotation(WrapUtil.toMap("unknownKey", "someValue"))
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
new file mode 100644
index 0000000..03bb9ee
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2011 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.configurations;
+
+
+import org.gradle.api.artifacts.UnknownConfigurationException
+import org.gradle.api.internal.DomainObjectContext
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.listener.ListenerManager
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter, Szczepan
+ */
+public class DefaultConfigurationContainerSpec extends Specification {
+    private static final String TEST_DESCRIPTION = "testDescription";
+    private static final Closure TEST_CLOSURE = HelperUtil.createSetterClosure("Description", TEST_DESCRIPTION);
+    private static final String TEST_NAME = "testName";
+
+    private ArtifactDependencyResolver dependencyResolver = Mock()
+    private Instantiator instantiator = Mock()
+    private DomainObjectContext domainObjectContext = Mock()
+    private ListenerManager listenerManager = Mock()
+    private DependencyMetaDataProvider metaDataProvider = Mock()
+    def ConfigurationInternal conf = Mock()
+
+    private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(dependencyResolver, instantiator, domainObjectContext, listenerManager, metaDataProvider);
+
+    def "adds and gets"() {
+        _ * conf.getName() >> "compile"
+        1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
+        1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
+                dependencyResolver, listenerManager, metaDataProvider, _ as DefaultResolutionStrategy) >> conf
+
+        when:
+        def compile = configurationContainer.add("compile")
+
+        then:
+        configurationContainer.getByName("compile") == compile
+
+        when:
+        configurationContainer.getByName("fooo")
+
+        then:
+        thrown(UnknownConfigurationException)
+    }
+
+    def "configures and finds"() {
+        _ * conf.getName() >> "compile"
+        1 * domainObjectContext.absoluteProjectPath("compile") >> ":compile"
+        1 * instantiator.newInstance(DefaultConfiguration.class, ":compile", "compile", configurationContainer,
+                dependencyResolver, listenerManager, metaDataProvider, _ as DefaultResolutionStrategy) >> conf
+
+        when:
+        def compile = configurationContainer.add("compile") {
+            description = "I compile!"
+        }
+
+        then:
+        configurationContainer.getByName("compile") == compile
+        1 * conf.setDescription("I compile!")
+
+        //finds configurations
+        configurationContainer.findByName("compile") == compile
+        configurationContainer.findByName("fooo") == null
+        configurationContainer.findAll { it.name == "compile" } as Set == [compile] as Set
+        configurationContainer.findAll { it.name == "fooo" } as Set == [] as Set
+
+        configurationContainer as List == [compile] as List
+    }
+
+    def "creates detached"() {
+        given:
+        def dependency1 = HelperUtil.createDependency("group1", "name1", "version1");
+        def dependency2 = HelperUtil.createDependency("group2", "name2", "version2");
+
+        when:
+        def detached = configurationContainer.detachedConfiguration(dependency1, dependency2);
+
+        then:
+        detached.getAll() == [detached] as Set
+        detached.getHierarchy() == [detached] as Set
+        [dependency1, dependency2].each { detached.getDependencies().contains(it) }
+        detached.getDependencies().size() == 2
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
new file mode 100644
index 0000000..7cef8b0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * 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.configurations
+
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.UnknownConfigurationException
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.listener.ListenerManager
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.api.internal.*
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+/**
+ * @author Hans Dockter
+ */
+
+ at RunWith(JMock)
+class DefaultConfigurationContainerTest {
+    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver)
+    private DomainObjectContext domainObjectContext = context.mock(DomainObjectContext.class)
+    private ListenerManager listenerManager = context.mock(ListenerManager.class)
+    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class)
+    private Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+    private DefaultConfigurationContainer configurationHandler = instantiator.newInstance(DefaultConfigurationContainer.class,
+            dependencyResolver, instantiator, { name -> name } as DomainObjectContext,
+            listenerManager, metaDataProvider)
+
+    @Before
+    public void setup() {
+        context.checking {
+            ignoring(listenerManager)
+        }
+    }
+
+    @Test
+    void addsNewConfigurationWhenConfiguringSelf() {
+        configurationHandler.configure {
+            newConf
+        }
+        assertThat(configurationHandler.findByName('newConf'), notNullValue())
+        assertThat(configurationHandler.newConf, notNullValue())
+    }
+
+    @Test(expected = UnknownConfigurationException)
+    void doesNotAddNewConfigurationWhenNotConfiguringSelf() {
+        configurationHandler.getByName('unknown')
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsProperty() {
+        Configuration configuration = configurationHandler.add('newConf')
+        assertThat(configuration, notNullValue())
+        assertThat(configurationHandler.getByName("newConf"), sameInstance(configuration))
+        assertThat(configurationHandler.newConf, sameInstance(configuration))
+    }
+
+    @Test
+    void addsNewConfigurationWithClosureWhenConfiguringSelf() {
+        String someDesc = 'desc1'
+        configurationHandler.configure {
+            newConf {
+                description = someDesc
+            }
+        }
+        assertThat(configurationHandler.newConf.getDescription(), equalTo(someDesc))
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsConfigureMethod() {
+        String someDesc = 'desc1'
+        configurationHandler.add('newConf')
+        Configuration configuration = configurationHandler.newConf {
+            description = someDesc
+        }
+        assertThat(configuration.getDescription(), equalTo(someDesc))
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsConfigureMethodWhenConfiguringSelf() {
+        String someDesc = 'desc1'
+        Configuration configuration = configurationHandler.add('newConf')
+        configurationHandler.configure {
+            newConf {
+                description = someDesc
+            }
+        }
+        assertThat(configuration.getDescription(), equalTo(someDesc))
+    }
+
+    @Test(expected = MissingMethodException)
+    void newConfigurationWithNonClosureParametersShouldThrowMissingMethodEx() {
+        configurationHandler.newConf('a', 'b')
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.java
deleted file mode 100644
index 4afe19c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.java
+++ /dev/null
@@ -1,165 +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.api.internal.artifacts.configurations;
-
-import groovy.lang.Closure;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.UnknownConfigurationException;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.artifacts.IvyService;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.*;
-
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultConfigurationContainerTest {
-    private static final String TEST_DESCRIPTION = "testDescription";
-    private static final Closure TEST_CLOSURE = HelperUtil.createSetterClosure("Description", TEST_DESCRIPTION);
-    private static final String TEST_NAME = "testName";
-
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private IvyService ivyServiceDummy = context.mock(IvyService.class);
-    private ClassGenerator classGenerator = context.mock(ClassGenerator.class);
-    private DomainObjectContext domainObjectContext = context.mock(DomainObjectContext.class);
-    private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(ivyServiceDummy, classGenerator, domainObjectContext);
-
-    @Test
-    public void init() {
-        assertThat(configurationContainer.getIvyService(), sameInstance(ivyServiceDummy));
-    }
-
-    @Test
-    public void testAdd() {
-        expectConfigurationCreated(TEST_NAME);
-        checkAddGetWithName((DefaultConfiguration) configurationContainer.add(TEST_NAME));
-    }
-
-    @Test
-    public void testAddWithNullClosure() {
-        expectConfigurationCreated(TEST_NAME);
-        checkAddGetWithName((DefaultConfiguration) configurationContainer.add(TEST_NAME, null));
-    }
-
-    @Test
-    public void testAddWithClosure() {
-        expectConfigurationCreated(TEST_NAME);
-        Configuration configuration = checkAddGetWithName((DefaultConfiguration) configurationContainer.add(TEST_NAME, TEST_CLOSURE));
-        assertThat(configuration.getDescription(), equalTo(TEST_DESCRIPTION));
-    }
-
-    private Configuration checkAddGetWithName(DefaultConfiguration configuration) {
-        assertThat(configuration, equalTo(configurationContainer.getByName(TEST_NAME)));
-        return configuration;
-    }
-
-    @Test
-    public void testFind() {
-        expectConfigurationCreated(TEST_NAME);
-        Configuration configuration = configurationContainer.add(TEST_NAME);
-        assertThat(configuration, sameInstance(configurationContainer.findByName(TEST_NAME)));
-    }
-
-    @Test
-    public void testFindNonExisitingConfiguration() {
-        assertThat(configurationContainer.findByName(TEST_NAME + "delta"), equalTo(null));
-    }
-
-    @Test(expected = UnknownConfigurationException.class)
-    public void testGetNonExisitingConfiguration() {
-        configurationContainer.getByName(TEST_NAME + "delta");
-    }
-
-    @Test
-    public void testGetWithClosure() {
-        expectConfigurationCreated(TEST_NAME);
-        configurationContainer.add(TEST_NAME);
-        Configuration configuration = configurationContainer.getByName(TEST_NAME, TEST_CLOSURE);
-        assertThat(configuration.getDescription(), equalTo(TEST_DESCRIPTION));
-    }
-
-    @Test
-    public void testGetWithFilter() {
-        expectConfigurationCreated(TEST_NAME);
-        expectConfigurationCreated(TEST_NAME + "delta");
-        Configuration configuration = configurationContainer.add(TEST_NAME);
-        configurationContainer.add(TEST_NAME + "delta");
-        Set<Configuration> result = configurationContainer.findAll(new Spec<Configuration>() {
-            public boolean isSatisfiedBy(Configuration element) {
-                return element.getName().equals(TEST_NAME);
-            }
-        });
-        assertThat(result, equalTo(WrapUtil.toSet(configuration)));
-    }
-
-    @Test
-    public void testGetAll() {
-        expectConfigurationCreated(TEST_NAME);
-        expectConfigurationCreated(TEST_NAME + "delta");
-        Configuration configuration1 = configurationContainer.add(TEST_NAME);
-        Configuration configuration2 = configurationContainer.add(TEST_NAME + "delta");
-        assertThat(configurationContainer.getAll(), equalTo(WrapUtil.toSet(configuration1, configuration2)));
-    }
-
-    @Test
-    public void testCreateDetached() {
-        Dependency dependency1 = HelperUtil.createDependency("group1", "name1", "version1");
-        Dependency dependency2 = HelperUtil.createDependency("group2", "name2", "version2");
-
-        Configuration detachedConf = configurationContainer.detachedConfiguration(dependency1, dependency2);
-
-        assertThat(detachedConf.getAll(), equalTo(WrapUtil.toSet(detachedConf)));
-        assertThat(detachedConf.getHierarchy(), equalTo(WrapUtil.<Configuration>toSet(detachedConf)));
-        assertThat(detachedConf.getDependencies(), equalTo(WrapUtil.toSet(dependency1, dependency2)));
-        assertNotSameInstances(detachedConf.getDependencies(), WrapUtil.toSet(dependency1, dependency2));
-    }
-
-    private void expectConfigurationCreated(final String name) {
-        context.checking(new Expectations(){{
-            one(domainObjectContext).absoluteProjectPath(name);
-            will(returnValue(name));
-            one(classGenerator).newInstance(DefaultConfiguration.class, name, name, configurationContainer, ivyServiceDummy);
-            will(returnValue(new DefaultConfiguration(name, name, configurationContainer, ivyServiceDummy)));
-        }});
-    }
-
-    private void assertNotSameInstances(Set<Dependency> dependencies, Set<Dependency> otherDependencies) {
-        for (Dependency dependency : dependencies) {
-            assertHasEqualButNotSameInstance(dependency, otherDependencies);
-        }
-    }
-
-    private void assertHasEqualButNotSameInstance(Dependency dependency, Set<Dependency> otherDependencies) {
-        assertThat(otherDependencies, hasItem(dependency));
-        for (Dependency otherDependency : otherDependencies) {
-            if (otherDependency.equals(dependency)) {
-                assertThat(otherDependency, not(sameInstance(dependency)));
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
new file mode 100644
index 0000000..9e956b9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2011 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.configurations
+
+import org.gradle.api.Action
+import org.gradle.api.Task
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.listener.ListenerManager
+import spock.lang.Specification
+import org.gradle.api.artifacts.*
+
+class DefaultConfigurationSpec extends Specification {
+
+    ConfigurationsProvider configurationsProvider = Mock()
+    ArtifactDependencyResolver dependencyResolver = Mock()
+    ListenerManager listenerManager = Mock()
+    DependencyMetaDataProvider metaDataProvider = Mock()
+    DefaultResolutionStrategy defaultResolutionStrategy = Mock()
+
+    DefaultConfiguration conf(String confName = "conf", String path = ":conf") {
+        new DefaultConfiguration(path, confName, configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, defaultResolutionStrategy)
+    }
+
+    DefaultPublishArtifact artifact(String name) {
+        artifact(name: name)
+    }
+
+    DefaultPublishArtifact artifact(Map props = [:]) {
+        new DefaultPublishArtifact(
+            props.name ?: "artifact",
+            props.extension ?: "artifact",
+            props.type,
+            props.classifier,
+            props.date,
+            props.file,
+            props.tasks ?: []
+        )
+    }
+
+    // You need to wrap this in an interaction {} block when calling it
+    ResolvedConfiguration resolvedConfiguration(Configuration config, ArtifactDependencyResolver dependencyResolver = dependencyResolver) {
+        ResolvedConfiguration resolvedConfiguration = Mock()
+        1 * dependencyResolver.resolve(config) >> resolvedConfiguration
+        resolvedConfiguration
+    }
+
+    def setup() {
+        ListenerBroadcast<DependencyResolutionListener> broadcast = new ListenerBroadcast<DependencyResolutionListener>(DependencyResolutionListener)
+        _ * listenerManager.createAnonymousBroadcaster(DependencyResolutionListener) >> broadcast
+    }
+
+    def "all artifacts collection has immediate artifacts"() {
+        given:
+        def c = conf()
+
+        when:
+        c.artifacts << artifact()
+        c.artifacts << artifact()
+
+        then:
+        c.allArtifacts.size() == 2
+    }
+
+    def "all artifacts collection has inherited artifacts"() {
+        given:
+        def master = conf()
+
+        def masterParent1 = conf()
+        def masterParent2 = conf()
+        master.extendsFrom masterParent1, masterParent2
+
+        def masterParent1Parent1 = conf()
+        def masterParent1Parent2 = conf()
+        masterParent1.extendsFrom masterParent1Parent1, masterParent1Parent2
+
+        def masterParent2Parent1 = conf()
+        def masterParent2Parent2 = conf()
+        masterParent2.extendsFrom masterParent2Parent1, masterParent2Parent2
+
+        def allArtifacts = master.allArtifacts
+
+        def added = []
+        allArtifacts.whenObjectAdded { added << it.name }
+        def removed = []
+        allArtifacts.whenObjectRemoved { removed << it.name }
+
+        expect:
+        allArtifacts.empty
+
+        when:
+        masterParent1.artifacts << artifact("p1-1")
+        masterParent1Parent1.artifacts << artifact("p1p1-1")
+        masterParent1Parent2.artifacts << artifact("p1p2-1")
+        masterParent2.artifacts << artifact("p2-1")
+        masterParent2Parent1.artifacts << artifact("p2p1-1")
+        masterParent2Parent2.artifacts << artifact("p2p2-1")
+
+        then:
+        allArtifacts.size() == 6
+        added == ["p1-1", "p1p1-1", "p1p2-1", "p2-1", "p2p1-1", "p2p2-1"]
+
+        when:
+        masterParent2Parent2.artifacts.remove masterParent2Parent2.artifacts.toList().first()
+
+        then:
+        allArtifacts.size() == 5
+        removed == ["p2p2-1"]
+
+        when:
+        removed.clear()
+        masterParent1.extendsFrom = []
+
+        then:
+        allArtifacts.size() == 3
+        removed == ["p1p1-1", "p1p2-1"]
+    }
+
+    def "incoming dependencies set has same name and path as owner configuration"() {
+        def config = conf("conf", ":path")
+
+        expect:
+        config.incoming.name == "conf"
+        config.incoming.path == ":path"
+    }
+
+    def "incoming dependencies set contains immediate dependencies"() {
+        def config = conf("conf")
+        Dependency dep1 = Mock()
+
+        given:
+        config.dependencies.add(dep1)
+
+        expect:
+        config.incoming.dependencies as List == [dep1]
+    }
+
+    def "incoming dependencies set contains inherited dependencies"() {
+        def parent = conf("conf")
+        def config = conf("conf")
+        Dependency dep1 = Mock()
+
+        given:
+        config.extendsFrom parent
+        parent.dependencies.add(dep1)
+
+        expect:
+        config.incoming.dependencies as List == [dep1]
+    }
+
+    def "incoming dependencies set files are resolved lazily"() {
+        setup:
+        def config = conf("conf")
+
+        when:
+        def files = config.incoming.files
+
+        then:
+        0 * _._
+
+        when:
+        files.files
+
+        then:
+        interaction { resolvedConfiguration(config) }
+        0 * dependencyResolver._
+    }
+
+    def "incoming dependencies set depends on all self resolving dependencies"() {
+        SelfResolvingDependency dependency = Mock()
+        Task task = Mock()
+        TaskDependency taskDep = Mock()
+        def config = conf("conf")
+
+        given:
+        config.dependencies.add(dependency)
+
+        when:
+        def depTaskDeps = config.incoming.dependencies.buildDependencies.getDependencies(null)
+        def fileTaskDeps = config.incoming.files.buildDependencies.getDependencies(null)
+
+        then:
+        depTaskDeps == [task] as Set
+        fileTaskDeps == [task] as Set
+        _ * dependency.buildDependencies >> taskDep
+        _ * taskDep.getDependencies(_) >> ([task] as Set)
+        0 * _._
+    }
+
+    def "notifies beforeResolve action on incoming dependencies set when dependencies are resolved"() {
+        Action<ResolvableDependencies> action = Mock()
+        def config = conf("conf")
+
+        given:
+        config.incoming.beforeResolve(action)
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        interaction { resolvedConfiguration(config) }
+        1 * action.execute(config.incoming)
+    }
+
+    def "calls beforeResolve closure on incoming dependencies set when dependencies are resolved"() {
+        def config = conf("conf")
+        resolvedConfiguration(config)
+        def called = false
+
+        expect:
+        config.incoming.afterResolve {
+            assert it == config.incoming
+            called = true
+        }
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        called
+    }
+
+    def "notifies afterResolve action on incoming dependencies set when dependencies are resolved"() {
+        Action<ResolvableDependencies> action = Mock()
+        def config = conf("conf")
+
+        given:
+        config.incoming.afterResolve(action)
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        interaction { resolvedConfiguration(config) }
+        1 * action.execute(config.incoming)
+
+    }
+
+    def "calls afterResolve closure on incoming dependencies set when dependencies are resolved"() {
+        def config = conf("conf")
+        resolvedConfiguration(config)
+        def called = false
+
+        expect:
+        config.incoming.afterResolve {
+            assert it == config.incoming
+            called = true
+        }
+
+        when:
+        config.resolvedConfiguration
+
+        then:
+        called
+    }
+    
+    def "a recursive copy of a configuration includes inherited exclude rules"() {
+        given:
+        def (p1, p2, child) = [conf("p1"), conf("p2"), conf("child")]
+        child.extendsFrom p1, p2
+        
+        and:
+        def (p1Exclude, p2Exclude) = [[group: 'p1', module: 'p1'], [group: 'p2', module: 'p2']]
+        p1.exclude p1Exclude
+        p2.exclude p2Exclude
+        
+        when:
+        def copied = child.copyRecursive()
+        
+        then:
+        copied.excludeRules.size() == 2
+        copied.excludeRules.collect{[group: it.group, module: it.module]}.sort { it.group } == [p1Exclude, p2Exclude]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
index 39817aa..c132e11 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
@@ -16,25 +16,30 @@
 package org.gradle.api.internal.artifacts.configurations;
 
 import groovy.lang.Closure;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.*;
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
 import org.gradle.api.artifacts.*;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.DefaultDependencySet;
 import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.api.internal.artifacts.IvyService;
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.artifacts.DefaultPublishArtifactSet;
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.tasks.TaskDependency;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
 import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.TestClosure;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,18 +54,27 @@ import static org.junit.Assert.*;
 
 @RunWith(JMock.class)
 public class DefaultConfigurationTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    private IvyService ivyServiceStub = context.mock(IvyService.class);
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver.class);
     private ConfigurationsProvider configurationContainer;
-
+    private ListenerManager listenerManager = context.mock(ListenerManager.class);
+    private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class);
+    private DefaultResolutionStrategy resolutionStrategy = new DefaultResolutionStrategy();
     private DefaultConfiguration configuration;
+    private DependencyResolutionListener dependencyResolutionBroadcast = context.mock(DependencyResolutionListener.class);
+    private ListenerBroadcast resolutionListenerBroadcast = context.mock(ListenerBroadcast.class); 
 
     @Before
     public void setUp() {
         configurationContainer = context.mock(ConfigurationsProvider.class);
+        context.checking(new Expectations(){{
+            allowing(listenerManager).createAnonymousBroadcaster(DependencyResolutionListener.class);
+            will(returnValue(resolutionListenerBroadcast));
+            allowing(resolutionListenerBroadcast).getSource();
+            will(returnValue(dependencyResolutionBroadcast));
+            allowing(dependencyResolutionBroadcast).afterResolve(with(any(ResolvableDependencies.class)));
+            allowing(dependencyResolutionBroadcast).beforeResolve(with(any(ResolvableDependencies.class)));
+        }});
         configuration = createNamedConfiguration("path", "name");
     }
 
@@ -76,6 +90,13 @@ public class DefaultConfigurationTest {
     }
 
     @Test
+    public void hasUsefulDisplayName() {
+        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
+        assertThat(configuration.toString(), equalTo("configuration 'path'"));
+        assertThat(configuration.getIncoming().toString(), equalTo("dependencies 'path'"));
+    }
+
+    @Test
     public void withPrivateVisibility() {
         configuration.setVisible(false);
         assertFalse(configuration.isVisible());
@@ -89,17 +110,17 @@ public class DefaultConfigurationTest {
 
     @Test
     public void exclude() {
-        Map<String, String> excludeArgs1 = toMap("org", "value");
-        Map<String, String> excludeArgs2 = toMap("org2", "value2");
+        Map<String, String> excludeArgs1 = toMap("group", "aGroup");
+        Map<String, String> excludeArgs2 = toMap("module", "aModule");
         assertThat(configuration.exclude(excludeArgs1), sameInstance(configuration));
         configuration.exclude(excludeArgs2);
         assertThat(configuration.getExcludeRules(), equalTo(WrapUtil.<ExcludeRule>toSet(
-                new DefaultExcludeRule(excludeArgs1), new DefaultExcludeRule(excludeArgs2))));
+                new DefaultExcludeRule("aGroup", null), new DefaultExcludeRule(null, "aModule"))));
     }
 
     @Test
     public void setExclude() {
-        Set<ExcludeRule> excludeRules = WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule(toMap("org", "value")));
+        Set<ExcludeRule> excludeRules = WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule("groupValue", null));
         configuration.setExcludeRules(excludeRules);
         assertThat(configuration.getExcludeRules(), equalTo(excludeRules));
     }
@@ -241,13 +262,13 @@ public class DefaultConfigurationTest {
     }
 
 
-    @SuppressWarnings("unchecked")
     @Test
     public void fileCollectionWithSpec() {
+        @SuppressWarnings("unchecked")
         Spec<Dependency> spec = context.mock(Spec.class);
         DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
                 configuration.fileCollection(spec);
-        assertThat(fileCollection.getDependencySpec(), sameInstance(spec));
+        assertThat(fileCollection.getDependencySpec(), sameInstance((Object)spec));
     }
 
     @Test
@@ -295,7 +316,6 @@ public class DefaultConfigurationTest {
         }});
     }
 
-    @SuppressWarnings("unchecked")
     private void makeResolveReturnFileSet(final Set<File> fileSet) {
         final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
         context.checking(new Expectations() {{
@@ -315,7 +335,7 @@ public class DefaultConfigurationTest {
 
     private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
         context.checking(new Expectations() {{
-            allowing(ivyServiceStub).resolve(configuration);
+            allowing(dependencyResolver).resolve(configuration);
             will(returnValue(resolvedConfiguration));
             allowing(resolvedConfiguration).hasError();
             will(returnValue(withErrors));
@@ -329,27 +349,16 @@ public class DefaultConfigurationTest {
     }
 
     @Test
-    public void publish() {
-        final Configuration otherConfiguration = createNamedConfiguration("testConf").extendsFrom(configuration);
-        final File someDescriptorDestination = new File("somePath");
-        final List<DependencyResolver> dependencyResolvers = toList(context.mock(DependencyResolver.class, "publish"));
-        context.checking(new Expectations() {{
-            allowing(ivyServiceStub).publish(new LinkedHashSet<Configuration>(otherConfiguration.getHierarchy()), someDescriptorDestination, dependencyResolvers);
-        }});
-        otherConfiguration.publish(dependencyResolvers, someDescriptorDestination);
-    }
-
-    @Test
     public void uploadTaskName() {
         assertThat(configuration.getUploadTaskName(), equalTo("uploadName"));
     }
 
     private DefaultConfiguration createNamedConfiguration(String confName) {
-        return new DefaultConfiguration(confName, confName, configurationContainer, ivyServiceStub);
+        return new DefaultConfiguration(confName, confName, configurationContainer, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy);
     }
     
     private DefaultConfiguration createNamedConfiguration(String path, String confName) {
-        return new DefaultConfiguration(path, confName, configurationContainer, ivyServiceStub);
+        return new DefaultConfiguration(path, confName, configurationContainer, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy);
     }
 
     @SuppressWarnings("unchecked")
@@ -358,27 +367,30 @@ public class DefaultConfigurationTest {
         final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
         final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
         final Configuration otherConfiguration = context.mock(Configuration.class);
-        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
-        final TaskDependency artifactTaskDependencyMock = context.mock(TaskDependency.class, "artifactTaskDep");
+        final TaskDependency otherArtifactTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
+        final PublishArtifactSet inheritedArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
         DefaultPublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        artifact.setTaskDependency(artifactTaskDependencyMock);
-        configuration.addArtifact(artifact);
+        artifact.builtBy(artifactTaskMock);
+        configuration.getArtifacts().add(artifact);
 
         context.checking(new Expectations() {{
-            allowing(otherConfiguration).getBuildArtifacts();
-            will(returnValue(otherConfTaskDependencyMock));
-
             allowing(otherConfiguration).getHierarchy();
             will(returnValue(toSet()));
 
-            allowing(otherConfTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(otherConfTaskMock)));
+            allowing(otherConfiguration).getAllArtifacts();
+            will(returnValue(inheritedArtifacts));
 
-            allowing(artifactTaskDependencyMock).getDependencies(with(any(Task.class)));
-            will(returnValue(toSet(artifactTaskMock)));
+            allowing(otherConfiguration).getAllDependencies();
+
+            allowing(otherArtifact).getBuildDependencies();
+            will(returnValue(otherArtifactTaskDependencyMock));
+            
+            allowing(otherArtifactTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(otherConfTaskMock)));
         }});
         configuration.setExtendsFrom(toSet(otherConfiguration));
-        assertThat((Set<Task>) configuration.getBuildArtifacts().getDependencies(context.mock(Task.class, "caller")),
+        assertThat((Set<Task>) configuration.getAllArtifacts().getBuildDependencies().getDependencies(context.mock(Task.class, "caller")),
                 equalTo(toSet(artifactTaskMock, otherConfTaskMock)));
     }
 
@@ -393,11 +405,17 @@ public class DefaultConfigurationTest {
         final File artifactFile2 = new File("artifact2");
         final PublishArtifact artifact = context.mock(PublishArtifact.class, "artifact");
         final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
+        final PublishArtifactSet otherArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
 
         context.checking(new Expectations() {{
             allowing(otherConfiguration).getHierarchy();
             will(returnValue(toSet()));
 
+            allowing(otherConfiguration).getAllArtifacts();
+            will(returnValue(otherArtifacts));
+
+            allowing(otherConfiguration).getAllDependencies();
+
             allowing(otherConfiguration).getExtendsFrom();
             will(returnValue(toSet()));
 
@@ -419,14 +437,14 @@ public class DefaultConfigurationTest {
             allowing(artifact).getBuildDependencies();
             will(returnValue(artifactTaskDependencyMock));
 
-            allowing(otherConfiguration).getBuildArtifacts();
+            allowing(otherArtifact).getBuildDependencies();
             will(returnValue(otherConfTaskDependencyMock));
         }});
 
-        configuration.addArtifact(artifact);
+        configuration.getArtifacts().add(artifact);
         configuration.setExtendsFrom(toSet(otherConfiguration));
 
-        FileCollection files = configuration.getAllArtifactFiles();
+        FileCollection files = configuration.getAllArtifacts().getFiles();
         assertThat(files.getFiles(), equalTo(toSet(artifactFile1, artifactFile2)));
         assertThat(files.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(otherConfTaskMock, artifactTaskMock)));
     }
@@ -456,8 +474,8 @@ public class DefaultConfigurationTest {
             will(returnValue(toSet(fileDepTaskDummy)));
         }});
 
-        configuration.addDependency(projectDependencyStub);
-        configuration.addDependency(fileCollectionDependencyStub);
+        configuration.getDependencies().add(projectDependencyStub);
+        configuration.getDependencies().add(fileCollectionDependencyStub);
 
         assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(fileDepTaskDummy,
                 projectDepTaskDummy)));
@@ -467,17 +485,24 @@ public class DefaultConfigurationTest {
     public void buildDependenciesDelegatesToInheritedConfigurations() {
         final Task target = context.mock(Task.class, "target");
         final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
-        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final TaskDependency dependencyTaskDependencyStub = context.mock(TaskDependency.class, "otherConfTaskDep");
         final Configuration otherConfiguration = context.mock(Configuration.class, "otherConf");
+        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
+        final DependencySet inherited = new DefaultDependencySet("dependencies", toDomainObjectSet(Dependency.class, fileCollectionDependencyStub));
 
         context.checking(new Expectations() {{
-            allowing(otherConfiguration).getBuildDependencies();
-            will(returnValue(otherConfTaskDependencyMock));
-
             allowing(otherConfiguration).getHierarchy();
             will(returnValue(toSet()));
 
-            allowing(otherConfTaskDependencyMock).getDependencies(target);
+            allowing(otherConfiguration).getAllArtifacts();
+
+            allowing(otherConfiguration).getAllDependencies();
+            will(returnValue(inherited));
+
+            allowing(fileCollectionDependencyStub).getBuildDependencies();
+            will(returnValue(dependencyTaskDependencyStub));
+
+            allowing(dependencyTaskDependencyStub).getDependencies(target);
             will(returnValue(toSet(otherConfTaskMock)));
         }});
 
@@ -492,7 +517,7 @@ public class DefaultConfigurationTest {
         configuration.extendsFrom(superConfig);
 
         final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
-        superConfig.addDependency(projectDependencyStub);
+        superConfig.getDependencies().add(projectDependencyStub);
 
         final Project projectStub = context.mock(Project.class);
         final TaskContainer taskContainerStub = context.mock(TaskContainer.class);
@@ -524,8 +549,8 @@ public class DefaultConfigurationTest {
         final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
         final Configuration dependentConfig = context.mock(Configuration.class);
         final ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-        final Set<ProjectDependency> projectDependencies = toSet(projectDependency);
-
+        final Set<ProjectDependency> projectDependencies = toDomainObjectSet(ProjectDependency.class, projectDependency);
+        final DependencySet otherDependencies = context.mock(DependencySet.class);
 
         context.checking(new Expectations() {{
             allowing(tdTask).getProject(); will(returnValue(taskProject));
@@ -535,7 +560,8 @@ public class DefaultConfigurationTest {
             allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
             allowing(configurationContainer).findByName(configName); will(returnValue(dependentConfig));
 
-            allowing(dependentConfig).getAllDependencies(ProjectDependency.class); will(returnValue(projectDependencies));
+            allowing(dependentConfig).getAllDependencies(); will(returnValue(otherDependencies));
+            allowing(otherDependencies).withType(ProjectDependency.class); will(returnValue(projectDependencies));
             allowing(projectDependency).getDependencyProject(); will(returnValue(taskProject));
         }});
 
@@ -578,22 +604,22 @@ public class DefaultConfigurationTest {
     @Test
     public void getDependencies() {
         Dependency dependency = context.mock(Dependency.class);
-        configuration.addDependency(dependency);
+        configuration.getDependencies().add(dependency);
         assertThat(configuration.getDependencies(), equalTo(toSet(dependency)));
     }
 
     @Test
     public void getTypedDependencies() {
         ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-        configuration.addDependency(context.mock(Dependency.class));
-        configuration.addDependency(projectDependency);
-        assertThat(configuration.getDependencies(ProjectDependency.class), equalTo(toSet(projectDependency)));
+        configuration.getDependencies().add(context.mock(Dependency.class));
+        configuration.getDependencies().add(projectDependency);
+        assertThat(configuration.getDependencies().withType(ProjectDependency.class), equalTo(toSet(projectDependency)));
     }
 
     @Test
     public void getTypedDependenciesReturnsEmptySetWhenNoMatches() {
-        configuration.addDependency(context.mock(Dependency.class));
-        assertThat(configuration.getDependencies(ProjectDependency.class), isEmpty());
+        configuration.getDependencies().add(context.mock(Dependency.class));
+        assertThat(configuration.getDependencies().withType(ProjectDependency.class), isEmpty());
     }
 
     @Test
@@ -602,10 +628,10 @@ public class DefaultConfigurationTest {
         Dependency dependencyOtherConf1 = HelperUtil.createDependency("group1", "name1", "version1");
         Dependency dependencyOtherConf2 = context.mock(Dependency.class, "dep2");
         Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.addDependency(dependencyConf);
+        configuration.getDependencies().add(dependencyConf);
         configuration.extendsFrom(otherConf);
-        otherConf.addDependency(dependencyOtherConf1);
-        otherConf.addDependency(dependencyOtherConf2);
+        otherConf.getDependencies().add(dependencyOtherConf1);
+        otherConf.getDependencies().add(dependencyOtherConf2);
 
         assertThat(configuration.getAllDependencies(), equalTo(toSet(dependencyConf, dependencyOtherConf2)));
         assertCorrectInstanceInAllDependencies(configuration.getAllDependencies(), dependencyConf);
@@ -614,25 +640,25 @@ public class DefaultConfigurationTest {
     @Test
     public void getAllTypedDependencies() {
         ProjectDependency projectDependencyCurrentConf = context.mock(ProjectDependency.class, "projectDepCurrentConf");
-        configuration.addDependency(context.mock(Dependency.class, "depCurrentConf"));
-        configuration.addDependency(projectDependencyCurrentConf);
+        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
+        configuration.getDependencies().add(projectDependencyCurrentConf);
         Configuration otherConf = createNamedConfiguration("otherConf");
         configuration.extendsFrom(otherConf);
         ProjectDependency projectDependencyExtendedConf = context.mock(ProjectDependency.class, "projectDepExtendedConf");
-        otherConf.addDependency(context.mock(Dependency.class, "depExtendedConf"));
-        otherConf.addDependency(projectDependencyExtendedConf);
+        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
+        otherConf.getDependencies().add(projectDependencyExtendedConf);
 
-        assertThat(configuration.getAllDependencies(ProjectDependency.class), equalTo(toSet(projectDependencyCurrentConf, projectDependencyExtendedConf)));
+        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), equalTo(toSet(projectDependencyCurrentConf, projectDependencyExtendedConf)));
     }
 
     @Test
     public void getAllTypedDependenciesReturnsEmptySetWhenNoMatches() {
-        configuration.addDependency(context.mock(Dependency.class, "depCurrentConf"));
+        configuration.getDependencies().add(context.mock(Dependency.class, "depCurrentConf"));
         Configuration otherConf = createNamedConfiguration("otherConf");
         configuration.extendsFrom(otherConf);
-        otherConf.addDependency(context.mock(Dependency.class, "depExtendedConf"));
+        otherConf.getDependencies().add(context.mock(Dependency.class, "depExtendedConf"));
 
-        assertThat(configuration.getAllDependencies(ProjectDependency.class), isEmpty());
+        assertThat(configuration.getAllDependencies().withType(ProjectDependency.class), isEmpty());
     }
 
     @Test
@@ -640,25 +666,54 @@ public class DefaultConfigurationTest {
         PublishArtifact artifactConf = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
         PublishArtifact artifactOtherConf2 = HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2");
         Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.addArtifact(artifactConf);
+        configuration.getArtifacts().add(artifactConf);
         configuration.extendsFrom(otherConf);
-        otherConf.addArtifact(artifactOtherConf2);
+        otherConf.getArtifacts().add(artifactOtherConf2);
         assertThat(configuration.getAllArtifacts(), equalTo(toSet(artifactConf, artifactOtherConf2)));
     }
 
     @Test
+    public void artifactAddedAction() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+
+        context.checking(new Expectations() {{
+            one(closure).call(artifact);
+        }});
+
+        configuration.getArtifacts().whenObjectAdded(HelperUtil.toClosure(closure));
+        configuration.getArtifacts().add(artifact);
+    }
+
+    @Test
+    public void artifactRemovedAction() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+
+        configuration.getArtifacts().add(artifact);
+
+        context.checking(new Expectations() {{
+            one(closure).call(artifact);
+        }});
+
+        configuration.getArtifacts().whenObjectRemoved(HelperUtil.toClosure(closure));
+
+        configuration.getArtifacts().remove(artifact);
+    }
+
+    @Test
     public void removeArtifact() {
         PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        configuration.addArtifact(artifact);
-        configuration.removeArtifact(artifact);
+        configuration.getArtifacts().add(artifact);
+        configuration.getArtifacts().remove(artifact);
         assertThat(configuration.getAllArtifacts(), equalTo(Collections.<PublishArtifact>emptySet()));
     }
 
     @Test
     public void removeArtifactWithUnknownArtifact() {
         PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        configuration.addArtifact(artifact);
-        configuration.removeArtifact(HelperUtil.createPublishArtifact("name2", "ext1", "type1", "classifier1"));
+        configuration.getArtifacts().add(artifact);
+        configuration.getArtifacts().remove(HelperUtil.createPublishArtifact("name2", "ext1", "type1", "classifier1"));
         assertThat(configuration.getAllArtifacts(), equalTo(WrapUtil.toSet(artifact)));
     }
 
@@ -672,27 +727,6 @@ public class DefaultConfigurationTest {
     }
 
     @Test
-    public void getConfiguration() {
-        Dependency configurationDependency = HelperUtil.createDependency("group1", "name1", "version1");
-        Dependency otherConfSimilarDependency = HelperUtil.createDependency("group1", "name1", "version1");
-        Dependency otherConfDependency = HelperUtil.createDependency("group2", "name2", "version2");
-        Configuration otherConf = createNamedConfiguration("otherConf");
-        configuration.extendsFrom(otherConf);
-        otherConf.addDependency(otherConfDependency);
-        otherConf.addDependency(otherConfSimilarDependency);
-        configuration.addDependency(configurationDependency);
-
-        assertThat((DefaultConfiguration) configuration.getConfiguration(configurationDependency), equalTo(configuration));
-        assertThat((DefaultConfiguration) configuration.getConfiguration(otherConfSimilarDependency), equalTo(configuration));
-        assertThat(configuration.getConfiguration(otherConfDependency), equalTo(otherConf));
-    }
-
-    @Test
-    public void getConfigurationWithUnknownDependency() {
-        assertThat(configuration.getConfiguration(HelperUtil.createDependency("group1", "name1", "version1")), equalTo(null));
-    }
-
-    @Test
     public void copy() {
         prepareConfigurationForCopyTest();
 
@@ -705,7 +739,7 @@ public class DefaultConfigurationTest {
     public void copyWithSpec() {
         prepareConfigurationForCopyTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
 
         Configuration copiedConfiguration = configuration.copy(new Spec<Dependency>() {
             public boolean isSatisfiedBy(Dependency element) {
@@ -720,7 +754,7 @@ public class DefaultConfigurationTest {
     public void copyWithClosure() {
         prepareConfigurationForCopyTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
 
         Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
         Configuration copiedConfiguration = configuration.copy(specClosure);
@@ -732,12 +766,12 @@ public class DefaultConfigurationTest {
         configuration.setVisible(false);
         configuration.setTransitive(false);
         configuration.setDescription("descript");
-        configuration.exclude(toMap("org", "value"));
-        configuration.exclude(toMap("org2", "value2"));
-        configuration.addArtifact(HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1"));
-        configuration.addArtifact(HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2"));
-        configuration.addDependency(HelperUtil.createDependency("group1", "name1", "version1"));
-        configuration.addDependency(HelperUtil.createDependency("group2", "name2", "version2"));
+        configuration.exclude(toMap("group", "value"));
+        configuration.exclude(toMap("group", "value2"));
+        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1"));
+        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group1", "name1", "version1"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group2", "name2", "version2"));
     }
 
     private void assertThatCopiedConfigurationHasElementsAndName(Configuration copiedConfiguration, Set<Dependency> expectedDependencies) {
@@ -745,10 +779,10 @@ public class DefaultConfigurationTest {
         assertThat(copiedConfiguration.isVisible(), equalTo(configuration.isVisible()));
         assertThat(copiedConfiguration.isTransitive(), equalTo(configuration.isTransitive()));
         assertThat(copiedConfiguration.getDescription(), equalTo(configuration.getDescription()));
-        assertThat(copiedConfiguration.getAllArtifacts(), equalTo(configuration.getAllArtifacts()));
+        assertThat(asSet(copiedConfiguration.getAllArtifacts()), equalTo(asSet(configuration.getAllArtifacts())));
         assertThat(copiedConfiguration.getExcludeRules(), equalTo(configuration.getExcludeRules()));
         assertThat(copiedConfiguration.getExcludeRules().iterator().next(), not(sameInstance(configuration.getExcludeRules().iterator().next())));
-        assertThat(copiedConfiguration.getDependencies(), equalTo(expectedDependencies));
+        assertThat(WrapUtil.asSet(copiedConfiguration.getDependencies()), equalTo(WrapUtil.asSet(expectedDependencies)));
         assertNotSameInstances(copiedConfiguration.getDependencies(), expectedDependencies);
     }
 
@@ -765,7 +799,7 @@ public class DefaultConfigurationTest {
     public void copyRecursiveWithSpec() {
         prepareConfigurationForCopyRecursiveTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
 
         Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
         Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
@@ -777,7 +811,7 @@ public class DefaultConfigurationTest {
     public void copyRecursiveWithClosure() {
         prepareConfigurationForCopyRecursiveTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
 
         Configuration copiedConfiguration = configuration.copyRecursive(new Spec<Dependency>() {
             public boolean isSatisfiedBy(Dependency element) {
@@ -793,8 +827,8 @@ public class DefaultConfigurationTest {
         Dependency similarDependency2InOtherConf = HelperUtil.createDependency("group2", "name2", "version2");
         Dependency otherConfDependency = HelperUtil.createDependency("group4", "name4", "version4");
         Configuration otherConf = createNamedConfiguration("otherConf");
-        otherConf.addDependency(similarDependency2InOtherConf);
-        otherConf.addDependency(otherConfDependency);
+        otherConf.getDependencies().add(similarDependency2InOtherConf);
+        otherConf.getDependencies().add(otherConfDependency);
         configuration.extendsFrom(otherConf);
     }
 
@@ -814,52 +848,6 @@ public class DefaultConfigurationTest {
     }
 
     @Test
-    public void allDependencies() {
-        DefaultExternalModuleDependency dependency1 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group1", "name", "version");
-        configuration.addDependency(dependency1);
-        configuration.allDependencies(new Action<Dependency>() {
-            public void execute(Dependency dependency) {
-             ((DefaultExternalModuleDependency) dependency).setForce(true);
-            }
-        });
-        configuration.allDependencies(HelperUtil.toClosure(new TestClosure() {
-            public Object call(Object param) {
-                return ((DefaultExternalModuleDependency) param).setChanging(true);
-            }
-        }));
-        DefaultExternalModuleDependency dependency2 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group2", "name2", "version2");
-        configuration.addDependency(dependency2);
-        
-        assertThat(dependency1.isForce(), equalTo(true));
-        assertThat(dependency1.isForce(), equalTo(true));
-        assertThat(dependency2.isChanging(), equalTo(true));
-        assertThat(dependency2.isChanging(), equalTo(true));
-    }
-
-    @Test
-    public void whenDependencyAdded() {
-        DefaultExternalModuleDependency dependency1 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group1", "name", "version");
-        configuration.addDependency(dependency1);
-        configuration.whenDependencyAdded(new Action<Dependency>() {
-            public void execute(Dependency dependency) {
-             ((DefaultExternalModuleDependency) dependency).setForce(true);
-            }
-        });
-        configuration.whenDependencyAdded(HelperUtil.toClosure(new TestClosure() {
-            public Object call(Object param) {
-                return ((DefaultExternalModuleDependency) param).setChanging(true);
-            }
-        }));
-        DefaultExternalModuleDependency dependency2 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group2", "name2", "version2");
-        configuration.addDependency(dependency2);
-
-        assertThat(dependency1.isForce(), equalTo(false));
-        assertThat(dependency1.isForce(), equalTo(false));
-        assertThat(dependency2.isChanging(), equalTo(true));
-        assertThat(dependency2.isChanging(), equalTo(true));
-    }
-
-    @Test
     public void propertyChangeWithNonUnresolvedStateShouldThrowEx() {
         makeResolveReturnFileSet(new HashSet<File>());
         configuration.resolve();
@@ -890,12 +878,12 @@ public class DefaultConfigurationTest {
         });
         assertInvalidUserDataException(new Executer() {
             public void execute() {
-                configuration.addArtifact(context.mock(PublishArtifact.class));
+                configuration.getDependencies().add(context.mock(Dependency.class));
             }
         });
         assertInvalidUserDataException(new Executer() {
             public void execute() {
-                configuration.addDependency(context.mock(Dependency.class));
+                configuration.getDependencies().add(context.mock(Dependency.class));
             }
         });
         assertInvalidUserDataException(new Executer() {
@@ -910,9 +898,47 @@ public class DefaultConfigurationTest {
         });
         assertInvalidUserDataException(new Executer() {
             public void execute() {
-                configuration.removeArtifact(context.mock(PublishArtifact.class, "removeeArtifact"));
+                configuration.getArtifacts().add(context.mock(PublishArtifact.class));
             }
         });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getArtifacts().remove(context.mock(PublishArtifact.class, "removeArtifact"));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.getArtifacts().add(context.mock(PublishArtifact.class, "removeArtifact"));
+            }
+        });
+    }
+    
+    @Test
+    public void dumpString() {
+        Dependency configurationDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
+        Dependency otherConfSimilarDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
+        Dependency otherConfDependency = HelperUtil.createDependency("dumpgroup2", "dumpname2", "dumpversion2");
+        Configuration otherConf = createNamedConfiguration("dumpConf");
+        configuration.extendsFrom(otherConf);
+        otherConf.getDependencies().add(otherConfDependency);
+        otherConf.getDependencies().add(otherConfSimilarDependency);
+        configuration.getDependencies().add(configurationDependency);
+
+        assertThat(configuration.dump(),
+                containsString(
+                "\nConfiguration:"
+                + "  class='class org.gradle.api.internal.artifacts.configurations.DefaultConfiguration'"
+                + "  name='name'"
+                + "  hashcode='"+ configuration.hashCode() +"'"
+                + "\nLocal Dependencies:"
+                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
+                + "\nLocal Artifacts:"
+                + "\n   none"
+                + "\nAll Dependencies:"
+                + "\n   DefaultExternalModuleDependency{group='dumpgroup1', name='dumpname1', version='dumpversion1', configuration='default'}"
+                + "\n   DefaultExternalModuleDependency{group='dumpgroup2', name='dumpname2', version='dumpversion2', configuration='default'}"
+                + "\nAll Artifacts:"
+                + "\n   none"));
     }
 
     private void assertInvalidUserDataException(Executer executer) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategyTest.groovy
new file mode 100644
index 0000000..5660b2d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultResolutionStrategyTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.configurations;
+
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/2/11
+ */
+public class DefaultResolutionStrategyTest extends Specification {
+
+    def strategy = new DefaultResolutionStrategy()
+
+    def "allows setting forced modules"() {
+        expect:
+        strategy.forcedModules.empty
+
+        when:
+        strategy.force 'org.foo:bar:1.0', 'org.foo:baz:2.0'
+
+        then:
+        def versions = strategy.forcedModules as List
+        versions.size() == 2
+
+        versions[0].group == 'org.foo'
+        versions[0].name == 'bar'
+        versions[0].version == '1.0'
+
+        versions[1].group == 'org.foo'
+        versions[1].name == 'baz'
+        versions[1].version == '2.0'
+    }
+
+    def "allows replacing forced modules"() {
+        given:
+        strategy.force 'org.foo:bar:1.0'
+
+        when:
+        strategy.forcedModules = ['hello:world:1.0', [group:'g', name:'n', version:'1']]
+
+        then:
+        def versions = strategy.forcedModules as List
+        versions.size() == 2
+        versions[0].group == 'hello'
+        versions[1].group == 'g'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParserTest.groovy
new file mode 100644
index 0000000..840da8f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ForcedModuleNotationParserTest.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2011 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.configurations;
+
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/14/11
+ */
+public class ForcedModuleNotationParserTest extends Specification {
+
+    def "understands group:name:version notation"() {
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation("org.foo:bar:1.0") as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '1.0'
+    }
+
+    def "works with CharSequences"() {
+        when:
+        def sb = new StringBuilder().append("org.foo:charsequence:1.0")
+        def v = new ForcedModuleNotationParser().parseNotation(sb) as List
+
+        then:
+        v.size() == 1
+        v[0].name  == 'charsequence'
+    }
+
+    def "allows exact type on input"() {
+        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
+
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation(id) as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '2.0'
+    }
+
+    def "allows list of objects on input"() {
+        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
+
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation([id, ["hey:man:1.0"], [group:'i', name:'like', version:'maps']]) as List
+
+        then:
+        v.size() == 3
+        v[0].name == 'bar'
+        v[1].name == 'man'
+        v[2].name == 'like'
+    }
+
+    def "allows map on input"() {
+        when:
+        def v = new ForcedModuleNotationParser().parseNotation([group: 'org.foo', name: 'bar', version:'1.0']) as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '1.0'
+    }
+
+    def "fails for unknown types"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation(new Object())
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports missing keys for map notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation([name: "bar", version: "1.0"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports wrong keys for map notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation([groop: 'groop', name: "bar", version: "1.0"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports invalid format for string notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation(["blahblah"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports invalid missing data for string notation"() {
+        when:
+        new ForcedModuleNotationParser().parseNotation([":foo:"])
+
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains 'cannot be empty'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicySpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicySpec.groovy
new file mode 100644
index 0000000..05d54fd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/dynamicversion/DefaultCachePolicySpec.groovy
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2011 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.configurations.dynamicversion;
+
+
+import java.util.concurrent.TimeUnit
+import org.gradle.api.Action
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ResolvedModuleVersion
+import org.gradle.api.artifacts.cache.ArtifactResolutionControl
+import org.gradle.api.artifacts.cache.DependencyResolutionControl
+import org.gradle.api.artifacts.cache.ModuleResolutionControl
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.DefaultArtifactIdentifier
+
+public class DefaultCachePolicySpec extends Specification {
+    private static final int SECOND = 1000;
+    private static final int MINUTE = SECOND * 60;
+    private static final int HOUR = MINUTE * 60;
+    private static final int DAY = HOUR * 24;
+    private static final int WEEK = DAY * 7;
+    private static final int FOREVER = Integer.MAX_VALUE
+
+    DefaultCachePolicy cachePolicy = new DefaultCachePolicy()
+
+    def "will cache default"() {
+        expect:
+        hasDynamicVersionTimeout(DAY)
+        hasChangingModuleTimeout(DAY)
+        hasModuleTimeout(FOREVER)
+        hasMissingArtifactTimeout(DAY)
+        hasMissingModuleTimeout(DAY)
+    }
+
+    def "uses changing module timeout for changing modules"() {
+        when:
+        cachePolicy.cacheChangingModulesFor(10, TimeUnit.SECONDS);
+
+        then:
+        hasDynamicVersionTimeout(DAY);
+        hasChangingModuleTimeout(10 * SECOND)
+        hasModuleTimeout(FOREVER)
+        hasMissingModuleTimeout(DAY)
+        hasMissingArtifactTimeout(DAY)
+    }
+
+    def "uses dynamic version timeout for dynamic versions"() {
+        when:
+        cachePolicy.cacheDynamicVersionsFor(10, TimeUnit.SECONDS)
+
+        then:
+        hasDynamicVersionTimeout(10 * SECOND)
+        hasChangingModuleTimeout(DAY)
+        hasMissingModuleTimeout(DAY)
+        hasMissingArtifactTimeout(DAY)
+        hasModuleTimeout(FOREVER)
+    }
+
+    def "applies invalidate rule for dynamic versions"() {
+        when:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                t.refresh()
+            }
+        })
+
+        then:
+        cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
+    }
+
+    def "applies useCachedResult for dynamic versions"() {
+        when:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                t.useCachedResult()
+            }
+        })
+
+        then:
+        !cachePolicy.mustRefreshDynamicVersion(null, null, 2 * SECOND)
+    }
+
+    def "applies cacheFor rules for dynamic versions"() {
+        when:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                t.cacheFor(100, TimeUnit.SECONDS)
+            }
+        })
+
+        then:
+        hasDynamicVersionTimeout(100 * SECOND)
+    }
+    
+    def "provides details of cached dynamic version"() {
+        expect:
+        cachePolicy.eachDependency(new Action<DependencyResolutionControl>() {
+            void execute(DependencyResolutionControl t) {
+                assertId(t.request, 'g', 'n', 'v')
+                assertId(t.cachedResult, 'group', 'name', 'version')
+                t.refresh()
+            }
+        })
+        cachePolicy.mustRefreshDynamicVersion(moduleSelector('g', 'n', 'v'), moduleIdentifier('group', 'name', 'version'), 0)
+    }
+    
+    def "provides details of cached module"() {
+        expect:
+        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
+            void execute(ModuleResolutionControl t) {
+                assertId(t.request, 'g', 'n', 'v')
+                assertId(t.cachedResult.id, 'group', 'name', 'version')
+                assert !t.changing
+                t.refresh()
+            }
+        })
+        cachePolicy.mustRefreshModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), 0)
+    }
+    
+    def "provides details of cached changing module"() {
+        expect:
+        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
+            void execute(ModuleResolutionControl t) {
+                assertId(t.request, 'g', 'n', 'v')
+                assertId(t.cachedResult.id, 'group', 'name', 'version')
+                assert t.changing
+                t.refresh()
+            }
+        })
+        cachePolicy.mustRefreshChangingModule(moduleIdentifier('g', 'n', 'v'), moduleVersion('group', 'name', 'version'), 0)
+    }
+    
+    def "provides details of cached artifact"() {
+        expect:
+        cachePolicy.eachArtifact(new Action<ArtifactResolutionControl>() {
+            void execute(ArtifactResolutionControl t) {
+                assertId(t.request.moduleVersionIdentifier, 'group', 'name', 'version')
+                assert t.request.name == 'artifact'
+                assert t.request.type == 'type'
+                assert t.request.extension == 'ext'
+                assert t.request.classifier == 'classifier'
+                assert t.cachedResult == null
+                t.refresh()
+            }
+        })
+        def artifactIdentifier = new DefaultArtifactIdentifier(moduleIdentifier('group', 'name', 'version'), 'artifact', 'type', 'ext', 'classifier')
+        cachePolicy.mustRefreshArtifact(artifactIdentifier, null, 0)
+    }
+    
+    def "can use cacheFor to control missing module and artifact timeout"() {
+        when:
+        cachePolicy.eachModule(new Action<ModuleResolutionControl>() {
+            void execute(ModuleResolutionControl t) {
+                if (t.cachedResult == null) {
+                    t.cacheFor(10, TimeUnit.SECONDS)
+                }
+            }
+        });
+        cachePolicy.eachArtifact(new Action<ArtifactResolutionControl>() {
+            void execute(ArtifactResolutionControl t) {
+                if (t.cachedResult == null) {
+                    t.cacheFor(20, TimeUnit.SECONDS)
+                }
+            }
+        });
+
+        then:
+        hasDynamicVersionTimeout(DAY)
+        hasChangingModuleTimeout(DAY)
+        hasModuleTimeout(FOREVER)
+        hasMissingModuleTimeout(10 * SECOND)
+        hasMissingArtifactTimeout(20 * SECOND)
+    }
+    
+    private def hasDynamicVersionTimeout(int timeout) {
+        def moduleId = moduleIdentifier('group', 'name', 'version')
+        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, 100)
+        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout);
+        assert !cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout - 1)
+        cachePolicy.mustRefreshDynamicVersion(null, moduleId, timeout + 1)
+    }
+
+    private def hasChangingModuleTimeout(int timeout) {
+        def module = moduleVersion('group', 'name', 'version')
+        assert !cachePolicy.mustRefreshChangingModule(null, module, timeout - 1)
+        assert !cachePolicy.mustRefreshChangingModule(null, module, timeout);
+        cachePolicy.mustRefreshChangingModule(null, module, timeout + 1)
+    }
+
+    private def hasModuleTimeout(int timeout) {
+        def module = moduleVersion('group', 'name', 'version')
+        assert !cachePolicy.mustRefreshModule(null, module, timeout);
+        assert !cachePolicy.mustRefreshModule(null, module, timeout - 1)
+        if (timeout == FOREVER) {
+            return true
+        }
+        cachePolicy.mustRefreshModule(null, module, timeout + 1)
+    }
+
+    private def hasMissingModuleTimeout(int timeout) {
+        assert !cachePolicy.mustRefreshModule(null, null, timeout);
+        assert !cachePolicy.mustRefreshModule(null, null, timeout - 1)
+        cachePolicy.mustRefreshModule(null, null, timeout + 1)
+    }
+
+    private def hasMissingArtifactTimeout(int timeout) {
+        assert !cachePolicy.mustRefreshArtifact(null, null, timeout);
+        assert !cachePolicy.mustRefreshArtifact(null, null, timeout - 1)
+        cachePolicy.mustRefreshArtifact(null, null, timeout + 1)
+    }
+    
+    private def assertId(def moduleId, String group, String name , String version) {
+        assert moduleId.group == group
+        assert moduleId.name == name
+        assert moduleId.version == version
+    }
+    
+    private def moduleSelector(String group, String name, String version) {
+        new DefaultModuleVersionIdentifier(group, name, version)
+    }
+
+    private def moduleIdentifier(String group, String name, String version) {
+        new DefaultModuleVersionIdentifier(group, name, version)
+    }
+
+    private def moduleVersion(String group, String name, String version) {
+        return new ResolvedModuleVersion() {
+            ModuleVersionIdentifier getId() {
+                return new DefaultModuleVersionIdentifier(group, name, version);
+            }
+        }
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
index a90a71d..7dffb32 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
@@ -63,15 +63,15 @@ abstract public class AbstractModuleDependencyTest {
 
     @Test
     public void exclude() {
-        Map<String, String> excludeArgs1 = WrapUtil.toMap("key", "value");
-        Map<String, String> excludeArgs2 = WrapUtil.toMap("key2", "value2");
+        Map<String, String> excludeArgs1 = WrapUtil.toMap("group", "aGroup");
+        Map<String, String> excludeArgs2 = WrapUtil.toMap("module", "aModule");
 
         getDependency().exclude(excludeArgs1);
         getDependency().exclude(excludeArgs2);
 
         assertThat(getDependency().getExcludeRules().size(), equalTo(2));
-        assertThat(getDependency().getExcludeRules(), hasItem((ExcludeRule) new DefaultExcludeRule(excludeArgs1)));
-        assertThat(getDependency().getExcludeRules(), hasItem((ExcludeRule) new DefaultExcludeRule(excludeArgs2)));
+        assertThat(getDependency().getExcludeRules(), hasItem((ExcludeRule) new DefaultExcludeRule("aGroup", null)));
+        assertThat(getDependency().getExcludeRules(), hasItem((ExcludeRule) new DefaultExcludeRule(null, "aModule")));
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExcludeRuleContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExcludeRuleContainerTest.java
deleted file mode 100644
index dcd5fa7..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExcludeRuleContainerTest.java
+++ /dev/null
@@ -1,72 +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.dependencies;
-
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.api.internal.artifacts.DefaultExcludeRuleContainer;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultExcludeRuleContainerTest {
-    @Test
-    public void testInit() {
-        assertThat(new DefaultExcludeRuleContainer().getRules().size(), equalTo(0));
-    }
-
-    @Test
-    public void testInitWithRules() {
-        Set<ExcludeRule> sourceExcludeRules = new HashSet<ExcludeRule>();
-        sourceExcludeRules.add(new DefaultExcludeRule(WrapUtil.toMap("key", "value")));
-        DefaultExcludeRuleContainer defaultExcludeRuleContainer = new DefaultExcludeRuleContainer(sourceExcludeRules);
-        assertThat(defaultExcludeRuleContainer.getRules(), equalTo(sourceExcludeRules));
-        assertThat(defaultExcludeRuleContainer.getRules(), not(sameInstance(sourceExcludeRules)));
-    }
-
-    @Test
-    public void testAdd() {
-        DefaultExcludeRuleContainer excludeRuleContainer = new DefaultExcludeRuleContainer();
-        Map excludeRuleArgs1 = WrapUtil.toMap("key1", "value1");
-        Map excludeRuleArgs2 = WrapUtil.toMap("key2", "value2");
-        excludeRuleContainer.add(excludeRuleArgs1);
-        excludeRuleContainer.add(excludeRuleArgs2);
-        assertThat(excludeRuleContainer.getRules().size(), equalTo(2));
-        assertExcludeRuleContainerHasCorrectExcludeRules(excludeRuleContainer.getRules(), excludeRuleArgs1, excludeRuleArgs2);
-    }
-
-    private void assertExcludeRuleContainerHasCorrectExcludeRules(Set<ExcludeRule> excludeRules, Map... excludeRuleArgs) {
-        Set<Map> foundRules = new HashSet<Map>();
-        for (ExcludeRule excludeRule : excludeRules) {
-            for (Map excludeRuleArg : excludeRuleArgs) {
-                if (excludeRule.getExcludeArgs().equals(excludeRuleArg)) {
-                    foundRules.add(excludeRuleArg);
-                    continue;
-                }
-            }
-        }
-    }
-
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
index 2103efc..d7458bf 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
@@ -18,16 +18,16 @@ package org.gradle.api.internal.artifacts.dependencies;
 
 import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
 import org.gradle.util.HelperUtil;
-import static org.hamcrest.Matchers.equalTo;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertThat;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
 
 /**
  * @author Hans Dockter
@@ -38,8 +38,6 @@ public class DefaultExternalModuleDependencyTest extends AbstractModuleDependenc
     private static final String TEST_NAME = "gradle-core";
     private static final String TEST_VERSION = "4.4-beta2";
 
-    protected DependencyDescriptorFactory dependencyDescriptorFactoryMock;
-
     protected DefaultDependencyDescriptor expectedDependencyDescriptor;
 
     private DefaultExternalModuleDependency moduleDependency;
@@ -59,7 +57,6 @@ public class DefaultExternalModuleDependencyTest extends AbstractModuleDependenc
     @Before public void setUp() {
         moduleDependency = new DefaultExternalModuleDependency(TEST_GROUP, TEST_NAME, TEST_VERSION);
         context.setImposteriser(ClassImposteriser.INSTANCE);
-        dependencyDescriptorFactoryMock = context.mock(DependencyDescriptorFactory.class);
         expectedDependencyDescriptor = HelperUtil.getTestDescriptor();
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
index 5e28a14..87916ae 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
@@ -16,11 +16,12 @@
 
 package org.gradle.api.internal.artifacts.dependencies;
 
-import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.*;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
 import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.api.tasks.TaskDependency;
@@ -34,6 +35,7 @@ import java.util.Set;
 
 import static org.gradle.util.Matchers.isEmpty;
 import static org.gradle.util.Matchers.strictlyEqual;
+import static org.gradle.util.WrapUtil.toList;
 import static org.gradle.util.WrapUtil.toSet;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
@@ -44,9 +46,9 @@ import static org.junit.Assert.*;
 @RunWith(JMock.class)
 public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
     private final ProjectDependenciesBuildInstruction instruction = new ProjectDependenciesBuildInstruction(true);
-    private final Project dependencyProjectStub = context.mock(Project.class);
-    private final ConfigurationContainer projectConfigurationsStub = context.mock(ConfigurationContainer.class);
-    private final Configuration projectConfigurationStub = context.mock(Configuration.class);
+    private final ProjectInternal dependencyProjectStub = context.mock(ProjectInternal.class);
+    private final ConfigurationContainerInternal projectConfigurationsStub = context.mock(ConfigurationContainerInternal.class);
+    private final ConfigurationInternal projectConfigurationStub = context.mock(ConfigurationInternal.class);
     private final TaskContainer dependencyProjectTaskContainerStub = context.mock(TaskContainer.class);
     private final DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, instruction);
 
@@ -103,7 +105,7 @@ public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
         }});
 
         DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction);
-        assertThat(projectDependency.getProjectConfiguration(), sameInstance(projectConfigurationStub));
+        assertThat(projectDependency.getProjectConfiguration(), sameInstance((Configuration) projectConfigurationStub));
     }
 
     @Test
@@ -111,12 +113,16 @@ public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
         final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
         final Dependency projectSelfResolvingDependency = context.mock(Dependency.class);
         final ProjectDependency transitiveProjectDependencyStub = context.mock(ProjectDependency.class);
+        final DependencySet dependencies = context.mock(DependencySet.class);
         context.checking(new Expectations() {{
             allowing(projectConfigurationsStub).getByName("conf1");
             will(returnValue(projectConfigurationStub));
 
             allowing(projectConfigurationStub).getAllDependencies();
-            will(returnValue(toSet(projectSelfResolvingDependency, transitiveProjectDependencyStub)));
+            will(returnValue(dependencies));
+
+            allowing(dependencies).iterator();
+            will(returnIterator(toList(projectSelfResolvingDependency, transitiveProjectDependencyStub)));
 
             allowing(resolveContext).isTransitive();
             will(returnValue(true));
@@ -185,28 +191,17 @@ public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
 
     private void expectTargetConfigurationHasArtifacts(final Task... tasks) {
         context.checking(new Expectations(){{
+            PublishArtifactSet artifacts = context.mock(PublishArtifactSet.class);
             TaskDependency dependencyStub = context.mock(TaskDependency.class, "artifacts");
 
-            allowing(projectConfigurationStub).getBuildArtifacts();
-            will(returnValue(dependencyStub));
-
-            allowing(dependencyStub).getDependencies(null);
-            will(returnValue(toSet(tasks)));
-        }});
-    }
+            allowing(projectConfigurationStub).getAllArtifacts();
+            will(returnValue(artifacts));
 
-    private void expectTargetConfigurationHasNoDependencies() {
-        context.checking(new Expectations() {{
-            TaskDependency dependencyStub = context.mock(TaskDependency.class);
-
-            allowing(projectConfigurationStub).getBuildDependencies();
-            will(returnValue(dependencyStub));
-
-            allowing(projectConfigurationStub).getBuildArtifacts();
+            allowing(artifacts).getBuildDependencies();
             will(returnValue(dependencyStub));
 
             allowing(dependencyStub).getDependencies(null);
-            will(returnValue(toSet()));
+            will(returnValue(toSet(tasks)));
         }});
     }
 
@@ -255,7 +250,7 @@ public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
                 dependencyProjectStub, "conf1", instruction)));
         assertThat(new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction), not(equalTo(new DefaultProjectDependency(
                 dependencyProjectStub, "conf2", instruction))));
-        Project otherProject = context.mock(Project.class, "otherProject");
+        ProjectInternal otherProject = context.mock(ProjectInternal.class, "otherProject");
         assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), not(equalTo(new DefaultProjectDependency(
                 otherProject, instruction))));
         assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), not(equalTo(new DefaultProjectDependency(
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
deleted file mode 100644
index c081631..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
+++ /dev/null
@@ -1,97 +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.dsl
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.util.JUnit4GroovyMockery
-import spock.lang.Specification
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-
-/**
- * @author Hans Dockter
- */
-class DefaultArtifactHandlerTest extends Specification {
-
-    private static final String TEST_CONF_NAME = "someConf"
-
-    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
-    private ConfigurationContainer configurationContainerStub = Mock()
-    private PublishArtifactFactory artifactFactoryStub = Mock()
-    private Configuration configurationMock = Mock()
-
-    private DefaultArtifactHandler artifactHandler = new DefaultArtifactHandler(configurationContainerStub, artifactFactoryStub)
-
-    void setup() {
-        configurationContainerStub.findByName(TEST_CONF_NAME) >> configurationMock
-    }
-
-    void pushOneDependency() {
-        String someNotation = "someNotation"
-        PublishArtifact artifactDummy = Mock()
-
-        when:
-        artifactFactoryStub.createArtifact(someNotation) >> artifactDummy
-        artifactHandler."$TEST_CONF_NAME"(someNotation)
-
-        then:
-        1 * configurationMock.addArtifact(artifactDummy)
-    }
-
-    void pushOneDependencyWithClosure() {
-        String someNotation = "someNotation"
-        DefaultPublishArtifact artifact = new DefaultPublishArtifact("name", "ext", "jar", "classifier", null, new File(""))
-
-        when:
-        artifactFactoryStub.createArtifact(someNotation) >> artifact
-        artifactHandler."$TEST_CONF_NAME"(someNotation) { type = 'source' }
-
-        then:
-        artifact.type == 'source'
-        1 * configurationMock.addArtifact(artifact)
-    }
-
-    void pushMultipleDependencies() {
-        String someNotation1 = "someNotation"
-        String someNotation2 = "someNotation2"
-        PublishArtifact artifactDummy1 = Mock()
-        PublishArtifact artifactDummy2 = Mock()
-
-        when:
-        artifactFactoryStub.createArtifact(someNotation1) >> artifactDummy1
-        artifactFactoryStub.createArtifact(someNotation2) >> artifactDummy2
-        artifactHandler."$TEST_CONF_NAME"(someNotation1, someNotation2)
-
-        then:
-        1 * configurationMock.addArtifact(artifactDummy1)
-        1 * configurationMock.addArtifact(artifactDummy2)
-
-    }
-
-    void pushToUnknownConfiguration() {
-        String unknownConf = TEST_CONF_NAME + "delta"
-
-        when:
-        artifactHandler."$unknownConf"("someNotation")
-        configurationContainerStub.findByName(unknownConf) >> null
-
-        then:
-        thrown(MissingMethodException)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultConfigurationHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultConfigurationHandlerTest.groovy
deleted file mode 100644
index 378b772..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultConfigurationHandlerTest.groovy
+++ /dev/null
@@ -1,103 +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.api.internal.artifacts.dsl
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.UnknownConfigurationException
-import org.gradle.api.internal.artifacts.IvyService
-import org.gradle.util.JUnit4GroovyMockery
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
-import org.gradle.api.internal.ClassGenerator
-import org.gradle.api.internal.AsmBackedClassGenerator
-import org.gradle.api.internal.DomainObjectContext
-
-/**
- * @author Hans Dockter
- */
-
-class DefaultConfigurationHandlerTest {
-    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
-    private IvyService ivyService = context.mock(IvyService)
-    private DomainObjectContext domainObjectContext = context.mock(DomainObjectContext.class)
-
-    private ClassGenerator classGenerator = new AsmBackedClassGenerator()
-    private DefaultConfigurationContainer configurationHandler = classGenerator.newInstance(DefaultConfigurationContainer.class, ivyService, classGenerator, { name -> name } as DomainObjectContext)
-
-    @Test
-    void addsNewConfigurationWhenConfiguringSelf() {
-        configurationHandler.configure {
-            newConf
-        }
-        assertThat(configurationHandler.findByName('newConf'), notNullValue())
-        assertThat(configurationHandler.newConf, notNullValue())
-    }
-
-    @Test(expected = UnknownConfigurationException)
-    void doesNotAddNewConfigurationWhenNotConfiguringSelf() {
-        configurationHandler.getByName('unknown')
-    }
-
-    @Test
-    void makesExistingConfigurationAvailableAsProperty() {
-        Configuration configuration = configurationHandler.add('newConf')
-        assertThat(configuration, is(not(null)))
-        assertThat(configurationHandler.getByName("newConf"), sameInstance(configuration))
-        assertThat(configurationHandler.newConf, sameInstance(configuration))
-    }
-
-    @Test
-    void addsNewConfigurationWithClosureWhenConfiguringSelf() {
-        String someDesc = 'desc1'
-        configurationHandler.configure {
-            newConf {
-                description = someDesc
-            }
-        }
-        assertThat(configurationHandler.newConf.getDescription(), equalTo(someDesc))
-    }
-
-    @Test
-    void makesExistingConfigurationAvailableAsConfigureMethod() {
-        String someDesc = 'desc1'
-        configurationHandler.add('newConf')
-        Configuration configuration = configurationHandler.newConf {
-            description = someDesc
-        }
-        assertThat(configuration.getDescription(), equalTo(someDesc))
-    }
-
-    @Test
-    void makesExistingConfigurationAvailableAsConfigureMethodWhenConfiguringSelf() {
-        String someDesc = 'desc1'
-        Configuration configuration = configurationHandler.add('newConf')
-        configurationHandler.configure {
-            newConf {
-                description = someDesc
-            }
-        }
-        assertThat(configuration.getDescription(), equalTo(someDesc))
-    }
-
-    @Test(expected = MissingMethodException)
-    void newConfigurationWithNonClosureParametersShouldThrowMissingMethodEx() {
-        configurationHandler.newConf('a', 'b')
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
deleted file mode 100644
index 0e7c459..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
+++ /dev/null
@@ -1,59 +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.dsl;
-
-
-import java.awt.Point
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
-import org.gradle.api.tasks.bundling.AbstractArchiveTask
-import org.hamcrest.Matchers
-import spock.lang.Specification
-import static org.junit.Assert.assertThat
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPublishArtifactFactoryTest extends Specification {
-    private DefaultPublishArtifactFactory publishArtifactFactory = new DefaultPublishArtifactFactory();
-
-    def createArtifact() {
-        AbstractArchiveTask archiveTaskMock = Mock()
-        archiveTaskMock.getArchivePath() >> new File("")
-
-        when:
-        ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) publishArtifactFactory.createArtifact(archiveTaskMock);
-
-        then:
-        assertThat(publishArtifact.getArchiveTask(), Matchers.sameInstance(archiveTaskMock));
-    }
-
-    public void createArtifactWithNullNotationShouldThrowInvalidUserDataEx() {
-        when:
-        publishArtifactFactory.createArtifact(null);
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    public void createArtifactWithUnknownNotationShouldThrowInvalidUserDataEx() {
-        when:
-        publishArtifactFactory.createArtifact(new Point(1,2));
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactoryTest.java
deleted file mode 100644
index a923434..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactoryTest.java
+++ /dev/null
@@ -1,57 +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.dsl;
-
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultRepositoryHandlerFactoryTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    private ResolverFactory repositoryFactoryMock = context.mock(ResolverFactory.class);
-    private ClassGenerator classGeneratorMock = context.mock(ClassGenerator.class);
-
-    @Test
-    public void createsARepositoryHandler() {
-        final RepositoryHandler repositoryHandlerMock = context.mock(DefaultRepositoryHandler.class);
-
-        context.checking(new Expectations() {{
-            one(classGeneratorMock).newInstance(DefaultRepositoryHandler.class, repositoryFactoryMock, classGeneratorMock);
-            will(returnValue(repositoryHandlerMock));
-        }});
-
-        DefaultRepositoryHandlerFactory repositoryHandlerFactory = new DefaultRepositoryHandlerFactory(
-                repositoryFactoryMock, classGeneratorMock);
-        DefaultRepositoryHandler repositoryHandler = repositoryHandlerFactory.create();
-        assertThat(repositoryHandler, sameInstance((Object) repositoryHandlerMock));
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
index 8182c35..9f067e3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
@@ -16,86 +16,119 @@
 
 package org.gradle.api.internal.artifacts.dsl
 
-import org.apache.ivy.plugins.resolver.ResolverSettings
-//import org.apache.maven.artifact.ant.RemoteRepository
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.artifacts.ResolverContainer
-import org.gradle.api.artifacts.dsl.RepositoryHandler
-import org.gradle.api.artifacts.maven.GroovyMavenDeployer
-import org.gradle.api.artifacts.maven.MavenResolver
-import org.gradle.api.internal.artifacts.DefaultResolverContainerTest
-import org.gradle.util.HashUtil
-import org.junit.Test
-import static org.junit.Assert.*
-import org.gradle.api.internal.AsmBackedClassGenerator
-import org.gradle.api.artifacts.dsl.IvyArtifactRepository
+import org.apache.ivy.plugins.resolver.DependencyResolver
 import org.gradle.api.Action
-import org.junit.runner.RunWith
-import org.jmock.integration.junit4.JMock
+import org.gradle.api.artifacts.ArtifactRepositoryContainer
+import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainerTest
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
-import org.apache.ivy.plugins.resolver.DependencyResolver
-import org.hamcrest.Matchers
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.assertEquals
 
 /**
  * @author Hans Dockter
  */
 @RunWith(JMock)
-class DefaultRepositoryHandlerTest extends DefaultResolverContainerTest {
-    static final String TEST_REPO_URL = 'http://www.gradle.org'
-
+class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTest {
     private DefaultRepositoryHandler repositoryHandler
 
-    public ResolverContainer createResolverContainer() {
-        AsmBackedClassGenerator classGenerator = new AsmBackedClassGenerator()
-        repositoryHandler = classGenerator.newInstance(DefaultRepositoryHandler.class, resolverFactoryMock, classGenerator);
+    public ArtifactRepositoryContainer createResolverContainer() {
+        repositoryHandler = new DefaultRepositoryHandler(resolverFactoryMock, new DirectInstantiator());
         return repositoryHandler;
     }
 
+    @Test public void testFlatDirWithClosure() {
+        def repository = context.mock(FlatDirectoryArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createFlatDirRepository(); will(returnValue(repository))
+            one(repository).setName('libs')
+            allowing(repository).getName(); will(returnValue('libs'))
+        }
+
+        assert repositoryHandler.flatDir { name = 'libs' }.is(repository)
+    }
+    
     @Test public void testFlatDirWithNameAndDirs() {
-        String resolverName = 'libs'
-        prepareFlatDirResolverCreation(resolverName, createFlatDirTestDirs())
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.flatDir([name: resolverName] + [dirs: createFlatDirTestDirsArgs()]).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.getResolvers())
+        def repository = context.mock(FlatDirectoryArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createFlatDirRepository(); will(returnValue(repository))
+            one(repository).setDirs(['a', 'b'])
+            one(repository).setName('libs')
+            allowing(repository).getName(); will(returnValue('libs'))
+        }
+
+        assert repositoryHandler.flatDir([name: 'libs'] + [dirs: ['a', 'b']]).is(repository)
     }
 
     @Test public void testFlatDirWithNameAndSingleDir() {
-        String resolverName = 'libs'
-        prepareFlatDirResolverCreation(resolverName, ['a' as File] as File[])
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.flatDir([name: resolverName] + [dirs: 'a']).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.getResolvers())
+        def repository = context.mock(FlatDirectoryArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createFlatDirRepository(); will(returnValue(repository))
+            one(repository).setDirs(['a'])
+            one(repository).setName('libs')
+            allowing(repository).getName(); will(returnValue('libs'))
+        }
+
+        assert repositoryHandler.flatDir([name: 'libs'] + [dirs: 'a']).is(repository)
     }
 
     @Test public void testFlatDirWithoutNameAndWithDirs() {
-        Object[] expectedDirs = createFlatDirTestDirs()
-        String expectedName = HashUtil.createHash(expectedDirs.join(''))
-        prepareFlatDirResolverCreation(expectedName, expectedDirs)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.flatDir([dirs: createFlatDirTestDirsArgs()]).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.getResolvers())
-    }
+        def repository = context.mock(FlatDirectoryArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createFlatDirRepository(); will(returnValue(repository))
+            one(repository).setDirs(['a', 12])
+            one(repository).getName(); will(returnValue(null))
+            one(repository).setName('flatDir')
+            allowing(repository).getName(); will(returnValue('flatDir'))
+        }
 
-    @Test(expected = InvalidUserDataException)
-    public void testFlatDirWithMissingDirs() {
-        repositoryHandler.flatDir([name: 'someName'])
+        assert repositoryHandler.flatDir([dirs: ['a', 12]]).is(repository)
     }
 
     @Test
     public void testMavenCentralWithNoArgs() {
-        prepareCreateMavenRepo(ResolverContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME, ResolverContainer.MAVEN_CENTRAL_URL)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenCentral().is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
+        MavenArtifactRepository repository = context.mock(MavenArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createMavenCentralRepository()
+            will(returnValue(repository))
+            one(repository).getName()
+            will(returnValue(null))
+            one(repository).setName(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME)
+            allowing(repository).getName()
+            will(returnValue(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME))
+        }
+
+        assert repositoryHandler.mavenCentral().is(repository)
     }
 
     @Test
     public void testMavenCentralWithSingleUrl() {
         String testUrl2 = 'http://www.gradle2.org'
-        prepareCreateMavenRepo(ResolverContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME, ResolverContainer.MAVEN_CENTRAL_URL, testUrl2)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenCentral(urls: testUrl2).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
+
+        MavenArtifactRepository repository = context.mock(MavenArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createMavenCentralRepository()
+            will(returnValue(repository))
+            one(repository).getName()
+            will(returnValue(null))
+            one(repository).setName(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME)
+            allowing(repository).getName()
+            will(returnValue(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME))
+            one(repository).setArtifactUrls([testUrl2])
+        }
+
+        assert repositoryHandler.mavenCentral(artifactUrls: [testUrl2]).is(repository)
     }
 
     @Test
@@ -103,36 +136,59 @@ class DefaultRepositoryHandlerTest extends DefaultResolverContainerTest {
         String testUrl1 = 'http://www.gradle1.org'
         String testUrl2 = 'http://www.gradle2.org'
         String name = 'customName'
-        prepareCreateMavenRepo(name, ResolverContainer.MAVEN_CENTRAL_URL, testUrl1, testUrl2)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenCentral(name: name, urls: [testUrl1, testUrl2]).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
+
+        MavenArtifactRepository repository = context.mock(MavenArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createMavenCentralRepository()
+            will(returnValue(repository))
+            one(repository).setName('customName')
+            allowing(repository).getName()
+            will(returnValue('customName'))
+            one(repository).setArtifactUrls([testUrl1, testUrl2])
+        }
+
+        assert repositoryHandler.mavenCentral(name: name, artifactUrls: [testUrl1, testUrl2]).is(repository)
     }
 
     @Test
     public void testMavenLocalWithNoArgs() {
+        MavenArtifactRepository repository = context.mock(MavenArtifactRepository)
+
         context.checking {
-            one(resolverFactoryMock).createMavenLocalResolver(ResolverContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME)
-            will(returnValue(expectedResolver))
+            one(resolverFactoryMock).createMavenLocalRepository()
+            will(returnValue(repository))
+            one(repository).getName()
+            will(returnValue(null))
+            one(repository).setName(ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME)
+            allowing(repository).getName()
+            will(returnValue(ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME))
         }
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenLocal().is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
-    }
 
-    @Test(expected = InvalidUserDataException)
-    public void testMavenRepoWithMissingUrls() {
-        repositoryHandler.mavenRepo([name: 'someName'])
+        assert repositoryHandler.mavenLocal() == repository
     }
 
     @Test
     public void testMavenRepoWithNameAndUrls() {
+        String testUrl1 = 'http://www.gradle1.org'
         String testUrl2 = 'http://www.gradle2.org'
         String repoRoot = 'http://www.reporoot.org'
         String repoName = 'mavenRepoName'
-        prepareCreateMavenRepo(repoName, repoRoot, testUrl2)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenRepo([name: repoName, urls: [repoRoot, testUrl2]]).is(expectedResolver)
+
+        TestMavenArtifactRepository repository = context.mock(TestMavenArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createMavenRepository()
+            will(returnValue(repository))
+            one(repository).setName(repoName)
+            allowing(repository).getName()
+            will(returnValue(repoName))
+            one(repository).setUrl(repoRoot)
+            one(repository).setArtifactUrls([testUrl1, testUrl2])
+            allowing(repository).createResolver(); will(returnValue(expectedResolver))
+        }
+
+        assert repositoryHandler.mavenRepo([name: repoName, url: repoRoot, artifactUrls: [testUrl1, testUrl2]]).is(expectedResolver)
         assertEquals([expectedResolver], repositoryHandler.resolvers)
     }
 
@@ -140,9 +196,20 @@ class DefaultRepositoryHandlerTest extends DefaultResolverContainerTest {
     public void testMavenRepoWithNameAndRootUrlOnly() {
         String repoRoot = 'http://www.reporoot.org'
         String repoName = 'mavenRepoName'
-        prepareCreateMavenRepo(repoName, repoRoot)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenRepo([name: repoName, urls: repoRoot]).is(expectedResolver)
+
+        TestMavenArtifactRepository repository = context.mock(TestMavenArtifactRepository)
+
+        context.checking {
+            one(resolverFactoryMock).createMavenRepository()
+            will(returnValue(repository))
+            one(repository).setName(repoName)
+            allowing(repository).getName()
+            will(returnValue(repoName))
+            one(repository).setUrl(repoRoot)
+            allowing(repository).createResolver(); will(returnValue(expectedResolver))
+        }
+
+        assert repositoryHandler.mavenRepo([name: repoName, url: repoRoot]).is(expectedResolver)
         assertEquals([expectedResolver], repositoryHandler.resolvers)
     }
 
@@ -150,180 +217,147 @@ class DefaultRepositoryHandlerTest extends DefaultResolverContainerTest {
     public void testMavenRepoWithoutName() {
         String testUrl2 = 'http://www.gradle2.org'
         String repoRoot = 'http://www.reporoot.org'
-        prepareCreateMavenRepo(repoRoot, repoRoot, testUrl2)
-        prepareResolverFactoryToTakeAndReturnExpectedResolver()
-        assert repositoryHandler.mavenRepo([urls: [repoRoot, testUrl2]]).is(expectedResolver)
-        assertEquals([expectedResolver], repositoryHandler.resolvers)
-    }
 
-    private prepareCreateMavenRepo(String name, String mavenUrl, String[] jarUrls) {
+        TestMavenArtifactRepository repository = context.mock(TestMavenArtifactRepository)
+
         context.checking {
-            one(resolverFactoryMock).createMavenRepoResolver(name, mavenUrl, jarUrls);
-            will(returnValue(expectedResolver))
+            one(resolverFactoryMock).createMavenRepository()
+            will(returnValue(repository))
+            allowing(repository).getName()
+            will(returnValue(null))
+            one(repository).setUrl(repoRoot)
+            one(repository).setArtifactUrls([testUrl2])
+            allowing(repository).createResolver(); will(returnValue(expectedResolver))
         }
+
+        assert repositoryHandler.mavenRepo([url: repoRoot, artifactUrls: [testUrl2]]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
     }
 
-    private def prepareFlatDirResolverCreation(String expectedName, File[] expectedDirs) {
+    @Test
+    public void createIvyRepositoryUsingClosure() {
+        IvyArtifactRepository repository = context.mock(IvyArtifactRepository.class)
+
         context.checking {
-            one(resolverFactoryMock).createFlatDirResolver(expectedName, expectedDirs); will(returnValue(expectedResolver))
+            one(resolverFactoryMock).createIvyRepository()
+            will(returnValue(repository))
+            allowing(repository).getName()
+            will(returnValue("name"))
         }
-    }
 
-    private List createFlatDirTestDirsArgs() {
-        return ['a', 'b' as File]
-    }
+        def arg
+        def result = repositoryHandler.ivy {
+            arg = it
+        }
 
-    private File[] createFlatDirTestDirs() {
-        return ['a' as File, 'b' as File] as File[]
+        assert arg == repository
+        assert result == repository
     }
 
-    private def prepareResolverFactoryToTakeAndReturnExpectedResolver() {
+    @Test
+    public void createIvyRepositoryUsingAction() {
+        IvyArtifactRepository repository = context.mock(IvyArtifactRepository.class)
+        Action<IvyArtifactRepository> action = context.mock(Action.class)
+
         context.checking {
-            one(resolverFactoryMock).createResolver(expectedResolver); will(returnValue(expectedResolver))
+            one(resolverFactoryMock).createIvyRepository()
+            will(returnValue(repository))
+            one(action).execute(repository)
+            allowing(repository).getName()
+            will(returnValue("name"))
         }
-    }
 
-    @Test
-    public void mavenDeployerWithoutName() {
-        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
-        String expectedName = RepositoryHandler.DEFAULT_MAVEN_DEPLOYER_NAME + "-" +
-                System.identityHashCode(expectedResolver)
-        prepareName(expectedResolver, expectedName)
-        assertSame(expectedResolver, repositoryHandler.mavenDeployer());
+        def result = repositoryHandler.ivy(action)
+        assert result == repository
     }
 
     @Test
-    public void mavenDeployerWithName() {
-        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
-        String expectedName = "someName"
-        prepareName(expectedResolver, expectedName)
-        assertSame(expectedResolver, repositoryHandler.mavenDeployer(name: expectedName));
-    }
+    public void providesADefaultNameForIvyRepository() {
+        IvyArtifactRepository repository1 = context.mock(IvyArtifactRepository.class)
 
-//    @Test
-//    public void mavenDeployerWithNameAndClosure() {
-//        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
-//        String expectedName = RepositoryHandler.DEFAULT_MAVEN_DEPLOYER_NAME + "-" +
-//                System.identityHashCode(expectedResolver)
-//        prepareName(expectedResolver, expectedName)
-//        RemoteRepository repositoryDummy = new RemoteRepository()
-//        context.checking {
-//            one(expectedResolver).setRepository(repositoryDummy)
-//        }
-//        assertSame(expectedResolver, repositoryHandler.mavenDeployer() {
-//            setRepository(repositoryDummy)
-//        });
-//    }
-//
-//    @Test
-//    public void mavenDeployerWithoutArgsAndWithClosure() {
-//        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
-//        String expectedName = "someName"
-//        prepareName(expectedResolver, expectedName)
-//        RemoteRepository repositoryDummy = new RemoteRepository()
-//        context.checking {
-//            one(expectedResolver).setRepository(repositoryDummy)
-//        }
-//        assertSame(expectedResolver, repositoryHandler.mavenDeployer(name: expectedName) {
-//            setRepository(repositoryDummy)
-//        });
-//    }
+        context.checking {
+            one(resolverFactoryMock).createIvyRepository()
+            will(returnValue(repository1))
+            one(repository1).getName()
+            will(returnValue(null))
+            one(repository1).setName("ivy")
+            allowing(repository1).getName()
+            will(returnValue("ivy"))
+        }
 
-    @Test
-    public void mavenInstallerWithoutName() {
-        MavenResolver expectedResolver = prepareMavenInstallerTests()
-        String expectedName = RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME + "-" +
-                System.identityHashCode(expectedResolver)
-        prepareName(expectedResolver, expectedName)
-        assertSame(expectedResolver, repositoryHandler.mavenInstaller());
-    }
+        repositoryHandler.ivy { }
 
-    @Test
-    public void mavenInstallerWithName() {
-        MavenResolver expectedResolver = prepareMavenInstallerTests()
-        String expectedName = "someName"
-        prepareName(expectedResolver, expectedName)
-        assertSame(expectedResolver, repositoryHandler.mavenInstaller(name: expectedName));
-    }
+        IvyArtifactRepository repository2 = context.mock(IvyArtifactRepository.class)
 
-    @Test
-    public void mavenInstallerWithNameAndClosure() {
-        MavenResolver expectedResolver = prepareMavenInstallerTests()
-        String expectedName = RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME + "-" +
-                System.identityHashCode(expectedResolver)
-        prepareName(expectedResolver, expectedName)
-        ResolverSettings resolverSettings = [:] as ResolverSettings
         context.checking {
-            one(expectedResolver).setSettings(resolverSettings)
+            one(resolverFactoryMock).createIvyRepository()
+            will(returnValue(repository2))
+            allowing(repository2).getName()
+            will(returnValue("ivy2"))
         }
-        assertSame(expectedResolver, repositoryHandler.mavenInstaller() {
-            setSettings(resolverSettings)
-        });
-    }
 
-    @Test
-    public void mavenInstallerWithoutArgsAndWithClosure() {
-        MavenResolver expectedResolver = prepareMavenInstallerTests()
-        String expectedName = "someName"
-        prepareName(expectedResolver, expectedName)
-        ResolverSettings resolverSettings = [:] as ResolverSettings
+        repositoryHandler.ivy { }
+
+        IvyArtifactRepository repository3 = context.mock(IvyArtifactRepository.class)
+
         context.checking {
-            one(expectedResolver).setSettings(resolverSettings)
+            one(resolverFactoryMock).createIvyRepository()
+            will(returnValue(repository3))
+            one(repository3).getName()
+            will(returnValue(null))
+            one(repository3).setName("ivy3")
+            allowing(repository3).getName()
+            will(returnValue("ivy3"))
         }
-        assertSame(expectedResolver, repositoryHandler.mavenInstaller(name: expectedName) {
-            setSettings(resolverSettings)
-        });
+
+        repositoryHandler.ivy { }
     }
 
     @Test
-    public void createIvyRepositoryUsingClosure() {
-        IvyArtifactRepository repository = context.mock(TestIvyArtifactRepository.class)
-        DependencyResolver resolver = context.mock(DependencyResolver.class)
+    public void createMavenRepositoryUsingClosure() {
+        MavenArtifactRepository repository = context.mock(TestMavenArtifactRepository.class)
 
         context.checking {
-            one(resolverFactoryMock).createIvyRepository()
+            one(resolverFactoryMock).createMavenRepository()
             will(returnValue(repository))
-            one(repository).createResolvers(withParam(Matchers.notNullValue()))
-            will { arg -> arg << resolver }
-            one(resolverFactoryMock).createResolver(resolver)
-            will(returnValue(resolver))
-            allowing(resolver).getName()
+            allowing(repository).getName()
             will(returnValue("name"))
         }
 
         def arg
-        def result = repositoryHandler.ivy {
+        def result = repositoryHandler.maven {
             arg = it
         }
 
         assert arg == repository
         assert result == repository
-        assert repositoryHandler.resolvers.contains(resolver)
     }
 
     @Test
-    public void createIvyRepositoryUsingAction() {
-        IvyArtifactRepository repository = context.mock(TestIvyArtifactRepository.class)
-        Action<IvyArtifactRepository> action = context.mock(Action.class)
-        DependencyResolver resolver = context.mock(DependencyResolver.class)
+    public void createMavenRepositoryUsingAction() {
+        MavenArtifactRepository repository = context.mock(TestMavenArtifactRepository.class)
+        Action<MavenArtifactRepository> action = context.mock(Action.class)
 
         context.checking {
-            one(resolverFactoryMock).createIvyRepository()
+            one(resolverFactoryMock).createMavenRepository()
             will(returnValue(repository))
             one(action).execute(repository)
-            one(repository).createResolvers(withParam(Matchers.notNullValue()))
-            will { arg -> arg << resolver }
-            one(resolverFactoryMock).createResolver(resolver)
-            will(returnValue(resolver))
-            allowing(resolver).getName()
+            allowing(repository).getName()
             will(returnValue("name"))
         }
 
-        def result = repositoryHandler.ivy(action)
+        def result = repositoryHandler.maven(action)
         assert result == repository
-        assert repositoryHandler.resolvers.contains(resolver)
     }
-    
+
+    private DependencyResolver resolver(String name = 'name') {
+        DependencyResolver resolver = context.mock(DependencyResolver.class)
+        context.checking {
+            allowing(resolver).getName(); will(returnValue(name))
+        }
+        return resolver
+    }
+
     private void prepareName(mavenResolver, String expectedName) {
         context.checking {
             one(mavenResolver).setName(expectedName)
@@ -331,6 +365,8 @@ class DefaultRepositoryHandlerTest extends DefaultResolverContainerTest {
     }
 }
 
-interface TestIvyArtifactRepository extends IvyArtifactRepository, ArtifactRepositoryInternal {
+interface TestMavenArtifactRepository extends MavenArtifactRepository, ArtifactRepositoryInternal {
+}
 
-}
\ No newline at end of file
+interface TestFlatDirectoryArtifactRepository extends FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/AbstractModuleFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/AbstractModuleFactoryTest.java
deleted file mode 100644
index 30f7c24..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/AbstractModuleFactoryTest.java
+++ /dev/null
@@ -1,170 +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.dsl.dependencies;
-
-import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.GUtil;
-import org.junit.Test;
-import static org.junit.Assert.*;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.hamcrest.Matchers.nullValue;
-import static org.hamcrest.Matchers.*;
-
-import java.awt.*;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractModuleFactoryTest {
-    protected static final String TEST_GROUP = "org.gradle";
-    protected static final String TEST_NAME = "gradle-core";
-    protected static final String TEST_VERSION = "4.4-beta2";
-    protected static final String TEST_CONFIGURATION = "testConf";
-    protected static final String TEST_TYPE = "mytype";
-    protected static final String TEST_CLASSIFIER = "jdk-1.4";
-    protected static final String TEST_MODULE_DESCRIPTOR = String.format("%s:%s:%s", TEST_GROUP, TEST_NAME, TEST_VERSION);
-    protected static final String TEST_MODULE_DESCRIPTOR_WITH_CLASSIFIER = TEST_MODULE_DESCRIPTOR + ":" + TEST_CLASSIFIER;
-
-    protected abstract ExternalDependency createDependency(Object notation);
-
-    @Test(expected = IllegalDependencyNotation.class)
-    public void testStringNotationWithOneElementStringShouldThrowInvalidUserDataEx() {
-        createDependency("singlestring");
-    }
-
-    @Test(expected = IllegalDependencyNotation.class)
-    public void testUnknownTypeShouldThrowInvalidUserDataEx() {
-        createDependency(new Point(3, 4));
-    }
-
-    @Test
-    public void testStringNotationWithGString() {
-        checkCommonModuleProperties(createDependency(HelperUtil.createScript(
-                 "descriptor = '" + TEST_MODULE_DESCRIPTOR + "'; \"$descriptor\"").run()));
-    }
-
-    @Test
-    public void testStringNotationWithModule() {
-        ExternalDependency moduleDependency = createDependency(TEST_MODULE_DESCRIPTOR);
-        checkCommonModuleProperties(moduleDependency);
-        assertTrue(moduleDependency.isTransitive());
-    }
-
-    @Test
-    public void testStringNotationWithNoGroup() {
-        ExternalDependency moduleDependency = createDependency(
-                String.format(":%s:%s", TEST_NAME, TEST_VERSION));
-        checkName(moduleDependency);
-        checkVersion(moduleDependency);
-        assertThat(moduleDependency.getGroup(), nullValue());
-        assertTrue(moduleDependency.isTransitive());
-    }
-
-    @Test
-    public void testStringNotationWithNoVersion() {
-        ExternalDependency moduleDependency = createDependency(
-                String.format("%s:%s", TEST_GROUP, TEST_NAME));
-        checkGroup(moduleDependency);
-        checkName(moduleDependency);
-        assertThat(moduleDependency.getVersion(), nullValue());
-        assertTrue(moduleDependency.isTransitive());
-    }
-
-    @Test
-    public void testStringNotationWithNoVersionAndNoGroup() {
-        ExternalDependency moduleDependency = createDependency(
-                String.format(":%s", TEST_NAME));
-        checkName(moduleDependency);
-        assertThat(moduleDependency.getGroup(), nullValue());
-        assertThat(moduleDependency.getVersion(), nullValue());
-        assertTrue(moduleDependency.isTransitive());
-    }
-
-    @Test
-    public void testStringNotationWithModuleAndClassifier() {
-        ExternalDependency moduleDependency = createDependency(TEST_MODULE_DESCRIPTOR_WITH_CLASSIFIER);
-        assertCorrectnesForModuleWithClassifier(moduleDependency);
-    }
-
-    @Test
-    public void testMapNotationWithModuleAndClassifier() {
-        ExternalDependency moduleDependency = createDependency(
-                GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION, "classifier", TEST_CLASSIFIER));
-        assertCorrectnesForModuleWithClassifier(moduleDependency);
-    }
-
-    private void assertCorrectnesForModuleWithClassifier(ExternalDependency moduleDependency) {
-        checkCommonModuleProperties(moduleDependency);
-        assertTrue(moduleDependency.isTransitive());
-        assertEquals(1, moduleDependency.getArtifacts().size());
-        DependencyArtifact artifact = moduleDependency.getArtifacts().iterator().next();
-        assertEquals(TEST_NAME, artifact.getName());
-        assertEquals(DependencyArtifact.DEFAULT_TYPE, artifact.getType());
-        assertEquals(DependencyArtifact.DEFAULT_TYPE, artifact.getExtension());
-        assertEquals(TEST_CLASSIFIER, artifact.getClassifier());
-    }
-
-    @Test
-    public void mapNotation() {
-        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION));
-        checkCommonModuleProperties(moduleDependency);
-        assertTrue(moduleDependency.isTransitive());
-    }
-
-    @Test
-    public void mapNotationWithConfiguration() {
-        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION,
-                "configuration", TEST_CONFIGURATION));
-        checkCommonModuleProperties(moduleDependency);
-        assertTrue(moduleDependency.isTransitive());
-        assertThat(moduleDependency.getConfiguration(), equalTo(TEST_CONFIGURATION));
-    }
-
-    @Test
-    public void mapNotationWithProperty() {
-        ExternalDependency moduleDependency = createDependency(
-                GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION, "transitive", false));
-        checkCommonModuleProperties(moduleDependency);
-        assertThat(moduleDependency.isTransitive(), equalTo(false));
-    }
-
-    protected void checkCommonModuleProperties(ExternalDependency moduleDependency) {
-        checkGroup(moduleDependency);
-        checkName(moduleDependency);
-        checkVersion(moduleDependency);
-        checkOtherProperties(moduleDependency);
-    }
-
-    protected void checkOtherProperties(ExternalDependency moduleDependency) {
-        assertFalse(moduleDependency.isForce());
-    }
-
-    private void checkVersion(ExternalDependency moduleDependency) {
-        assertEquals(TEST_VERSION, moduleDependency.getVersion());
-    }
-
-    private void checkName(ExternalDependency moduleDependency) {
-        assertEquals(TEST_NAME, moduleDependency.getName());
-    }
-
-    private void checkGroup(ExternalDependency moduleDependency) {
-        assertEquals(TEST_GROUP, moduleDependency.getGroup());
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactoryTest.groovy
deleted file mode 100644
index 840ce1d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactoryTest.groovy
+++ /dev/null
@@ -1,63 +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.api.internal.artifacts.dsl.dependencies
-
-
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.runner.RunWith
-import org.junit.Test
-import org.gradle.api.internal.ClassGenerator
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
-import org.gradle.api.internal.ClassPathRegistry
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.file.FileCollection
-import org.gradle.api.artifacts.Dependency
-
- at RunWith(JMock.class)
-public class ClassPathDependencyFactoryTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final ClassGenerator classGenerator = context.mock(ClassGenerator.class)
-    private final ClassPathRegistry classPathRegistry = context.mock(ClassPathRegistry.class)
-    private final FileResolver fileResolver = context.mock(FileResolver.class)
-    private final ClassPathDependencyFactory factory = new ClassPathDependencyFactory(classGenerator, classPathRegistry, fileResolver)
-
-    @Test
-    void createsDependencyForAClassPathNotation() {
-        Dependency dependency = context.mock(Dependency.class)
-
-        context.checking {
-            Set files = []
-            FileCollection fileCollection = context.mock(FileCollection.class)
-
-            one(classPathRegistry).getClassPathFiles('GRADLE_API')
-            will(returnValue(files))
-
-            one(fileResolver).resolveFiles(withParam(hasItemInArray(files)))
-            will(returnValue(fileCollection))
-
-            one(classGenerator).newInstance(DefaultSelfResolvingDependency.class, fileCollection)
-            will(returnValue(dependency))
-        }
-
-        assertThat(factory.createDependency(Dependency.class, DependencyFactory.ClassPathNotation.GRADLE_API), sameInstance(dependency))
-    }
-}
-
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactoryTest.java
deleted file mode 100644
index be81460..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactoryTest.java
+++ /dev/null
@@ -1,30 +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.dsl.dependencies;
-
-import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.internal.AsmBackedClassGenerator;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultClientModuleFactoryTest extends AbstractModuleFactoryTest {
-    private DefaultClientModuleFactory clientModuleFactory = new DefaultClientModuleFactory(new AsmBackedClassGenerator());
-
-    protected ExternalDependency createDependency(Object notation) {
-        return clientModuleFactory.createDependency(ExternalDependency.class, notation);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactoryTest.java
deleted file mode 100644
index 0b872b1..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactoryTest.java
+++ /dev/null
@@ -1,168 +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.dsl.dependencies;
-
-import groovy.lang.Closure;
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.awt.*;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultDependencyFactoryTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private IDependencyImplementationFactory testImplPointFactoryStub = context.mock(IDependencyImplementationFactory.class, "Point");
-    private DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(
-            WrapUtil.toSet(testImplPointFactoryStub), null, null);
-
-    @Test
-    public void testCreateDependencyWithValidDescription() {
-        final Point point = createAnonymousPoint();
-        final Dependency pointDependencyDummy = context.mock(Dependency.class, "PointDependency");
-        context.checking(new Expectations() {{
-            allowing(testImplPointFactoryStub).createDependency(Dependency.class, point);
-            will(returnValue(pointDependencyDummy));
-        }});
-        assertSame(pointDependencyDummy, dependencyFactory.createDependency(point));
-    }
-
-    @Test
-    public void createDependencyWithDependencyObject() {
-        final Dependency dependencyDummy = context.mock(Dependency.class);
-        assertSame(dependencyDummy, dependencyFactory.createDependency(dependencyDummy));    
-    }
-
-    @Test
-    public void testCreateDependencyWithValidDescriptionAndClosure() {
-        final Point point = createAnonymousPoint();
-        final Dependency pointDependencyMock = context.mock(Dependency.class, "PointDependency");
-        context.checking(new Expectations() {{
-            allowing(testImplPointFactoryStub).createDependency(Dependency.class, point);
-            will(returnValue(pointDependencyMock));
-        }});
-        assertSame(pointDependencyMock, dependencyFactory.createDependency(point));
-    }
-
-    private Point createAnonymousPoint() {
-        return new Point(5, 4);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void testCreateDependencyWithInvalidDescriptionShouldThrowInvalidUserDataEx() {
-        final IDependencyImplementationFactory testImplStringFactoryStub = context.mock(IDependencyImplementationFactory.class, "String");
-        context.checking(new Expectations() {{
-            allowing(testImplPointFactoryStub).createDependency(with(equalTo(Dependency.class)), with(not(instanceOf(Point.class))));
-            will(throwException(new IllegalDependencyNotation()));
-            allowing(testImplStringFactoryStub).createDependency(with(equalTo(Dependency.class)), with(not(instanceOf(String.class))));
-            will(throwException(new IllegalDependencyNotation()));
-        }});
-        dependencyFactory.createDependency(createAnonymousInteger());
-    }
-
-    private Integer createAnonymousInteger() {
-        return new Integer(5);
-    }
-
-    @Test
-    public void createProject() {
-        final ProjectDependencyFactory projectDependencyFactoryStub = context.mock(ProjectDependencyFactory.class);
-        final ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-        final ProjectFinder projectFinderDummy = context.mock(ProjectFinder.class);
-        DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(null, null, projectDependencyFactoryStub);
-        final Map map = WrapUtil.toMap("key", "value");
-        context.checking(new Expectations() {{
-            allowing(projectDependencyFactoryStub).createProjectDependencyFromMap(projectFinderDummy, map);
-            will(returnValue(projectDependency));
-        }});
-        Closure configureClosure = HelperUtil.toClosure("{ transitive = false }");
-        assertThat(dependencyFactory.createProjectDependencyFromMap(projectFinderDummy, map), sameInstance(projectDependency));
-    }
-
-    @Test
-    public void createModule() {
-        final IDependencyImplementationFactory testImplStringFactoryStub = context.mock(IDependencyImplementationFactory.class, "String");
-        final IDependencyImplementationFactory clientModuleFactoryStub = context.mock(IDependencyImplementationFactory.class);
-        final ClientModule clientModuleMock = context.mock(ClientModule.class);
-        DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(WrapUtil.toSet(testImplStringFactoryStub), clientModuleFactoryStub, null);
-        final String someNotation1 = "someNotation1";
-        final String someNotation2 = "someNotation2";
-        final String someNotation3 = "someNotation3";
-        final String someNotation4 = "someNotation4";
-        final String someModuleNotation = "junit:junit:4.4";
-        final ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
-        final ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
-        final ModuleDependency dependencyDummy3 = context.mock(ModuleDependency.class, "dep3");
-        final ModuleDependency dependencyMock = context.mock(ModuleDependency.class, "dep4");
-        context.checking(new Expectations() {{
-            allowing(clientModuleFactoryStub).createDependency(ClientModule.class, someModuleNotation);
-            will(returnValue(clientModuleMock));
-            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation1);
-            will(returnValue(dependencyDummy1));
-            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation2);
-            will(returnValue(dependencyDummy2));
-            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation3);
-            will(returnValue(dependencyDummy3));
-            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation4);
-            will(returnValue(dependencyMock));
-            one(dependencyMock).setTransitive(true);
-            one(clientModuleMock).addDependency(dependencyDummy1);
-            one(clientModuleMock).addDependency(dependencyDummy2);
-            one(clientModuleMock).addDependency(dependencyDummy3);
-            one(clientModuleMock).addDependency(dependencyMock);
-        }});
-        Closure configureClosure = HelperUtil.toClosure(String.format(
-                "{dependency('%s'); dependencies('%s', '%s'); dependency('%s') { transitive = true }}",
-                someNotation1, someNotation2, someNotation3, someNotation4));
-        assertThat(dependencyFactory.createModule(someModuleNotation, configureClosure), equalTo(clientModuleMock));
-    }
-
-    @Test
-    public void createModuleWithNullClosure() {
-        final IDependencyImplementationFactory testImplStringFactoryStub = context.mock(IDependencyImplementationFactory.class, "String");
-        final IDependencyImplementationFactory clientModuleFactoryStub = context.mock(IDependencyImplementationFactory.class);
-        final ClientModule clientModuleMock = context.mock(ClientModule.class);
-        DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(WrapUtil.toSet(testImplStringFactoryStub), clientModuleFactoryStub, null);
-
-        final String someModuleNotation = "junit:junit:4.4";
-        context.checking(new Expectations() {{
-            allowing(clientModuleFactoryStub).createDependency(ClientModule.class, someModuleNotation);
-            will(returnValue(clientModuleMock));
-        }});
-        assertThat(dependencyFactory.createModule(someModuleNotation, null), equalTo(clientModuleMock));
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
index d86e097..c58f082 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
@@ -15,246 +15,251 @@
  */
 package org.gradle.api.internal.artifacts.dsl.dependencies
 
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.util.ConfigureUtil
-import org.gradle.util.HelperUtil
-import org.gradle.util.JUnit4GroovyMockery
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
+import spock.lang.Specification
 import org.gradle.api.artifacts.*
-import static org.hamcrest.Matchers.equalTo
-import static org.hamcrest.Matchers.sameInstance
-import static org.junit.Assert.assertThat
 
 /**
  * @author Hans Dockter
  */
- at RunWith (org.jmock.integration.junit4.JMock)
-class DefaultDependencyHandlerTest {
+class DefaultDependencyHandlerTest extends Specification {
     private static final String TEST_CONF_NAME = "someConf"
+    private ConfigurationContainer configurationContainer = Mock()
+    private DependencyFactory dependencyFactory = Mock()
+    private Configuration configuration = Mock()
+    private ProjectFinder projectFinder = Mock()
+    private DependencySet dependencySet = Mock()
+
+    private DefaultDependencyHandler dependencyHandler = new DefaultDependencyHandler(configurationContainer, dependencyFactory, projectFinder)
+
+    void setup() {
+        _ * configurationContainer.findByName(TEST_CONF_NAME) >> configuration
+        _ * configurationContainer.getAt(TEST_CONF_NAME) >> configuration
+        _ * configuration.dependencies >> dependencySet
+    }
 
-    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    void "creates and adds a dependency from some notation"() {
+        Dependency dependency = Mock()
 
-    private ConfigurationContainer configurationContainerStub = context.mock(ConfigurationContainer)
-    private DependencyFactory dependencyFactoryStub = context.mock(DependencyFactory)
-    private Configuration configurationMock = context.mock(Configuration)
-    private ProjectFinder projectFinderDummy = context.mock(ProjectFinder)
+        when:
+        def result = dependencyHandler.add(TEST_CONF_NAME, "someNotation")
 
-    private DefaultDependencyHandler dependencyHandler = new DefaultDependencyHandler(
-            configurationContainerStub, dependencyFactoryStub, projectFinderDummy)
+        then:
+        result == dependency
 
-    @Before
-    void setUp() {
-        context.checking {
-            allowing(configurationContainerStub).findByName(TEST_CONF_NAME); will(returnValue(configurationMock))
-        }
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency
+        1 * dependencySet.add(dependency)
     }
 
-    @Test
-    void add() {
-        String someNotation = "someNotation"
-        Dependency dependencyDummy = context.mock(Dependency)
-        context.checking {
-            allowing(configurationContainerStub).getAt(TEST_CONF_NAME); will(returnValue(configurationMock))
-            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(dependencyDummy))
-            one(configurationMock).addDependency(dependencyDummy);
+    void "creates, configures and adds a dependency from some notation"() {
+        ExternalDependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.add(TEST_CONF_NAME, "someNotation") {
+            force = true
         }
 
-        assertThat(dependencyHandler.add(TEST_CONF_NAME, someNotation), equalTo(dependencyDummy))
+        then:
+        result == dependency
+
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency
+        1 * dependency.setForce(true)
+        1 * dependencySet.add(dependency)
     }
 
-    @Test
-    void addWithClosure() {
-        String someNotation = "someNotation"
-        def closure = { }
-        DefaultExternalModuleDependency returnedDependency = HelperUtil.createDependency("group", "name", "1.0")
-        context.checking {
-            allowing(configurationContainerStub).getAt(TEST_CONF_NAME); will(returnValue(configurationMock))
-            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(returnedDependency))
-            one(configurationMock).addDependency(returnedDependency);
-        }
-        def dependency = dependencyHandler.add(TEST_CONF_NAME, someNotation) {
-            force = true    
-        }
-        assertThat(dependency, equalTo(returnedDependency))
-        assertThat(dependency.force, equalTo(true))
+    void "creates a dependency from some notation"() {
+        Dependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.create("someNotation")
+
+        then:
+        result == dependency
+
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency
     }
 
-    @Test
-    void pushOneDependency() {
-        String someNotation = "someNotation"
-        Dependency dependencyDummy = context.mock(Dependency)
-        context.checking {
-            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(dependencyDummy))
-            one(configurationMock).addDependency(dependencyDummy);
-        }
+    void "creates and configures a dependency from some notation"() {
+        ExternalDependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.create("someNotation") { force = true}
 
-        assertThat(dependencyHandler."$TEST_CONF_NAME"(someNotation), equalTo(dependencyDummy))
+        then:
+        result == dependency
+
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency
+        1 * dependency.setForce(true)
     }
 
-    @Test
-    void pushOneDependencyWithClosure() {
-        String someNotation = "someNotation"
-        DefaultExternalModuleDependency returnedDependency = HelperUtil.createDependency("group", "name", "1.0")
-        context.checking {
-            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(returnedDependency))
-            one(configurationMock).addDependency(returnedDependency);
-        }
+    void "can use dynamic method to add dependency"() {
+        Dependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.someConf("someNotation")
+
+        then:
+        result == dependency
+
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency
+        1 * dependencySet.add(dependency)
 
-        def dependency = dependencyHandler."$TEST_CONF_NAME"(someNotation) {
-            force = true
-        }
-        assertThat(dependency, equalTo(returnedDependency))
-        assertThat(dependency.force, equalTo(true))
     }
 
-    @Test
-    void pushMultipleDependencies() {
-        String someNotation1 = "someNotation"
-        Map someNotation2 = [a: 'b', c: 'd']
-        Dependency dependencyDummy1 = context.mock(Dependency, "dep1")
-        Dependency dependencyDummy2 = context.mock(Dependency, "dep2")
-        context.checking {
-            allowing(dependencyFactoryStub).createDependency(someNotation1); will(returnValue(dependencyDummy1))
-            allowing(dependencyFactoryStub).createDependency(someNotation2); will(returnValue(dependencyDummy2))
-            one(configurationMock).addDependency(dependencyDummy1);
-            one(configurationMock).addDependency(dependencyDummy2);
-        }
+    void "can use dynamic method to add and configure dependency"() {
+        ExternalDependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.someConf("someNotation") { force = true }
+
+        then:
+        result == dependency
 
-        dependencyHandler."$TEST_CONF_NAME"(someNotation1, someNotation2)
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency
+        1 * dependencySet.add(dependency)
+        1 * dependency.setForce(true)
     }
 
-    @Test
-    void pushMultipleDependenciesViaNestedList() {
-        String someNotation1 = "someNotation"
-        Map someNotation2 = [a: 'b', c: 'd']
-        Dependency dependencyDummy1 = context.mock(Dependency, "dep1")
-        Dependency dependencyDummy2 = context.mock(Dependency, "dep2")
-        context.checking {
-            allowing(dependencyFactoryStub).createDependency(someNotation1); will(returnValue(dependencyDummy1))
-            allowing(dependencyFactoryStub).createDependency(someNotation2); will(returnValue(dependencyDummy2))
-            one(configurationMock).addDependency(dependencyDummy1);
-            one(configurationMock).addDependency(dependencyDummy2);
-        }
+    void "can use dynamic method to add multiple dependencies"() {
+        Dependency dependency1 = Mock()
+        Dependency dependency2 = Mock()
+
+        when:
+        def result = dependencyHandler.someConf("someNotation", "someOther")
 
-        dependencyHandler."$TEST_CONF_NAME"([[someNotation1, someNotation2]])
+        then:
+        result == null
+
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency1
+        1 * dependencyFactory.createDependency("someOther") >> dependency2
+        1 * dependencySet.add(dependency1)
+        1 * dependencySet.add(dependency2)
     }
 
-    @Test
-    void pushProjectByMap() {
-        ProjectDependency projectDependency = context.mock(ProjectDependency)
-        Map someMapNotation = [:]
-        Closure projectDependencyClosure = {
-            assertThat("$TEST_CONF_NAME"(project(someMapNotation)), equalTo(projectDependency))
-        }
-        context.checking {
-            allowing(dependencyFactoryStub).createProjectDependencyFromMap(projectFinderDummy, someMapNotation); will(returnValue(projectDependency))
-            allowing(dependencyFactoryStub).createDependency(projectDependency); will(returnValue(projectDependency))
-            one(configurationMock).addDependency(projectDependency);
-        }
+    void "can use dynamic method to add multiple dependencies from nested lists"() {
+        Dependency dependency1 = Mock()
+        Dependency dependency2 = Mock()
+
+        when:
+        def result = dependencyHandler.someConf([["someNotation"], ["someOther"]])
+
+        then:
+        result == null
 
-        ConfigureUtil.configure(projectDependencyClosure, dependencyHandler)
+        and:
+        1 * dependencyFactory.createDependency("someNotation") >> dependency1
+        1 * dependencyFactory.createDependency("someOther") >> dependency2
+        1 * dependencySet.add(dependency1)
+        1 * dependencySet.add(dependency2)
     }
 
-    @Test
-    void pushProjectByMapWithConfigureClosure() {
-        ProjectDependency projectDependency = context.mock(ProjectDependency)
-        Map someMapNotation = [:]
-        Closure projectDependencyClosure = {
-            def dependency = "$TEST_CONF_NAME"(project(someMapNotation)) {
-                copy()    
-            }
-            assertThat(dependency, equalTo(projectDependency))
-        }
-        context.checking {
-            allowing(dependencyFactoryStub).createProjectDependencyFromMap(projectFinderDummy, someMapNotation); will(returnValue(projectDependency))
-            allowing(dependencyFactoryStub).createDependency(projectDependency); will(returnValue(projectDependency))
-            one(configurationMock).addDependency(projectDependency);
-            one(projectDependency).copy();
-        }
+    void "creates a project dependency from map"() {
+        ProjectDependency projectDependency = Mock()
+
+        when:
+        def result = dependencyHandler.project([:])
+
+        then:
+        result == projectDependency
 
-        ConfigureUtil.configure(projectDependencyClosure, dependencyHandler)
+        and:
+        1 * dependencyFactory.createProjectDependencyFromMap(projectFinder, [:]) >> projectDependency
     }
 
-    @Test
-    void pushModule() {
-        ClientModule clientModule = context.mock(ClientModule)
-        String someNotation = "someNotation"
-        Closure moduleClosure = {
-            assertThat("$TEST_CONF_NAME"(module(someNotation)), equalTo(clientModule))
-        }
-        context.checking {
-            allowing(dependencyFactoryStub).createModule(someNotation, null); will(returnValue(clientModule))
-            allowing(dependencyFactoryStub).createDependency(clientModule); will(returnValue(clientModule))
-            one(configurationMock).addDependency(clientModule);
-        }
+    void "attaches configuration from same project to target configuration"() {
+        Configuration other = Mock()
+
+        given:
+        configurationContainer.contains(other) >> true
+
+        when:
+        def result = dependencyHandler.add(TEST_CONF_NAME, other)
 
-        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+        then:
+        result == null
+
+        and:
+        1 * configuration.extendsFrom(other)
     }
 
-    @Test
-    void pushModuleWithConfigureClosure() {
-        ClientModule clientModule = context.mock(ClientModule)
-        String someNotation = "someNotation"
-        Closure configureClosure = {}
-        Closure moduleClosure = {
-            assertThat("$TEST_CONF_NAME"(module(someNotation, configureClosure)), equalTo(clientModule))
-        }
-        context.checking {
-            allowing(dependencyFactoryStub).createModule(someNotation, configureClosure); will(returnValue(clientModule))
-            allowing(dependencyFactoryStub).createDependency(clientModule); will(returnValue(clientModule))
-            one(configurationMock).addDependency(clientModule);
-        }
+    void "cannot create project dependency for configuration from different project"() {
+        Configuration other = Mock()
+
+        given:
+        configurationContainer.contains(other) >> false
 
-        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+        when:
+        dependencyHandler.add(TEST_CONF_NAME, other)
+
+        then:
+        UnsupportedOperationException e = thrown()
+        e.message == 'Currently you can only declare dependencies on configurations from the same project.'
     }
 
-    @Test
-    void pushGradleApi() {
-        Dependency dependency = context.mock(Dependency)
-        context.checking {
-            one(dependencyFactoryStub).createDependency(DependencyFactory.ClassPathNotation.GRADLE_API)
-            will(returnValue(dependency))
+    void "creates client module dependency"() {
+        ClientModule clientModule = Mock()
 
-            one(dependencyFactoryStub).createDependency(dependency)
-            will(returnValue(dependency))
+        when:
+        def result = dependencyHandler.module("someNotation")
 
-            one(configurationMock).addDependency(dependency)
-        }
+        then:
+        result == clientModule
 
-        Closure moduleClosure = {
-            assertThat("$TEST_CONF_NAME"(gradleApi()), sameInstance(dependency))
-        }
-        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+        and:
+        1 * dependencyFactory.createModule("someNotation", null) >> clientModule
     }
 
-    @Test
-    void pushLocalGroovy() {
-        Dependency dependency = context.mock(Dependency)
-        context.checking {
-            one(dependencyFactoryStub).createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY)
-            will(returnValue(dependency))
+    void "creates and configures client module dependency"() {
+        ClientModule clientModule = Mock()
+        Closure cl = {}
 
-            one(dependencyFactoryStub).createDependency(dependency)
-            will(returnValue(dependency))
+        when:
+        def result = dependencyHandler.module("someNotation", cl)
 
-            one(configurationMock).addDependency(dependency)
-        }
+        then:
+        result == clientModule
 
-        Closure moduleClosure = {
-            assertThat("$TEST_CONF_NAME"(localGroovy()), sameInstance(dependency))
-        }
-        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+        and:
+        1 * dependencyFactory.createModule("someNotation", cl) >> clientModule
     }
-    
-    @Test (expected = MissingMethodException)
-    void pushToUnknownConfiguration() {
-        String unknownConf = TEST_CONF_NAME + "delta"
-        context.checking {
-            allowing(configurationContainerStub).findByName(unknownConf); will(returnValue(null))
-        }
-        dependencyHandler."$unknownConf"("someNotation")
+
+    void "creates gradle api dependency"() {
+        Dependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.gradleApi()
+
+        then:
+        result == dependency
+
+        and:
+        1 * dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.GRADLE_API) >> dependency
     }
 
+    void "creates local groovy dependency"() {
+        Dependency dependency = Mock()
+
+        when:
+        def result = dependencyHandler.localGroovy()
+
+        then:
+        result == dependency
+
+        and:
+        1 * dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY) >> dependency
+    }
+
+    void "cannot add dependency to unknown configuration"() {
+        when:
+        dependencyHandler.unknown("someNotation")
+
+        then:
+        thrown(MissingMethodException)
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactoryTest.java
deleted file mode 100644
index 4d8a851..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactoryTest.java
+++ /dev/null
@@ -1,75 +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.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.util.GUtil;
-import org.gradle.util.HelperUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-
-import java.awt.*;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultProjectDependencyFactoryTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private final ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(false);
-    private ProjectDependencyFactory projectDependencyFactory = new DefaultProjectDependencyFactory(projectDependenciesBuildInstruction, new AsmBackedClassGenerator());
-    private ProjectFinder projectFinder = context.mock(ProjectFinder.class);
-
-    @Test
-    public void testCreateProjectDependencyWithProject() {
-        Project dependencyProject = HelperUtil.createRootProject();
-        DefaultProjectDependency projectDependency = (DefaultProjectDependency)
-                projectDependencyFactory.createDependency(Dependency.class, dependencyProject);
-        assertThat(projectDependency.getDependencyProject(), equalTo(dependencyProject));
-    }
-
-    @Test
-    public void testCreateProjectDependencyWithMapNotation() {
-        boolean expectedTransitive = false;
-        final Map<String, Object> mapNotation = GUtil.map("path", ":path", "configuration", "conf", "transitive", expectedTransitive);
-        final ProjectInternal projectDummy = context.mock(ProjectInternal.class);
-        context.checking(new Expectations() {{
-            allowing(projectFinder).getProject((String) mapNotation.get("path"));
-            will(returnValue(projectDummy));
-        }});
-        DefaultProjectDependency projectDependency = (DefaultProjectDependency)
-                projectDependencyFactory.createProjectDependencyFromMap(projectFinder, mapNotation);
-        assertThat((ProjectInternal) projectDependency.getDependencyProject(), equalTo(projectDummy));
-        assertThat(projectDependency.getConfiguration(), equalTo(mapNotation.get("configuration")));
-        assertThat(projectDependency.isTransitive(), equalTo(expectedTransitive));
-    }
-
-    @Test (expected = IllegalDependencyNotation.class)
-    public void testWithUnknownTypeShouldThrowUnknownDependencyNotationEx() {
-        projectDependencyFactory.createDependency(Dependency.class, new Point(3, 4));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactoryTest.java
deleted file mode 100644
index b31edf3..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactoryTest.java
+++ /dev/null
@@ -1,90 +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.dsl.dependencies;
-
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.util.GUtil;
-
-/**
- * @author Hans Dockter
- */
-public class ModuleDependencyFactoryTest extends AbstractModuleFactoryTest {
-    private static final String TEST_ARTIFACT_DESCRIPTOR = TEST_MODULE_DESCRIPTOR + "@" + TEST_TYPE;
-    private static final String TEST_ARTIFACT_DESCRIPTOR_WITH_CLASSIFIER = TEST_MODULE_DESCRIPTOR + String.format(":%s@%s", TEST_CLASSIFIER, TEST_TYPE);
-    
-    private ModuleDependencyFactory moduleDependencyFactory = new ModuleDependencyFactory(new AsmBackedClassGenerator());
-
-    protected ExternalDependency createDependency(Object notation) {
-        return moduleDependencyFactory.createDependency(ExternalDependency.class, notation);
-    }
-
-    protected void checkOtherProperties(ExternalDependency moduleDependency) {
-        super.checkOtherProperties(moduleDependency);
-        assertFalse(((ExternalModuleDependency)moduleDependency).isChanging());
-    }
-
-    @Test
-    public void testStringNotationWithArtifact() {
-        ExternalDependency moduleDependency = createDependency(TEST_ARTIFACT_DESCRIPTOR);
-        assertIsArtifactOnly(moduleDependency);
-    }
-
-    @Test
-    public void testStringNotationWithArtifactAndClassifier() {
-        ExternalDependency moduleDependency = createDependency(TEST_ARTIFACT_DESCRIPTOR_WITH_CLASSIFIER);
-        assertIsArtifactOnlyWithClassifier(moduleDependency);
-    }
-
-    @Test
-    public void testMapNotationWithArtifact() {
-        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION, "ext", TEST_TYPE));
-        assertIsArtifactOnly(moduleDependency);
-    }
-
-    @Test
-    public void testMapNotationWithArtifactAndClassifier() {
-        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version",
-                TEST_VERSION, "ext", TEST_TYPE, "classifier", TEST_CLASSIFIER));
-        assertIsArtifactOnlyWithClassifier(moduleDependency);
-    }
-
-    private void assertIsArtifactOnlyWithClassifier(ExternalDependency moduleDependency) {
-        checkCommonModuleProperties(moduleDependency);
-        assertFalse(moduleDependency.isTransitive());
-        assertEquals(1, moduleDependency.getArtifacts().size());
-        DependencyArtifact artifact = moduleDependency.getArtifacts().iterator().next();
-        assertEquals(TEST_NAME, artifact.getName());
-        assertEquals(TEST_TYPE, artifact.getType());
-        assertEquals(TEST_TYPE, artifact.getExtension());
-        assertEquals(TEST_CLASSIFIER, artifact.getClassifier());
-    }
-
-    private void assertIsArtifactOnly(ExternalDependency moduleDependency) {
-        checkCommonModuleProperties(moduleDependency);
-        assertFalse(moduleDependency.isTransitive());
-        assertEquals(1, moduleDependency.getArtifacts().size());
-        DependencyArtifact artifact = moduleDependency.getArtifacts().iterator().next();
-        assertEquals(TEST_NAME, artifact.getName());
-        assertEquals(TEST_TYPE, artifact.getType());
-        assertEquals(null, artifact.getClassifier());
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactoryTest.java
deleted file mode 100644
index 329b741..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactoryTest.java
+++ /dev/null
@@ -1,49 +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.dsl.dependencies;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
-import static org.hamcrest.Matchers.*;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class SelfResolvingDependencyFactoryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final SelfResolvingDependencyFactory factory = new SelfResolvingDependencyFactory(new AsmBackedClassGenerator());
-
-    @Test
-    public void createsADependencyFromAFileCollectionNotation() {
-        FileCollection collection = context.mock(FileCollection.class);
-
-        Dependency dependency = factory.createDependency(Dependency.class, collection);
-        assertThat(dependency, instanceOf(DefaultSelfResolvingDependency.class));
-        DefaultSelfResolvingDependency selfResolvingDependency = (DefaultSelfResolvingDependency) dependency;
-        assertThat(selfResolvingDependency.getSource(), sameInstance(collection));
-    }
-
-    @Test(expected = IllegalDependencyNotation.class)
-    public void throwsExceptionForOtherNotation() {
-        factory.createDependency(Dependency.class, "something else");
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolverTest.groovy
deleted file mode 100644
index 2e6d05c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolverTest.groovy
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice
-
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-class ClientModuleResolverTest {
-    @Test public void testGetDependency() {
-      // todo implement
-//        ClientModuleResolver clientModuleResolver = new ClientModuleResolver()
-//        clientModuleResolver.moduleRegistry = [:]
-//        clientModuleResolver.mainResolver = [:] as DependencyResolver
-//
-//        DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(null,
-//                createModuleRevisionId([(CLIENT_MODULE_KEY): id]), false, true, true)
-//        ResolveData resolveData = new ResolveData(new ResolveEngine(), new ResolveOptions())
-//        ModuleDescriptor = clientModuleResolver.getDependency(dd, resolveData)
-
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolverTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolverTest.java
deleted file mode 100644
index e5168e2..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolverTest.java
+++ /dev/null
@@ -1,299 +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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.apache.ivy.core.report.ResolveReport;
-import org.apache.ivy.core.resolve.ResolveOptions;
-import org.gradle.api.GradleException;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifactTest;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.util.GUtil;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultIvyDependencyResolverTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    private Configuration configurationStub = context.mock(Configuration.class, "<configuration>");
-    private Ivy ivyStub = context.mock(Ivy.class);
-    private DefaultIvyReportConverter ivyReportConverterStub = context.mock(DefaultIvyReportConverter.class);
-    private ResolveReport resolveReportMock = context.mock(ResolveReport.class);
-
-    private DefaultIvyDependencyResolver ivyDependencyResolver = new DefaultIvyDependencyResolver(ivyReportConverterStub);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(configurationStub).getName();
-            will(returnValue("someConfName"));
-        }});
-    }
-
-    @Test
-    public void testResolveAndGetFilesWithDependencySubset() throws IOException, ParseException {
-        prepareResolveReport();
-        final ModuleDependency moduleDependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
-        final ModuleDependency moduleDependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
-        final SelfResolvingDependency selfResolvingDependencyDummy = context.mock(SelfResolvingDependency.class);
-        final ResolvedDependency root = context.mock(ResolvedDependency.class, "root");
-        final ResolvedDependency resolvedDependency1 = context.mock(ResolvedDependency.class, "resolved1");
-        final ResolvedDependency resolvedDependency2 = context.mock(ResolvedDependency.class, "resolved2");
-        ResolvedDependency resolvedDependency3 = context.mock(ResolvedDependency.class, "resolved3");
-        final IvyConversionResult conversionResultStub = context.mock(IvyConversionResult.class);
-        final Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies = GUtil.map(
-                moduleDependencyDummy1,
-                toSet(resolvedDependency1, resolvedDependency2),
-                moduleDependencyDummy2,
-                toSet(resolvedDependency3));
-
-        context.checking(new Expectations() {{
-            allowing(resolvedDependency1).getParentArtifacts(root);
-            will(returnValue(toSet(DefaultResolvedArtifactTest.createResolvedArtifact(context, "dep1parent", "someType", "someExtension", new File("dep1parent")))));
-            allowing(resolvedDependency1).getModuleArtifacts();
-            will(returnValue(toSet(DefaultResolvedArtifactTest.createResolvedArtifact(context, "dep1", "someType", "someExtension", new File("dep1")))));
-            allowing(resolvedDependency1).getChildren();
-            will(returnValue(toSet()));
-            allowing(resolvedDependency2).getParentArtifacts(root);
-            will(returnValue(toSet()));
-            allowing(resolvedDependency2).getModuleArtifacts();
-            will(returnValue(toSet(DefaultResolvedArtifactTest.createResolvedArtifact(context, "dep2", "someType", "someExtension", new File("dep2")))));
-            allowing(resolvedDependency2).getChildren();
-            will(returnValue(toSet()));
-            allowing(configurationStub).getAllDependencies();
-            will(returnValue(toSet(moduleDependencyDummy1, moduleDependencyDummy2, selfResolvingDependencyDummy)));
-            allowing(configurationStub).getAllDependencies(ModuleDependency.class);
-            will(returnValue(toSet(moduleDependencyDummy1, moduleDependencyDummy2)));
-            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
-            will(returnValue(conversionResultStub));
-            allowing(conversionResultStub).getFirstLevelResolvedDependencies();
-            will(returnValue(firstLevelResolvedDependencies));
-            allowing(conversionResultStub).getRoot();
-            will(returnValue(root));
-        }});
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-
-        ResolvedConfiguration resolvedConfig = ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor);
-        Set<File> actualFiles = resolvedConfig.getFiles(
-                new Spec<Dependency>() {
-                    public boolean isSatisfiedBy(Dependency element) {
-                        return element == moduleDependencyDummy1 || element == selfResolvingDependencyDummy;
-                    }
-                });
-        assertThat(actualFiles, equalTo(toSet(new File("dep1"), new File("dep2"), new File("dep1parent"))));
-
-        Set<ResolvedDependency> actualDeps = resolvedConfig.getFirstLevelModuleDependencies(
-                new Spec<Dependency>() {
-                    public boolean isSatisfiedBy(Dependency element) {
-                        return element == moduleDependencyDummy1;
-                    }
-                });
-        assertThat(actualDeps, equalTo(toSet(resolvedDependency1, resolvedDependency2)));
-    }
-
-    @Test
-    public void testGetModuleDependencies() throws IOException, ParseException {
-        prepareResolveReport();
-        final ModuleDependency moduleDependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
-        final ResolvedDependency root = context.mock(ResolvedDependency.class, "root");
-        final ResolvedDependency resolvedDependency1 = context.mock(ResolvedDependency.class, "resolved1");
-        final ResolvedDependency resolvedDependency2 = context.mock(ResolvedDependency.class, "resolved2");
-        final IvyConversionResult conversionResultStub = context.mock(IvyConversionResult.class);
-        final Set<ResolvedDependency> resolvedDependenciesSet = toSet(resolvedDependency1, resolvedDependency2);
-
-        context.checking(new Expectations() {{
-            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
-            will(returnValue(conversionResultStub));
-            allowing(conversionResultStub).getRoot();
-            will(returnValue(root));
-            allowing(root).getChildren();
-            will(returnValue(resolvedDependenciesSet));
-        }});
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-
-        Set<ResolvedDependency> actualFirstLevelModuleDependencies = ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).getFirstLevelModuleDependencies();
-        assertThat(actualFirstLevelModuleDependencies, equalTo(resolvedDependenciesSet));
-    }
-
-    @Test
-    public void testGetResolvedArtifacts() throws IOException, ParseException {
-        prepareResolveReport();
-        final IvyConversionResult conversionResultStub = context.mock(IvyConversionResult.class);
-        final ResolvedArtifact resolvedArtifactDummy = context.mock(ResolvedArtifact.class);
-        final Set<ResolvedArtifact> resolvedArtifacts = toSet(resolvedArtifactDummy);
-        context.checking(new Expectations() {{
-            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
-            will(returnValue(conversionResultStub));
-            allowing(conversionResultStub).getResolvedArtifacts();
-            will(returnValue(resolvedArtifacts));
-        }});
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-        assertThat(ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor)
-                .getResolvedArtifacts(), equalTo(resolvedArtifacts));
-    }
-
-    @Test
-    public void testResolveAndGetFilesWithMissingDependenciesShouldThrowGradleEx() throws IOException, ParseException {
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-        prepareResolveReportWithError();
-        ResolvedConfiguration configuration = ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor);
-        context.checking(new Expectations() {{
-            allowing(configurationStub).getAllDependencies();
-            allowing(configurationStub).getAllDependencies(ModuleDependency.class);
-        }});
-        try {
-            configuration.getFiles(Specs.SATISFIES_ALL);
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), startsWith("Could not resolve all dependencies for <configuration>"));
-        }
-    }
-
-    @Test
-    public void testResolveAndRethrowFailureWithMissingDependenciesShouldThrowGradleEx() throws IOException, ParseException {
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-        prepareResolveReportWithError();
-        ResolvedConfiguration configuration = ivyDependencyResolver.resolve(configurationStub, ivyStub,
-                moduleDescriptor);
-
-        assertTrue(configuration.hasError());
-        try {
-            configuration.rethrowFailure();
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), startsWith("Could not resolve all dependencies for <configuration>"));
-        }
-    }
-
-    @Test
-    public void testResolveAndRethrowFailureWithNoMissingDependenciesShouldDoNothing() throws IOException, ParseException {
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-        prepareResolveReport();
-        context.checking(new Expectations() {{
-            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
-        }});
-        ResolvedConfiguration configuration = ivyDependencyResolver.resolve(configurationStub, ivyStub,
-                moduleDescriptor);
-
-        assertFalse(configuration.hasError());
-        configuration.rethrowFailure();
-    }
-
-    @Test
-    public void testResolveAndGetReport() throws IOException, ParseException {
-        prepareResolveReport();
-        context.checking(new Expectations() {{
-            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
-        }});
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-        assertEquals(false, ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).hasError());
-    }
-
-    @Test
-    public void testResolveAndGetReportWithMissingDependenciesAndFailFalse() throws IOException, ParseException {
-        prepareResolveReportWithError();
-        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
-        prepareTestsThatRetrieveDependencies(moduleDescriptor);
-        assertEquals(true, ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).hasError());
-    }
-
-    private ModuleDescriptor createAnonymousModuleDescriptor() {
-        return DefaultModuleDescriptor.newDefaultInstance(
-                ModuleRevisionId.newInstance("org", "name", "1.0", new HashMap()));
-    }
-
-    private void prepareResolveReport() throws IOException, ParseException {
-        context.checking(new Expectations() {
-            {
-                allowing(resolveReportMock).hasError();
-                will(returnValue(false));
-            }
-        });
-    }
-
-    private void prepareResolveReportWithError() throws IOException, ParseException {
-        context.checking(new Expectations() {
-            {
-                allowing(resolveReportMock).hasError();
-                will(returnValue(true));
-                allowing(resolveReportMock).getAllProblemMessages();
-                will(returnValue(toList("a problem")));
-            }
-        });
-    }
-
-    private void prepareTestsThatRetrieveDependencies(final ModuleDescriptor moduleDescriptor) throws IOException, ParseException {
-        final String confName = configurationStub.getName();
-        context.checking(new Expectations() {
-            {
-                allowing(ivyStub).resolve(with(equal(moduleDescriptor)), with(equaltResolveOptions(confName)));
-                will(returnValue(resolveReportMock));
-            }
-        });
-    }
-
-    Matcher<ResolveOptions> equaltResolveOptions(final String... confs) {
-        return new BaseMatcher<ResolveOptions>() {
-            public boolean matches(Object o) {
-                ResolveOptions otherOptions = (ResolveOptions) o;
-                return Arrays.equals(confs, otherOptions.getConfs());
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("Checking Resolveoptions");
-            }
-        };
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.java
deleted file mode 100644
index 0f9f963..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.settings.IvySettings;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyFactoryTest {
-    @Test
-    public void testCreateIvy() {
-        IvySettings ivySettings = new IvySettings();
-        Ivy ivy = new DefaultIvyFactory().createIvy(ivySettings);
-        assertThat(ivy, notNullValue());
-        assertThat(ivy.getSettings(), equalTo(ivySettings));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServicePublishTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServicePublishTest.java
deleted file mode 100644
index 978f309..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServicePublishTest.java
+++ /dev/null
@@ -1,175 +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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.publish.PublishEngine;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
-import org.gradle.api.internal.artifacts.configurations.Configurations;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultIvyServicePublishTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    private ModuleDescriptor publishModuleDescriptorDummy = context.mock(ModuleDescriptor.class, "publishModuleDescriptor");
-    private ModuleDescriptor fileModuleDescriptorMock = context.mock(ModuleDescriptor.class, "fileModuleDescriptor");
-    private PublishEngine publishEngineDummy = context.mock(PublishEngine.class);
-    private InternalRepository internalRepositoryDummy = context.mock(InternalRepository.class);
-    private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
-
-    @Test
-    public void testPublish() throws IOException, ParseException {
-        final IvySettings ivySettingsDummy = new IvySettings();
-        final Set<Configuration> configurations = createConfigurations();
-        final File someDescriptorDestination = new File("somePath");
-        final List<DependencyResolver> publishResolversDummy = createPublishResolversDummy();
-        final Module moduleDummy = context.mock(Module.class, "moduleForResolve");
-        File cacheParentDirDummy = createCacheParentDirDummy();
-        final DefaultIvyService ivyService = createIvyService();
-
-        setUpForPublish(configurations, publishResolversDummy, moduleDummy, cacheParentDirDummy,
-                ivyService, ivySettingsDummy);
-
-        final Set<String> expectedConfigurations = Configurations.getNames(configurations, true);
-        context.checking(new Expectations() {{
-            one(fileModuleDescriptorMock).toIvyFile(someDescriptorDestination);
-            one(ivyService.getDependencyPublisher()).publish(expectedConfigurations,
-                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, publishEngineDummy);
-        }});
-
-        ivyService.publish(configurations, someDescriptorDestination, publishResolversDummy);
-    }
-
-    private DefaultIvyService createIvyService() {
-        SettingsConverter settingsConverterStub = context.mock(SettingsConverter.class);
-        ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "resolve");
-        ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publishConverter");
-        ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "fileConverter");
-        IvyDependencyPublisher ivyDependencyPublisherMock = context.mock(IvyDependencyPublisher.class);
-        ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
-
-        DefaultIvyService ivyService = new DefaultIvyService(dependencyMetaDataProviderMock, resolverProvider,
-                settingsConverterStub, resolveModuleDescriptorConverter, publishModuleDescriptorConverter,
-                fileModuleDescriptorConverter,
-                new DefaultIvyFactory(), context.mock(IvyDependencyResolver.class),
-                ivyDependencyPublisherMock, new HashMap());
-
-        return ivyService;
-    }
-
-    private File createCacheParentDirDummy() {
-        return new File("cacheParentDirDummy");
-    }
-
-    private List<DependencyResolver> createPublishResolversDummy() {
-        return WrapUtil.toList(context.mock(DependencyResolver.class, "publish"));
-    }
-
-    private Set<Configuration> createConfigurations() {
-        final Configuration configurationStub1 = context.mock(Configuration.class, "confStub1");
-        final Configuration configurationStub2 = context.mock(Configuration.class, "confStub2");
-        context.checking(new Expectations() {{
-            allowing(configurationStub1).getName();
-            will(returnValue("conf1"));
-
-            allowing(configurationStub1).getHierarchy();
-            will(returnValue(WrapUtil.toLinkedSet(configurationStub1)));
-
-            allowing(configurationStub1).getAll();
-            will(returnValue(WrapUtil.toLinkedSet(configurationStub1, configurationStub2)));
-
-            allowing(configurationStub2).getName();
-            will(returnValue("conf2"));
-
-            allowing(configurationStub2).getHierarchy();
-            will(returnValue(WrapUtil.toLinkedSet(configurationStub2)));
-
-            allowing(configurationStub2).getAll();
-            will(returnValue(WrapUtil.toLinkedSet(configurationStub1, configurationStub2)));
-        }});
-        return WrapUtil.toSet(configurationStub1, configurationStub2);
-    }
-
-    private void setUpForPublish(final Set<Configuration> configurations,
-                                 final List<DependencyResolver> publishResolversDummy, final Module moduleDummy,
-                                 final File cacheParentDirDummy, final DefaultIvyService ivyService, final IvySettings ivySettingsDummy) {
-        context.checking(new Expectations() {{
-            allowing(dependencyMetaDataProviderMock).getInternalRepository();
-            will(returnValue(internalRepositoryDummy));
-
-            allowing(dependencyMetaDataProviderMock).getGradleUserHomeDir();
-            will(returnValue(cacheParentDirDummy));
-
-            allowing(dependencyMetaDataProviderMock).getModule();
-            will(returnValue(moduleDummy));
-
-            allowing(ivyService.getSettingsConverter()).convertForPublish(publishResolversDummy, cacheParentDirDummy,
-                    internalRepositoryDummy);
-            will(returnValue(ivySettingsDummy));
-
-            allowing(setUpIvyFactory(ivySettingsDummy, ivyService)).getPublishEngine();
-            will(returnValue(publishEngineDummy));
-
-            allowing(ivyService.getPublishModuleDescriptorConverter()).convert(configurations,
-                    moduleDummy, ivySettingsDummy);
-            will(returnValue(publishModuleDescriptorDummy));
-
-            allowing(ivyService.getFileModuleDescriptorConverter()).convert(configurations,
-                    moduleDummy, ivySettingsDummy);
-            will(returnValue(fileModuleDescriptorMock));
-        }});
-    }
-
-    private Ivy setUpIvyFactory(final IvySettings ivySettingsDummy, DefaultIvyService ivyService) {
-        final IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
-        final Ivy ivyStub = context.mock(Ivy.class);
-        context.checking(new Expectations() {{
-            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
-            will(returnValue(ivyStub));
-
-            allowing(ivyStub).getSettings();
-            will(returnValue(ivySettingsDummy));
-        }});
-        ivyService.setIvyFactory(ivyFactoryStub);
-        return ivyStub;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceResolveTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceResolveTest.java
deleted file mode 100644
index 24cc838..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceResolveTest.java
+++ /dev/null
@@ -1,129 +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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.sameInstance;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertThat;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultIvyServiceResolveTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    private Module moduleDummy = context.mock(Module.class);
-    private File cacheParentDirDummy = new File("cacheParentDirDummy");
-    private Map<String, ModuleDescriptor> clientModuleRegistryDummy = WrapUtil.toMap("a", context.mock(ModuleDescriptor.class));
-    private List<DependencyResolver> dependencyResolversDummy = WrapUtil.toList(context.mock(DependencyResolver.class, "dependencies"));
-
-    private InternalRepository internalRepositoryDummy = context.mock(InternalRepository.class);
-    private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
-    private ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
-
-    // SUT
-    private DefaultIvyService ivyService;
-
-    @Before
-    public void setUp() {
-        SettingsConverter settingsConverterMock = context.mock(SettingsConverter.class);
-        ModuleDescriptorConverter resolveModuleDescriptorConverterStub = context.mock(ModuleDescriptorConverter.class, "resolve");
-        ModuleDescriptorConverter publishModuleDescriptorConverterDummy = context.mock(ModuleDescriptorConverter.class, "publish");
-        IvyDependencyResolver ivyDependencyResolverMock = context.mock(IvyDependencyResolver.class);
-
-        context.checking(new Expectations() {{
-            allowing(dependencyMetaDataProviderMock).getInternalRepository();
-            will(returnValue(internalRepositoryDummy));
-
-            allowing(dependencyMetaDataProviderMock).getGradleUserHomeDir();
-            will(returnValue(cacheParentDirDummy));
-
-            allowing(dependencyMetaDataProviderMock).getModule();
-            will(returnValue(moduleDummy));
-
-            allowing(resolverProvider).getResolvers();
-            will(returnValue(dependencyResolversDummy));
-        }});
-
-        ivyService = new DefaultIvyService(dependencyMetaDataProviderMock, resolverProvider,
-                settingsConverterMock, resolveModuleDescriptorConverterStub, publishModuleDescriptorConverterDummy,
-                publishModuleDescriptorConverterDummy,
-                new DefaultIvyFactory(), ivyDependencyResolverMock, 
-                context.mock(IvyDependencyPublisher.class), clientModuleRegistryDummy);
-    }
-
-    @Test
-    public void testResolve() {
-        final Configuration configurationDummy = context.mock(Configuration.class);
-        final Set<Configuration> configurations = WrapUtil.toSet(configurationDummy);
-        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-        final ModuleDescriptor moduleDescriptorDummy = HelperUtil.createModuleDescriptor(WrapUtil.toSet("someConf"));
-        final IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
-        final Ivy ivyStub = context.mock(Ivy.class);
-        final IvySettings ivySettingsDummy = new IvySettings();
-
-        context.checking(new Expectations() {{
-            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
-            will(returnValue(ivyStub));
-
-            allowing(configurationDummy).getAll();
-            will(returnValue(configurations));
-
-            allowing(ivyStub).getSettings();
-            will(returnValue(ivySettingsDummy));
-
-            allowing(ivyService.getDependencyResolver()).resolve(configurationDummy, ivyStub, moduleDescriptorDummy);
-            will(returnValue(resolvedConfiguration));
-
-            allowing(ivyService.getResolveModuleDescriptorConverter()).convert(WrapUtil.toSet(configurationDummy), moduleDummy,
-                    ivySettingsDummy);
-            will(returnValue(moduleDescriptorDummy));
-
-            allowing(ivyService.getSettingsConverter()).convertForResolve(dependencyResolversDummy, cacheParentDirDummy,
-                    internalRepositoryDummy, clientModuleRegistryDummy);
-            will(returnValue(ivySettingsDummy));
-        }});
-
-        ivyService.setIvyFactory(ivyFactoryStub);
-        assertThat(ivyService.resolve(configurationDummy), sameInstance(resolvedConfiguration));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceTest.java
deleted file mode 100644
index e2c35a9..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice;
-
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import static org.hamcrest.Matchers.sameInstance;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultIvyServiceTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    @Test
-    public void init() {
-        DependencyMetaDataProvider dependencyMetaDataProvider = context.mock(DependencyMetaDataProvider.class);
-        ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
-        SettingsConverter settingsConverter = context.mock(SettingsConverter.class);
-        ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "resolve");
-        ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publish");
-        ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "file");
-        IvyFactory ivyFactory = context.mock(IvyFactory.class);
-        IvyDependencyResolver dependencyResolver = context.mock(IvyDependencyResolver.class);
-        IvyDependencyPublisher dependencyPublisher = context.mock(IvyDependencyPublisher.class);
-        Map clientModuleRegistry = new HashMap();
-
-        DefaultIvyService ivyService = new DefaultIvyService(dependencyMetaDataProvider, resolverProvider,
-                settingsConverter, resolveModuleDescriptorConverter, publishModuleDescriptorConverter,
-                fileModuleDescriptorConverter,
-                ivyFactory, dependencyResolver, dependencyPublisher, clientModuleRegistry);
-        
-        assertThat(ivyService.getMetaDataProvider(), sameInstance(dependencyMetaDataProvider));
-        assertThat(ivyService.getSettingsConverter(), sameInstance(settingsConverter));
-        assertThat(ivyService.getResolveModuleDescriptorConverter(), sameInstance(resolveModuleDescriptorConverter));
-        assertThat(ivyService.getPublishModuleDescriptorConverter(), sameInstance(publishModuleDescriptorConverter));
-        assertThat(ivyService.getFileModuleDescriptorConverter(), sameInstance(fileModuleDescriptorConverter));
-        assertThat(ivyService.getIvyFactory(), sameInstance(ivyFactory));
-        assertThat(ivyService.getDependencyResolver(), sameInstance(dependencyResolver));
-        assertThat(ivyService.getDependencyPublisher(), sameInstance(dependencyPublisher));
-        assertThat(ivyService.getClientModuleRegistry(), sameInstance(clientModuleRegistry));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
deleted file mode 100644
index ca11d50..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.api.internal.artifacts.ivyservice
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.settings.IvySettings
-import org.apache.ivy.plugins.resolver.ChainResolver
-import org.apache.ivy.plugins.resolver.IBiblioResolver
-import org.gradle.api.artifacts.ResolverContainer
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.lib.legacy.ClassImposteriser
-import org.junit.Before
-import org.junit.Test
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertTrue
-import static org.junit.Assert.assertSame
-
-/**
- * @author Hans Dockter
- */
-class DefaultSettingsConverterTest {
-    final IBiblioResolver testResolver = new IBiblioResolver()
-    final IBiblioResolver testResolver2 = new IBiblioResolver()
-    final IBiblioResolver testBuildResolver = new IBiblioResolver()
-
-    DefaultSettingsConverter converter
-
-    Map clientModuleRegistry
-
-    File testGradleUserHome
-
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-
-    @Before public void setUp()  {
-        testResolver.name = 'resolver'
-        testBuildResolver.name = 'buildResolver'
-        context.setImposteriser(ClassImposteriser.INSTANCE)
-        converter = new DefaultSettingsConverter()
-        clientModuleRegistry = [a: [:] as ModuleDescriptor]
-        testGradleUserHome = new File('gradleUserHome')
-    }
-
-    @Test public void testConvertForResolve() {
-        IvySettings settings = converter.convertForResolve([testResolver, testResolver2], testGradleUserHome,
-                testBuildResolver, clientModuleRegistry)
-        ChainResolver chainResolver = settings.getResolver(DefaultSettingsConverter.CHAIN_RESOLVER_NAME)
-        assertEquals(3, chainResolver.resolvers.size())
-        assert chainResolver.resolvers[0].name.is(testBuildResolver.name)
-        assert chainResolver.resolvers[1].is(testResolver)
-        assert chainResolver.resolvers[2].is(testResolver2)
-        assertTrue chainResolver.returnFirst
-
-        ClientModuleResolver clientModuleResolver = settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_NAME)
-        ChainResolver clientModuleChain = settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_CHAIN_NAME)
-        assertTrue clientModuleChain.returnFirst
-        assert clientModuleChain.resolvers[0].is(clientModuleResolver)
-        assert clientModuleChain.resolvers[1].is(chainResolver)
-        assert settings.defaultResolver.is(clientModuleChain)
-
-        [testBuildResolver.name, testResolver.name, testResolver2.name, DefaultSettingsConverter.CHAIN_RESOLVER_NAME,
-                DefaultSettingsConverter.CLIENT_MODULE_CHAIN_NAME].each {
-            assert settings.getResolver(it)
-            assert settings.getResolver(it).getRepositoryCacheManager().settings == settings
-        }
-        assert settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_NAME)
-
-        assertEquals(new File(testGradleUserHome, ResolverContainer.DEFAULT_CACHE_DIR_NAME),
-                settings.defaultCache)
-        assertEquals(settings.defaultCacheArtifactPattern, ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN)
-    }
-
-    @Test public void testConvertForPublish() {
-        IvySettings settings = converter.convertForPublish([testResolver, testResolver2], testGradleUserHome,
-                testBuildResolver)
-
-        [testResolver.name, testResolver2.name].each {
-            assert settings.getResolver(it)
-            assert settings.getResolver(it).getRepositoryCacheManager().settings == settings
-        }
-
-        assert settings.getResolver(testResolver.name).is(testResolver)
-        assert settings.getResolver(testResolver2.name).is(testResolver2)
-        assertEquals(new File(testGradleUserHome, ResolverContainer.DEFAULT_CACHE_DIR_NAME),
-                settings.defaultCache)
-        assertEquals(settings.defaultCacheArtifactPattern, ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN)
-    }
-
-    @Test
-    public void repositoryCacheManagerShouldBeSharedBetweenSettings() {
-        IvySettings settings1 = converter.convertForPublish([testResolver, testResolver2], testGradleUserHome,
-                testBuildResolver)
-        IvySettings settings2 = converter.convertForPublish([testResolver, testResolver2], testGradleUserHome,
-                testBuildResolver)
-        IvySettings settings3 = converter.convertForResolve([testResolver, testResolver2], testGradleUserHome,
-                testBuildResolver, clientModuleRegistry)
-        assertSame(settings1.getDefaultRepositoryCacheManager(), settings2.getDefaultRepositoryCacheManager())
-        assertSame(settings1.getDefaultRepositoryCacheManager(), settings3.getDefaultRepositoryCacheManager())
-
-    }
-
-    @Test public void testWithGivenSettings() {
-        IvySettings ivySettings = [:] as IvySettings
-        converter.ivySettings = ivySettings
-        assert ivySettings.is(converter.convertForResolve([testResolver], new File(''),
-                testBuildResolver, clientModuleRegistry))
-        assert ivySettings.is(converter.convertForPublish([testResolver], new File(''),
-                testBuildResolver))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyServiceTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyServiceTest.groovy
deleted file mode 100644
index 9b4eaac..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyServiceTest.groovy
+++ /dev/null
@@ -1,156 +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.ivyservice;
-
-
-import org.gradle.api.internal.artifacts.IvyService
-import org.gradle.api.specs.Spec
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.api.artifacts.*
-import static org.hamcrest.Matchers.equalTo
-import static org.hamcrest.Matchers.sameInstance
-import static org.junit.Assert.assertThat
-import static org.junit.Assert.fail
-
- at RunWith(JMock.class)
-public class ErrorHandlingIvyServiceTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
-    private final IvyService ivyServiceMock = context.mock(IvyService.class);
-    private final ResolvedConfiguration resolvedConfigurationMock = context.mock(ResolvedConfiguration.class);
-    private final Configuration configurationMock = context.mock(Configuration.class, "<config display name>");
-    private final Spec<Dependency> specDummy = context.mock(Spec.class);
-    private final RuntimeException failure = new RuntimeException();
-    private final ErrorHandlingIvyService ivyService = new ErrorHandlingIvyService(ivyServiceMock);
-
-    @Test
-    public void resolveDelegatesToBackingService() {
-        context.checking {
-            one(ivyServiceMock).resolve(configurationMock);
-            will(returnValue(resolvedConfigurationMock));
-        }
-
-        ResolvedConfiguration resolvedConfiguration = ivyService.resolve(configurationMock);
-
-        context.checking {
-            one(resolvedConfigurationMock).hasError();
-            one(resolvedConfigurationMock).rethrowFailure();
-            one(resolvedConfigurationMock).getFiles(specDummy);
-        }
-
-        resolvedConfiguration.hasError();
-        resolvedConfiguration.rethrowFailure();
-        resolvedConfiguration.getFiles(specDummy);
-    }
-
-    @Test
-    public void returnsAnExceptionThrowingConfigurationWhenResolveFails() {
-
-        context.checking {
-            one(ivyServiceMock).resolve(configurationMock);
-            will(throwException(failure));
-        }
-
-        ResolvedConfiguration resolvedConfiguration = ivyService.resolve(configurationMock);
-
-        assertThat(resolvedConfiguration.hasError(), equalTo(true));
-
-        assertFailsWithResolveException {
-            resolvedConfiguration.rethrowFailure();
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getFiles(specDummy);
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getFirstLevelModuleDependencies();
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getResolvedArtifacts();
-        }
-    }
-
-    @Test
-    public void wrapsExceptionsThrownByResolvedConfiguration() {
-        context.checking {
-            one(ivyServiceMock).resolve(configurationMock);
-            will(returnValue(resolvedConfigurationMock));
-        }
-
-        ResolvedConfiguration resolvedConfiguration = ivyService.resolve(configurationMock);
-
-        context.checking {
-            allowing(resolvedConfigurationMock).rethrowFailure()
-            will(throwException(failure))
-            allowing(resolvedConfigurationMock).getFiles(specDummy)
-            will(throwException(failure))
-            allowing(resolvedConfigurationMock).getFirstLevelModuleDependencies()
-            will(throwException(failure))
-            allowing(resolvedConfigurationMock).getResolvedArtifacts()
-            will(throwException(failure))
-        }
-
-        assertFailsWithResolveException {
-            resolvedConfiguration.rethrowFailure();
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getFiles(specDummy);
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getFirstLevelModuleDependencies();
-        }
-        assertFailsWithResolveException {
-            resolvedConfiguration.getResolvedArtifacts();
-        }
-    }
-
-    @Test
-    public void publishDelegatesToBackingService() {
-        context.checking {
-            one(ivyServiceMock).publish([configurationMock] as Set, null, [])
-        }
-
-        ivyService.publish([configurationMock] as Set, null, [])
-    }
-
-    @Test
-    public void wrapsPublishException() {
-        context.checking {
-            one(ivyServiceMock).publish([configurationMock] as Set, null, [])
-            will(throwException(failure))
-        }
-
-        try {
-            ivyService.publish([configurationMock] as Set, null, [])
-            fail()
-        }
-        catch(PublishException e) {
-            assertThat e.message, equalTo("Could not publish configurations [<config display name>].")
-            assertThat(e.cause, sameInstance((Throwable) failure));
-        }
-    }
-
-    def assertFailsWithResolveException(Closure cl) {
-        try {
-            cl();
-            fail();
-        } catch (ResolveException e) {
-            assertThat(e.message, equalTo("Could not resolve all dependencies for <config display name>."));
-            assertThat(e.cause, sameInstance((Throwable) failure));
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolverTest.groovy
deleted file mode 100644
index 3822360..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolverTest.groovy
+++ /dev/null
@@ -1,88 +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.api.internal.artifacts.ivyservice
-
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter
- */
-class GradleIBiblioResolverTest extends Specification {
-    GradleIBiblioResolver gradleIBiblioResolver = new GradleIBiblioResolver()
-
-    def defaults() {
-        expect:
-        gradleIBiblioResolver.getSnapshotTimeout().is(GradleIBiblioResolver.DAILY)
-    }
-
-    def usesDailyExpiryForRemoteUrls() {
-        when:
-        gradleIBiblioResolver.setRoot("http://server/repo")
-
-        then:
-        gradleIBiblioResolver.getSnapshotTimeout().is(GradleIBiblioResolver.DAILY)
-    }
-
-    def usesAlwaysExpiryForLocalUrls() {
-        when:
-        gradleIBiblioResolver.setRoot(new File(".").toURI().toString())
-
-        then:
-        gradleIBiblioResolver.getSnapshotTimeout().is(GradleIBiblioResolver.ALWAYS)
-    }
-
-    def timeoutStrategyNever_shouldReturnAlwaysFalse() {
-        expect:
-        !GradleIBiblioResolver.NEVER.isCacheTimedOut(0)
-        !GradleIBiblioResolver.NEVER.isCacheTimedOut(System.currentTimeMillis())
-    }
-
-    def timeoutStrategyAlways_shouldReturnAlwaysTrue() {
-        expect:
-        GradleIBiblioResolver.ALWAYS.isCacheTimedOut(0)
-        GradleIBiblioResolver.ALWAYS.isCacheTimedOut(System.currentTimeMillis())
-    }
-
-    def timeoutStrategyDaily() {
-        expect:
-        !GradleIBiblioResolver.DAILY.isCacheTimedOut(System.currentTimeMillis())
-        GradleIBiblioResolver.ALWAYS.isCacheTimedOut(System.currentTimeMillis() - 24 * 60 * 60 * 1000)
-    }
-
-    def timeoutInterval() {
-        def interval = new GradleIBiblioResolver.Interval(1000)
-
-        expect:
-        interval.isCacheTimedOut(System.currentTimeMillis() - 5000)
-        !interval.isCacheTimedOut(System.currentTimeMillis())
-    }
-
-    def setTimeoutByMilliseconds() {
-        when:
-        gradleIBiblioResolver.setSnapshotTimeout(1000)
-
-        then:
-        ((GradleIBiblioResolver.Interval) gradleIBiblioResolver.getSnapshotTimeout()).interval == 1000
-    }
-
-    def setTimeoutByStrategy() {
-        when:
-        gradleIBiblioResolver.setSnapshotTimeout(GradleIBiblioResolver.NEVER)
-
-        then:
-        gradleIBiblioResolver.getSnapshotTimeout().is(GradleIBiblioResolver.NEVER)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/Report2ClasspathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/Report2ClasspathTest.groovy
deleted file mode 100644
index 1b53a8e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/Report2ClasspathTest.groovy
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.api.internal.artifacts.ivyservice
-
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-class Report2ClasspathTest {
-    @Test public void testGetClasspath() {
-      // todo implement 
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
deleted file mode 100644
index 7a0bf24..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
+++ /dev/null
@@ -1,169 +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.ivyservice;
-
-import org.apache.ivy.Ivy;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ResolvedArtifact;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.DependencyInternal;
-import org.gradle.api.internal.artifacts.DependencyResolveContext;
-import org.gradle.api.specs.Specs;
-import org.hamcrest.Description;
-import org.jmock.Expectations;
-import org.jmock.api.Action;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParseException;
-
-import static org.gradle.util.WrapUtil.toLinkedSet;
-import static org.gradle.util.WrapUtil.toSet;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class SelfResolvingDependencyResolverTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final IvyDependencyResolver delegate = context.mock(IvyDependencyResolver.class);
-    private final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
-    private final Configuration configuration = context.mock(Configuration.class);
-    private final Ivy ivy = Ivy.newInstance();
-    private final ModuleDescriptor moduleDescriptor = context.mock(ModuleDescriptor.class);
-
-    private final SelfResolvingDependencyResolver resolver = new SelfResolvingDependencyResolver(delegate);
-
-    @Test
-    public void wrapsResolvedConfigurationProvidedByDelegate() {
-        context.checking(new Expectations() {{
-            one(delegate).resolve(configuration, ivy, moduleDescriptor);
-            will(returnValue(resolvedConfiguration));
-            allowing(configuration).getAllDependencies(DependencyInternal.class);
-            will(returnValue(toSet()));
-            allowing(configuration).isTransitive();
-            will(returnValue(true));
-        }});
-
-        ResolvedConfiguration configuration = resolver.resolve(this.configuration, ivy, moduleDescriptor);
-        assertThat(configuration, not(sameInstance(resolvedConfiguration)));
-
-        final File file = new File("file");
-
-        context.checking(new Expectations() {{
-            one(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
-            will(returnValue(toSet(file)));
-        }});
-
-        assertThat(configuration.getFiles(Specs.SATISFIES_ALL), equalTo(toLinkedSet(file)));
-    }
-    
-    @Test
-    public void addsFilesFromSelfResolvingDependenciesBeforeFilesFromResolvedConfiguration() {
-        final DependencyInternal dependency = context.mock(DependencyInternal.class);
-
-        context.checking(new Expectations() {{
-            one(delegate).resolve(configuration, ivy, moduleDescriptor);
-            will(returnValue(resolvedConfiguration));
-            allowing(configuration).getAllDependencies(DependencyInternal.class);
-            will(returnValue(toSet(dependency)));
-            allowing(configuration).isTransitive();
-            will(returnValue(true));
-        }});
-
-        ResolvedConfiguration actualResolvedConfiguration = resolver.resolve(this.configuration, ivy, moduleDescriptor);
-        assertThat(actualResolvedConfiguration, not(sameInstance(resolvedConfiguration)));
-
-        final File configFile = new File("from config");
-        final File depFile = new File("from dep");
-        final FileCollection depFiles = context.mock(FileCollection.class);
-
-        final boolean transitive = true;
-        context.checking(new Expectations() {{
-            allowing(configuration);
-            will(returnValue(transitive));
-            one(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
-            will(returnValue(toSet(configFile)));
-            one(dependency).resolve(with(notNullValue(DependencyResolveContext.class)));
-            will(new Action() {
-                public void describeTo(Description description) {
-                    description.appendText("add files to context");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    ((DependencyResolveContext) invocation.getParameter(0)).add(depFiles);
-                    return null;
-                }
-            });
-            allowing(depFiles).getFiles();
-            will(returnValue(toSet(depFile)));
-        }});
-
-        assertThat(actualResolvedConfiguration.getFiles(Specs.SATISFIES_ALL), equalTo(toLinkedSet(depFile, configFile)));
-    }
-
-    @Test
-    public void testGetModuleDependencies() throws IOException, ParseException {
-        context.checking(new Expectations() {{
-            one(delegate).resolve(configuration, ivy, moduleDescriptor);
-            will(returnValue(resolvedConfiguration));
-            allowing(configuration).getAllDependencies(DependencyInternal.class);
-            will(returnValue(toSet()));
-            allowing(configuration).isTransitive();
-            will(returnValue(true));
-        }});
-
-        final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
-
-        context.checking(new Expectations() {{
-            one(resolvedConfiguration).getFirstLevelModuleDependencies();
-            will(returnValue(toSet(resolvedDependency)));
-        }});
-
-        assertThat(resolver.resolve(this.configuration, ivy, moduleDescriptor).getFirstLevelModuleDependencies(),
-                equalTo(toSet(resolvedDependency)));
-    }
-
-    @Test
-    public void testGetResolvedArtifacts() {
-        context.checking(new Expectations() {{
-            one(delegate).resolve(configuration, ivy, moduleDescriptor);
-            will(returnValue(resolvedConfiguration));
-            allowing(configuration).getAllDependencies(DependencyInternal.class);
-            will(returnValue(toSet()));
-            allowing(configuration).isTransitive();
-            will(returnValue(true));
-        }});
-
-        final ResolvedArtifact resolvedArtifact = context.mock(ResolvedArtifact.class);
-
-        context.checking(new Expectations() {{
-            one(resolvedConfiguration).getResolvedArtifacts();
-            will(returnValue(toSet(resolvedArtifact)));
-        }});
-
-        assertThat(resolver.resolve(this.configuration, ivy, moduleDescriptor).getResolvedArtifacts(),
-                equalTo(toSet(resolvedArtifact)));
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyServiceTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyServiceTest.java
deleted file mode 100644
index 96efa5e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyServiceTest.java
+++ /dev/null
@@ -1,92 +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.ivyservice;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ResolvedConfiguration;
-import org.gradle.api.internal.artifacts.IvyService;
-import org.gradle.api.specs.Specs;
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.WrapUtil.toList;
-import static org.gradle.util.WrapUtil.toSet;
-import static org.hamcrest.Matchers.sameInstance;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
- at RunWith(JMock.class)
-public class ShortcircuitEmptyConfigsIvyServiceTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final IvyService delegate = context.mock(IvyService.class);
-    private final Configuration configuration = context.mock(Configuration.class);
-    private final ShortcircuitEmptyConfigsIvyService ivyService = new ShortcircuitEmptyConfigsIvyService(delegate);
-
-    @Test
-    public void resolveReturnsEmptyResolvedConfigWhenConfigHasNoDependencies() {
-        context.checking(new Expectations(){{
-            allowing(configuration).getAllDependencies();
-            will(returnValue(Collections.emptySet()));
-        }});
-
-        ResolvedConfiguration resolvedConfig = ivyService.resolve(configuration);
-
-        assertFalse(resolvedConfig.hasError());
-        resolvedConfig.rethrowFailure();
-        assertThat(resolvedConfig.getFiles(Specs.<Dependency>satisfyAll()), isEmpty());
-        assertThat(resolvedConfig.getFirstLevelModuleDependencies(), isEmpty());
-        assertThat(resolvedConfig.getResolvedArtifacts(), isEmpty());
-    }
-
-    @Test
-    public void resolveDelegatesToBackingServiceWhenConfigHasDependencies() {
-        final Dependency dependencyDummy = context.mock(Dependency.class);
-        final ResolvedConfiguration resolvedConfigDummy = context.mock(ResolvedConfiguration.class);
-
-        context.checking(new Expectations() {{
-            allowing(configuration).getAllDependencies();
-            will(returnValue(toSet(dependencyDummy)));
-
-            one(delegate).resolve(configuration);
-            will(returnValue(resolvedConfigDummy));
-        }});
-
-        assertThat(ivyService.resolve(configuration), sameInstance(resolvedConfigDummy));
-    }
-
-    @Test
-    public void publishDelegatesToBackingService() {
-        final Set<Configuration> configurations = toSet(configuration);
-        final File someDescriptorDestination = new File("somePth");
-        final List<DependencyResolver> resolvers = toList(context.mock(DependencyResolver.class));
-
-        context.checking(new Expectations(){{
-            one(delegate).publish(configurations, someDescriptorDestination, resolvers);
-        }});
-
-        ivyService.publish(configurations, someDescriptorDestination, resolvers);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
deleted file mode 100644
index df7666b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultArtifactsToModuleDescriptorConverterTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void testAddArtifacts() {
-        final PublishArtifact publishArtifactConf1 = createNamedPublishArtifact("conf1");
-        Configuration configurationStub1 = createConfigurationStub(publishArtifactConf1);
-        final PublishArtifact publishArtifactConf2 = createNamedPublishArtifact("conf2");
-        Configuration configurationStub2 = createConfigurationStub(publishArtifactConf2);
-        final ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategyMock = context.mock(ArtifactsExtraAttributesStrategy.class);
-        final Map<String, String> extraAttributesArtifact1 = WrapUtil.toMap("name", publishArtifactConf1.getName());
-        final Map<String, String> extraAttributesArtifact2 = WrapUtil.toMap("name", publishArtifactConf2.getName());
-        context.checking(new Expectations() {{
-            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf1);
-            will(returnValue(extraAttributesArtifact1));
-            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf2);
-            will(returnValue(extraAttributesArtifact2));
-        }});
-        DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(configurationStub1.getName(),
-                configurationStub2.getName()));
-
-        DefaultArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter =
-                new DefaultArtifactsToModuleDescriptorConverter(artifactsExtraAttributesStrategyMock);
-
-        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, WrapUtil.toSet(configurationStub1, configurationStub2));
-
-        assertArtifactIsAdded(configurationStub1, moduleDescriptor, extraAttributesArtifact1);
-        assertArtifactIsAdded(configurationStub2, moduleDescriptor, extraAttributesArtifact2);
-        assertThat(moduleDescriptor.getAllArtifacts().length, equalTo(2));
-    }
-
-    @Test
-    public void testIvyFileStrategy() {
-        assertThat(
-                DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY.createExtraAttributes(context.mock(PublishArtifact.class)),
-                equalTo((Map) new HashMap<String, String>()));
-    }
-
-    @Test
-    public void testResolveStrategy() {
-        PublishArtifact publishArtifact = createNamedPublishArtifact("someName");
-        Map<String, String> expectedExtraAttributes = WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
-        assertThat(
-                DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY.createExtraAttributes(publishArtifact),
-                equalTo(expectedExtraAttributes));
-    }
-
-    private void assertArtifactIsAdded(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, Map<String, String> extraAttributes) {
-        assertThat(moduleDescriptor.getArtifacts(configuration.getName()),
-                equalTo(WrapUtil.toArray(expectedIvyArtifact(configuration, moduleDescriptor, extraAttributes))));
-    }
-
-    private Artifact expectedIvyArtifact(Configuration configuration, ModuleDescriptor moduleDescriptor, Map<String, String> additionalExtraAttributes) {
-        PublishArtifact publishArtifact = configuration.getArtifacts().iterator().next();
-        Map<String, String> extraAttributes = WrapUtil.toMap(Dependency.CLASSIFIER, publishArtifact.getClassifier());
-        extraAttributes.putAll(additionalExtraAttributes);
-        return new DefaultArtifact(moduleDescriptor.getModuleRevisionId(),
-                publishArtifact.getDate(),
-                publishArtifact.getName(),
-                publishArtifact.getType(),
-                publishArtifact.getExtension(),
-                extraAttributes);
-    }
-
-    private Configuration createConfigurationStub(final PublishArtifact publishArtifact) {
-        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(publishArtifact.getName(), context);
-        context.checking(new Expectations() {{
-            allowing(configurationStub).getArtifacts();
-            will(returnValue(WrapUtil.toSet(publishArtifact)));
-        }});
-        return configurationStub;
-    }
-
-    private PublishArtifact createNamedPublishArtifact(String name) {
-        return new DefaultPublishArtifact(name, "ext", "type", "classifier", new Date(), new File("somePath"));
-    }
-
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
deleted file mode 100644
index e3dd41c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
+++ /dev/null
@@ -1,54 +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.ivyservice.moduleconverter;
-
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
-import org.apache.ivy.plugins.matcher.PatternMatcher;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.util.GUtil;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Matchers;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-import java.util.Map;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultExcludeRuleConverterTest {
-
-    @Test
-    public void testCreateExcludeRule() {
-        String configurationName = "someConf";
-        Map excludeRuleArgs = GUtil.map(ExcludeRule.GROUP_KEY, "someOrg", ExcludeRule.MODULE_KEY, "someModule");
-        org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule =
-                new DefaultExcludeRuleConverter().createExcludeRule(configurationName, new DefaultExcludeRule(excludeRuleArgs));
-        assertThat(ivyExcludeRule.getId().getModuleId().getOrganisation(),
-                Matchers.equalTo(excludeRuleArgs.get(ExcludeRule.GROUP_KEY)));
-        assertThat(ivyExcludeRule.getId().getName(),
-                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
-        assertThat(ivyExcludeRule.getId().getExt(),
-                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
-        assertThat(ivyExcludeRule.getId().getType(), 
-                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
-        assertThat((ExactPatternMatcher) ivyExcludeRule.getMatcher(),
-                Matchers.equalTo(ExactPatternMatcher.INSTANCE));
-        assertThat(ivyExcludeRule.getConfigurations(),
-                Matchers.equalTo(WrapUtil.toArray(configurationName)));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.java
deleted file mode 100644
index 7ce1eb3..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2007-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.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.DefaultModule;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultModuleDescriptorFactoryTest {
-    @Test
-    public void testCreateModuleDescriptor() {
-        Module module = new DefaultModule("org", "name", "version", "status");
-        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptorFactory().createModuleDescriptor(module);
-        assertThat(moduleDescriptor.getModuleRevisionId().getOrganisation(), equalTo(module.getGroup()));
-        assertThat(moduleDescriptor.getModuleRevisionId().getName(), equalTo(module.getName()));
-        assertThat(moduleDescriptor.getModuleRevisionId().getRevision(), equalTo(module.getVersion()));
-        assertThat(moduleDescriptor.getStatus(), equalTo(module.getStatus()));
-        assertThat(moduleDescriptor.getPublicationDate(), equalTo(null));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.java
deleted file mode 100644
index 7c379c1..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class PublishModuleDescriptorConverterTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    @Test
-    public void convert() {
-        final Set<Configuration> configurationsDummy = WrapUtil.toSet(context.mock(Configuration.class, "conf1"),
-                context.mock(Configuration.class, "conf2"));
-        final Module moduleDummy = context.mock(Module.class);
-        final IvySettings ivySettingsDummy = context.mock(IvySettings.class);
-        final DefaultModuleDescriptor moduleDescriptorDummy = context.mock(DefaultModuleDescriptor.class);
-        final ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter = context.mock(ArtifactsToModuleDescriptorConverter.class);
-        final ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class);
-        PublishModuleDescriptorConverter publishModuleDescriptorConverter = new PublishModuleDescriptorConverter(
-                resolveModuleDescriptorConverter,
-                artifactsToModuleDescriptorConverter);
-
-        context.checking(new Expectations() {{
-            allowing(resolveModuleDescriptorConverter).convert(configurationsDummy, moduleDummy, ivySettingsDummy);
-            will(returnValue(moduleDescriptorDummy));
-            one(moduleDescriptorDummy).addExtraAttributeNamespace(PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE_PREFIX,
-                    PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE);
-            one(artifactsToModuleDescriptorConverter).addArtifacts(moduleDescriptorDummy, configurationsDummy);
-        }});
-
-        DefaultModuleDescriptor actualModuleDescriptor = (DefaultModuleDescriptor)
-                publishModuleDescriptorConverter.convert(configurationsDummy, moduleDummy, ivySettingsDummy);
-
-        assertThat(actualModuleDescriptor, equalTo(moduleDescriptorDummy));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.java
deleted file mode 100644
index cbda0cc..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.api.internal.artifacts.ivyservice.moduleconverter;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.settings.IvySettings;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.Expectations;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class ResolveModuleDescriptorConverterTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    @Test
-    public void convert() {
-        final Set<Configuration> configurationsDummy = WrapUtil.toSet(context.mock(Configuration.class, "conf1"),
-                context.mock(Configuration.class, "conf2"));
-        final Module moduleDummy = context.mock(Module.class);
-        final IvySettings ivySettingsDummy = new IvySettings();
-        final DefaultModuleDescriptor moduleDescriptorDummy = context.mock(DefaultModuleDescriptor.class);
-        final ModuleDescriptorFactory moduleDescriptorFactoryStub = context.mock(ModuleDescriptorFactory.class);
-        final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverterMock = context.mock(ConfigurationsToModuleDescriptorConverter.class);
-        final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverterMock = context.mock(DependenciesToModuleDescriptorConverter.class);
-
-        ResolveModuleDescriptorConverter resolveModuleDescriptorConverter = new ResolveModuleDescriptorConverter(
-                moduleDescriptorFactoryStub,
-                configurationsToModuleDescriptorConverterMock,
-                dependenciesToModuleDescriptorConverterMock);
-
-        context.checking(new Expectations() {{
-            allowing(moduleDescriptorFactoryStub).createModuleDescriptor(moduleDummy);
-            will(returnValue(moduleDescriptorDummy));
-            one(configurationsToModuleDescriptorConverterMock).addConfigurations(moduleDescriptorDummy, configurationsDummy);
-            one(dependenciesToModuleDescriptorConverterMock).addDependencyDescriptors(moduleDescriptorDummy, configurationsDummy, ivySettingsDummy);
-        }});
-
-        DefaultModuleDescriptor actualModuleDescriptor = (DefaultModuleDescriptor)
-                resolveModuleDescriptorConverter.convert(configurationsDummy, moduleDummy, ivySettingsDummy);
-
-        assertThat(actualModuleDescriptor, equalTo(moduleDescriptorDummy));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
deleted file mode 100644
index d39231a..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
+++ /dev/null
@@ -1,137 +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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Matchers;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public abstract class AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    protected static final String TEST_CONF = "conf";
-    protected static final String TEST_DEP_CONF = "depconf1";
-    protected static final String TEST_OTHER_DEP_CONF = "depconf2";
-
-    private static final ExcludeRule TEST_EXCLUDE_RULE = new org.gradle.api.internal.artifacts.DefaultExcludeRule(WrapUtil.toMap("org", "testOrg"));
-    private static final org.apache.ivy.core.module.descriptor.ExcludeRule TEST_IVY_EXCLUDE_RULE = HelperUtil.getTestExcludeRule();
-    protected ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
-    protected final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(TEST_CONF));
-    private DefaultDependencyArtifact artifact = new DefaultDependencyArtifact("name", "type", null, null, null);
-    private DefaultDependencyArtifact artifactWithClassifiers = new DefaultDependencyArtifact("name2", "type2", "ext2", "classifier2", "http://www.url2.com");
-
-    private DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal;
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(excludeRuleConverterStub).createExcludeRule(TEST_CONF, TEST_EXCLUDE_RULE);
-            will(returnValue(TEST_IVY_EXCLUDE_RULE));
-        }});
-    }
-
-    protected void assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
-            ModuleDependency dependency1, ModuleDependency dependency2, DependencyDescriptorFactoryInternal factoryInternal
-    ) {
-        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency1);
-        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency2);
-        assertThat(moduleDescriptor.getDependencies().length, equalTo(1));
-
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_DEP_CONF));
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_OTHER_DEP_CONF));
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF).length, equalTo(2));
-    }
-
-    protected Dependency setUpDependency(ModuleDependency dependency) {
-        return dependency.addArtifact(artifact).
-                addArtifact(artifactWithClassifiers).
-                exclude(TEST_EXCLUDE_RULE.getExcludeArgs()).
-                setTransitive(true);
-    }
-
-    protected void assertDependencyDescriptorHasCommonFixtureValues(DefaultDependencyDescriptor dependencyDescriptor) {
-        assertThat(dependencyDescriptor.getParentRevisionId(), equalTo(moduleDescriptor.getModuleRevisionId()));
-        assertEquals(TEST_IVY_EXCLUDE_RULE, dependencyDescriptor.getExcludeRules(TEST_CONF)[0]);
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), equalTo(WrapUtil.toArray(TEST_DEP_CONF)));
-        assertThat(dependencyDescriptor.isTransitive(), equalTo(true));
-        assertDependencyDescriptorHasArtifacts(dependencyDescriptor);
-    }
-
-    private void assertDependencyDescriptorHasArtifacts(DefaultDependencyDescriptor dependencyDescriptor) {
-        List<DependencyArtifactDescriptor> artifactDescriptors = WrapUtil.toList(dependencyDescriptor.getDependencyArtifacts(TEST_CONF));
-        assertThat(artifactDescriptors.size(), equalTo(2));
-
-        
-        DependencyArtifactDescriptor artifactDescriptorWithoutClassifier = findDescriptor(artifactDescriptors, artifact);
-        assertEquals(new HashMap(), artifactDescriptorWithoutClassifier.getExtraAttributes());
-        assertEquals(null, artifactDescriptorWithoutClassifier.getUrl());
-        compareArtifacts(artifact, artifactDescriptorWithoutClassifier);
-        assertEquals(artifact.getType(), artifactDescriptorWithoutClassifier.getExt());
-
-        DependencyArtifactDescriptor artifactDescriptorWithClassifierAndConfs = findDescriptor(artifactDescriptors, artifactWithClassifiers);
-        assertEquals(WrapUtil.toMap(Dependency.CLASSIFIER, artifactWithClassifiers.getClassifier()), artifactDescriptorWithClassifierAndConfs.getQualifiedExtraAttributes());
-        compareArtifacts(artifactWithClassifiers, artifactDescriptorWithClassifierAndConfs);
-        assertEquals(artifactWithClassifiers.getExtension(), artifactDescriptorWithClassifierAndConfs.getExt());
-        try {
-            assertEquals(new URL(artifactWithClassifiers.getUrl()), artifactDescriptorWithClassifierAndConfs.getUrl());
-        } catch (MalformedURLException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private DependencyArtifactDescriptor findDescriptor(List<DependencyArtifactDescriptor> artifactDescriptors, DefaultDependencyArtifact dependencyArtifact) {
-        for (DependencyArtifactDescriptor artifactDescriptor : artifactDescriptors) {
-            if (artifactDescriptor.getName().equals(dependencyArtifact.getName())) {
-                return artifactDescriptor;
-            }
-        }
-        throw new RuntimeException("Descriptor could not be found");
-    }
-
-    private void compareArtifacts(DependencyArtifact artifact, DependencyArtifactDescriptor artifactDescriptor) {
-        assertEquals(artifact.getName(), artifactDescriptor.getName());
-        assertEquals(artifact.getType(), artifactDescriptor.getType());
-    }
-}
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
deleted file mode 100644
index 27808f5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
+++ /dev/null
@@ -1,98 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Matchers;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-import java.util.HashMap;
-
-/**
- * @author Hans Dockter
- */
-public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule = context.mock(ModuleDescriptorFactoryForClientModule.class);
-    private HashMap clientModuleRegistry = new HashMap();
-    private ClientModuleDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleDependencyDescriptorFactory(
-            excludeRuleConverterStub,
-            moduleDescriptorFactoryForClientModule,
-            clientModuleRegistry);
-
-    @Test
-    public void canConvert() {
-        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
-        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ClientModule.class)), Matchers.equalTo(true));
-    }
-
-    @Test
-    public void testAddDependencyDescriptorForClientModule() {
-        final ModuleDependency dependencyDependency = context.mock(ModuleDependency.class, "dependencyDependency");
-        final DefaultClientModule clientModule = new DefaultClientModule("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
-        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(
-                clientModule, WrapUtil.toMap(ClientModule.CLIENT_MODULE_KEY, clientModule.getId()));
-
-        setUpDependency(clientModule);
-        clientModule.addDependency(dependencyDependency);
-        final ModuleDescriptor moduleDescriptorForClientModule = context.mock(ModuleDescriptor.class);
-        context.checking(new Expectations() {{
-            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
-                    testModuleRevisionId,
-                    WrapUtil.toSet(dependencyDependency)
-            );
-            will(returnValue(moduleDescriptorForClientModule));
-        }});
-
-        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
-        assertThat((ModuleDescriptor) clientModuleRegistry.get(clientModule.getId()), equalTo(moduleDescriptorForClientModule));
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertEquals(testModuleRevisionId,
-                dependencyDescriptor.getDependencyRevisionId());
-        assertFalse(dependencyDescriptor.isChanging());
-    }
-
-    @Test
-    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
-        ClientModule clientModule = new DefaultClientModule(null, "gradle-core", null, TEST_DEP_CONF);
-        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(
-                clientModule, WrapUtil.toMap(ClientModule.CLIENT_MODULE_KEY, clientModule.getId()));
-        context.checking(new Expectations() {{
-            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
-                    testModuleRevisionId,
-                    WrapUtil.<ModuleDependency>toSet()
-            );
-        }});
-
-        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(testModuleRevisionId));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
deleted file mode 100644
index 2536759..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
+++ /dev/null
@@ -1,134 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.conflict.ConflictManager;
-import org.apache.ivy.plugins.conflict.LatestConflictManager;
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.DefaultExcludeRule;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.IvyConverterTestUtil;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collections;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultDependenciesToModuleDescriptorConverterTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private static final ExcludeRule GRADLE_EXCLUDE_RULE_DUMMY_1 = new DefaultExcludeRule(toMap("org", "testOrg"));
-    private static final ExcludeRule GRADLE_EXCLUDE_RULE_DUMMY_2 = new DefaultExcludeRule(toMap("org2", "testOrg2"));
-
-    private ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
-    private ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
-    private ModuleDependency similarDependency1 = HelperUtil.createDependency("group", "name", "version");
-    private ModuleDependency similarDependency2 = HelperUtil.createDependency("group", "name", "version");
-    private ModuleDependency similarDependency3 = HelperUtil.createDependency("group", "name", "version");
-    private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub1 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule1");
-    private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub2 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule2");
-
-    private DependencyDescriptorFactory dependencyDescriptorFactoryStub = context.mock(DependencyDescriptorFactory.class);
-    private ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
-    private IvySettings ivySettingsDummy = new IvySettings();
-
-    @Test
-    public void testAddDependencyDescriptors() {
-        DefaultDependenciesToModuleDescriptorConverter converter = new DefaultDependenciesToModuleDescriptorConverter(
-                dependencyDescriptorFactoryStub, excludeRuleConverterStub);
-        Configuration configurationStub1 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf1", GRADLE_EXCLUDE_RULE_DUMMY_1, dependencyDummy1, similarDependency1);
-        Configuration configurationStub2 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf2", GRADLE_EXCLUDE_RULE_DUMMY_2, dependencyDummy2, similarDependency2);
-        Configuration configurationStub3 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf3", null, similarDependency3);
-        DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(toSet(configurationStub1.getName(),
-                configurationStub2.getName()));
-        associateDependencyWithDescriptor(dependencyDummy1, moduleDescriptor, configurationStub1);
-        associateDependencyWithDescriptor(dependencyDummy2, moduleDescriptor, configurationStub2);
-        associateDependencyWithDescriptor(similarDependency1, moduleDescriptor, configurationStub1);
-        associateDependencyWithDescriptor(similarDependency2, moduleDescriptor, configurationStub2);
-        associateDependencyWithDescriptor(similarDependency3, moduleDescriptor, configurationStub3);
-        associateGradleExcludeRuleWithIvyExcludeRule(GRADLE_EXCLUDE_RULE_DUMMY_1, ivyExcludeRuleStub1, configurationStub1);
-        associateGradleExcludeRuleWithIvyExcludeRule(GRADLE_EXCLUDE_RULE_DUMMY_2, ivyExcludeRuleStub2, configurationStub2);
-
-        converter.addDependencyDescriptors(moduleDescriptor, toSet(configurationStub1, configurationStub2, configurationStub3),
-                ivySettingsDummy);
-                
-        assertThat(moduleDescriptor.getExcludeRules(toArray(configurationStub1.getName())), equalTo(toArray(
-                ivyExcludeRuleStub1)));
-        assertThat(moduleDescriptor.getExcludeRules(toArray(configurationStub2.getName())), equalTo(toArray(
-                ivyExcludeRuleStub2)));
-        assertIsCorrectConflictResolver(moduleDescriptor);
-        
-    }
-
-    private void assertIsCorrectConflictResolver(DefaultModuleDescriptor moduleDescriptor) {
-        ConflictManager conflictManager = moduleDescriptor.getConflictManager(new ModuleId(ExactPatternMatcher.ANY_EXPRESSION, ExactPatternMatcher.ANY_EXPRESSION));
-        assertThat(conflictManager, instanceOf(LatestConflictManager.class));
-        assertThat(((LatestConflictManager) conflictManager).getSettings(), equalTo(ivySettingsDummy));
-
-    }
-
-    private void associateGradleExcludeRuleWithIvyExcludeRule(final ExcludeRule gradleExcludeRule,
-                                                              final org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule,
-                                                              final Configuration configuration) {
-        final String expectedConfigurationName = configuration.getName();
-        context.checking(new Expectations() {{
-            allowing(excludeRuleConverterStub).createExcludeRule(expectedConfigurationName, gradleExcludeRule);
-            will(returnValue(ivyExcludeRule));
-
-            allowing(ivyExcludeRule).getConfigurations();
-            will(returnValue(WrapUtil.toArray(configuration.getName())));
-        }});
-    }
-
-    private void associateDependencyWithDescriptor(final ModuleDependency dependency, final DefaultModuleDescriptor parent,
-                                                   final Configuration configuration) {
-        final String configurationName = configuration.getName();
-        context.checking(new Expectations() {{
-            allowing(dependencyDescriptorFactoryStub).addDependencyDescriptor(with(equal(configurationName)),
-                    with(equal(parent)), with(sameInstance(dependency)));
-        }});
-    }
-    
-    private Configuration createNamedConfigurationStubWithDependenciesAndExcludeRules(final String name, final ExcludeRule excludeRule,
-                                                                                      final ModuleDependency... dependencies) {
-        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(name, context);
-        context.checking(new Expectations() {{
-            allowing(configurationStub).getDependencies(ModuleDependency.class);
-            will(returnValue(toSet(dependencies)));    
-
-            allowing(configurationStub).getExcludeRules();
-            will(returnValue(excludeRule == null ? Collections.emptySet() : toSet(excludeRule)));
-        }});
-        return configurationStub;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
deleted file mode 100644
index 5faf335..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
+++ /dev/null
@@ -1,92 +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.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultModuleDescriptorFactoryForClientModuleTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    @Test
-    public void testCreateModuleDescriptor() {
-        DependencyDescriptor dependencyDescriptorDummy = context.mock(DependencyDescriptor.class);
-        DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy = new DependencyDescriptorFactorySpy(dependencyDescriptorDummy);
-        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory =
-                new DefaultModuleDescriptorFactoryForClientModule();
-        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactorySpy);
-        ModuleDependency dependencyMock = context.mock(ModuleDependency.class);
-        final ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance("org", "name", "version");
-
-        ModuleDescriptor moduleDescriptor = clientModuleDescriptorFactory.createModuleDescriptor(
-                moduleRevisionId,
-                WrapUtil.toSet(dependencyMock));
-
-        assertThat(moduleDescriptor.getModuleRevisionId(), equalTo(moduleRevisionId));
-        assertThatDescriptorHasOnlyDefaultConfiguration(moduleDescriptor);
-        assertCorrectCallToDependencyDescriptorFactory(dependencyDescriptorFactorySpy, Dependency.DEFAULT_CONFIGURATION, moduleDescriptor, dependencyMock);
-    }
-
-    private void assertThatDescriptorHasOnlyDefaultConfiguration(ModuleDescriptor moduleDescriptor) {
-        assertThat(moduleDescriptor.getConfigurations().length, equalTo(1));
-        assertThat(moduleDescriptor.getConfigurationsNames()[0], equalTo(Dependency.DEFAULT_CONFIGURATION));
-    }
-
-    private void assertCorrectCallToDependencyDescriptorFactory(DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy,
-                                                                String configuration,
-                                                                ModuleDescriptor parent,
-                                                                Dependency dependency) {
-        assertThat(dependencyDescriptorFactorySpy.configuration, equalTo(configuration));
-        assertThat(dependencyDescriptorFactorySpy.parent, equalTo(parent));
-        assertThat(dependencyDescriptorFactorySpy.dependency, equalTo(dependency));
-    }
-
-    private static class DependencyDescriptorFactorySpy implements DependencyDescriptorFactory {
-        DependencyDescriptor dependencyDescriptor;
-
-        String configuration;
-        ModuleDescriptor parent;
-        Dependency dependency;
-
-        private DependencyDescriptorFactorySpy(DependencyDescriptor dependencyDescriptor) {
-            this.dependencyDescriptor = dependencyDescriptor;
-        }
-
-        public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
-                                            ModuleDependency dependency) {
-            this.configuration = configuration;
-            this.parent = moduleDescriptor;
-            this.dependency = dependency;
-        }
-
-        public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-            // do nothing
-            return null;
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
deleted file mode 100644
index f927ab0..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
+++ /dev/null
@@ -1,127 +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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
-import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.hamcrest.Matchers;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    ExternalModuleDependencyDescriptorFactory externalModuleDependencyDescriptorFactory =
-            new ExternalModuleDependencyDescriptorFactory(excludeRuleConverterStub);
-    
-    @Test
-    public void canConvert() {
-        assertThat(externalModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
-        assertThat(externalModuleDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), Matchers.equalTo(true));
-    }
-
-    @Test
-    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
-        ModuleDependency dependency = new DefaultExternalModuleDependency(null, "gradle-core", null, TEST_DEP_CONF);
-        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(IvyUtil.createModuleRevisionId(dependency)));
-    }
-
-    @Test
-    public void testCreateFromModuleDependency() {
-        DefaultExternalModuleDependency moduleDependency = new DefaultExternalModuleDependency("org.gradle",
-                "gradle-core", "1.0", TEST_DEP_CONF);
-        setUpDependency(moduleDependency);
-
-        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor,
-                moduleDependency);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor
-                .getDependencies()[0];
-
-        assertEquals(moduleDependency.isChanging(), dependencyDescriptor.isChanging());
-        assertEquals(dependencyDescriptor.isForce(), moduleDependency.isForce());
-        assertEquals(IvyUtil.createModuleRevisionId(moduleDependency), dependencyDescriptor.getDependencyRevisionId());
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-    }
-
-    @Test
-    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor() {
-        ModuleDependency dependency1 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
-        ModuleDependency dependency2 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_OTHER_DEP_CONF);
-
-        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
-                dependency1, dependency2, externalModuleDependencyDescriptorFactory
-        );
-    }
-
-    @Test
-    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndSameClassifiersShouldBePartOfSameDependencyDescriptor() {
-        ExternalDependency dependency1 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency1, null, "jdk14");
-
-        ExternalDependency dependency2 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_OTHER_DEP_CONF);
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency2, null, "jdk14");
-
-        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
-                dependency1, dependency2, externalModuleDependencyDescriptorFactory
-        );
-    }
-
-
-    @Test
-    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndDifferentClassifiersShouldNotBePartOfSameDependencyDescriptor() {
-        ExternalDependency dependency1 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency1, null, "jdk14");
-
-        ExternalDependency dependency2 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_OTHER_DEP_CONF);
-        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency2, null, "jdk15");
-
-        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentClassifiersShouldNotBePartOfSameDependencyDescriptor(
-                dependency1, dependency2, externalModuleDependencyDescriptorFactory
-        );
-    }
-
-    private void assertThataddDependenciesWithSameModuleRevisionIdAndDifferentClassifiersShouldNotBePartOfSameDependencyDescriptor(
-            ModuleDependency dependency1, ModuleDependency dependency2, DependencyDescriptorFactoryInternal factoryInternal
-    ) {
-        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency1);
-        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency2);
-        assertThat(moduleDescriptor.getDependencies().length, equalTo(2));
-
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_DEP_CONF));
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF).length, equalTo(1));
-
-        dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[1];
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_OTHER_DEP_CONF));
-        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF).length, equalTo(1));
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
deleted file mode 100644
index 57d33de..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
+++ /dev/null
@@ -1,127 +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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.api.internal.project.AbstractProject;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private ProjectDependencyDescriptorStrategy descriptorStrategyStub = context.mock(ProjectDependencyDescriptorStrategy.class);
-    private ProjectDependencyDescriptorFactory projectDependencyDescriptorFactory =
-            new ProjectDependencyDescriptorFactory(excludeRuleConverterStub, descriptorStrategyStub);
-
-    @Test
-    public void canConvert() {
-        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
-        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
-    }
-
-    @Test
-    public void testCreateFromProjectDependency() {
-        final ModuleRevisionId someModuleRevisionId = ModuleRevisionId.newInstance("a", "b", "c");
-        final ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
-        setUpDependency(projectDependency);
-        context.checking(new Expectations() {{
-            allowing(descriptorStrategyStub).createModuleRevisionId(projectDependency);
-            will(returnValue(someModuleRevisionId));
-            allowing(descriptorStrategyStub).isChanging();
-            will(returnValue(true));
-        }});
-        projectDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, projectDependency);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertTrue(dependencyDescriptor.isChanging());
-        assertFalse(dependencyDescriptor.isForce());
-        assertEquals(someModuleRevisionId,
-                dependencyDescriptor.getDependencyRevisionId());
-    }
-
-    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
-        AbstractProject dependencyProject = HelperUtil.createRootProject();
-        dependencyProject.setGroup("someGroup");
-        dependencyProject.setVersion("someVersion");
-        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, new ProjectDependenciesBuildInstruction(true));
-    }
-
-    @Test
-    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor() {
-        final ProjectDependency dependency1 = createProjectDependency(TEST_DEP_CONF);
-        final ProjectDependency dependency2 = createProjectDependency(TEST_OTHER_DEP_CONF);
-
-        context.checking(new Expectations() {{
-            allowing(descriptorStrategyStub).isChanging();
-            will(returnValue(true));
-            allowing(descriptorStrategyStub).createModuleRevisionId(dependency1);
-            will(returnValue(IvyUtil.createModuleRevisionId(dependency1)));
-            allowing(descriptorStrategyStub).createModuleRevisionId(dependency2);
-            will(returnValue(IvyUtil.createModuleRevisionId(dependency2)));
-        }});
-        
-        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
-                dependency1, dependency2, projectDependencyDescriptorFactory
-        );
-    }
-
-    @Test
-    public void ivyFileModuleRevisionIdShouldBeDeterminedByModuleForPublicDescriptorWithoutExtraAttributes() {
-        ProjectDependency projectDependency = createProjectDependency(TEST_CONF);
-        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
-        ModuleRevisionId moduleRevisionId =
-                ProjectDependencyDescriptorFactory.IVY_FILE_DESCRIPTOR_STRATEGY.createModuleRevisionId(projectDependency);
-        assertThat(moduleRevisionId.getOrganisation(), equalTo(module.getGroup()));
-        assertThat(moduleRevisionId.getName(), equalTo(module.getName()));
-        assertThat(moduleRevisionId.getRevision(), equalTo(module.getVersion()));
-        assertThat(moduleRevisionId.getExtraAttributes(), equalTo((Map) new HashMap()));
-    }
-
-    @Test
-    public void resolveModuleRevisionIdShouldBeDeterminedByModuleForResolvePlusExtraAttributes() {
-        ProjectDependency projectDependency = createProjectDependency(TEST_CONF);
-        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
-        ModuleRevisionId moduleRevisionId =
-                ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY.createModuleRevisionId(projectDependency);
-        assertThat(moduleRevisionId.getOrganisation(), equalTo(module.getGroup()));
-        assertThat(moduleRevisionId.getName(), equalTo(module.getName()));
-        assertThat(moduleRevisionId.getRevision(), equalTo(module.getVersion()));
-        assertThat(moduleRevisionId.getExtraAttributes(),
-                equalTo((Map) WrapUtil.toMap(DependencyDescriptorFactory.PROJECT_PATH_KEY, projectDependency.getDependencyProject().getPath())));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainerTest.java
deleted file mode 100644
index 9b51701..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainerTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2007-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;
-
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.assertThat;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultArtifactContainerTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private DefaultArtifactContainer artifactContainer = new DefaultArtifactContainer();
-
-    @Test
-    public void addGetArtifactsWithArtifactInstance() {
-        PublishArtifact publishArtifact1 = context.mock(PublishArtifact.class, "artifact1");
-        PublishArtifact publishArtifact2 = context.mock(PublishArtifact.class, "artifact2");
-        artifactContainer.addArtifacts(publishArtifact1);
-        assertThat(artifactContainer.getArtifacts(), equalTo(WrapUtil.toSet(publishArtifact1)));
-        artifactContainer.addArtifacts(publishArtifact2);
-        assertThat(artifactContainer.getArtifacts(), equalTo(WrapUtil.toSet(publishArtifact1, publishArtifact2)));
-    }
-
-    @Test
-    public void getArtifactsWithFilterSpec() {
-        final PublishArtifact publishArtifact1 = context.mock(PublishArtifact.class, "artifact1");
-        final PublishArtifact publishArtifact2 = context.mock(PublishArtifact.class, "artifact2");
-        artifactContainer.addArtifacts(publishArtifact1);
-        artifactContainer.addArtifacts(publishArtifact2);
-        final boolean[] artifact1Offered = new boolean[]{false};
-        final boolean[] artifact2Offered = new boolean[]{false};
-        Set<PublishArtifact> actualArtifacts = artifactContainer.getArtifacts(new Spec<PublishArtifact>() {
-            public boolean isSatisfiedBy(PublishArtifact filterCandidate) {
-                if (filterCandidate == publishArtifact1) {
-                    artifact1Offered[0] = true;
-                }
-                if (filterCandidate == publishArtifact2) {
-                    artifact2Offered[0] = true;
-                }
-                return filterCandidate == publishArtifact1;
-            }
-        });
-        assertThat(actualArtifacts, equalTo(WrapUtil.toSet(publishArtifact1)));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
index 4a3163e..e1a857f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
@@ -18,18 +18,19 @@ package org.gradle.api.internal.artifacts.publish;
 
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Matchers;
 import org.jmock.integration.junit4.JMock;
-import static org.junit.Assert.assertThat;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Set;
 
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
 /**
-* @author Hans Dockter
-*/
+ * @author Hans Dockter
+ */
 @RunWith(JMock.class)
 public class DefaultPublishArtifactTest extends AbstractPublishArtifactTest {
     protected PublishArtifact createPublishArtifact(String classifier) {
@@ -42,7 +43,24 @@ public class DefaultPublishArtifactTest extends AbstractPublishArtifactTest {
         Task task2 = context.mock(Task.class, "task2");
         DefaultPublishArtifact publishArtifact = new DefaultPublishArtifact(getTestName(), getTestExt(), getTestType(),
                 getTestClassifier(), getDate(), getTestFile(), task1, task2);
-        assertThat((Set<Task>) publishArtifact.getBuildDependencies().getDependencies(null), Matchers.equalTo(WrapUtil.toSet(task1, task2)));
+        assertThat((Set<Task>) publishArtifact.getBuildDependencies().getDependencies(null), equalTo(toSet(task1, task2)));
         assertCommonPropertiesAreSet(publishArtifact, true);
     }
+
+    @Test
+    public void canSpecifyTheBuilderTasksOnConstruction() {
+        Task task = context.mock(Task.class);
+        DefaultPublishArtifact publishArtifact = new DefaultPublishArtifact("name", "extension", "type", null, null, null, task);
+
+        assertThat((Set<Task>)publishArtifact.getBuildDependencies().getDependencies(null), equalTo(toSet(task)));
+    }
+    
+    @Test
+    public void canSpecifyTheBuilderTasks() {
+        Task task = context.mock(Task.class);
+        DefaultPublishArtifact publishArtifact = new DefaultPublishArtifact("name", "extension", "type", null, null, null);
+        publishArtifact.builtBy(task);
+
+        assertThat((Set<Task>) publishArtifact.getBuildDependencies().getDependencies(null), equalTo(toSet(task)));
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/cache/MapBackedCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/cache/MapBackedCacheTest.groovy
new file mode 100644
index 0000000..67392b2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/cache/MapBackedCacheTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.cache
+
+import org.gradle.internal.Factory
+
+import spock.lang.*
+
+class MapBackedCacheTest extends Specification {
+    
+    def cache(map = [:]) {
+        new MapBackedCache(map)
+    }
+
+    def "has cache semantics"() {
+        given:
+        def map = [:]
+        def cache = cache(map)
+        
+        expect:
+        cache.get("a", { 1 } as Factory) == 1
+        cache.get("a", { 2 } as Factory) == 1
+        
+        and:
+        map.a == 1
+        
+        when:
+        map.clear()
+        
+        then:
+        cache.get("a", { 2 } as Factory) == 2
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy
index 6b17dc8..94a6498 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CacheBackedFileSnapshotRepositoryTest.groovy
@@ -15,16 +15,18 @@
  */
 package org.gradle.api.internal.changedetection
 
-import spock.lang.Specification
-import org.gradle.cache.CacheRepository
-import org.gradle.cache.CacheBuilder
-import org.gradle.cache.PersistentCache
 import org.gradle.cache.PersistentIndexedCache
+import spock.lang.Specification
 
 class CacheBackedFileSnapshotRepositoryTest extends Specification {
-    final CacheRepository cacheRepository = Mock()
+    final TaskArtifactStateCacheAccess cacheAccess = Mock()
     final PersistentIndexedCache<Object, Object> indexedCache = Mock()
-    final FileSnapshotRepository repository = new CacheBackedFileSnapshotRepository(cacheRepository)
+    FileSnapshotRepository repository
+
+    def setup() {
+        1 * cacheAccess.createCache("fileSnapshots", Object, Object) >> indexedCache
+        repository = new CacheBackedFileSnapshotRepository(cacheAccess)
+    }
 
     def "assigns an id when a snapshot is added"() {
         FileCollectionSnapshot snapshot = Mock()
@@ -34,9 +36,6 @@ class CacheBackedFileSnapshotRepositoryTest extends Specification {
 
         then:
         id == 4
-        interaction {
-            expectCacheOpened()
-        }
         1 * indexedCache.get("nextId") >> (4 as Long)
         1 * indexedCache.put("nextId", 5)
         1 * indexedCache.put(4, snapshot)
@@ -51,9 +50,6 @@ class CacheBackedFileSnapshotRepositoryTest extends Specification {
 
         then:
         result == snapshot
-        interaction {
-            expectCacheOpened()
-        }
         1 * indexedCache.get(4) >> snapshot
         0 * _._
     }
@@ -63,18 +59,7 @@ class CacheBackedFileSnapshotRepositoryTest extends Specification {
         repository.remove(4)
 
         then:
-        interaction {
-            expectCacheOpened()
-        }
         1 * indexedCache.remove(4)
         0 * _._
     }
-
-    def expectCacheOpened() {
-        CacheBuilder builder = Mock()
-        PersistentCache cache = Mock()
-        1 * cacheRepository.cache("fileSnapshots") >> builder
-        1 * builder.open() >> cache
-        1 * cache.openIndexedCache() >> indexedCache
-    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
index 76c6ca5..55baf9f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
@@ -15,22 +15,23 @@
  */
 package org.gradle.api.internal.changedetection;
 
-import org.gradle.cache.*;
-
-import static org.gradle.util.Matchers.*;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
 import org.gradle.util.TemporaryFolder;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.Before;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 
+import static org.gradle.util.Matchers.reflectionEquals;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
 @RunWith(JMock.class)
 public class CachingHasherTest {
     @Rule
@@ -39,7 +40,7 @@ public class CachingHasherTest {
     private final Hasher delegate = context.mock(Hasher.class);
     private final PersistentIndexedCache<File, CachingHasher.FileInfo> cache = context.mock(
             PersistentIndexedCache.class);
-    private final CacheRepository cacheRepository = context.mock(CacheRepository.class);
+    private final TaskArtifactStateCacheAccess cacheAccess = context.mock(TaskArtifactStateCacheAccess.class);
     private final byte[] hash = "hash".getBytes();
     private final File file = tmpDir.createFile("testfile").write("content");
     private CachingHasher hasher;
@@ -47,19 +48,10 @@ public class CachingHasherTest {
     @Before
     public void setup() {
         context.checking(new Expectations(){{
-            CacheBuilder cacheBuilder = context.mock(CacheBuilder.class);
-            PersistentCache persistentCache = context.mock(PersistentCache.class);
-
-            one(cacheRepository).cache("fileHashes");
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).open();
-            will(returnValue(persistentCache));
-
-            one(persistentCache).openIndexedCache(with(notNullValue(Serializer.class)));
+            one(cacheAccess).createCache(with(equalTo("fileHashes")), with(equalTo(File.class)), with(notNullValue(Class.class)), with(notNullValue(Serializer.class)));
             will(returnValue(cache));
         }});
-        hasher = new CachingHasher(delegate, cacheRepository);
+        hasher = new CachingHasher(delegate, cacheAccess);
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccessTest.groovy
new file mode 100644
index 0000000..afbbad1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccessTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.changedetection
+
+import org.gradle.cache.CacheRepository
+import spock.lang.Specification
+import org.gradle.api.internal.GradleInternal
+import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.PersistentIndexedCache
+
+class DefaultTaskArtifactStateCacheAccessTest extends Specification {
+    final GradleInternal gradle = Mock()
+    final CacheRepository cacheRepository = Mock()
+    final DefaultTaskArtifactStateCacheAccess cacheAccess = new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository)
+    
+    def "opens backing cache on first use"() {
+        DirectoryCacheBuilder cacheBuilder = Mock()
+        PersistentCache backingCache = Mock()
+        PersistentIndexedCache<String, Integer> backingIndexedCache = Mock()
+
+        when:
+        def indexedCache = cacheAccess.createCache("some-cache", String, Integer)
+
+        then:
+        0 * _._
+
+        when:
+        indexedCache.get("key")
+
+        then:
+        1 * cacheRepository.cache("taskArtifacts") >> cacheBuilder
+        1 * cacheBuilder.open() >> backingCache
+        _ * cacheBuilder._ >> cacheBuilder
+        _ * backingCache.baseDir >> new File("baseDir")
+        1 * backingCache.createCache(new File("baseDir/some-cache.bin"), String, Integer) >> backingIndexedCache
+        1 * backingIndexedCache.get("key")
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
index c632c1b..c784909 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
@@ -16,35 +16,34 @@
 
 package org.gradle.api.internal.changedetection;
 
+import org.gradle.CacheUsage;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.invocation.Gradle;
-import org.gradle.cache.*;
-import org.gradle.util.*;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.internal.DefaultCacheRepository;
+import org.gradle.testfixtures.internal.InMemoryCacheFactory;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.RandomLongIdGenerator;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
 import org.hamcrest.Matcher;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-import java.io.*;
+import java.io.File;
 import java.util.*;
 
-import static org.gradle.util.Matchers.*;
+import static org.gradle.util.Matchers.isEmpty;
 import static org.gradle.util.WrapUtil.*;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
- at RunWith(JMock.class)
 public class DefaultTaskArtifactStateRepositoryTest {
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final CacheRepository cacheRepository = context.mock(CacheRepository.class);
     private final ProjectInternal project = HelperUtil.createRootProject();
     private final Gradle gradle = project.getGradle();
     private final TestFile outputFile = tmpDir.file("output-file");
@@ -60,33 +59,20 @@ public class DefaultTaskArtifactStateRepositoryTest {
     private final Set<TestFile> inputFiles = toSet(inputFile, inputDir, missingInputFile);
     private final Set<TestFile> outputFiles = toSet(outputFile, outputDir, emptyOutputDir, missingOutputFile);
     private final Set<TestFile> createFiles = toSet(outputFile, outputDirFile, outputDirFile2);
-    private final PersistentCache persistentCache = context.mock(PersistentCache.class);
     private DefaultTaskArtifactStateRepository repository;
 
     @Before
     public void setup() {
-        context.checking(new Expectations() {{
-            CacheBuilder builder = context.mock(CacheBuilder.class);
-
-            one(cacheRepository).cache("outputFileStates");
-            will(returnValue(builder));
-
-            one(builder).open();
-            will(returnValue(persistentCache));
-
-            one(persistentCache).openIndexedCache();
-            will(returnValue(new InMemoryIndexedCache()));
-        }});
-
+        CacheRepository cacheRepository = new DefaultCacheRepository(tmpDir.createDir("user-home"), null, CacheUsage.ON, new InMemoryCacheFactory());
+        TaskArtifactStateCacheAccess cacheAccess = new DefaultTaskArtifactStateCacheAccess(gradle, cacheRepository);
         FileSnapshotter inputFilesSnapshotter = new DefaultFileSnapshotter(new DefaultHasher());
-        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(inputFilesSnapshotter, new RandomLongIdGenerator(), cacheRepository);
-        repository = new DefaultTaskArtifactStateRepository(cacheRepository, inputFilesSnapshotter, outputFilesSnapshotter);
+        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(inputFilesSnapshotter, new RandomLongIdGenerator(), cacheAccess);
+        TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(cacheAccess, new CacheBackedFileSnapshotRepository(cacheAccess));
+        repository = new DefaultTaskArtifactStateRepository(taskHistoryRepository, inputFilesSnapshotter, outputFilesSnapshotter);
     }
 
     @Test
     public void artifactsAreNotUpToDateWhenCacheIsEmpty() {
-        expectEmptyCacheLocated();
-
         TaskArtifactState state = repository.getStateFor(task());
         assertNotNull(state);
         assertFalse(state.isUpToDate());
@@ -310,7 +296,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
 
     @Test
     public void artifactsAreNotUpToDateWhenStateHasNotBeenUpdated() {
-        expectEmptyCacheLocated();
         repository.getStateFor(task());
 
         TaskArtifactState state = repository.getStateFor(task());
@@ -321,7 +306,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
     public void artifactsAreNotUpToDateWhenOutputDirWhichUsedToExistHasBeenDeleted() {
         // Output dir already exists before first execution of task
         outputDirFile.createFile();
-        expectEmptyCacheLocated();
 
         TaskInternal task1 = builder().withOutputFiles(outputDir).createsFiles(outputDirFile).task();
         TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).createsFiles(outputDirFile2).task();
@@ -376,8 +360,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
 
     @Test
     public void hasEmptyTaskHistoryWhenTaskHasNeverBeenExecuted() {
-        expectEmptyCacheLocated();
-
         TaskArtifactState state = repository.getStateFor(task());
         assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), isEmpty());
     }
@@ -444,8 +426,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
 
     @Test
     public void considersExistingFileInOutputDirectoryWhichIsUpdatedByTheTaskAsProducedByTask() {
-        expectEmptyCacheLocated();
-
         TestFile otherFile = outputDir.file("other").createFile();
 
         TaskInternal task = task();
@@ -461,7 +441,7 @@ public class DefaultTaskArtifactStateRepositoryTest {
 
         state = repository.getStateFor(task());
         assertFalse(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), (Matcher) hasItem(otherFile));
+        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), hasItem((File) otherFile));
     }
 
     @Test
@@ -478,7 +458,7 @@ public class DefaultTaskArtifactStateRepositoryTest {
 
         state = repository.getStateFor(task());
         assertTrue(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), (Matcher) not(hasItem(outputDirFile)));
+        assertThat(state.getExecutionHistory().getOutputFiles().getFiles(), not(hasItem((File) outputDirFile)));
         state.afterTask();
     }
 
@@ -497,25 +477,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
     }
 
     @Test
-    public void artifactsAreNotUpToDateWhenTaskDoesNotProduceAnyOutputs() {
-        TaskInternal task = builder().doesNotProduceOutput().task();
-        execute(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-    }
-
-    @Test
-    public void taskHistoryIsEmptyWhenTaskDoesNotProduceAnyOutout() {
-        TaskInternal task = builder().doesNotProduceOutput().task();
-        execute(task);
-
-        TaskArtifactState state = repository.getStateFor(task);
-        assertFalse(state.isUpToDate());
-        assertThat(state.getExecutionHistory().getOutputFiles(), isEmpty());
-    }
-
-    @Test
     public void artifactsAreUpToDateWhenTaskHasNoInputFiles() {
         TaskInternal task = builder().withInputFiles().task();
         execute(task);
@@ -556,7 +517,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
     }
 
     private void execute(TaskInternal... tasks) {
-        expectEmptyCacheLocated();
         for (TaskInternal task : tasks) {
             TaskArtifactState state = repository.getStateFor(task);
             state.isUpToDate();
@@ -565,34 +525,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
         }
     }
 
-    private void expectEmptyCacheLocated() {
-        context.checking(new Expectations() {{
-            CacheBuilder tasksCacheBuilder = context.mock(CacheBuilder.class);
-            CacheBuilder fileSnapshotCacheBuilder = context.mock(CacheBuilder.class);
-
-            one(cacheRepository).cache("taskArtifacts");
-            will(returnValue(tasksCacheBuilder));
-
-            one(tasksCacheBuilder).forObject(gradle);
-            will(returnValue(tasksCacheBuilder));
-
-            one(tasksCacheBuilder).open();
-            will(returnValue(persistentCache));
-
-            atMost(1).of(cacheRepository).cache("fileSnapshots");
-            will(returnValue(fileSnapshotCacheBuilder));
-
-            atMost(1).of(fileSnapshotCacheBuilder).open();
-            will(returnValue(persistentCache));
-
-            one(persistentCache).openIndexedCache(with(notNullValue(Serializer.class)));
-            will(returnValue(new InMemoryIndexedCache()));
-
-            atMost(1).of(persistentCache).openIndexedCache();
-            will(returnValue(new InMemoryIndexedCache()));
-        }});
-    }
-
     private TaskInternal task() {
         return builder().task();
     }
@@ -640,11 +572,6 @@ public class DefaultTaskArtifactStateRepositoryTest {
             return this;
         }
 
-        public TaskBuilder doesNotProduceOutput() {
-            outputs = null;
-            return this;
-        }
-
         public TaskBuilder withProperty(String name, Object value) {
             inputProperties.put(name, value);
             return this;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
old mode 100644
new mode 100755
index 7d9caba..5be392d
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
@@ -24,7 +24,6 @@ import org.gradle.api.specs.Spec;
 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;
 
@@ -36,25 +35,27 @@ public class ShortCircuitTaskArtifactStateRepositoryTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final StartParameter startParameter = new StartParameter();
     private final TaskArtifactStateRepository delegate = context.mock(TaskArtifactStateRepository.class);
-    private final TaskInternal task = context.mock(TaskInternal.class);
     private final TaskArtifactState taskArtifactState = context.mock(TaskArtifactState.class);
     private final TaskOutputsInternal taskOutputsInternal = context.mock(TaskOutputsInternal.class);
     private final Spec<Task> upToDateSpec = context.mock(Spec.class);
     private final ShortCircuitTaskArtifactStateRepository repository = new ShortCircuitTaskArtifactStateRepository(startParameter, delegate);
 
-    @Before
-    public void setup() {
-        context.checking(new Expectations() {{
-            allowing(task).getOutputs();
-            will(returnValue(taskOutputsInternal));
-            allowing(taskOutputsInternal).getUpToDateSpec();
-            will(returnValue(upToDateSpec));
-        }});
-    }
+    @Test
+    public void doesNotCreateStateObjectWhenTaskHasNotDeclaredAnyOutputs() {
+        TaskInternal task = taskWithNoOutputs();
+        TaskArtifactState state = repository.getStateFor(task);
+        assertNotNull(state);
 
+        assertFalse(state.isUpToDate());
+        state.beforeTask();
+        state.afterTask();
+        state.finished();
+    }
+    
     @Test
-    public void delegatesToBackingRepositoryToCreateStateObject() {
-        expectTaskStateCreated();
+    public void delegatesToBackingRepositoryToCreateStateObjectForTaskThatHasDeclaredSomeOutputs() {
+        TaskInternal task = taskWithOutputs();
+        expectTaskStateCreated(task);
 
         TaskArtifactState state = repository.getStateFor(task);
         assertNotNull(state);
@@ -76,8 +77,9 @@ public class ShortCircuitTaskArtifactStateRepositoryTest {
     }
 
     @Test
-    public void taskArtifactsAreOutOfDateWhenStartParameterOverrideIsSet() {
-        expectTaskStateCreated();
+    public void taskArtifactsAreOutOfDateWhenStartParameterOverrideNoOptIsSet() {
+        TaskInternal task = taskWithOutputs();
+        expectTaskStateCreated(task);
 
         TaskArtifactState state = repository.getStateFor(task);
 
@@ -86,8 +88,20 @@ public class ShortCircuitTaskArtifactStateRepositoryTest {
     }
 
     @Test
+    public void taskArtifactsAreOutOfDateWhenStartParameterOverrideRerunTasksIsSet() {
+        TaskInternal task = taskWithOutputs();
+        expectTaskStateCreated(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+
+        startParameter.setRerunTasks(true);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
     public void taskArtifactsAreOutOfDateWhenUpToDateSpecIsFalse() {
-        expectTaskStateCreated();
+        final TaskInternal task = taskWithOutputs();
+        expectTaskStateCreated(task);
 
         TaskArtifactState state = repository.getStateFor(task);
 
@@ -101,7 +115,8 @@ public class ShortCircuitTaskArtifactStateRepositoryTest {
 
     @Test
     public void determinesWhetherTaskArtifactsAreUpToDateUsingBackingRepository() {
-        expectTaskStateCreated();
+        final TaskInternal task = taskWithOutputs();
+        expectTaskStateCreated(task);
 
         TaskArtifactState state = repository.getStateFor(task);
 
@@ -115,10 +130,36 @@ public class ShortCircuitTaskArtifactStateRepositoryTest {
         assertTrue(state.isUpToDate());
     }
 
-    private void expectTaskStateCreated() {
+    private void expectTaskStateCreated(final TaskInternal task) {
         context.checking(new Expectations() {{
             one(delegate).getStateFor(task);
             will(returnValue(taskArtifactState));
         }});
     }
+
+    private TaskInternal taskWithOutputs() {
+        final TaskInternal task = context.mock(TaskInternal.class);
+        context.checking(new Expectations() {{
+            allowing(task).getOutputs();
+            will(returnValue(taskOutputsInternal));
+            allowing(taskOutputsInternal).getHasOutput();
+            will(returnValue(true));
+            allowing(taskOutputsInternal).getUpToDateSpec();
+            will(returnValue(upToDateSpec));
+        }});
+
+        return task;
+    }
+
+    private TaskInternal taskWithNoOutputs() {
+        final TaskInternal task = context.mock(TaskInternal.class);
+        context.checking(new Expectations() {{
+            allowing(task).getOutputs();
+            will(returnValue(taskOutputsInternal));
+            allowing(taskOutputsInternal).getHasOutput();
+            will(returnValue(false));
+        }});
+
+        return task;
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy
new file mode 100644
index 0000000..b70407b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistryTest.groovy
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2011 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.classpath
+
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultModuleRegistryTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    TestFile runtimeDep
+    TestFile resourcesDir
+    TestFile jarFile
+    TestFile distDir
+
+    def setup() {
+        distDir = tmpDir.createDir("dist")
+
+        distDir.createDir("lib")
+        distDir.createDir("lib/plugins")
+        runtimeDep = distDir.createZip("lib/dep-1.2.jar")
+
+        resourcesDir = tmpDir.createDir("classes")
+        def properties = new Properties()
+        properties.runtime = 'dep-1.2.jar'
+        properties.projects = ''
+        resourcesDir.file("gradle-some-module-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        jarFile = distDir.file("lib/gradle-some-module-5.1.jar")
+        resourcesDir.zipTo(jarFile)
+    }
+
+    def "uses manifest from jar in distribution image"() {
+        given:
+        def cl = new URLClassLoader([] as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.implementationClasspath.asFiles == [jarFile]
+        module.runtimeClasspath.asFiles == [runtimeDep]
+    }
+
+    def "uses manifest from classpath when run from IDEA"() {
+        given:
+        def classesDir = tmpDir.createDir("out/production/someModule")
+        def staticResourcesDir = tmpDir.createDir("some-module/src/main/resources")
+        def ignoredDir = tmpDir.createDir("ignore-me-out/production/someModule")
+        def cl = new URLClassLoader([ignoredDir, classesDir, resourcesDir, staticResourcesDir, runtimeDep].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.implementationClasspath.asFiles == [classesDir, staticResourcesDir, resourcesDir]
+        module.runtimeClasspath.asFiles == [runtimeDep]
+    }
+
+    def "uses manifest from classpath when run from Eclipse"() {
+        given:
+        def classesDir = tmpDir.createDir("some-module/bin")
+        def staticResourcesDir = tmpDir.createDir("some-module/src/main/resources")
+        def cl = new URLClassLoader([classesDir, resourcesDir, staticResourcesDir, runtimeDep].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.implementationClasspath.asFiles == [classesDir, staticResourcesDir, resourcesDir]
+        module.runtimeClasspath.asFiles == [runtimeDep]
+    }
+
+    def "uses manifest from classpath when run from build"() {
+        given:
+        def classesDir = tmpDir.createDir("some-module/build/classes/main")
+        def staticResourcesDir = tmpDir.createDir("some-module/build/resources/main")
+        def cl = new URLClassLoader([classesDir, resourcesDir, staticResourcesDir, runtimeDep].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.implementationClasspath.asFiles == [classesDir, staticResourcesDir, resourcesDir]
+        module.runtimeClasspath.asFiles == [runtimeDep]
+    }
+
+    def "uses manifest from a jar on the classpath"() {
+        given:
+        def cl = new URLClassLoader([jarFile, runtimeDep].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.implementationClasspath.asFiles == [jarFile]
+        module.runtimeClasspath.asFiles == [runtimeDep]
+    }
+
+    def "handles empty classpaths in manifest"() {
+        given:
+        def properties = new Properties()
+        properties.runtime = ''
+        properties.projects = ''
+        resourcesDir.file("gradle-some-module-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        def cl = new URLClassLoader([resourcesDir, runtimeDep].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.runtimeClasspath.empty
+        module.requiredModules.empty
+    }
+
+    def "extracts required modules from manifest"() {
+        given:
+        def properties = new Properties()
+        properties.projects = 'gradle-module-2'
+        resourcesDir.file("gradle-some-module-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        properties = new Properties()
+        resourcesDir.file("gradle-module-2-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        def cl = new URLClassLoader([resourcesDir].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.requiredModules as List == [registry.getModule("gradle-module-2")]
+    }
+
+    def "builds transitive closure of required modules"() {
+        given:
+        def properties = new Properties()
+        properties.projects = 'gradle-module-2'
+        resourcesDir.file("gradle-some-module-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        properties = new Properties()
+        properties.projects = 'gradle-module-3'
+        resourcesDir.file("gradle-module-2-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        properties = new Properties()
+        properties.projects = ''
+        resourcesDir.file("gradle-module-3-classpath.properties").withOutputStream { outstr -> properties.save(outstr, "header") }
+
+        def cl = new URLClassLoader([resourcesDir].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        expect:
+        def module = registry.getModule("gradle-some-module")
+        module.allRequiredModules as List == [module, registry.getModule("gradle-module-2"), registry.getModule("gradle-module-3")]
+    }
+
+    def "fails when classpath does not contain manifest resource"() {
+        given:
+        def cl = new URLClassLoader([] as URL[])
+        def registry = new DefaultModuleRegistry(cl, null)
+
+        when:
+        registry.getModule("gradle-some-module")
+
+        then:
+        UnknownModuleException e = thrown()
+        e.message == "Cannot locate classpath manifest for module 'gradle-some-module' in classpath."
+    }
+
+    def "fails when classpath and distribution image do not contain manifest"() {
+        given:
+        def cl = new URLClassLoader([] as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        when:
+        registry.getModule("gradle-other-module")
+
+        then:
+        UnknownModuleException e = thrown()
+        e.message == "Cannot locate JAR for module 'gradle-other-module' in distribution directory '$distDir'."
+    }
+
+    def "locates an external module as a JAR on the classpath"() {
+        given:
+        def cl = new URLClassLoader([runtimeDep].collect { it.toURI().toURL() } as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        expect:
+        def module = registry.getExternalModule("dep")
+        module.implementationClasspath.asFiles == [runtimeDep]
+        module.runtimeClasspath.empty
+    }
+
+    def "locates an external module as a JAR in the distribution image when not available on the classpath"() {
+        given:
+        def cl = new URLClassLoader([] as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        expect:
+        def module = registry.getExternalModule("dep")
+        module.implementationClasspath.asFiles == [runtimeDep]
+        module.runtimeClasspath.empty
+    }
+
+    def "fails when external module cannot be found"() {
+        given:
+        def cl = new URLClassLoader([] as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        when:
+        registry.getExternalModule("unknown")
+
+        then:
+        UnknownModuleException e = thrown()
+        e.message == "Cannot locate JAR for module 'unknown' in distribution directory '$distDir'."
+    }
+
+    def "ignores jars which have the same prefix as an external module"() {
+        given:
+        distDir.createFile("dep-launcher-1.2.jar")
+        distDir.createFile("dep-launcher-1.2-beta-3.jar")
+        def cl = new URLClassLoader([] as URL[])
+        def registry = new DefaultModuleRegistry(cl, distDir)
+
+        expect:
+        def module = registry.getExternalModule("dep")
+        module.implementationClasspath.asFiles == [runtimeDep]
+        module.runtimeClasspath.empty
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy
new file mode 100644
index 0000000..18c73cb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/classpath/ManifestUtilTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012 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.classpath
+
+import java.util.jar.Attributes
+import java.util.jar.JarOutputStream
+import java.util.jar.Manifest
+import java.util.zip.ZipEntry
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+public class ManifestUtilTest extends Specification {
+    @Rule
+    final TemporaryFolder tmpDir = new TemporaryFolder()
+    def jarFile = tmpDir.file("mydir/jarfile.jar").createFile()
+
+    def "creates manifest classpath with relative urls"() {
+        when:
+        def classpathFiles = [tmpDir.file('mydir/jar1.jar'), tmpDir.file('mydir/nested/jar2.jar')]
+
+        then:
+        ManifestUtil.createManifestClasspath(jarFile, classpathFiles) == "jar1.jar nested/jar2.jar";
+    }
+
+    def "creates manifest classpath with absolute urls"() {
+        when:
+        def tmpDirPath = tmpDir.dir.toURI().rawPath
+        def file1 = tmpDir.file('different/jar1.jar')
+        def file2 = tmpDir.file('different/nested/jar2.jar')
+        
+        then:
+        ManifestUtil.createManifestClasspath(jarFile, [file1, file2]) == "${tmpDirPath}different/jar1.jar ${tmpDirPath}different/nested/jar2.jar"
+    }
+    
+    def "url encodes spaces in manifest classpath"() {
+        when:
+        def classpathFiles = [tmpDir.file('mydir/jar one.jar'), tmpDir.file('mydir/nested dir/jar two.jar')]
+
+        then:
+        ManifestUtil.createManifestClasspath(jarFile, classpathFiles) == "jar%20one.jar nested%20dir/jar%20two.jar";
+    }
+
+    def "returns empty classpath list for missing file or directory"() {
+        when:
+        def nonexistant = new File("does no exist");
+        def directory = tmpDir.createDir("new_directory");
+        
+        then:
+        ManifestUtil.parseManifestClasspath(nonexistant) == []
+        ManifestUtil.parseManifestClasspath(directory) == []
+    }
+    
+    def "returns empty classpath for non-jar file"() {
+        when:
+        def file = tmpDir.createFile('non-jar.zip')
+        file << "text"
+        
+        then:
+        ManifestUtil.parseManifestClasspath(file) == []
+    }
+
+    def "returns empty classpath for jar without manifest"() {
+        when:
+        createJar()
+
+        then:
+        ManifestUtil.parseManifestClasspath(jarFile) == []
+    }
+
+    def "returns empty classpath for jar with manifest without Class-Path"() {
+        when:
+        createJar(manifestWithClasspath(null))
+
+        then:
+        ManifestUtil.parseManifestClasspath(jarFile) == []
+    }
+
+    def "returns empty classpath for jar with manifest with empty Class-Path"() {
+        when:
+        createJar(manifestWithClasspath(""))
+
+        then:
+        ManifestUtil.parseManifestClasspath(jarFile) == []
+    }
+
+    def "returns classpath for jar with manifest"() {
+        when:
+        createJar(manifestWithClasspath('foo.jar'))
+
+        then:
+        ManifestUtil.parseManifestClasspath(jarFile) == [new File(jarFile.parentFile, 'foo.jar').toURI()]
+    }
+
+    def "returned classpath URI is absolute and can be used to locate file"() {
+        when:
+        def classpathFile = tmpDir.createFile("mydir/foo.jar")
+        createJar(manifestWithClasspath('foo.jar'))
+
+        and:
+        def classpathURI = ManifestUtil.parseManifestClasspath(jarFile)[0]
+        
+        then:
+        new File(classpathURI).absoluteFile == classpathFile.absoluteFile
+    }
+
+    private def createJar(def manifest = null) throws IOException {
+        def jarOutputStream
+
+        if (manifest == null) {
+            jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile))
+        } else {
+            jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile), manifest)
+        }
+
+        jarOutputStream.putNextEntry(new ZipEntry("META-INF/"));
+        jarOutputStream.close();
+    }
+
+    private def manifestWithClasspath(def manifestClasspath) {
+        Manifest manifest = new Manifest();
+        Attributes attributes = manifest.getMainAttributes();
+        attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+        if (manifestClasspath != null) {
+            attributes.putValue("Class-Path", manifestClasspath);
+        }
+        return manifest
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/collections/CollectionEventRegisterSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/collections/CollectionEventRegisterSpec.groovy
new file mode 100644
index 0000000..38d9cb9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/collections/CollectionEventRegisterSpec.groovy
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 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.collections
+
+import org.gradle.api.specs.Spec
+import org.gradle.api.Action
+
+import spock.lang.*
+
+class CollectionEventRegisterSpec extends Specification {
+
+    def r = register()
+    def added = []
+    def removed = []
+
+    protected CollectionEventRegister register() {
+        new CollectionEventRegister()
+    }
+
+    protected CollectionFilter filter(Class type, Closure spec = null) {
+        if (spec) {
+            new CollectionFilter(type, spec as Spec)
+        } else {
+            new CollectionFilter(type)
+        }
+    }
+
+    protected Action a(Closure impl) {
+        impl as Action
+    }
+
+    def "register actions"() {
+        given:
+        r.registerAddAction a({ added << it })
+        r.registerAddAction a({ added << it + 10 })
+        r.registerRemoveAction a({ removed << it })
+        r.registerRemoveAction a({ removed << it + 10 })
+
+        when:
+        r.addAction.execute 1
+        r.removeAction.execute 2
+
+        then:
+        added == [1, 11]
+        removed == [2, 12]
+    }
+
+    def "listener added on filtered adds to root"() {
+        given:
+        r.registerAddAction a({ added << "root" })
+
+        and:
+        def filter = filter(Object)
+        def f = r.filtered(filter)
+        f.registerAddAction a({ added << "filtered" })
+
+        expect:
+        filter.isSatisfiedBy("an object")
+
+        when:
+        f.addAction.execute "an object"
+
+        then:
+        added == ["root", "filtered"]
+    }
+
+    def "filter by type"() {
+        given:
+        def filter = filter(Number)
+        def f = register().filtered(filter)
+        r.registerAddAction a({ added << "root" })
+        f.registerAddAction a({ added << "filtered" })
+
+        expect:
+        !filter.isSatisfiedBy("not a number")
+
+        when:
+        r.addAction.execute "not a number"
+
+        then:
+        added == ["root"]
+    }
+
+    def "filter by type on filtered"() {
+        given:
+        r.registerAddAction a({ added << "root" })
+
+        def f1 = r.filtered(filter(Number))
+        f1.registerAddAction a({ added << "filtering for number" })
+
+        def f2 = r.filtered(filter(Integer))
+        f2.registerAddAction a({ added << "filtering for integers" })
+
+        when:
+        r.addAction.execute 1.2 // not an integer
+
+        then:
+        added == ["root", "filtering for number"]
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistryTest.groovy
new file mode 100644
index 0000000..3a246e6
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistryTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.concurrent;
+
+
+import org.gradle.internal.service.ServiceRegistry
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/24/11
+ */
+public class SynchronizedServiceRegistryTest extends Specification {
+
+    def delegate = Mock(ServiceRegistry)
+    def reg = new SynchronizedServiceRegistry(delegate);
+
+    def "gets services from delegate"() {
+        when:
+        reg.get(Object)
+        then:
+        1 * delegate.get(Object)
+        when:
+        reg.getFactory(String)
+        then:
+        1 * delegate.getFactory(String)
+        when:
+        reg.newInstance(Integer)
+        then:
+        1 * delegate.newInstance(Integer)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
index e0fc255..478de82 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
@@ -16,9 +16,12 @@
 package org.gradle.api.internal.file;
 
 import org.gradle.api.file.RelativePath;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
+import org.gradle.util.TestPrecondition;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -28,6 +31,7 @@ import java.io.InputStream;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
 
 public class AbstractFileTreeElementTest {
     @Rule
@@ -35,8 +39,7 @@ public class AbstractFileTreeElementTest {
 
     @Test
     public void canCopyToOutputStream() {
-        TestFile src = tmpDir.file("src");
-        src.write("content");
+        TestFile src = writeToFile("src", "content");
 
         ByteArrayOutputStream outstr = new ByteArrayOutputStream();
         new TestFileTreeElement(src).copyTo(outstr);
@@ -46,8 +49,7 @@ public class AbstractFileTreeElementTest {
 
     @Test
     public void canCopyToFile() {
-        TestFile src = tmpDir.file("src");
-        src.write("content");
+        TestFile src = writeToFile("src", "content");
         TestFile dest = tmpDir.file("dir/dest");
 
         new TestFileTreeElement(src).copyTo(dest);
@@ -55,12 +57,52 @@ public class AbstractFileTreeElementTest {
         dest.assertIsFile();
         assertThat(dest.getText(), equalTo("content"));
     }
-    
+
+    @Test
+    public void copiedFileHasExpectedPermissions() throws Exception {
+        assumeTrue(TestPrecondition.FILE_PERMISSIONS.isFulfilled());
+
+        TestFile src = writeToFile("src", "");
+        TestFile dest = tmpDir.file("dest");
+
+        new TestFileTreeElement(src, 0666).copyTo(dest);
+        assertPermissionsEquals("666", dest);
+
+        new TestFileTreeElement(src, 0644).copyTo(dest);
+        assertPermissionsEquals("644", dest);
+    }
+
+    @Test
+    public void defaultPermissionValuesAreUsed() {
+        TestFileTreeElement dir = new TestFileTreeElement(tmpDir.getDir());
+        TestFileTreeElement file = new TestFileTreeElement(tmpDir.file("someFile"));
+
+        assertThat(dir.getMode(), equalTo(FileSystem.DEFAULT_DIR_MODE));
+        assertThat(file.getMode(), equalTo(FileSystem.DEFAULT_FILE_MODE));
+    }
+
+    private TestFile writeToFile(String name, String content) {
+        final TestFile result = tmpDir.file(name);
+        result.write(content);
+        return result;
+    }
+
+    private void assertPermissionsEquals(String expected, File f) throws Exception {
+        assertThat(Integer.toOctalString(FileSystems.getDefault().getUnixMode(f)),
+            equalTo(expected));
+    }
+
     private class TestFileTreeElement extends AbstractFileTreeElement {
         private final TestFile file;
+        private final Integer mode;
 
         public TestFileTreeElement(TestFile file) {
+            this(file, null);
+        }
+
+        public TestFileTreeElement(TestFile file, Integer mode) {
             this.file = file;
+            this.mode = mode;
         }
 
         public String getDisplayName() {
@@ -90,5 +132,11 @@ public class AbstractFileTreeElementTest {
         public InputStream open() {
             return GFileUtils.openInputStream(file);
         }
+
+        public int getMode() {
+            return mode == null
+                ? super.getMode()
+                : mode;
+        }
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirConverterTest.groovy
deleted file mode 100644
index c29e9f3..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirConverterTest.groovy
+++ /dev/null
@@ -1,335 +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.api.internal.file
-
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.PathValidation
-import org.gradle.api.file.FileCollection
-import org.gradle.util.TemporaryFolder
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import java.util.concurrent.Callable
-import org.gradle.util.OperatingSystem
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
-
-/**
- * @author Hans Dockter
- */
-class BaseDirConverterTest {
-    static final String TEST_PATH = 'testpath'
-
-    File baseDir
-    File testFile
-    File testDir
-
-    BaseDirConverter baseDirConverter
-    @Rule public TemporaryFolder rootDir = new TemporaryFolder();
-
-    @Before public void setUp() {
-        baseDir = rootDir.dir
-        baseDirConverter = new BaseDirConverter(baseDir)
-        testFile = new File(baseDir, 'testfile')
-        testDir = new File(baseDir, 'testdir')
-    }
-
-    @Test(expected = IllegalArgumentException) public void testWithNullPath() {
-        baseDirConverter.resolve(null)
-    }
-
-    @Test public void testWithNoPathValidation() {
-        // No exceptions means test has passed
-        baseDirConverter.resolve(TEST_PATH)
-        baseDirConverter.resolve(TEST_PATH, PathValidation.NONE)
-    }
-
-    @Test public void testPathValidationWithNonExistingFile() {
-        try {
-            baseDirConverter.resolve(testFile.name, PathValidation.FILE)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("File '$testFile.canonicalFile' does not exist.".toString()))
-        }
-    }
-
-    @Test public void testPathValidationForFileWithDirectory() {
-        testDir.mkdir()
-        try {
-            baseDirConverter.resolve(testDir.name, PathValidation.FILE)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("File '$testDir.canonicalFile' is not a file.".toString()))
-        }
-    }
-
-    @Test public void testWithValidFile() {
-        testFile.createNewFile()
-        baseDirConverter.resolve(testFile.name, PathValidation.FILE)
-    }
-
-    @Test public void testPathValidationWithNonExistingDirectory() {
-        try {
-            baseDirConverter.resolve(testDir.name, PathValidation.DIRECTORY)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("Directory '$testDir.canonicalFile' does not exist.".toString()))
-        }
-    }
-
-    @Test public void testPathValidationWithValidDirectory() {
-        testDir.mkdir()
-        baseDirConverter.resolve(testDir.name, PathValidation.DIRECTORY)
-    }
-
-    @Test public void testPathValidationForDirectoryWithFile() {
-        testFile.createNewFile()
-        try {
-            baseDirConverter.resolve(testFile.name, PathValidation.DIRECTORY)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("Directory '$testFile.canonicalFile' is not a directory.".toString()))
-        }
-    }
-
-    @Test public void testPathValidationForExistingDirAndFile() {
-        testDir.mkdir()
-        testFile.createNewFile()
-        baseDirConverter.resolve(testDir.name, PathValidation.EXISTS)
-        baseDirConverter.resolve(testFile.name, PathValidation.EXISTS)
-    }
-
-    @Test public void testExistsPathValidationWithNonExistingDir() {
-        try {
-            baseDirConverter.resolve(testDir.name, PathValidation.EXISTS)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("File '$testDir.canonicalFile' does not exist.".toString()))
-        }
-    }
-
-    @Test public void testExistsPathValidationWithNonExistingFile() {
-        try {
-            baseDirConverter.resolve(testFile.name, PathValidation.EXISTS)
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo("File '$testFile.canonicalFile' does not exist.".toString()))
-        }
-    }
-
-    @Test public void testResolveAbsolutePath() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.path))
-    }
-
-    @Test public void testResolveRelativePath() {
-        String relativeFileName = "relative"
-        assertEquals(new File(baseDir, relativeFileName), baseDirConverter.resolve(relativeFileName))
-        assertEquals(baseDir, baseDirConverter.resolve("."))
-    }
-
-    @Test public void testResolveFileWithAbsolutePath() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile))
-    }
-
-    @Test public void testResolveRelativeObject() {
-        assertEquals(new File(baseDir, "12"), baseDirConverter.resolve(12))
-    }
-
-    @Test public void testResolveFileWithRelativePath() {
-        File relativeFile = new File('relative')
-        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(relativeFile))
-    }
-
-    @Test public void testResolveAbsolutePathOnCaseInsensitiveFileSystemToUri() {
-        if (OperatingSystem.current().isCaseSensitiveFileSystem()) {
-            return
-        }
-
-        String path = baseDir.absolutePath.toLowerCase()
-        assertEquals(baseDir, baseDirConverter.resolve(path))
-    }
-
-    @Test public void testResolveRelativeFileURIString() {
-        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve('file:relative'))
-        assertEquals(new File(baseDir.parentFile, 'relative'), baseDirConverter.resolve('file:../relative'))
-    }
-
-    @Test public void testResolveAbsoluteFileURIString() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toString()))
-    }
-
-    @Test public void testResolveAbsoluteFileURI() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI()))
-    }
-
-    @Test public void testResolveAbsoluteFileURL() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toURL()))
-    }
-
-    @Test public void testResolveFilePathWithURIEncodedAndReservedCharacters() {
-        File absoluteFile = new File('white%20space').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.absolutePath))
-        absoluteFile = new File('white space').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.absolutePath))
-    }
-
-    @Test public void testResolveURIStringWithEncodedAndReservedCharacters() {
-        assertEquals(new File(baseDir, 'white space'), baseDirConverter.resolve('file:white%20space'))
-        assertEquals(new File(baseDir, 'not%encoded'), baseDirConverter.resolve('file:not%encoded'))
-        assertEquals(new File(baseDir, 'bad%1'), baseDirConverter.resolve('file:bad%1'))
-        assertEquals(new File(baseDir, 'white space'), baseDirConverter.resolve('file:white space'))
-    }
-
-    @Test public void testResolveURIWithReservedCharacters() {
-        File absoluteFile = new File('white space').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI()))
-    }
-
-    @Test public void testResolveURLWithReservedCharacters() {
-        File absoluteFile = new File('white space').canonicalFile
-        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toURL()))
-    }
-
-    @Test public void testCannotResolveNonFileURI() {
-        try {
-            baseDirConverter.resolve("http://www.gradle.org")
-            fail()
-        } catch (InvalidUserDataException e) {
-            assertThat(e.message, equalTo('Cannot convert URL \'http://www.gradle.org\' to a file.'))
-        }
-    }
-
-    @Test public void testResolveClosure() {
-        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve({'relative'}))
-    }
-
-    @Test public void testResolveCallable() {
-        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve({'relative'} as Callable))
-    }
-
-    @Test public void testResolveFileSource() {
-        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(baseDirConverter.resolveLater('relative')))
-    }
-
-    @Test public void testResolveNestedClosuresAndCallables() {
-        Callable callable = {'relative'} as Callable
-        Closure closure = {callable}
-        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(closure))
-    }
-
-    @Test public void testFiles() {
-        FileCollection collection = baseDirConverter.resolveFiles('a', 'b')
-        assertThat(collection, instanceOf(DefaultConfigurableFileCollection))
-        assertThat(collection.from, equalTo(['a', 'b'] as LinkedHashSet))
-    }
-
-    @Test public void testFilesReturnsSourceFileCollection() {
-        FileCollection source = baseDirConverter.resolveFiles('a')
-        FileCollection collection = baseDirConverter.resolveFiles(source)
-        assertThat(collection, sameInstance(source))
-    }
-
-    @Test public void testResolveAbsolutePathToUri() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile.toURI(), baseDirConverter.resolveUri(absoluteFile.path))
-    }
-
-    @Test public void testResolveRelativePathToUri() {
-        assertEquals(new File(baseDir, 'relative').toURI(), baseDirConverter.resolveUri('relative'))
-    }
-
-    @Test public void testResolveFileWithAbsolutePathToUri() {
-        File absoluteFile = new File('nonRelative').canonicalFile
-        assertEquals(absoluteFile.toURI(), baseDirConverter.resolveUri(absoluteFile))
-    }
-
-    @Test public void testResolveFileWithRelativePathToUri() {
-        File relativeFile = new File('relative')
-        assertEquals(new File(baseDir, 'relative').toURI(), baseDirConverter.resolveUri(relativeFile))
-    }
-
-    @Test public void testResolveUriStringToUri() {
-        assertEquals(new URI("http://www.gradle.org"), baseDirConverter.resolveUri("http://www.gradle.org"))
-    }
-
-    @Test public void testResolveUriObjectToUri() {
-        URI uri = new URI("http://www.gradle.org")
-        assertEquals(uri, baseDirConverter.resolveUri(uri))
-    }
-
-    @Test public void testResolveUrlObjectToUri() {
-        assertEquals(new URI("http://www.gradle.org"), baseDirConverter.resolveUri(new URL("http://www.gradle.org")))
-    }
-
-    @Test public void testResolveAbsolutePathWithReservedCharsToUri() {
-        assertEquals(new File(baseDir, 'with white%20space').toURI(), baseDirConverter.resolveUri('with white%20space'))
-        assertEquals('with white%20space', baseDirConverter.resolve(baseDirConverter.resolveUri('with white%20space')).name)
-    }
-
-    @Test public void testResolveUriStringWithEncodedCharsToUri() {
-        assertEquals(new URI("http://www.gradle.org/white%20space"), baseDirConverter.resolveUri("http://www.gradle.org/white%20space"))
-    }
-    
-    @Test public void testResolveRelativePathToRelativePath() {
-        assertEquals("relative", baseDirConverter.resolveAsRelativePath("relative"))
-    }
-
-    @Test public void testResolveAbsoluteChildPathToRelativePath() {
-        def absoluteFile = new File(baseDir, 'child').absoluteFile
-        assertEquals('child', baseDirConverter.resolveAsRelativePath(absoluteFile))
-        assertEquals('child', baseDirConverter.resolveAsRelativePath(absoluteFile.absolutePath))
-    }
-
-    @Test public void testResolveAbsoluteSiblingPathToRelativePath() {
-        def absoluteFile = new File(baseDir, '../sibling').absoluteFile
-        assertEquals("..${File.separator}sibling".toString(), baseDirConverter.resolveAsRelativePath(absoluteFile))
-        assertEquals("..${File.separator}sibling".toString(), baseDirConverter.resolveAsRelativePath(absoluteFile.absolutePath))
-    }
-
-    @Test public void testResolveBaseDirToRelativePath() {
-        assertEquals('.', baseDirConverter.resolveAsRelativePath(baseDir))
-        assertEquals('.', baseDirConverter.resolveAsRelativePath(baseDir.absolutePath))
-        assertEquals('.', baseDirConverter.resolveAsRelativePath('.'))
-        assertEquals('.', baseDirConverter.resolveAsRelativePath("../$baseDir.name"))
-    }
-
-    @Test public void testResolveParentDirToRelativePath() {
-        assertEquals('..', baseDirConverter.resolveAsRelativePath(baseDir.parentFile))
-        assertEquals('..', baseDirConverter.resolveAsRelativePath('..'))
-    }
-
-    @Test public void testResolveLater() {
-        String src;
-        Closure cl = { src }
-        FileSource source = baseDirConverter.resolveLater(cl)
-        src = 'file1'
-        assertEquals(new File(baseDir, 'file1'), source.get())
-    }
-    
-    @Test public void testCreateFileResolver() {
-        File newBaseDir = new File(baseDir, 'subdir')
-        assertEquals(new File(newBaseDir, 'file'), baseDirConverter.withBaseDir('subdir').resolve('file'))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
new file mode 100644
index 0000000..3d1f84d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2011 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.file
+
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+
+import org.junit.Rule
+import spock.lang.Specification
+
+class BaseDirFileResolverSpec extends Specification {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "normalizes absolute path which points to an absolute link"() {
+        def target = createFile(new File(tmpDir.dir, 'target.txt'))
+        def file = new File(tmpDir.dir, 'a/other.txt')
+        createLink(file, target)
+        assert file.exists() && file.file
+
+        expect:
+        normalize(file) == file
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "normalizes absolute path which points to a relative link"() {
+        def target = createFile(new File(tmpDir.dir, 'target.txt'))
+        def file = new File(tmpDir.dir, 'a/other.txt')
+        createLink(file, '../target.txt')
+        assert file.exists() && file.file
+
+        expect:
+        normalize(file) == file
+    }
+
+    @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
+    def "normalizes absolute path which has mismatched case"() {
+        def file = createFile(new File(tmpDir.dir, 'dir/file.txt'))
+        def path = new File(tmpDir.dir, 'dir/FILE.txt')
+        assert path.exists() && path.file
+
+        expect:
+        normalize(path) == file
+    }
+
+    @Requires([TestPrecondition.SYMLINKS, TestPrecondition.CASE_INSENSITIVE_FS])
+    def "normalizes absolute path which points to a target using mismatched case"() {
+        def target = createFile(new File(tmpDir.dir, 'target.txt'))
+        def file = new File(tmpDir.dir, 'dir/file.txt')
+        createLink(file, target)
+        def path = new File(tmpDir.dir, 'dir/FILE.txt')
+        assert path.exists() && path.file
+
+        expect:
+        normalize(path) == file
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "normalizes path which points to a link to something that does not exist"() {
+        def file = new File(tmpDir.dir, 'a/other.txt')
+        createLink(file, 'unknown.txt')
+        assert !file.exists() && !file.file
+
+        expect:
+        normalize(file) == file
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "normalizes path when ancestor is an absolute link"() {
+        def target = createFile(new File(tmpDir.dir, 'target/file.txt'))
+        def file = new File(tmpDir.dir, 'a/b/file.txt')
+        createLink(file.parentFile, target.parentFile)
+        assert file.exists() && file.file
+
+        expect:
+        normalize(file) == file
+    }
+
+    @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
+    def "normalizes path when ancestor has mismatched case"() {
+        def file = createFile(new File(tmpDir.dir, "a/b/file.txt"))
+        def path = new File(tmpDir.dir, "A/b/file.txt")
+        assert file.exists() && file.file
+
+        expect:
+        normalize(path) == file
+    }
+
+    @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
+    def "normalizes ancestor with mismatched case when target file does not exist"() {
+        tmpDir.createDir("a")
+        def file = new File(tmpDir.dir, "a/b/file.txt")
+        def path = new File(tmpDir.dir, "A/b/file.txt")
+
+        expect:
+        normalize(path) == file
+    }
+
+    def "normalizes relative path"() {
+        def ancestor = new File(tmpDir.dir, "test")
+        def baseDir = new File(ancestor, "base")
+        def sibling = new File(ancestor, "sub")
+        def child = createFile(new File(baseDir, "a/b/file.txt"))
+
+        expect:
+        normalize("a/b/file.txt", baseDir) == child
+        normalize("./a/b/file.txt", baseDir) == child
+        normalize(".//a/b//file.txt", baseDir) == child
+        normalize("sub/../a/b/file.txt", baseDir) == child
+        normalize("../sub", baseDir) == sibling
+        normalize("..", baseDir) == ancestor
+        normalize(".", baseDir) == baseDir
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "normalizes relative path when base dir is a link"() {
+        def target = createFile(new File(tmpDir.dir, 'target/file.txt'))
+        def baseDir = new File(tmpDir.dir, 'base')
+        createLink(baseDir, "target")
+        def file = new File(baseDir, 'file.txt')
+        assert file.exists() && file.file
+
+        expect:
+        normalize('file.txt', baseDir) == file
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "normalizes path which uses windows 8.3 name"() {
+        def file = createFile(new File(tmpDir.dir, 'dir/file-with-long-name.txt'))
+        def path = new File(tmpDir.dir, 'dir/FILE-W~1.TXT')
+        assert path.exists() && path.file
+
+        expect:
+        normalize(path) == file
+    }
+
+    def "normalizes file system roots"() {
+        expect:
+        normalize(root) == root
+
+        where:
+        root << File.listRoots()
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "normalizes non-existent file system root"() {
+        def file = new File("Q:\\")
+        assert !file.exists()
+        assert file.absolute
+
+        expect:
+        normalize(file) == file
+    }
+
+    def "normalizes relative path that refers to ancestor of file system root"() {
+        File root = File.listRoots()[0]
+
+        expect:
+        normalize("../../..", root) == root
+    }
+
+    def createLink(File link, File target) {
+        FileSystems.default.createSymbolicLink(link, target)
+    }
+
+    def createLink(File link, String target) {
+        createLink(link, new File(target))
+    }
+
+    def createFile(File file) {
+        file.parentFile.mkdirs()
+        file.text = 'content'
+        file
+    }
+
+    def normalize(Object path, File baseDir = tmpDir.dir) {
+        new BaseDirFileResolver(FileSystems.default, baseDir).resolve(path)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
new file mode 100644
index 0000000..21a06b4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
@@ -0,0 +1,333 @@
+/*
+ * 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.file
+
+import java.util.concurrent.Callable
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.PathValidation
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.util.PreconditionVerifier
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class BaseDirFileResolverTest {
+    static final String TEST_PATH = 'testpath'
+
+    File baseDir
+    File testFile
+    File testDir
+
+    BaseDirFileResolver baseDirConverter
+    @Rule public TemporaryFolder rootDir = new TemporaryFolder()
+    @Rule public PreconditionVerifier preconditions = new PreconditionVerifier()
+
+    @Before public void setUp() {
+        baseDir = rootDir.dir
+        baseDirConverter = new BaseDirFileResolver(FileSystems.default, baseDir)
+        testFile = new File(baseDir, 'testfile')
+        testDir = new File(baseDir, 'testdir')
+    }
+
+    @Test(expected = IllegalArgumentException) public void testWithNullPath() {
+        baseDirConverter.resolve(null)
+    }
+
+    @Test public void testWithNoPathValidation() {
+        // No exceptions means test has passed
+        baseDirConverter.resolve(TEST_PATH)
+        baseDirConverter.resolve(TEST_PATH, PathValidation.NONE)
+    }
+
+    @Test public void testPathValidationWithNonExistingFile() {
+        try {
+            baseDirConverter.resolve(testFile.name, PathValidation.FILE)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testFile.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testPathValidationForFileWithDirectory() {
+        testDir.mkdir()
+        try {
+            baseDirConverter.resolve(testDir.name, PathValidation.FILE)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testDir.canonicalFile' is not a file.".toString()))
+        }
+    }
+
+    @Test public void testWithValidFile() {
+        testFile.createNewFile()
+        baseDirConverter.resolve(testFile.name, PathValidation.FILE)
+    }
+
+    @Test public void testPathValidationWithNonExistingDirectory() {
+        try {
+            baseDirConverter.resolve(testDir.name, PathValidation.DIRECTORY)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Directory '$testDir.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testPathValidationWithValidDirectory() {
+        testDir.mkdir()
+        baseDirConverter.resolve(testDir.name, PathValidation.DIRECTORY)
+    }
+
+    @Test public void testPathValidationForDirectoryWithFile() {
+        testFile.createNewFile()
+        try {
+            baseDirConverter.resolve(testFile.name, PathValidation.DIRECTORY)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Directory '$testFile.canonicalFile' is not a directory.".toString()))
+        }
+    }
+
+    @Test public void testPathValidationForExistingDirAndFile() {
+        testDir.mkdir()
+        testFile.createNewFile()
+        baseDirConverter.resolve(testDir.name, PathValidation.EXISTS)
+        baseDirConverter.resolve(testFile.name, PathValidation.EXISTS)
+    }
+
+    @Test public void testExistsPathValidationWithNonExistingDir() {
+        try {
+            baseDirConverter.resolve(testDir.name, PathValidation.EXISTS)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testDir.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testExistsPathValidationWithNonExistingFile() {
+        try {
+            baseDirConverter.resolve(testFile.name, PathValidation.EXISTS)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testFile.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testResolveAbsolutePath() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.path))
+    }
+
+    @Test public void testResolveRelativePath() {
+        String relativeFileName = "relative"
+        assertEquals(new File(baseDir, relativeFileName), baseDirConverter.resolve(relativeFileName))
+        assertEquals(baseDir, baseDirConverter.resolve("."))
+    }
+
+    @Test public void testResolveFileWithAbsolutePath() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile))
+    }
+
+    @Test public void testResolveRelativeObject() {
+        assertEquals(new File(baseDir, "12"), baseDirConverter.resolve(12))
+    }
+
+    @Test public void testResolveFileWithRelativePath() {
+        File relativeFile = new File('relative')
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(relativeFile))
+    }
+
+    @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
+    @Test public void testResolveAbsolutePathOnCaseInsensitiveFileSystemToUri() {
+        String path = baseDir.absolutePath.toLowerCase()
+        assertEquals(baseDir, baseDirConverter.resolve(path))
+    }
+
+    @Test public void testResolveRelativeFileURIString() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve('file:relative'))
+        assertEquals(new File(baseDir.parentFile, 'relative'), baseDirConverter.resolve('file:../relative'))
+    }
+
+    @Test public void testResolveAbsoluteFileURIString() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toString()))
+    }
+
+    @Test public void testResolveAbsoluteFileURI() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI()))
+    }
+
+    @Test public void testResolveAbsoluteFileURL() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toURL()))
+    }
+
+    @Test public void testResolveFilePathWithURIEncodedAndReservedCharacters() {
+        File absoluteFile = new File('white%20space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.absolutePath))
+        absoluteFile = new File('white space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.absolutePath))
+    }
+
+    @Test public void testResolveURIStringWithEncodedAndReservedCharacters() {
+        assertEquals(new File(baseDir, 'white space'), baseDirConverter.resolve('file:white%20space'))
+        assertEquals(new File(baseDir, 'not%encoded'), baseDirConverter.resolve('file:not%encoded'))
+        assertEquals(new File(baseDir, 'bad%1'), baseDirConverter.resolve('file:bad%1'))
+        assertEquals(new File(baseDir, 'white space'), baseDirConverter.resolve('file:white space'))
+    }
+
+    @Test public void testResolveURIWithReservedCharacters() {
+        File absoluteFile = new File('white space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI()))
+    }
+
+    @Test public void testResolveURLWithReservedCharacters() {
+        File absoluteFile = new File('white space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toURL()))
+    }
+
+    @Test public void testCannotResolveNonFileURI() {
+        try {
+            baseDirConverter.resolve("http://www.gradle.org")
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo('Cannot convert URL \'http://www.gradle.org\' to a file.'))
+        }
+    }
+
+    @Test public void testResolveClosure() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve({'relative'}))
+    }
+
+    @Test public void testResolveCallable() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve({'relative'} as Callable))
+    }
+
+    @Test public void testResolveFileSource() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(baseDirConverter.resolveLater('relative')))
+    }
+
+    @Test public void testResolveNestedClosuresAndCallables() {
+        Callable callable = {'relative'} as Callable
+        Closure closure = {callable}
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(closure))
+    }
+
+    @Test public void testFiles() {
+        FileCollection collection = baseDirConverter.resolveFiles('a', 'b')
+        assertThat(collection, instanceOf(DefaultConfigurableFileCollection))
+        assertThat(collection.from, equalTo(['a', 'b'] as LinkedHashSet))
+    }
+
+    @Test public void testFilesReturnsSourceFileCollection() {
+        FileCollection source = baseDirConverter.resolveFiles('a')
+        FileCollection collection = baseDirConverter.resolveFiles(source)
+        assertThat(collection, sameInstance(source))
+    }
+
+    @Test public void testResolveAbsolutePathToUri() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile.toURI(), baseDirConverter.resolveUri(absoluteFile.path))
+    }
+
+    @Test public void testResolveRelativePathToUri() {
+        assertEquals(new File(baseDir, 'relative').toURI(), baseDirConverter.resolveUri('relative'))
+    }
+
+    @Test public void testResolveFileWithAbsolutePathToUri() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile.toURI(), baseDirConverter.resolveUri(absoluteFile))
+    }
+
+    @Test public void testResolveFileWithRelativePathToUri() {
+        File relativeFile = new File('relative')
+        assertEquals(new File(baseDir, 'relative').toURI(), baseDirConverter.resolveUri(relativeFile))
+    }
+
+    @Test public void testResolveUriStringToUri() {
+        assertEquals(new URI("http://www.gradle.org"), baseDirConverter.resolveUri("http://www.gradle.org"))
+    }
+
+    @Test public void testResolveUriObjectToUri() {
+        URI uri = new URI("http://www.gradle.org")
+        assertEquals(uri, baseDirConverter.resolveUri(uri))
+    }
+
+    @Test public void testResolveUrlObjectToUri() {
+        assertEquals(new URI("http://www.gradle.org"), baseDirConverter.resolveUri(new URL("http://www.gradle.org")))
+    }
+
+    @Test public void testResolveAbsolutePathWithReservedCharsToUri() {
+        assertEquals(new File(baseDir, 'with white%20space').toURI(), baseDirConverter.resolveUri('with white%20space'))
+        assertEquals('with white%20space', baseDirConverter.resolve(baseDirConverter.resolveUri('with white%20space')).name)
+    }
+
+    @Test public void testResolveUriStringWithEncodedCharsToUri() {
+        assertEquals(new URI("http://www.gradle.org/white%20space"), baseDirConverter.resolveUri("http://www.gradle.org/white%20space"))
+    }
+    
+    @Test public void testResolveRelativePathToRelativePath() {
+        assertEquals("relative", baseDirConverter.resolveAsRelativePath("relative"))
+    }
+
+    @Test public void testResolveAbsoluteChildPathToRelativePath() {
+        def absoluteFile = new File(baseDir, 'child').absoluteFile
+        assertEquals('child', baseDirConverter.resolveAsRelativePath(absoluteFile))
+        assertEquals('child', baseDirConverter.resolveAsRelativePath(absoluteFile.absolutePath))
+    }
+
+    @Test public void testResolveAbsoluteSiblingPathToRelativePath() {
+        def absoluteFile = new File(baseDir, '../sibling').absoluteFile
+        assertEquals("..${File.separator}sibling".toString(), baseDirConverter.resolveAsRelativePath(absoluteFile))
+        assertEquals("..${File.separator}sibling".toString(), baseDirConverter.resolveAsRelativePath(absoluteFile.absolutePath))
+    }
+
+    @Test public void testResolveBaseDirToRelativePath() {
+        assertEquals('.', baseDirConverter.resolveAsRelativePath(baseDir))
+        assertEquals('.', baseDirConverter.resolveAsRelativePath(baseDir.absolutePath))
+        assertEquals('.', baseDirConverter.resolveAsRelativePath('.'))
+        assertEquals('.', baseDirConverter.resolveAsRelativePath("../$baseDir.name"))
+    }
+
+    @Test public void testResolveParentDirToRelativePath() {
+        assertEquals('..', baseDirConverter.resolveAsRelativePath(baseDir.parentFile))
+        assertEquals('..', baseDirConverter.resolveAsRelativePath('..'))
+    }
+
+    @Test public void testResolveLater() {
+        String src;
+        Closure cl = { src }
+        FileSource source = baseDirConverter.resolveLater(cl)
+        src = 'file1'
+        assertEquals(new File(baseDir, 'file1'), source.get())
+    }
+    
+    @Test public void testCreateFileResolver() {
+        File newBaseDir = new File(baseDir, 'subdir')
+        assertEquals(new File(newBaseDir, 'file'), baseDirConverter.withBaseDir('subdir').resolve('file'))
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
index bdbd2b2..e0382ca 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
@@ -212,7 +212,8 @@ public class CompositeFileCollectionTest {
     public void filterDelegatesToEachSet() {
         final FileCollection filtered1 = context.mock(FileCollection.class);
         final FileCollection filtered2 = context.mock(FileCollection.class);
-        final Spec spec = context.mock(Spec.class);
+        @SuppressWarnings("unchecked")
+        final Spec<File> spec = context.mock(Spec.class);
 
         FileCollection filtered = collection.filter(spec);
         assertThat(filtered, instanceOf(CompositeFileCollection.class));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
old mode 100644
new mode 100755
index 1729854..29d9a7d
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
@@ -29,15 +29,16 @@ import org.gradle.api.internal.file.collections.FileTreeAdapter
 import org.gradle.api.internal.file.copy.CopyActionImpl
 import org.gradle.api.internal.file.copy.CopySpecImpl
 import org.gradle.api.internal.tasks.TaskResolver
+import org.gradle.internal.os.OperatingSystem
 import org.gradle.process.ExecResult
 import org.gradle.process.internal.ExecException
 import org.gradle.util.ClasspathUtil
-import org.gradle.util.OperatingSystem
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.junit.Rule
 import org.junit.Test
 import spock.lang.Specification
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
 
 public class DefaultFileOperationsTest extends Specification {
     private final FileResolver resolver = Mock()
@@ -94,6 +95,22 @@ public class DefaultFileOperationsTest extends Specification {
         fileTree.resolver.is(resolver)
     }
 
+    def createsAndConfiguresFileTree() {
+        given:
+        TestFile baseDir = expectPathResolved('base')
+        
+        when:
+        def fileTree = fileOperations.fileTree('base') {
+            builtBy 1
+        }
+        
+        then:
+        fileTree instanceof FileTree
+        fileTree.dir == baseDir
+        fileTree.resolver.is(resolver)
+        fileTree.builtBy == [1] as Set
+    }
+    
     def createsFileTreeFromMap() {
         TestFile baseDir = expectPathResolved('base')
 
@@ -131,8 +148,8 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def createsTarFileTree() {
-        expectPathResolved('path')
-        expectTempFileCreated()
+        TestFile file = tmpDir.file('path')
+        resolver.resolveResource('path') >> new FileResource(file)
 
         when:
         def tarTree = fileOperations.tarTree('path')
@@ -320,7 +337,7 @@ public class DefaultFileOperationsTest extends Specification {
     }
 
     def resolver() {
-        return new BaseDirConverter(tmpDir.testDir)
+        return new BaseDirFileResolver(FileSystems.default, tmpDir.testDir)
     }
 }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
new file mode 100644
index 0000000..a0b9ee2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultFileTreeElementTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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.file
+
+import org.gradle.api.file.FileTreeElement
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultFileTreeElementTest extends Specification {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "permissions on file can be read"() {
+        def f = tmpDir.createFile("f")
+        FileTreeElement e = new DefaultFileTreeElement(f, null)
+
+        when:
+        FileSystems.default.chmod(f, 0644)
+
+        then:
+        e.mode == 0644
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
old mode 100644
new mode 100755
index ba91b0f..c58d2d9
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
@@ -1,40 +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.file
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
-import org.jmock.integration.junit4.JMock
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
- at RunWith(JMock.class)
-public class DefaultTemporaryFileProviderTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
-
-    @Test
-    public void allocatesTempFile() {
-        DefaultTemporaryFileProvider provider = new DefaultTemporaryFileProvider({tmpDir.getDir()} as FileSource)
-        assertThat(provider.newTemporaryFile('a', 'b'), equalTo(tmpDir.file('a', 'b')))
-    }
-}
+/*
+ * 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.file
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+
+import spock.lang.Specification
+
+class DefaultTemporaryFileProviderTest extends Specification {
+    @Rule TemporaryFolder tmpDir
+    DefaultTemporaryFileProvider provider
+
+    def setup() {
+        provider = new DefaultTemporaryFileProvider({tmpDir.dir} as FileSource)
+    }
+
+    def "allocates temp file"() {
+        expect:
+        provider.newTemporaryFile('a', 'b') == tmpDir.file('a', 'b')
+    }
+
+    def "can create temp file"() {
+        when:
+        def file = provider.createTemporaryFile("prefix", "suffix", "foo/bar")
+
+        then:
+        correctTempFileCreated(file)
+    }
+
+    def "can create multiple temp files with same arguments"() {
+        when:
+        def file1 = provider.createTemporaryFile("prefix", "suffix", "foo/bar")
+        def file2 = provider.createTemporaryFile("prefix", "suffix", "foo/bar")
+        def file3 = provider.createTemporaryFile("prefix", "suffix", "foo/bar")
+
+        then:
+        correctTempFileCreated(file1)
+        correctTempFileCreated(file2)
+        correctTempFileCreated(file2)
+        file1 != file2
+        file2 != file3
+    }
+
+    void correctTempFileCreated(File file) {
+        assert file.exists()
+        assert file.name.startsWith("prefix")
+        assert file.name.endsWith("suffix")
+        assert file.path.startsWith(new File(tmpDir.dir, "foo/bar").path)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
new file mode 100644
index 0000000..68d4242
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/FileOrUriNotationParserTest.groovy
@@ -0,0 +1,108 @@
+/*
+ * 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.file
+
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class FileOrUriNotationParserTest extends Specification {
+
+    @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+    final FileOrUriNotationParser<Serializable> parser = new FileOrUriNotationParser<Serializable>(FileSystems.default)
+
+    def "with File returns this File"() {
+        setup:
+        def testFile = folder.createFile("test1")
+        when:
+        def object = parser.parseNotation(testFile)
+        then:
+        object instanceof File
+        testFile == object
+    }
+
+    def "with file path as String"() {
+        setup:
+        def testFile = folder.createFile("test1")
+        when:
+        def object = parser.parseNotation(testFile.getAbsolutePath())
+        then:
+        object instanceof File
+        testFile.getAbsolutePath() == object.getAbsolutePath()
+    }
+
+    def "with file URI"() {
+        setup:
+        def testFileURI = folder.createFile("test1").toURI()
+        when:
+        def object = parser.parseNotation(testFileURI)
+        then:
+        object instanceof File
+        object.toURI() == testFileURI
+    }
+
+    def "with URI as CharSequence"() {
+        setup:
+        def uriString = folder.createFile("test1").toURI().toString()
+        when:
+        def object = parser.parseNotation(uriString)
+        then:
+        object instanceof File
+        object.toURI().toString() == uriString
+    }
+
+    def "with URL"() {
+        setup:
+        def testFileURL = folder.createFile("test1").toURI().toURL()
+        when:
+        def object = parser.parseNotation(testFileURL)
+        then:
+        object instanceof File
+        object.toURI().toURL() == testFileURL
+    }
+
+    def "with non File URI URI instance is returned"() {
+        setup:
+        def unsupportedURI = URI.create("http://gradle.org")
+        when:
+        def parsed = parser.parseNotation(unsupportedURI)
+        then:
+        parsed instanceof URI
+    }
+
+    def "with non File URI String URI is returned"() {
+        setup:
+        def unsupportedURIString = "http://gradle.org"
+        when:
+        def parsed = parser.parseNotation(unsupportedURIString)
+        then:
+        parsed instanceof URI
+    }
+
+//    @Issue("GRADLE-2072")
+//    def "parsing unknown types causes UnsupportedNotationException"() {
+//        setup:
+//        def taskInternalMock = Mock(TaskInternal)
+//        def fileResolverMock = Mock(FileResolver)
+//        when:
+//        parser.parseNotation(new DefaultTaskOutputs(fileResolverMock, taskInternalMock))
+//        then:
+//        thrown(UnsupportedNotationException)
+//    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy
new file mode 100644
index 0000000..c07f889
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/MaybeCompressedFileResourceTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.file;
+
+
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver
+import org.gradle.api.internal.file.archive.compression.GzipArchiver
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/24/11
+ */
+public class MaybeCompressedFileResourceTest extends Specification {
+
+    def "understands file extensions"() {
+        expect:
+        new MaybeCompressedFileResource(new FileResource(new File("foo"))).resource instanceof FileResource
+        new MaybeCompressedFileResource(new FileResource(new File("foo.tgz"))).resource instanceof GzipArchiver
+        new MaybeCompressedFileResource(new FileResource(new File("foo.gz"))).resource instanceof GzipArchiver
+        new MaybeCompressedFileResource(new FileResource(new File("foo.bz2"))).resource instanceof Bzip2Archiver
+        new MaybeCompressedFileResource(new FileResource(new File("foo.tbz2"))).resource instanceof Bzip2Archiver
+
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
index 52f2531..0654315 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -15,29 +15,37 @@
  */
 package org.gradle.api.internal.file.archive;
 
+import java.util.Map;
+import java.util.HashMap;
 import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.file.copy.ReadableCopySpec;
-import org.gradle.api.tasks.bundling.Compression;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
-import org.gradle.util.TestFile;
+import org.gradle.api.internal.file.FileResource;
+import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
+import org.gradle.api.internal.file.archive.compression.Compressor;
+import org.gradle.api.internal.file.archive.compression.GzipArchiver;
+import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
+import org.gradle.api.internal.file.copy.ReadableCopySpec;
 import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
 import org.hamcrest.Description;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
 import org.jmock.api.Action;
 import org.jmock.api.Invocation;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.Before;
 import org.junit.runner.RunWith;
 
 import java.io.OutputStream;
 
+import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
 @RunWith(JMock.class)
 public class TarCopySpecVisitorTest {
     @Rule
@@ -47,34 +55,15 @@ public class TarCopySpecVisitorTest {
     private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
     private final TarCopySpecVisitor visitor = new TarCopySpecVisitor();
 
-    @Before
-    public void setUp() {
-        context.checking(new Expectations(){{
-            allowing(copySpec).getFileMode();
-            will(returnValue(1));
-            allowing(copySpec).getDirMode();
-            will(returnValue(2));
-        }});
-    }
-    
     @Test
     public void createsTarFile() {
-        final TestFile tarFile = tmpDir.getDir().file("test.tar");
-
-        context.checking(new Expectations() {{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(tarFile));
-            allowing(copyAction).getCompression();
-            will(returnValue(Compression.NONE));
-        }});
-
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        visitor.visitFile(file("dir/file1"));
-        visitor.visitFile(file("file2"));
+        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tar"),
+            new SimpleCompressor());
+        tarAndUntarAndCheckFileContents(tarFile);
+    }
 
-        visitor.endVisit();
+    private void tarAndUntarAndCheckFileContents(TestFile tarFile) {
+        tar(file("dir/file1"), file("file2"));
 
         TestFile expandDir = tmpDir.getDir().file("expanded");
         tarFile.untarTo(expandDir);
@@ -84,62 +73,37 @@ public class TarCopySpecVisitorTest {
 
     @Test
     public void createsGzipCompressedTarFile() {
-        final TestFile tarFile = tmpDir.getDir().file("test.tgz");
-
-        context.checking(new Expectations(){{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(tarFile));
-            allowing(copyAction).getCompression();
-            will(returnValue(Compression.GZIP));
-        }});
-
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        visitor.visitFile(file("dir/file1"));
-        visitor.visitFile(file("file2"));
-
-        visitor.endVisit();
-
-        TestFile expandDir = tmpDir.getDir().file("expanded");
-        tarFile.untarTo(expandDir);
-        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
-        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tgz"),
+            GzipArchiver.getCompressor());
+        tarAndUntarAndCheckFileContents(tarFile);
     }
 
     @Test
     public void createsBzip2CompressedTarFile() {
-        final TestFile tarFile = tmpDir.getDir().file("test.tbz2");
-
-        context.checking(new Expectations(){{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(tarFile));
-            allowing(copyAction).getCompression();
-            will(returnValue(Compression.BZIP2));
-        }});
+        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tbz2"),
+            Bzip2Archiver.getCompressor());
+        tarAndUntarAndCheckFileContents(tarFile);
+    }
 
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
+    @Test
+    public void tarFileContainsExpectedPermissions() {
+        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tar"),
+            new SimpleCompressor());
 
-        visitor.visitFile(file("dir/file1"));
-        visitor.visitFile(file("file2"));
+        tar(dir("dir"), file("file"));
 
-        visitor.endVisit();
+        Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("dir", 2);
+        expected.put("file", 1);
 
-        TestFile expandDir = tmpDir.getDir().file("expanded");
-        tarFile.untarTo(expandDir);
-        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
-        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+        assertVisitsPermissions(new TarFileTree(new FileResource(tarFile), null),
+            expected);
     }
 
     @Test
     public void wrapsFailureToOpenOutputFile() {
-        final TestFile tarFile = tmpDir.createDir("test.tar");
-
-        context.checking(new Expectations(){{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(tarFile));
-        }});
+        final TestFile tarFile = initializeTarFile(tmpDir.createDir("test.tar"),
+            new SimpleCompressor());
 
         try {
             visitor.startVisit(copyAction);
@@ -151,15 +115,8 @@ public class TarCopySpecVisitorTest {
 
     @Test
     public void wrapsFailureToAddElement() {
-        final TestFile tarFile = tmpDir.getDir().file("test.tar");
-
-        context.checking(new Expectations(){{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(tarFile));
-
-            allowing(copyAction).getCompression();
-            will(returnValue(Compression.NONE));
-        }});
+        final TestFile tarFile = initializeTarFile(tmpDir.getDir().file("test.tar"),
+            new SimpleCompressor());
 
         visitor.startVisit(copyAction);
         visitor.visitSpec(copySpec);
@@ -174,6 +131,31 @@ public class TarCopySpecVisitorTest {
         }
     }
 
+    private TestFile initializeTarFile(final TestFile tarFile, final Compressor compressor) {
+        context.checking(new Expectations() {{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(tarFile));
+            allowing(copyAction).getCompressor();
+            will(returnValue(compressor));
+        }});
+        return tarFile;
+    }
+
+    private void tar(FileVisitDetails... files) {
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        for (FileVisitDetails f : files) {
+            if (f.isDirectory()) {
+                visitor.visitDir(f);
+            } else {
+                visitor.visitFile(f);
+            }
+        }
+
+        visitor.endVisit();
+    }
+
     private FileVisitDetails file(final String path) {
         final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
         final String content = String.format("contents of %s", path);
@@ -188,6 +170,12 @@ public class TarCopySpecVisitorTest {
             allowing(details).getSize();
             will(returnValue((long)content.getBytes().length));
 
+            allowing(details).isDirectory();
+            will(returnValue(false));
+
+            allowing(details).getMode();
+            will(returnValue(1));
+
             allowing(details).copyTo(with(notNullValue(OutputStream.class)));
             will(new Action() {
                 public void describeTo(Description description) {
@@ -204,6 +192,26 @@ public class TarCopySpecVisitorTest {
         return details;
     }
 
+    private FileVisitDetails dir(final String path) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(false, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).isDirectory();
+            will(returnValue(true));
+
+            allowing(details).getMode();
+            will(returnValue(2));
+        }});
+
+        return details;
+    }
+
     private FileVisitDetails brokenFile(final String path, final Throwable failure) {
         final FileVisitDetails details = context.mock(FileVisitDetails.class, String.format("[%s]", path));
 
@@ -217,6 +225,12 @@ public class TarCopySpecVisitorTest {
             allowing(details).getSize();
             will(returnValue(1000L));
 
+            allowing(details).isDirectory();
+            will(returnValue(false));
+
+            allowing(details).getMode();
+            will(returnValue(1));
+
             allowing(details).copyTo(with(notNullValue(OutputStream.class)));
             will(new Action() {
                 public void describeTo(Description description) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
index fe98cad..f4b7663 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
@@ -15,28 +15,37 @@
  */
 package org.gradle.api.internal.file.archive;
 
+import java.util.Map;
+import java.util.HashMap;
+
 import org.gradle.api.GradleException;
 import org.gradle.api.InvalidUserDataException;
-import static org.gradle.api.file.FileVisitorUtil.*;
-import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
-import org.gradle.util.TestFile;
+import org.gradle.api.internal.file.FileResource;
+import org.gradle.api.internal.file.MaybeCompressedFileResource;
+import org.gradle.util.Resources;
 import org.gradle.util.TemporaryFolder;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
+import org.gradle.util.TestFile;
 import org.junit.Rule;
 import org.junit.Test;
 
-import static java.util.Collections.*;
+import static java.util.Collections.EMPTY_LIST;
+import static org.gradle.api.file.FileVisitorUtil.assertCanStopVisiting;
+import static org.gradle.api.file.FileVisitorUtil.assertVisits;
+import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
+import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 public class TarFileTreeTest {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final Resources resources = new Resources();
     private final TestFile tarFile = tmpDir.getDir().file("test.tar");
     private final TestFile rootDir = tmpDir.getDir().file("root");
     private final TestFile expandDir = tmpDir.getDir().file("tmp");
-    private final TarFileTree tree = new TarFileTree(tarFile, expandDir);
+    private final TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tarFile)), expandDir);
 
     @Test
     public void displayName() {
@@ -54,6 +63,34 @@ public class TarFileTreeTest {
     }
 
     @Test
+    public void readsGzippedTarFile() {
+        TestFile tgz = tmpDir.getDir().file("test.tgz");
+
+        rootDir.file("subdir/file1.txt").write("content");
+        rootDir.file("subdir2/file2.txt").write("content");
+        rootDir.tgzTo(tgz);
+
+        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tgz)), expandDir);
+
+        assertVisits(tree, toList("subdir/file1.txt", "subdir2/file2.txt"), toList("subdir", "subdir2"));
+        assertSetContainsForAllTypes(tree, toList("subdir/file1.txt", "subdir2/file2.txt"));
+    }
+
+    @Test
+    public void readsBzippedTarFile() {
+        TestFile tbz2 = tmpDir.getDir().file("test.tbz2");
+
+        rootDir.file("subdir/file1.txt").write("content");
+        rootDir.file("subdir2/file2.txt").write("content");
+        rootDir.tbzTo(tbz2);
+
+        TarFileTree tree = new TarFileTree(new MaybeCompressedFileResource(new FileResource(tbz2)), expandDir);
+
+        assertVisits(tree, toList("subdir/file1.txt", "subdir2/file2.txt"), toList("subdir", "subdir2"));
+        assertSetContainsForAllTypes(tree, toList("subdir/file1.txt", "subdir2/file2.txt"));
+    }
+
+    @Test
     public void canStopVisitingFiles() {
         rootDir.file("subdir/file1.txt").write("content");
         rootDir.file("subdir/other/file2.txt").write("content");
@@ -76,7 +113,7 @@ public class TarFileTreeTest {
             tree.visit(null);
             fail();
         } catch (InvalidUserDataException e) {
-            assertThat(e.getMessage(), equalTo("Cannot expand TAR '" + tarFile + "' as it is not a file."));
+            assertThat(e.getMessage(), containsString("Cannot expand TAR '" + tarFile + "'"));
         }
     }
 
@@ -89,7 +126,18 @@ public class TarFileTreeTest {
             tree.visit(null);
             fail();
         } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo("Could not expand TAR '" + tarFile + "'."));
+            assertThat(e.getMessage(), containsString("Unable to expand TAR '" + tarFile + "'"));
         }
     }
-}
\ No newline at end of file
+
+    @Test
+    public void expectedFilePermissionsAreFound() {
+        resources.findResource("permissions.tar").copyTo(tarFile);
+
+        final Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("file", 0644);
+        expected.put("folder", 0755);
+
+        assertVisitsPermissions(tree, expected);
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
index 5064a61..ef16626 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api.internal.file.archive;
 
+import java.util.Map;
+import java.util.HashMap;
 import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileVisitDetails;
@@ -24,20 +26,22 @@ import org.gradle.api.internal.file.copy.ReadableCopySpec;
 import org.gradle.util.TestFile;
 import org.gradle.util.TemporaryFolder;
 import org.hamcrest.Description;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
 import org.jmock.api.Action;
 import org.jmock.api.Invocation;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.Before;
 import org.junit.runner.RunWith;
 
 import java.io.OutputStream;
 
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.gradle.api.file.FileVisitorUtil.assertVisitsPermissions;
+
 @RunWith(JMock.class)
 public class ZipCopySpecVisitorTest {
     @Rule
@@ -46,34 +50,20 @@ public class ZipCopySpecVisitorTest {
     private final ArchiveCopyAction copyAction = context.mock(ArchiveCopyAction.class);
     private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
     private final ZipCopySpecVisitor visitor = new ZipCopySpecVisitor();
+    private TestFile zipFile;
 
     @Before
-    public void setUp() {
+    public void setup() {
+        zipFile = tmpDir.getDir().file("test.zip");
         context.checking(new Expectations(){{
-            allowing(copySpec).getFileMode();
-            will(returnValue(1));
-            allowing(copySpec).getDirMode();
-            will(returnValue(2));
+            allowing(copyAction).getArchivePath();
+            will(returnValue(zipFile));
         }});
     }
 
     @Test
     public void createsZipFile() {
-        final TestFile zipFile = tmpDir.getDir().file("test.zip");
-
-        context.checking(new Expectations(){{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(zipFile));
-        }});
-
-        visitor.startVisit(copyAction);
-        visitor.visitSpec(copySpec);
-
-        visitor.visitDir(dir("dir"));
-        visitor.visitFile(file("dir/file1"));
-        visitor.visitFile(file("file2"));
-
-        visitor.endVisit();
+        zip(dir("dir"), file("dir/file1"), file("file2"));
 
         TestFile expandDir = tmpDir.getDir().file("expanded");
         zipFile.unzipTo(expandDir);
@@ -82,12 +72,23 @@ public class ZipCopySpecVisitorTest {
     }
 
     @Test
+    public void zipFileContainsExpectedPermissions() {
+        zip(dir("dir"), file("file"));
+
+        Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("dir", 2);
+        expected.put("file", 1);
+
+        assertVisitsPermissions(new ZipFileTree(zipFile, null), expected);
+    }
+
+    @Test
     public void wrapsFailureToOpenOutputFile() {
-        final TestFile zipFile = tmpDir.createDir("test.zip");
+        final TestFile invalidZipFile = tmpDir.createDir("test.zip");
 
         context.checking(new Expectations(){{
             allowing(copyAction).getArchivePath();
-            will(returnValue(zipFile));
+            will(returnValue(invalidZipFile));
         }});
 
         try {
@@ -100,13 +101,6 @@ public class ZipCopySpecVisitorTest {
 
     @Test
     public void wrapsFailureToAddElement() {
-        final TestFile zipFile = tmpDir.getDir().file("test.zip");
-
-        context.checking(new Expectations(){{
-            allowing(copyAction).getArchivePath();
-            will(returnValue(zipFile));
-        }});
-
         visitor.startVisit(copyAction);
         visitor.visitSpec(copySpec);
 
@@ -120,6 +114,21 @@ public class ZipCopySpecVisitorTest {
         }
     }
 
+    private void zip(FileVisitDetails... files) {
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        for (FileVisitDetails f : files) {
+            if (f.isDirectory()) {
+                visitor.visitDir(f);
+            } else {
+                visitor.visitFile(f);
+            }
+        }
+
+        visitor.endVisit();
+    }
+
     private FileVisitDetails file(final String path) {
         final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
 
@@ -130,6 +139,12 @@ public class ZipCopySpecVisitorTest {
             allowing(details).getLastModified();
             will(returnValue(1000L));
 
+            allowing(details).isDirectory();
+            will(returnValue(false));
+
+            allowing(details).getMode();
+            will(returnValue(1));
+
             allowing(details).copyTo(with(notNullValue(OutputStream.class)));
             will(new Action() {
                 public void describeTo(Description description) {
@@ -155,6 +170,12 @@ public class ZipCopySpecVisitorTest {
 
             allowing(details).getLastModified();
             will(returnValue(1000L));
+
+            allowing(details).isDirectory();
+            will(returnValue(true));
+
+            allowing(details).getMode();
+            will(returnValue(2));
         }});
 
         return details;
@@ -170,6 +191,12 @@ public class ZipCopySpecVisitorTest {
             allowing(details).getLastModified();
             will(returnValue(1000L));
 
+            allowing(details).isDirectory();
+            will(returnValue(false));
+
+            allowing(details).getMode();
+            will(returnValue(1));
+
             allowing(details).copyTo(with(notNullValue(OutputStream.class)));
             will(new Action() {
                 public void describeTo(Description description) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
index 473b2da..eab06e8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
@@ -15,24 +15,27 @@
  */
 package org.gradle.api.internal.file.archive;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import java.util.Map;
+import java.util.HashMap;
 
+import org.gradle.util.Resources;
+import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.GradleException;
 import org.junit.Test;
 import org.junit.Rule;
-import org.gradle.util.TemporaryFolder;
+
 import static org.gradle.util.WrapUtil.*;
 import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
 import static org.gradle.api.file.FileVisitorUtil.*;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.GradleException;
-
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
 import static java.util.Collections.*;
 
 public class ZipFileTreeTest {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    @Rule public final Resources resources = new Resources();
     private final TestFile zipFile = tmpDir.getDir().file("test.zip");
     private final TestFile rootDir = tmpDir.getDir().file("root");
     private final TestFile expandDir = tmpDir.getDir().file("tmp");
@@ -91,4 +94,26 @@ public class ZipFileTreeTest {
             assertThat(e.getMessage(), equalTo("Could not expand ZIP '" + zipFile + "'."));
         }
     }
+
+    @Test
+    public void expectedFilePermissionsAreFound() {
+        resources.findResource("permissions.zip").copyTo(zipFile);
+
+        final Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("file", 0644);
+        expected.put("folder", 0755);
+
+        assertVisitsPermissions(tree, expected);
+    }
+
+    @Test
+    public void expectedDefaultForNoModeZips() {
+        resources.findResource("nomodeinfos.zip").copyTo(zipFile);
+
+        final Map<String, Integer> expected = new HashMap<String, Integer>();
+        expected.put("file.txt", 0644);
+        expected.put("folder", 0755);
+
+        assertVisitsPermissions(tree, expected);
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy
new file mode 100644
index 0000000..7dd9b99
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/compression/ArchiversTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.file.archive.compression;
+
+
+import org.gradle.api.internal.file.FileResource
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/13/11
+ */
+public class ArchiversTest extends Specification {
+
+    def "archivers have unqique URIs"() {
+        when:
+        def file = new File("/some/file")
+
+        def resource = new FileResource(file)
+        def bzip2 = new Bzip2Archiver(resource)
+        def gzip = new GzipArchiver(resource)
+
+        then:
+        resource.URI != bzip2.URI
+        bzip2.URI != gzip.URI
+        gzip.URI != resource.URI
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy
index 9d999aa..eea1f05 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/collections/DefaultFileCollectionResolveContextTest.groovy
@@ -1,401 +1,433 @@
-/*
- * Copyright 2011 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.file.collections
-
-import java.util.concurrent.Callable
-
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.tasks.TaskDependency
-import spock.lang.Specification
-import org.gradle.api.file.FileTree
-import org.gradle.api.file.FileCollection
-
-class DefaultFileCollectionResolveContextTest extends Specification {
-    final FileResolver resolver = Mock()
-    final DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext(resolver)
-
-    def resolveAsFileCollectionReturnsEmptyListWhenContextIsEmpty() {
-        expect:
-        context.resolveAsFileCollections() == []
-    }
-
-    def resolveAsFileTreeReturnsEmptyListWhenContextIsEmpty() {
-        expect:
-        context.resolveAsFileTrees() == []
-    }
-
-    def resolveAsMinimalFileCollectionReturnsEmptyListWhenContextIsEmpty() {
-        expect:
-        context.resolveAsMinimalFileCollections() == []
-    }
-
-    def resolveAsFileCollectionWrapsAMinimalFileSet() {
-        MinimalFileSet fileSet = Mock()
-
-        when:
-        context.add(fileSet)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileCollectionAdapter
-        result[0].fileCollection == fileSet
-    }
-
-    def resolveAsFileTreeConvertsTheElementsOfMinimalFileSet() {
-        MinimalFileSet fileSet = Mock()
-        File file = this.file('file1')
-        File dir = directory('file2')
-        File doesNotExist = nonExistent('file3')
-
-        when:
-        context.add(fileSet)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 2
-        result[0] instanceof FileTreeAdapter
-        result[0].tree instanceof SingletonFileTree
-        result[0].tree.file == file
-        result[1] instanceof FileTreeAdapter
-        result[1].tree instanceof DirectoryFileTree
-        result[1].tree.dir == dir
-        1 * fileSet.files >> ([file, dir, doesNotExist] as LinkedHashSet)
-    }
-
-    def resolveAsMinimalFileCollectionReturnsMinimalFileSet() {
-        MinimalFileSet fileSet = Mock()
-
-        when:
-        context.add(fileSet)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == [fileSet]
-    }
-
-    def resolveAsFileCollectionWrapsAMinimalFileTree() {
-        MinimalFileTree fileTree = Mock()
-
-        when:
-        context.add(fileTree)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree == fileTree
-    }
-
-    def resolveAsFileTreesWrapsAMinimalFileTree() {
-        MinimalFileTree fileTree = Mock()
-
-        when:
-        context.add(fileTree)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree == fileTree
-    }
-
-    def resolveAsMinimalFileCollectionWrapsAMinimalFileTree() {
-        MinimalFileTree fileTree = Mock()
-
-        when:
-        context.add(fileTree)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == [fileTree]
-    }
-
-    def resolveAsFileCollectionsForAFileCollection() {
-        FileCollection fileCollection = Mock()
-
-        when:
-        context.add(fileCollection)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [fileCollection]
-    }
-
-    def resolveAsFileCollectionsDelegatesToACompositeFileCollection() {
-        FileCollectionContainer composite = Mock()
-        FileCollection contents = Mock()
-
-        when:
-        context.add(composite)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [contents]
-        1 * composite.resolve(!null) >> { it[0].add(contents) }
-    }
-
-    def resolveAsFileTreesDelegatesToACompositeFileCollection() {
-        FileCollectionContainer composite = Mock()
-        FileTree contents = Mock()
-
-        when:
-        context.add(composite)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result == [contents]
-        1 * composite.resolve(!null) >> { it[0].add(contents) }
-    }
-
-    def resolveAsMinimalFileCollectionsDelegatesToACompositeFileCollection() {
-        FileCollectionContainer composite = Mock()
-        MinimalFileCollection contents = Mock()
-
-        when:
-        context.add(composite)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == [contents]
-        1 * composite.resolve(!null) >> { it[0].add(contents) }
-    }
-
-    def resolvesCompositeFileCollectionsInDepthwiseOrder() {
-        FileCollectionContainer parent1 = Mock()
-        FileCollection child1 = Mock()
-        FileCollectionContainer parent2 = Mock()
-        FileCollection child2 = Mock()
-        FileCollection child3 = Mock()
-
-        when:
-        context.add(parent1)
-        context.add(child3)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [child1, child2, child3]
-        1 * parent1.resolve(!null) >> { it[0].add(child1); it[0].add(parent2) }
-        1 * parent2.resolve(!null) >> { it[0].add(child2) }
-    }
-
-    def recursivelyResolvesReturnValueOfAClosure() {
-        FileCollection content = Mock()
-
-        when:
-        context.add { content }
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [content]
-    }
-
-    def resolvesAClosureWhichReturnsNull() {
-        when:
-        context.add { null }
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == []
-    }
-
-    def recursivelyResolvesReturnValueOfACallable() {
-        FileCollection content = Mock()
-        Callable<?> callable = Mock()
-
-        when:
-        context.add(callable)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * callable.call() >> content
-        result == [content]
-    }
-
-    def resolvesACallableWhichReturnsNull() {
-        Callable<?> callable = Mock()
-
-        when:
-        context.add(callable)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * callable.call() >> null
-        result == []
-    }
-
-    def recursivelyResolvesElementsOfAnIterable() {
-        FileCollection content = Mock()
-        Iterable<Object> iterable = Mock()
-
-        when:
-        context.add(iterable)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        1 * iterable.iterator() >> [content].iterator()
-        result == [content]
-    }
-
-    def recursivelyResolvesElementsAnArray() {
-        FileCollection content = Mock()
-
-        when:
-        context.add([content] as Object[])
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == [content]
-    }
-
-    def resolveAsFileCollectionsIgnoresATaskDependency() {
-        TaskDependency dependency = Mock()
-
-        when:
-        context.add(dependency)
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result == []
-    }
-
-    def resolveAsFileTreesIgnoresATaskDependency() {
-        TaskDependency dependency = Mock()
-
-        when:
-        context.add(dependency)
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result == []
-    }
-
-    def resolveAsMinimalFileCollectionsIgnoresATaskDependency() {
-        TaskDependency dependency = Mock()
-
-        when:
-        context.add(dependency)
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result == []
-    }
-
-    def resolveAsFileCollectionsUsesFileResolverToResolveOtherTypes() {
-        File file1 = new File('a')
-        File file2 = new File('b')
-
-        when:
-        context.add('a')
-        context.add('b')
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 2
-        result[0] instanceof FileCollectionAdapter
-        result[0].fileCollection instanceof ListBackedFileSet
-        result[0].fileCollection.files as List == [file1]
-        result[1] instanceof FileCollectionAdapter
-        result[1].fileCollection instanceof ListBackedFileSet
-        result[1].fileCollection.files as List == [file2]
-        1 * resolver.resolve('a') >> file1
-        1 * resolver.resolve('b') >> file2
-    }
-
-    def resolveAsFileTreeUsesFileResolverToResolveOtherTypes() {
-        File file = file('a')
-
-        when:
-        context.add('a')
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree instanceof SingletonFileTree
-        result[0].tree.file == file
-        1 * resolver.resolve('a') >> file
-    }
-
-    def resolveAsMinimalFileCollectionUsesFileResolverToResolveOtherTypes() {
-        File file = file('a')
-
-        when:
-        context.add('a')
-        def result = context.resolveAsMinimalFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof ListBackedFileSet
-        result[0].files as List == [file]
-        1 * resolver.resolve('a') >> file
-    }
-
-    def canPushContextWhichUsesADifferentFileResolverToConvertToFileCollections() {
-        FileResolver fileResolver = Mock()
-        File file = new File('a')
-
-        when:
-        context.push(fileResolver).add('a')
-        def result = context.resolveAsFileCollections()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileCollectionAdapter
-        result[0].fileCollection instanceof ListBackedFileSet
-        result[0].fileCollection.files as List == [file]
-        1 * fileResolver.resolve('a') >> file
-        0 * _._
-    }
-
-    def canPushContextWhichUsesADifferentFileResolverToConvertToFileTrees() {
-        FileResolver fileResolver = Mock()
-        File file = file('a')
-
-        when:
-        context.push(fileResolver).add('a')
-        def result = context.resolveAsFileTrees()
-
-        then:
-        result.size() == 1
-        result[0] instanceof FileTreeAdapter
-        result[0].tree instanceof SingletonFileTree
-        result[0].tree.file == file
-        1 * fileResolver.resolve('a') >> file
-    }
-
-    def file(String name) {
-        File f = Mock()
-        _ * f.file >> true
-        _ * f.exists() >> true
-        _ * f.canonicalFile >> f
-        f
-    }
-
-    def directory(String name) {
-        File f = Mock()
-        _ * f.directory >> true
-        _ * f.exists() >> true
-        _ * f.canonicalFile >> f
-        f
-    }
-
-    def nonExistent(String name) {
-        File f = Mock()
-        _ * f.canonicalFile >> f
-        f
-    }
-}
+/*
+ * Copyright 2011 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.file.collections
+
+import java.util.concurrent.Callable
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.tasks.TaskDependency
+import spock.lang.Specification
+import org.gradle.api.file.FileTree
+import org.gradle.api.file.FileCollection
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskOutputs
+
+class DefaultFileCollectionResolveContextTest extends Specification {
+    final FileResolver resolver = Mock()
+    final DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext(resolver)
+
+    def resolveAsFileCollectionReturnsEmptyListWhenContextIsEmpty() {
+        expect:
+        context.resolveAsFileCollections() == []
+    }
+
+    def resolveAsFileTreeReturnsEmptyListWhenContextIsEmpty() {
+        expect:
+        context.resolveAsFileTrees() == []
+    }
+
+    def resolveAsMinimalFileCollectionReturnsEmptyListWhenContextIsEmpty() {
+        expect:
+        context.resolveAsMinimalFileCollections() == []
+    }
+
+    def resolveAsFileCollectionWrapsAMinimalFileSet() {
+        MinimalFileSet fileSet = Mock()
+
+        when:
+        context.add(fileSet)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileCollectionAdapter
+        result[0].fileCollection == fileSet
+    }
+
+    def resolveAsFileTreeConvertsTheElementsOfMinimalFileSet() {
+        MinimalFileSet fileSet = Mock()
+        File file = this.file('file1')
+        File dir = directory('file2')
+        File doesNotExist = nonExistent('file3')
+
+        when:
+        context.add(fileSet)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 2
+        result[0] instanceof FileTreeAdapter
+        result[0].tree instanceof SingletonFileTree
+        result[0].tree.file == file
+        result[1] instanceof FileTreeAdapter
+        result[1].tree instanceof DirectoryFileTree
+        result[1].tree.dir == dir
+        1 * fileSet.files >> ([file, dir, doesNotExist] as LinkedHashSet)
+    }
+
+    def resolveAsMinimalFileCollectionReturnsMinimalFileSet() {
+        MinimalFileSet fileSet = Mock()
+
+        when:
+        context.add(fileSet)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == [fileSet]
+    }
+
+    def resolveAsFileCollectionWrapsAMinimalFileTree() {
+        MinimalFileTree fileTree = Mock()
+
+        when:
+        context.add(fileTree)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree == fileTree
+    }
+
+    def resolveAsFileTreesWrapsAMinimalFileTree() {
+        MinimalFileTree fileTree = Mock()
+
+        when:
+        context.add(fileTree)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree == fileTree
+    }
+
+    def resolveAsMinimalFileCollectionWrapsAMinimalFileTree() {
+        MinimalFileTree fileTree = Mock()
+
+        when:
+        context.add(fileTree)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == [fileTree]
+    }
+
+    def resolveAsFileCollectionsForAFileCollection() {
+        FileCollection fileCollection = Mock()
+
+        when:
+        context.add(fileCollection)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [fileCollection]
+    }
+
+    def resolveAsFileCollectionsDelegatesToACompositeFileCollection() {
+        FileCollectionContainer composite = Mock()
+        FileCollection contents = Mock()
+
+        when:
+        context.add(composite)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [contents]
+        1 * composite.resolve(!null) >> { it[0].add(contents) }
+    }
+
+    def resolveAsFileTreesDelegatesToACompositeFileCollection() {
+        FileCollectionContainer composite = Mock()
+        FileTree contents = Mock()
+
+        when:
+        context.add(composite)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result == [contents]
+        1 * composite.resolve(!null) >> { it[0].add(contents) }
+    }
+
+    def resolveAsMinimalFileCollectionsDelegatesToACompositeFileCollection() {
+        FileCollectionContainer composite = Mock()
+        MinimalFileCollection contents = Mock()
+
+        when:
+        context.add(composite)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == [contents]
+        1 * composite.resolve(!null) >> { it[0].add(contents) }
+    }
+
+    def resolvesCompositeFileCollectionsInDepthwiseOrder() {
+        FileCollectionContainer parent1 = Mock()
+        FileCollection child1 = Mock()
+        FileCollectionContainer parent2 = Mock()
+        FileCollection child2 = Mock()
+        FileCollection child3 = Mock()
+
+        when:
+        context.add(parent1)
+        context.add(child3)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [child1, child2, child3]
+        1 * parent1.resolve(!null) >> { it[0].add(child1); it[0].add(parent2) }
+        1 * parent2.resolve(!null) >> { it[0].add(child2) }
+    }
+
+    def recursivelyResolvesReturnValueOfAClosure() {
+        FileCollection content = Mock()
+
+        when:
+        context.add { content }
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [content]
+    }
+
+    def resolvesAClosureWhichReturnsNull() {
+        when:
+        context.add { null }
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == []
+    }
+
+    def resolvesTasksOutputsWithEmptyFileCollection() {
+        FileCollection content = Mock()
+        TaskOutputs outputs = Mock()
+        when:
+
+        context.add outputs
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * outputs.files >> content
+        result == [content]
+    }
+
+    def recursivelyResolvesReturnValueOfACallable() {
+        FileCollection content = Mock()
+        Callable<?> callable = Mock()
+
+        when:
+        context.add(callable)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * callable.call() >> content
+        result == [content]
+    }
+
+    def resolvesACallableWhichReturnsNull() {
+        Callable<?> callable = Mock()
+
+        when:
+        context.add(callable)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * callable.call() >> null
+        result == []
+    }
+
+    def recursivelyResolvesElementsOfAnIterable() {
+        FileCollection content = Mock()
+        Iterable<Object> iterable = Mock()
+
+        when:
+        context.add(iterable)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        1 * iterable.iterator() >> [content].iterator()
+        result == [content]
+    }
+
+    def recursivelyResolvesElementsAnArray() {
+        FileCollection content = Mock()
+
+        when:
+        context.add([content] as Object[])
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [content]
+    }
+
+    def resolveAsFileCollectionsIgnoresATaskDependency() {
+        TaskDependency dependency = Mock()
+
+        when:
+        context.add(dependency)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == []
+    }
+
+    def resolveAsFileTreesIgnoresATaskDependency() {
+        TaskDependency dependency = Mock()
+
+        when:
+        context.add(dependency)
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result == []
+    }
+
+    def resolveAsMinimalFileCollectionsIgnoresATaskDependency() {
+        TaskDependency dependency = Mock()
+
+        when:
+        context.add(dependency)
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result == []
+    }
+
+    def resolveAsFileCollectionsResolvesTaskToItsOutputFiles() {
+        Task task = Mock()
+        TaskOutputs outputs = Mock()
+        FileCollection outputFiles = Mock()
+
+        given:
+        _ * task.outputs >> outputs
+        _ * outputs.files >> outputFiles
+
+        when:
+        context.add(task)
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result == [outputFiles]
+    }
+
+    def resolveAsFileCollectionsUsesFileResolverToResolveOtherTypes() {
+        File file1 = new File('a')
+        File file2 = new File('b')
+
+        when:
+        context.add('a')
+        context.add('b')
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 2
+        result[0] instanceof FileCollectionAdapter
+        result[0].fileCollection instanceof ListBackedFileSet
+        result[0].fileCollection.files as List == [file1]
+        result[1] instanceof FileCollectionAdapter
+        result[1].fileCollection instanceof ListBackedFileSet
+        result[1].fileCollection.files as List == [file2]
+        1 * resolver.resolve('a') >> file1
+        1 * resolver.resolve('b') >> file2
+    }
+
+    def resolveAsFileTreeUsesFileResolverToResolveOtherTypes() {
+        File file = file('a')
+
+        when:
+        context.add('a')
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree instanceof SingletonFileTree
+        result[0].tree.file == file
+        1 * resolver.resolve('a') >> file
+    }
+
+    def resolveAsMinimalFileCollectionUsesFileResolverToResolveOtherTypes() {
+        File file = file('a')
+
+        when:
+        context.add('a')
+        def result = context.resolveAsMinimalFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof ListBackedFileSet
+        result[0].files as List == [file]
+        1 * resolver.resolve('a') >> file
+    }
+
+    def canPushContextWhichUsesADifferentFileResolverToConvertToFileCollections() {
+        FileResolver fileResolver = Mock()
+        File file = new File('a')
+
+        when:
+        context.push(fileResolver).add('a')
+        def result = context.resolveAsFileCollections()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileCollectionAdapter
+        result[0].fileCollection instanceof ListBackedFileSet
+        result[0].fileCollection.files as List == [file]
+        1 * fileResolver.resolve('a') >> file
+        0 * _._
+    }
+
+    def canPushContextWhichUsesADifferentFileResolverToConvertToFileTrees() {
+        FileResolver fileResolver = Mock()
+        File file = file('a')
+
+        when:
+        context.push(fileResolver).add('a')
+        def result = context.resolveAsFileTrees()
+
+        then:
+        result.size() == 1
+        result[0] instanceof FileTreeAdapter
+        result[0].tree instanceof SingletonFileTree
+        result[0].tree.file == file
+        1 * fileResolver.resolve('a') >> file
+    }
+
+    def file(String name) {
+        File f = Mock()
+        _ * f.file >> true
+        _ * f.exists() >> true
+        _ * f.canonicalFile >> f
+        f
+    }
+
+    def directory(String name) {
+        File f = Mock()
+        _ * f.directory >> true
+        _ * f.exists() >> true
+        _ * f.canonicalFile >> f
+        f
+    }
+
+    def nonExistent(String name) {
+        File f = Mock()
+        _ * f.canonicalFile >> f
+        f
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
index 253d525..6a2a41d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
@@ -17,23 +17,21 @@ package org.gradle.api.internal.file.copy
 
 import org.apache.tools.ant.filters.HeadFilter
 import org.apache.tools.ant.filters.StripJavaComments
+import org.gradle.api.Action
+import org.gradle.api.file.FileTree
 import org.gradle.api.file.RelativePath
-import org.gradle.util.TestFile
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.util.PatternSet
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
 import org.jmock.integration.junit4.JMock
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.apache.tools.zip.UnixStat
-import org.gradle.api.specs.Spec
-import org.gradle.api.tasks.util.PatternSet
-import org.gradle.api.file.FileTree
-import org.gradle.api.Action
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.file.CopySpec
 
 @RunWith(JMock)
 public class CopySpecImplTest {
@@ -107,15 +105,6 @@ public class CopySpecImplTest {
         assertThat(spec.childSpecs.size(), equalTo(2))
     }
     
-    @Test public void testFromSpec() {
-        CopySpecImpl other1 = new CopySpecImpl(fileResolver)
-        CopySpecImpl other2 = new CopySpecImpl(fileResolver)
-
-        spec.from other1, other2
-        assertTrue(spec.sourcePaths.empty)
-        assertThat(spec.childSpecs.size(), equalTo(2))
-    }
-
     @Test public void testWithSpecSource() {
         CopyActionImpl source = new CopyActionImpl(fileResolver, null)
 
@@ -329,9 +318,9 @@ public class CopySpecImplTest {
         assertThat(childSpec.allCopyActions, equalTo([parentAction, childAction]))
     }
 
-    @Test public void testDefaultPermissions() {
-        org.junit.Assert.assertEquals(UnixStat.DEFAULT_FILE_PERM, spec.fileMode)
-        org.junit.Assert.assertEquals(UnixStat.DEFAULT_DIR_PERM, spec.dirMode)
+    @Test public void testHasNoPermissionsByDefault() {
+        assert spec.fileMode == null
+        assert spec.dirMode == null
     }
 
     @Test public void testInheritsPermissionsFromParent() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
index 7a37201..37c7ccd 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
@@ -15,12 +15,13 @@
  */
 package org.gradle.api.internal.file.copy
 
-import org.gradle.api.internal.file.BaseDirConverter
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.api.internal.file.BaseDirFileResolver
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
 
 /**
  * @author Hans Dockter
@@ -29,7 +30,7 @@ class DeleteActionImplTest extends Specification {
     @Rule
     TemporaryFolder tmpDir = new TemporaryFolder();
 
-    FileResolver fileResolver = new BaseDirConverter(tmpDir.getDir())
+    FileResolver fileResolver = new BaseDirFileResolver(FileSystems.default, tmpDir.getDir())
     
     DeleteActionImpl delete = new DeleteActionImpl(fileResolver);
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy
index 23d5215..ddd94ed 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.file.copy
 
 import org.junit.Test
-import org.gradle.util.SystemProperties
+import org.gradle.internal.SystemProperties
 
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
index b4da117..d4700d1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
@@ -77,7 +77,9 @@ public class MappingCopySpecVisitorTest {
 
     @Test
     public void visitFileInvokesEachCopyAction() {
+        @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action1 = context.mock(Action.class, "action1");
+        @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
         final Collector<FileCopyDetails> collectDetails1 = collector();
         final Collector<Object> collectDetails2 = collector();
@@ -156,7 +158,9 @@ public class MappingCopySpecVisitorTest {
 
     @Test
     public void copyActionCanExcludeFile() {
+        @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action1 = context.mock(Action.class, "action1");
+        @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
 
         context.checking(new Expectations(){{
@@ -196,6 +200,9 @@ public class MappingCopySpecVisitorTest {
     public void copyActionCanFilterContentWhenFileIsCopiedToFile() {
         final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
 
+        // shortcut the permission logic by explicitly setting permissions
+        mappedDetails.setMode(0644);
+
         context.checking(new Expectations() {{
             one(details).open();
             will(returnValue(new ByteArrayInputStream("content".getBytes())));
@@ -239,6 +246,62 @@ public class MappingCopySpecVisitorTest {
         assertThat(mappedDetails.getFile(), sameInstance(file));
     }
 
+    @Test
+    public void permissionsArePreservedByDefault() {
+        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations(){{
+            one(details).isDirectory();
+            will(returnValue(true));
+
+            one(spec).getDirMode();
+            will(returnValue(null));
+
+            one(details).getMode();
+            will(returnValue(123));
+        }});
+
+        assertThat(copyDetails.getMode(), equalTo(123));
+    }
+
+    @Test
+    public void filePermissionsCanBeOverriddenBySpec() {
+        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations(){{
+            one(details).isDirectory();
+            will(returnValue(false));
+
+            one(spec).getFileMode();
+            will(returnValue(234));
+        }});
+
+        assertThat(copyDetails.getMode(), equalTo(234));
+    }
+
+    @Test
+    public void directoryPermissionsCanBeOverriddenBySpec() {
+        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations(){{
+            one(details).isDirectory();
+            will(returnValue(true));
+
+            one(spec).getDirMode();
+            will(returnValue(345));
+        }});
+
+        assertThat(copyDetails.getMode(), equalTo(345));
+    }
+
+    @Test
+    public void permissionsCanBeOverriddenByCopyAction() {
+        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
+
+        copyDetails.setMode(456);
+        assertThat(copyDetails.getMode(), equalTo(456));
+    }
+
     private FileVisitDetails expectSpecAndFileVisited() {
         final Collector<FileVisitDetails> collector = collector();
 
@@ -259,6 +322,7 @@ public class MappingCopySpecVisitorTest {
 
     private FileCopyDetails expectActionExecutedWhenFileVisited() {
         final Collector<FileCopyDetails> collectDetails = collector();
+        @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action = context.mock(Action.class, "action1");
 
         context.checking(new Expectations(){{
@@ -280,8 +344,7 @@ public class MappingCopySpecVisitorTest {
         visitor.visitSpec(spec);
         visitor.visitFile(details);
 
-        FileCopyDetails copyDetails = collectDetails.get();
-        return copyDetails;
+        return collectDetails.get();
     }
 
     private FileVisitDetails expectSpecAndDirVisited() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/PathNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/PathNotationParserTest.groovy
new file mode 100644
index 0000000..3b4010a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/PathNotationParserTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 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.file.copy
+
+import java.util.concurrent.Callable
+import org.gradle.api.internal.artifacts.DefaultExcludeRule
+import spock.lang.Specification
+
+class PathNotationParserTest extends Specification {
+    PathNotationParser<String> pathNotationParser = new PathNotationParser<String>();
+
+    def "with null"() {
+        expect:
+        null == pathNotationParser.parseNotation(null);
+    }
+
+    def "with CharSequence"() {
+        expect:
+        pathToParse == pathNotationParser.parseNotation(pathToParse);
+        where:
+        pathToParse << ["this/is/a/path", 'this/is/a/path', "this/is/a/${'path'}"]
+    }
+
+    def "with Number"() {
+        expect:
+        expected == pathNotationParser.parseNotation(input);
+        where:
+        input << [1, 1.5, -1]
+        expected << ["1", "1.5", "-1"]
+    }
+
+    def "with Boolean"() {
+        expect:
+        expected == pathNotationParser.parseNotation(input);
+        where:
+        input << [true, false, Boolean.TRUE, Boolean.FALSE]
+        expected << ["true", "false", "true", "false"]
+    }
+
+    def "with unsupported class"() {
+        def customObj = new DefaultExcludeRule();
+        expect:
+        customObj.toString() == pathNotationParser.parseNotation(customObj);
+    }
+
+    def "with closure "() {
+        expect:
+        "closure/path" == pathNotationParser.parseNotation({ "closure/path"});
+    }
+
+    def "with double nested closure "() {
+        expect:
+        "closure/path" == pathNotationParser.parseNotation({ "closure/path" });
+    }
+
+    def "with closure with null return value"() {
+        expect:
+        null == pathNotationParser.parseNotation({ null });
+    }
+
+    def "with closure of unsupported return value"() {
+        def customObj = new DefaultExcludeRule()
+        expect:
+        customObj.toString() == pathNotationParser.parseNotation({ customObj });
+    }
+
+    def "with Callable that throws exception"() {
+        Callable callable = new Callable<String>() {
+            String call() {
+                return "callable/path";
+            }
+        };
+        expect:
+        "callable/path" == pathNotationParser.parseNotation(callable);
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java
index 76ae2b9..4206163 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java
@@ -27,7 +27,8 @@ import org.junit.runner.RunWith;
 @RunWith(JMock.class)
 public class RenamingCopyActionTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Transformer<String> transformer = context.mock(Transformer.class);
+    @SuppressWarnings("unchecked")
+    private final Transformer<String, String> transformer = context.mock(Transformer.class);
     private final FileCopyDetails details = context.mock(FileCopyDetails.class);
     private final RenamingCopyAction action = new RenamingCopyAction(transformer);
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
index 685c770..eb04b6b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
@@ -87,7 +87,7 @@ public class SyncCopySpecVisitorTest {
         Set visited = (Set) field.get(visitor);
         assert visited.contains(new RelativePath(true, "included.txt"));
         assert !visited.contains(new RelativePath(true, "extra", "extra.txt"));
-        final Set actual = new HashSet();
+        final Set<RelativePath> actual = new HashSet<RelativePath>();
         new DirectoryFileTree(destDir).depthFirst().visit(new FileVisitor() {
             public void visitDir(FileVisitDetails dirDetails) {
             }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
index 767209f..36e91a4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
@@ -15,25 +15,25 @@
  */
 package org.gradle.api.internal.initialization
 
-import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.dsl.RepositoryHandler
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory
+import org.gradle.api.internal.artifacts.DependencyManagementServices
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
-import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
+import org.gradle.api.internal.file.FileResolver
 import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.util.ObservableUrlClassLoader
+
 import spock.lang.Specification
-import org.gradle.api.internal.Factory
+import org.gradle.util.MutableURLClassLoader
 
 class DefaultScriptHandlerFactoryTest extends Specification {
-    private final Factory<RepositoryHandler> repositoryHandlerFactory = Mock()
-    private final ConfigurationContainerFactory configurationContainerFactory = Mock()
     private final DependencyMetaDataProvider metaDataProvider = Mock()
-    private final DependencyFactory dependencyFactory = Mock()
     private final ClassLoader parentClassLoader = new ClassLoader() {}
     private final RepositoryHandler repositoryHandler = Mock()
-    private final ConfigurationContainer configurationContainer = Mock()
-    private final DefaultScriptHandlerFactory factory = new DefaultScriptHandlerFactory(repositoryHandlerFactory, configurationContainerFactory, metaDataProvider, dependencyFactory)
+    private final ConfigurationContainerInternal configurationContainer = Mock()
+    private final FileResolver fileResolver = Mock()
+    private final DependencyManagementServices dependencyManagementServices = Mock()
+    private final DefaultScriptHandlerFactory factory = new DefaultScriptHandlerFactory(dependencyManagementServices, fileResolver, metaDataProvider)
 
     def createsScriptHandler() {
         ScriptSource script = scriptSource()
@@ -44,7 +44,7 @@ class DefaultScriptHandlerFactoryTest extends Specification {
 
         then:
         handler instanceof DefaultScriptHandler
-        handler.classLoader instanceof ObservableUrlClassLoader
+        handler.classLoader instanceof MutableURLClassLoader
         handler.classLoader.parent == parentClassLoader
     }
 
@@ -77,8 +77,10 @@ class DefaultScriptHandlerFactoryTest extends Specification {
     }
 
     private def expectConfigContainerCreated() {
-        _ * repositoryHandlerFactory.create() >> repositoryHandler
-        _ * configurationContainerFactory.createConfigurationContainer(repositoryHandler, metaDataProvider, _) >> configurationContainer
+        DependencyResolutionServices dependencyResolutionServices = Mock()
+        _ * dependencyManagementServices.create(fileResolver, metaDataProvider, _, _) >> dependencyResolutionServices
+        _ * dependencyResolutionServices.resolveRepositoryHandler >> repositoryHandler
+        _ * dependencyResolutionServices.configurationContainer >> configurationContainer
     }
 
     private def scriptSource(String className = 'script') {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
index 639b10d..e295350 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
@@ -24,11 +24,12 @@ import org.gradle.util.WrapUtil
 import org.jmock.integration.junit4.JMock
 import org.junit.Test
 import org.junit.runner.RunWith
-import static org.gradle.util.Matchers.*
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.util.ObservableUrlClassLoader
+import org.gradle.util.MutableURLClassLoader
+import org.gradle.util.ConfigureUtil
 
 @RunWith(JMock)
 public class DefaultScriptHandlerTest {
@@ -38,7 +39,7 @@ public class DefaultScriptHandlerTest {
     private final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class)
     private final Configuration configuration = context.mock(Configuration.class)
     private final ScriptSource scriptSource = context.mock(ScriptSource.class)
-    private final ObservableUrlClassLoader classLoader = context.mock(ObservableUrlClassLoader.class)
+    private final MutableURLClassLoader classLoader = context.mock(MutableURLClassLoader.class)
 
     @Test void addsClasspathConfiguration() {
         context.checking {
@@ -69,13 +70,17 @@ public class DefaultScriptHandlerTest {
     @Test void canConfigureRepositories() {
         DefaultScriptHandler handler = handler()
 
+        def configure = {
+            mavenCentral()
+        }
+
         context.checking {
+            one(repositoryHandler).configure(configure)
+            will { ConfigureUtil.configure(configure, repositoryHandler, false) }
             one(repositoryHandler).mavenCentral()
         }
 
-        handler.repositories {
-            mavenCentral()
-        }
+        handler.repositories(configure)
     }
 
     @Test void canConfigureDependencies() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
new file mode 100644
index 0000000..b39e7c0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.notations.parsers
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.internal.notations.api.UnsupportedNotationException
+import spock.lang.Specification
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ErrorHandlingNotationParserTest extends Specification {
+    def NotationParser<String> target = Mock()
+    def parser = new ErrorHandlingNotationParser<String>("String", "<broken>", target)
+
+    def "reports unable to parse null"() {
+        given:
+        target.parseNotation(null) >> { throw new UnsupportedNotationException(null) }
+        target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
+
+        when:
+        parser.parseNotation(null)
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == toPlatformLineSeparators('''Cannot convert a null value to an object of type String.
+The following types/formats are supported:
+  - format 1
+  - format 2
+<broken>''')
+    }
+
+    def "reports unable to parse non-null"() {
+        given:
+        target.parseNotation("bad") >> { throw new UnsupportedNotationException("broken-part") }
+        target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
+
+        when:
+        parser.parseNotation("bad")
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == toPlatformLineSeparators('''Cannot convert the provided notation to an object of type String: broken-part.
+The following types/formats are supported:
+  - format 1
+  - format 2
+<broken>''')
+
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/MapNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/MapNotationParserTest.groovy
new file mode 100644
index 0000000..98a12f0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/MapNotationParserTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011 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.notations.parsers
+
+import spock.lang.Specification
+import org.gradle.api.internal.notations.api.UnsupportedNotationException
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.tasks.Optional
+
+class MapNotationParserTest extends Specification {
+    final DummyParser parser = new DummyParser()
+    
+    def "parses map with required keys"() {
+        expect:
+        def object = parser.parseNotation([name: 'name', version: 'version'])
+        object.key1 == 'name'
+        object.key2 == 'version'
+        object.prop1 == null
+    }
+
+    def "parses map with required and optional keys"() {
+        expect:
+        def object = parser.parseNotation([name: 'name', version: 'version', optional: '1.2'])
+        object.key1 == 'name'
+        object.key2 == 'version'
+        object.optional == '1.2'
+        object.prop1 == null
+    }
+
+    def "configures properties of converted object using extra properties"() {
+        expect:
+        def object = parser.parseNotation([name: 'name', version: 'version', prop1: 'prop1', optional: '1.2'])
+        object.key1 == 'name'
+        object.key2 == 'version'
+        object.prop1 == 'prop1'
+    }
+
+    def "does not mutate original map"() {
+        def source = [name: 'name', version: 'version', prop1: 'prop1', optional: '1.2']
+        def copy = new HashMap<String, Object>(source)
+        
+        when:
+        parser.parseNotation(source)
+        
+        then:
+        source == copy
+    }
+
+    def "does not parse map with missing keys"() {
+        when:
+        parser.parseNotation([name: 'name'])
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == 'Required keys [version] are missing from map {name=name}.'
+    }
+
+    def "treats empty strings and null values as missing"() {
+        when:
+        parser.parseNotation([name: null, version: ''])
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message.startsWith 'Required keys [name, version] are missing from map '
+    }
+
+    def "does not parse map with unknown extra properties"() {
+        when:
+        parser.parseNotation([name: 'name', version: 1.2, unknown: 'unknown'])
+
+        then:
+        MissingFieldException e = thrown()
+    }
+
+    def "does not parse notation that is not a map"() {
+        when:
+        parser.parseNotation('string')
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+    
+    static class DummyParser extends MapNotationParser<TargetObject> {
+        protected TargetObject parseMap(@MapKey('name') String name,
+                                        @MapKey('version') String version,
+                                        @Optional @MapKey('optional') optional) {
+            return new TargetObject(key1:  name, key2:  version, optional:  optional)
+        }
+    }
+
+    static class TargetObject {
+        String key1;
+        String key2;
+        String optional;
+        String prop1;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParserTest.groovy
new file mode 100644
index 0000000..85881bd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TypedNotationParserTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.notations.parsers;
+
+
+import org.gradle.api.internal.notations.api.UnsupportedNotationException
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/10/11
+ */
+public class TypedNotationParserTest extends Specification {
+
+    def parser = new DummyParser();
+
+    def "parses object of source type"(){
+        expect:
+        parser.parseNotation("100") == 100
+    }
+
+    def "throws meaningful exception on parse attempt"(){
+        when:
+        parser.parseNotation(new Object())
+
+        then:
+        thrown(UnsupportedNotationException)
+    }
+
+    class DummyParser extends TypedNotationParser<String, Integer> {
+
+        DummyParser() {
+            super(String.class)
+        }
+
+        Integer parseType(String notation) {
+            return Integer.valueOf(notation);
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
index fa856d3..769bd53 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
@@ -16,13 +16,15 @@
 
 package org.gradle.api.internal.plugins
 
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.plugins.Convention
 import org.gradle.api.plugins.TestPluginConvention1
 import org.gradle.api.plugins.TestPluginConvention2
 import org.junit.Before
 import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
 
 /**
  * @author Hans Dockter
@@ -33,62 +35,48 @@ class DefaultConventionTest {
     TestPluginConvention1 convention1
     TestPluginConvention2 convention2
 
+    Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
+
     @Before public void setUp() {
-        convention = new DefaultConvention()
+        convention = new DefaultConvention(instantiator)
         convention1 = new TestPluginConvention1()
         convention2 = new TestPluginConvention2()
         convention.plugins.plugin1 = convention1
         convention.plugins.plugin2 = convention2
     }
 
-    @Test public void testGetProperty() {
-        assertEquals(convention1.a, convention.plugins.plugin1.a)
-        assertEquals(convention1.a, convention.a)
-    }
-
-    @Test public void testGetPropertiesWithAmbiguity() {
-        assertEquals(convention1.a, convention.plugins.plugin1.a)
-        assertEquals(convention2.a, convention.plugins.plugin2.a)
-        assertEquals(convention1.a, convention.a)
+    @Test public void mixesInEachPropertyOfConventionObject() {
+        assertEquals(convention1.b, convention.extensionsAsDynamicObject.b)
     }
 
-    @Test public void testGetAllProperties() {
-        assertEquals(convention1.a, convention.properties.a)
-        assertEquals(convention1.b, convention.properties.b)
-        assertEquals(convention1.c, convention.properties.c)
+    @Test public void conventionObjectsPropertiesHavePrecendenceAccordingToOrderAdded() {
+        assertEquals(convention1.a, convention.extensionsAsDynamicObject.a)
     }
 
-    @Test public void testSetProperties() {
-        convention.b = 'newvalue'
-        assertEquals('newvalue', convention.plugins.plugin1.b)
+    @Test public void canSetConventionObjectProperties() {
+        convention.extensionsAsDynamicObject.b = 'newvalue'
+        assertEquals('newvalue', convention1.b)
     }
 
-    @Test public void testSetPropertiesWithAmbiguity() {
-        convention.a = 'newvalue'
+    @Test public void canSetPropertiesWithAmbiguity() {
+        convention.extensionsAsDynamicObject.a = 'newvalue'
         assertEquals('newvalue', convention1.a)
     }
 
-    @Test (expected = MissingPropertyException) public void testMissingPropertiesWithGet() {
-        convention.prop
+    @Test(expected = MissingPropertyException) public void throwsMissingPropertyExceptionForUnknownProperty() {
+        convention.extensionsAsDynamicObject.prop
     }
 
-    @Test(expected = MissingPropertyException) public void testMissingPropertiesWithSet() {
-        convention.prop = 'newvalue'
+    @Test public void mixesInEachMethodOfConventionObject() {
+        assertEquals(convention1.meth('somearg'), convention.extensionsAsDynamicObject.meth('somearg'))
     }
 
-    @Test public void testMethods() {
-        assertEquals(convention1.meth('somearg'), convention.plugins.plugin1.meth('somearg'))
-        assertEquals(convention1.meth('somearg'), convention.meth('somearg'))
+    @Test public void conventionObjectsMethodsHavePrecendenceAccordingToOrderAdded() {
+        assertEquals(convention1.meth(), convention.extensionsAsDynamicObject.meth())
     }
 
-    @Test public void testMethodsWithAmbiguity() {
-        assertEquals(convention1.meth(), convention.plugins.plugin1.meth())
-        assertEquals(convention2.meth(), convention.plugins.plugin2.meth())
-        assertEquals(convention.meth(), convention1.meth())
-    }
-
-    @Test (expected = MissingMethodException) public void testMissingMethod() {
-        convention.methUnknown()
+    @Test(expected = MissingMethodException) public void testMissingMethod() {
+        convention.extensionsAsDynamicObject.methUnknown()
     }
 
     @Test public void testCanLocateConventionObjectByType() {
@@ -97,7 +85,7 @@ class DefaultConventionTest {
         assertSame(convention1, convention.findPlugin(TestPluginConvention1))
         assertSame(convention2, convention.findPlugin(TestPluginConvention2))
     }
-    
+
     @Test public void testGetPluginFailsWhenMultipleConventionObjectsWithCompatibleType() {
         try {
             convention.getPlugin(Object)
@@ -124,8 +112,50 @@ class DefaultConventionTest {
             assertThat(e.message, equalTo('Could not find any convention object of type String.'))
         }
     }
-    
+
     @Test public void testFindPluginReturnsNullWhenNoConventionObjectsWithCompatibleType() {
         assertNull(convention.findPlugin(String))
     }
-}
+
+    @Test public void addsPropertyAndConfigureMethodForEachExtension() {
+        //when
+        convention = new DefaultConvention(instantiator)
+        def ext = new FooExtension()
+        convention.add("foo", ext)
+
+        //then
+        assertTrue(convention.extensionsAsDynamicObject.hasProperty("foo"))
+        assertTrue(convention.extensionsAsDynamicObject.hasMethod("foo", {}))
+        assertEquals(convention.extensionsAsDynamicObject.properties.get("foo"), ext);
+    }
+
+    @Test public void extensionsTakePrecendenceOverPluginConventions() {
+        convention = new DefaultConvention(instantiator)
+        convention.plugins.foo = new FooPluginExtension()
+        convention.add("foo", new FooExtension())
+
+        assertTrue(convention.extensionsAsDynamicObject.properties.get("foo") instanceof FooExtension);
+        assertTrue(convention.extensionsAsDynamicObject.foo instanceof FooExtension);
+        convention.extensionsAsDynamicObject.foo {
+            assertEquals("Hello world!", message);
+        }
+    }
+
+    @Test void canCreateExtensions() {
+        convention = new DefaultConvention(instantiator)
+        FooExtension extension = convention.create("foo", FooExtension)
+        assert extension.is(convention.getByName("foo"))
+    }
+
+    static class FooExtension {
+        String message = "Hello world!";
+    }
+
+    static class FooPluginExtension {
+        String foo = "foo"
+
+        void foo(Closure closure) {
+            fail("should not be called");
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultExtraPropertiesExtensionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultExtraPropertiesExtensionTest.groovy
new file mode 100644
index 0000000..2b1d3a7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultExtraPropertiesExtensionTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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.gradle.api.plugins.ExtraPropertiesExtensionTest
+
+public class DefaultExtraPropertiesExtensionTest extends ExtraPropertiesExtensionTest<DefaultExtraPropertiesExtension> {
+
+    DefaultExtraPropertiesExtension createExtension() {
+        new DefaultExtraPropertiesExtension()
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java
index 69e626a..b119351 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java
@@ -23,13 +23,13 @@ import org.gradle.api.internal.project.TestPlugin2;
 import org.gradle.api.plugins.PluginInstantiationException;
 import org.gradle.api.plugins.UnknownPluginException;
 import org.gradle.util.GUtil;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
 import org.hamcrest.Matchers;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -41,7 +41,8 @@ import java.net.URL;
 import java.util.Properties;
 
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 /**
  * @author Hans Dockter
@@ -50,9 +51,7 @@ import static org.junit.Assert.*;
 public class DefaultPluginRegistryTest {
     private String pluginId = "test";
     private DefaultPluginRegistry pluginRegistry;
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
     @Rule
     public TemporaryFolder testDir = new TemporaryFolder();
     private ClassLoader classLoader;
@@ -133,6 +132,29 @@ public class DefaultPluginRegistryTest {
     }
 
     @Test
+    public void failsWhenImplementationClassSpecifiedInPropertiesFileDoesNotImplementPlugin() throws MalformedURLException, ClassNotFoundException {
+        Properties properties = new Properties();
+        final TestFile propertiesFile = testDir.file("prop");
+        properties.setProperty("implementation-class", String.class.getName());
+        GUtil.saveProperties(properties, propertiesFile);
+        final URL url = propertiesFile.toURI().toURL();
+
+        context.checking(new Expectations() {{
+            allowing(classLoader).getResource("META-INF/gradle-plugins/brokenImpl.properties");
+            will(returnValue(url));
+            allowing(classLoader).loadClass("java.lang.String");
+            will(returnValue(String.class));
+        }});
+
+        try {
+            pluginRegistry.getTypeForId("brokenImpl");
+            fail();
+        } catch (PluginInstantiationException e) {
+            assertThat(e.getMessage(), equalTo("Implementation class 'java.lang.String' specified for plugin 'brokenImpl' does not implement the Plugin interface. Specified in " + url + "."));
+        }
+    }
+
+    @Test
     public void wrapsPluginInstantiationFailure() {
         try {
             pluginRegistry.loadPlugin(BrokenPlugin.class);
@@ -144,6 +166,19 @@ public class DefaultPluginRegistryTest {
     }
 
     @Test
+    public void wrapsFailureToLoadImplementationClass() throws ClassNotFoundException {
+        expectClassesNotFound(classLoader);
+
+        try {
+            pluginRegistry.getTypeForId(pluginId);
+            fail();
+        } catch (PluginInstantiationException e) {
+            assertThat(e.getMessage(), startsWith("Could not find implementation class '" + TestPlugin1.class.getName() + "' for plugin 'test' specified in "));
+            assertThat(e.getCause(), instanceOf(ClassNotFoundException.class));
+        }
+    }
+
+    @Test
     public void childDelegatesToParentRegistryToLoadPlugin() throws Exception {
         ClassLoader childClassLoader = createClassLoader("other", TestPlugin1.class.getName(), "child");
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
index 046f1a3..5c27473 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
@@ -27,7 +27,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
 
 /**
  * @author Hans Dockter
@@ -38,7 +38,7 @@ public class DefaultProjectsPluginContainerTest {
     private final DefaultProject project = HelperUtil.createRootProject();
 
     private PluginRegistry pluginRegistryStub = context.mock(PluginRegistry.class);
-    private DefaultProjectsPluginContainer projectsPluginHandler = new DefaultProjectsPluginContainer(pluginRegistryStub, project);
+    private DefaultProjectsPluginContainer container = new DefaultProjectsPluginContainer(pluginRegistryStub, project);
 
     private TestPlugin1 pluginWithIdMock = new TestPlugin1();
     private TestPlugin2 pluginWithoutIdMock = new TestPlugin2();
@@ -54,68 +54,78 @@ public class DefaultProjectsPluginContainerTest {
 
     @Test
     public void usePluginById() {
-        Plugin addedPlugin = projectsPluginHandler.apply(pluginId);
+        Plugin addedPlugin = container.apply(pluginId);
         assertThat(pluginWithIdMock, sameInstance(addedPlugin));
-        assertThat(projectsPluginHandler.apply(pluginId), sameInstance(addedPlugin));
+        assertThat(container.apply(pluginId), sameInstance(addedPlugin));
 
-        assertThat(projectsPluginHandler.findPlugin(addedPlugin.getClass()), sameInstance(addedPlugin));
-        assertThat(projectsPluginHandler.findPlugin(pluginId), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(addedPlugin.getClass()), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(pluginId), sameInstance(addedPlugin));
     }
 
     @Test
     public void usePluginWithIdByType() {
         Class<? extends Plugin> type = pluginWithIdMock.getClass();
 
-        Plugin addedPlugin = projectsPluginHandler.apply(type);
+        Plugin addedPlugin = container.apply(type);
         assertThat(pluginWithIdMock, sameInstance(addedPlugin));
-        assertThat(projectsPluginHandler.apply(type), sameInstance(addedPlugin));
-        assertThat(projectsPluginHandler.apply(pluginId), sameInstance(addedPlugin));
+        assertThat(container.apply(type), sameInstance(addedPlugin));
+        assertThat(container.apply(pluginId), sameInstance(addedPlugin));
 
-        assertThat(projectsPluginHandler.findPlugin(type), sameInstance(addedPlugin));
-        assertThat(projectsPluginHandler.findPlugin(pluginId), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(type), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(pluginId), sameInstance(addedPlugin));
     }
 
     @Test
     public void usePluginWithoutId() {
         Class<? extends Plugin> type = pluginWithoutIdMock.getClass();
-        Plugin addedPlugin = projectsPluginHandler.apply(type);
+        Plugin addedPlugin = container.apply(type);
         assertThat(pluginWithoutIdMock, sameInstance(addedPlugin));
-        assertThat(projectsPluginHandler.apply(type), sameInstance(addedPlugin));
+        assertThat(container.apply(type), sameInstance(addedPlugin));
 
-        assertThat(projectsPluginHandler.findPlugin(type), sameInstance(addedPlugin));
+        assertThat(container.findPlugin(type), sameInstance(addedPlugin));
     }
 
     @Test
     public void hasAndFindForPluginWithId() {
-        projectsPluginHandler.apply(pluginId);
-        assertThat(projectsPluginHandler.hasPlugin(pluginId), equalTo(true));
-        assertThat(projectsPluginHandler.hasPlugin(pluginWithIdMock.getClass()), equalTo(true));
-        assertThat(projectsPluginHandler.findPlugin(pluginId), sameInstance((Plugin) pluginWithIdMock));
-        assertThat(projectsPluginHandler.findPlugin(pluginWithIdMock.getClass()), sameInstance((Plugin) pluginWithIdMock));
+        container.apply(pluginId);
+        assertThat(container.hasPlugin(pluginId), equalTo(true));
+        assertThat(container.hasPlugin(pluginWithIdMock.getClass()), equalTo(true));
+        assertThat(container.findPlugin(pluginId), sameInstance((Plugin) pluginWithIdMock));
+        assertThat(container.findPlugin(pluginWithIdMock.getClass()), sameInstance((Plugin) pluginWithIdMock));
+    }
+
+    @Test
+    public void hasAndFindForUnknownPluginId() {
+        context.checking(new Expectations() {{
+            allowing(pluginRegistryStub).getTypeForId("unknown"); will(throwException(new UnknownPluginException("unknown")));
+        }});
+
+        assertThat(container.hasPlugin("unknown"), equalTo(false));
+        assertThat(container.findPlugin("unknown"), nullValue());
     }
 
     @Test
     public void hasAndFindForPluginWithoutId() {
         Plugin plugin = pluginWithoutIdMock;
         Class<? extends Plugin> pluginType = plugin.getClass();
-        projectsPluginHandler.apply(pluginType);
-        assertThat(projectsPluginHandler.hasPlugin(pluginType), equalTo(true));
-        assertThat(projectsPluginHandler.findPlugin(pluginType), sameInstance(plugin));
+        container.apply(pluginType);
+        assertThat(container.hasPlugin(pluginType), equalTo(true));
+        assertThat(container.findPlugin(pluginType), sameInstance(plugin));
     }
 
     @Test
     public void hasAndFindPluginByTypeWithUnknownPlugin() {
-        assertThat(projectsPluginHandler.hasPlugin(TestPlugin2.class), equalTo(false));
-        assertThat(projectsPluginHandler.findPlugin(TestPlugin2.class), nullValue());
+        assertThat(container.hasPlugin(TestPlugin2.class), equalTo(false));
+        assertThat(container.findPlugin(TestPlugin2.class), nullValue());
     }
 
     @Test(expected = UnknownPluginException.class)
     public void getNonUsedPluginById() {
-        projectsPluginHandler.getPlugin(pluginId);
+        container.getPlugin(pluginId);
     }
 
     @Test(expected = UnknownPluginException.class)
     public void getNonUsedPluginByType() {
-        projectsPluginHandler.getPlugin(TestPlugin1.class);
+        container.getPlugin(TestPlugin1.class);
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DslObjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DslObjectTest.groovy
new file mode 100644
index 0000000..2cd4bb2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DslObjectTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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.gradle.api.internal.ThreadGlobalInstantiator
+import spock.lang.Specification
+
+class DslObjectTest extends Specification {
+    
+    def "fails lazily for non dsl object"() {
+        when:
+        def dsl = new DslObject(new Object())
+
+        then:
+        notThrown(Exception)
+
+        when:
+        dsl.asDynamicObject
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    static class Thing {}
+
+    def "works for dsl object"() {
+        when:
+        new DslObject(ThreadGlobalInstantiator.getOrCreate().newInstance(Thing))
+
+        then:
+        notThrown(Exception)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy
new file mode 100644
index 0000000..2c78e79
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionContainerTest.groovy
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2011 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.gradle.api.UnknownDomainObjectException
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.plugins.ExtensionAware
+import org.gradle.api.plugins.ExtraPropertiesExtension
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 6/24/11
+ */
+public class ExtensionContainerTest extends Specification {
+
+    def container = new DefaultConvention(ThreadGlobalInstantiator.getOrCreate())
+    def extension = new FooExtension()
+    def barExtension = new BarExtension()
+
+    class FooExtension {
+        String message = "smile"
+    }
+
+    class BarExtension {}
+    class SomeExtension {}
+
+    def "has dynamic extension"() {
+        expect:
+        container.getByName(ExtraPropertiesExtension.EXTENSION_NAME) == container.extraProperties
+    }
+    
+    def "extension can be accessed and configured"() {
+        when:
+        container.add("foo", extension)
+        container.extensionsAsDynamicObject.foo.message = "Hey!"
+
+        then:
+        extension.message == "Hey!"
+    }
+
+    def "extension can be configured via script block"() {
+        when:
+        container.add("foo", extension)
+        container.extensionsAsDynamicObject.foo {
+            message = "You cool?"
+        }
+
+        then:
+        extension.message == "You cool?"
+    }
+
+    def "extension cannot be set as property because we want users to use explicit method to add extensions"() {
+        when:
+        container.add("foo", extension)
+        container.extensionsAsDynamicObject.foo = new FooExtension()
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "There's an extension registered with name 'foo'. You should not reassign it via a property setter."
+    }
+
+    def "can register extensions using dynamic property setter"() {
+        when:
+        container.foo = extension
+
+        then:
+        container.findByName('foo') == extension
+    }
+
+    def "can access extensions using dynamic property getter"() {
+        when:
+        container.add('foo', extension)
+
+        then:
+        container.foo == extension
+    }
+
+    def "cannot replace an extension"() {
+        given:
+        container.add('foo', extension)
+
+        when:
+        container.add('foo', 'other')
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Cannot add extension with name 'foo', as there is an extension already registered with that name."
+
+        when:
+        container.foo = 'other'
+
+        then:
+        IllegalArgumentException e2 = thrown()
+        e2.message == "There's an extension registered with name 'foo'. You should not reassign it via a property setter."
+    }
+
+    def "knows registered extensions"() {
+        when:
+        container.add("foo", extension)
+        container.add("bar", barExtension)
+
+        then:
+        container.getByName("foo") == extension
+        container.findByName("bar") == barExtension
+
+        container.getByType(BarExtension) == barExtension
+        container.findByType(FooExtension) == extension
+
+        container.findByType(SomeExtension) == null
+        container.findByName("i don't exist") == null
+    }
+
+    def "throws when unknown exception wanted by name"() {
+        container.add("foo", extension)
+
+        when:
+        container.getByName("i don't exist")
+
+        then:
+        def ex = thrown(UnknownDomainObjectException)
+        ex.message == "Extension with name 'i don't exist' does not exist. Currently registered extension names: [${ExtraPropertiesExtension.EXTENSION_NAME}, foo]"
+    }
+
+    def "throws when unknown extension wanted by type"() {
+        container.add("foo", extension)
+
+        when:
+        container.getByType(SomeExtension)
+
+        then:
+        def ex = thrown(UnknownDomainObjectException)
+        ex.message == "Extension of type 'SomeExtension' does not exist. Currently registered extension types: [${DefaultExtraPropertiesExtension.simpleName}, FooExtension]"
+    }
+
+    def "types can be retrieved by interface and super types"() {
+        given:
+        def impl = new Impl()
+        def child = new Child()
+
+        when:
+        container.add('i', impl)
+        container.add('c', child)
+
+        then:
+        container.findByType(Capability) == impl
+        container.getByType(Impl) == impl
+        container.findByType(Parent) == child
+        container.getByType(Parent) == child
+    }
+    
+    def "can create ExtensionAware extensions"() {
+        given:
+        container.add("foo", Parent)
+        def extension = container.getByName("foo")
+
+        expect:
+        extension instanceof ExtensionAware
+        
+        when:
+        extension.extensions.create("thing", Thing, "bar")
+        
+        then:
+        extension.thing.name == "bar"
+    }
+
+}
+
+interface Capability {}
+class Impl implements Capability {}
+
+class Parent {}
+class Child extends Parent {}
+class Thing {
+    String name
+
+    Thing(String name) {
+        this.name = name
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtraPropertiesDynamicObjectAdapterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtraPropertiesDynamicObjectAdapterTest.groovy
new file mode 100644
index 0000000..e40f683
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtraPropertiesDynamicObjectAdapterTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 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.gradle.api.internal.BeanDynamicObject
+import org.gradle.api.plugins.ExtraPropertiesExtension
+import spock.lang.Specification
+
+public class ExtraPropertiesDynamicObjectAdapterTest extends Specification {
+
+    Object delegate = new Object()
+    ExtraPropertiesExtension extension = new DefaultExtraPropertiesExtension()
+    ExtraPropertiesDynamicObjectAdapter adapter =  new ExtraPropertiesDynamicObjectAdapter(delegate, new BeanDynamicObject(delegate), extension)
+
+    def "can get and set properties"() {
+        given:
+        extension.set("foo", "bar")
+
+        expect:
+        adapter.getProperty("foo") == "bar"
+
+        when:
+        adapter.setProperty("foo", "baz")
+
+        then:
+        adapter.getProperty("foo") == "baz"
+        extension.foo == "baz"
+
+        when:
+        extension.foo = "bar"
+
+        then:
+        adapter.getProperty("foo") == "bar"
+    }
+
+    def "can get properties map"() {
+        given:
+        extension.set("p1", 1)
+        extension.set("p2", 2)
+        extension.set("p3", 3)
+
+        expect:
+        extension.properties == adapter.properties
+    }
+
+    def "has no methods"() {
+        given:
+        extension.set("foo") { }
+
+        expect:
+        !adapter.hasMethod("foo", "anything")
+
+        and:
+        !adapter.hasMethod("other")
+    }
+    
+    def "getting or setting missing property throws MPE"() {
+        when:
+        adapter.getProperty("foo")
+        
+        then:
+        thrown(MissingPropertyException)
+    }
+
+    def "invoking method throws MME"() {
+        when:
+        adapter.invokeMethod("foo", "bar")
+
+        then:
+        thrown(groovy.lang.MissingMethodException)
+    }
+
+    static class NamedExtraPropertiesExtension extends DefaultExtraPropertiesExtension {
+        String name
+    }
+
+    def "has property 'properties'"() {
+        expect:
+        adapter.hasProperty("properties")
+
+        and:
+        new ExtraPropertiesDynamicObjectAdapter(delegate, new BeanDynamicObject(delegate), new NamedExtraPropertiesExtension()).hasProperty("name")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy
index 9654372..7e7bd37 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy
@@ -22,25 +22,33 @@ import org.apache.tools.ant.Project
 import org.apache.tools.ant.taskdefs.ConditionTask
 import org.gradle.api.GradleException
 import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.api.internal.DefaultClassPathProvider
 import org.gradle.api.internal.DefaultClassPathRegistry
+import org.gradle.api.internal.classpath.DefaultModuleRegistry
+import org.gradle.api.internal.classpath.ModuleRegistry
 import org.gradle.api.internal.project.ant.BasicAntBuilder
+import org.gradle.logging.LoggingTestHelper
+import org.gradle.util.DefaultClassLoaderFactory
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.logging.LoggingTestHelper
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
+import org.apache.tools.ant.Task
+import org.gradle.util.ClasspathUtil
 
 class DefaultIsolatedAntBuilderTest {
-    private final ClassPathRegistry registry = new DefaultClassPathRegistry()
-    private final DefaultIsolatedAntBuilder builder = new DefaultIsolatedAntBuilder(registry)
+    private final ModuleRegistry moduleRegistry = new DefaultModuleRegistry()
+    private final ClassPathRegistry registry = new DefaultClassPathRegistry(new DefaultClassPathProvider(moduleRegistry))
+    private final DefaultIsolatedAntBuilder builder = new DefaultIsolatedAntBuilder(registry, new DefaultClassLoaderFactory())
     private final TestAppender appender = new TestAppender()
     private final LoggingTestHelper helper = new LoggingTestHelper(appender)
-    private Collection classpath
+    private Collection<File> classpath
 
     @Before
     public void attachAppender() {
-        classpath = registry.getClassPathFiles("LOCAL_GROOVY")
+        classpath = registry.getClassPath("GROOVY").asFiles
         helper.attachAppender()
         helper.setLevel(Level.INFO);
     }
@@ -97,13 +105,37 @@ class DefaultIsolatedAntBuilderTest {
     }
 
     @Test
+    public void canAccessAntBuilderFromWithinClosures() {
+        builder.execute {
+            assertThat(ant, sameInstance(delegate))
+            
+            ant.property(name: 'prop', value: 'a message')
+            assertThat(project.properties.prop, equalTo('a message'))
+        }
+    }
+
+    @Test
     public void attachesLogger() {
         builder.execute {
             property(name: 'message', value: 'a message')
             echo('${message}')
         }
 
-        assertThat(appender.writer.toString(), equalTo('[ant:echo] a message'))
+        assertThat(appender.writer.toString(), equalTo('[[ant:echo] a message]'))
+    }
+
+    @Test
+    public void bridgesLogging() {
+        def classpath = ClasspathUtil.getClasspathForClass(TestAntTask)
+
+        builder.withClasspath([classpath]).execute {
+            taskdef(name: 'loggingTask', classname: TestAntTask.name)
+            loggingTask()
+        }
+
+        assertThat(appender.writer.toString(), containsString('[a jcl log message]'))
+        assertThat(appender.writer.toString(), containsString('[an slf4j log message]'))
+        assertThat(appender.writer.toString(), containsString('[a log4j log message]'))
     }
 
     @Test
@@ -128,6 +160,20 @@ class DefaultIsolatedAntBuilderTest {
     }
 
     @Test
+    public void cachesClassloaderForGivenAntAndGroovyImplementationClassPath() {
+        ClassLoader antClassLoader = null
+        builder.withClasspath([new File("no-existo.jar")]).execute {
+            antClassLoader = project.class.classLoader
+        }
+        ClassLoader antClassLoader2 = null
+        builder.withClasspath([new File("unknown.jar")]).execute {
+            antClassLoader2 = project.class.classLoader
+        }
+
+        assertThat(antClassLoader, sameInstance(antClassLoader2))
+    }
+
+    @Test
     public void setsContextClassLoader() {
         ClassLoader originalLoader = Thread.currentThread().contextClassLoader
         ClassLoader contextLoader = null
@@ -138,7 +184,7 @@ class DefaultIsolatedAntBuilderTest {
             contextLoader = Thread.currentThread().contextClassLoader
         }
 
-        assertThat(contextLoader, sameInstance(antProject.class.classLoader))
+        assertThat(contextLoader.loadClass(Project.name), sameInstance(antProject.class))
         assertThat(Thread.currentThread().contextClassLoader, sameInstance(originalLoader))
     }
 
@@ -158,14 +204,25 @@ class DefaultIsolatedAntBuilderTest {
     }
 }
 
+class TestAntTask extends Task {
+    @Override
+    void execute() {
+        org.apache.commons.logging.LogFactory.getLog('ant-test').info("a jcl log message")
+        org.slf4j.LoggerFactory.getLogger('ant-test').info("an slf4j log message")
+        org.apache.log4j.Logger.getLogger('ant-test').info("a log4j log message")
+    }
+}
+
 class TestAppender<LoggingEvent> extends AppenderBase<LoggingEvent> {
-    StringWriter writer = new StringWriter()
+    final StringWriter writer = new StringWriter()
 
     synchronized void doAppend(LoggingEvent e) {
         append(e)
     }
 
     protected void append(LoggingEvent e) {
-        writer.write(e.formattedMessage)
+        writer.append("[")
+        writer.append(e.formattedMessage)
+        writer.append("]")
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
index beb754d..4f19dc3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
@@ -19,40 +19,34 @@ package org.gradle.api.internal.project
 import java.awt.Point
 import java.text.FieldPosition
 import org.apache.tools.ant.types.FileSet
-import org.gradle.api.artifacts.ConfigurationContainer
 import org.gradle.api.artifacts.Module
 import org.gradle.api.artifacts.dsl.ArtifactHandler
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.api.artifacts.dsl.RepositoryHandler
 import org.gradle.api.initialization.dsl.ScriptHandler
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
-import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
-import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory
-import org.gradle.api.internal.artifacts.repositories.InternalRepository
 import org.gradle.api.internal.file.FileOperations
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider
-import org.gradle.api.internal.plugins.DefaultConvention
 import org.gradle.api.internal.tasks.TaskContainerInternal
 import org.gradle.api.invocation.Gradle
-import org.gradle.api.logging.LogLevel
-import org.gradle.api.plugins.Convention
 import org.gradle.api.plugins.PluginContainer
 import org.gradle.api.tasks.Directory
 import org.gradle.configuration.ProjectEvaluator
 import org.gradle.configuration.ScriptPluginFactory
 import org.gradle.groovy.scripts.EmptyScript
 import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.internal.Factory
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.StandardOutputCapture
 import org.gradle.util.HelperUtil
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TestClosure
 import org.jmock.integration.junit4.JMock
-import org.jmock.lib.legacy.ClassImposteriser
 import org.junit.Before
 import org.junit.Ignore
 import org.junit.Test
@@ -61,7 +55,6 @@ import org.gradle.api.*
 import org.gradle.api.internal.*
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.api.internal.Factory
 
 /**
  * @author Hans Dockter
@@ -80,7 +73,7 @@ class DefaultProjectTest {
 
     DefaultProject project, child1, child2, childchild
 
-    ProjectEvaluator projectEvaluator
+    ProjectEvaluator projectEvaluator = context.mock(ProjectEvaluator.class)
 
     IProjectRegistry projectRegistry
 
@@ -88,55 +81,34 @@ class DefaultProjectTest {
 
     groovy.lang.Script testScript
 
-    ScriptSource script;
+    ScriptSource script = context.mock(ScriptSource.class)
 
     ServiceRegistry serviceRegistryMock
     ServiceRegistryFactory projectServiceRegistryFactoryMock
-    TaskContainerInternal taskContainerMock
-    Factory<AntBuilder> antBuilderFactoryMock
+    TaskContainerInternal taskContainerMock = context.mock(TaskContainerInternal.class)
+    Factory<AntBuilder> antBuilderFactoryMock = context.mock(Factory.class)
     AntBuilder testAntBuilder
 
-    ConfigurationContainerFactory configurationContainerFactoryMock;
-    DefaultConfigurationContainer configurationContainerMock;
-    InternalRepository internalRepositoryDummy = context.mock(InternalRepository)
-    ResolverFactory resolverFactoryMock = context.mock(ResolverFactory.class);
-    Factory<RepositoryHandler> repositoryHandlerFactoryMock = context.mock(Factory.class);
-    RepositoryHandler repositoryHandlerMock
-    DependencyFactory dependencyFactoryMock
+    DefaultConfigurationContainer configurationContainerMock = context.mock(DefaultConfigurationContainer.class)
+    RepositoryHandler repositoryHandlerMock = context.mock(RepositoryHandler.class)
+    DependencyFactory dependencyFactoryMock = context.mock(DependencyFactory.class)
     DependencyHandler dependencyHandlerMock = context.mock(DependencyHandler)
     PluginContainer pluginContainerMock = context.mock(PluginContainer)
-    PublishArtifactFactory publishArtifactFactoryMock = context.mock(PublishArtifactFactory)
     ScriptHandler scriptHandlerMock = context.mock(ScriptHandler)
     DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider)
-    Gradle build;
-    Convention convention = new DefaultConvention();
-    FileOperations fileOperationsMock
-    LoggingManagerInternal loggingManagerMock;
+    Gradle build = context.mock(GradleInternal)
+    FileOperations fileOperationsMock = context.mock(FileOperations)
+    ProcessOperations processOperationsMock = context.mock(ProcessOperations)
+    LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class)
+    Instantiator instantiatorMock = context.mock(Instantiator)
 
     @Before
     void setUp() {
         rootDir = new File("/path/root").absoluteFile
 
-        context.imposteriser = ClassImposteriser.INSTANCE
-        dependencyFactoryMock = context.mock(DependencyFactory.class)
-        loggingManagerMock = context.mock(LoggingManagerInternal.class)
-        taskContainerMock = context.mock(TaskContainerInternal.class);
-        antBuilderFactoryMock = context.mock(Factory.class)
         testAntBuilder = new DefaultAntBuilder()
         context.checking {
             allowing(antBuilderFactoryMock).create(); will(returnValue(testAntBuilder))
-        }
-        configurationContainerMock = context.mock(DefaultConfigurationContainer.class)
-        configurationContainerFactoryMock = [createConfigurationContainer: {
-          resolverProvider, dependencyMetaDataProvider, projectDependenciesBuildInstruction ->
-            assertSame(build.startParameter.projectDependenciesBuildInstruction, projectDependenciesBuildInstruction)
-            configurationContainerMock}] as ConfigurationContainerFactory
-        repositoryHandlerMock =  context.mock(RepositoryHandler.class);
-        context.checking {
-          allowing(repositoryHandlerFactoryMock).create(); will(returnValue(repositoryHandlerMock))
-        }
-        script = context.mock(ScriptSource.class)
-        context.checking {
             allowing(script).getDisplayName(); will(returnValue('[build file]'))
             allowing(script).getClassName(); will(returnValue('scriptClass'))
             allowing(scriptHandlerMock).getSourceFile(); will(returnValue(new File(rootDir, TEST_BUILD_FILE_NAME)))
@@ -146,24 +118,19 @@ class DefaultProjectTest {
 
         testTask = HelperUtil.createTask(DefaultTask)
 
-        projectEvaluator = context.mock(ProjectEvaluator.class)
         projectRegistry = new DefaultProjectRegistry()
 
         projectServiceRegistryFactoryMock = context.mock(ServiceRegistryFactory.class, 'parent')
         serviceRegistryMock = context.mock(ServiceRegistryFactory.class, 'project')
-        build = context.mock(GradleInternal.class)
-        fileOperationsMock = context.mock(FileOperations.class)
 
         context.checking {
             allowing(projectServiceRegistryFactoryMock).createFor(withParam(notNullValue())); will(returnValue(serviceRegistryMock))
             allowing(serviceRegistryMock).newInstance(TaskContainerInternal); will(returnValue(taskContainerMock))
-            allowing(taskContainerMock).getAsDynamicObject(); will(returnValue(new BeanDynamicObject(new TaskContainerDynamicObject(someTask: testTask))))
+            allowing(taskContainerMock).getTasksAsDynamicObject(); will(returnValue(new BeanDynamicObject(new TaskContainerDynamicObject(someTask: testTask))))
             allowing(serviceRegistryMock).get(RepositoryHandler); will(returnValue(repositoryHandlerMock))
-            allowing(serviceRegistryMock).getFactory(RepositoryHandler); will(returnValue(repositoryHandlerFactoryMock))
-            allowing(serviceRegistryMock).get(ConfigurationContainer); will(returnValue(configurationContainerMock))
+            allowing(serviceRegistryMock).get(ConfigurationContainerInternal); will(returnValue(configurationContainerMock))
             allowing(serviceRegistryMock).get(ArtifactHandler); will(returnValue(context.mock(ArtifactHandler)))
             allowing(serviceRegistryMock).get(DependencyHandler); will(returnValue(dependencyHandlerMock))
-            allowing(serviceRegistryMock).get(Convention); will(returnValue(convention))
             allowing(serviceRegistryMock).get(ProjectEvaluator); will(returnValue(projectEvaluator))
             allowing(serviceRegistryMock).getFactory(AntBuilder); will(returnValue(antBuilderFactoryMock))
             allowing(serviceRegistryMock).get(PluginContainer); will(returnValue(pluginContainerMock))
@@ -173,11 +140,11 @@ class DefaultProjectTest {
             allowing(serviceRegistryMock).get(StandardOutputCapture); will(returnValue(context.mock(StandardOutputCapture)))
             allowing(serviceRegistryMock).get(IProjectRegistry); will(returnValue(projectRegistry))
             allowing(serviceRegistryMock).get(DependencyMetaDataProvider); will(returnValue(dependencyMetaDataProviderMock))
-            allowing(serviceRegistryMock).get(FileResolver); will(returnValue([:] as FileResolver))
-            allowing(serviceRegistryMock).get(ClassGenerator); will(returnValue(new AsmBackedClassGenerator()))
-            allowing(serviceRegistryMock).get(FileOperations);
-            will(returnValue(fileOperationsMock))
-            allowing(serviceRegistryMock).get(ScriptPluginFactory); will(returnValue([:] as ScriptPluginFactory))
+            allowing(serviceRegistryMock).get(FileResolver); will(returnValue([toString: { -> "file resolver" }] as FileResolver))
+            allowing(serviceRegistryMock).get(Instantiator); will(returnValue(instantiatorMock))
+            allowing(serviceRegistryMock).get(FileOperations); will(returnValue(fileOperationsMock))
+            allowing(serviceRegistryMock).get(ProcessOperations); will(returnValue(processOperationsMock))
+            allowing(serviceRegistryMock).get(ScriptPluginFactory); will(returnValue([toString: { -> "script plugin factory" }] as ScriptPluginFactory))
             Object listener = context.mock(ProjectEvaluationListener)
             ignoring(listener)
             allowing(build).getProjectEvaluationBroadcaster();
@@ -185,7 +152,7 @@ class DefaultProjectTest {
         }
 
         // TODO - don't decorate the project objects
-        ClassGenerator classGenerator = new AsmBackedClassGenerator()
+        AsmBackedClassGenerator classGenerator = new AsmBackedClassGenerator()
         project = classGenerator.newInstance(DefaultProject.class, 'root', null, rootDir, script, build, projectServiceRegistryFactoryMock);
         child1 = classGenerator.newInstance(DefaultProject.class, "child1", project, new File("child1"), script, build, projectServiceRegistryFactoryMock)
         project.addChildProject(child1)
@@ -198,14 +165,6 @@ class DefaultProjectTest {
         }
     }
 
-  @Test void testRepositories() {
-      context.checking {
-          allowing(repositoryHandlerFactoryMock).create(); will(returnValue(repositoryHandlerMock))
-          ignoring(repositoryHandlerMock)
-      }
-      assertThat(project.createRepositoryHandler(), sameInstance(repositoryHandlerMock))
-  }
-
   @Ignore void testArtifacts() {
         boolean called = false;
         ArtifactHandler artifactHandlerMock = [testMethod: { called = true }] as ArtifactHandler
@@ -265,11 +224,9 @@ class DefaultProjectTest {
         assertNotNull(project.convention)
         assertEquals([], project.getDefaultTasks())
         assert project.configurations.is(configurationContainerMock)
-        assert project.repositoryHandlerFactory.is(repositoryHandlerFactoryMock)
         assertSame(repositoryHandlerMock, project.repositories)
         assert projectRegistry.is(project.projectRegistry)
         assertFalse project.state.executed
-        assertEquals DefaultProject.DEFAULT_BUILD_DIR_NAME, project.buildDirName
     }
 
     @Test public void testNullVersionAndStatus() {
@@ -371,6 +328,36 @@ class DefaultProjectTest {
         child1.projectEvaluator = mockReader2
         project.evaluate()
         assertTrue mockReader1Called
+        assertTrue mockReader2Finished
+    }
+
+    @Test
+    void testEvaluationDependsOnChildren() {
+        boolean child1MockReaderFinished = false
+        boolean child2MockReaderFinished = false
+        boolean mockReader1Called = false
+        final ProjectEvaluator mockReader1 = [evaluate: {DefaultProject project, state ->
+            project.evaluationDependsOnChildren()
+            assertTrue(child1MockReaderFinished)
+            assertTrue(child2MockReaderFinished)
+            mockReader1Called = true
+            testScript
+        }] as ProjectEvaluator
+        final ProjectEvaluator mockReader2 = [
+                evaluate: {DefaultProject project, state ->
+                    child1MockReaderFinished = true
+                    testScript
+                }] as ProjectEvaluator
+        final ProjectEvaluator mockReader3 = [
+                evaluate: {DefaultProject project, state ->
+                    child2MockReaderFinished = true
+                    testScript
+                }] as ProjectEvaluator
+        project.projectEvaluator = mockReader1
+        child1.projectEvaluator = mockReader2
+        child2.projectEvaluator = mockReader3
+        project.evaluate();
+        assertTrue mockReader1Called
     }
 
     @Test (expected = InvalidUserDataException) void testEvaluationDependsOnWithNullArgument() {
@@ -499,46 +486,6 @@ class DefaultProjectTest {
         project.defaultTasks("a", null);
     }
 
-    @Test public void testCreateTaskWithName() {
-        context.checking {
-            one(taskContainerMock).add([name: TEST_TASK_NAME]); will(returnValue(testTask))
-        }
-        assertSame(testTask, project.createTask(TEST_TASK_NAME));
-    }
-
-    @Test public void testCreateTaskWithNameAndArgs() {
-        Map testArgs = [a: 'b']
-        context.checking {
-            one(taskContainerMock).add(testArgs + [name: TEST_TASK_NAME]); will(returnValue(testTask))
-        }
-        assertSame(testTask, project.createTask(testArgs, TEST_TASK_NAME));
-    }
-
-    @Test public void testCreateTaskWithNameAndAction() {
-        Action<Task> testAction = {} as Action
-        context.checking {
-            one(taskContainerMock).add([name: TEST_TASK_NAME, action: testAction]); will(returnValue(testTask))
-        }
-        assertSame(testTask, project.createTask(TEST_TASK_NAME, testAction));
-    }
-
-    @Test public void testCreateTaskWithNameAndClosureAction() {
-        Closure testAction = {}
-        context.checking {
-            one(taskContainerMock).add([name: TEST_TASK_NAME, action: testAction]); will(returnValue(testTask))
-        }
-        assertSame(testTask, project.createTask(TEST_TASK_NAME, testAction));
-    }
-
-    @Test public void testCreateTaskWithNameArgsAndActions() {
-        Map testArgs = [a: 'b']
-        Action<Task> testAction = {} as Action
-        context.checking {
-            one(taskContainerMock).add(testArgs + [name: TEST_TASK_NAME, action: testAction]); will(returnValue(testTask))
-        }
-        assertSame(testTask, project.createTask(testArgs, TEST_TASK_NAME, testAction));
-    }
-
     @Test void testCanAccessTaskAsAProjectProperty() {
         assertThat(project.someTask, sameInstance(testTask))
     }
@@ -635,10 +582,14 @@ class DefaultProjectTest {
         expectedMap[childchild] = [] as TreeSet
 
         context.checking {
-            one(taskContainerMock).getAll(); will(returnValue([projectTask] as Set))
-            one(taskContainerMock).getAll(); will(returnValue([child1Task] as Set))
-            one(taskContainerMock).getAll(); will(returnValue([child2Task] as Set))
-            one(taskContainerMock).getAll(); will(returnValue([] as Set))
+            atMost(1).of(taskContainerMock).size(); will(returnValue(1))
+            one(taskContainerMock).iterator(); will(returnValue(([projectTask] as Set).iterator()))
+            atMost(1).of(taskContainerMock).size(); will(returnValue(1))
+            one(taskContainerMock).iterator(); will(returnValue(([child1Task] as Set).iterator()))
+            atMost(1).of(taskContainerMock).size(); will(returnValue(1))
+            one(taskContainerMock).iterator(); will(returnValue(([child2Task] as Set).iterator()))
+            atMost(1).of(taskContainerMock).size(); will(returnValue(0))
+            one(taskContainerMock).iterator(); will(returnValue(([] as Set).iterator()))
         }
 
         assertEquals(expectedMap, project.getAllTasks(true))
@@ -651,7 +602,8 @@ class DefaultProjectTest {
         expectedMap[project] = [projectTask] as TreeSet
 
         context.checking {
-            one(taskContainerMock).getAll(); will(returnValue([projectTask] as Set))
+            allowing(taskContainerMock).size(); will(returnValue(1))
+            one(taskContainerMock).iterator(); will(returnValue(([projectTask] as Set).iterator()))
         }
 
         assertEquals(expectedMap, project.getAllTasks(false))
@@ -752,13 +704,12 @@ def scriptMethod(Closure closure) {
     }
 
     @Test void testSetPropertyAndPropertyMissingWithConventionProperty() {
-        String propertyName = 'conv'
         String expectedValue = 'somevalue'
         project.convention.plugins.test = new TestConvention()
-        project."$propertyName" = expectedValue
-        assertEquals(expectedValue, project."$propertyName")
-        assertEquals(expectedValue, project.convention."$propertyName")
-        assertEquals(expectedValue, child1."$propertyName")
+        project.conv = expectedValue
+        assertEquals(expectedValue, project.conv)
+        assertEquals(expectedValue, project.convention.plugins.test.conv)
+        assertEquals(expectedValue, child1.conv)
     }
 
     @Test void testSetPropertyAndPropertyMissingWithProjectAndConventionProperty() {
@@ -791,11 +742,7 @@ def scriptMethod(Closure closure) {
         assertFalse(child1.hasProperty(propertyName))
 
         project.convention.plugins.test = new FieldPosition(0)
-        project.convention."$propertyName" = 5
-        assertTrue(project.hasProperty(propertyName))
-        assertTrue(child1.hasProperty(propertyName))
-        project.convention = new DefaultConvention()
-        project."$propertyName" = 4
+        project."$propertyName" = 5
         assertTrue(project.hasProperty(propertyName))
         assertTrue(child1.hasProperty(propertyName))
     }
@@ -814,13 +761,6 @@ def scriptMethod(Closure closure) {
         assertSame(properties['someTask'], testTask)
     }
 
-    @Test void testAdditionalProperty() {
-        String expectedPropertyName = 'somename'
-        String expectedPropertyValue = 'somevalue'
-        project.additionalProperties[expectedPropertyName] = expectedPropertyValue
-        assertEquals(project."$expectedPropertyName", expectedPropertyValue)
-    }
-
     @Test void testAdditionalPropertiesAreInheritable() {
         project.somename = 'somevalue'
         assertTrue(project.inheritedScope.hasProperty('somename'))
@@ -991,22 +931,6 @@ def scriptMethod(Closure closure) {
     }
 
     @Test
-    void disableStandardOutputCapture() {
-        context.checking {
-            one(loggingManagerMock).disableStandardOutputCapture()
-        }
-        project.disableStandardOutputCapture()
-    }
-
-    @Test
-    void captureStandardOutput() {
-        context.checking {
-            one(loggingManagerMock).captureStandardOutput(LogLevel.DEBUG)
-        }
-        project.captureStandardOutput(LogLevel.DEBUG)
-    }
-
-    @Test
     void configure() {
         Point expectedPoint = new Point(4, 3)
         Point actualPoint = project.configure(new Point()) {
@@ -1057,12 +981,14 @@ def scriptMethod(Closure closure) {
     }
 
     @Test void createsADomainObjectContainer() {
-        assertThat(project.container(String.class), instanceOf(DefaultAutoCreateDomainObjectContainer.class))
-        assertThat(project.container(String.class), instanceOf(IConventionAware.class))
-
-        assertThat(project.container(String.class, context.mock(NamedDomainObjectFactory.class)), instanceOf(DefaultAutoCreateDomainObjectContainer.class))
-
-        assertThat(project.container(String.class, { }), instanceOf(DefaultAutoCreateDomainObjectContainer.class))
+        def container = context.mock(FactoryNamedDomainObjectContainer)
+        context.checking {
+            allowing(instantiatorMock).newInstance(withParam(equalTo(FactoryNamedDomainObjectContainer)), withParam(notNullValue()))
+            will(returnValue(container))
+        }
+        assertThat(project.container(String.class), sameInstance(container))
+        assertThat(project.container(String.class, context.mock(NamedDomainObjectFactory.class)), sameInstance(container))
+        assertThat(project.container(String.class, { }), sameInstance(container))
     }
 
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultServiceRegistryTest.java
deleted file mode 100644
index d8d2e8e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultServiceRegistryTest.java
+++ /dev/null
@@ -1,297 +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.api.internal.project;
-
-import org.gradle.api.internal.Factory;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.math.BigDecimal;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultServiceRegistryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final TestRegistry registry = new TestRegistry();
-
-    @Test
-    public void throwsExceptionForUnknownService() {
-        try {
-            registry.get(Map.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void delegatesToParentForUnknownService() {
-        final BigDecimal value = BigDecimal.TEN;
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations(){{
-            one(parent).get(BigDecimal.class);
-            will(returnValue(value));
-        }});
-
-        assertThat(registry.get(BigDecimal.class), sameInstance(value));
-    }
-
-    @Test
-    public void throwsExceptionForUnknownParentService() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations(){{
-            one(parent).get(Map.class);
-            will(throwException(new DefaultServiceRegistry.UnknownServiceException(Map.class, "fail")));
-        }});
-
-        try {
-            registry.get(Map.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void returnsAddedServiceInstance() {
-        BigDecimal value = BigDecimal.TEN;
-        registry.add(BigDecimal.class, value);
-        assertThat(registry.get(BigDecimal.class), sameInstance(value));
-        assertThat(registry.get(Number.class), sameInstance((Object) value));
-    }
-
-    @Test
-    public void createsAndCachesRegisteredServiceInstance() {
-        final BigDecimal value = BigDecimal.TEN;
-        registry.add(new DefaultServiceRegistry.Service(BigDecimal.class) {
-            @Override
-            protected Object create() {
-                return value;
-            }
-        });
-        assertThat(registry.get(BigDecimal.class), sameInstance(value));
-        assertThat(registry.get(Number.class), sameInstance((Object) value));
-    }
-
-    @Test
-    public void usesFactoryMethodToCreateServiceInstance() {
-        assertThat(registry.get(String.class), equalTo("12"));
-        assertThat(registry.get(Integer.class), equalTo(12));
-    }
-
-    @Test
-    public void usesDecoratorMethodToDecorateParentServiceInstance() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations() {{
-            one(parent).get(Long.class);
-            will(returnValue(110L));
-        }});
-
-        assertThat(registry.get(Long.class), equalTo(120L));
-    }
-
-    @Test
-    public void canGetServiceAsFactoryWhenTheServiceImplementsFactoryInterface() {
-        assertThat(registry.getFactory(BigDecimal.class), instanceOf(TestFactory.class));
-        assertThat(registry.getFactory(BigDecimal.class), sameInstance((Object) registry.getFactory(BigDecimal.class)));
-    }
-
-    @Test
-    public void canLocateFactoryWhenServiceInterfaceExtendsFactory() {
-        registry.add(StringFactory.class, new StringFactory() {
-            public String create() {
-                return "value";
-            }
-        });
-        assertThat(registry.getFactory(String.class).create(), equalTo("value"));
-    }
-
-    @Test
-    public void usesAFactoryServiceToCreateInstances() {
-        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(0)));
-        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(1)));
-        assertThat(registry.newInstance(BigDecimal.class), equalTo(BigDecimal.valueOf(2)));
-    }
-
-    @Test
-    public void delegatesToParentForUnknownFactory() {
-        final Factory<Map> factory = context.mock(Factory.class);
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations() {{
-            one(parent).getFactory(Map.class);
-            will(returnValue(factory));
-        }});
-
-        assertThat(registry.getFactory(Map.class), sameInstance((Object) factory));
-    }
-
-    @Test
-    public void usesDecoratorMethodToDecorateParentFactoryInstance() {
-        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-        final Factory<Long> factory = context.mock(Factory.class);
-        TestRegistry registry = new TestRegistry(parent);
-
-        context.checking(new Expectations() {{
-            one(parent).getFactory(Long.class);
-            will(returnValue(factory));
-            allowing(factory).create();
-            will(onConsecutiveCalls(returnValue(10L), returnValue(20L)));
-        }});
-
-        assertThat(registry.newInstance(Long.class), equalTo(12L));
-        assertThat(registry.newInstance(Long.class), equalTo(22L));
-    }
-    
-    @Test
-    public void throwsExceptionForUnknownFactory() {
-        try {
-            registry.getFactory(String.class);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("No factory for objects of type String available in TestRegistry."));
-        }
-    }
-
-    @Test
-    public void servicesCreatedByFactoryMethodsAreVisibleWhenUsingASubClass() {
-        ServiceRegistry registry = new SubType();
-        assertThat(registry.get(String.class), equalTo("12"));
-        assertThat(registry.get(Integer.class), equalTo(12));
-    }
-    
-    @Test
-    public void closeInvokesCloseMethodOnEachService() {
-        final TestCloseService service = context.mock(TestCloseService.class);
-        registry.add(TestCloseService.class, service);
-
-        context.checking(new Expectations() {{
-            one(service).close();
-        }});
-
-        registry.close();
-    }
-
-    @Test
-    public void closeInvokesStopMethodOnEachService() {
-        final TestStopService service = context.mock(TestStopService.class);
-        registry.add(TestStopService.class, service);
-
-        context.checking(new Expectations() {{
-            one(service).stop();
-        }});
-
-        registry.close();
-    }
-
-    @Test
-    public void closeIgnoresServiceWithNoCloseOrStopMethod() {
-        registry.add(String.class, "service");
-
-        registry.close();
-    }
-
-    @Test
-    public void discardsServicesOnClose() {
-        registry.get(String.class);
-        registry.close();
-        try {
-            registry.get(String.class);
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo("Cannot locate service of type String, as TestRegistry has been closed."));
-        }
-    }
-
-    @Test
-    public void discardsFactoriesOnClose() {
-        registry.getFactory(BigDecimal.class);
-        registry.close();
-        try {
-            registry.getFactory(BigDecimal.class);
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo("Cannot locate factory for objects of type BigDecimal, as TestRegistry has been closed."));
-        }
-    }
-
-    private static class TestRegistry extends DefaultServiceRegistry {
-        public TestRegistry() {
-        }
-
-        public TestRegistry(ServiceRegistry parent) {
-            super(parent);
-        }
-
-        protected String createString() {
-            return get(Integer.class).toString();
-        }
-
-        protected Long createLong(Long value) {
-            return value + 10;
-        }
-
-        protected Integer createInt() {
-            return 12;
-        }
-
-        protected Factory<BigDecimal> createTestFactory() {
-            return new TestFactory();
-        }
-
-        protected Factory<Long> createLongFactory(final Factory<Long> factory) {
-            return new Factory<Long>() {
-                public Long create() {
-                    return factory.create() + 2;
-                }
-            };
-        }
-    }
-
-    private static class SubType extends TestRegistry {
-    }
-
-    private static class TestFactory implements Factory<BigDecimal> {
-        int value;
-        public BigDecimal create() {
-            return BigDecimal.valueOf(value++);
-        }
-    }
-
-    private interface StringFactory extends Factory<String> {
-    }
-
-    public interface TestCloseService {
-        void close();
-    }
-
-    public interface TestStopService {
-        void stop();
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
old mode 100644
new mode 100755
index ec5ac03..bedab2a
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
@@ -1,87 +1,137 @@
-/*
- * 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.project;
-
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.DefaultClassPathProvider;
-import org.gradle.api.internal.DefaultClassPathRegistry;
-import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.cache.AutoCloseCacheFactory;
-import org.gradle.cache.CacheFactory;
-import org.gradle.initialization.ClassLoaderFactory;
-import org.gradle.initialization.CommandLineConverter;
-import org.gradle.initialization.DefaultClassLoaderFactory;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.*;
-import org.gradle.logging.internal.DefaultLoggingManagerFactory;
-import org.gradle.logging.internal.DefaultProgressLoggerFactory;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-public class GlobalServicesRegistryTest {
-    private final GlobalServicesRegistry registry = new GlobalServicesRegistry();
-
-    @Test
-    public void providesCommandLineArgsConverter() {
-        assertThat(registry.get(CommandLineConverter.class), instanceOf(
-                DefaultCommandLineConverter.class));
-    }
-
-    @Test
-    public void providesACacheFactory() {
-        assertThat(registry.get(CacheFactory.class), instanceOf(AutoCloseCacheFactory.class));
-    }
-
-    @Test
-    public void providesAClassPathRegistry() {
-        assertThat(registry.get(ClassPathRegistry.class), instanceOf(DefaultClassPathRegistry.class));
-    }
-
-    @Test
-    public void providesAClassLoaderFactory() {
-        assertThat(registry.get(ClassLoaderFactory.class), instanceOf(DefaultClassLoaderFactory.class));
-    }
-
-    @Test
-    public void providesALoggingManagerFactory() {
-        assertThat(registry.getFactory(LoggingManagerInternal.class), instanceOf(DefaultLoggingManagerFactory.class));
-    }
-    
-    @Test
-    public void providesAListenerManager() {
-        assertThat(registry.get(ListenerManager.class), instanceOf(DefaultListenerManager.class));
-    }
-    
-    @Test
-    public void providesAProgressLoggerFactory() {
-        assertThat(registry.get(ProgressLoggerFactory.class), instanceOf(DefaultProgressLoggerFactory.class));
-    }
-    
-    @Test
-    public void providesAGradleDistributionLocator() {
-        assertThat(registry.get(GradleDistributionLocator.class), instanceOf(DefaultClassPathProvider.class));
-    }
-    
-    @Test
-    public void providesAnIsolatedAntBuilder() {
-        assertThat(registry.get(IsolatedAntBuilder.class), instanceOf(DefaultIsolatedAntBuilder.class));
-    }
-}
+/*
+ * 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.project;
+
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.DefaultPluginModuleRegistry;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.classpath.PluginModuleRegistry;
+import org.gradle.cache.internal.CacheFactory;
+import org.gradle.cache.internal.DefaultCacheFactory;
+import org.gradle.cache.internal.DefaultFileLockManager;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.initialization.ClassLoaderRegistry;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.initialization.DefaultClassLoaderRegistry;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.internal.nativeplatform.*;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.logging.internal.DefaultLoggingManagerFactory;
+import org.gradle.logging.internal.DefaultProgressLoggerFactory;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.util.ClassLoaderFactory;
+import org.gradle.util.DefaultClassLoaderFactory;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+public class GlobalServicesRegistryTest {
+    private final GlobalServicesRegistry registry = new GlobalServicesRegistry();
+
+    @Test
+    public void providesCommandLineArgsConverter() {
+        assertThat(registry.get(CommandLineConverter.class), instanceOf(
+                DefaultCommandLineConverter.class));
+    }
+
+    @Test
+    public void providesACacheFactoryFactory() {
+        assertThat(registry.getFactory(CacheFactory.class), instanceOf(DefaultCacheFactory.class));
+    }
+
+    @Test
+    public void providesAModuleRegistry() {
+        assertThat(registry.get(ModuleRegistry.class), instanceOf(DefaultModuleRegistry.class));
+    }
+
+    @Test
+    public void providesAPluginModuleRegistry() {
+        assertThat(registry.get(PluginModuleRegistry.class), instanceOf(DefaultPluginModuleRegistry.class));
+    }
+
+    @Test
+    public void providesAClassPathRegistry() {
+        assertThat(registry.get(ClassPathRegistry.class), instanceOf(DefaultClassPathRegistry.class));
+    }
+
+    @Test
+    public void providesAClassLoaderRegistry() {
+        assertThat(registry.get(ClassLoaderRegistry.class), instanceOf(DefaultClassLoaderRegistry.class));
+    }
+
+    @Test
+    public void providesALoggingManagerFactory() {
+        assertThat(registry.getFactory(LoggingManagerInternal.class), instanceOf(DefaultLoggingManagerFactory.class));
+    }
+    
+    @Test
+    public void providesAListenerManager() {
+        assertThat(registry.get(ListenerManager.class), instanceOf(DefaultListenerManager.class));
+    }
+    
+    @Test
+    public void providesAProgressLoggerFactory() {
+        assertThat(registry.get(ProgressLoggerFactory.class), instanceOf(DefaultProgressLoggerFactory.class));
+    }
+    
+    @Test
+    public void providesAGradleDistributionLocator() {
+        assertThat(registry.get(GradleDistributionLocator.class), instanceOf(DefaultModuleRegistry.class));
+    }
+    
+    @Test
+    public void providesAClassLoaderFactory() {
+        assertThat(registry.get(ClassLoaderFactory.class), instanceOf(DefaultClassLoaderFactory.class));
+    }
+
+    @Test
+    public void providesAMessagingServer() {
+        assertThat(registry.get(MessagingServer.class), instanceOf(MessagingServer.class));
+    }
+
+    @Test
+    public void providesAClassGenerator() {
+        assertThat(registry.get(ClassGenerator.class), instanceOf(AsmBackedClassGenerator.class));
+    }
+    
+    @Test
+    public void providesAnInstantiator() {
+        assertThat(registry.get(Instantiator.class), instanceOf(ClassGeneratorBackedInstantiator.class));
+    }
+
+    @Test
+    public void providesAFileLockManager() {
+        assertThat(registry.get(FileLockManager.class), instanceOf(DefaultFileLockManager.class));
+    }
+
+    @Test
+    public void providesAProcessEnvironment() {
+        assertThat(registry.get(ProcessEnvironment.class), notNullValue());
+    }
+
+    @Test
+    public void providesAFileSystem() {
+        assertThat(registry.get(FileSystem.class), notNullValue());
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java
index 419befc..5e6485c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java
@@ -19,13 +19,14 @@ import org.gradle.StartParameter;
 import org.gradle.api.execution.TaskExecutionGraphListener;
 import org.gradle.api.execution.TaskExecutionListener;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.PublishModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.repositories.DefaultInternalRepository;
-import org.gradle.api.internal.artifacts.repositories.InternalRepository;
 import org.gradle.api.internal.plugins.DefaultPluginRegistry;
 import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.cache.CacheRepository;
+import org.gradle.execution.BuildExecuter;
+import org.gradle.execution.DefaultBuildExecuter;
 import org.gradle.execution.DefaultTaskGraphExecuter;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.JUnit4GroovyMockery;
@@ -48,15 +49,11 @@ public class GradleInternalServiceRegistryTest {
     private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
     private final GradleInternalServiceRegistry registry = new GradleInternalServiceRegistry(parent, gradle);
     private final StartParameter startParameter = new StartParameter();
-    private final PublishModuleDescriptorConverter publishModuleDescriptorConverter =
-            context.mock(PublishModuleDescriptorConverter.class);
     private final ListenerManager listenerManager = context.mock(ListenerManager.class);
 
     @Before
     public void setUp() {
         context.checking(new Expectations() {{
-            allowing(parent).get(PublishModuleDescriptorConverter.class);
-            will(returnValue(publishModuleDescriptorConverter));
             allowing(parent).get(ListenerManager.class);
             will(returnValue(listenerManager));
             allowing(gradle).getStartParameter();
@@ -86,6 +83,17 @@ public class GradleInternalServiceRegistryTest {
     }
 
     @Test
+    public void providesABuildExecuter() {
+        context.checking(new Expectations(){{
+            allowing(parent).get(CacheRepository.class);
+            will(returnValue(context.mock(CacheRepository.class)));
+        }});
+
+        assertThat(registry.get(BuildExecuter.class), instanceOf(DefaultBuildExecuter.class));
+        assertThat(registry.get(BuildExecuter.class), sameInstance(registry.get(BuildExecuter.class)));
+    }
+
+    @Test
     public void providesATaskGraphExecuter() {
         context.checking(new Expectations() {{
             one(listenerManager).createAnonymousBroadcaster(TaskExecutionGraphListener.class);
@@ -93,13 +101,8 @@ public class GradleInternalServiceRegistryTest {
             one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
             will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
         }});
+
         assertThat(registry.get(TaskGraphExecuter.class), instanceOf(DefaultTaskGraphExecuter.class));
         assertThat(registry.get(TaskGraphExecuter.class), sameInstance(registry.get(TaskGraphExecuter.class)));
     }
-
-    @Test
-    public void providesAnInternalRepository() {
-        assertThat(registry.get(InternalRepository.class), instanceOf(DefaultInternalRepository.class));
-        assertThat(registry.get(InternalRepository.class), sameInstance(registry.get(InternalRepository.class)));
-    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
index 51e0613..a78648a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
@@ -20,24 +20,19 @@ import org.apache.commons.io.FileUtils;
 import org.gradle.StartParameter;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.initialization.ProjectDescriptor;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.Factory;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
-import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.internal.Factory;
+import org.gradle.testfixtures.internal.GlobalTestServices;
 import org.gradle.testfixtures.internal.TestTopLevelBuildServiceRegistry;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.MultiParentClassLoader;
 import org.gradle.util.TemporaryFolder;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -54,21 +49,17 @@ import static org.junit.Assert.*;
  */
 @RunWith(JMock.class)
 public class ProjectFactoryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final MultiParentClassLoader buildScriptClassLoader = new MultiParentClassLoader(getClass().getClassLoader());
     @Rule
     public TemporaryFolder testDir = new TemporaryFolder();
     private final File rootDir = testDir.getDir();
     private final File projectDir = new File(rootDir, "project");
-    private ConfigurationContainerFactory configurationContainerFactory = context.mock(
-            ConfigurationContainerFactory.class);
     private Factory<RepositoryHandler> repositoryHandlerFactory = context.mock(Factory.class);
-    private DefaultRepositoryHandler repositoryHandler = context.mock(DefaultRepositoryHandler.class);
+    private RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
     private StartParameter startParameterStub = new StartParameter();
-    private ServiceRegistryFactory serviceRegistryFactory = new TestTopLevelBuildServiceRegistry(new GlobalServicesRegistry(), startParameterStub, rootDir);
-    private ClassGenerator classGeneratorMock = serviceRegistryFactory.get(ClassGenerator.class);
+    private ServiceRegistryFactory serviceRegistryFactory = new TestTopLevelBuildServiceRegistry(new GlobalTestServices(), startParameterStub, rootDir);
+    private Instantiator instantiatorMock = serviceRegistryFactory.get(Instantiator.class);
     private GradleInternal gradle = context.mock(GradleInternal.class);
 
     private ProjectFactory projectFactory;
@@ -86,8 +77,6 @@ public class ProjectFactoryTest {
             will(returnValue(gradleServices));
             allowing(gradle).getStartParameter();
             will(returnValue(startParameterStub));
-            allowing(configurationContainerFactory).createConfigurationContainer(with(any(ResolverProvider.class)),
-                    with(any(DependencyMetaDataProvider.class)), with(any(DomainObjectContext.class)));
             allowing(gradle).getProjectRegistry();
             will(returnValue(gradleServices.get(IProjectRegistry.class)));
             allowing(gradle).getScriptClassLoader();
@@ -97,7 +86,7 @@ public class ProjectFactoryTest {
             ignoring(gradle).getProjectEvaluationBroadcaster();
         }});
 
-        projectFactory = new ProjectFactory(null, classGeneratorMock);
+        projectFactory = new ProjectFactory(instantiatorMock);
     }
 
     @Test
@@ -173,9 +162,9 @@ public class ProjectFactoryTest {
 
     @Test
     public void testConstructsRootProjectWithEmbeddedBuildScript() {
-        ScriptSource expectedScriptSource = new StringScriptSource("script", "content");
 
-        ProjectFactory projectFactory = new ProjectFactory(expectedScriptSource, classGeneratorMock);
+
+        ProjectFactory projectFactory = new ProjectFactory(instantiatorMock);
 
         DefaultProject project = projectFactory.createProject(descriptor("somename"), null, gradle);
 
@@ -186,7 +175,7 @@ public class ProjectFactoryTest {
         assertSame(project, project.getRootProject());
         assertNotNull(project.getConvention());
         checkProjectResources(project);
-        assertSame(project.getBuildScriptSource(), expectedScriptSource);
+
     }
 
     private ProjectDescriptor descriptor(String name) {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
index b7cb3f0..c989f5a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
@@ -17,31 +17,31 @@
 package org.gradle.api.internal.project;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.*;
-import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.internal.artifacts.DependencyManagementServices;
+import org.gradle.api.internal.artifacts.DependencyResolutionServices;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
-import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
-import org.gradle.api.internal.artifacts.dsl.SharedConventionRepositoryHandlerFactory;
-import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
 import org.gradle.api.internal.file.*;
 import org.gradle.api.internal.initialization.DefaultScriptHandler;
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
-import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.internal.plugins.DefaultProjectsPluginContainer;
 import org.gradle.api.internal.plugins.PluginRegistry;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.logging.LoggingManager;
-import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.PluginContainer;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
@@ -60,17 +60,19 @@ import static org.junit.Assert.assertThat;
 public class ProjectInternalServiceRegistryTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+    private final ConfigurationContainerInternal configurationContainer = context.mock(ConfigurationContainerInternal.class);
     private final GradleInternal gradle = context.mock(GradleInternal.class);
-    private final ConfigurationContainerFactory configurationContainerFactory = context.mock(
-            ConfigurationContainerFactory.class);
-    private final Factory<RepositoryHandler> repositoryHandlerFactory = context.mock(Factory.class);
+    private final DependencyManagementServices dependencyManagementServices = context.mock(DependencyManagementServices.class);
     private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
-    private final PublishArtifactFactory publishArtifactFactory = context.mock(PublishArtifactFactory.class);
     private final DependencyFactory dependencyFactory = context.mock(DependencyFactory.class);
     private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
     private final ProjectInternalServiceRegistry registry = new ProjectInternalServiceRegistry(parent, project);
     private final PluginRegistry pluginRegistry = context.mock(PluginRegistry.class);
+    private final DependencyResolutionServices dependencyResolutionServices = context.mock(DependencyResolutionServices.class);
+    private final RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
+    private final Factory publishServicesFactory = context.mock(Factory.class);
+    private final DependencyHandler dependencyHandler = context.mock(DependencyHandler.class);
+    private final ArtifactHandler artifactHandler = context.mock(ArtifactHandler.class);
 
     @Before
     public void setUp() {
@@ -78,22 +80,20 @@ public class ProjectInternalServiceRegistryTest {
             allowing(project).getGradle();
             will(returnValue(gradle));
             allowing(project).getProjectDir();
-            will(returnValue(new File("project-dir")));
+            will(returnValue(new File("project-dir").getAbsoluteFile()));
             allowing(project).getBuildScriptSource();
             allowing(parent).get(ITaskFactory.class);
             will(returnValue(taskFactory));
-            allowing(parent).getFactory(RepositoryHandler.class);
-            will(returnValue(repositoryHandlerFactory));
-            allowing(parent).get(ConfigurationContainerFactory.class);
-            will(returnValue(configurationContainerFactory));
-            allowing(parent).get(PublishArtifactFactory.class);
-            will(returnValue(publishArtifactFactory));
             allowing(parent).get(DependencyFactory.class);
             will(returnValue(dependencyFactory));
             allowing(parent).get(PluginRegistry.class);
             will(returnValue(pluginRegistry));
-            allowing(parent).get(ClassGenerator.class);
-            will(returnValue(new AsmBackedClassGenerator()));
+            allowing(parent).get(DependencyManagementServices.class);
+            will(returnValue(dependencyManagementServices));
+            allowing(parent).get(Instantiator.class);
+            will(returnValue(new DirectInstantiator()));
+            allowing(parent).get(FileSystem.class);
+            will(returnValue(context.mock(FileSystem.class)));
         }});
     }
 
@@ -104,12 +104,6 @@ public class ProjectInternalServiceRegistryTest {
     }
 
     @Test
-    public void providesAConvention() {
-        assertThat(registry.get(Convention.class), instanceOf(DefaultConvention.class));
-        assertThat(registry.get(Convention.class), sameInstance(registry.get(Convention.class)));
-    }
-
-    @Test
     public void providesATaskContainerFactory() {
         assertThat(registry.getFactory(TaskContainerInternal.class), instanceOf(DefaultTaskContainerFactory.class));
     }
@@ -126,23 +120,42 @@ public class ProjectInternalServiceRegistryTest {
     }
 
     @Test
-    public void providesARepositoryHandlerFactory() {
-        assertThat(registry.getFactory(RepositoryHandler.class), instanceOf(SharedConventionRepositoryHandlerFactory.class));
+    public void providesAnArtifactPublicationServicesFactory() {
+        expectDependencyResolutionServicesCreated();
+
+        assertThat(registry.getFactory(ArtifactPublicationServices.class), sameInstance(publishServicesFactory));
+        assertThat(registry.getFactory(ArtifactPublicationServices.class), sameInstance(registry.getFactory(ArtifactPublicationServices.class)));
+    }
+
+    @Test
+    public void providesARepositoryHandler() {
+        expectDependencyResolutionServicesCreated();
+
+        assertThat(registry.get(RepositoryHandler.class), sameInstance(repositoryHandler));
+        assertThat(registry.get(RepositoryHandler.class), sameInstance(registry.get(RepositoryHandler.class)));
+    }
+
+    @Test
+    public void providesAConfigurationContainer() {
+        expectDependencyResolutionServicesCreated();
+
+        assertThat(registry.get(ConfigurationContainerInternal.class), sameInstance(configurationContainer));
+        assertThat(registry.get(ConfigurationContainerInternal.class), sameInstance(registry.get(ConfigurationContainerInternal.class)));
     }
 
     @Test
     public void providesAnArtifactHandler() {
-        expectConfigurationHandlerCreated();
+        expectDependencyResolutionServicesCreated();
 
-        assertThat(registry.get(ArtifactHandler.class), instanceOf(DefaultArtifactHandler.class));
+        assertThat(registry.get(ArtifactHandler.class), sameInstance(artifactHandler));
         assertThat(registry.get(ArtifactHandler.class), sameInstance(registry.get(ArtifactHandler.class)));
     }
 
     @Test
     public void providesADependencyHandler() {
-        expectConfigurationHandlerCreated();
+        expectDependencyResolutionServicesCreated();
 
-        assertThat(registry.get(DependencyHandler.class), instanceOf(DefaultDependencyHandler.class));
+        assertThat(registry.get(DependencyHandler.class), sameInstance(dependencyHandler));
         assertThat(registry.get(DependencyHandler.class), sameInstance(registry.get(DependencyHandler.class)));
     }
 
@@ -164,7 +177,7 @@ public class ProjectInternalServiceRegistryTest {
 
     @Test
     public void providesAFileResolver() {
-        assertThat(registry.get(FileResolver.class), instanceOf(BaseDirConverter.class));
+        assertThat(registry.get(FileResolver.class), instanceOf(BaseDirFileResolver.class));
         assertThat(registry.get(FileResolver.class), sameInstance(registry.get(FileResolver.class)));
     }
 
@@ -201,37 +214,45 @@ public class ProjectInternalServiceRegistryTest {
     }
 
     private void expectScriptClassLoaderProviderCreated() {
-        expectConfigurationHandlerCreated();
-        
         context.checking(new Expectations() {{
+            one(dependencyManagementServices).create(with(notNullValue(FileResolver.class)),
+                    with(notNullValue(DependencyMetaDataProvider.class)),
+                    with(notNullValue(ProjectFinder.class)),
+                    with(notNullValue(DomainObjectContext.class)));
+            will(returnValue(dependencyResolutionServices));
+
+            ignoring(dependencyResolutionServices);
+
             allowing(project).getParent();
             will(returnValue(null));
 
             allowing(gradle).getScriptClassLoader();
             will(returnValue(null));
-
-            ignoring(configurationContainer);
         }});
     }
 
-    private void expectConfigurationHandlerCreated() {
-        context.checking(new Expectations() {{
-            RepositoryHandler repositoryHandler = context.mock(TestRepositoryHandler.class);
-
-            allowing(project).getRepositories();
-            will(returnValue(repositoryHandler));
+    private void expectDependencyResolutionServicesCreated() {
+        context.checking(new Expectations(){{
+            one(dependencyManagementServices).create(with(notNullValue(FileResolver.class)),
+                    with(notNullValue(DependencyMetaDataProvider.class)),
+                    with(notNullValue(ProjectFinder.class)),
+                    with(notNullValue(DomainObjectContext.class)));
+            will(returnValue(dependencyResolutionServices));
 
-            allowing(repositoryHandlerFactory).create();
+            allowing(dependencyResolutionServices).getResolveRepositoryHandler();
             will(returnValue(repositoryHandler));
 
-            ignoring(repositoryHandler);
+            allowing(dependencyResolutionServices).getPublishServicesFactory();
+            will(returnValue(publishServicesFactory));
 
-            one(configurationContainerFactory).createConfigurationContainer(with(sameInstance(repositoryHandler)), with(
-                    notNullValue(DependencyMetaDataProvider.class)), with(sameInstance(project)));
+            allowing(dependencyResolutionServices).getConfigurationContainer();
             will(returnValue(configurationContainer));
-        }});
-    }
 
-    private interface TestRepositoryHandler extends RepositoryHandler, IConventionAware {
+            allowing(dependencyResolutionServices).getDependencyHandler();
+            will(returnValue(dependencyHandler));
+
+            allowing(dependencyResolutionServices).getArtifactHandler();
+            will(returnValue(artifactHandler));
+        }});
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectStateInternalSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectStateInternalSpec.groovy
new file mode 100644
index 0000000..d11f326
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectStateInternalSpec.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.project
+
+import spock.lang.*
+import org.gradle.util.ConfigureUtil
+
+class ProjectStateInternalSpec extends Specification {
+	
+	def "to string representation"() {
+		expect:
+		stateString {} == "NOT EXECUTED"
+		stateString { executing = true } == "EXECUTING"
+		stateString { executed() } == "EXECUTED"
+		stateString { executed(new Error("bang")) } == "FAILED (bang)"
+	}
+	
+	String stateString(Closure closure) {
+		def state = ConfigureUtil.configure(closure, new ProjectStateInternal())
+		def matcher = state.toString() =~ /^project state '(.*?)'$/
+		assert matcher
+		matcher[0][1]
+	}
+	
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskExecutionServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskExecutionServicesTest.groovy
new file mode 100644
index 0000000..a73b72b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskExecutionServicesTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.project
+
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter
+import org.gradle.listener.ListenerManager
+import org.gradle.cache.CacheRepository
+import org.gradle.StartParameter
+import org.gradle.api.invocation.Gradle
+import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.cache.PersistentCache
+import org.gradle.internal.service.ServiceRegistry
+
+class TaskExecutionServicesTest extends Specification {
+    final ServiceRegistry parent = Mock()
+    final Gradle gradle = Mock()
+    final TaskExecutionServices services = new TaskExecutionServices(parent, gradle)
+
+    def "makes a TaskExecutor available"() {
+        given:
+        ListenerManager listenerManager = Mock()
+        StartParameter startParameter = Mock()
+        CacheRepository cacheRepository = Mock()
+        DirectoryCacheBuilder cacheBuilder = Mock()
+        PersistentCache cache = Mock()
+        _ * parent.get(ListenerManager) >> listenerManager
+        _ * parent.get(StartParameter) >> startParameter
+        _ * parent.get(CacheRepository) >> cacheRepository
+        _ * cacheRepository.cache(!null) >> cacheBuilder
+        _ * cacheBuilder.forObject(gradle) >> cacheBuilder
+        _ * cacheBuilder.withDisplayName(!null) >> cacheBuilder
+        _ * cacheBuilder.withLockMode(!null) >> cacheBuilder
+        _ * cacheBuilder.open() >> cache
+
+        expect:
+        services.get(TaskExecuter) instanceof ExecuteAtMostOnceTaskExecuter
+        services.get(TaskExecuter).is(services.get(TaskExecuter))
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
index 675c473..cc90720 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.project;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.TaskOutputsInternal;
 import org.gradle.api.internal.file.FileResolver;
@@ -24,6 +24,7 @@ import org.gradle.api.internal.tasks.DefaultTaskInputs;
 import org.gradle.api.internal.tasks.DefaultTaskOutputs;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.tasks.TaskInputs;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
new file mode 100644
index 0000000..19ad21c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
@@ -0,0 +1,264 @@
+/*
+ * 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.project;
+
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.classpath.DefaultModuleRegistry
+import org.gradle.api.internal.classpath.ModuleRegistry
+import org.gradle.api.internal.classpath.PluginModuleRegistry
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.internal.CacheFactory
+import org.gradle.cache.internal.DefaultCacheRepository
+import org.gradle.configuration.BuildConfigurer
+import org.gradle.configuration.DefaultBuildConfigurer
+import org.gradle.configuration.DefaultScriptPluginFactory
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.DefaultScriptCompilerFactory
+import org.gradle.groovy.scripts.ScriptCompilerFactory
+import org.gradle.internal.Factory
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.listener.DefaultListenerManager
+import org.gradle.listener.ListenerManager
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.messaging.concurrent.DefaultExecutorFactory
+import org.gradle.messaging.concurrent.ExecutorFactory
+import org.gradle.messaging.remote.MessagingServer
+import org.gradle.process.internal.DefaultWorkerProcessFactory
+import org.gradle.process.internal.WorkerProcessBuilder
+import org.gradle.profile.ProfileEventAdapter
+import org.gradle.util.ClassLoaderFactory
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultiParentClassLoader
+import org.gradle.util.TemporaryFolder
+import org.jmock.integration.junit4.JUnit4Mockery
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Timeout
+import org.gradle.api.internal.*
+import org.gradle.initialization.*
+import static org.hamcrest.Matchers.instanceOf
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+
+public class TopLevelBuildServiceRegistryTest extends Specification {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final StartParameter startParameter = new StartParameter();
+    private final ServiceRegistry parent = Mock();
+    private final Factory<CacheFactory> cacheFactoryFactory = Mock();
+    private final ClosableCacheFactory cacheFactory = Mock();
+    private final ClassLoaderRegistry classLoaderRegistry = Mock();
+
+    private final TopLevelBuildServiceRegistry registry = new TopLevelBuildServiceRegistry(parent, startParameter);
+
+    def setup() {
+        startParameter.gradleUserHomeDir = tmpDir.dir
+        _ * parent.getFactory(CacheFactory.class) >> cacheFactoryFactory
+        _ * cacheFactoryFactory.create() >> cacheFactory
+        _ * parent.get(ClassLoaderRegistry.class) >> classLoaderRegistry
+        _ * parent.getFactory(LoggingManagerInternal) >> Mock(Factory)
+        _ * parent.get(ModuleRegistry) >> new DefaultModuleRegistry()
+        _ * parent.get(PluginModuleRegistry.class) >> Mock(PluginModuleRegistry)
+    }
+
+    def delegatesToParentForUnknownService() {
+        setup:
+        parent.get(String.class) >> "value"
+
+        expect:
+        registry.get(String.class) == "value"
+    }
+
+    def throwsExceptionForUnknownDomainObject() {
+        when:
+        registry.createFor("string")
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == "Cannot create services for unknown domain object of type String."
+    }
+
+    def canCreateServicesForAGradleInstance() {
+        setup:
+        GradleInternal gradle = Mock();
+        ServiceRegistryFactory registry = this.registry.createFor(gradle);
+        expect:
+        registry instanceof GradleInternalServiceRegistry
+    }
+
+    def providesAListenerManager() {
+        setup:
+        ListenerManager listenerManager = expectListenerManagerCreated();
+        expect:
+        assertThat(registry.get(ListenerManager.class), sameInstance(listenerManager))
+    }
+
+    @Timeout(5)
+    def providesAScriptCompilerFactory() {
+        setup:
+        expectListenerManagerCreated();
+
+        expect:
+        registry.get(ScriptCompilerFactory.class) instanceof DefaultScriptCompilerFactory
+        registry.get(ScriptCompilerFactory.class) == registry.get(ScriptCompilerFactory)
+    }
+
+    def providesACacheRepositoryAndCleansUpOnClose() {
+        setup:
+        1 * cacheFactory.close()
+
+        expect:
+        registry.get(CacheRepository.class) instanceof DefaultCacheRepository
+        registry.get(CacheRepository.class) == registry.get(CacheRepository.class)
+        registry.close();
+    }
+
+    def providesAnInitScriptHandler() {
+        setup:
+        allowGetCoreImplClassLoader();
+        expectScriptClassLoaderCreated();
+        expectListenerManagerCreated();
+        allowGetGradleDistributionLocator()
+
+        expect:
+        registry.get(InitScriptHandler.class) instanceof InitScriptHandler
+        registry.get(InitScriptHandler.class) == registry.get(InitScriptHandler.class)
+    }
+
+    def providesAScriptObjectConfigurerFactory() {
+        setup:
+        allowGetCoreImplClassLoader();
+        expectListenerManagerCreated();
+        expectScriptClassLoaderCreated();
+        expect:
+        assertThat(registry.get(ScriptPluginFactory.class), instanceOf(DefaultScriptPluginFactory.class));
+        assertThat(registry.get(ScriptPluginFactory.class), sameInstance(registry.get(ScriptPluginFactory.class)));
+    }
+
+    def providesASettingsProcessor() {
+        setup:
+        allowGetCoreImplClassLoader();
+        expectListenerManagerCreated();
+        expectScriptClassLoaderCreated();
+        expect:
+        assertThat(registry.get(SettingsProcessor.class), instanceOf(PropertiesLoadingSettingsProcessor.class));
+        assertThat(registry.get(SettingsProcessor.class), sameInstance(registry.get(SettingsProcessor.class)));
+    }
+
+    def providesAnExceptionAnalyser() {
+        setup:
+        expectListenerManagerCreated();
+        expect:
+        assertThat(registry.get(ExceptionAnalyser.class), instanceOf(DefaultExceptionAnalyser.class));
+        assertThat(registry.get(ExceptionAnalyser.class), sameInstance(registry.get(ExceptionAnalyser.class)));
+    }
+
+    def providesAWorkerProcessFactory() {
+        setup:
+        expectParentServiceLocated(MessagingServer.class);
+        allowGetCoreImplClassLoader();
+
+        expect:
+        assertThat(registry.getFactory(WorkerProcessBuilder.class), instanceOf(DefaultWorkerProcessFactory.class));
+    }
+
+    def providesAnIsolatedAntBuilder() {
+        setup:
+        expectParentServiceLocated(ClassLoaderFactory.class);
+        allowGetCoreImplClassLoader();
+        expect:
+
+        assertThat(registry.get(IsolatedAntBuilder.class), instanceOf(DefaultIsolatedAntBuilder.class));
+        assertThat(registry.get(IsolatedAntBuilder.class), sameInstance(registry.get(IsolatedAntBuilder.class)));
+    }
+
+    def providesAProjectFactory() {
+        setup:
+        expectParentServiceLocated(Instantiator.class);
+        expectParentServiceLocated(ClassGenerator.class);
+        expect:
+        assertThat(registry.get(IProjectFactory.class), instanceOf(ProjectFactory.class));
+        assertThat(registry.get(IProjectFactory.class), sameInstance(registry.get(IProjectFactory.class)));
+    }
+
+    def providesAnExecutorFactory() {
+        expect:
+        assertThat(registry.get(ExecutorFactory.class), instanceOf(DefaultExecutorFactory.class));
+        assertThat(registry.get(ExecutorFactory.class), sameInstance(registry.get(ExecutorFactory.class)));
+    }
+
+    def providesABuildConfigurer() {
+        expect:
+        assertThat(registry.get(BuildConfigurer.class), instanceOf(DefaultBuildConfigurer.class));
+        assertThat(registry.get(BuildConfigurer.class), sameInstance(registry.get(BuildConfigurer.class)));
+    }
+
+    def providesAPropertiesLoader() {
+        expect:
+        assertThat(registry.get(IGradlePropertiesLoader.class), instanceOf(DefaultGradlePropertiesLoader.class));
+        assertThat(registry.get(IGradlePropertiesLoader.class), sameInstance(registry.get(IGradlePropertiesLoader.class)));
+    }
+
+    def providesABuildLoader() {
+        setup:
+        expectParentServiceLocated(Instantiator.class);
+        expect:
+        assertThat(registry.get(BuildLoader.class), instanceOf(ProjectPropertySettingBuildLoader.class));
+        assertThat(registry.get(BuildLoader.class), sameInstance(registry.get(BuildLoader.class)));
+    }
+
+    def providesAProfileEventAdapter() {
+        setup:
+        expectParentServiceLocated(BuildRequestMetaData.class);
+        expectListenerManagerCreated();
+
+        expect:
+        assertThat(registry.get(ProfileEventAdapter.class), instanceOf(ProfileEventAdapter.class));
+        assertThat(registry.get(ProfileEventAdapter.class), sameInstance(registry.get(ProfileEventAdapter.class)));
+    }
+
+    private <T> T expectParentServiceLocated(final Class<T> type) {
+        final T t = Mock(type);
+        parent.get(type) >> t
+        t
+    }
+
+    private ListenerManager expectListenerManagerCreated() {
+        final ListenerManager listenerManager = new DefaultListenerManager()
+        final ListenerManager listenerManagerParent = Mock()
+        parent.get(ListenerManager) >> listenerManagerParent
+        1 * listenerManagerParent.createChild() >> listenerManager
+        listenerManager
+    }
+
+    private void allowGetCoreImplClassLoader() {
+        classLoaderRegistry.getCoreImplClassLoader() >> new ClassLoader() {}
+    }
+
+    private void expectScriptClassLoaderCreated() {
+        1 * classLoaderRegistry.createScriptClassLoader() >> new MultiParentClassLoader()
+    }
+
+    private void allowGetGradleDistributionLocator() {
+        parent.get(GradleDistributionLocator.class) >> Mock(GradleDistributionLocator)
+    }
+
+    public interface ClosableCacheFactory extends CacheFactory {
+        void close();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.java
deleted file mode 100644
index 113c36e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.java
+++ /dev/null
@@ -1,257 +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.api.internal.project;
-
-import org.gradle.StartParameter;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.ExceptionAnalyser;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
-import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandlerFactory;
-import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
-import org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.cache.CacheFactory;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.DefaultCacheRepository;
-import org.gradle.configuration.BuildConfigurer;
-import org.gradle.configuration.DefaultBuildConfigurer;
-import org.gradle.configuration.DefaultScriptPluginFactory;
-import org.gradle.configuration.ScriptPluginFactory;
-import org.gradle.groovy.scripts.DefaultScriptCompilerFactory;
-import org.gradle.groovy.scripts.ScriptCompilerFactory;
-import org.gradle.initialization.*;
-import org.gradle.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.process.internal.DefaultWorkerProcessFactory;
-import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.MultiParentClassLoader;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.Collections;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith(JMock.class)
-public class TopLevelBuildServiceRegistryTest {
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
-    private final StartParameter startParameter = new StartParameter();
-    private final CacheFactory cacheFactory = context.mock(CacheFactory.class);
-    private final ClassPathRegistry classPathRegistry = context.mock(ClassPathRegistry.class);
-    private final TopLevelBuildServiceRegistry factory = new TopLevelBuildServiceRegistry(parent, startParameter);
-    private final ClassLoaderFactory classLoaderFactory = context.mock(ClassLoaderFactory.class);
-    private final Factory<LoggingManagerInternal> loggingManagerFactory = context.mock(Factory.class);
-    private final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory.class);
-
-    @Before
-    public void setUp() {
-        startParameter.setGradleUserHomeDir(tmpDir.getDir());
-        context.checking(new Expectations(){{
-            allowing(parent).get(CacheFactory.class);
-            will(returnValue(cacheFactory));
-            allowing(parent).get(ClassPathRegistry.class);
-            will(returnValue(classPathRegistry));
-            allowing(parent).get(ClassLoaderFactory.class);
-            will(returnValue(classLoaderFactory));
-            allowing(parent).getFactory(LoggingManagerInternal.class);
-            will(returnValue(loggingManagerFactory));
-            allowing(parent).get(ProgressLoggerFactory.class);
-            will(returnValue(progressLoggerFactory));
-        }});
-    }
-    
-    @Test
-    public void delegatesToParentForUnknownService() {
-        context.checking(new Expectations(){{
-            allowing(parent).get(String.class);
-            will(returnValue("value"));
-        }});
-
-        assertThat(factory.get(String.class), equalTo("value"));
-    }
-
-    @Test
-    public void throwsExceptionForUnknownDomainObject() {
-        try {
-            factory.createFor("string");
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("Cannot create services for unknown domain object of type String."));
-        }
-    }
-
-    @Test
-    public void canCreateServicesForAGradleInstance() {
-        GradleInternal gradle = context.mock(GradleInternal.class);
-        ServiceRegistryFactory registry = factory.createFor(gradle);
-        assertThat(registry, instanceOf(GradleInternalServiceRegistry.class));
-    }
-
-    @Test
-    public void providesAListenerManager() {
-        ListenerManager listenerManager = expectListenerManagerCreated();
-        assertThat(factory.get(ListenerManager.class), sameInstance(listenerManager));
-    }
-
-    @Test
-    public void providesAPublishArtifactFactory() {
-        assertThat(factory.get(PublishArtifactFactory.class), instanceOf(DefaultPublishArtifactFactory.class));
-        assertThat(factory.get(PublishArtifactFactory.class), sameInstance(factory.get(PublishArtifactFactory.class)));
-    }
-
-    @Test
-    public void providesATaskExecuter() {
-        expectListenerManagerCreated();
-        context.checking(new Expectations(){{
-            allowing(cacheFactory).open(with(notNullValue(File.class)), with(equalTo(startParameter.getCacheUsage())), with(equalTo(Collections.EMPTY_MAP)));
-        }});
-        assertThat(factory.get(TaskExecuter.class), instanceOf(ExecuteAtMostOnceTaskExecuter.class));
-        assertThat(factory.get(TaskExecuter.class), sameInstance(factory.get(TaskExecuter.class)));
-    }
-
-    @Test
-    public void providesARepositoryHandlerFactory() {
-        allowGetCoreImplClassLoader();
-        assertThat(factory.getFactory(RepositoryHandler.class), instanceOf(DefaultRepositoryHandlerFactory.class));
-    }
-
-    @Test
-    public void providesAScriptCompilerFactory() {
-        expectListenerManagerCreated();
-        assertThat(factory.get(ScriptCompilerFactory.class), instanceOf(DefaultScriptCompilerFactory.class));
-        assertThat(factory.get(ScriptCompilerFactory.class), sameInstance(factory.get(ScriptCompilerFactory.class)));
-    }
-
-    @Test
-    public void providesACacheRepository() {
-        assertThat(factory.get(CacheRepository.class), instanceOf(DefaultCacheRepository.class));
-        assertThat(factory.get(CacheRepository.class), sameInstance(factory.get(CacheRepository.class)));
-    }
-
-    @Test
-    public void providesAnInitScriptHandler() {
-        allowGetCoreImplClassLoader();
-        expectScriptClassLoaderCreated();
-        expectListenerManagerCreated();
-        assertThat(factory.get(InitScriptHandler.class), instanceOf(InitScriptHandler.class));
-        assertThat(factory.get(InitScriptHandler.class), sameInstance(factory.get(InitScriptHandler.class)));
-    }
-
-    @Test
-    public void providesAScriptObjectConfigurerFactory() {
-        allowGetCoreImplClassLoader();
-        expectListenerManagerCreated();
-        expectScriptClassLoaderCreated();
-        assertThat(factory.get(ScriptPluginFactory.class), instanceOf(DefaultScriptPluginFactory.class));
-        assertThat(factory.get(ScriptPluginFactory.class), sameInstance(factory.get(ScriptPluginFactory.class)));
-    }
-
-    @Test
-    public void providesASettingsProcessor() {
-        allowGetCoreImplClassLoader();
-        expectListenerManagerCreated();
-        expectScriptClassLoaderCreated();
-        assertThat(factory.get(SettingsProcessor.class), instanceOf(PropertiesLoadingSettingsProcessor.class));
-        assertThat(factory.get(SettingsProcessor.class), sameInstance(factory.get(SettingsProcessor.class)));
-    }
-
-    @Test
-    public void providesAnExceptionAnalyser() {
-        expectListenerManagerCreated();
-        assertThat(factory.get(ExceptionAnalyser.class), instanceOf(DefaultExceptionAnalyser.class));
-        assertThat(factory.get(ExceptionAnalyser.class), sameInstance(factory.get(ExceptionAnalyser.class)));
-    }
-
-    @Test
-    public void providesAWorkerProcessFactory() {
-        allowGetRootClassLoader();
-        assertThat(factory.getFactory(WorkerProcessBuilder.class), instanceOf(DefaultWorkerProcessFactory.class));
-    }
-
-    @Test
-    public void providesAProjectFactory() {
-        assertThat(factory.get(IProjectFactory.class), instanceOf(ProjectFactory.class));
-        assertThat(factory.get(IProjectFactory.class), sameInstance(factory.get(IProjectFactory.class)));
-    }
-
-    @Test
-    public void providesAnExecutorFactory() {
-        assertThat(factory.get(ExecutorFactory.class), instanceOf(DefaultExecutorFactory.class));
-        assertThat(factory.get(ExecutorFactory.class), sameInstance(factory.get(ExecutorFactory.class)));
-    }
-
-    @Test
-    public void providesABuildConfigurer() {
-        assertThat(factory.get(BuildConfigurer.class), instanceOf(DefaultBuildConfigurer.class));
-        assertThat(factory.get(BuildConfigurer.class), sameInstance(factory.get(BuildConfigurer.class)));
-    }
-
-    private ListenerManager expectListenerManagerCreated() {
-        final ListenerManager listenerManager = new DefaultListenerManager();
-        context.checking(new Expectations(){{
-            allowing(parent).get(ListenerManager.class);
-            ListenerManager parent = context.mock(ListenerManager.class);
-            will(returnValue(parent));
-            one(parent).createChild();
-            will(returnValue(listenerManager));
-        }});
-        return listenerManager;
-    }
-
-    private void expectScriptClassLoaderCreated() {
-        context.checking(new Expectations() {{
-            one(classLoaderFactory).createScriptClassLoader();
-            will(returnValue(new MultiParentClassLoader()));
-        }});
-    }
-
-    private void allowGetRootClassLoader() {
-        context.checking(new Expectations() {{
-            allowing(classLoaderFactory).getRootClassLoader();
-            will(returnValue(new ClassLoader() {
-            }));
-        }});
-    }
-
-    private void allowGetCoreImplClassLoader() {
-        context.checking(new Expectations() {{
-            allowing(classLoaderFactory).getCoreImplClassLoader();
-            will(returnValue(new ClassLoader() {
-            }));
-        }});
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
index f7bd574..035b96e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
@@ -16,7 +16,6 @@
 
 package org.gradle.api.internal.project.taskfactory;
 
-import org.gradle.api.Action;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.Task;
@@ -26,10 +25,7 @@ import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.*;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.HelperUtil;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
+import org.gradle.util.*;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -39,10 +35,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.Callable;
 
 import static org.gradle.util.Matchers.isEmpty;
@@ -67,6 +60,7 @@ public class AnnotationProcessingTaskFactoryTest {
     private final File missingFile = testDir.file("missing.txt");
     private final TestFile existingDir = testDir.file("dir").createDir();
     private final File missingDir = testDir.file("missing-dir");
+    private final File missingDir2 = testDir.file("missing-dir2");
     private final AnnotationProcessingTaskFactory factory = new AnnotationProcessingTaskFactory(delegate);
 
     @Test
@@ -147,7 +141,7 @@ public class AnnotationProcessingTaskFactoryTest {
         TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingFile);
         TaskWithInputFile task2 = expectTaskCreated(TaskWithInputFile.class, missingFile);
 
-        assertThat(task.getActions().get(0), sameInstance((Action) task2.getActions().get(0)));
+        assertThat(ReflectionUtil.getProperty(task.getActions().get(0), "action"), sameInstance(ReflectionUtil.getProperty(task2.getActions().get(0), "action")));
     }
     
     @Test
@@ -248,6 +242,12 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionSucceedsWhenSpecifiedOutputFilesIsAFile() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, Collections.singletonList(existingFile));
+        task.execute();
+    }
+
+    @Test
     public void validationActionSucceedsWhenSpecifiedOutputFileDoesNotExist() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new File(testDir, "subdir/output.txt"));
 
@@ -257,18 +257,40 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionSucceedsWhenSpecifiedOutputFilesDoesNotExist() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, Arrays.asList(new File(testDir, "subdir/output.txt"), new File(testDir, "subdir2/output.txt")));
+
+        task.execute();
+
+        assertTrue(new File(testDir, "subdir").isDirectory());
+        assertTrue(new File(testDir, "subdir2").isDirectory());
+    }
+
+    @Test
     public void validationActionSucceedsWhenOptionalOutputFileNotSpecified() {
         TaskWithOptionalOutputFile task = expectTaskCreated(TaskWithOptionalOutputFile.class);
         task.execute();
     }
 
     @Test
+    public void validationActionSucceedsWhenOptionalOutputFilesNotSpecified() {
+        TaskWithOptionalOutputFiles task = expectTaskCreated(TaskWithOptionalOutputFiles.class);
+        task.execute();
+    }
+    
+    @Test
     public void validationActionFailsWhenOutputFileNotSpecified() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new Object[]{null});
         assertValidationFails(task, "No value has been specified for property 'outputFile'.");
     }
 
     @Test
+    public void validationActionFailsWhenOutputFilesNotSpecified() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, new Object[]{null});
+        assertValidationFails(task, "No value has been specified for property 'outputFiles'.");
+    }
+    
+    @Test
     public void validationActionFailsWhenSpecifiedOutputFileIsADirectory() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingDir);
         assertValidationFails(task, String.format(
@@ -277,6 +299,14 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionFailsWhenSpecifiedOutputFilesIsADirectory() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, Collections.singletonList(existingDir));
+        assertValidationFails(task, String.format(
+                "Cannot write to file '%s' specified for property 'outputFiles' as it is a directory.",
+                task.outputFiles.get(0)));
+    }
+    
+    @Test
     public void validationActionFailsWhenSpecifiedOutputFileParentIsAFile() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new File(testDir, "subdir/output.txt"));
         GFileUtils.touch(task.outputFile.getParentFile());
@@ -286,18 +316,42 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionFailsWhenSpecifiedOutputFilesParentIsAFile() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, Collections.singletonList(new File(testDir, "subdir/output.txt")));
+        GFileUtils.touch(task.outputFiles.get(0).getParentFile());
+
+        assertValidationFails(task, String.format("Cannot write to file '%s' specified for property 'outputFiles', as ancestor '%s' is not a directory.",
+                task.outputFiles.get(0), task.outputFiles.get(0).getParentFile()));
+    }
+    
+    @Test
     public void registersSpecifiedOutputFile() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingFile);
         assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(existingFile)));
     }
 
     @Test
+    public void registersSpecifiedOutputFiles() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, Collections.singletonList(existingFile));
+        assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(existingFile)));
+    }
+
+    @Test
     public void doesNotRegisterOutputFileWhenNoneSpecified() {
         TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new Object[]{null});
         assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
     }
 
     @Test
+    public void doesNotRegisterOutputFilesWhenNoneSpecified() {
+        TaskWithOutputFiles task = expectTaskCreated(TaskWithOutputFiles.class, new Object[]{null});
+        assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
+
+        task = expectTaskCreated(TaskWithOutputFiles.class, Collections.<File>emptyList());
+        assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
+    }
+
+    @Test
     public void validationActionSucceedsWhenInputFilesSpecified() {
         TaskWithInputFiles task = expectTaskCreated(TaskWithInputFiles.class, toList(testDir));
         task.execute();
@@ -343,24 +397,50 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionSucceedsWhenSpecifiedOutputDirectoriesDoesNotExist() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Collections.singletonList(missingDir));
+        task.execute();
+
+        assertTrue(task.outputDirs.get(0).isDirectory());
+    }
+
+    @Test
     public void validationActionSucceedsWhenSpecifiedOutputDirectoryIsDirectory() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, existingDir);
         task.execute();
     }
 
     @Test
+    public void validationActionSucceedsWhenSpecifiedOutputDirectoriesAreDirectories() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Collections.singletonList(existingDir));
+        task.execute();
+    }
+    
+    @Test
     public void validationActionSucceedsWhenOptionalOutputDirectoryNotSpecified() {
         TaskWithOptionalOutputDir task = expectTaskCreated(TaskWithOptionalOutputDir.class);
         task.execute();
     }
 
     @Test
+    public void validationActionSucceedsWhenOptionalOutputDirectoriesNotSpecified() {
+        TaskWithOptionalOutputDirs task = expectTaskCreated(TaskWithOptionalOutputDirs.class);
+        task.execute();
+    }
+
+    @Test
     public void validationActionFailsWhenOutputDirectoryNotSpecified() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new Object[]{null});
         assertValidationFails(task, "No value has been specified for property 'outputDir'.");
     }
 
     @Test
+    public void validationActionFailsWhenOutputDirectoriesNotSpecified() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, new Object[]{null});
+        assertValidationFails(task, "No value has been specified for property 'outputDirs'.");
+    }
+
+    @Test
     public void validationActionFailsWhenOutputDirectoryIsAFile() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, existingFile);
         assertValidationFails(task, String.format("Directory '%s' specified for property 'outputDir' is not a directory.",
@@ -368,6 +448,13 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionFailsWhenOutputDirectoriesIsAFile() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Collections.singletonList(existingFile));
+        assertValidationFails(task, String.format("Directory '%s' specified for property 'outputDirs' is not a directory.",
+                task.outputDirs.get(0)));
+    }
+    
+    @Test
     public void validationActionFailsWhenParentOfOutputDirectoryIsAFile() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new File(testDir, "subdir/output"));
         GFileUtils.touch(task.outputDir.getParentFile());
@@ -376,18 +463,41 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationActionFailsWhenParentOfOutputDirectoriesIsAFile() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Collections.singletonList(new File(testDir, "subdir/output")));
+        GFileUtils.touch(task.outputDirs.get(0).getParentFile());
+
+        assertValidationFails(task, String.format("Cannot write to directory '%s' specified for property 'outputDirs', as ancestor '%s' is not a directory.", task.outputDirs.get(0), task.outputDirs.get(0).getParentFile()));
+    }
+    
+    @Test
     public void registersSpecifiedOutputDirectory() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, missingDir);
         assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(missingDir)));
     }
 
     @Test
+    public void registersSpecifiedOutputDirectories() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, Arrays.<File>asList(missingDir, missingDir2));
+        assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(missingDir, missingDir2)));
+    }
+    
+    @Test
     public void doesNotRegisterOutputDirectoryWhenNoneSpecified() {
         TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new Object[]{null});
         assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
     }
 
     @Test
+    public void doesNotRegisterOutputDirectoriesWhenNoneSpecified() {
+        TaskWithOutputDirs task = expectTaskCreated(TaskWithOutputDirs.class, new Object[]{null});
+        assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
+
+        task = expectTaskCreated(TaskWithOutputDirs.class, Collections.<File>emptyList());
+        assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
+    }
+
+    @Test
     public void validationActionSucceedsWhenSpecifiedInputDirectoryIsDirectory() {
         TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, existingDir);
         task.execute();
@@ -465,18 +575,36 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validatesNestedBeansWithPrivateType() {
+        TaskWithNestedBeanWithPrivateClass task = expectTaskCreated(TaskWithNestedBeanWithPrivateClass.class, new Object[]{existingFile, null});
+        assertValidationFails(task, "No value has been specified for property 'bean.inputFile'.");
+    }
+
+    @Test
     public void registersInputPropertyForNestedBeanClass() {
         TaskWithNestedBean task = expectTaskCreated(TaskWithNestedBean.class, new Object[]{null});
         assertThat(task.getInputs().getProperties().get("bean.class"), equalTo((Object) Bean.class.getName()));
     }
 
     @Test
+    public void registersInputPropertyForNestedBeanClassWithPrivateType() {
+        TaskWithNestedBeanWithPrivateClass task = expectTaskCreated(TaskWithNestedBeanWithPrivateClass.class, new Object[]{null, null});
+        assertThat(task.getInputs().getProperties().get("bean.class"), equalTo((Object) Bean2.class.getName()));
+    }
+
+    @Test
     public void doesNotRegisterInputPropertyWhenNestedBeanIsNull() {
         TaskWithOptionalNestedBean task = expectTaskCreated(TaskWithOptionalNestedBean.class);
         assertThat(task.getInputs().getProperties().get("bean.class"), nullValue());
     }
 
     @Test
+    public void doesNotRegisterInputPropertyWhenNestedBeanWithPrivateTypeIsNull() {
+        TaskWithOptionalNestedBeanWithPrivateType task = expectTaskCreated(TaskWithOptionalNestedBeanWithPrivateType.class);
+        assertThat(task.getInputs().getProperties().get("bean.class"), nullValue());
+    }
+
+    @Test
     public void validationFailsWhenNestedBeanIsNull() {
         TaskWithNestedBean task = expectTaskCreated(TaskWithNestedBean.class, new Object[]{null});
         task.bean = null;
@@ -484,12 +612,25 @@ public class AnnotationProcessingTaskFactoryTest {
     }
 
     @Test
+    public void validationFailsWhenNestedBeanWithPrivateTypeIsNull() {
+        TaskWithNestedBeanWithPrivateClass task = expectTaskCreated(TaskWithNestedBeanWithPrivateClass.class, new Object[]{null, null});
+        task.bean = null;
+        assertValidationFails(task, "No value has been specified for property 'bean'.");
+    }
+
+    @Test
     public void validationSucceedsWhenNestedBeanIsNullAndMarkedOptional() {
         TaskWithOptionalNestedBean task = expectTaskCreated(TaskWithOptionalNestedBean.class);
         task.execute();
     }
 
     @Test
+    public void validationSucceedsWhenNestedBeanWithPrivateTypeIsNullAndMarkedOptional() {
+        TaskWithOptionalNestedBeanWithPrivateType task = expectTaskCreated(TaskWithOptionalNestedBeanWithPrivateType.class);
+        task.execute();
+    }
+
+    @Test
     public void canAttachAnnotationToGroovyProperty() {
         InputFileTask task = expectTaskCreated(InputFileTask.class);
         assertValidationFails(task, "No value has been specified for property 'srcFile'.");
@@ -664,6 +805,19 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static class TaskWithOutputFiles extends DefaultTask {
+        List<File> outputFiles;
+
+        public TaskWithOutputFiles(List<File> outputFiles) {
+            this.outputFiles = outputFiles;
+        }
+
+        @OutputFiles
+        public List<File> getOutputFiles() {
+            return outputFiles;
+        }
+    }
+    
     public static class TaskWithOptionalOutputFile extends DefaultTask {
         @OutputFile @Optional
         public File getOutputFile() {
@@ -671,6 +825,13 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static class TaskWithOptionalOutputFiles extends DefaultTask {
+        @OutputFiles @Optional
+        public List<File> getOutputFiles() {
+            return null;
+        }
+    }
+
     public static class TaskWithOutputDir extends DefaultTask {
         File outputDir;
 
@@ -684,6 +845,19 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static class TaskWithOutputDirs extends DefaultTask {
+        List<File> outputDirs;
+
+        public TaskWithOutputDirs(List<File> outputDirs) {
+            this.outputDirs = outputDirs;
+        }
+
+        @OutputDirectories
+        public List<File> getOutputDirs() {
+            return outputDirs;
+        }
+    }
+    
     public static class TaskWithOptionalOutputDir extends DefaultTask {
         @OutputDirectory @Optional
         public File getOutputDir() {
@@ -691,6 +865,13 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static class TaskWithOptionalOutputDirs extends DefaultTask {
+        @OutputDirectories @Optional
+        public File getOutputDirs() {
+            return null;
+        }
+    }
+
     public static class TaskWithInputFiles extends DefaultTask {
         Iterable<? extends File> input;
 
@@ -740,6 +921,21 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    
+    public static class TaskWithNestedBeanWithPrivateClass extends DefaultTask {
+        Bean2 bean = new Bean2();
+
+        public TaskWithNestedBeanWithPrivateClass(File inputFile, File inputFile2) {
+            bean.inputFile = inputFile;
+            bean.inputFile2 = inputFile2;
+        }
+
+        @Nested
+        public Bean getBean() {
+            return bean;
+        }
+    }
+    
     public static class TaskWithMultipleProperties extends TaskWithNestedBean {
         public TaskWithMultipleProperties(File inputFile) {
             super(inputFile);
@@ -758,6 +954,15 @@ public class AnnotationProcessingTaskFactoryTest {
         }
     }
 
+    public static class TaskWithOptionalNestedBeanWithPrivateType extends DefaultTask {
+        Bean2 bean = new Bean2();
+
+        @Nested @Optional
+        public Bean getBean() {
+            return null;
+        }
+    }
+
     public static class Bean {
         @InputFile
         File inputFile;
@@ -766,4 +971,13 @@ public class AnnotationProcessingTaskFactoryTest {
             return inputFile;
         }
     }
+
+    public static class Bean2 extends Bean {
+        @InputFile
+        File inputFile2;
+
+        public File getInputFile() {
+            return inputFile2;
+        }
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
index 500be51..1f4d61c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
@@ -18,13 +18,11 @@ 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.internal.IConventionAware;
 import org.gradle.api.internal.project.DefaultProject;
-import org.gradle.api.plugins.Convention;
-import org.gradle.api.tasks.ConventionValue;
 import org.gradle.api.tasks.TaskInstantiationException;
 import org.gradle.util.GUtil;
 import org.gradle.util.HelperUtil;
+import org.gradle.util.ReflectionUtil;
 import org.gradle.util.WrapUtil;
 import org.junit.Before;
 import org.junit.Test;
@@ -33,6 +31,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Callable;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
@@ -106,8 +105,8 @@ public class TaskFactoryTest {
 
         assertThat(task.getProperty(), nullValue());
 
-        task.getConventionMapping().map("property", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        task.getConventionMapping().map("property", new Callable<Object>() {
+            public Object call() throws Exception {
                 return "conventionValue";
             }
         });
@@ -121,8 +120,8 @@ public class TaskFactoryTest {
     @Test
     public void doesNotApplyConventionMappingToGettersDefinedByTaskInterface() {
         TestConventionTask task = (TestConventionTask) checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, TestConventionTask.class)));
-        task.getConventionMapping().map("description", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        task.getConventionMapping().map("description", new Callable<Object>() {
+            public Object call() throws Exception {
                 throw new UnsupportedOperationException();
             }
         });
@@ -137,13 +136,15 @@ public class TaskFactoryTest {
         };
 
         Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_ACTION, action)));
-        assertThat((List)task.getActions(), equalTo((List) WrapUtil.toList(action)));
+        assertThat(task.getActions().size(), equalTo(1));
+        assertThat(ReflectionUtil.getProperty(task.getActions().get(0), "action"), sameInstance((Object) action));
     }
 
     @Test
     public void testCreateTaskWithActionClosure() {
         Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_ACTION, HelperUtil.TEST_CLOSURE)));
-        assertFalse(task.getActions().isEmpty());
+        assertThat(task.getActions().size(), equalTo(1));
+        assertThat(ReflectionUtil.getProperty(task.getActions().get(0), "closure"), sameInstance((Object) HelperUtil.TEST_CLOSURE));
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
index 3510fb6..a1fa172 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
@@ -17,12 +17,14 @@
 
 package org.gradle.api.internal.resource
 
+import org.gradle.testing.internal.util.Network
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import static org.hamcrest.Matchers.*
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.nullValue
 import static org.junit.Assert.*
 
 class UriResourceTest {
@@ -147,6 +149,8 @@ class UriResourceTest {
 
     @Test
     public void hasNoContentWhenUsingHttpUriAndFileDoesNotExist() {
+        if (Network.offline) { return } // when this test moves to spock, ignore this test instead of just passing.
+
         UriResource resource = new UriResource('<display-name>', new URI("http://www.gradle.org/unknown.txt"));
         assertFalse(resource.exists)
         try {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy
new file mode 100644
index 0000000..cb11589
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/resources/URIBuilderTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.resources;
+
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/13/11
+ */
+public class URIBuilderTest extends Specification {
+
+    def "builds URIs"() {
+        expect:
+        new URIBuilder(new URI("protocol:some/uri")).schemePrefix("hey").build().toString() == 'hey:protocol:some/uri'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
index 2e253b0..47f0ff5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
@@ -16,35 +16,40 @@
 package org.gradle.api.internal.tasks;
 
 import groovy.lang.Closure;
-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;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Rule;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownTaskException;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.util.GUtil;
 import org.gradle.util.HelperUtil;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Map;
 
+import static java.util.Collections.singletonMap;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
 @RunWith(JMock.class)
 public class DefaultTaskContainerTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
     private final ProjectInternal project = context.mock(ProjectInternal.class, "<project>");
     private int taskCount;
-    private final DefaultTaskContainer container = new DefaultTaskContainer(project, context.mock(ClassGenerator.class), taskFactory);
+    private final DefaultTaskContainer container = new DefaultTaskContainer(project, context.mock(Instantiator.class), taskFactory);
 
     @Test
     public void addsTaskWithMap() {
-        final Map<String, ?> options = GUtil.map("option", "value");
+        final Map<String, ?> options = singletonMap("option", "value");
         final Task task = task("task");
 
         context.checking(new Expectations(){{
@@ -57,7 +62,7 @@ public class DefaultTaskContainerTest {
 
     @Test
     public void addsTaskWithName() {
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
         final Task task = task("task");
 
         context.checking(new Expectations(){{
@@ -82,7 +87,7 @@ public class DefaultTaskContainerTest {
     @Test
     public void addsTaskWithNameAndConfigureClosure() {
         final Closure action = HelperUtil.toClosure("{ description = 'description' }");
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
         final Task task = task("task");
 
         context.checking(new Expectations(){{
@@ -96,7 +101,7 @@ public class DefaultTaskContainerTest {
 
     @Test
     public void replacesTaskWithName() {
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
         final Task task = task("task");
 
         context.checking(new Expectations(){{
@@ -122,7 +127,7 @@ public class DefaultTaskContainerTest {
     @Test
     public void doesNotFireRuleWhenAddingTask() {
         Rule rule = context.mock(Rule.class);
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
         final Task task = task("task");
 
         container.addRule(rule);
@@ -140,7 +145,7 @@ public class DefaultTaskContainerTest {
         final Task task = addTask("task");
 
         context.checking(new Expectations() {{
-            one(taskFactory).createTask(project, GUtil.map(Task.TASK_NAME, "task"));
+            one(taskFactory).createTask(project, singletonMap(Task.TASK_NAME, "task"));
             will(returnValue(task("task")));
         }});
 
@@ -160,7 +165,7 @@ public class DefaultTaskContainerTest {
 
         final Task newTask = task("task");
         context.checking(new Expectations() {{
-            one(taskFactory).createTask(project, GUtil.map(Task.TASK_NAME, "task"));
+            one(taskFactory).createTask(project, singletonMap(Task.TASK_NAME, "task"));
             will(returnValue(newTask));
         }});
         
@@ -259,8 +264,8 @@ public class DefaultTaskContainerTest {
     
     private void expectTaskLookupInOtherProject(final String projectPath, final String taskName, final Task task) {
         context.checking(new Expectations() {{
-            Project otherProject = context.mock(Project.class);
-            TaskContainer otherTaskContainer = context.mock(TaskContainer.class);
+            ProjectInternal otherProject = context.mock(ProjectInternal.class);
+            TaskContainerInternal otherTaskContainer = context.mock(TaskContainerInternal.class);
 
             allowing(project).findProject(projectPath);
             will(returnValue(otherProject));
@@ -284,7 +289,7 @@ public class DefaultTaskContainerTest {
 
     private Task addTask(String name) {
         final Task task = task(name);
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, name);
+        final Map<String, ?> options = singletonMap(Task.TASK_NAME, name);
         context.checking(new Expectations() {{
             one(taskFactory).createTask(project, options);
             will(returnValue(task));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
index f960fd0..84f775d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.java
@@ -21,11 +21,11 @@ import org.gradle.api.execution.TaskActionListener;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.TaskStateInternal;
-import org.gradle.logging.StandardOutputCapture;
 import org.gradle.api.tasks.StopActionException;
 import org.gradle.api.tasks.StopExecutionException;
 import org.gradle.api.tasks.TaskExecutionException;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
@@ -35,16 +35,19 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import static java.util.Collections.emptyList;
 import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.*;
+import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
 
 @RunWith(JMock.class)
 public class ExecuteActionsTaskExecuterTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final TaskInternal task = context.mock(TaskInternal.class, "<task>");
+    @SuppressWarnings("unchecked")
     private final Action<Task> action1 = context.mock(Action.class, "action1");
+    @SuppressWarnings("unchecked")
     private final Action<Task> action2 = context.mock(Action.class, "action2");
     private final TaskStateInternal state = context.mock(TaskStateInternal.class);
     private final ScriptSource scriptSource = context.mock(ScriptSource.class);
@@ -75,7 +78,7 @@ public class ExecuteActionsTaskExecuterTest {
     public void doesNothingWhenTaskHasNoActions() {
         context.checking(new Expectations() {{
             allowing(task).getActions();
-            will(returnValue(toList()));
+            will(returnValue(emptyList()));
 
             one(listener).beforeActions(task);
             inSequence(sequence);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy
old mode 100644
new mode 100755
index 1a265bd..1a28b48
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy
@@ -1,266 +1,297 @@
-/*
- * 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.util
-
-import static org.hamcrest.Matchers.*
-
-import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.api.internal.file.IdentityFileResolver
-import org.gradle.process.JavaForkOptions
-import org.gradle.process.internal.DefaultJavaForkOptions
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.Jvm
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.gradle.util.Matchers.isEmpty
-import static org.gradle.util.Matchers.isEmptyMap
-import static org.junit.Assert.*
-import org.junit.Before
-
- at RunWith(JMock.class)
-public class DefaultJavaForkOptionsTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final FileResolver resolver = context.mock(FileResolver.class)
-    private DefaultJavaForkOptions options
-
-    @Before
-    public void setup() {
-        context.checking {
-            allowing(resolver).resolveLater(".")
-        }
-        options = new DefaultJavaForkOptions(resolver, Jvm.current())
-    }
-
-    @Test
-    public void defaultValues() {
-        assertThat(options.executable, notNullValue())
-        assertThat(options.jvmArgs, isEmpty())
-        assertThat(options.systemProperties, isEmptyMap())
-        assertThat(options.maxHeapSize, nullValue())
-        assertThat(options.bootstrapClasspath.files, isEmpty())
-        assertFalse(options.enableAssertions)
-        assertFalse(options.debug)
-        assertThat(options.allJvmArgs, isEmpty())
-    }
-
-    @Test
-    public void convertsJvmArgsToStringOnGet() {
-        options.jvmArgs = [12, "${1 + 2}"]
-        assertThat(options.jvmArgs, equalTo(['12', '3']))
-    }
-
-    @Test
-    public void canAddJvmArgs() {
-        options.jvmArgs('arg1', 'arg2')
-        assertThat(options.jvmArgs, equalTo(['arg1', 'arg2']))
-    }
-
-    @Test
-    public void canSetSystemProperties() {
-        options.systemProperties = [key: 12, key2: "value", key3: null]
-        assertThat(options.systemProperties, equalTo(key: 12, key2: "value", key3: null))
-    }
-
-    @Test
-    public void canAddSystemProperties() {
-        options.systemProperties(key: 12)
-        options.systemProperty('key2', 'value2')
-        assertThat(options.systemProperties, equalTo(key: 12, key2: 'value2'))
-    }
-
-    @Test
-    public void allJvmArgsIncludeSystemPropertiesAsString() {
-        options.systemProperties(key: 12, key2: null, "key3": 'value')
-        options.jvmArgs('arg1')
-
-        assertThat(options.allJvmArgs, equalTo(['arg1', '-Dkey=12', '-Dkey2', '-Dkey3=value']))
-    }
-
-    @Test
-    public void systemPropertiesAreUpdatedWhenAddedUsingJvmArgs() {
-        options.systemProperties(key: 12)
-        options.jvmArgs('-Dkey=new value', '-Dkey2')
-
-        assertThat(options.systemProperties, equalTo(key: 'new value', key2: null))
-
-        options.allJvmArgs = []
-
-        assertThat(options.systemProperties, equalTo([:]))
-
-        options.allJvmArgs = ['-Dkey=value']
-
-        assertThat(options.systemProperties, equalTo([key: 'value']))
-    }
-
-    @Test
-    public void allJvmArgsIncludeMaxHeapSize() {
-        options.maxHeapSize = '1g'
-        options.jvmArgs('arg1')
-
-        assertThat(options.allJvmArgs, equalTo(['arg1', '-Xmx1g']))
-    }
-
-    @Test
-    public void maxHeapSizeIsUpdatedWhenSetUsingJvmArgs() {
-        options.maxHeapSize = '1g'
-        options.jvmArgs('-Xmx1024m')
-
-        assertThat(options.maxHeapSize, equalTo('1024m'))
-
-        options.allJvmArgs = []
-
-        assertThat(options.maxHeapSize, nullValue())
-
-        options.allJvmArgs = ['-Xmx1g']
-
-        assertThat(options.maxHeapSize, equalTo('1g'))
-    }
-
-    @Test
-    public void allJvmArgsIncludeAssertionsEnabled() {
-        assertThat(options.allJvmArgs, equalTo([]))
-
-        options.enableAssertions = true
-
-        assertThat(options.allJvmArgs, equalTo(['-ea']))
-    }
-
-    @Test
-    public void assertionsEnabledIsUpdatedWhenSetUsingJvmArgs() {
-        options.jvmArgs('-ea')
-        assertTrue(options.enableAssertions)
-        assertThat(options.jvmArgs, equalTo([]))
-
-        options.allJvmArgs = []
-        assertFalse(options.enableAssertions)
-
-        options.jvmArgs('-enableassertions')
-        assertTrue(options.enableAssertions)
-
-        options.allJvmArgs = ['-da']
-        assertFalse(options.enableAssertions)
-    }
-
-    @Test
-    public void allJvmArgsIncludeDebugArgs() {
-        assertThat(options.allJvmArgs, equalTo([]))
-
-        options.debug = true
-
-        assertThat(options.allJvmArgs, equalTo(['-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']))
-    }
-
-    @Test
-    public void debugIsUpdatedWhenSetUsingJvmArgs() {
-        options.jvmArgs('-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005')
-        assertTrue(options.debug)
-        assertThat(options.jvmArgs, equalTo([]))
-
-        options.allJvmArgs = []
-        assertFalse(options.debug)
-
-        options.jvmArgs = ['-Xdebug']
-        assertFalse(options.debug)
-        assertThat(options.jvmArgs, equalTo(['-Xdebug']))
-
-        options.jvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']
-        assertFalse(options.debug)
-        assertThat(options.jvmArgs, equalTo(['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']))
-
-        options.jvmArgs '-Xdebug'
-        assertTrue(options.debug)
-        assertThat(options.jvmArgs, equalTo([]))
-
-        options.jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=other']
-        assertFalse(options.debug)
-        assertThat(options.jvmArgs, equalTo(['-Xdebug', '-Xrunjdwp:transport=other']))
-
-        options.allJvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005', '-Xdebug']
-        assertTrue(options.debug)
-        assertThat(options.jvmArgs, equalTo([]))
-    }
-    
-    @Test
-    public void canSetBootstrapClasspath() {
-        def bootstrapClasspath = [:] as FileCollection
-        options.bootstrapClasspath = bootstrapClasspath
-
-        assertThat(options.bootstrapClasspath, sameInstance(bootstrapClasspath))
-    }
-
-    @Test
-    public void canAddToBootstrapClasspath() {
-        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
-        options = new DefaultJavaForkOptions(new IdentityFileResolver());
-        options.bootstrapClasspath(files[0])
-        options.bootstrapClasspath(files[1])
-
-        assertThat(options.bootstrapClasspath.getFiles(), equalTo(files as Set))
-    }
-
-    @Test
-    public void allJvmArgsIncludeBootstrapClasspath() {
-        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
-        options = new DefaultJavaForkOptions(new IdentityFileResolver());
-        options.bootstrapClasspath(files)
-
-        context.checking {
-            allowing(resolver).resolveFiles(['file.jar'])
-            will(returnValue([isEmpty: {false}, getAsPath: {'<classpath>'}] as FileCollection))
-        }
-
-        assertThat(options.allJvmArgs, equalTo(['-Xbootclasspath:' + files.join(System.properties['path.separator'])]))
-    }
-
-    @Test
-    public void canSetBootstrapClasspathViaAllJvmArgs() {
-        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
-        options = new DefaultJavaForkOptions(new IdentityFileResolver());
-        options.bootstrapClasspath(files[0])
-
-        options.allJvmArgs = ['-Xbootclasspath:' + files[1]]
-
-        assertThat(options.bootstrapClasspath.files, equalTo([files[1]] as Set))
-    }
-
-    @Test
-    public void canCopyToTargetOptions() {
-        options.executable('executable')
-        options.jvmArgs('arg')
-        options.systemProperties(key: 12)
-        options.maxHeapSize = '1g'
-
-        JavaForkOptions target = context.mock(JavaForkOptions.class)
-        context.checking {
-            one(target).setExecutable('executable')
-            one(target).setJvmArgs(['arg'])
-            one(target).setSystemProperties(key: 12)
-            one(target).setMaxHeapSize('1g')
-            one(target).setBootstrapClasspath(options.bootstrapClasspath)
-            one(target).setEnableAssertions(false)
-            one(target).setDebug(false)
-            ignoring(target)
-        }
-
-        options.copyTo(target)
-    }
-}
-
-
+/*
+ * 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.util
+
+import java.nio.charset.Charset
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.process.JavaForkOptions
+import org.gradle.process.internal.DefaultJavaForkOptions
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.internal.jvm.Jvm
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.gradle.util.Matchers.isEmpty
+import static org.gradle.util.Matchers.isEmptyMap
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(JMock.class)
+public class DefaultJavaForkOptionsTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final FileResolver resolver = context.mock(FileResolver.class)
+    private DefaultJavaForkOptions options
+
+    @Before
+    public void setup() {
+        context.checking {
+            allowing(resolver).resolveLater(".")
+        }
+        options = new DefaultJavaForkOptions(resolver, Jvm.current())
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(options.executable, notNullValue())
+        assertThat(options.jvmArgs, isEmpty())
+        assertThat(options.systemProperties, isEmptyMap())
+        assertThat(options.minHeapSize, nullValue())
+        assertThat(options.maxHeapSize, nullValue())
+        assertThat(options.bootstrapClasspath.files, isEmpty())
+        assertFalse(options.enableAssertions)
+        assertFalse(options.debug)
+        assertThat(options.allJvmArgs, equalTo([fileEncodingProperty()]))
+    }
+
+    @Test
+    public void convertsJvmArgsToStringOnGet() {
+        options.jvmArgs = [12, "${1 + 2}"]
+        assertThat(options.jvmArgs, equalTo(['12', '3']))
+    }
+
+    @Test
+    public void canAddJvmArgs() {
+        options.jvmArgs('arg1', 'arg2')
+        assertThat(options.jvmArgs, equalTo(['arg1', 'arg2']))
+    }
+
+    @Test
+    public void canSetSystemProperties() {
+        options.systemProperties = [key: 12, key2: "value", key3: null]
+        assertThat(options.systemProperties, equalTo(key: 12, key2: "value", key3: null))
+    }
+
+    @Test
+    public void canAddSystemProperties() {
+        options.systemProperties(key: 12)
+        options.systemProperty('key2', 'value2')
+        assertThat(options.systemProperties, equalTo(key: 12, key2: 'value2'))
+    }
+
+    @Test
+    public void allJvmArgsIncludeSystemPropertiesAsString() {
+        options.systemProperties(key: 12, key2: null, "key3": 'value')
+        options.jvmArgs('arg1')
+
+        assertThat(options.allJvmArgs, equalTo(['-Dkey=12', '-Dkey2', '-Dkey3=value', 'arg1', fileEncodingProperty()]))
+    }
+
+    @Test
+    public void systemPropertiesAreUpdatedWhenAddedUsingJvmArgs() {
+        options.systemProperties(key: 12)
+        options.jvmArgs('-Dkey=new value', '-Dkey2')
+
+        assertThat(options.systemProperties, equalTo(key: 'new value', key2: ''))
+
+        options.allJvmArgs = []
+
+        assertThat(options.systemProperties, equalTo([:]))
+
+        options.allJvmArgs = ['-Dkey=value']
+
+        assertThat(options.systemProperties, equalTo([key: 'value']))
+    }
+
+    @Test
+    public void allJvmArgsIncludeMinHeapSize() {
+        options.minHeapSize = '64m'
+        options.jvmArgs('arg1')
+
+        assertThat(options.allJvmArgs, equalTo(['arg1', '-Xms64m', fileEncodingProperty()]))
+    }
+
+    @Test
+    public void allJvmArgsIncludeMaxHeapSize() {
+        options.maxHeapSize = '1g'
+        options.jvmArgs('arg1')
+
+        assertThat(options.allJvmArgs, equalTo(['arg1', '-Xmx1g', fileEncodingProperty()]))
+    }
+
+    @Test
+    public void minHeapSizeIsUpdatedWhenSetUsingJvmArgs() {
+        options.minHeapSize = '64m'
+        options.jvmArgs('-Xms128m')
+
+        assertThat(options.minHeapSize, equalTo('128m'))
+
+        options.allJvmArgs = []
+
+        assertThat(options.minHeapSize, nullValue())
+
+        options.allJvmArgs = ['-Xms92m']
+
+        assertThat(options.minHeapSize, equalTo('92m'))
+    }
+
+    @Test
+    public void maxHeapSizeIsUpdatedWhenSetUsingJvmArgs() {
+        options.maxHeapSize = '1g'
+        options.jvmArgs('-Xmx1024m')
+
+        assertThat(options.maxHeapSize, equalTo('1024m'))
+
+        options.allJvmArgs = []
+
+        assertThat(options.maxHeapSize, nullValue())
+
+        options.allJvmArgs = ['-Xmx1g']
+
+        assertThat(options.maxHeapSize, equalTo('1g'))
+    }
+
+    @Test
+    public void allJvmArgsIncludeAssertionsEnabled() {
+        assertThat(options.allJvmArgs, equalTo([fileEncodingProperty()]))
+
+        options.enableAssertions = true
+
+        assertThat(options.allJvmArgs, equalTo([fileEncodingProperty(), '-ea']))
+    }
+
+    @Test
+    public void assertionsEnabledIsUpdatedWhenSetUsingJvmArgs() {
+        options.jvmArgs('-ea')
+        assertTrue(options.enableAssertions)
+        assertThat(options.jvmArgs, equalTo([]))
+
+        options.allJvmArgs = []
+        assertFalse(options.enableAssertions)
+
+        options.jvmArgs('-enableassertions')
+        assertTrue(options.enableAssertions)
+
+        options.allJvmArgs = ['-da']
+        assertFalse(options.enableAssertions)
+    }
+
+    @Test
+    public void allJvmArgsIncludeDebugArgs() {
+        assertThat(options.allJvmArgs, equalTo([fileEncodingProperty()]))
+
+        options.debug = true
+
+        assertThat(options.allJvmArgs, equalTo([fileEncodingProperty(), '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']))
+    }
+
+    @Test
+    public void debugIsUpdatedWhenSetUsingJvmArgs() {
+        options.jvmArgs('-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005')
+        assertTrue(options.debug)
+        assertThat(options.jvmArgs, equalTo([]))
+
+        options.allJvmArgs = []
+        assertFalse(options.debug)
+
+        options.jvmArgs = ['-Xdebug']
+        assertFalse(options.debug)
+        assertThat(options.jvmArgs, equalTo(['-Xdebug']))
+
+        options.jvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']
+        assertFalse(options.debug)
+        assertThat(options.jvmArgs, equalTo(['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']))
+
+        options.jvmArgs '-Xdebug'
+        assertTrue(options.debug)
+        assertThat(options.jvmArgs, equalTo([]))
+
+        options.jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=other']
+        assertFalse(options.debug)
+        assertThat(options.jvmArgs, equalTo(['-Xdebug', '-Xrunjdwp:transport=other']))
+
+        options.allJvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005', '-Xdebug']
+        assertTrue(options.debug)
+        assertThat(options.jvmArgs, equalTo([]))
+    }
+
+    @Test
+    public void canSetBootstrapClasspath() {
+        def bootstrapClasspath = [:] as FileCollection
+        options.bootstrapClasspath = bootstrapClasspath
+
+        assertThat(options.bootstrapClasspath.from, equalTo([bootstrapClasspath] as Set))
+    }
+
+    @Test
+    public void canAddToBootstrapClasspath() {
+        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
+        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options.bootstrapClasspath(files[0])
+        options.bootstrapClasspath(files[1])
+
+        assertThat(options.bootstrapClasspath.getFiles(), equalTo(files as Set))
+    }
+
+    @Test
+    public void allJvmArgsIncludeBootstrapClasspath() {
+        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
+        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options.bootstrapClasspath(files)
+
+        context.checking {
+            allowing(resolver).resolveFiles(['file.jar'])
+            will(returnValue([isEmpty: {false}, getAsPath: {'<classpath>'}] as FileCollection))
+        }
+
+        assertThat(options.allJvmArgs, equalTo(['-Xbootclasspath:' + files.join(System.properties['path.separator']), fileEncodingProperty()]))
+    }
+
+    @Test
+    public void canSetBootstrapClasspathViaAllJvmArgs() {
+        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
+        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options.bootstrapClasspath(files[0])
+
+        options.allJvmArgs = ['-Xbootclasspath:' + files[1]]
+
+        assertThat(options.bootstrapClasspath.files, equalTo([files[1]] as Set))
+    }
+
+    @Test
+    public void canCopyToTargetOptions() {
+        options.executable('executable')
+        options.jvmArgs('arg')
+        options.systemProperties(key: 12)
+        options.minHeapSize = '64m'
+        options.maxHeapSize = '1g'
+
+        JavaForkOptions target = context.mock(JavaForkOptions.class)
+        context.checking {
+            one(target).setExecutable('executable')
+            one(target).setJvmArgs(['arg'])
+            one(target).setSystemProperties(key: 12)
+            one(target).setMinHeapSize('64m')
+            one(target).setMaxHeapSize('1g')
+            one(target).setBootstrapClasspath(options.bootstrapClasspath)
+            one(target).setEnableAssertions(false)
+            one(target).setDebug(false)
+            ignoring(target)
+        }
+
+        options.copyTo(target)
+    }
+
+    private String fileEncodingProperty(String encoding = Charset.defaultCharset().name()) {
+        return "-Dfile.encoding=$encoding"
+    }
+}
+
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy
new file mode 100644
index 0000000..3eece02
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2012 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+/**
+ * Contract test for dynamic extension implementations.
+ *
+ * @param <T> The concrete implementation type
+ */
+abstract class ExtraPropertiesExtensionTest<T extends ExtraPropertiesExtension> extends Specification {
+    
+    T extension
+
+    def setup() {
+        extension = createExtension()
+    }
+
+    abstract T createExtension()
+
+    def "can register properties"() {
+        when:
+        extension.set("foo", "baz")
+
+        then:
+        extension.get("foo") == "baz"
+    }
+
+    def "cannot get or set properties that have not been added"() {
+        when:
+        extension.get("foo")
+
+        then:
+        thrown(ExtraPropertiesExtension.UnknownPropertyException)
+    }
+
+    def "can read/write properties using groovy notation"() {
+        given:
+        extension.foo = null
+
+        expect:
+        extension.foo == null
+
+        when:
+        extension.foo = "bar"
+
+        then:
+        extension.foo == "bar"
+    }
+
+    def "cannot read or write unregistered property using groovy syntax"() {
+        when:
+        extension.foo
+
+        then:
+        thrown(MissingPropertyException)
+
+        when:
+        extension.foo = "bar"
+
+        then:
+        extension.foo == "bar"
+    }
+
+    def "can call closure properties like methods"() {
+        given:
+        extension.m0 = { -> "m0" }
+        extension.m1 = { it }
+        extension.m2 = { String a1, String a2 -> "$a1 $a2" }
+        
+        expect:
+        extension.m0() == "m0"
+        extension.m1("foo") == "foo"
+        extension.m2("foo", "bar") == "foo bar"
+
+        when:
+        extension.m0(1)
+
+        then:
+        thrown(MissingMethodException)
+
+        and:
+        extension.m1() == null
+
+        when:
+        extension.m1(1, 2)
+
+        then:
+        thrown(MissingMethodException)
+
+        when:
+        extension.m2("a")
+
+        then:
+        thrown(MissingMethodException)
+        
+        when:
+        extension.m2(1, "a")
+        
+        then:
+        thrown(MissingMethodException)
+    }
+    
+    def "can get properties as a detached map"() {
+        given:
+        extension.p1 = 1
+        extension.p2 = 2
+        extension.p3 = 3
+        
+        and:
+        def props = extension.properties.sort()
+        
+        expect:
+        props == [p1: 1, p2: 2, p3: 3]
+        
+        when:
+        props.p1 = 10
+        
+        then:
+        extension.p1 == old(extension.p1)
+    }
+    
+    def "can detect if has a property"() {
+        given:
+        extension.foo = "bar"
+        
+        expect:
+        extension.has("foo")
+        
+        and:
+        !extension.has("other")
+        
+        when:
+        extension.foo = null
+        
+        then:
+        extension.has("foo")
+    }
+    
+    def "can resolve from owning context when in extension closure"() {
+        given:
+        Project project = ProjectBuilder.builder().build()
+                
+        project.configure(project) {
+            extensions.add("dynamic", extension)
+            version = "1.0"            
+            dynamic {
+                version = version // should resolve to project.version
+            }    
+        }
+        
+        expect:
+        project.dynamic.version == project.version
+    }
+
+    def "can resolve method from owning context when in extension closure"() {
+        given:
+        Project project = ProjectBuilder.builder().build()
+
+        project.task("custom").extensions.add("dynamic", extension)
+
+        project.custom {
+            dynamic {
+                doLast { // should resolve to task.doLast
+
+                }
+            }
+        }
+
+        expect:
+        notThrown(Exception)
+    }
+    
+    def "can use [] notation to get and set"() {
+        when:
+        extension["foo"]
+        
+        then:
+        thrown(MissingPropertyException)
+
+        when:
+        extension["foo"] = "bar"
+
+        then:
+        extension["foo"] == "bar"
+    }
+
+    def "cannot assign to properties"() {
+        when:
+        extension.properties = [:]
+        
+        then:
+        thrown(MissingPropertyException)
+    }
+    
+    def "does not have properties property"() {
+        expect:
+        !extension.has("properties")
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
deleted file mode 100644
index 750f7b9..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
+++ /dev/null
@@ -1,35 +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.api.specs
-
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter
- */
-class SpecsTest extends Specification {
-    def filterIterable() {
-        List list = ['a', 'b', 'c']
-
-        expect:
-        ['a', 'c'] as Set == Specs.filterIterable(list, Specs.convertClosureToSpec{ item -> item != 'b' })
-    }
-
-    def filterIterableWithNullReturningSpec() {
-        expect:
-        [] as Set == Specs.filterIterable(['a'], Specs.convertClosureToSpec { item -> println item })
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
deleted file mode 100644
index b2a9b68..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
+++ /dev/null
@@ -1,45 +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.tasks;
-
-import org.gradle.api.internal.ConventionAwareHelper;
-import org.gradle.api.internal.ConventionMapping;
-import org.gradle.api.internal.ConventionTask;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractConventionTaskTest extends AbstractTaskTest {
-
-    public abstract ConventionTask getTask();
-    
-    @Test
-    public void testConventionAwareness() {
-        ConventionTask task = getTask();
-        assertThat(task.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
-        assertThat(task.getConventionMapping().getConvention(), sameInstance(getProject().getConvention()));
-
-        ConventionMapping conventionMapping = context.mock(ConventionMapping.class);
-        task.setConventionMapping(conventionMapping);
-        assertThat(task.getConventionMapping(), sameInstance(conventionMapping));
-    }
-}
-
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java
index da07e7d..0e4a369 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java
@@ -50,7 +50,7 @@ public class AbstractCopyTaskTest extends AbstractTaskTest {
 
     @Test
     public void usesDefaultSourceWhenNoSourceHasBeenSpecified() {
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(task.action).hasSource();
             will(returnValue(false));
 
@@ -61,7 +61,7 @@ public class AbstractCopyTaskTest extends AbstractTaskTest {
     @Test
     public void doesNotUseDefaultSourceWhenSourceHasBeenSpecifiedOnSpec() {
         final FileTree source = context.mock(FileTree.class, "source");
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(task.action).hasSource();
             will(returnValue(true));
             one(task.action).getAllSource();
@@ -70,6 +70,7 @@ public class AbstractCopyTaskTest extends AbstractTaskTest {
         assertThat(task.getSource(), sameInstance((FileCollection) source));
     }
 
+
     @Test
     public void copySpecMethodsDelegateToMainSpecOfCopyAction() {
         context.checking(new Expectations() {{
@@ -91,6 +92,7 @@ public class AbstractCopyTaskTest extends AbstractTaskTest {
         }
 
         @Override
+        @SuppressWarnings("deprecation")
         public FileCollection getDefaultSource() {
             return defaultSource;
         }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
deleted file mode 100644
index f1bb7e3..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
+++ /dev/null
@@ -1,375 +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.api.tasks;
-
-
-import java.util.concurrent.atomic.AtomicBoolean
-import org.gradle.api.Action
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.internal.AbstractTask
-import org.gradle.api.internal.AsmBackedClassGenerator
-import org.gradle.api.internal.project.AbstractProject
-import org.gradle.api.internal.project.DefaultProject
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
-import org.gradle.api.internal.project.taskfactory.ITaskFactory
-import org.gradle.api.internal.project.taskfactory.TaskFactory
-import org.gradle.api.internal.tasks.TaskExecuter
-import org.gradle.api.internal.tasks.TaskStateInternal
-import org.gradle.api.logging.LogLevel
-import org.gradle.api.specs.Spec
-import org.gradle.util.GUtil
-import org.gradle.util.HelperUtil
-import org.gradle.util.Matchers
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-import spock.lang.Specification
-import static org.junit.Assert.assertFalse
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractSpockTaskTest extends Specification {
-    public static final String TEST_TASK_NAME = "taskname"
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder()
-
-    private AbstractProject project = HelperUtil.createRootProject()
-
-    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()))
-
-    public abstract AbstractTask getTask();
-
-    public <T extends AbstractTask> T createTask(Class<T> type) {
-        return createTask(type, project, TEST_TASK_NAME);
-    }
-
-    public Task createTask(Project project, String name) {
-        return createTask(getTask().getClass(), project, name);
-    }
-
-    public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
-        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
-                GUtil.map(Task.TASK_TYPE, type,
-                        Task.TASK_NAME, name))
-        assert type.isAssignableFrom(task.getClass())
-        return type.cast(task);
-    }
-
-    def testTask() {
-        expect:
-        getTask().isEnabled()
-        TEST_TASK_NAME ==  getTask().getName()
-        getTask().getDescription() == null
-        project.is( getTask().getProject())
-        getTask().getStandardOutputCapture() != null
-        new HashMap() ==  getTask().getAdditionalProperties()
-        getTask().getInputs() != null
-        getTask().getOutputs() != null
-        getTask().getOnlyIf() != null
-        getTask().getOnlyIf().isSatisfiedBy(getTask())
-    }
-
-    def testPath() {
-        DefaultProject rootProject = HelperUtil.createRootProject();
-        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
-        childProject.getProjectDir().mkdirs();
-        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
-        childchildProject.getProjectDir().mkdirs();
-
-        when:
-        Task task = createTask(rootProject, TEST_TASK_NAME);
-
-        then:
-        Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
-
-        when:
-        task = createTask(childProject, TEST_TASK_NAME);
-
-        then:
-        Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
-
-        when:
-        task = createTask(childchildProject, TEST_TASK_NAME);
-
-        then:
-        Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
-    }
-
-    def testDependsOn() {
-        Task dependsOnTask = createTask(project, "somename");
-        Task task = createTask(project, TEST_TASK_NAME);
-        project.getTasks().add("path1");
-        project.getTasks().add("path2");
-
-        when:
-        task.dependsOn(Project.PATH_SEPARATOR + "path1");
-
-        then:
-        Matchers.dependsOn("path1").matches(task)
-
-        when:
-        task.dependsOn("path2", dependsOnTask);
-
-        then:
-        Matchers.dependsOn("path1", "path2", "somename").matches(task)
-    }
-
-    def testToString() {
-        "task '" + getTask().getPath() + "'" ==  getTask().toString()
-    }
-
-    def testDoFirst() {
-        when:
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
-
-        then:
-        int actionSizeBefore = getTask().getActions().size();
-        getTask().is( getTask().doFirst(action2))
-        actionSizeBefore + 1 ==  getTask().getActions().size()
-        action2 ==  getTask().getActions().get(0)
-        getTask().is( getTask().doFirst(action1))
-        action1 ==  getTask().getActions().get(0)
-    }
-
-    def testDoLast() {
-        when:
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
-
-        then:
-        int actionSizeBefore = getTask().getActions().size();
-        getTask().is( getTask().doLast(action1))
-        actionSizeBefore + 1 ==  getTask().getActions().size()
-        action1 ==  getTask().getActions().get(getTask().getActions().size() - 1)
-        getTask().is( getTask().doLast(action2))
-        action2 ==  getTask().getActions().get(getTask().getActions().size() - 1)
-    }
-
-    def testDeleteAllActions() {
-        when:
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
-        getTask().doLast(action1);
-        getTask().doLast(action2);
-
-        then:
-        getTask().is( getTask().deleteAllActions())
-        new ArrayList() ==  getTask().getActions()
-    }
-
-    def testAddActionWithNull() {
-        when:
-        getTask().doLast((Closure) null)
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def testAddActionsWithClosures() {
-        when:
-        GroovyTaskTestHelper.checkAddActionsWithClosures(getTask());
-
-        then:
-        true
-    }
-
-    def testExecuteDelegatesToTaskExecuter() {
-        final AbstractTask task = getTask()
-        TaskExecuter executer = Mock()
-        task.setExecuter(executer);
-
-        when:
-        task.execute()
-
-        then:
-        1 * executer.execute(task, _ as TaskStateInternal)
-
-    }
-
-    def testConfigure() {
-        when:
-        getTask().setActions(new ArrayList());
-
-        then:
-        GroovyTaskTestHelper.checkConfigure(getTask());
-    }
-
-    public AbstractProject getProject() {
-        return project;
-    }
-
-    public void setProject(AbstractProject project) {
-        this.project = project;
-    }
-
-    def disableStandardOutCapture() {
-        when:
-        getTask().disableStandardOutputCapture();
-
-        then:
-        assertFalse(getTask().getLogging().isStandardOutputCaptureEnabled());
-    }
-
-    def captureStandardOut() {
-        when:
-        getTask().captureStandardOutput(LogLevel.DEBUG);
-
-        then:
-        getTask().getLogging().isStandardOutputCaptureEnabled()
-        LogLevel.DEBUG ==  getTask().getLogging().getStandardOutputCaptureLevel()
-    }
-
-    def setGetDescription() {
-        when:
-        String testDescription = "testDescription";
-        getTask().setDescription(testDescription);
-
-        then:
-        testDescription ==  getTask().getDescription()
-    }
-
-    def canSpecifyOnlyIfPredicateUsingClosure() {
-        AbstractTask task = getTask();
-
-        expect:
-        task.getOnlyIf().isSatisfiedBy(task)
-
-        when:
-        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-    }
-
-    def canSpecifyOnlyIfPredicateUsingSpec() {
-        final Spec<Task> spec = Mock()
-        final AbstractTask task = getTask();
-
-        expect:
-        task.getOnlyIf().isSatisfiedBy(task)
-
-        when:
-        spec.isSatisfiedBy(task) >> false
-        task.onlyIf(spec);
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-    }
-
-    def onlyIfPredicateIsTrueWhenTaskIsEnabledAndAllPredicatesAreTrue() {
-        final AtomicBoolean condition1 = new AtomicBoolean(true);
-        final AtomicBoolean condition2 = new AtomicBoolean(true);
-
-        AbstractTask task = getTask();
-        task.onlyIf {
-            condition1.get()
-        }
-        task.onlyIf {
-            condition2.get()
-        }
-
-        expect:
-        task.getOnlyIf().isSatisfiedBy(task)
-
-        when:
-        task.setEnabled(false);
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        when:
-        task.setEnabled(true);
-        condition1.set(false);
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        when:
-        condition1.set(true);
-        condition2.set(false);
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        when:
-        condition2.set(true);
-
-        then:
-        task.getOnlyIf().isSatisfiedBy(task)
-    }
-
-    def canReplaceOnlyIfSpec() {
-        final AtomicBoolean condition1 = new AtomicBoolean(true);
-        AbstractTask task = getTask();
-        task.onlyIf(Mock(Spec))
-        task.setOnlyIf {
-            return condition1.get();
-        }
-
-        expect:
-        task.getOnlyIf().isSatisfiedBy(task)
-
-        when:
-        task.setEnabled(false);
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        when:
-        task.setEnabled(true);
-        condition1.set(false);
-
-        then:
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        when:
-        condition1.set(true);
-
-        then:
-        task.getOnlyIf().isSatisfiedBy(task)
-    }
-
-    def testDependentTaskDidWork() {
-        Task task1 = Mock()
-        Task task2 = Mock()
-        TaskDependency dependencyMock = Mock()
-        getTask().dependsOn(dependencyMock)
-        dependencyMock.getDependencies(getTask()) >> [task1, task2] 
-
-        when:
-        task1.getDidWork() >> false
-        task2.getDidWork() >>> [false, true]
-
-
-        then:
-        !getTask().dependsOnTaskDidWork()
-        getTask().dependsOnTaskDidWork()
-    }
-
-    public static Action<Task> createTaskAction() {
-        return new Action<Task>() {
-            public void execute(Task task) {
-
-            }
-        };
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java
deleted file mode 100644
index 3776cf5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java
+++ /dev/null
@@ -1,341 +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.api.tasks;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.internal.AbstractTask;
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.gradle.api.internal.project.AbstractProject;
-import org.gradle.api.internal.project.DefaultProject;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.internal.project.taskfactory.TaskFactory;
-import org.gradle.api.internal.tasks.TaskExecuter;
-import org.gradle.api.internal.tasks.TaskStateInternal;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.specs.Spec;
-import org.gradle.util.*;
-import org.jmock.Expectations;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static org.gradle.util.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractTaskTest {
-    public static final String TEST_TASK_NAME = "taskname";
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-
-    private AbstractProject project;
-
-    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()));
-
-    @Before
-    public void setUp() {
-        project = HelperUtil.createRootProject();
-    }
-
-    public abstract AbstractTask getTask();
-
-    public <T extends AbstractTask> T createTask(Class<T> type) {
-        return createTask(type, project, TEST_TASK_NAME);
-    }
-
-    public Task createTask(Project project, String name) {
-        return createTask(getTask().getClass(), project, name);
-    }
-
-    public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
-        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
-                GUtil.map(Task.TASK_TYPE, type,
-                        Task.TASK_NAME, name));
-        assertTrue(type.isAssignableFrom(task.getClass()));
-        return type.cast(task);
-    }
-
-    @Test
-    public void testTask() {
-        assertTrue(getTask().isEnabled());
-        assertEquals(TEST_TASK_NAME, getTask().getName());
-        assertNull(getTask().getDescription());
-        assertSame(project, getTask().getProject());
-        assertNotNull(getTask().getStandardOutputCapture());
-        assertEquals(new HashMap(), getTask().getAdditionalProperties());
-        assertNotNull(getTask().getInputs());
-        assertNotNull(getTask().getOutputs());
-        assertNotNull(getTask().getOnlyIf());
-        assertTrue(getTask().getOnlyIf().isSatisfiedBy(getTask()));
-    }
-
-    @Test
-    public void testPath() {
-        DefaultProject rootProject = HelperUtil.createRootProject();
-        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
-        childProject.getProjectDir().mkdirs();
-        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
-        childchildProject.getProjectDir().mkdirs();
-
-        Task task = createTask(rootProject, TEST_TASK_NAME);
-        assertEquals(Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
-        task = createTask(childProject, TEST_TASK_NAME);
-        assertEquals(Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
-        task = createTask(childchildProject, TEST_TASK_NAME);
-        assertEquals(Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
-    }
-
-    @Test
-    public void testDependsOn() {
-        Task dependsOnTask = createTask(project, "somename");
-        Task task = createTask(project, TEST_TASK_NAME);
-        project.getTasks().add("path1");
-        project.getTasks().add("path2");
-
-        task.dependsOn(Project.PATH_SEPARATOR + "path1");
-        assertThat(task, dependsOn("path1"));
-        task.dependsOn("path2", dependsOnTask);
-        assertThat(task, dependsOn("path1", "path2", "somename"));
-    }
-
-    @Test
-    public void testToString() {
-        assertEquals("task '" + getTask().getPath() + "'", getTask().toString());
-    }
-
-    @Test
-    public void testDoFirst() {
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
-        int actionSizeBefore = getTask().getActions().size();
-        assertSame(getTask(), getTask().doFirst(action2));
-        assertEquals(actionSizeBefore + 1, getTask().getActions().size());
-        assertEquals(action2, getTask().getActions().get(0));
-        assertSame(getTask(), getTask().doFirst(action1));
-        assertEquals(action1, getTask().getActions().get(0));
-    }
-
-    @Test
-    public void testDoLast() {
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
-        int actionSizeBefore = getTask().getActions().size();
-        assertSame(getTask(), getTask().doLast(action1));
-        assertEquals(actionSizeBefore + 1, getTask().getActions().size());
-        assertEquals(action1, getTask().getActions().get(getTask().getActions().size() - 1));
-        assertSame(getTask(), getTask().doLast(action2));
-        assertEquals(action2, getTask().getActions().get(getTask().getActions().size() - 1));
-    }
-
-    @Test
-    public void testDeleteAllActions() {
-        Action<Task> action1 = createTaskAction();
-        Action<Task> action2 = createTaskAction();
-        getTask().doLast(action1);
-        getTask().doLast(action2);
-        assertSame(getTask(), getTask().deleteAllActions());
-        assertEquals(new ArrayList(), getTask().getActions());
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void testAddActionWithNull() {
-        getTask().doLast((Closure) null);
-    }
-
-    @Test
-    public void testAddActionsWithClosures() {
-        GroovyTaskTestHelper.checkAddActionsWithClosures(getTask());
-    }
-
-    @Test
-    public void testExecuteDelegatesToTaskExecuter() {
-        final AbstractTask task = getTask();
-
-        final TaskExecuter executer = context.mock(TaskExecuter.class);
-        task.setExecuter(executer);
-
-        context.checking(new Expectations(){{
-            one(executer).execute(with(sameInstance(task)), with(notNullValue(TaskStateInternal.class)));
-        }});
-
-        task.execute();
-    }
-
-    @Test
-    public void testConfigure() {
-        getTask().setActions(new ArrayList());
-        GroovyTaskTestHelper.checkConfigure(getTask());
-    }
-
-    public AbstractProject getProject() {
-        return project;
-    }
-
-    public void setProject(AbstractProject project) {
-        this.project = project;
-    }
-
-    @Test
-    public void disableStandardOutCapture() {
-        getTask().disableStandardOutputCapture();
-        assertFalse(getTask().getLogging().isStandardOutputCaptureEnabled());
-    }
-
-    @Test
-    public void captureStandardOut() {
-        getTask().captureStandardOutput(LogLevel.DEBUG);
-        assertTrue(getTask().getLogging().isStandardOutputCaptureEnabled());
-        assertEquals(LogLevel.DEBUG, getTask().getLogging().getStandardOutputCaptureLevel());
-    }
-
-    @Test
-    public void setGetDescription() {
-        String testDescription = "testDescription";
-        getTask().setDescription(testDescription);
-        assertEquals(testDescription, getTask().getDescription());
-    }
-
-    @Test
-    public void canSpecifyOnlyIfPredicateUsingClosure() {
-        AbstractTask task = getTask();
-        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
-
-        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-    }
-
-    @Test
-    public void canSpecifyOnlyIfPredicateUsingSpec() {
-        final Spec<Task> spec = context.mock(Spec.class);
-
-        final AbstractTask task = getTask();
-        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
-
-        context.checking(new Expectations() {{
-            allowing(spec).isSatisfiedBy(task);
-            will(returnValue(false));
-        }});
-
-        task.onlyIf(spec);
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-    }
-
-    @Test
-    public void onlyIfPredicateIsTrueWhenTaskIsEnabledAndAllPredicatesAreTrue() {
-        final AtomicBoolean condition1 = new AtomicBoolean(true);
-        final AtomicBoolean condition2 = new AtomicBoolean(true);
-
-        AbstractTask task = getTask();
-        task.onlyIf(new Spec<Task>() {
-            public boolean isSatisfiedBy(Task element) {
-                return condition1.get();
-            }
-        });
-        task.onlyIf(new Spec<Task>() {
-            public boolean isSatisfiedBy(Task element) {
-                return condition2.get();
-            }
-        });
-
-        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
-
-        task.setEnabled(false);
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        task.setEnabled(true);
-        condition1.set(false);
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        condition1.set(true);
-        condition2.set(false);
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        condition2.set(true);
-        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
-    }
-
-    @Test
-    public void canReplaceOnlyIfSpec() {
-        final AtomicBoolean condition1 = new AtomicBoolean(true);
-        AbstractTask task = getTask();
-        task.onlyIf(context.mock(Spec.class, "spec1"));
-        task.setOnlyIf(new Spec<Task>() {
-            public boolean isSatisfiedBy(Task element) {
-                return condition1.get();
-            }
-        });
-
-        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
-
-        task.setEnabled(false);
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        task.setEnabled(true);
-        condition1.set(false);
-        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
-
-        condition1.set(true);
-        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
-    }
-
-    @Test
-    public void testDependentTaskDidWork() {
-        final Task task1 = context.mock(Task.class, "task1");
-        final Task task2 = context.mock(Task.class, "task2");
-        final TaskDependency dependencyMock = context.mock(TaskDependency.class);
-        getTask().dependsOn(dependencyMock);
-        context.checking(new Expectations() {{
-            allowing(dependencyMock).getDependencies(getTask()); will(returnValue(WrapUtil.toSet(task1, task2)));
-
-            exactly(2).of(task1).getDidWork();
-            will(returnValue(false));
-
-            exactly(2).of(task2).getDidWork();
-            will(onConsecutiveCalls(returnValue(false), returnValue(true)));
-        }});
-
-        assertFalse(getTask().dependsOnTaskDidWork());
-
-        assertTrue(getTask().dependsOnTaskDidWork());
-    }
-
-    public static Action<Task> createTaskAction() {
-        return new Action<Task>() {
-            public void execute(Task task) {
-
-            }
-        };
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/GroovyTaskTestHelper.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/GroovyTaskTestHelper.groovy
deleted file mode 100644
index 62fb0c5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/GroovyTaskTestHelper.groovy
+++ /dev/null
@@ -1,45 +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.tasks
-
-import org.gradle.api.Task
-import org.gradle.api.internal.AbstractTask
-import static org.junit.Assert.*
-
-/**
- * @author Hans Dockter
- */
-class GroovyTaskTestHelper {
-    public static void checkAddActionsWithClosures(AbstractTask task) {
-        task.deleteAllActions();
-        boolean action1Called = false
-        Closure action1 = {action1Called = true}
-        boolean action2Called = false
-        Closure action2 = {Task t -> action2Called = true}
-        task.doFirst(action1)
-        task.doLast(action2)
-        assertEquals([action1, action2], task.actions.collect { it.closure })
-    }
-
-    public static void checkConfigure(AbstractTask task) {
-        Closure action1 = { Task t -> }
-        assertSame(task, task.configure {
-            doFirst(action1)
-        });
-        assertEquals(1, task.actions.size())
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
index 21386cc..6e9e2eb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
@@ -16,24 +16,29 @@
 
 package org.gradle.api.tasks;
 
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.Configuration;
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.PublishArtifactSet;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.util.ConfigureUtil;
 import org.gradle.util.HelperUtil;
-import static org.gradle.util.WrapUtil.toList;
-import static org.hamcrest.Matchers.*;
 import org.jmock.Expectations;
+import org.jmock.api.Invocation;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import static org.junit.Assert.*;
+import org.jmock.lib.action.CustomAction;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
 /**
  * @author Hans Dockter
  */
@@ -43,20 +48,16 @@ public class UploadTest extends AbstractTaskTest {
 
     private JUnit4Mockery context = new JUnit4Mockery();
     private RepositoryHandler repositoriesMock;
-    private DependencyResolver repositoryDummy;
-    private Configuration configurationMock;
+    private ArtifactPublisher artifactPublisherMock;
+    private ConfigurationInternal configurationMock;
 
     @Before public void setUp() {
         super.setUp();
         upload = createTask(Upload.class);
         repositoriesMock = context.mock(RepositoryHandler.class);
-        repositoryDummy = context.mock(DependencyResolver.class);
+        artifactPublisherMock = context.mock(ArtifactPublisher.class);
 
-        context.checking(new Expectations(){{
-            allowing(repositoriesMock).getResolvers();
-            will(returnValue(toList(repositoryDummy)));
-        }});
-        configurationMock = context.mock(Configuration.class);
+        configurationMock = context.mock(ConfigurationInternal.class);
     }
 
     public AbstractTask getTask() {
@@ -74,9 +75,9 @@ public class UploadTest extends AbstractTaskTest {
         upload.setUploadDescriptor(true);
         upload.setDescriptorDestination(descriptorDestination);
         upload.setConfiguration(configurationMock);
-        upload.setRepositories(repositoriesMock);
+        upload.setArtifactPublisher(artifactPublisherMock);
         context.checking(new Expectations() {{
-            one(configurationMock).publish(toList(repositoryDummy), descriptorDestination);
+            one(artifactPublisherMock).publish(configurationMock, descriptorDestination);
         }});
         upload.upload();
     }
@@ -85,9 +86,9 @@ public class UploadTest extends AbstractTaskTest {
         upload.setUploadDescriptor(false);
         upload.setDescriptorDestination(new File("somePath"));
         upload.setConfiguration(configurationMock);
-        upload.setRepositories(repositoriesMock);
+        upload.setArtifactPublisher(artifactPublisherMock);
         context.checking(new Expectations() {{
-            one(configurationMock).publish(toList(repositoryDummy), null);
+            one(artifactPublisherMock).publish(configurationMock, null);
         }});
         upload.upload();
     }
@@ -96,6 +97,12 @@ public class UploadTest extends AbstractTaskTest {
         upload.setRepositories(repositoriesMock);
 
         context.checking(new Expectations(){{
+            one(repositoriesMock).configure(with(any(Closure.class)));
+            will(new CustomAction("execution configure") { 
+                public Object invoke(Invocation invocation) {
+                    return ConfigureUtil.configure((Closure)invocation.getParameter(0), invocation.getInvokedObject(), false);
+                }
+            });
             one(repositoriesMock).mavenCentral();
         }});
 
@@ -107,9 +114,12 @@ public class UploadTest extends AbstractTaskTest {
 
         upload.setConfiguration(configurationMock);
 
+        final PublishArtifactSet artifacts = context.mock(PublishArtifactSet.class);
         final FileCollection files = context.mock(FileCollection.class);
         context.checking(new Expectations(){{
-            one(configurationMock).getAllArtifactFiles();
+            one(configurationMock).getAllArtifacts();
+            will(returnValue(artifacts));
+            one(artifacts).getFiles();
             will(returnValue(files));
         }});
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java
index 122aae6..24a613b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java
@@ -17,27 +17,28 @@ package org.gradle.api.tasks.diagnostics;
 
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationsProvider;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.diagnostics.internal.AsciiReportRenderer;
 import org.gradle.api.tasks.diagnostics.internal.DependencyReportRenderer;
-import org.gradle.util.WrapUtil;
 import org.gradle.util.HelperUtil;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.nullValue;
+import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.assertThat;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
 
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
 @RunWith(JMock.class)
 public class DependencyReportTaskTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
@@ -67,7 +68,7 @@ public class DependencyReportTaskTest {
 
     @Test
     public void passesEachProjectConfigurationToRenderer() throws IOException {
-        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+        final ConfigurationsProvider configurationContainer = context.mock(ConfigurationsProvider.class);
         final Configuration configuration1 = context.mock(Configuration.class, "Configuration1");
         final Configuration configuration2 = context.mock(Configuration.class, "Configuration2");
         context.checking(new Expectations() {{
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
index dd2728a..14cdae5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
@@ -87,10 +87,13 @@ public class TaskReportTaskTest {
             allowing(project).getDefaultTasks();
             will(returnValue(testDefaultTasks));
 
-            one(taskContainer).iterator();
+            allowing(taskContainer).size();
+            will(returnValue(4));
+
+            allowing(taskContainer).iterator();
             will(returnIterator(toLinkedSet(task2, task3, task4, task1)));
 
-            one(implicitTasks).iterator();
+            allowing(implicitTasks).iterator();
             will(returnIterator(toLinkedSet()));
 
             allowing(taskContainer).getRules();
@@ -139,10 +142,13 @@ public class TaskReportTaskTest {
             allowing(project).getDefaultTasks();
             will(returnValue(defaultTasks));
 
-            one(taskContainer).iterator();
+            allowing(taskContainer).size();
+            will(returnValue(0));
+
+            allowing(taskContainer).iterator();
             will(returnIterator(toLinkedSet()));
 
-            one(implicitTasks).iterator();
+            allowing(implicitTasks).iterator();
             will(returnIterator(toLinkedSet()));
 
             one(taskContainer).getRules();
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy
index 25a3c87..674325a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy
@@ -43,10 +43,10 @@ class AsciiReportRendererTest extends Specification {
 
     def writesConfigurationHeader() {
         Configuration configuration1 = Mock()
-        _ * configuration1.getName() >> 'config1'
-        _ * configuration1.getDescription() >> 'description'
+        configuration1.getName() >> 'config1'
+        configuration1.getDescription() >> 'description'
         Configuration configuration2 = Mock()
-        _ * configuration2.getName() >> 'config2'
+        configuration2.getName() >> 'config2'
 
         when:
         renderer.startConfiguration(configuration1);
@@ -70,23 +70,23 @@ class AsciiReportRendererTest extends Specification {
         ResolvedDependency dep2 = Mock()
         ResolvedDependency dep21 = Mock()
         ResolvedDependency dep22 = Mock()
-        _ * configuration.name >> 'config'
-        _ * resolvedConfig.getFirstLevelModuleDependencies() >> {[dep1, dep2] as LinkedHashSet}
-        _ * dep1.getChildren() >> {[dep11] as LinkedHashSet}
-        _ * dep1.getName() >> 'dep1'
-        _ * dep1.getConfiguration() >> 'config1'
-        _ * dep11.getChildren() >> {[] as LinkedHashSet}
-        _ * dep11.getName() >> 'dep1.1'
-        _ * dep11.getConfiguration() >> 'config1.1'
-        _ * dep2.getChildren() >> {[dep21, dep22] as LinkedHashSet}
-        _ * dep2.getName() >> 'dep2'
-        _ * dep2.getConfiguration() >> 'config2'
-        _ * dep21.getChildren() >> {[] as LinkedHashSet}
-        _ * dep21.getName() >> 'dep2.1'
-        _ * dep21.getConfiguration() >> 'config2.1'
-        _ * dep22.getChildren() >> {[] as LinkedHashSet}
-        _ * dep22.getName() >> 'dep2.2'
-        _ * dep22.getConfiguration() >> 'config2.2'
+        configuration.name >> 'config'
+        resolvedConfig.getFirstLevelModuleDependencies() >> {[dep1, dep2] as LinkedHashSet}
+        dep1.getChildren() >> {[dep11] as LinkedHashSet}
+        dep1.getName() >> 'dep1'
+        dep1.getConfiguration() >> 'config1'
+        dep11.getChildren() >> {[] as LinkedHashSet}
+        dep11.getName() >> 'dep1.1'
+        dep11.getConfiguration() >> 'config1.1'
+        dep2.getChildren() >> {[dep21, dep22] as LinkedHashSet}
+        dep2.getName() >> 'dep2'
+        dep2.getConfiguration() >> 'config2'
+        dep21.getChildren() >> {[] as LinkedHashSet}
+        dep21.getName() >> 'dep2.1'
+        dep21.getConfiguration() >> 'config2.1'
+        dep22.getChildren() >> {[] as LinkedHashSet}
+        dep22.getName() >> 'dep2.2'
+        dep22.getConfiguration() >> 'config2.2'
 
         when:
         renderer.startConfiguration(configuration)
@@ -105,7 +105,7 @@ class AsciiReportRendererTest extends Specification {
 
     def rendersDependencyTreeForEmptyConfiguration() {
         ResolvedConfiguration configuration = Mock()
-        _ * configuration.getFirstLevelModuleDependencies() >> {[] as Set}
+        configuration.getFirstLevelModuleDependencies() >> {[] as Set}
 
         when:
         renderer.render(configuration)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
index c1ab927..7a2cb2c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
@@ -19,7 +19,6 @@ import org.gradle.logging.internal.TestStyledTextOutput;
 import org.junit.Test;
 
 import static org.gradle.util.Matchers.containsLine;
-import static org.junit.Assert.assertThat;
 
 public class PropertyReportRendererTest {
     private final TestStyledTextOutput out = new TestStyledTextOutput();
@@ -31,6 +30,6 @@ public class PropertyReportRendererTest {
     public void writesProperty() {
         renderer.addProperty("prop", "value");
 
-        assertThat(out.toString(), containsLine("prop: value"));
+        assert containsLine(out.toString(), "prop: value");
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy
index 6a18b53..f3cc046 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskDetailsFactoryTest.groovy
@@ -26,14 +26,14 @@ class TaskDetailsFactoryTest extends TaskModelSpecification {
     TaskDetailsFactory factory
 
     def setup() {
-        _ * project.allprojects >> ([project, subproject] as Set)
+        project.allprojects >> [project, subproject]
         factory = new TaskDetailsFactory(project)
     }
     
     def createsDetailsForTaskInMainProject() {
-        _ * task.project >> project
-        _ * task.path >> ':path'
-        _ * project.relativeProjectPath(':path') >> 'task'
+        task.project >> project
+        task.path >> ':path'
+        project.relativeProjectPath(':path') >> 'task'
 
         expect:
         def details = factory.create(task)
@@ -41,9 +41,9 @@ class TaskDetailsFactoryTest extends TaskModelSpecification {
     }
 
     def createsDetailsForTaskInSubProject() {
-        _ * task.project >> subproject
-        _ * task.path >> ':sub:path'
-        _ * project.relativeProjectPath(':sub:path') >> 'sub:task'
+        task.project >> subproject
+        task.path >> ':sub:path'
+        project.relativeProjectPath(':sub:path') >> 'sub:task'
 
         expect:
         def details = factory.create(task)
@@ -52,8 +52,8 @@ class TaskDetailsFactoryTest extends TaskModelSpecification {
 
     def createsDetailsForTaskInOtherProject() {
         Project other = Mock()
-        _ * task.project >> other
-        _ * task.path >> ':other:task'
+        task.project >> other
+        task.path >> ':other:task'
 
         expect:
         def details = factory.create(task)
@@ -61,9 +61,9 @@ class TaskDetailsFactoryTest extends TaskModelSpecification {
     }
 
     def providesValuesForOtherProperties() {
-        _ * task.project >> project
-        _ * task.name >> 'task'
-        _ * task.description >> 'description'
+        task.project >> project
+        task.name >> 'task'
+        task.description >> 'description'
 
         expect:
         def details = factory.create(task)
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
new file mode 100644
index 0000000..34c597b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * 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.diagnostics.internal;
+
+
+import org.gradle.api.Project
+import org.gradle.logging.internal.StreamingStyledTextOutput
+import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.util.TemporaryFolder
+import org.jmock.Expectations
+import org.jmock.integration.junit4.JMock
+import org.jmock.integration.junit4.JUnit4Mockery
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.instanceOf
+import static org.hamcrest.Matchers.nullValue
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.assertTrue
+
+ at RunWith(JMock.class)
+public class TextReportRendererTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public final TemporaryFolder testDir = new TemporaryFolder();
+    private final TextReportRenderer renderer = new TextReportRenderer();
+
+    @Test
+    public void writesReportToAFile() throws IOException {
+        File outFile = new File(testDir.getDir(), "report.txt");
+        renderer.setOutputFile(outFile);
+        assertThat(renderer.getTextOutput(), instanceOf(StreamingStyledTextOutput.class));
+
+        renderer.complete();
+
+        assertTrue(outFile.isFile());
+        assertThat(renderer.getTextOutput(), nullValue());
+    }
+
+    @Test
+    public void writeRootProjectHeader() throws IOException {
+        final Project project = context.mock(Project.class);
+        TestStyledTextOutput textOutput = new TestStyledTextOutput();
+
+        context.checking(new Expectations() {{
+            allowing(project).getRootProject();
+            will(returnValue(project));
+            allowing(project).getDescription();
+            will(returnValue(null));
+        }});
+
+        renderer.setOutput(textOutput);
+        renderer.startProject(project);
+        renderer.completeProject(project);
+        renderer.complete();
+
+        assert containsLine(textOutput.toString(), "Root project");
+    }
+
+    @Test
+    public void writeSubProjectHeader() throws IOException {
+        final Project project = context.mock(Project.class);
+        TestStyledTextOutput textOutput = new TestStyledTextOutput();
+
+        context.checking(new Expectations() {{
+            allowing(project).getRootProject();
+            will(returnValue(context.mock(Project.class, "root")));
+            allowing(project).getDescription();
+            will(returnValue(null));
+            allowing(project).getPath();
+            will(returnValue("<path>"));
+        }});
+
+        renderer.setOutput(textOutput);
+        renderer.startProject(project);
+        renderer.completeProject(project);
+        renderer.complete();
+
+        assert containsLine(textOutput.toString(), "Project <path>");
+    }
+
+    @Test
+    public void includesProjectDescriptionInHeader() throws IOException {
+        final Project project = context.mock(Project.class);
+        TestStyledTextOutput textOutput = new TestStyledTextOutput();
+
+        context.checking(new Expectations() {{
+            allowing(project).getRootProject();
+            will(returnValue(project));
+            allowing(project).getDescription();
+            will(returnValue("this is the root project"));
+        }});
+
+        renderer.setOutput(textOutput);
+        renderer.startProject(project);
+        renderer.completeProject(project);
+        renderer.complete();
+
+        assert containsLine(textOutput.toString(), "Root project - this is the root project");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.java
deleted file mode 100644
index 595146c..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.java
+++ /dev/null
@@ -1,118 +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.api.tasks.diagnostics.internal;
-
-import org.gradle.api.Project;
-import org.gradle.logging.internal.StreamingStyledTextOutput;
-import org.gradle.logging.internal.TestStyledTextOutput;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.gradle.util.Matchers.containsLine;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
- at RunWith(JMock.class)
-public class TextReportRendererTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    @Rule
-    public final TemporaryFolder testDir = new TemporaryFolder();
-    private final TextReportRenderer renderer = new TextReportRenderer();
-
-    @Test
-    public void writesReportToAFile() throws IOException {
-        File outFile = new File(testDir.getDir(), "report.txt");
-        renderer.setOutputFile(outFile);
-        assertThat(renderer.getTextOutput(), instanceOf(StreamingStyledTextOutput.class));
-
-        renderer.complete();
-
-        assertTrue(outFile.isFile());
-        assertThat(renderer.getTextOutput(), nullValue());
-    }
-
-    @Test
-    public void writeRootProjectHeader() throws IOException {
-        final Project project = context.mock(Project.class);
-        TestStyledTextOutput textOutput = new TestStyledTextOutput();
-
-        context.checking(new Expectations() {{
-            allowing(project).getRootProject();
-            will(returnValue(project));
-            allowing(project).getDescription();
-            will(returnValue(null));
-        }});
-
-        renderer.setOutput(textOutput);
-        renderer.startProject(project);
-        renderer.completeProject(project);
-        renderer.complete();
-
-        assertThat(textOutput.toString(), containsLine("Root Project"));
-    }
-
-    @Test
-    public void writeSubProjectHeader() throws IOException {
-        final Project project = context.mock(Project.class);
-        TestStyledTextOutput textOutput = new TestStyledTextOutput();
-
-        context.checking(new Expectations() {{
-            allowing(project).getRootProject();
-            will(returnValue(context.mock(Project.class, "root")));
-            allowing(project).getDescription();
-            will(returnValue(null));
-            allowing(project).getPath();
-            will(returnValue("<path>"));
-        }});
-
-        renderer.setOutput(textOutput);
-        renderer.startProject(project);
-        renderer.completeProject(project);
-        renderer.complete();
-
-        assertThat(textOutput.toString(), containsLine("Project <path>"));
-    }
-
-    @Test
-    public void includesProjectDescriptionInHeader() throws IOException {
-        final Project project = context.mock(Project.class);
-        TestStyledTextOutput textOutput = new TestStyledTextOutput();
-
-        context.checking(new Expectations() {{
-            allowing(project).getRootProject();
-            will(returnValue(project));
-            allowing(project).getDescription();
-            will(returnValue("this is the root project"));
-        }});
-
-        renderer.setOutput(textOutput);
-        renderer.startProject(project);
-        renderer.completeProject(project);
-        renderer.complete();
-
-        assertThat(textOutput.toString(), containsLine("Root Project - this is the root project"));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/AutoCloseCacheFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/AutoCloseCacheFactoryTest.groovy
deleted file mode 100644
index 5b9c16e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/AutoCloseCacheFactoryTest.groovy
+++ /dev/null
@@ -1,102 +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.cache
-
-import org.gradle.CacheUsage
-import spock.lang.Specification
-
-public class AutoCloseCacheFactoryTest extends Specification {
-    private final CacheFactory backingFactory = Mock()
-    private final AutoCloseCacheFactory factory = new AutoCloseCacheFactory(backingFactory)
-
-    public void createsCachesUsingBackingFactory() {
-        PersistentCache cache = Mock()
-
-        when:
-        def retval = factory.open(new File('dir1'), CacheUsage.ON, [:])
-
-        then:
-        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
-        retval == cache
-    }
-
-    public void cachesCacheInstanceForAGivenDirectory() {
-        PersistentCache cache = Mock()
-
-        when:
-        def cache1 = factory.open(new File('dir1'), CacheUsage.ON, [:])
-        def cache2 = factory.open(new File('dir1').canonicalFile, CacheUsage.ON, [:])
-
-        then:
-        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
-        cache1 == cache2
-    }
-
-    public void closesCacheUsingBackingFactory() {
-        PersistentCache cache = Mock()
-
-        when:
-        factory.open(new File('dir1'), CacheUsage.ON, [:])
-
-        then:
-        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
-
-        when:
-        factory.close(cache)
-
-        then:
-        1 * backingFactory.close(cache)
-    }
-
-    public void closesCacheWhenLastReferenceClosed() {
-        PersistentCache cache = Mock()
-
-        when:
-        factory.open(new File('dir1'), CacheUsage.ON, [:])
-        factory.open(new File('dir1'), CacheUsage.ON, [:])
-
-        then:
-        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
-
-        when:
-        factory.close(cache)
-        factory.close(cache)
-
-        then:
-        1 * backingFactory.close(cache)
-    }
-    
-    public void closesEachOpenCacheOnClose() {
-        PersistentCache cache1 = Mock()
-        PersistentCache cache2 = Mock()
-
-        when:
-        factory.open(new File('dir1'), CacheUsage.ON, [:])
-        factory.open(new File('dir2'), CacheUsage.ON, [:])
-
-        then:
-        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache1
-        1 * backingFactory.open(new File('dir2'), CacheUsage.ON, [:]) >> cache2
-
-        when:
-        factory.close()
-
-        then:
-        1 * backingFactory.close(cache1)
-        1 * backingFactory.close(cache2)
-    }
-}
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultCacheFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/DefaultCacheFactoryTest.groovy
deleted file mode 100644
index 9796005..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultCacheFactoryTest.groovy
+++ /dev/null
@@ -1,41 +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.cache
-
-import org.gradle.CacheUsage
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-import spock.lang.Specification
-
-class DefaultCacheFactoryTest extends Specification {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder()
-    private final DefaultCacheFactory factory = new DefaultCacheFactory()
-
-    public void createsCache() {
-        when:
-        PersistentCache cache = factory.open(tmpDir.dir, CacheUsage.ON, [prop: 'value'])
-
-        then:
-        cache instanceof DefaultPersistentDirectoryCache
-        cache.baseDir == tmpDir.dir
-    }
-}
-
-
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultCacheRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/DefaultCacheRepositoryTest.java
deleted file mode 100644
index 80708e5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultCacheRepositoryTest.java
+++ /dev/null
@@ -1,133 +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.cache;
-
-import org.gradle.CacheUsage;
-import org.gradle.api.Project;
-import org.gradle.api.invocation.Gradle;
-import org.gradle.util.GUtil;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collections;
-import java.util.Map;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultCacheRepositoryTest {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final TestFile homeDir = tmpDir.createDir("home");
-    private final TestFile buildRootDir = tmpDir.createDir("build");
-    private final TestFile sharedCacheDir = homeDir.file("caches");
-    private final String version = GradleVersion.current().getVersion();
-    private final Map<String, ?> properties = GUtil.map("a", "value", "b", "value2");
-    private final CacheFactory cacheFactory = context.mock(CacheFactory.class);
-    private final PersistentCache cache = context.mock(PersistentCache.class);
-    private final Gradle gradle = context.mock(Gradle.class);
-    private final DefaultCacheRepository repository = new DefaultCacheRepository(homeDir, CacheUsage.ON, cacheFactory);
-
-    @Before
-    public void setup() {
-        context.checking(new Expectations() {{
-            Project project = context.mock(Project.class);
-
-            allowing(cache).getBaseDir();
-            will(returnValue(tmpDir.getDir()));
-            allowing(gradle).getRootProject();
-            will(returnValue(project));
-            allowing(project).getProjectDir();
-            will(returnValue(buildRootDir));
-        }});
-    }
-
-    @Test
-    public void createsGlobalCache() {
-        context.checking(new Expectations() {{
-            one(cacheFactory).open(sharedCacheDir.file(version, "a/b/c"), CacheUsage.ON, Collections.EMPTY_MAP);
-            will(returnValue(cache));
-        }});
-
-        assertThat(repository.cache("a/b/c").open(), sameInstance(cache));
-    }
-
-    @Test
-    public void createsGlobalCacheWithProperties() {
-        context.checking(new Expectations() {{
-            one(cacheFactory).open(sharedCacheDir.file(version, "a/b/c"), CacheUsage.ON, properties);
-            will(returnValue(cache));
-        }});
-
-        assertThat(repository.cache("a/b/c").withProperties(properties).open(), sameInstance(cache));
-    }
-
-    @Test
-    public void createsCacheForAGradleInstance() {
-
-        context.checking(new Expectations() {{
-            one(cacheFactory).open(buildRootDir.file(".gradle", version, "a/b/c"), CacheUsage.ON,
-                    Collections.EMPTY_MAP);
-            will(returnValue(cache));
-        }});
-
-        assertThat(repository.cache("a/b/c").forObject(gradle).open(), sameInstance(cache));
-    }
-
-    @Test
-    public void createsCacheForAFile() {
-        final TestFile dir = tmpDir.createDir("otherDir");
-
-        context.checking(new Expectations() {{
-            one(cacheFactory).open(dir.file(".gradle", version, "a/b/c"), CacheUsage.ON, Collections.EMPTY_MAP);
-            will(returnValue(cache));
-        }});
-
-        assertThat(repository.cache("a/b/c").forObject(dir).open(), sameInstance(cache));
-    }
-
-    @Test
-    public void createsCrossVersionCache() {
-        context.checking(new Expectations() {{
-            one(cacheFactory).open(sharedCacheDir.file("noVersion", "a/b/c"), CacheUsage.ON, Collections.singletonMap(
-                    "gradle.version", version));
-            will(returnValue(cache));
-        }});
-
-        assertThat(repository.cache("a/b/c").invalidateOnVersionChange().open(), sameInstance(cache));
-    }
-
-    @Test
-    public void createsCrossVersionCacheForAGradleInstance() {
-        context.checking(new Expectations() {{
-            one(cacheFactory).open(buildRootDir.file(".gradle", "noVersion", "a/b/c"), CacheUsage.ON,
-                    Collections.singletonMap("gradle.version", version));
-            will(returnValue(cache));
-        }});
-
-        assertThat(repository.cache("a/b/c").invalidateOnVersionChange().forObject(gradle).open(), sameInstance(cache));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultPersistentDirectoryCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/DefaultPersistentDirectoryCacheTest.java
deleted file mode 100644
index 0f1e4a8..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/DefaultPersistentDirectoryCacheTest.java
+++ /dev/null
@@ -1,171 +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.cache;
-
-import org.gradle.CacheUsage;
-import org.gradle.cache.btree.BTreePersistentIndexedCache;
-import org.gradle.util.TestFile;
-import org.gradle.util.GUtil;
-import org.gradle.util.TemporaryFolder;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-public class DefaultPersistentDirectoryCacheTest {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-    private final Map<String, String> properties = GUtil.map("prop", "value", "prop2", "other-value");
-
-    @Test
-    public void cacheIsInvalidWhenCacheDirDoesNotExist() {
-        TestFile emptyDir = tmpDir.getDir().file("dir");
-        emptyDir.assertDoesNotExist();
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(emptyDir, CacheUsage.ON, properties);
-        assertFalse(cache.isValid());
-
-        emptyDir.assertIsDir();
-    }
-
-    @Test
-    public void cacheIsInvalidWhenPropertiesFileDoesNotExist() {
-        TestFile dir = tmpDir.getDir().file("dir").createDir();
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertFalse(cache.isValid());
-
-        dir.assertIsDir();
-    }
-
-    @Test
-    public void rebuildsCacheWhenPropertiesHaveChanged() {
-        TestFile dir = createCacheDir("prop", "other-value");
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertFalse(cache.isValid());
-
-        dir.assertHasDescendants();
-    }
-
-    @Test
-    public void rebuildsCacheWhenCacheRebuildRequested() {
-        TestFile dir = createCacheDir();
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.REBUILD, properties);
-        assertFalse(cache.isValid());
-
-        dir.assertHasDescendants();
-    }
-
-    @Test
-    public void usesExistingCacheDirWhenItIsNotInvalid() {
-        TestFile dir = createCacheDir();
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertTrue(cache.isValid());
-
-        dir.file("cache.properties").assertIsFile();
-        dir.file("some-file").assertIsFile();
-    }
-
-    @Test
-    public void updateCreatesPropertiesFileWhenItDoesNotExist() {
-        TestFile dir = tmpDir.getDir().file("dir");
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        cache.markValid();
-
-        assertTrue(cache.isValid());
-        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
-    }
-
-    @Test
-    public void updatesPropertiesWhenMarkedValid() {
-        TestFile dir = createCacheDir("prop", "some-other-value");
-
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        cache.markValid();
-
-        assertTrue(cache.isValid());
-        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
-    }
-
-    @Test
-    public void createsAnIndexedCache() {
-        TestFile dir = tmpDir.getDir().file("dir");
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertThat(cache.openIndexedCache(), instanceOf(BTreePersistentIndexedCache.class));
-    }
-
-    @Test
-    public void reusesTheIndexedCache() {
-        TestFile dir = tmpDir.getDir().file("dir");
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertThat(cache.openIndexedCache(), sameInstance(cache.openIndexedCache()));
-    }
-
-    @Test
-    public void closesIndexedCacheOnClose() {
-        TestFile dir = tmpDir.getDir().file("dir");
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        
-        BTreePersistentIndexedCache indexedCache = cache.openIndexedCache();
-        assertTrue(indexedCache.isOpen());
-
-        cache.close();
-        assertFalse(indexedCache.isOpen());
-    }
-    
-    @Test
-    public void createsAnStateCache() {
-        TestFile dir = tmpDir.getDir().file("dir");
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertThat(cache.openStateCache(), instanceOf(SimpleStateCache.class));
-    }
-
-    @Test
-    public void reusesTheStateCache() {
-        TestFile dir = tmpDir.getDir().file("dir");
-        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
-        assertThat(cache.openStateCache(), sameInstance(cache.openStateCache()));
-    }
-
-    private Map<String, String> loadProperties(TestFile file) {
-        Properties properties = GUtil.loadProperties(file);
-        Map<String, String> result = new HashMap<String, String>();
-        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
-            result.put(entry.getKey().toString(), entry.getValue().toString());
-        }
-        return result;
-    }
-
-    private TestFile createCacheDir(String... extraProps) {
-        TestFile dir = tmpDir.getDir();
-        Properties properties = new Properties();
-        properties.putAll(this.properties);
-        properties.putAll(GUtil.map((Object[])extraProps));
-        GUtil.saveProperties(properties, dir.file("cache.properties"));
-        dir.file("some-file").touch();
-
-        return dir;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/SimpleStateCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/SimpleStateCacheTest.groovy
deleted file mode 100644
index 8252d4e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/SimpleStateCacheTest.groovy
+++ /dev/null
@@ -1,61 +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.cache
-
-
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.integration.junit4.JMock
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-
- at RunWith(JMock.class)
-class SimpleStateCacheTest {
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final PersistentCache backingCache = context.mock(PersistentCache.class)
-
-    @Before
-    public void setup() {
-        context.checking {
-            allowing(backingCache).getBaseDir()
-            will(returnValue(tmpDir.dir))
-        }
-    }
-
-    @Test
-    public void getReturnsNullWhenFileDoesNotExist() {
-        SimpleStateCache<String> cache = new SimpleStateCache<String>(backingCache, new DefaultSerializer<String>())
-        assertThat(cache.get(), nullValue())
-    }
-    
-    @Test
-    public void getReturnsLastWrittenValue() {
-        SimpleStateCache<String> cache = new SimpleStateCache<String>(backingCache, new DefaultSerializer<String>())
-
-        context.checking {
-            one(backingCache).markValid()
-        }
-
-        cache.set('some value')
-        tmpDir.file('state.bin').assertIsFile()
-        assertThat(cache.get(), equalTo('some value'))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/btree/BTreePersistentIndexedCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/btree/BTreePersistentIndexedCacheTest.java
deleted file mode 100644
index a9b3e73..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/cache/btree/BTreePersistentIndexedCacheTest.java
+++ /dev/null
@@ -1,321 +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.cache.btree;
-
-import org.gradle.cache.DefaultSerializer;
-import org.gradle.cache.PersistentCache;
-import org.gradle.cache.Serializer;
-import org.gradle.util.TestFile;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.*;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class BTreePersistentIndexedCacheTest {
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final PersistentCache backingCache = context.mock(PersistentCache.class);
-    private final Serializer<Integer> serializer = new DefaultSerializer<Integer>();
-    private BTreePersistentIndexedCache<String, Integer> cache;
-
-    @Before
-    public void setup() {
-        context.checking(new Expectations(){{
-            allowing(backingCache).getBaseDir();
-            will(returnValue(tmpDir.getDir()));
-            allowing(backingCache).markValid();
-        }});
-
-        cache = new BTreePersistentIndexedCache<String, Integer>(backingCache, serializer, (short) 4, 100);
-    }
-
-    @Test
-    public void getReturnsNullWhenEntryDoesNotExist() {
-        assertNull(cache.get("unknown"));
-        cache.verify();
-    }
-
-    @Test
-    public void persistsAddedEntries() {
-        checkAdds(1, 2, 3, 4, 5);
-        cache.verify();
-    }
-
-    @Test
-    public void persistsAddedEntriesInReverseOrder() {
-        checkAdds(5, 4, 3, 2, 1);
-        cache.verify();
-    }
-
-    @Test
-    public void persistsAddedEntriesOverMultipleIndexBlocks() {
-        checkAdds(3, 2, 11, 5, 7, 1, 10, 8, 9, 4, 6, 0);
-        cache.verify();
-    }
-
-    @Test
-    public void persistsAddedEntriesAfterReopen() {
-        checkAdds(1, 2, 3, 4);
-
-        cache.reset();
-
-        checkAdds(5, 6, 7, 8);
-        cache.verify();
-    }
-    
-    @Test
-    public void persistsReplacedEntries() {
-
-        cache.put("key_1", 1);
-        cache.put("key_2", 2);
-        cache.put("key_3", 3);
-        cache.put("key_4", 4);
-        cache.put("key_5", 5);
-
-        cache.put("key_1", 1);
-        cache.put("key_4", 12);
-
-        assertThat(cache.get("key_1"), equalTo(1));
-        assertThat(cache.get("key_2"), equalTo(2));
-        assertThat(cache.get("key_3"), equalTo(3));
-        assertThat(cache.get("key_4"), equalTo(12));
-        assertThat(cache.get("key_5"), equalTo(5));
-
-        cache.reset();
-
-        assertThat(cache.get("key_1"), equalTo(1));
-        assertThat(cache.get("key_2"), equalTo(2));
-        assertThat(cache.get("key_3"), equalTo(3));
-        assertThat(cache.get("key_4"), equalTo(12));
-        assertThat(cache.get("key_5"), equalTo(5));
-
-        cache.verify();
-    }
-
-    @Test
-    public void reusesEmptySpaceWhenPuttingEntries() {
-        BTreePersistentIndexedCache<String, String> cache = new BTreePersistentIndexedCache<String, String>(
-                backingCache, new DefaultSerializer<String>(), (short) 4, 100);
-        TestFile cacheFile = tmpDir.getDir().file("cache.bin");
-
-        cache.put("key_1", "abcd");
-        cache.put("key_2", "abcd");
-        cache.put("key_3", "abcd");
-        cache.put("key_4", "abcd");
-        cache.put("key_5", "abcd");
-
-        long len = cacheFile.length();
-        assertThat(len, greaterThan(0L));
-
-        cache.put("key_1", "1234");
-        assertThat(cacheFile.length(), equalTo(len));
-
-        cache.remove("key_1");
-        cache.put("key_new", "a1b2");
-        assertThat(cacheFile.length(), equalTo(len));
-
-        cache.put("key_new", "longer value");
-        assertThat(cacheFile.length(), greaterThan(len));
-        len = cacheFile.length();
-
-        cache.put("key_1", "1234");
-        assertThat(cacheFile.length(), equalTo(len));
-    }
-    
-    @Test
-    public void canHandleLargeNumberOfEntries() {
-
-        int count = 2000;
-        List<Integer> values = new ArrayList<Integer>();
-        for (int i = 0; i < count; i++) {
-            values.add(i);
-        }
-
-        checkAddsAndRemoves(null, values);
-
-        TestFile testFile = tmpDir.getDir().file("cache.bin");
-        long len = testFile.length();
-
-        checkAddsAndRemoves(Collections.<Integer>reverseOrder(), values);
-
-        // need to make this better
-        assertThat(testFile.length(), lessThan((long)(1.4 * len)));
-
-        checkAdds(values);
-        
-        // need to make this better
-        assertThat(testFile.length(), lessThan((long)(1.4 * 1.4 * len)));
-    }
-
-    @Test
-    public void persistsRemovalOfEntries() {
-        checkAddsAndRemoves(1, 2, 3, 4, 5);
-        cache.verify();
-    }
-
-    @Test
-    public void persistsRemovalOfEntriesInReverse() {
-        checkAddsAndRemoves(Collections.<Integer>reverseOrder(), 1, 2, 3, 4, 5);
-        cache.verify();
-    }
-
-    @Test
-    public void persistsRemovalOfEntriesOverMultipleIndexBlocks() {
-        checkAddsAndRemoves(4, 12, 9, 1, 3, 10, 11, 7, 8, 2, 5, 6);
-        cache.verify();
-    }
-
-    @Test
-    public void removalRedistributesRemainingEntriesWithLeftSibling() {
-        // Ends up with: 1 2 3 -> 4 <- 5 6
-        checkAdds(1, 2, 5, 6, 4, 3);
-        cache.verify();
-        cache.remove("key_5");
-        cache.verify();
-    }
-
-    @Test
-    public void removalMergesRemainingEntriesIntoLeftSibling() {
-        // Ends up with: 1 2 -> 3 <- 4 5
-        checkAdds(1, 2, 4, 5, 3);
-        cache.verify();
-        cache.remove("key_4");
-        cache.verify();
-    }
-
-    @Test
-    public void removalRedistributesRemainingEntriesWithRightSibling() {
-        // Ends up with: 1 2 -> 3 <- 4 5 6
-        checkAdds(1, 2, 4, 5, 3, 6);
-        cache.verify();
-        cache.remove("key_2");
-        cache.verify();
-    }
-
-    @Test
-    public void removalMergesRemainingEntriesIntoRightSibling() {
-        // Ends up with: 1 2 -> 3 <- 4 5
-        checkAdds(1, 2, 4, 5, 3);
-        cache.verify();
-        cache.remove("key_2");
-        cache.verify();
-    }
-
-    @Test
-    public void handlesBadlyFormedCacheFile() throws IOException {
-
-        TestFile testFile = tmpDir.getDir().file("cache.bin");
-        testFile.assertIsFile();
-        testFile.write("some junk");
-
-        BTreePersistentIndexedCache<String, Integer> cache = new BTreePersistentIndexedCache<String, Integer>(backingCache, serializer);
-
-        assertNull(cache.get("key_1"));
-        cache.put("key_1", 99);
-
-        RandomAccessFile file = new RandomAccessFile(testFile, "rw");
-        file.setLength(file.length() - 10);
-
-        cache.reset();
-
-        assertNull(cache.get("key_1"));
-        cache.verify();
-    }
-
-    @Test
-    public void canUseFileAsKey() {
-
-        BTreePersistentIndexedCache<File, Integer> cache = new BTreePersistentIndexedCache<File, Integer>(backingCache, serializer);
-
-        cache.put(new File("file"), 1);
-        cache.put(new File("dir/file"), 2);
-        cache.put(new File("File"), 3);
-
-        assertThat(cache.get(new File("file")), equalTo(1));
-        assertThat(cache.get(new File("dir/file")), equalTo(2));
-        assertThat(cache.get(new File("File")), equalTo(3));
-    }
-
-    private void checkAdds(Integer... values) {
-        checkAdds(Arrays.asList(values));
-    }
-
-    private Map<String, Integer> checkAdds(Iterable<Integer> values) {
-        Map<String, Integer> added = new LinkedHashMap<String, Integer>();
-
-        for (Integer value : values) {
-            String key = String.format("key_%d", value);
-            cache.put(key, value);
-            added.put(String.format("key_%d", value), value);
-        }
-
-        for (Map.Entry<String, Integer> entry : added.entrySet()) {
-            assertThat(cache.get(entry.getKey()), equalTo(entry.getValue()));
-        }
-
-        cache.reset();
-
-        for (Map.Entry<String, Integer> entry : added.entrySet()) {
-            assertThat(cache.get(entry.getKey()), equalTo(entry.getValue()));
-        }
-
-        return added;
-    }
-
-    private void checkAddsAndRemoves(Integer... values) {
-        checkAddsAndRemoves(null, values);
-    }
-
-    private void checkAddsAndRemoves(Comparator<Integer> comparator, Integer... values) {
-        checkAddsAndRemoves(comparator, Arrays.asList(values));
-    }
-
-    private void checkAddsAndRemoves(Comparator<Integer> comparator, Collection<Integer> values) {
-        checkAdds(values);
-
-        List<Integer> deleteValues = new ArrayList<Integer>(values);
-        Collections.sort(deleteValues, comparator);
-        for (Integer value : deleteValues) {
-            String key = String.format("key_%d", value);
-            assertThat(cache.get(key), notNullValue());
-            cache.remove(key);
-            assertThat(cache.get(key), nullValue());
-        }
-
-        cache.reset();
-        cache.verify();
-
-        for (Integer value : deleteValues) {
-            String key = String.format("key_%d", value);
-            assertThat(cache.get(key), nullValue());
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
new file mode 100644
index 0000000..cdb20a4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import org.gradle.internal.Factory
+import org.gradle.cache.Serializer
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import static org.gradle.cache.internal.FileLockManager.LockMode.*
+
+class DefaultCacheAccessTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final FileLockManager lockManager = Mock()
+    final File lockFile = tmpDir.file('lock.bin')
+    final File targetFile = tmpDir.file('cache.bin')
+    final FileLock lock = Mock()
+    final BTreePersistentIndexedCache<String, Integer> backingCache = Mock()
+    final DefaultCacheAccess manager = new DefaultCacheAccess("<display-name>", lockFile, lockManager) {
+        @Override
+        def <K, V> BTreePersistentIndexedCache<K, V> doCreateCache(File cacheFile, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
+            return backingCache
+        }
+    }
+
+    def "executes cache action and returns result"() {
+        Factory<String> action = Mock()
+
+        given:
+        manager.open(None)
+
+        when:
+        def result = manager.useCache("some operation", action)
+
+        then:
+        result == 'result'
+
+        and:
+        1 * action.create() >> 'result'
+        0 * _._
+    }
+
+    def "can create cache instance outside of cache action"() {
+        given:
+        manager.open(None)
+
+        when:
+        def cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
+
+        then:
+        cache instanceof MultiProcessSafePersistentIndexedCache
+        0 * _._
+    }
+
+    def "can create cache instance inside of cache action"() {
+        def cache
+
+        given:
+        manager.open(None)
+
+        when:
+        manager.useCache("init", {
+            cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
+        } as Factory)
+
+        then:
+        cache instanceof MultiProcessSafePersistentIndexedCache
+        0 * _._
+    }
+
+    def "acquires lock on open and releases on close when initial lock mode is not none"() {
+        when:
+        manager.open(Shared)
+
+        then:
+        1 * lockManager.lock(lockFile, Shared, "<display-name>") >> lock
+        0 * _._
+
+        when:
+        manager.close()
+
+        then:
+        1 * lock.close()
+        0 * _._
+    }
+
+    def "does not acquires lock on open when initial lock mode is none"() {
+        when:
+        manager.open(None)
+
+        then:
+        0 * _._
+
+        when:
+        manager.close()
+
+        then:
+        0 * _._
+    }
+
+    def "does not acquire lock when no caches used during cache action"() {
+        given:
+        manager.open(None)
+        def cache = manager.newCache(tmpDir.file('cache.bin'), String.class, Integer.class)
+
+        when:
+        manager.useCache("some operation", {} as Factory)
+
+        then:
+        0 * _._
+    }
+
+    def "acquires lock when a cache is used and releases lock at the end of the cache action when initial lock mode is none"() {
+        Factory<String> action = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+        }
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        _ * lock.readFromFile(_)
+
+        and:
+        _ * lock.writeToFile(_)
+        1 * lock.close()
+        0 * _._
+    }
+
+    def "does not acquire lock at start of cache action when initial lock mode is exclusive"() {
+        Factory<String> action = Mock()
+
+        given:
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
+        manager.open(Exclusive)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+        }
+        _ * lock.readFromFile(_)
+        _ * lock.writeToFile(_)
+
+        and:
+        0 * _._
+    }
+
+    def "releases lock before long running operation and reacquires after"() {
+        Factory<String> action = Mock()
+        Factory<String> longRunningAction = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+            manager.longRunningOperation("nested", longRunningAction)
+            cache.get("key")
+        }
+        1 * longRunningAction.create()
+        2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        _ * lock.readFromFile(_)
+        _ * lock.writeToFile(_)
+        2 * lock.close()
+        0 * _._
+    }
+
+    def "cannot run long running operation from outside cache action"() {
+        given:
+        manager.open(None)
+
+        when:
+        manager.longRunningOperation("operation", Mock(Factory))
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Cannot start long running operation, as the <display-name> has not been locked.'
+    }
+
+    def "cannot use cache from within long running operation"() {
+        Factory<String> action = Mock()
+        Factory<String> longRunningAction = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'The <display-name> has not been locked.'
+
+        and:
+        1 * action.create() >> {
+            manager.longRunningOperation("nested", longRunningAction)
+        }
+        1 * longRunningAction.create() >> {
+            cache.get("key")
+        }
+        0 * _._
+    }
+
+    def "can execute cache action from within long running operation"() {
+        Factory<String> action = Mock()
+        Factory<String> longRunningAction = Mock()
+        Factory<String> nestedAction = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+            manager.longRunningOperation("nested", longRunningAction)
+        }
+        1 * longRunningAction.create() >> {
+            manager.useCache("nested 2", nestedAction)
+        }
+        1 * nestedAction.create() >> {
+            cache.get("key")
+        }
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested 2") >> lock
+        _ * lock.readFromFile(_)
+        _ * lock.writeToFile(_)
+        2 * lock.close()
+        0 * _._
+    }
+
+    def "can execute long running operation from within long running operation"() {
+        Factory<String> action = Mock()
+        Factory<String> longRunningAction = Mock()
+        Factory<String> nestedAction = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+            manager.longRunningOperation("nested", longRunningAction)
+        }
+        1 * longRunningAction.create() >> {
+            manager.longRunningOperation("nested 2", nestedAction)
+        }
+        1 * nestedAction.create()
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        _ * lock.readFromFile(_)
+        _ * lock.writeToFile(_)
+        1 * lock.close()
+        0 * _._
+    }
+
+    def "can execute cache action from within cache action"() {
+        Factory<String> action = Mock()
+        Factory<String> nestedAction = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+            manager.useCache("nested", nestedAction)
+        }
+        1 * nestedAction.create() >> {
+            cache.get("key")
+        }
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        _ * lock.readFromFile(_)
+        _ * lock.writeToFile(_)
+        1 * lock.close()
+        0 * _._
+    }
+
+    def "closes caches at the end of the cache action when initial lock mode is none"() {
+        Factory<String> action = Mock()
+
+        given:
+        manager.open(None)
+        def cache = manager.newCache(targetFile, String, Integer)
+
+        when:
+        manager.useCache("some operation", action)
+
+        then:
+        1 * action.create() >> {
+            cache.get("key")
+        }
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
+        _ * lock.readFromFile(_)
+
+        and:
+        _ * lock.writeToFile(_) >> {Runnable runnable -> runnable.run()}
+        1 * backingCache.close()
+        1 * lock.close()
+        0 * _._
+    }
+
+    def "closes caches on close when initial lock mode is not none"() {
+        given:
+        1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
+        _ * lock.readFromFile(_) >> {Factory factory -> factory.create()}
+        _ * lock.writeToFile(_) >> {Runnable runnable -> runnable.run()}
+
+        and:
+        manager.open(Exclusive)
+        def cache = manager.newCache(targetFile, String, Integer)
+        cache.get("key")
+
+        when:
+        manager.close()
+
+        then:
+        _ * lock.readFromFile(_) >> {Factory factory -> factory.create()}
+        _ * lock.writeToFile(_) >> {Runnable runnable -> runnable.run()}
+        1 * backingCache.close()
+        1 * lock.close()
+        0 * _._
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
new file mode 100755
index 0000000..3db8098
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
@@ -0,0 +1,399 @@
+/*
+ * 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.cache.internal
+
+import org.gradle.CacheUsage
+import org.gradle.api.Action
+import org.gradle.cache.DefaultSerializer
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.cache.CacheValidator
+
+class DefaultCacheFactoryTest extends Specification {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    final Action<?> opened = Mock()
+    final Action<?> closed = Mock()
+    final ProcessMetaDataProvider metaDataProvider = Mock()
+    private final DefaultCacheFactory factoryFactory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider)) {
+        @Override
+        void onOpen(Object cache) {
+            opened.execute(cache)
+        }
+
+        @Override
+        void onClose(Object cache) {
+            closed.execute(cache)
+        }
+    }
+
+    def setup() {
+        _ * metaDataProvider.processIdentifier >> '123'
+        _ * metaDataProvider.processDisplayName >> 'process'
+    }
+
+    def cleanup() {
+        factoryFactory.close()
+    }
+
+    public void "creates directory backed store instance"() {
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.openStore(tmpDir.dir, "<display>", FileLockManager.LockMode.Shared, null)
+
+        then:
+        cache instanceof DefaultPersistentDirectoryStore
+        cache.baseDir == tmpDir.dir
+        cache.toString().startsWith "<display>"
+
+        when:
+        factory.close()
+
+        then:
+        1 * closed.execute(cache)
+    }
+
+    public void "creates directory backed cache instance"() {
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.open(tmpDir.dir, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+
+        then:
+        cache instanceof DefaultPersistentDirectoryCache
+        cache.baseDir == tmpDir.dir
+        cache.toString().startsWith "<display>"
+
+        when:
+        factory.close()
+
+        then:
+        1 * closed.execute(cache)
+    }
+
+    public void "creates indexed cache instance"() {
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
+
+        then:
+        cache instanceof MultiProcessSafePersistentIndexedCache
+    }
+
+    public void "creates state cache instance"() {
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, new DefaultSerializer())
+
+        then:
+        cache instanceof SimpleStateCache
+    }
+
+    public void "reuses directory backed cache instances"() {
+        when:
+        def factory = factoryFactory.create()
+        def ref1 = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses directory backed cache instances across multiple sessions"() {
+        when:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        def ref1 = factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses directory backed store instances"() {
+        when:
+        def factory = factoryFactory.create()
+        def ref1 = factory.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses directory backed store instances across multiple sessions"() {
+        when:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        def ref1 = factory1.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.openStore(tmpDir.dir, null, FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses indexed cache instances"() {
+        when:
+        def factory = factoryFactory.create()
+        def ref1 = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses indexed cache instances across multiple sessions"() {
+        when:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        def ref1 = factory1.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses state cache instances"() {
+        when:
+        def factory = factoryFactory.create()
+        def ref1 = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "reuses state cache instances across multiple sessions"() {
+        when:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        def ref1 = factory1.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def ref2 = factory2.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        ref1.is(ref2)
+        0 * closed._
+    }
+
+    public void "releases directory cache instance when last reference released"() {
+        given:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory1.close()
+        factory2.close()
+
+        then:
+        1 * closed.execute(!null)
+
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        !cache.is(oldCache)
+        1 * opened.execute(!null)
+        0 * closed._
+    }
+
+    public void "releases index cache instance and backing directory instance when last reference released"() {
+        given:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        factory1.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory2.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory1.close()
+        factory2.close()
+
+        then:
+        2 * closed.execute(!null)
+
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        !cache.is(oldCache)
+        2 * opened.execute(!null)
+        0 * closed._
+    }
+
+    public void "releases state cache instance and backing directory instance when last reference released"() {
+        given:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        factory1.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def oldCache = factory2.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory1.close()
+        factory2.close()
+
+        then:
+        2 * closed.execute(!null)
+
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        !cache.is(oldCache)
+        2 * opened.execute(!null)
+        0 * closed._
+    }
+
+    public void "can open and release cache as directory and indexed and state cache"() {
+        given:
+        def factory1 = factoryFactory.create()
+        def factory2 = factoryFactory.create()
+        def oldCache = factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.openIndexedCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.openStateCache(tmpDir.dir, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory1.close()
+        factory2.close()
+
+        then:
+        3 * closed.execute(!null)
+
+        when:
+        def factory = factoryFactory.create()
+        def cache = factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        !oldCache.is(cache)
+        1 * opened.execute(!null)
+        0 * closed._
+    }
+
+    public void "fails when directory cache is already open with different properties"() {
+        given:
+        def factory = factoryFactory.create()
+        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cache '${tmpDir.dir}' is already open with different state."
+    }
+
+    public void "fails when directory cache is already open with different properties in different session"() {
+        given:
+        def factory1 = factoryFactory.create()
+        factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        def factory2 = factoryFactory.create()
+        factory2.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cache '${tmpDir.dir}' is already open with different state."
+    }
+
+    public void "fails when directory cache is already open when rebuild is requested"() {
+        given:
+        def factory = factoryFactory.create()
+        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot rebuild cache '${tmpDir.dir}' as it is already open."
+    }
+
+    public void "fails when directory cache is already open in different session when rebuild is requested"() {
+        given:
+        def factory1 = factoryFactory.create()
+        factory1.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        def factory2 = factoryFactory.create()
+        factory2.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot rebuild cache '${tmpDir.dir}' as it is already open."
+    }
+
+    public void "can open directory cache when rebuild is requested and cache was rebuilt in same session"() {
+        given:
+        def factory = factoryFactory.create()
+        factory.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        when:
+        factory.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        notThrown(RuntimeException)
+    }
+
+    public void "can open directory cache when rebuild is requested and has been closed"() {
+        given:
+        def factory1 = factoryFactory.create()
+        factory1.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        factory1.close()
+
+        when:
+        def factory2 = factoryFactory.create()
+        factory2.open(tmpDir.dir, null, CacheUsage.REBUILD, null, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        notThrown(RuntimeException)
+    }
+
+    public void "fails when directory cache when cache is already open with different lock mode"() {
+        given:
+        def factory = factoryFactory.create()
+        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.Shared, null)
+
+        when:
+        factory.open(tmpDir.dir, null, CacheUsage.ON, null, [prop: 'other'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "Cannot open cache '${tmpDir.dir}' with exclusive lock mode as it is already open with shared lock mode."
+    }
+
+    public void "can pass CacheValidator to Cache"() {
+        given:
+        def factory1 = factoryFactory.create()
+        CacheValidator validator = Mock()
+
+        when:
+        def cache = factory1.open(tmpDir.dir, null, CacheUsage.ON, validator, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+
+        then:
+        cache != null
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
new file mode 100644
index 0000000..e85eacf
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheRepositoryTest.groovy
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import org.gradle.CacheUsage
+import org.gradle.api.Action
+import org.gradle.api.Project
+import org.gradle.api.invocation.Gradle
+import org.gradle.cache.CacheBuilder.VersionStrategy
+import org.gradle.cache.DefaultSerializer
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.PersistentIndexedCache
+import org.gradle.cache.PersistentStateCache
+import org.gradle.util.GradleVersion
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.cache.CacheValidator
+
+class DefaultCacheRepositoryTest extends Specification {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    private final TestFile homeDir = tmpDir.createDir("home")
+    private final TestFile buildRootDir = tmpDir.createDir("build")
+    private final TestFile sharedCacheDir = homeDir.file("caches")
+    private final String version = GradleVersion.current().version
+    private final Map<String, ?> properties = [a: "value", b: "value2"]
+    private final CacheFactory cacheFactory = Mock()
+    private final PersistentCache cache = Mock()
+    private final Gradle gradle = Mock()
+    private final DefaultCacheRepository repository = new DefaultCacheRepository(homeDir, null, CacheUsage.ON, cacheFactory)
+
+    public void setup() {
+        Project project = Mock()
+        _ * cache.baseDir >> tmpDir.dir
+        _ * gradle.rootProject >> project
+        _ * project.projectDir >> buildRootDir
+    }
+
+    public void createsGlobalDirectoryBackedStore() {
+        when:
+        def result = repository.store("a/b/c").open()
+
+        then:
+        result == cache
+        1 * cacheFactory.openStore(sharedCacheDir.file(version, "a/b/c"), null, FileLockManager.LockMode.Shared, null) >> cache
+        0 * cacheFactory._
+    }
+
+    public void createsGlobalDirectoryBackedCache() {
+        when:
+        def result = repository.cache("a/b/c").open()
+
+        then:
+        result == cache
+        1 * cacheFactory.open(sharedCacheDir.file(version, "a/b/c"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
+        0 * cacheFactory._
+    }
+
+    public void createsGlobalIndexedCache() {
+        given:
+        PersistentIndexedCache<String, Integer> indexedCache = Mock()
+
+        when:
+        def result = repository.indexedCache(String.class, Integer.class, "key").open()
+
+        then:
+        result == indexedCache
+        1 * cacheFactory.openIndexedCache(sharedCacheDir.file(version, "key"), CacheUsage.ON, null, [:], FileLockManager.LockMode.Exclusive, {it instanceof DefaultSerializer}) >> indexedCache
+        0 * cacheFactory._
+    }
+
+    public void createsGlobalStateCache() {
+        given:
+        PersistentStateCache<String> stateCache = Mock()
+
+        when:
+        def result = repository.stateCache(String.class, "key").open()
+
+        then:
+        result == stateCache
+        1 * cacheFactory.openStateCache(sharedCacheDir.file(version, "key"), CacheUsage.ON, null, [:], FileLockManager.LockMode.Exclusive, {it instanceof DefaultSerializer}) >> stateCache
+        0 * cacheFactory._
+    }
+
+    public void createsGlobalCacheWithProperties() {
+        when:
+        repository.cache("a/b/c").withProperties(properties).open()
+
+        then:
+        1 * cacheFactory.open(sharedCacheDir.file(version, "a/b/c"), null, CacheUsage.ON, null, properties, FileLockManager.LockMode.Shared, null) >> cache
+    }
+
+    public void createsCacheForAGradleInstance() {
+        when:
+        repository.cache("a/b/c").forObject(gradle).open()
+
+        then:
+        1 * cacheFactory.open(buildRootDir.file(".gradle", version, "a/b/c"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
+    }
+
+    public void createsCacheForAFile() {
+        final TestFile dir = tmpDir.createDir("otherDir");
+
+        when:
+        repository.cache("a/b/c").forObject(dir).open()
+
+        then:
+        1 * cacheFactory.open(dir.file(".gradle", version, "a/b/c"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
+    }
+
+    public void createsCrossVersionCacheThatIsInvalidatedOnVersionChange() {
+        when:
+        repository.cache("a/b/c").withVersionStrategy(VersionStrategy.SharedCacheInvalidateOnVersionChange).open()
+
+        then:
+        1 * cacheFactory.open(sharedCacheDir.file("noVersion", "a/b/c"), null, CacheUsage.ON, null, ["gradle.version": version], FileLockManager.LockMode.Shared, null) >> cache
+    }
+
+    public void createsCrossVersionCacheForAGradleInstanceThatIsInvalidatedOnVersionChange() {
+        when:
+        repository.cache("a/b/c").withVersionStrategy(VersionStrategy.SharedCacheInvalidateOnVersionChange).forObject(gradle).open()
+
+        then:
+        1 * cacheFactory.open(buildRootDir.file(".gradle", "noVersion", "a/b/c"), null, CacheUsage.ON, null, ["gradle.version": version], FileLockManager.LockMode.Shared, null) >> cache
+    }
+
+    public void canSpecifyInitializerActionForDirectoryCache() {
+        Action<?> action = Mock()
+
+        when:
+        repository.cache("a").withInitializer(action).open()
+
+        then:
+        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, action) >> cache
+    }
+
+    public void canSpecifyLockModeForDirectoryCache() {
+        when:
+        repository.cache("a").withLockMode(FileLockManager.LockMode.None).open()
+
+        then:
+        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), null, CacheUsage.ON, null, [:], FileLockManager.LockMode.None, null) >> cache
+    }
+
+    public void canSpecifyDisplayNameForDirectoryCache() {
+        when:
+        repository.cache("a").withDisplayName("<cache>").open()
+
+        then:
+        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), "<cache>", CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, null) >> cache
+    }
+
+    public void canSpecifyCacheValidatorForDirectoryCache() {
+        CacheValidator validator = Mock();
+        when:
+        repository.cache("a").withValidator(validator).open()
+
+        then:
+        1 * cacheFactory.open(sharedCacheDir.file(version, "a"), null, CacheUsage.ON, validator, [:], FileLockManager.LockMode.Shared, null) >> cache
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
new file mode 100644
index 0000000..7384ca0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import org.gradle.internal.Factory
+import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 8/30/11
+ */
+class DefaultFileLockManagerTest extends Specification {
+    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
+    ProcessMetaDataProvider metaDataProvider = Mock()
+    FileLockManager manager = new DefaultFileLockManager(metaDataProvider)
+
+    def setup() {
+        metaDataProvider.processIdentifier >> '123'
+        metaDataProvider.processDisplayName >> 'process'
+    }
+
+    def "can lock a file"() {
+        when:
+        def lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Shared, "lock")
+
+        then:
+        lock.isLockFile(tmpDir.createFile("file.txt.lock"))
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "can lock a directory"() {
+        when:
+        def lock = manager.lock(tmpDir.createDir("some-dir"), LockMode.Shared, "lock")
+
+        then:
+        lock.isLockFile(tmpDir.createFile("some-dir/some-dir.lock"))
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "can lock a file once it has been closed"() {
+        given:
+        def fileLock = lock(FileLockManager.LockMode.Exclusive);
+        fileLock.close()
+
+        when:
+        lock(FileLockManager.LockMode.Exclusive);
+
+        then:
+        notThrown(RuntimeException)
+    }
+
+    def "lock on new file is not unlocked cleanly"() {
+        when:
+        def lock = manager.lock(tmpDir.createFile("file.txt"), mode, "lock")
+
+        then:
+        !lock.unlockedCleanly
+
+        cleanup:
+        lock?.close()
+
+        where:
+        mode << [LockMode.Shared, LockMode.Exclusive]
+    }
+
+    def "existing lock is unlocked cleanly after writeToFile() has been called"() {
+        when:
+        def lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
+        lock.writeToFile({} as Runnable)
+
+        then:
+        lock.unlockedCleanly
+
+        when:
+        lock.close()
+        lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
+
+        then:
+        lock.unlockedCleanly
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "existing lock is unlocked cleanly after writeToFile() throws exception"() {
+        def failure = new RuntimeException()
+
+        when:
+        def lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
+        lock.writeToFile({throw failure} as Runnable)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        !lock.unlockedCleanly
+
+        when:
+        lock.close()
+        lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
+
+        then:
+        !lock.unlockedCleanly
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "cannot lock a file twice in single process"() {
+        given:
+        lock(LockMode.Exclusive);
+
+        when:
+        lock(LockMode.Exclusive);
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot lock twice in single process for mixed modes"() {
+        given:
+        lock(LockMode.Exclusive);
+
+        when:
+        lock(LockMode.Shared);
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot lock twice in single process for shared mode"() {
+        given:
+        lock(LockMode.Shared);
+
+        when:
+        lock(LockMode.Shared);
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "can close a lock multiple times"() {
+        given:
+        def lock = lock(LockMode.Exclusive)
+        lock.close()
+
+        expect:
+        lock.close()
+    }
+
+    def "cannot read from file after lock has been closed"() {
+        given:
+        def lock = lock(LockMode.Exclusive)
+        lock.close()
+
+        when:
+        lock.readFromFile({} as Factory)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot write to file after lock has been closed"() {
+        given:
+        def lock = lock(LockMode.Exclusive)
+        lock.close()
+
+        when:
+        lock.writeToFile({} as Runnable)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "leaves version 1 lock file after exclusive lock on new file closed"() {
+        def file = tmpDir.file("state.bin")
+        def lockFile = tmpDir.file("state.bin.lock")
+
+        when:
+        def lock = manager.lock(file, LockMode.Exclusive, "foo")
+        lock.close()
+
+        then:
+        lock.isLockFile(lockFile)
+
+        and:
+        isVersion1LockFile(lockFile)
+    }
+
+    def "leaves empty lock file after shared lock on new file closed"() {
+        def file = tmpDir.file("state.bin")
+        def lockFile = tmpDir.file("state.bin.lock")
+
+        when:
+        def lock = manager.lock(file, LockMode.Shared, "foo")
+        lock.close()
+
+        then:
+        lock.isLockFile(lockFile)
+
+        and:
+        isEmptyLockFile(lockFile)
+    }
+
+    def "leaves version 1 lock file after lock on existing file is closed"() {
+        def file = tmpDir.file("state.bin")
+        def lockFile = tmpDir.file("state.bin.lock")
+        lockFile.withDataOutputStream {
+            it.writeByte(1)
+            it.writeBoolean(false)
+        }
+
+        when:
+        def lock = manager.lock(file, mode, "foo")
+        lock.close()
+
+        then:
+        lock.isLockFile(lockFile)
+
+        and:
+        isVersion1LockFile(lockFile)
+
+        where:
+        mode << [LockMode.Shared, LockMode.Exclusive]
+    }
+
+    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
+    def "writes version 2 lock file while exclusive lock is open"() {
+        def file = tmpDir.file("state.bin")
+        def lockFile = tmpDir.file("state.bin.lock")
+
+        when:
+        def lock = manager.lock(file, LockMode.Exclusive, "foo", "operation")
+
+        then:
+        lock.isLockFile(lockFile)
+
+        and:
+        isVersion2LockFile(lockFile)
+
+        cleanup:
+        lock?.close()
+    }
+
+    def "can acquire lock on partially written lock file"() {
+        def file = tmpDir.file("state.bin")
+        def lockFile = tmpDir.file("state.bin.lock")
+
+        when:
+        lockFile.withDataOutputStream {
+            it.writeByte(1)
+        }
+        def lock = manager.lock(file, mode, "foo")
+
+        then:
+        lock.isLockFile(lockFile)
+        lock.close()
+
+        when:
+        lockFile.withDataOutputStream {
+            it.writeByte(1)
+            it.writeBoolean(true)
+            it.writeByte(2)
+            it.writeByte(12)
+        }
+        lock = manager.lock(file, mode, "foo")
+
+        then:
+        lock.isLockFile(lockFile)
+        lock.close()
+
+        where:
+        mode << [LockMode.Shared, LockMode.Exclusive]
+    }
+
+    def "fails to acquire lock on lock file with unknown version"() {
+        def file = tmpDir.file("state.bin")
+        def lockFile = tmpDir.file("state.bin.lock")
+        lockFile.withDataOutputStream {
+            it.writeByte(125)
+        }
+
+        when:
+        manager.lock(file, mode, "foo")
+
+        then:
+        thrown(IllegalStateException)
+
+        where:
+        mode << [LockMode.Shared, LockMode.Exclusive]
+    }
+
+    private void isEmptyLockFile(TestFile lockFile) {
+        assert lockFile.isFile()
+        assert lockFile.length() == 0
+    }
+
+    private void isVersion1LockFile(TestFile lockFile) {
+        assert lockFile.isFile()
+        assert lockFile.length() == 2
+        lockFile.withDataInputStream { str ->
+            assert str.readByte() == 1
+            assert !str.readBoolean()
+        }
+    }
+
+    private void isVersion2LockFile(TestFile lockFile) {
+        assert lockFile.isFile()
+        assert lockFile.length() > 3
+        lockFile.withDataInputStream { str ->
+            assert str.readByte() == 1
+            assert !str.readBoolean()
+            assert str.readByte() == 2
+            assert str.readUTF() == '123'
+            assert str.readUTF() == 'operation'
+            assert str.read() < 0
+        }
+    }
+
+    private FileLock lock(LockMode lockMode) {
+        return manager.lock(tmpDir.file("state.bin"), lockMode, "foo")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
new file mode 100644
index 0000000..c20d7c7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.cache.internal;
+
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.cache.CacheOpenException;
+import org.gradle.cache.CacheValidator;
+import org.gradle.cache.PersistentCache;
+import org.gradle.util.GUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.gradle.cache.internal.FileLockManager.LockMode;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+ at RunWith(JMock.class)
+public class DefaultPersistentDirectoryCacheTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ProcessMetaDataProvider metaDataProvider = context.mock(ProcessMetaDataProvider.class);
+    private final FileLockManager lockManager = new DefaultFileLockManager(metaDataProvider);
+    private final Action<PersistentCache> action = context.mock(Action.class);
+    private final CacheValidator validator = context.mock(CacheValidator.class);
+    private final Map<String, String> properties = GUtil.map("prop", "value", "prop2", "other-value");
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations() {{
+            allowing(metaDataProvider).getProcessDisplayName();
+            will(returnValue("gradle"));
+            allowing(metaDataProvider).getProcessIdentifier();
+            allowing(validator).isValid();
+            will(returnValue(true));
+        }});
+    }
+
+    @Test
+    public void initialisesCacheWhenCacheDirDoesNotExist() {
+        TestFile emptyDir = tmpDir.getDir().file("dir");
+        emptyDir.assertDoesNotExist();
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(emptyDir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        assertThat(loadProperties(emptyDir.file("cache.properties")), equalTo(properties));
+    }
+
+    @Test
+    public void initializesCacheWhenPropertiesFileDoesNotExist() {
+        TestFile dir = tmpDir.getDir().file("dir").createDir();
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+
+    @Test
+    public void rebuildsCacheWhenPropertiesHaveChanged() {
+        TestFile dir = createCacheDir("prop", "other-value");
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+
+    @Test
+    public void rebuildsCacheWhenCacheRebuildRequested() {
+        TestFile dir = createCacheDir();
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.REBUILD, validator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+    
+    @Test
+    public void rebuildsCacheWhenCacheValidatorReturnsFalse() {
+        TestFile dir = createCacheDir();
+        final CacheValidator invalidator = context.mock(CacheValidator.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+            one(invalidator).isValid();
+            will(returnValue(false));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, invalidator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+
+    @Test
+    public void rebuildsCacheWhenInitialiserFailedOnPreviousOpen() {
+        TestFile dir = tmpDir.getDir().file("dir").createDir();
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+            will(throwException(failure));
+        }});
+
+        try {
+            new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager).open();
+            fail();
+        } catch (CacheOpenException e) {
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(PersistentCache.class)));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+    
+    @Test
+    public void doesNotInitializeCacheWhenCacheDirExistsAndIsNotInvalid() {
+        TestFile dir = createCacheDir();
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, action, lockManager);
+        cache.open();
+        dir.file("cache.properties").assertIsFile();
+        dir.file("some-file").assertIsFile();
+    }
+
+    private Map<String, String> loadProperties(TestFile file) {
+        Properties properties = GUtil.loadProperties(file);
+        Map<String, String> result = new HashMap<String, String>();
+        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+            result.put(entry.getKey().toString(), entry.getValue().toString());
+        }
+        return result;
+    }
+
+    private TestFile createCacheDir(String... extraProps) {
+        TestFile dir = tmpDir.getDir();
+
+        Map<String, Object> properties = new HashMap<String, Object>();
+        properties.putAll(this.properties);
+        properties.putAll(GUtil.map((Object[]) extraProps));
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, validator, properties, LockMode.Shared, null, lockManager);
+        cache.open();
+        dir.file("some-file").touch();
+        cache.close();
+
+        return dir;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
new file mode 100644
index 0000000..d7c5e33
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStoreTest.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import static org.gradle.cache.internal.FileLockManager.LockMode.None
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
+
+class DefaultPersistentDirectoryStoreTest extends Specification {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    final FileLockManager lockManager = Mock()
+    final FileLock lock = Mock()
+    final cacheDir = tmpDir.file("dir")
+    final cacheFile = cacheDir.file("some-content.bin")
+    final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", None, lockManager)
+
+    def "has useful toString() implementation"() {
+        expect:
+        store.toString() == "<display> ($cacheDir)"
+    }
+
+    def "open creates directory if it does not exist"() {
+        given:
+        cacheDir.assertDoesNotExist()
+
+        when:
+        store.open()
+
+        then:
+        cacheDir.assertIsDir()
+    }
+
+    def "open does nothing when directory already exists"() {
+        given:
+        cacheDir.createDir()
+
+        when:
+        store.open()
+
+        then:
+        notThrown(RuntimeException)
+    }
+
+    def "open locks cache directory with requested mode"() {
+        final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", Shared, lockManager)
+
+        when:
+        store.open()
+
+        then:
+        1 * lockManager.lock(cacheDir, Shared, "<display> ($cacheDir)") >> lock
+
+        when:
+        store.close()
+
+        then:
+        1 * lock.close()
+        0 * _._
+    }
+
+    def "open does not lock cache directory when None mode requested"() {
+        final store = new DefaultPersistentDirectoryStore(cacheDir, "<display>", None, lockManager)
+
+        when:
+        store.open()
+
+        then:
+        0 * _._
+
+        when:
+        store.close()
+
+        then:
+        0 * _._
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultProcessMetaDataProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultProcessMetaDataProviderTest.groovy
new file mode 100644
index 0000000..4a62257
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultProcessMetaDataProviderTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import spock.lang.Specification
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+
+class DefaultProcessMetaDataProviderTest extends Specification {
+    final ProcessEnvironment processEnvironment = Mock()
+    final DefaultProcessMetaDataProvider provider = new DefaultProcessMetaDataProvider(processEnvironment)
+
+    def "uses process PID as process identifier"() {
+        given:
+        processEnvironment.maybeGetPid() >> 1234
+
+        expect:
+        provider.processIdentifier == '1234'
+    }
+
+    def "uses 'gradle' when PID is not available"() {
+        given:
+        processEnvironment.maybeGetPid() >> null
+
+        expect:
+        provider.processIdentifier == 'gradle'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
new file mode 100644
index 0000000..4e8c7a5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import org.gradle.internal.Factory
+import org.gradle.cache.internal.btree.BTreePersistentIndexedCache
+import spock.lang.Specification
+
+class MultiProcessSafePersistentIndexedCacheTest extends Specification {
+    final FileAccess fileAccess = Mock()
+    final Factory<BTreePersistentIndexedCache<String, String>> factory = Mock()
+    final MultiProcessSafePersistentIndexedCache<String, String> cache = new MultiProcessSafePersistentIndexedCache<String, String>(factory, fileAccess)
+    final BTreePersistentIndexedCache<String, String> backingCache = Mock()
+    
+    def "opens cache on first access"() {
+        when:
+        cache.get("value")
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * factory.create() >> backingCache
+    }
+
+    def "holds read lock while getting entry from cache"() {
+        given:
+        cacheOpened()
+
+        when:
+        def result = cache.get("value")
+
+        then:
+        result == "result"
+
+        and:
+        1 * fileAccess.readFromFile(!null) >> { Factory action -> action.create() }
+        1 * backingCache.get("value") >> "result"
+        0 * _._
+    }
+
+    def "holds write lock while putting entry into cache"() {
+        given:
+        cacheOpened()
+
+        when:
+        cache.put("key", "value")
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * backingCache.put("key", "value")
+        0 * _._
+    }
+
+    def "holds write lock while removing entry from cache"() {
+        given:
+        cacheOpened()
+
+        when:
+        cache.remove("key")
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * backingCache.remove("key")
+        0 * _._
+    }
+
+    def "holds write lock while closing cache"() {
+        given:
+        cacheOpened()
+
+        when:
+        cache.close()
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * backingCache.close()
+        0 * _._
+    }
+
+    def "closes cache at end of unit of work"() {
+        given:
+        cacheOpened()
+
+        when:
+        cache.onEndWork()
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * backingCache.close()
+        0 * _._
+    }
+
+    def "does nothing at end of unit of work when cache is not open"() {
+        when:
+        cache.onEndWork()
+
+        then:
+        0 * _._
+    }
+
+    def "does nothing on close when cache is not open"() {
+        when:
+        cache.close()
+
+        then:
+        0 * _._
+    }
+
+    def "does nothing on close after cache already closed"() {
+        cacheOpened()
+
+        when:
+        cache.close()
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * backingCache.close()
+        0 * _._
+
+        when:
+        cache.close()
+
+        then:
+        0 * _._
+    }
+
+    def cacheOpened() {
+        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * factory.create() >> backingCache
+        
+        cache.get("something")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
new file mode 100644
index 0000000..33ffcb2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.cache.internal
+
+import org.gradle.internal.Factory
+import org.gradle.cache.internal.FileLockManager.LockMode
+import spock.lang.Specification
+
+class OnDemandFileAccessTest extends Specification {
+    final FileLockManager manager = Mock()
+    final FileLock targetLock = Mock()
+    final File file = new File("some-target-file")
+    final OnDemandFileAccess lock = new OnDemandFileAccess(file, "some-lock", manager)
+
+    def "acquires shared lock to read file"() {
+        def action = {} as Factory
+
+        when:
+        lock.readFromFile(action)
+
+        then:
+        1 * manager.lock(file, LockMode.Shared, "some-lock") >> targetLock
+        1 * targetLock.readFromFile(action)
+        1 * targetLock.close()
+        0 * targetLock._
+    }
+
+    def "acquires exclusive lock to write to file"() {
+        def action = {} as Runnable
+
+        when:
+        lock.writeToFile(action)
+
+        then:
+        1 * manager.lock(file, LockMode.Exclusive, "some-lock") >> targetLock
+        1 * targetLock.writeToFile(action)
+        1 * targetLock.close()
+        0 * targetLock._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
new file mode 100644
index 0000000..8fb2c49
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.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.cache.internal
+
+import org.gradle.cache.DefaultSerializer
+import org.gradle.cache.PersistentStateCache
+import org.gradle.cache.Serializer
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class SimpleStateCacheTest extends Specification {
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    final FileAccess fileAccess = Mock()
+    final Serializer<String> serializer = new DefaultSerializer<String>()
+    final SimpleStateCache<String> cache = new SimpleStateCache<String>(tmpDir.file("state.bin"), fileAccess, serializer)
+
+    def "returns null when file does not exist"() {
+        when:
+        def result = cache.get()
+
+        then:
+        result == null
+        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+    }
+    
+    def "get returns last value written to file"() {
+        when:
+        cache.set('some value')
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+        tmpDir.file('state.bin').assertIsFile()
+
+        when:
+        def result = cache.get()
+
+        then:
+        result == 'some value'
+        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+    }
+
+    def "update provides access to cached value"() {
+        when:
+        cache.set("foo")
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+
+        when:
+        cache.update({ value ->
+            assert value == "foo"
+            return "foo bar"
+        } as PersistentStateCache.UpdateAction)
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+
+        when:
+        def result = cache.get()
+
+        then:
+        result == "foo bar"
+        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+    }
+
+    def "update does not explode when no existing value"() {
+        when:
+        cache.update({ value ->
+            assert value == null
+            return "bar"
+        } as PersistentStateCache.UpdateAction)
+
+        then:
+        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+
+        when:
+        def result = cache.get()
+
+        then:
+        result == "bar"
+        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
new file mode 100644
index 0000000..efcf254
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.cache.internal.btree;
+
+import org.gradle.cache.DefaultSerializer;
+import org.gradle.cache.Serializer;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.*;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+public class BTreePersistentIndexedCacheTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final Serializer<String> stringSerializer = new DefaultSerializer<String>();
+    private final Serializer<Integer> integerSerializer = new DefaultSerializer<Integer>();
+    private BTreePersistentIndexedCache<String, Integer> cache;
+    private TestFile cacheFile;
+
+    @Before
+    public void setup() {
+        cacheFile = tmpDir.file("cache.bin");
+        cache = new BTreePersistentIndexedCache<String, Integer>(cacheFile, stringSerializer, integerSerializer, (short) 4, 100);
+    }
+
+    @Test
+    public void getReturnsNullWhenEntryDoesNotExist() {
+        assertNull(cache.get("unknown"));
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntries() {
+        checkAdds(1, 2, 3, 4, 5);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntriesInReverseOrder() {
+        checkAdds(5, 4, 3, 2, 1);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntriesOverMultipleIndexBlocks() {
+        checkAdds(3, 2, 11, 5, 7, 1, 10, 8, 9, 4, 6, 0);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntriesAfterReopen() {
+        checkAdds(1, 2, 3, 4);
+
+        cache.reset();
+
+        checkAdds(5, 6, 7, 8);
+        cache.verify();
+    }
+    
+    @Test
+    public void persistsReplacedEntries() {
+
+        cache.put("key_1", 1);
+        cache.put("key_2", 2);
+        cache.put("key_3", 3);
+        cache.put("key_4", 4);
+        cache.put("key_5", 5);
+
+        cache.put("key_1", 1);
+        cache.put("key_4", 12);
+
+        assertThat(cache.get("key_1"), equalTo(1));
+        assertThat(cache.get("key_2"), equalTo(2));
+        assertThat(cache.get("key_3"), equalTo(3));
+        assertThat(cache.get("key_4"), equalTo(12));
+        assertThat(cache.get("key_5"), equalTo(5));
+
+        cache.reset();
+
+        assertThat(cache.get("key_1"), equalTo(1));
+        assertThat(cache.get("key_2"), equalTo(2));
+        assertThat(cache.get("key_3"), equalTo(3));
+        assertThat(cache.get("key_4"), equalTo(12));
+        assertThat(cache.get("key_5"), equalTo(5));
+
+        cache.verify();
+    }
+
+    @Test
+    public void reusesEmptySpaceWhenPuttingEntries() {
+        BTreePersistentIndexedCache<String, String> cache = new BTreePersistentIndexedCache<String, String>(cacheFile, stringSerializer, stringSerializer, (short) 4, 100);
+
+        cache.put("key_1", "abcd");
+        cache.put("key_2", "abcd");
+        cache.put("key_3", "abcd");
+        cache.put("key_4", "abcd");
+        cache.put("key_5", "abcd");
+
+        long len = cacheFile.length();
+        assertThat(len, greaterThan(0L));
+
+        cache.put("key_1", "1234");
+        assertThat(cacheFile.length(), equalTo(len));
+
+        cache.remove("key_1");
+        cache.put("key_new", "a1b2");
+        assertThat(cacheFile.length(), equalTo(len));
+
+        cache.put("key_new", "longer value");
+        assertThat(cacheFile.length(), greaterThan(len));
+        len = cacheFile.length();
+
+        cache.put("key_1", "1234");
+        assertThat(cacheFile.length(), equalTo(len));
+    }
+    
+    @Test
+    public void canHandleLargeNumberOfEntries() {
+
+        int count = 2000;
+        List<Integer> values = new ArrayList<Integer>();
+        for (int i = 0; i < count; i++) {
+            values.add(i);
+        }
+
+        checkAddsAndRemoves(null, values);
+
+        long len = cacheFile.length();
+
+        checkAddsAndRemoves(Collections.<Integer>reverseOrder(), values);
+
+        // need to make this better
+        assertThat(cacheFile.length(), lessThan((long)(1.4 * len)));
+
+        checkAdds(values);
+        
+        // need to make this better
+        assertThat(cacheFile.length(), lessThan((long) (1.4 * 1.4 * len)));
+    }
+
+    @Test
+    public void persistsRemovalOfEntries() {
+        checkAddsAndRemoves(1, 2, 3, 4, 5);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsRemovalOfEntriesInReverse() {
+        checkAddsAndRemoves(Collections.<Integer>reverseOrder(), 1, 2, 3, 4, 5);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsRemovalOfEntriesOverMultipleIndexBlocks() {
+        checkAddsAndRemoves(4, 12, 9, 1, 3, 10, 11, 7, 8, 2, 5, 6);
+        cache.verify();
+    }
+
+    @Test
+    public void removalRedistributesRemainingEntriesWithLeftSibling() {
+        // Ends up with: 1 2 3 -> 4 <- 5 6
+        checkAdds(1, 2, 5, 6, 4, 3);
+        cache.verify();
+        cache.remove("key_5");
+        cache.verify();
+    }
+
+    @Test
+    public void removalMergesRemainingEntriesIntoLeftSibling() {
+        // Ends up with: 1 2 -> 3 <- 4 5
+        checkAdds(1, 2, 4, 5, 3);
+        cache.verify();
+        cache.remove("key_4");
+        cache.verify();
+    }
+
+    @Test
+    public void removalRedistributesRemainingEntriesWithRightSibling() {
+        // Ends up with: 1 2 -> 3 <- 4 5 6
+        checkAdds(1, 2, 4, 5, 3, 6);
+        cache.verify();
+        cache.remove("key_2");
+        cache.verify();
+    }
+
+    @Test
+    public void removalMergesRemainingEntriesIntoRightSibling() {
+        // Ends up with: 1 2 -> 3 <- 4 5
+        checkAdds(1, 2, 4, 5, 3);
+        cache.verify();
+        cache.remove("key_2");
+        cache.verify();
+    }
+
+    @Test
+    public void handlesBadlyFormedCacheFile() throws IOException {
+        cacheFile.assertIsFile();
+        cacheFile.write("some junk");
+
+        BTreePersistentIndexedCache<String, Integer> cache = new BTreePersistentIndexedCache<String, Integer>(cacheFile, stringSerializer, integerSerializer);
+
+        assertNull(cache.get("key_1"));
+        cache.put("key_1", 99);
+
+        RandomAccessFile file = new RandomAccessFile(cacheFile, "rw");
+        file.setLength(file.length() - 10);
+
+        cache.reset();
+
+        assertNull(cache.get("key_1"));
+        cache.verify();
+    }
+
+    @Test
+    public void canUseFileAsKey() {
+        BTreePersistentIndexedCache<File, Integer> cache = new BTreePersistentIndexedCache<File, Integer>(cacheFile, new DefaultSerializer<File>(), integerSerializer);
+
+        cache.put(new File("file"), 1);
+        cache.put(new File("dir/file"), 2);
+        cache.put(new File("File"), 3);
+
+        assertThat(cache.get(new File("file")), equalTo(1));
+        assertThat(cache.get(new File("dir/file")), equalTo(2));
+        assertThat(cache.get(new File("File")), equalTo(3));
+    }
+
+    @Test
+    public void handlesKeysWithSameHashCode() {
+        String key1 = new String(new byte[]{2, 31});
+        String key2 = new String(new byte[]{1, 62});
+        cache.put(key1, 1);
+        cache.put(key2, 2);
+
+        assertThat(cache.get(key1), equalTo(1));
+        assertThat(cache.get(key2), equalTo(2));
+    }
+
+    private void checkAdds(Integer... values) {
+        checkAdds(Arrays.asList(values));
+    }
+
+    private Map<String, Integer> checkAdds(Iterable<Integer> values) {
+        Map<String, Integer> added = new LinkedHashMap<String, Integer>();
+
+        for (Integer value : values) {
+            String key = String.format("key_%d", value);
+            cache.put(key, value);
+            added.put(String.format("key_%d", value), value);
+        }
+
+        for (Map.Entry<String, Integer> entry : added.entrySet()) {
+            assertThat(cache.get(entry.getKey()), equalTo(entry.getValue()));
+        }
+
+        cache.reset();
+
+        for (Map.Entry<String, Integer> entry : added.entrySet()) {
+            assertThat(cache.get(entry.getKey()), equalTo(entry.getValue()));
+        }
+
+        return added;
+    }
+
+    private void checkAddsAndRemoves(Integer... values) {
+        checkAddsAndRemoves(null, values);
+    }
+
+    private void checkAddsAndRemoves(Comparator<Integer> comparator, Integer... values) {
+        checkAddsAndRemoves(comparator, Arrays.asList(values));
+    }
+
+    private void checkAddsAndRemoves(Comparator<Integer> comparator, Collection<Integer> values) {
+        checkAdds(values);
+
+        List<Integer> deleteValues = new ArrayList<Integer>(values);
+        Collections.sort(deleteValues, comparator);
+        for (Integer value : deleteValues) {
+            String key = String.format("key_%d", value);
+            assertThat(cache.get(key), notNullValue());
+            cache.remove(key);
+            assertThat(cache.get(key), nullValue());
+        }
+
+        cache.reset();
+        cache.verify();
+
+        for (Integer value : deleteValues) {
+            String key = String.format("key_%d", value);
+            assertThat(cache.get(key), nullValue());
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultProjectEvaluatorTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultProjectEvaluatorTest.java
deleted file mode 100644
index 0b85c76..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultProjectEvaluatorTest.java
+++ /dev/null
@@ -1,127 +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.configuration;
-
-import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.hamcrest.Matchers;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-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 static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultProjectEvaluatorTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final ProjectEvaluationListener listener = context.mock(ProjectEvaluationListener.class);
-    private final ProjectEvaluator delegate = context.mock(ProjectEvaluator.class, "delegate");
-    private final ProjectStateInternal state = context.mock(ProjectStateInternal.class);
-    private final DefaultProjectEvaluator evaluator = new DefaultProjectEvaluator(delegate);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(project).getProjectEvaluationBroadcaster();
-            will(returnValue(listener));
-        }});
-    }
-
-    @Test
-    public void doesNothingWhenProjectHasAlreadyBeenExecuted() {
-        context.checking(new Expectations() {{
-            allowing(state).getExecuted();
-            will(returnValue(true));
-        }});
-
-        evaluator.evaluate(project, state);
-    }
-    
-    @Test
-    public void createsAndExecutesScriptAndNotifiesListener() {
-        context.checking(new Expectations() {{
-            allowing(state).getExecuted();
-            will(returnValue(false));
-
-            Sequence sequence = context.sequence("seq");
-
-            one(listener).beforeEvaluate(project);
-            inSequence(sequence);
-
-            one(state).setExecuting(true);
-            inSequence(sequence);
-
-            one(delegate).evaluate(project, state);
-            inSequence(sequence);
-
-            one(state).setExecuting(false);
-            inSequence(sequence);
-
-            one(state).executed();
-            inSequence(sequence);
-
-            one(listener).afterEvaluate(project, state);
-            inSequence(sequence);
-        }});
-
-        evaluator.evaluate(project, state);
-    }
-
-    @Test
-    public void notifiesListenerOnFailure() {
-        final RuntimeException failure = new RuntimeException();
-
-        context.checking(new Expectations() {{
-            allowing(state).getExecuted();
-            will(returnValue(false));
-
-            Sequence sequence = context.sequence("seq");
-
-            one(listener).beforeEvaluate(project);
-            inSequence(sequence);
-
-            one(state).setExecuting(true);
-            inSequence(sequence);
-
-            one(delegate).evaluate(project, state);
-            will(throwException(failure));
-            inSequence(sequence);
-
-            one(state).setExecuting(false);
-            inSequence(sequence);
-
-            one(state).executed();
-            inSequence(sequence);
-            
-            one(listener).afterEvaluate(project, state);
-            inSequence(sequence);
-        }});
-
-        try {
-            evaluator.evaluate(project, state);
-            fail();
-        } catch (RuntimeException e) {
-            assertThat(e, Matchers.sameInstance(failure));
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
old mode 100644
new mode 100755
index 551136b..d32ee50
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
@@ -15,11 +15,11 @@
  */
 package org.gradle.configuration;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.artifacts.dsl.ClasspathScriptTransformer;
 import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptHandlerInternal;
-import org.gradle.api.internal.project.ServiceRegistry;
 import org.gradle.groovy.scripts.*;
 import org.gradle.logging.LoggingManagerInternal;
 import org.jmock.Expectations;
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java
index 1fe9fac..0760af7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java
@@ -17,19 +17,19 @@ package org.gradle.configuration;
 
 import org.gradle.api.internal.resource.Resource;
 import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
 
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class ImportsScriptSourceTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private ScriptSource backingSource;
     private ImportsReader importsReader;
     private ImportsScriptSource source;
@@ -37,7 +37,6 @@ public class ImportsScriptSourceTest {
 
     @Before
     public void setUp() {
-        context.setImposteriser(ClassImposteriser.INSTANCE);
         backingSource = context.mock(ScriptSource.class);
         importsReader = context.mock(ImportsReader.class);
         resource = context.mock(Resource.class);
@@ -74,6 +73,19 @@ public class ImportsScriptSourceTest {
     }
 
     @Test
+    public void doesNotPrependImportsWhenScriptContainsOnlyWhitespace() {
+        context.checking(new Expectations(){{
+            one(backingSource).getResource();
+            will(returnValue(resource));
+
+            one(resource).getText();
+            will(returnValue(" \n\t"));
+        }});
+
+        assertThat(source.getResource().getText(), equalTo(" \n\t"));
+    }
+
+    @Test
     public void delegatesAllOtherMethodsToBackingScriptSource() {
         context.checking(new Expectations(){{
             one(backingSource).getClassName();
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.java
new file mode 100644
index 0000000..9950f31
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+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 static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class LifecycleProjectEvaluatorTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final ProjectEvaluationListener listener = context.mock(ProjectEvaluationListener.class);
+    private final ProjectEvaluator delegate = context.mock(ProjectEvaluator.class, "delegate");
+    private final ProjectStateInternal state = context.mock(ProjectStateInternal.class);
+    private final LifecycleProjectEvaluator evaluator = new LifecycleProjectEvaluator(delegate);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getProjectEvaluationBroadcaster();
+            will(returnValue(listener));
+        }});
+    }
+
+    @Test
+    public void doesNothingWhenProjectHasAlreadyBeenExecuted() {
+        context.checking(new Expectations() {{
+            allowing(state).getExecuted();
+            will(returnValue(true));
+        }});
+
+        evaluator.evaluate(project, state);
+    }
+    
+    @Test
+    public void createsAndExecutesScriptAndNotifiesListener() {
+        context.checking(new Expectations() {{
+            allowing(state).getExecuted();
+            will(returnValue(false));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(listener).beforeEvaluate(project);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(delegate).evaluate(project, state);
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(state).executed();
+            inSequence(sequence);
+
+            one(listener).afterEvaluate(project, state);
+            inSequence(sequence);
+        }});
+
+        evaluator.evaluate(project, state);
+    }
+
+    @Test
+    public void notifiesListenerOnFailure() {
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            allowing(state).getExecuted();
+            will(returnValue(false));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(listener).beforeEvaluate(project);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(delegate).evaluate(project, state);
+            will(throwException(failure));
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(state).executed();
+            inSequence(sequence);
+            
+            one(listener).afterEvaluate(project, state);
+            inSequence(sequence);
+        }});
+
+        try {
+            evaluator.evaluate(project, state);
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, Matchers.sameInstance(failure));
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.groovy
new file mode 100644
index 0000000..6a39fe5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.groovy
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2011 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.execution
+
+import org.gradle.api.internal.GradleInternal
+import spock.lang.Specification
+
+class DefaultBuildExecuterTest extends Specification {
+    final GradleInternal gradleInternal = Mock()
+
+    def "select method calls configure method on first configuration action"() {
+        BuildConfigurationAction action1 = Mock()
+        BuildConfigurationAction action2 = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([action1, action2], [])
+
+        when:
+        buildExecution.select(gradleInternal)
+
+        then:
+        1 * action1.configure(!null)
+        0 * _._
+    }
+
+    def "calls next action in chain when configuration action calls proceed"() {
+        BuildConfigurationAction action1 = Mock()
+        BuildConfigurationAction action2 = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([action1, action2], [])
+
+        when:
+        buildExecution.select(gradleInternal)
+
+        then:
+        1 * action1.configure(!null) >> { it[0].proceed() }
+
+        and:
+        1 * action2.configure(!null)
+    }
+
+    def "does nothing when last configuration action calls proceed"() {
+        BuildConfigurationAction action1 = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([action1], [])
+
+        when:
+        buildExecution.select(gradleInternal)
+
+        then:
+        1 * action1.configure(!null) >> { it[0].proceed() }
+        0 * _._
+    }
+
+    def "execute method calls execute method on first execution action"() {
+        BuildExecutionAction action1 = Mock()
+        BuildExecutionAction action2 = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([], [action1, action2])
+        buildExecution.select(gradleInternal)
+
+        when:
+        buildExecution.execute()
+
+        then:
+        1 * action1.execute(!null)
+        0 * _._
+    }
+
+    def "calls next action in chain when execution action calls proceed"() {
+        BuildExecutionAction action1 = Mock()
+        BuildExecutionAction action2 = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([], [action1, action2])
+        buildExecution.select(gradleInternal)
+
+        when:
+        buildExecution.execute()
+
+        then:
+        1 * action1.execute(!null) >> { it[0].proceed() }
+
+        and:
+        1 * action2.execute(!null)
+    }
+
+    def "does nothing when last execution action calls proceed"() {
+        BuildExecutionAction action1 = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([], [action1])
+        buildExecution.select(gradleInternal)
+
+        when:
+        buildExecution.execute()
+
+        then:
+        1 * action1.execute(!null) >> { it[0].proceed() }
+        0 * _._
+    }
+
+    def "makes Gradle instance available to actions"() {
+        BuildConfigurationAction configurationAction = Mock()
+        BuildExecutionAction executionAction = Mock()
+
+        given:
+        def buildExecution = new DefaultBuildExecuter([configurationAction], [executionAction])
+
+        when:
+        buildExecution.select(gradleInternal)
+        buildExecution.execute()
+
+        then:
+        1 * configurationAction.configure(!null) >> {
+            assert it[0].gradle ==gradleInternal
+        }
+        1 * executionAction.execute(!null) >> {
+            assert it[0].gradle ==gradleInternal
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.java
deleted file mode 100644
index 0269772..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.java
+++ /dev/null
@@ -1,109 +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.execution;
-
-import org.gradle.api.Task;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.TaskContainerInternal;
-import org.gradle.api.specs.Spec;
-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.util.Collections;
-import java.util.List;
-
-import static org.gradle.util.WrapUtil.toList;
-import static org.gradle.util.WrapUtil.toSet;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class DefaultBuildExecuterTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final GradleInternal gradle = context.mock(GradleInternal.class);
-    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-
-    @Before
-    public void setup() {
-        context.checking(new Expectations(){{
-            allowing(gradle).getTaskGraph();
-            will(returnValue(taskExecuter));
-            allowing(gradle).getDefaultProject();
-            will(returnValue(project));
-        }});
-    }
-    
-    @Test
-    public void usesProjectDefaultExecuterWhenNoTaskNamesProvided() {
-        DefaultBuildExecuter executer = new DefaultBuildExecuter(Collections.EMPTY_LIST, Collections.EMPTY_LIST);
-        assertThat(executer.getDelegate(), instanceOf(ProjectDefaultsBuildExecuter.class));
-    }
-
-    @Test
-    public void usesNameResolvingExecuterWhenTaskNamesProvided() {
-        List<String> taskNames = toList("a", "b");
-        DefaultBuildExecuter executer = new DefaultBuildExecuter(taskNames, Collections.EMPTY_LIST);
-        assertThat(executer.getDelegate(), instanceOf(TaskNameResolvingBuildExecuter.class));
-        TaskNameResolvingBuildExecuter delegate = (TaskNameResolvingBuildExecuter) executer.getDelegate();
-        assertThat(delegate.getNames(), equalTo(taskNames));
-    }
-
-    @Test
-    public void usesProjectDefaultExecuterAndNameFilterWhenExcludePatternsProvided() {
-        DefaultBuildExecuter executer = new DefaultBuildExecuter(Collections.EMPTY_LIST, toList("b"));
-        assertThat(executer.getDelegate(), instanceOf(ProjectDefaultsBuildExecuter.class));
-
-        checkNameFilterApplied(executer);
-    }
-
-    @Test
-    public void usesNameResolvingExecuterAndNameFilterWhenTaskNamesAndExcludePatternsProvided() {
-        DefaultBuildExecuter executer = new DefaultBuildExecuter(toList("a"), toList("b"));
-        assertThat(executer.getDelegate(), instanceOf(TaskNameResolvingBuildExecuter.class));
-        TaskNameResolvingBuildExecuter delegate = (TaskNameResolvingBuildExecuter) executer.getDelegate();
-        assertThat(delegate.getNames(), equalTo(toList("a")));
-
-        checkNameFilterApplied(executer);
-    }
-
-    private void checkNameFilterApplied(DefaultBuildExecuter executer) {
-        final BuildExecuter delegate = context.mock(BuildExecuter.class);
-        executer.setDelegate(delegate);
-
-        context.checking(new Expectations(){{
-            Task task = context.mock(Task.class);
-            TaskContainerInternal taskContainer = context.mock(TaskContainerInternal.class);
-            allowing(project).getSubprojects();
-            will(returnValue(toSet()));
-            allowing(project).getTasks();
-            will(returnValue(taskContainer));
-            one(taskContainer).findByName("b");
-            will(returnValue(task));
-            allowing(task).getName();
-            will(returnValue("b"));
-            one(taskExecuter).useFilter(with(notNullValue(Spec.class)));
-            one(delegate).select(gradle);
-        }});
-
-        executer.select(gradle);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
index b4c8df4..aced70f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
@@ -16,22 +16,23 @@
 
 package org.gradle.execution;
 
-import org.gradle.api.Action;
 import org.gradle.api.CircularReferenceException;
-import org.gradle.api.DefaultTask;
 import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionGraphListener;
 import org.gradle.api.execution.TaskExecutionListener;
-import org.gradle.api.internal.AbstractTask;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.TaskStateInternal;
 import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.api.tasks.TaskDependency;
 import org.gradle.api.tasks.TaskState;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.TestClosure;
+import org.hamcrest.Description;
 import org.jmock.Expectations;
+import org.jmock.api.Invocation;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
@@ -40,10 +41,11 @@ import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
 
-import static org.gradle.util.HelperUtil.*;
-import static org.gradle.util.WrapUtil.*;
+import static org.gradle.util.HelperUtil.createRootProject;
+import static org.gradle.util.HelperUtil.toClosure;
+import static org.gradle.util.WrapUtil.toList;
+import static org.gradle.util.WrapUtil.toSet;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
@@ -53,9 +55,9 @@ import static org.junit.Assert.*;
 @RunWith(JMock.class)
 public class DefaultTaskGraphExecuterTest {
 
-    JUnit4Mockery context = new JUnit4Mockery();
+    JUnit4Mockery context = new JUnit4GroovyMockery();
     private final ListenerManager listenerManager = context.mock(ListenerManager.class);
-    TaskGraphExecuter taskExecuter;
+    DefaultTaskGraphExecuter taskExecuter;
     ProjectInternal root;
     List<Task> executedTasks = new ArrayList<Task>();
 
@@ -73,10 +75,10 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testExecutesTasksInDependencyOrder() {
-        Task a = createTask("a");
-        Task b = createTask("b", a);
-        Task c = createTask("c", b, a);
-        Task d = createTask("d", c);
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c", b, a);
+        Task d = task("d", c);
 
         taskExecuter.execute(toList(d));
 
@@ -85,10 +87,10 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testExecutesDependenciesInNameOrder() {
-        Task a = createTask("a");
-        Task b = createTask("b");
-        Task c = createTask("c");
-        Task d = createTask("d", b, a, c);
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+        Task d = task("d", b, a, c);
 
         taskExecuter.execute(toList(d));
 
@@ -97,9 +99,9 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testExecutesTasksInASingleBatchInNameOrder() {
-        Task a = createTask("a");
-        Task b = createTask("b");
-        Task c = createTask("c");
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
 
         taskExecuter.execute(toList(b, c, a));
 
@@ -108,10 +110,10 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testExecutesBatchesInOrderAdded() {
-        Task a = createTask("a");
-        Task b = createTask("b");
-        Task c = createTask("c");
-        Task d = createTask("d");
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c");
+        Task d = task("d");
 
         taskExecuter.addTasks(toList(c, b));
         taskExecuter.addTasks(toList(d, a));
@@ -122,11 +124,11 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testExecutesSharedDependenciesOfBatchesOnceOnly() {
-        Task a = createTask("a");
-        Task b = createTask("b");
-        Task c = createTask("c", a, b);
-        Task d = createTask("d");
-        Task e = createTask("e", b, d);
+        Task a = task("a");
+        Task b = task("b");
+        Task c = task("c", a, b);
+        Task d = task("d");
+        Task e = task("e", b, d);
 
         taskExecuter.addTasks(toList(c));
         taskExecuter.addTasks(toList(e));
@@ -137,10 +139,10 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testAddTasksAddsDependencies() {
-        Task a = createTask("a");
-        Task b = createTask("b", a);
-        Task c = createTask("c", b, a);
-        Task d = createTask("d", c);
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c", b, a);
+        Task d = task("d", c);
         taskExecuter.addTasks(toList(d));
 
         assertTrue(taskExecuter.hasTask(":a"));
@@ -156,10 +158,10 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testGetAllTasksReturnsTasksInExecutionOrder() {
-        Task d = createTask("d");
-        Task c = createTask("c");
-        Task b = createTask("b", d, c);
-        Task a = createTask("a", b);
+        Task d = task("d");
+        Task c = task("c");
+        Task b = task("b", d, c);
+        Task a = task("a", b);
         taskExecuter.addTasks(toList(a));
 
         assertThat(taskExecuter.getAllTasks(), equalTo(toList(c, d, b, a)));
@@ -176,7 +178,7 @@ public class DefaultTaskGraphExecuterTest {
         }
 
         try {
-            taskExecuter.hasTask(createTask("a"));
+            taskExecuter.hasTask(task("a"));
             fail();
         } catch (IllegalStateException e) {
             assertThat(e.getMessage(), equalTo(
@@ -194,8 +196,8 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testDiscardsTasksAfterExecute() {
-        Task a = createTask("a");
-        Task b = createTask("b", a);
+        Task a = task("a");
+        Task b = task("b", a);
 
         taskExecuter.addTasks(toList(b));
         taskExecuter.execute();
@@ -207,9 +209,9 @@ public class DefaultTaskGraphExecuterTest {
 
     @Test
     public void testCanExecuteMultipleTimes() {
-        Task a = createTask("a");
-        Task b = createTask("b", a);
-        Task c = createTask("c");
+        Task a = task("a");
+        Task b = task("b", a);
+        Task c = task("c");
 
         taskExecuter.addTasks(toList(b));
         taskExecuter.execute();
@@ -229,9 +231,9 @@ public class DefaultTaskGraphExecuterTest {
     @Test
     public void testCannotAddTaskWithCircularReference() {
         Task a = createTask("a");
-        Task b = createTask("b", a);
-        Task c = createTask("c", b);
-        a.dependsOn(c);
+        Task b = task("b", a);
+        Task c = task("c", b);
+        dependsOn(a, c);
 
         try {
             taskExecuter.addTasks(toList(c));
@@ -244,7 +246,7 @@ public class DefaultTaskGraphExecuterTest {
     @Test
     public void testNotifiesGraphListenerBeforeExecute() {
         final TaskExecutionGraphListener listener = context.mock(TaskExecutionGraphListener.class);
-        Task a = createTask("a");
+        Task a = task("a");
 
         taskExecuter.addTaskExecutionGraphListener(listener);
         taskExecuter.addTasks(toList(a));
@@ -259,7 +261,7 @@ public class DefaultTaskGraphExecuterTest {
     @Test
     public void testExecutesWhenReadyClosureBeforeExecute() {
         final TestClosure runnable = context.mock(TestClosure.class);
-        Task a = createTask("a");
+        Task a = task("a");
 
         taskExecuter.whenReady(toClosure(runnable));
 
@@ -275,8 +277,8 @@ public class DefaultTaskGraphExecuterTest {
     @Test
     public void testNotifiesTaskListenerAsTasksAreExecuted() {
         final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
-        final Task a = createTask("a");
-        final Task b = createTask("b");
+        final Task a = task("a");
+        final Task b = task("b");
 
         taskExecuter.addTaskExecutionListener(listener);
         taskExecuter.addTasks(toList(a, b));
@@ -295,12 +297,7 @@ public class DefaultTaskGraphExecuterTest {
     public void testNotifiesTaskListenerWhenTaskFails() {
         final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
         final RuntimeException failure = new RuntimeException();
-        final Task a = createTask("a");
-        a.doLast(new Action<Task>() {
-            public void execute(Task task) {
-                throw failure;
-            }
-        });
+        final Task a = brokenTask("a", failure);
 
         taskExecuter.addTaskExecutionListener(listener);
         taskExecuter.addTasks(toList(a));
@@ -313,16 +310,100 @@ public class DefaultTaskGraphExecuterTest {
         try {
             taskExecuter.execute();
             fail();
-        } catch (TaskExecutionException e) {
-            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+        
+        assertThat(executedTasks, equalTo(toList(a)));
+    }
+
+    @Test
+    public void testStopsExecutionOnFirstFailureWhenNoFailureHandlerProvided() {
+        final RuntimeException failure = new RuntimeException();
+        final Task a = brokenTask("a", failure);
+        final Task b = task("b");
+
+        taskExecuter.addTasks(toList(a, b));
+
+        try {
+            taskExecuter.execute();
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+
+        assertThat(executedTasks, equalTo(toList(a)));
+    }
+    
+    @Test
+    public void testStopsExecutionOnFailureWhenFailureHandlerIndicatesThatExecutionShouldStop() {
+        final TaskFailureHandler handler = context.mock(TaskFailureHandler.class);
+
+        final RuntimeException failure = new RuntimeException();
+        final RuntimeException wrappedFailure = new RuntimeException();
+        final Task a = brokenTask("a", failure);
+        final Task b = task("b");
+
+        taskExecuter.useFailureHandler(handler);
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations(){{
+            one(handler).onTaskFailure(a);
+            will(throwException(wrappedFailure));
+        }});
+        try {
+            taskExecuter.execute();
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(wrappedFailure));
         }
+
+        assertThat(executedTasks, equalTo(toList(a)));
+    }
+    
+    @Test
+    public void testContinuesExecutionOnFailureWhenFailureHandlerIndicatesThatExecutionShouldContinue() {
+        final TaskFailureHandler handler = context.mock(TaskFailureHandler.class);
+
+        final RuntimeException failure = new RuntimeException();
+        final Task a = brokenTask("a", failure);
+        final Task b = task("b");
+
+        taskExecuter.useFailureHandler(handler);
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations(){{
+            one(handler).onTaskFailure(a);
+        }});
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, b)));
     }
+    
+    @Test
+    public void testDoesNotAttemptToExecuteTasksWhoseDependenciesFailedToExecute() {
+        final TaskFailureHandler handler = context.mock(TaskFailureHandler.class);
+
+        final RuntimeException failure = new RuntimeException();
+        final Task a = brokenTask("a", failure);
+        final Task b = task("b", a);
+        final Task c = task("c");
+
+        taskExecuter.useFailureHandler(handler);
+        taskExecuter.addTasks(toList(b, c));
 
+        context.checking(new Expectations() {{
+            one(handler).onTaskFailure(a);
+        }});
+        taskExecuter.execute();
+        assertThat(executedTasks, equalTo(toList(a, c)));
+    }
+    
     @Test
     public void testNotifiesBeforeTaskClosureAsTasksAreExecuted() {
         final TestClosure runnable = context.mock(TestClosure.class);
-        final Task a = createTask("a");
-        final Task b = createTask("b");
+        final Task a = task("a");
+        final Task b = task("b");
 
         taskExecuter.beforeTask(toClosure(runnable));
 
@@ -339,8 +420,8 @@ public class DefaultTaskGraphExecuterTest {
     @Test
     public void testNotifiesAfterTaskClosureAsTasksAreExecuted() {
         final TestClosure runnable = context.mock(TestClosure.class);
-        final Task a = createTask("a");
-        final Task b = createTask("b");
+        final Task a = task("a");
+        final Task b = task("b");
 
         taskExecuter.afterTask(toClosure(runnable));
 
@@ -355,9 +436,9 @@ public class DefaultTaskGraphExecuterTest {
     }
 
     @Test
-    public void doesNotAddFilteredTasks() {
-        final Task a = createTask("a", createTask("a-dep"));
-        Task b = createTask("b");
+    public void doesNotExecuteFilteredTasks() {
+        final Task a = task("a", task("a-dep"));
+        Task b = task("b");
         Spec<Task> spec = new Spec<Task>() {
             public boolean isSatisfiedBy(Task element) {
                 return element != a;
@@ -367,13 +448,17 @@ public class DefaultTaskGraphExecuterTest {
         taskExecuter.useFilter(spec);
         taskExecuter.addTasks(toList(a, b));
         assertThat(taskExecuter.getAllTasks(), equalTo(toList(b)));
+
+        taskExecuter.execute();
+        
+        assertThat(executedTasks, equalTo(toList(b)));
     }
 
     @Test
-    public void doesNotAddFilteredDependencies() {
-        final Task a = createTask("a", createTask("a-dep"));
-        Task b = createTask("b");
-        Task c = createTask("c", a, b);
+    public void doesNotExecuteFilteredDependencies() {
+        final Task a = task("a", task("a-dep"));
+        Task b = task("b");
+        Task c = task("c", a, b);
         Spec<Task> spec = new Spec<Task>() {
             public boolean isSatisfiedBy(Task element) {
                 return element != a;
@@ -383,20 +468,112 @@ public class DefaultTaskGraphExecuterTest {
         taskExecuter.useFilter(spec);
         taskExecuter.addTasks(toList(c));
         assertThat(taskExecuter.getAllTasks(), equalTo(toList(b, c)));
+        
+        taskExecuter.execute();
+                
+        assertThat(executedTasks, equalTo(toList(b, c)));
     }
 
-    private Task createTask(String name, final Task... dependsOn) {
-        final TaskInternal task = AbstractTask.injectIntoNewInstance(root, name, new Callable<TaskInternal>() {
-            public TaskInternal call() throws Exception {
-                return new DefaultTask();
-            }
-        });
-        task.dependsOn((Object[]) dependsOn);
-        task.doFirst(new Action<Task>() {
-            public void execute(Task task) {
-                executedTasks.add(task);
+    @Test
+    public void willExecuteATaskWhoseDependenciesHaveBeenFilteredOnFailure() {
+        final TaskFailureHandler handler = context.mock(TaskFailureHandler.class);
+        final RuntimeException failure = new RuntimeException();
+        final Task a = brokenTask("a", failure);
+        final Task b = task("b");
+        final Task c = task("c", b);
+
+        taskExecuter.useFailureHandler(handler);
+        taskExecuter.useFilter(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return element != b;
             }
         });
+        taskExecuter.addTasks(toList(a, c));
+
+        context.checking(new Expectations() {{
+            ignoring(handler);
+        }});
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, c)));
+    }
+
+    private void dependsOn(final Task task, final Task... dependsOn) {
+        context.checking(new Expectations() {{
+            TaskDependency taskDependency = context.mock(TaskDependency.class);
+            allowing(task).getTaskDependencies();
+            will(returnValue(taskDependency));
+            allowing(taskDependency).getDependencies(task);
+            will(returnValue(toSet(dependsOn)));
+        }});
+    }
+    
+    private Task brokenTask(String name, final RuntimeException failure, final Task... dependsOn) {
+        final TaskInternal task = createTask(name);
+        dependsOn(task, dependsOn);
+        context.checking(new Expectations() {{
+            atMost(1).of(task).executeWithoutThrowingTaskFailure();
+            will(new ExecuteTaskAction(task));
+            allowing(task.getState()).getFailure();
+            will(returnValue(failure));
+            allowing(task.getState()).rethrowFailure();
+            will(throwException(failure));
+        }});
+        return task;
+    }
+    
+    private Task task(final String name, final Task... dependsOn) {
+        final TaskInternal task = createTask(name);
+        dependsOn(task, dependsOn);
+        context.checking(new Expectations() {{
+            atMost(1).of(task).executeWithoutThrowingTaskFailure();
+            will(new ExecuteTaskAction(task));
+            allowing(task.getState()).getFailure();
+            will(returnValue(null));
+        }});
+        return task;
+    }
+    
+    private TaskInternal createTask(final String name) {
+        final TaskInternal task = context.mock(TaskInternal.class);
+        context.checking(new Expectations() {{
+            TaskStateInternal state = context.mock(TaskStateInternal.class);
+
+            allowing(task).getName();
+            will(returnValue(name));
+            allowing(task).getPath();
+            will(returnValue(":" + name));
+            allowing(task).getState();
+            will(returnValue(state));
+            allowing(task).compareTo(with(notNullValue(TaskInternal.class)));
+            will(new org.jmock.api.Action() {
+                public Object invoke(Invocation invocation) throws Throwable {
+                    return name.compareTo(((Task) invocation.getParameter(0)).getName());
+                }
+
+                public void describeTo(Description description) {
+                    description.appendText("compare to");
+                }
+            });
+        }});
+
         return task;
     }
+
+    private class ExecuteTaskAction implements org.jmock.api.Action {
+        private final TaskInternal task;
+
+        public ExecuteTaskAction(TaskInternal task) {
+            this.task = task;
+        }
+
+        public Object invoke(Invocation invocation) throws Throwable {
+            executedTasks.add(task);
+            return null;
+        }
+
+        public void describeTo(Description description) {
+            description.appendText("execute task");
+        }
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTasksBuildExecutionActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTasksBuildExecutionActionTest.groovy
new file mode 100644
index 0000000..2b28cff
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/DefaultTasksBuildExecutionActionTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.execution
+
+import spock.lang.Specification
+import org.gradle.StartParameter
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.GradleInternal
+
+class DefaultTasksBuildExecutionActionTest extends Specification {
+    final DefaultTasksBuildExecutionAction action = new DefaultTasksBuildExecutionAction()
+    final BuildExecutionContext context = Mock()
+    final StartParameter startParameter = Mock()
+    final ProjectInternal defaultProject = Mock()
+
+    def setup() {
+        GradleInternal gradle = Mock()
+        _ * context.gradle >> gradle
+        _ * gradle.startParameter >> startParameter
+        _ * gradle.defaultProject >> defaultProject
+    }
+
+    def "proceeds when task names specified in StartParameter"() {
+        given:
+        _ * startParameter.taskNames >> ['a']
+
+        when:
+        action.configure(context)
+
+        then:
+        1 * context.proceed()
+    }
+
+    def "sets task names to project defaults when none specified in StartParameter"() {
+        given:
+        _ * startParameter.taskNames >> []
+        _ * defaultProject.defaultTasks >> ['a', 'b']
+
+        when:
+        action.configure(context)
+
+        then:
+        1 * startParameter.setTaskNames(['a', 'b'])
+        1 * context.proceed()
+    }
+
+    def "uses the help task if no tasks specified in StartParameter or project"() {
+        given:
+        _ * startParameter.taskNames >> []
+        _ * defaultProject.defaultTasks >> []
+
+        when:
+        action.configure(context)
+
+        then:
+        1 * startParameter.setTaskNames(['help'])
+        1 * context.proceed()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DryRunBuildExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/DryRunBuildExecuterTest.java
deleted file mode 100644
index 0dca12b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/DryRunBuildExecuterTest.java
+++ /dev/null
@@ -1,61 +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.execution;
-
-import org.gradle.api.Task;
-import org.gradle.api.internal.GradleInternal;
-import static org.gradle.util.WrapUtil.*;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class DryRunBuildExecuterTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final BuildExecuter delegate = context.mock(BuildExecuter.class);
-    private final GradleInternal gradle = context.mock(GradleInternal.class);
-    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
-    private final DryRunBuildExecuter executer = new DryRunBuildExecuter(delegate);
-
-    @Test
-    public void disablesAllSelectedTasksBeforeExecution() {
-        final Task task1 = context.mock(Task.class, "task1");
-        final Task task2 = context.mock(Task.class, "task2");
-
-        context.checking(new Expectations() {{
-            allowing(gradle).getTaskGraph();
-            will(returnValue(taskExecuter));
-
-            one(delegate).select(gradle);
-        }});
-
-        executer.select(gradle);
-
-        context.checking(new Expectations() {{
-            one(taskExecuter).getAllTasks();
-            will(returnValue(toList(task1, task2)));
-
-            one(task1).setEnabled(false);
-            one(task2).setEnabled(false);
-
-            one(delegate).execute();
-        }});
-
-        executer.execute();
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/DryRunBuildExecutionActionTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/DryRunBuildExecutionActionTest.java
new file mode 100644
index 0000000..a1968dd
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/DryRunBuildExecutionActionTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.execution;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.util.JUnit4GroovyMockery;
+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 static org.gradle.util.WrapUtil.toList;
+
+ at RunWith(JMock.class)
+public class DryRunBuildExecutionActionTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final BuildExecutionContext executionContext = context.mock(BuildExecutionContext.class);
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private final StartParameter startParameter = context.mock(StartParameter.class);
+    private final DryRunBuildExecutionAction action = new DryRunBuildExecutionAction();
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations(){{
+            allowing(gradle).getStartParameter();
+            will(returnValue(startParameter));
+            allowing(executionContext).getGradle();
+            will(returnValue(gradle));
+            allowing(gradle).getTaskGraph();
+            will(returnValue(taskExecuter));
+        }});
+    }
+    
+    @Test
+    public void disablesAllSelectedTasksBeforeProceedingWhenDryRunIsEnabled() {
+        final Task task1 = context.mock(Task.class, "task1");
+        final Task task2 = context.mock(Task.class, "task2");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).isDryRun();
+            will(returnValue(true));
+
+            one(taskExecuter).getAllTasks();
+            will(returnValue(toList(task1, task2)));
+
+            one(task1).setEnabled(false);
+            one(task2).setEnabled(false);
+
+            one(executionContext).proceed();
+        }});
+
+        action.execute(executionContext);
+    }
+
+    @Test
+    public void proceedsWhenDryRunIsNotSelected() {
+        context.checking(new Expectations() {{
+            allowing(startParameter).isDryRun();
+            will(returnValue(false));
+
+            one(executionContext).proceed();
+        }});
+
+        action.execute(executionContext);
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
new file mode 100644
index 0000000..55b72d3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ExcludedTaskFilteringBuildConfigurationActionTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.execution
+
+import org.gradle.StartParameter
+import org.gradle.api.internal.GradleInternal
+import spock.lang.Specification
+
+class ExcludedTaskFilteringBuildConfigurationActionTest extends Specification {
+    final BuildExecutionContext context = Mock()
+    final StartParameter startParameter = Mock()
+    final TaskGraphExecuter taskGraph = Mock()
+    final TaskSelector selector = Mock()
+    final GradleInternal gradle = Mock()
+    final ExcludedTaskFilteringBuildConfigurationAction action = new ExcludedTaskFilteringBuildConfigurationAction(selector)
+
+    def setup() {
+        _ * context.gradle >> gradle
+        _ * gradle.startParameter >> startParameter
+        _ * gradle.taskGraph >> taskGraph
+    }
+
+    def "calls proceed when there are no excluded tasks defined"() {
+        given:
+        _ * startParameter.excludedTaskNames >> []
+
+        when:
+        action.configure(context)
+
+        then:
+        1 * context.proceed()
+    }
+
+    def "applies a filter for excluded tasks before proceeding"() {
+        given:
+        _ * startParameter.excludedTaskNames >> ['a']
+
+        when:
+        action.configure(context)
+
+        then:
+        1 * selector.selectTasks(gradle, 'a')
+        _ * selector.tasks >> []
+        1 * taskGraph.useFilter(!null)
+        1 * context.proceed()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectDefaultsBuildExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectDefaultsBuildExecuterTest.java
deleted file mode 100644
index d806d7e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectDefaultsBuildExecuterTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 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.execution;
-
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.gradle.util.WrapUtil.toList;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.assertThat;
-
- at RunWith (org.jmock.integration.junit4.JMock.class)
-public class ProjectDefaultsBuildExecuterTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ProjectInternal project = context.mock(ProjectInternal.class, "[project]");
-    private final GradleInternal gradle = context.mock(GradleInternal.class);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations(){{
-            allowing(gradle).getDefaultProject();
-            will(returnValue(project));
-        }});
-    }
-    
-    @Test public void usesProjectDefaultTasksFromProject() {
-        context.checking(new Expectations() {{
-            one(project).getDefaultTasks();
-            will(returnValue(toList("a", "b")));
-        }});
-
-        TestProjectDefaultsBuildExecuter executer = new TestProjectDefaultsBuildExecuter();
-        executer.select(gradle);
-
-        assertThat(executer.actualDelegate, instanceOf(TaskNameResolvingBuildExecuter.class));
-        TaskNameResolvingBuildExecuter delegate = (TaskNameResolvingBuildExecuter) executer.actualDelegate;
-        assertThat(delegate.getNames(), equalTo(toList("a", "b")));
-    }
-
-    @Test public void createsDescription() {
-        context.checking(new Expectations() {{
-            one(project).getDefaultTasks();
-            will(returnValue(toList("a", "b")));
-        }});
-
-        TestProjectDefaultsBuildExecuter executer = new TestProjectDefaultsBuildExecuter();
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("project default tasks 'a', 'b'"));
-    }
-
-    @Test public void usesHelpTaskWhenProjectHasNoDefaultTasks() {
-        context.checking(new Expectations() {{
-            one(project).getDefaultTasks();
-            will(returnValue(toList()));
-        }});
-
-        TestProjectDefaultsBuildExecuter executer = new TestProjectDefaultsBuildExecuter();
-        executer.select(gradle);
-
-        assertThat(executer.actualDelegate, instanceOf(TaskNameResolvingBuildExecuter.class));
-        TaskNameResolvingBuildExecuter delegate = (TaskNameResolvingBuildExecuter) executer.actualDelegate;
-        assertThat(delegate.getNames(), equalTo(toList("help")));
-
-        assertThat(executer.getDisplayName(), equalTo("default task 'help'"));
-    }
-
-    private static class TestProjectDefaultsBuildExecuter extends ProjectDefaultsBuildExecuter {
-        private BuildExecuter actualDelegate;
-
-        @Override
-        protected void setDelegate(BuildExecuter delegate) {
-            actualDelegate = delegate;
-            super.setDelegate(new BuildExecuter() {
-                public void select(GradleInternal gradle) {
-                }
-
-                public String getDisplayName() {
-                    return null;
-                }
-
-                public void execute() {
-                }
-            });
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy
new file mode 100644
index 0000000..8bdd330
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/SelectedTaskExecutionActionTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 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.execution
+
+import spock.lang.Specification
+import org.gradle.api.internal.GradleInternal
+import org.gradle.StartParameter
+import org.gradle.api.tasks.TaskState
+import org.gradle.api.Task
+import org.gradle.api.internal.MultiCauseException
+import org.gradle.api.internal.AbstractMultiCauseException
+
+class SelectedTaskExecutionActionTest extends Specification {
+    final SelectedTaskExecutionAction action = new SelectedTaskExecutionAction()
+    final BuildExecutionContext context = Mock()
+    final TaskGraphExecuter executer = Mock()
+    final GradleInternal gradleInternal = Mock()
+    final StartParameter startParameter = Mock()
+
+    def setup() {
+        _ * context.gradle >> gradleInternal
+        _ * gradleInternal.taskGraph >> executer
+        _ * gradleInternal.startParameter >> startParameter
+    }
+
+    def "executes selected tasks"() {
+        given:
+        _ * startParameter.continueOnFailure >> false
+
+        when:
+        action.execute(context)
+
+        then:
+        1 * executer.execute()
+    }
+
+    def "executes selected tasks when continue specified"() {
+        given:
+        _ * startParameter.continueOnFailure >> true
+
+        when:
+        action.execute(context)
+
+        then:
+        1 * executer.useFailureHandler(!null)
+        1 * executer.execute()
+    }
+
+    def "rethrows single failure after build when continue specified"() {
+        TaskFailureHandler handler
+        RuntimeException failure = new RuntimeException()
+
+        given:
+        _ * startParameter.continueOnFailure >> true
+
+        when:
+        action.execute(context)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * executer.useFailureHandler(!null) >> { handler = it[0] }
+        1 * executer.execute() >> { handler.onTaskFailure(brokenTask(failure)) }
+    }
+
+    def "rethrows failures after build when continue specified"() {
+        TaskFailureHandler handler
+        RuntimeException failure1 = new RuntimeException()
+        RuntimeException failure2 = new RuntimeException()
+
+        given:
+        _ * startParameter.continueOnFailure >> true
+
+        when:
+        action.execute(context)
+
+        then:
+        AbstractMultiCauseException e = thrown()
+        e.causes == [failure1, failure2]
+        1 * executer.useFailureHandler(!null) >> { handler = it[0] }
+        1 * executer.execute() >> {
+            handler.onTaskFailure(brokenTask(failure1))
+            handler.onTaskFailure(brokenTask(failure2))
+        }
+    }
+
+    def brokenTask(Throwable failure) {
+        Task task = Mock()
+        TaskState state = Mock()
+        _ * task.state >> state
+        _ * state.failure >> failure
+        return task
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
new file mode 100644
index 0000000..74b210d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildConfigurationActionTest.java
@@ -0,0 +1,409 @@
+/*
+ * 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.execution;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.gradle.StartParameter;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.CommandLineOption;
+import org.gradle.util.GUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+ at RunWith (org.jmock.integration.junit4.JMock.class)
+public class TaskNameResolvingBuildConfigurationActionTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ProjectInternal project = context.mock(AbstractProject.class, "[project]");
+    private final ProjectInternal otherProject = context.mock(AbstractProject.class, "[otherProject]");
+    private final ProjectInternal rootProject = context.mock(ProjectInternal.class);
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private final TaskNameResolver resolver = context.mock(TaskNameResolver.class);
+    private final BuildExecutionContext executionContext = context.mock(BuildExecutionContext.class);
+    private final StartParameter startParameter = context.mock(StartParameter.class);
+    private final TaskNameResolvingBuildConfigurationAction action = new TaskNameResolvingBuildConfigurationAction(resolver);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(executionContext).getGradle();
+            will(returnValue(gradle));
+            allowing(gradle).getDefaultProject();
+            will(returnValue(project));
+            allowing(gradle).getTaskGraph();
+            will(returnValue(taskExecuter));
+            allowing(gradle).getStartParameter();
+            will(returnValue(startParameter));
+            allowing(project).getAllprojects();
+            will(returnValue(toSet(project, otherProject)));
+            allowing(otherProject).getPath();
+            will(returnValue(":anotherProject"));
+            allowing(rootProject).getPath();
+            will(returnValue(":"));
+        }});
+    }
+
+    @Test
+    public void selectsAllTasksWithTheProvidedNameInCurrentProjectAndSubprojects() {
+        final Task task1 = task("name");
+        final Task task2 = task("name");
+        final Task task3 = task("other");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("name")));
+
+            one(resolver).selectAll("name", project);
+            will(returnValue(tasks(task1, task2, task3)));
+            one(taskExecuter).addTasks(toSet(task1, task2));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void usesCamelCaseAbbreviationToSelectTasksWhenNoExactMatch() {
+        assertMatches("soTaWN", "someTaskWithName", "saTaWN");
+        assertMatches("ta1", "task1", "Task1", "T1", "t2");
+        assertMatches("t1", "t1extra");
+        assertMatches("t1", "t12");
+        assertMatches("t1", "task1extra", "task2extra");
+        assertMatches("ABC", "AbcBbcCdc", "aabbcc");
+        assertMatches("s-t", "some-task");
+        assertMatches("s t", "some task");
+        assertMatches("s.t", "some.task");
+        assertMatches("a\\De(", "abc\\Def(", "a\\Df(");
+    }
+
+    private void assertMatches(final String pattern, final String matches, String... otherNames) {
+        final Set<Task> tasks = new HashSet<Task>();
+        final Task task1 = task(matches);
+        tasks.add(task1);
+        final Task task2 = task(matches);
+        tasks.add(task2);
+        for (String name : otherNames) {
+            tasks.add(task(name));
+        }
+        tasks.add(task("."));
+        tasks.add(task("other"));
+
+        context.checking(new Expectations() {{
+            one(startParameter).getTaskNames();
+            will(returnValue(toList(pattern)));
+
+            one(resolver).selectAll(pattern, project);
+            will(returnValue(tasks(tasks)));
+            one(taskExecuter).addTasks(toSet(task1, task2));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+    
+    @Test
+    public void selectsTaskWithMatchingRelativePath() {
+        final Task task1 = task("b");
+        final Task task2 = task("a");
+
+        context.checking(new Expectations(){{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("a:b")));
+
+            one(project).getChildProjects();
+            will(returnValue(toMap("a", otherProject)));
+            one(resolver).select("b", otherProject);
+            will(returnValue(tasks(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void selectsTaskWithMatchingTaskInRootProject() {
+        final Task task1 = task("b");
+        final Task task2 = task("a");
+
+        context.checking(new Expectations(){{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList(":b")));
+
+            one(project).getRootProject();
+            will(returnValue(rootProject));
+            one(resolver).select("b", rootProject);
+            will(returnValue(tasks(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void selectsTaskWithMatchingAbsolutePath() {
+        final Task task1 = task("b");
+        final Task task2 = task("a");
+
+        context.checking(new Expectations(){{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList(":a:b")));
+
+            one(project).getRootProject();
+            will(returnValue(rootProject));
+            one(rootProject).getChildProjects();
+            will(returnValue(toMap("a", otherProject)));
+            one(resolver).select("b", otherProject);
+            will(returnValue(tasks(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void usesCamelCaseAbbreviationToSelectTasksWhenNoExactMatchAndPathProvided() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("other");
+
+        context.checking(new Expectations(){{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("anotherProject:soTa")));
+
+            one(project).getChildProjects();
+            will(returnValue(toMap("anotherProject", otherProject)));
+            one(resolver).select("soTa", otherProject);
+            will(returnValue(tasks(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void usesCamelCaseAbbreviationToSelectProjectWhenPathProvided() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("other");
+
+        context.checking(new Expectations(){{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("anPr:soTa")));
+
+            one(project).getChildProjects();
+            will(returnValue(toMap("anotherProject", otherProject)));
+            one(resolver).select("soTa", otherProject);
+            will(returnValue(tasks(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            one(executionContext).proceed();
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void failsWhenProvidedTaskNameIsAmbiguous() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("someTasks");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("soTa")));
+
+            one(resolver).selectAll("soTa", project);
+            will(returnValue(tasks(task1, task2)));
+        }});
+
+        try {
+            action.configure(executionContext);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Task 'soTa' is ambiguous in [project]. Candidates are: 'someTask', 'someTasks'."));
+        }
+    }
+
+    @Test
+    public void reportsTyposInTaskName() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("someTasks");
+        final Task task3 = task("sometask");
+        final Task task4 = task("other");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("ssomeTask")));
+
+            one(resolver).selectAll("ssomeTask", project);
+            will(returnValue(tasks(task1, task2, task3, task4)));
+        }});
+
+        try {
+            action.configure(executionContext);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Task 'ssomeTask' not found in [project]. Some candidates are: 'someTask', 'someTasks', 'sometask'."));
+        }
+    }
+
+    @Test
+    public void treatsEachProvidedNameAsASeparateGroup() {
+        final Task task1 = task("name1");
+        final Task task2 = task("name2");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("child:name1", "name2")));
+
+            one(project).getChildProjects();
+            will(returnValue(toMap("child", otherProject)));
+            one(resolver).select("name1", otherProject);
+            will(returnValue(tasks(task1)));
+            one(resolver).selectAll("name2", project);
+            will(returnValue(tasks(task2)));
+
+            Sequence sequence = context.sequence("tasks");
+
+            one(taskExecuter).addTasks(toSet(task1));
+            inSequence(sequence);
+
+            one(taskExecuter).addTasks(toSet(task2));
+            inSequence(sequence);
+
+            ignoring(executionContext);
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void canConfigureSingleTaskUsingCommandLineOptions() {
+        final TaskWithBooleanProperty task1 = task("name1", TaskWithBooleanProperty.class);
+        final Task task2 = task("name2");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("name1", "--all", "name2")));
+
+            one(resolver).selectAll("name1", project);
+            will(returnValue(tasks(task1)));
+            one(resolver).selectAll("name2", project);
+            will(returnValue(tasks(task2)));
+
+            Sequence sequence = context.sequence("tasks");
+
+            one(task1).setSomeFlag(true);
+
+            one(taskExecuter).addTasks(toSet(task1));
+            inSequence(sequence);
+
+            one(taskExecuter).addTasks(toSet(task2));
+            inSequence(sequence);
+
+            ignoring(executionContext);
+        }});
+
+        action.configure(executionContext);
+    }
+
+    @Test
+    public void failsWhenUnknownTaskNameIsProvided() {
+        final Task task1 = task("t1");
+        final Task task2 = task("t2");
+
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("b3")));
+
+            one(resolver).selectAll("b3", project);
+            will(returnValue(tasks(task1, task2)));
+        }});
+
+        try {
+            action.configure(executionContext);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Task 'b3' not found in [project]."));
+        }
+    }
+
+    @Test
+    public void failsWhenCannotFindProjectInPath() {
+        context.checking(new Expectations() {{
+            allowing(startParameter).getTaskNames();
+            will(returnValue(toList("a:b", "name2")));
+
+            one(project).getChildProjects();
+            will(returnValue(GUtil.map("aa", otherProject, "ab", otherProject)));
+        }});
+
+        try {
+            action.configure(executionContext);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Project 'a' is ambiguous in [project]. Candidates are: 'aa', 'ab'."));
+        }
+    }
+
+    private Task task(String name) {
+        return task(name, Task.class);
+    }
+
+    private <T extends Task> T task(final String name, Class<T> taskType) {
+        final T task = context.mock(taskType);
+        context.checking(new Expectations(){{
+            allowing(task).getName();
+            will(returnValue(name));
+        }});
+        return task;
+    }
+
+    private Multimap<String, Task> tasks(Task... tasks) {
+        return tasks(Arrays.asList(tasks));
+    }
+
+    private Multimap<String, Task> tasks(Iterable<Task> tasks) {
+        Multimap<String, Task> map = LinkedHashMultimap.create();
+        for (Task task : tasks) {
+            map.put(task.getName(), task);
+        }
+        return map;
+    }
+
+    public abstract class TaskWithBooleanProperty implements Task {
+        @CommandLineOption(options = "all", description = "Some boolean flag")
+        public void setSomeFlag(boolean flag) { }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildExecuterTest.java b/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildExecuterTest.java
deleted file mode 100644
index 6382efa..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildExecuterTest.java
+++ /dev/null
@@ -1,392 +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.execution;
-
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import org.gradle.api.Task;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.project.AbstractProject;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.tasks.CommandLineOption;
-import org.gradle.util.GUtil;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.*;
-
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith (org.jmock.integration.junit4.JMock.class)
-public class TaskNameResolvingBuildExecuterTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ProjectInternal project = context.mock(AbstractProject.class, "[project]");
-    private final ProjectInternal otherProject = context.mock(AbstractProject.class, "[otherProject]");
-    private final ProjectInternal rootProject = context.mock(ProjectInternal.class);
-    private final GradleInternal gradle = context.mock(GradleInternal.class);
-    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
-    private final TaskNameResolver resolver = context.mock(TaskNameResolver.class);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations(){{
-            allowing(gradle).getDefaultProject();
-            will(returnValue(project));
-            allowing(gradle).getTaskGraph();
-            will(returnValue(taskExecuter));
-            allowing(project).getAllprojects();
-            will(returnValue(toSet(project, otherProject)));
-            allowing(otherProject).getPath();
-            will(returnValue(":anotherProject"));
-            allowing(rootProject).getPath();
-            will(returnValue(":"));
-        }});
-    }
-
-    @Test
-    public void selectsAllTasksWithTheProvidedNameInCurrentProjectAndSubprojects() {
-        final Task task1 = task("name");
-        final Task task2 = task("name");
-        final Task task3 = task("other");
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll("name", project);
-            will(returnValue(tasks(task1, task2, task3)));
-            one(taskExecuter).addTasks(toSet(task1, task2));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("name"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary task 'name'"));
-    }
-
-    @Test
-    public void usesCamelCaseAbbreviationToSelectTasksWhenNoExactMatch() {
-        assertMatches("soTaWN", "someTaskWithName", "saTaWN");
-        assertMatches("ta1", "task1", "Task1", "T1", "t2");
-        assertMatches("t1", "t1extra");
-        assertMatches("t1", "t12");
-        assertMatches("t1", "task1extra", "task2extra");
-        assertMatches("ABC", "AbcBbcCdc", "aabbcc");
-        assertMatches("s-t", "some-task");
-        assertMatches("s t", "some task");
-        assertMatches("s.t", "some.task");
-        assertMatches("a\\De(", "abc\\Def(", "a\\Df(");
-    }
-
-    private void assertMatches(final String pattern, final String matches, String... otherNames) {
-        final Set<Task> tasks = new HashSet<Task>();
-        final Task task1 = task(matches);
-        tasks.add(task1);
-        final Task task2 = task(matches);
-        tasks.add(task2);
-        for (String name : otherNames) {
-            tasks.add(task(name));
-        }
-        tasks.add(task("."));
-        tasks.add(task("other"));
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll(pattern, project);
-            will(returnValue(tasks(tasks)));
-            one(taskExecuter).addTasks(toSet(task1, task2));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList(pattern), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo(String.format("primary task '%s'", matches)));
-    }
-    
-    @Test
-    public void selectsTaskWithMatchingRelativePath() {
-        final Task task1 = task("b");
-        final Task task2 = task("a");
-
-        context.checking(new Expectations(){{
-            one(project).getChildProjects();
-            will(returnValue(toMap("a", otherProject)));
-            one(resolver).select("b", otherProject);
-            will(returnValue(tasks(task1, task2)));
-            one(taskExecuter).addTasks(toSet(task1));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("a:b"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary task 'a:b'"));
-    }
-
-    @Test
-    public void selectsTaskWithMatchingTaskInRootProject() {
-        final Task task1 = task("b");
-        final Task task2 = task("a");
-
-        context.checking(new Expectations(){{
-            one(project).getRootProject();
-            will(returnValue(rootProject));
-            one(resolver).select("b", rootProject);
-            will(returnValue(tasks(task1, task2)));
-            one(taskExecuter).addTasks(toSet(task1));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList(":b"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary task ':b'"));
-    }
-
-    @Test
-    public void selectsTaskWithMatchingAbsolutePath() {
-        final Task task1 = task("b");
-        final Task task2 = task("a");
-
-        context.checking(new Expectations(){{
-            one(project).getRootProject();
-            will(returnValue(rootProject));
-            one(rootProject).getChildProjects();
-            will(returnValue(toMap("a", otherProject)));
-            one(resolver).select("b", otherProject);
-            will(returnValue(tasks(task1, task2)));
-            one(taskExecuter).addTasks(toSet(task1));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList(":a:b"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary task ':a:b'"));
-    }
-
-    @Test
-    public void usesCamelCaseAbbreviationToSelectTasksWhenNoExactMatchAndPathProvided() {
-        final Task task1 = task("someTask");
-        final Task task2 = task("other");
-
-        context.checking(new Expectations(){{
-            one(project).getChildProjects();
-            will(returnValue(toMap("anotherProject", otherProject)));
-            one(resolver).select("soTa", otherProject);
-            will(returnValue(tasks(task1, task2)));
-            one(taskExecuter).addTasks(toSet(task1));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("anotherProject:soTa"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary task ':anotherProject:someTask'"));
-    }
-
-    @Test
-    public void usesCamelCaseAbbreviationToSelectProjectWhenPathProvided() {
-        final Task task1 = task("someTask");
-        final Task task2 = task("other");
-
-        context.checking(new Expectations(){{
-            one(project).getChildProjects();
-            will(returnValue(toMap("anotherProject", otherProject)));
-            one(resolver).select("soTa", otherProject);
-            will(returnValue(tasks(task1, task2)));
-            one(taskExecuter).addTasks(toSet(task1));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("anPr:soTa"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary task ':anotherProject:someTask'"));
-    }
-
-    @Test
-    public void failsWhenProvidedTaskNameIsAmbiguous() {
-        final Task task1 = task("someTask");
-        final Task task2 = task("someTasks");
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll("soTa", project);
-            will(returnValue(tasks(task1, task2)));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("soTa"), resolver);
-        try {
-            executer.select(gradle);
-            fail();
-        } catch (TaskSelectionException e) {
-            assertThat(e.getMessage(), equalTo("Task 'soTa' is ambiguous in [project]. Candidates are: 'someTask', 'someTasks'."));
-        }
-    }
-
-    @Test
-    public void reportsTyposInTaskName() {
-        final Task task1 = task("someTask");
-        final Task task2 = task("someTasks");
-        final Task task3 = task("sometask");
-        final Task task4 = task("other");
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll("ssomeTask", project);
-            will(returnValue(tasks(task1, task2, task3, task4)));
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("ssomeTask"), resolver);
-        try {
-            executer.select(gradle);
-            fail();
-        } catch (TaskSelectionException e) {
-            assertThat(e.getMessage(), equalTo("Task 'ssomeTask' not found in [project]. Some candidates are: 'someTask', 'someTasks', 'sometask'."));
-        }
-    }
-
-    @Test
-    public void executesAllSelectedTasks() {
-        final Task task1 = task("name");
-        final Task task2 = task("name");
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll("name", project);
-            will(returnValue(tasks(task1, task2)));
-            one(taskExecuter).addTasks(toSet(task1, task2));
-            one(taskExecuter).execute();
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("name"), resolver);
-        executer.select(gradle);
-        executer.execute();
-    }
-    
-    @Test
-    public void treatsEachProvidedNameAsASeparateGroup() {
-        final Task task1 = task("name1");
-        final Task task2 = task("name2");
-
-        context.checking(new Expectations() {{
-            one(project).getChildProjects();
-            will(returnValue(toMap("child", otherProject)));
-            one(resolver).select("name1", otherProject);
-            will(returnValue(tasks(task1)));
-            one(resolver).selectAll("name2", project);
-            will(returnValue(tasks(task2)));
-
-            Sequence sequence = context.sequence("tasks");
-
-            one(taskExecuter).addTasks(toSet(task1));
-            inSequence(sequence);
-
-            one(taskExecuter).addTasks(toSet(task2));
-            inSequence(sequence);
-
-            one(taskExecuter).execute();
-            inSequence(sequence);
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("child:name1", "name2"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary tasks 'child:name1', 'name2'"));
-        executer.execute();
-    }
-
-    @Test
-    public void canConfigureSingleTaskUsingCommandLineOptions() {
-        final TaskWithBooleanProperty task1 = task("name1", TaskWithBooleanProperty.class);
-        final Task task2 = task("name2");
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll("name1", project);
-            will(returnValue(tasks(task1)));
-            one(resolver).selectAll("name2", project);
-            will(returnValue(tasks(task2)));
-
-            Sequence sequence = context.sequence("tasks");
-
-            one(task1).setSomeFlag(true);
-
-            one(taskExecuter).addTasks(toSet(task1));
-            inSequence(sequence);
-
-            one(taskExecuter).addTasks(toSet(task2));
-            inSequence(sequence);
-        }});
-
-        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("name1", "--all", "name2"), resolver);
-        executer.select(gradle);
-        assertThat(executer.getDisplayName(), equalTo("primary tasks 'name1', 'name2'"));
-    }
-
-    @Test
-    public void failsWhenUnknownTaskNameIsProvided() {
-        final Task task1 = task("t1");
-        final Task task2 = task("t2");
-
-        context.checking(new Expectations() {{
-            one(resolver).selectAll("b3", project);
-            will(returnValue(tasks(task1, task2)));
-        }});
-
-        BuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("b3"), resolver);
-        try {
-            executer.select(gradle);
-            fail();
-        } catch (TaskSelectionException e) {
-            assertThat(e.getMessage(), equalTo("Task 'b3' not found in [project]."));
-        }
-    }
-
-    @Test
-    public void failsWhenCannotFindProjectInPath() {
-        context.checking(new Expectations() {{
-            one(project).getChildProjects();
-            will(returnValue(GUtil.map("aa", otherProject, "ab", otherProject)));
-        }});
-
-        BuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("a:b", "name2"), resolver);
-        try {
-            executer.select(gradle);
-            fail();
-        } catch (TaskSelectionException e) {
-            assertThat(e.getMessage(), equalTo("Project 'a' is ambiguous in [project]. Candidates are: 'aa', 'ab'."));
-        }
-    }
-
-    private Task task(String name) {
-        return task(name, Task.class);
-    }
-
-    private <T extends Task> T task(final String name, Class<T> taskType) {
-        final T task = context.mock(taskType);
-        context.checking(new Expectations(){{
-            allowing(task).getName();
-            will(returnValue(name));
-        }});
-        return task;
-    }
-
-    private Multimap<String, Task> tasks(Task... tasks) {
-        return tasks(Arrays.asList(tasks));
-    }
-
-    private Multimap<String, Task> tasks(Iterable<Task> tasks) {
-        Multimap<String, Task> map = LinkedHashMultimap.create();
-        for (Task task : tasks) {
-            map.put(task.getName(), task);
-        }
-        return map;
-    }
-
-    public abstract class TaskWithBooleanProperty implements Task {
-        @CommandLineOption(options = "all", description = "Some boolean flag")
-        public void setSomeFlag(boolean flag) { }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGeneratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGeneratorTest.groovy
deleted file mode 100644
index a91b183..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGeneratorTest.groovy
+++ /dev/null
@@ -1,37 +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.groovy.scripts
-
-import spock.lang.Specification
-
-class AsmBackedEmptyScriptGeneratorTest extends Specification {
-    private final AsmBackedEmptyScriptGenerator generator = new AsmBackedEmptyScriptGenerator()
-
-    def generatesEmptyScriptClass() {
-        expect:
-        def cl = generator.generate(groovy.lang.Script.class)
-        def script = cl.newInstance()
-        script instanceof groovy.lang.Script
-        script.run() == null
-    }
-    
-    def cachesScriptClass() {
-        expect:
-        def cl1 = generator.generate(groovy.lang.Script.class)
-        def cl2 = generator.generate(groovy.lang.Script.class)
-        cl1 == cl2
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandlerTest.groovy
deleted file mode 100644
index ce25fba..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandlerTest.groovy
+++ /dev/null
@@ -1,91 +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.groovy.scripts
-
-import spock.lang.Specification
-
-class CachingScriptCompilationHandlerTest extends Specification {
-    private final ScriptCompilationHandler target = Mock()
-    private final CachingScriptCompilationHandler handler = new CachingScriptCompilationHandler(target)
-
-    def cachesScriptClassForGivenClassDirAndParentClassLoader() {
-        ScriptSource script1 = scriptSource('script')
-        ScriptSource script2 = scriptSource('script')
-        ClassLoader parentClassLoader = Mock()
-        File cacheDir = new File('cacheDir')
-
-        when:
-        def c1 = handler.loadFromDir(script1, parentClassLoader, cacheDir, Script.class)
-        def c2 = handler.loadFromDir(script2, parentClassLoader, cacheDir, Script.class)
-
-        then:
-        1 * target.loadFromDir(script1, parentClassLoader, cacheDir, Script.class) >> Script.class
-        0 * target._
-    }
-
-    def doesNotCacheForDifferentScriptClass() {
-        ScriptSource script1 = scriptSource('script')
-        ScriptSource script2 = scriptSource('other')
-        ClassLoader parentClassLoader = Mock()
-        File cacheDir = new File('cacheDir')
-
-        when:
-        def c1 = handler.loadFromDir(script1, parentClassLoader, cacheDir, Script.class)
-        def c2 = handler.loadFromDir(script2, parentClassLoader, cacheDir, Script.class)
-
-        then:
-        1 * target.loadFromDir(script1, parentClassLoader, cacheDir, Script.class) >> Script.class
-        1 * target.loadFromDir(script2, parentClassLoader, cacheDir, Script.class) >> Script.class
-    }
-
-    def doesNotCacheForDifferentClassDir() {
-        ScriptSource script1 = scriptSource('script')
-        ScriptSource script2 = scriptSource('script')
-        ClassLoader parentClassLoader = Mock()
-        File cacheDir1 = new File('cacheDir')
-        File cacheDir2 = new File('cacheDir2')
-
-        when:
-        def c1 = handler.loadFromDir(script1, parentClassLoader, cacheDir1, Script.class)
-        def c2 = handler.loadFromDir(script2, parentClassLoader, cacheDir2, Script.class)
-
-        then:
-        1 * target.loadFromDir(script1, parentClassLoader, cacheDir1, Script.class) >> Script.class
-        1 * target.loadFromDir(script2, parentClassLoader, cacheDir2, Script.class) >> Script.class
-    }
-
-    def doesNotCacheForDifferentParentClassLoader() {
-        ScriptSource script1 = scriptSource('script')
-        ScriptSource script2 = scriptSource('script')
-        ClassLoader parentClassLoader1 = Mock()
-        ClassLoader parentClassLoader2 = Mock()
-        File cacheDir = new File('cacheDir')
-
-        when:
-        def c1 = handler.loadFromDir(script1, parentClassLoader1, cacheDir, Script.class)
-        def c2 = handler.loadFromDir(script2, parentClassLoader2, cacheDir, Script.class)
-
-        then:
-        1 * target.loadFromDir(script1, parentClassLoader1, cacheDir, Script.class) >> Script.class
-        1 * target.loadFromDir(script2, parentClassLoader2, cacheDir, Script.class) >> Script.class
-    }
-    
-    def scriptSource(String className = 'script') {
-        ScriptSource script = Mock()
-        _ * script.className >> className
-        script
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptSourceTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptSourceTest.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandlerTest.java
deleted file mode 100644
index 0d6ff18..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandlerTest.java
+++ /dev/null
@@ -1,289 +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.groovy.scripts;
-
-import groovy.lang.Script;
-import org.codehaus.groovy.ast.ClassHelper;
-import org.codehaus.groovy.ast.CodeVisitorSupport;
-import org.codehaus.groovy.ast.expr.ArgumentListExpression;
-import org.codehaus.groovy.ast.expr.ClassExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.codehaus.groovy.control.Phases;
-import org.codehaus.groovy.control.SourceUnit;
-import org.gradle.api.GradleException;
-import org.gradle.api.ScriptCompilationException;
-import org.gradle.api.internal.artifacts.dsl.AbstractScriptTransformer;
-import org.gradle.api.internal.resource.Resource;
-import org.gradle.util.TemporaryFolder;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.gradle.util.Matchers.containsLine;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultScriptCompilationHandlerTest {
-
-    static final String TEST_EXPECTED_SYSTEMPROP_VALUE = "somevalue";
-    static final String TEST_EXPECTED_SYSTEMPROP_KEY = "somekey";
-
-    private DefaultScriptCompilationHandler scriptCompilationHandler;
-
-    private File scriptCacheDir;
-    private File cachedFile;
-
-    private ScriptSource scriptSource;
-    private String scriptText;
-    private String scriptClassName;
-    private String scriptFileName;
-
-    private ClassLoader classLoader;
-
-    private Class<? extends Script> expectedScriptClass;
-
-    private JUnit4Mockery context = new JUnit4Mockery();
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-
-    @Before
-    public void setUp() throws IOException, ClassNotFoundException {
-        File testProjectDir = tmpDir.createDir("projectDir");
-        classLoader = getClass().getClassLoader();
-        scriptCompilationHandler = new DefaultScriptCompilationHandler();
-        scriptCacheDir = new File(testProjectDir, "cache");
-        scriptText = "System.setProperty('" + TEST_EXPECTED_SYSTEMPROP_KEY + "', '" + TEST_EXPECTED_SYSTEMPROP_VALUE
-                + "')";
-
-        scriptClassName = "ScriptClassName";
-        scriptFileName = "script-file-name";
-        scriptSource = scriptSource();
-        cachedFile = new File(scriptCacheDir, scriptClassName + ".class");
-        expectedScriptClass = TestBaseScript.class;
-    }
-
-    private ScriptSource scriptSource() {
-        return scriptSource(scriptText);
-    }
-
-    private ScriptSource scriptSource(final String scriptText) {
-        final ScriptSource source = context.mock(ScriptSource.class, scriptText);
-        context.checking(new Expectations(){{
-            Resource resource = context.mock(Resource.class, scriptText + "resource");
-
-            allowing(source).getClassName();
-            will(returnValue(scriptClassName));
-            allowing(source).getFileName();
-            will(returnValue(scriptFileName));
-            allowing(source).getDisplayName();
-            will(returnValue("script-display-name"));
-            allowing(source).getResource();
-            will(returnValue(resource));
-            allowing(resource).getText();
-            will(returnValue(scriptText));
-        }});
-        return source;
-    }
-
-    @After
-    public void tearDown() {
-        System.getProperties().remove(TEST_EXPECTED_SYSTEMPROP_KEY);
-    }
-
-    @Test
-    public void testCompileScriptToDir() throws Exception {
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-
-        checkScriptClassesInCache();
-
-        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                expectedScriptClass).newInstance();
-        evaluateScript(script);
-    }
-
-    @Test
-    public void testCompileScriptToDirWithPackageDeclaration() throws Exception {
-        final ScriptSource scriptSource = scriptSource("package org.gradle.test\n" + scriptText);
-
-        try {
-            scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-            fail();
-        } catch (UnsupportedOperationException e) {
-            assertThat(e.getMessage(), equalTo("Script-display-name should not contain a package statement."));
-        }
-    }
-
-    @Test
-    public void testCompileScriptToDirWithWhitespaceOnly() throws Exception {
-        final ScriptSource scriptSource = scriptSource("// ignore me\n");
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-
-        checkEmptyScriptInCache();
-
-        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
-    }
-
-    @Test
-    public void testCompileScriptToDirWithEmptyScript() throws Exception {
-        final ScriptSource scriptSource = scriptSource("");
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-
-        checkEmptyScriptInCache();
-
-        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
-    }
-
-    @Test
-    public void testCompileScriptToDirWithClassDefinitionOnlyScript() throws Exception {
-        final ScriptSource scriptSource = scriptSource("class SomeClass {}");
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-
-        checkEmptyScriptInCache();
-
-        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
-    }
-
-    @Test
-    public void testCompileScriptToDirWithMethodOnlyScript() throws Exception {
-        final ScriptSource scriptSource = scriptSource("def method() { println 'hi' }");
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-
-        checkScriptClassesInCache();
-
-        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
-    }
-
-    @Test
-    public void testCompileScriptToDirWithPropertiesOnlyScript() throws Exception {
-        final ScriptSource scriptSource = scriptSource("String a");
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
-
-        checkScriptClassesInCache();
-
-        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                expectedScriptClass).newInstance();
-        assertThat(script, is(expectedScriptClass));
-    }
-
-    @Test
-    public void testLoadFromDirWhenNotAssignableToBaseClass() {
-        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, Script.class);
-        try {
-            scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
-                    expectedScriptClass);
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), containsString("Could not load compiled classes for script-display-name from cache."));
-            assertThat(e.getCause(), instanceOf(ClassCastException.class));
-        }
-    }
-
-    @Test
-    public void testCompileToDirWithSyntaxError() {
-        ScriptSource source = new StringScriptSource("script.gradle", "\n\nnew HHHHJSJSJ jsj");
-        try {
-            scriptCompilationHandler.compileToDir(source, classLoader, scriptCacheDir, null, expectedScriptClass);
-            fail();
-        } catch (ScriptCompilationException e) {
-            assertThat(e.getScriptSource(), sameInstance(source));
-            assertThat(e.getLineNumber(), equalTo(3));
-            assertThat(e.getCause().getMessage(), containsLine(startsWith("script.gradle: 3: unexpected token: jsj")));
-        }
-
-        checkScriptCacheEmpty();
-    }
-
-    @Test
-    public void testCanVisitAndTransformScriptClass() throws Exception {
-        Transformer visitor = new AbstractScriptTransformer() {
-            public String getId() {
-                return "id";
-            }
-
-            protected int getPhase() {
-                return Phases.CANONICALIZATION;
-            }
-
-            @Override
-            public void call(SourceUnit source) throws CompilationFailedException {
-                source.getAST().getStatementBlock().visit(new CodeVisitorSupport() {
-                    @Override
-                    public void visitMethodCallExpression(MethodCallExpression call) {
-                        call.setObjectExpression(new ClassExpression(ClassHelper.make(System.class)));
-                        call.setMethod(new ConstantExpression("setProperty"));
-                        ArgumentListExpression arguments = (ArgumentListExpression) call.getArguments();
-                        arguments.addExpression(new ConstantExpression(TEST_EXPECTED_SYSTEMPROP_KEY));
-                        arguments.addExpression(new ConstantExpression(TEST_EXPECTED_SYSTEMPROP_VALUE));
-                    }
-                });
-            }
-        };
-
-        ScriptSource source = scriptSource("transformMe()");
-        scriptCompilationHandler.compileToDir(source, classLoader, scriptCacheDir, visitor, expectedScriptClass);
-        Script script = scriptCompilationHandler.loadFromDir(source, classLoader, scriptCacheDir, expectedScriptClass).newInstance();
-        evaluateScript(script);
-    }
-
-    private void checkScriptClassesInCache() {
-        assertTrue(scriptCacheDir.isDirectory());
-        assertTrue(cachedFile.isFile());
-        assertFalse(new File(scriptCacheDir, "emptyScript.txt").exists());
-    }
-
-    private void checkEmptyScriptInCache() {
-        assertTrue(scriptCacheDir.isDirectory());
-        assertTrue(new File(scriptCacheDir, "emptyScript.txt").isFile());
-    }
-
-    private void checkScriptCacheEmpty() {
-        assertFalse(scriptCacheDir.exists());
-    }
-
-    private void evaluateScript(Script script) {
-        assertThat(script, instanceOf(expectedScriptClass));
-        assertEquals(script.getClass().getSimpleName(), scriptClassName);
-        System.setProperty(TEST_EXPECTED_SYSTEMPROP_KEY, "not the expected value");
-        script.run();
-        assertEquals(TEST_EXPECTED_SYSTEMPROP_VALUE, System.getProperty(TEST_EXPECTED_SYSTEMPROP_KEY));
-    }
-
-    public abstract static class TestBaseScript extends Script {
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.groovy
new file mode 100644
index 0000000..0b6d153
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.groovy.scripts
+
+import spock.lang.Specification
+import org.gradle.logging.StandardOutputCapture
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.groovy.scripts.internal.ScriptRunnerFactory
+import org.gradle.groovy.scripts.internal.ScriptClassCompiler
+
+class DefaultScriptCompilerFactoryTest extends Specification {
+    final ScriptRunnerFactory scriptRunnerFactory = Mock()
+    final ScriptClassCompiler scriptClassCompiler = Mock()
+    final ScriptSource source = Mock()
+    final ScriptRunner<TestScript> runner = Mock()
+    final ClassLoader classLoader = Mock()
+    final Transformer transformer = Mock()
+    final DefaultScriptCompilerFactory factory = new DefaultScriptCompilerFactory(scriptClassCompiler, scriptRunnerFactory)
+
+    def "compiles script into class and wraps instance in script runner"() {
+        when:
+        def compiler = factory.createCompiler(source)
+        compiler.classloader = classLoader
+        compiler.transformer = transformer
+        def result = compiler.compile(Script)
+
+        then:
+        result == runner
+        1 * scriptClassCompiler.compile({it instanceof CachingScriptSource}, classLoader, transformer, Script) >> TestScript
+        1 * scriptRunnerFactory.create({it instanceof TestScript}) >> runner
+        0 * scriptRunnerFactory._
+        0 * scriptClassCompiler._
+    }
+}
+
+class TestScript extends Script {
+    @Override
+    StandardOutputCapture getStandardOutputCapture() {
+    }
+
+    @Override
+    void init(Object target, ServiceRegistry services) {
+    }
+
+    Object run() {
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.java
deleted file mode 100644
index 6845ad7..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.java
+++ /dev/null
@@ -1,272 +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.groovy.scripts;
-
-import org.gradle.api.internal.project.ServiceRegistry;
-import org.gradle.api.internal.resource.Resource;
-import org.gradle.logging.StandardOutputCapture;
-import org.gradle.cache.CacheBuilder;
-import org.gradle.cache.CacheRepository;
-import org.gradle.cache.PersistentCache;
-import org.gradle.util.GUtil;
-import org.gradle.util.HashUtil;
-import org.gradle.util.TemporaryFolder;
-import org.gradle.util.TestFile;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Map;
-
-import static org.gradle.util.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultScriptCompilerFactoryTest {
-    static final String TEST_SCRIPT_TEXT = "sometext";
-
-    DefaultScriptCompilerFactory scriptProcessor;
-
-    File cacheDir;
-    File expectedScriptCacheDir;
-    File testScriptFile;
-
-    ClassLoader testClassLoader;
-    ClassLoader originalClassLoader;
-
-    ScriptCompilationHandler scriptCompilationHandlerMock;
-    ScriptRunnerFactory scriptRunnerFactoryMock;
-    CacheRepository cacheRepositoryMock;
-    PersistentCache cacheMock;
-
-    Mockery context = new JUnit4Mockery();
-
-    Class expectedScriptBaseClass = groovy.lang.Script.class;
-    Map<String, Object> expectedCacheProperties;
-
-    ScriptSource source;
-    ScriptSource expectedSource;
-    ScriptRunner expectedScriptRunner;
-    CacheBuilder cacheBuilder;
-
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-
-    @Before
-    public void setUp() {
-        context.setImposteriser(ClassImposteriser.INSTANCE);
-        scriptCompilationHandlerMock = context.mock(ScriptCompilationHandler.class);
-        scriptRunnerFactoryMock = context.mock(ScriptRunnerFactory.class);
-        cacheRepositoryMock = context.mock(CacheRepository.class);
-        cacheMock = context.mock(PersistentCache.class);
-        testClassLoader = new URLClassLoader(new URL[0]);
-        testScriptFile = new File(tmpDir.getDir(), "script/mybuild.craidle");
-        cacheDir = new File(tmpDir.getDir(), "cache");
-        expectedScriptCacheDir = new TestFile(cacheDir, "Script").createDir();
-        expectedScriptRunner = context.mock(ScriptRunner.class);
-        scriptProcessor = new DefaultScriptCompilerFactory(scriptCompilationHandlerMock, scriptRunnerFactoryMock, cacheRepositoryMock);
-        source = context.mock(ScriptSource.class);
-        cacheBuilder = context.mock(CacheBuilder.class);
-
-        context.checking(new Expectations() {{
-            Resource resource = context.mock(Resource.class);
-
-            allowing(source).getDisplayName();
-            will(returnValue("[script source]"));
-            allowing(source).getClassName();
-            will(returnValue("class-name"));
-            allowing(source).getFileName();
-            will(returnValue("file-name"));
-            allowing(source).getResource();
-            will(returnValue(resource));
-            allowing(resource).getText();
-            will(returnValue(TEST_SCRIPT_TEXT));
-
-            allowing(cacheMock).getBaseDir();
-            will(returnValue(cacheDir));
-        }});
-
-        expectedSource = new CachingScriptSource(source);
-        String expectedHash = HashUtil.createHash(TEST_SCRIPT_TEXT);
-        expectedCacheProperties = GUtil.map("source.filename", "file-name", "source.hash", expectedHash);
-
-        originalClassLoader = Thread.currentThread().getContextClassLoader();
-        Thread.currentThread().setContextClassLoader(testClassLoader);
-    }
-
-    @After
-    public void tearDown() {
-        Thread.currentThread().setContextClassLoader(originalClassLoader);
-    }
-
-    @Test
-    public void testWithSourceFileNotCached() {
-        final Collector<TestScript> collector = collector();
-
-        context.checking(new Expectations() {{
-            one(cacheRepositoryMock).cache("scripts/class-name");
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).withProperties(expectedCacheProperties);
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).open();
-            will(returnValue(cacheMock));
-
-            allowing(cacheMock).isValid();
-            will(returnValue(false));
-
-            one(scriptCompilationHandlerMock).compileToDir(expectedSource, testClassLoader, expectedScriptCacheDir, null,
-                    expectedScriptBaseClass);
-
-            one(cacheMock).markValid();
-
-            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, testClassLoader, expectedScriptCacheDir,
-                    expectedScriptBaseClass);
-            will(returnValue(TestScript.class));
-
-            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
-            will(collectTo(collector).then(returnValue(expectedScriptRunner)));
-        }});
-
-        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).compile(expectedScriptBaseClass));
-        assertSame(testClassLoader, collector.get().getContextClassloader());
-        assertEquals(expectedSource, collector.get().getScriptSource());
-    }
-
-    @Test
-    public void testWithCachedSourceFile() {
-        final Collector<TestScript> collector = collector();
-
-        context.checking(new Expectations() {{
-            one(cacheRepositoryMock).cache("scripts/class-name");
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).withProperties(expectedCacheProperties);
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).open();
-            will(returnValue(cacheMock));
-
-            allowing(cacheMock).isValid();
-            will(returnValue(true));
-
-            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, testClassLoader, expectedScriptCacheDir, expectedScriptBaseClass);
-            will(returnValue(TestScript.class));
-
-            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
-            will(collectTo(collector).then(returnValue(expectedScriptRunner)));
-        }});
-
-        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).compile(expectedScriptBaseClass));
-        assertSame(testClassLoader, collector.get().getContextClassloader());
-        assertEquals(expectedSource, collector.get().getScriptSource());
-    }
-
-    @Test
-    public void testUsesSuppliedClassLoaderToLoadScript() {
-        final Collector<TestScript> collector = collector();
-        final ClassLoader classLoader = new ClassLoader() {
-        };
-
-        context.checking(new Expectations(){{
-            one(cacheRepositoryMock).cache("scripts/class-name");
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).withProperties(expectedCacheProperties);
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).open();
-            will(returnValue(cacheMock));
-
-            allowing(cacheMock).isValid();
-            will(returnValue(true));
-
-            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, classLoader, expectedScriptCacheDir,
-                    expectedScriptBaseClass);
-            will(returnValue(TestScript.class));
-
-            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
-            will(collectTo(collector).then(returnValue(expectedScriptRunner)));
-        }});
-
-        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).setClassloader(classLoader).compile(expectedScriptBaseClass));
-        assertSame(classLoader, collector.get().getContextClassloader());
-    }
-
-    @Test
-    public void testUsesSuppliedTransformerToDetermineCacheDirName() {
-        final Transformer transformer = context.mock(Transformer.class);
-        final File expectedCacheDir = new TestFile(expectedScriptCacheDir.getParentFile(), "transformer_Script").createDir();
-
-        context.checking(new Expectations(){{
-            allowing(transformer).getId();
-            will(returnValue("transformer"));
-
-            one(cacheRepositoryMock).cache("scripts/class-name");
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).withProperties(expectedCacheProperties);
-            will(returnValue(cacheBuilder));
-
-            one(cacheBuilder).open();
-            will(returnValue(cacheMock));
-
-            allowing(cacheMock).isValid();
-            will(returnValue(true));
-
-            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, testClassLoader, expectedCacheDir,
-                    expectedScriptBaseClass);
-            will(returnValue(TestScript.class));
-
-            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
-            will(returnValue(expectedScriptRunner));
-        }});
-
-        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).setTransformer(transformer).compile(expectedScriptBaseClass));
-    }
-
-    public static class TestScript extends Script {
-        @Override
-        public StandardOutputCapture getStandardOutputCapture() {
-            return null;
-        }
-
-        @Override
-        public void init(Object target, ServiceRegistry services) {
-        }
-
-        @Override
-        public Object run() {
-            return null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactoryTest.java
deleted file mode 100644
index 92786a0..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactoryTest.java
+++ /dev/null
@@ -1,146 +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.groovy.scripts;
-
-import org.gradle.api.GradleScriptException;
-import org.gradle.logging.StandardOutputCapture;
-import org.hamcrest.Description;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.api.Action;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultScriptRunnerFactoryTest {
-    private final JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    private final Script scriptMock = context.mock(Script.class, "<script-to-string>");
-    private final StandardOutputCapture standardOutputCaptureMock = context.mock(StandardOutputCapture.class);
-    private final ClassLoader classLoaderDummy = context.mock(ClassLoader.class);
-    private final ScriptSource scriptSourceDummy = context.mock(ScriptSource.class);
-    private final ScriptExecutionListener scriptExecutionListenerMock = context.mock(ScriptExecutionListener.class);
-    private final DefaultScriptRunnerFactory factory = new DefaultScriptRunnerFactory(scriptExecutionListenerMock);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(scriptMock).getStandardOutputCapture();
-            will(returnValue(standardOutputCaptureMock));
-            allowing(scriptMock).getScriptSource();
-            will(returnValue(scriptSourceDummy));
-            allowing(scriptMock).getContextClassloader();
-            will(returnValue(classLoaderDummy));
-            ignoring(scriptSourceDummy);
-        }});
-    }
-
-    @Test
-    public void createsScriptRunner() {
-        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
-        assertThat(scriptRunner.getScript(), sameInstance(scriptMock));
-    }
-
-    @Test
-    public void redirectsStandardOutputAndSetsContextClassLoaderWhenScriptIsRun() {
-        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
-
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("seq");
-
-            one(scriptExecutionListenerMock).beforeScript(scriptMock);
-            inSequence(sequence);
-
-            one(standardOutputCaptureMock).start();
-            inSequence(sequence);
-
-            one(scriptMock).run();
-            inSequence(sequence);
-            will(doAll(new Action() {
-                public void describeTo(Description description) {
-                    description.appendValue("check context classloader");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(classLoaderDummy));
-                    return null;
-                }
-            }));
-
-            one(standardOutputCaptureMock).stop();
-            inSequence(sequence);
-
-            one(scriptExecutionListenerMock).afterScript(scriptMock, null);
-            inSequence(sequence);
-        }});
-
-        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
-        assertThat(originalClassLoader, not(sameInstance(classLoaderDummy)));
-
-        scriptRunner.run();
-
-        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(originalClassLoader));
-    }
-
-    @Test
-    public void wrapsExecutionExceptionAndRestoresStateWhenScriptFails() {
-        final RuntimeException failure = new RuntimeException();
-
-        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
-
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("seq");
-
-            one(scriptExecutionListenerMock).beforeScript(scriptMock);
-            inSequence(sequence);
-
-            one(standardOutputCaptureMock).start();
-            inSequence(sequence);
-
-            one(scriptMock).run();
-            inSequence(sequence);
-            will(throwException(failure));
-
-            one(standardOutputCaptureMock).stop();
-            inSequence(sequence);
-
-            one(scriptExecutionListenerMock).afterScript(with(sameInstance(scriptMock)), with(notNullValue(Throwable.class)));
-            inSequence(sequence);
-        }});
-
-        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
-        assertThat(originalClassLoader, not(sameInstance(classLoaderDummy)));
-
-        try {
-            scriptRunner.run();
-            fail();
-        } catch (GradleScriptException e) {
-            assertThat(e.getMessage(), equalTo("A problem occurred evaluating <script-to-string>."));
-            assertThat(e.getCause(), sameInstance((Throwable) failure));
-        }
-
-        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(originalClassLoader));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
index c00d33b..0a32cd1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
@@ -21,7 +21,7 @@ package org.gradle.groovy.scripts
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.gradle.api.initialization.dsl.ScriptHandler
 import org.gradle.api.internal.project.DefaultProject
-import org.gradle.api.internal.project.ServiceRegistry
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.api.logging.LoggingManager
 import org.gradle.logging.StandardOutputCapture
 import org.gradle.util.HelperUtil
@@ -57,7 +57,6 @@ class DefaultScriptTest {
         script.run();
         assertEquals("scriptMethod", script.scriptMethod())
         assertEquals(testProject.path + "mySuffix", script.scriptProperty)
-        assertEquals(testProject.path + "mySuffix", testProject.additionalProperties["scriptProperty"])
     }
 
     private CompilerConfiguration createBaseCompilerConfiguration() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
index 634b5cd..e290eee 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
@@ -112,30 +112,36 @@ public class UriScriptSourceTest {
     }
     
     @Test
-    public void generatesClassNameFromFileNameByRemovingExtensionAndAddingHashOfURL() {
+    public void generatesClassNameFromFileNameByRemovingExtensionAndAddingHashOfFileURL() {
         UriScriptSource source = new UriScriptSource("<file-type>", scriptFile);
-        assertThat(source.getClassName(), matchesRegexp("build_[0-9a-z]{25,26}"));
+        assertThat(source.getClassName(), matchesRegexp("build_[0-9a-z]+"));
+    }
+
+    @Test
+    public void generatesClassNameFromFileNameByRemovingExtensionAndAddingHashOfJarURL() throws Exception {
+        UriScriptSource source = new UriScriptSource("<file-type>", createJar());
+        assertThat(source.getClassName(), matchesRegexp("build_[0-9a-z]+"));
     }
 
     @Test
     public void truncatesClassNameAt30Characters() {
         UriScriptSource source = new UriScriptSource("<file-type>", new File(testDir, "a-long-file-name-12345678901234567890.gradle"));
-        assertThat(source.getClassName(), matchesRegexp("a_long_file_name_1234567890123_[0-9a-z]{25,26}"));
+        assertThat(source.getClassName(), matchesRegexp("a_long_file_name_1234567890123_[0-9a-z]+"));
     }
 
     @Test
     public void encodesReservedCharactersInClassName() {
         UriScriptSource source = new UriScriptSource("<file-type>", new File(testDir, "name-+.chars.gradle"));
-        assertThat(source.getClassName(), matchesRegexp("name___chars_[0-9a-z]{25,26}"));
+        assertThat(source.getClassName(), matchesRegexp("name___chars_[0-9a-z]+"));
     }
 
     @Test
     public void prefixesClassNameWhenFirstCharacterIsNotValidIdentifierStartChar() {
         UriScriptSource source = new UriScriptSource("<file-type>", new File(testDir, "123"));
-        assertThat(source.getClassName(), matchesRegexp("_123_[0-9a-z]{25,26}"));
+        assertThat(source.getClassName(), matchesRegexp("_123_[0-9a-z]+"));
 
         source = new UriScriptSource("<file-type>", new File(testDir, "-"));
-        assertThat(source.getClassName(), matchesRegexp("__[0-9a-z]{25,26}"));
+        assertThat(source.getClassName(), matchesRegexp("__[0-9a-z]+"));
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGeneratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGeneratorTest.groovy
new file mode 100644
index 0000000..4d6c6a8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/AsmBackedEmptyScriptGeneratorTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.groovy.scripts.internal
+
+import spock.lang.Specification
+
+class AsmBackedEmptyScriptGeneratorTest extends Specification {
+    private final AsmBackedEmptyScriptGenerator generator = new AsmBackedEmptyScriptGenerator()
+
+    def generatesEmptyScriptClass() {
+        expect:
+        def cl = generator.generate(groovy.lang.Script.class)
+        def script = cl.newInstance()
+        script instanceof groovy.lang.Script
+        script.run() == null
+    }
+    
+    def cachesScriptClass() {
+        expect:
+        def cl1 = generator.generate(groovy.lang.Script.class)
+        def cl2 = generator.generate(groovy.lang.Script.class)
+        cl1 == cl2
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/CachingScriptClassCompilerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/CachingScriptClassCompilerTest.groovy
new file mode 100644
index 0000000..8364cae
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/CachingScriptClassCompilerTest.groovy
@@ -0,0 +1,117 @@
+/*
+ * 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.groovy.scripts.internal
+
+import spock.lang.Specification
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.groovy.scripts.Transformer
+import org.gradle.groovy.scripts.Script
+import org.gradle.groovy.scripts.TestScript
+
+class CachingScriptClassCompilerTest extends Specification {
+    private final ScriptClassCompiler target = Mock()
+    private final CachingScriptClassCompiler compiler = new CachingScriptClassCompiler(target)
+
+    def "caches the script class for a given script class and classloader and transformer and baseclass"() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader = Mock()
+        Transformer transformer = transformer()
+
+        when:
+        def c1 = compiler.compile(script1, parentClassLoader, transformer, Script.class)
+        def c2 = compiler.compile(script2, parentClassLoader, transformer, Script.class)
+
+        then:
+        c1 == c2
+        1 * target.compile(script1, parentClassLoader, transformer, Script.class) >> Script.class
+        0 * target._
+    }
+
+    def "does not cache script class for different script class"() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('other')
+        ClassLoader parentClassLoader = Mock()
+        Transformer transformer = transformer()
+
+        when:
+        def c1 = compiler.compile(script1, parentClassLoader, transformer, Script.class)
+        def c2 = compiler.compile(script2, parentClassLoader, transformer, Script.class)
+
+        then:
+        1 * target.compile(script1, parentClassLoader, transformer, Script.class) >> Script.class
+        1 * target.compile(script2, parentClassLoader, transformer, Script.class) >> Script.class
+    }
+
+    def "does not cache script class for different transformers"() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader = Mock()
+        Transformer transformer1 = transformer('t1')
+        Transformer transformer2 = transformer('t2')
+
+        when:
+        def c1 = compiler.compile(script1, parentClassLoader, transformer1, Script.class)
+        def c2 = compiler.compile(script2, parentClassLoader, transformer2, Script.class)
+
+        then:
+        1 * target.compile(script1, parentClassLoader, transformer1, Script.class) >> Script.class
+        1 * target.compile(script2, parentClassLoader, transformer2, Script.class) >> Script.class
+    }
+
+    def "does not cache script class for different classloaders"() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader1 = Mock()
+        ClassLoader parentClassLoader2 = Mock()
+        Transformer transformer = transformer()
+
+        when:
+        def c1 = compiler.compile(script1, parentClassLoader1, transformer, Script.class)
+        def c2 = compiler.compile(script2, parentClassLoader2, transformer, Script.class)
+
+        then:
+        1 * target.compile(script1, parentClassLoader1, transformer, Script.class) >> Script.class
+        1 * target.compile(script2, parentClassLoader2, transformer, Script.class) >> Script.class
+    }
+
+    def "does not cache script class for different base classes"() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader = Mock()
+        Transformer transformer = transformer()
+
+        when:
+        def c1 = compiler.compile(script1, parentClassLoader, transformer, Script.class)
+        def c2 = compiler.compile(script2, parentClassLoader, transformer, TestScript.class)
+
+        then:
+        1 * target.compile(script1, parentClassLoader, transformer, Script.class) >> Script.class
+        1 * target.compile(script2, parentClassLoader, transformer, TestScript.class) >> TestScript.class
+    }
+
+    def scriptSource(String className = 'script') {
+        ScriptSource script = Mock()
+        _ * script.className >> className
+        script
+    }
+
+    def transformer(String id = 'id') {
+        Transformer transformer = Mock()
+        _ * transformer.id >> id
+        transformer
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
new file mode 100644
index 0000000..cb60f9f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.groovy.scripts.internal;
+
+import groovy.lang.Script;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.control.SourceUnit;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.artifacts.dsl.AbstractScriptTransformer;
+import org.gradle.api.internal.resource.Resource;
+import org.gradle.groovy.scripts.ScriptCompilationException;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.groovy.scripts.Transformer;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.gradle.util.Matchers.containsLine;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultScriptCompilationHandlerTest {
+
+    static final String TEST_EXPECTED_SYSTEMPROP_VALUE = "somevalue";
+    static final String TEST_EXPECTED_SYSTEMPROP_KEY = "somekey";
+
+    private DefaultScriptCompilationHandler scriptCompilationHandler;
+
+    private File scriptCacheDir;
+    private File cachedFile;
+
+    private ScriptSource scriptSource;
+    private String scriptText;
+    private String scriptClassName;
+    private String scriptFileName;
+
+    private ClassLoader classLoader;
+
+    private Class<? extends Script> expectedScriptClass;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws IOException, ClassNotFoundException {
+        File testProjectDir = tmpDir.createDir("projectDir");
+        classLoader = getClass().getClassLoader();
+        scriptCompilationHandler = new DefaultScriptCompilationHandler(new AsmBackedEmptyScriptGenerator());
+        scriptCacheDir = new File(testProjectDir, "cache");
+        scriptText = "System.setProperty('" + TEST_EXPECTED_SYSTEMPROP_KEY + "', '" + TEST_EXPECTED_SYSTEMPROP_VALUE
+                + "')";
+
+        scriptClassName = "ScriptClassName";
+        scriptFileName = "script-file-name";
+        scriptSource = scriptSource();
+        cachedFile = new File(scriptCacheDir, scriptClassName + ".class");
+        expectedScriptClass = TestBaseScript.class;
+    }
+
+    private ScriptSource scriptSource() {
+        return scriptSource(scriptText);
+    }
+
+    private ScriptSource scriptSource(final String scriptText) {
+        final ScriptSource source = context.mock(ScriptSource.class, scriptText);
+        context.checking(new Expectations(){{
+            Resource resource = context.mock(Resource.class, scriptText + "resource");
+
+            allowing(source).getClassName();
+            will(returnValue(scriptClassName));
+            allowing(source).getFileName();
+            will(returnValue(scriptFileName));
+            allowing(source).getDisplayName();
+            will(returnValue("script-display-name"));
+            allowing(source).getResource();
+            will(returnValue(resource));
+            allowing(resource).getText();
+            will(returnValue(scriptText));
+        }});
+        return source;
+    }
+
+    @After
+    public void tearDown() {
+        System.getProperties().remove(TEST_EXPECTED_SYSTEMPROP_KEY);
+    }
+
+    @Test
+    public void testCompileScriptToDir() throws Exception {
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkScriptClassesInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        evaluateScript(script);
+    }
+
+    @Test
+    public void testCompileScriptToDirWithPackageDeclaration() throws Exception {
+        final ScriptSource scriptSource = scriptSource("package org.gradle.test\n" + scriptText);
+
+        try {
+            scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+            fail();
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), equalTo("Script-display-name should not contain a package statement."));
+        }
+    }
+
+    @Test
+    public void testCompileScriptToDirWithWhitespaceOnly() throws Exception {
+        final ScriptSource scriptSource = scriptSource("// ignore me\n");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkEmptyScriptInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithEmptyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkEmptyScriptInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithClassDefinitionOnlyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("class SomeClass {}");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkEmptyScriptInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithMethodOnlyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("def method() { println 'hi' }");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkScriptClassesInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithPropertiesOnlyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("String a");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkScriptClassesInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testLoadFromDirWhenNotAssignableToBaseClass() {
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, Script.class);
+        try {
+            scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                    expectedScriptClass);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), containsString("Could not load compiled classes for script-display-name from cache."));
+            assertThat(e.getCause(), instanceOf(ClassCastException.class));
+        }
+    }
+
+    @Test
+    public void testCompileToDirWithSyntaxError() {
+        ScriptSource source = new StringScriptSource("script.gradle", "\n\nnew HHHHJSJSJ jsj");
+        try {
+            scriptCompilationHandler.compileToDir(source, classLoader, scriptCacheDir, null, expectedScriptClass);
+            fail();
+        } catch (ScriptCompilationException e) {
+            assertThat(e.getScriptSource(), sameInstance(source));
+            assertThat(e.getLineNumber(), equalTo(3));
+            assertThat(e.getCause().getMessage(), containsLine(startsWith("script.gradle: 3: unexpected token: jsj")));
+        }
+
+        checkScriptCacheEmpty();
+    }
+
+    @Test
+    public void testCanVisitAndTransformScriptClass() throws Exception {
+        Transformer visitor = new AbstractScriptTransformer() {
+            public String getId() {
+                return "id";
+            }
+
+            protected int getPhase() {
+                return Phases.CANONICALIZATION;
+            }
+
+            @Override
+            public void call(SourceUnit source) throws CompilationFailedException {
+                source.getAST().getStatementBlock().visit(new CodeVisitorSupport() {
+                    @Override
+                    public void visitMethodCallExpression(MethodCallExpression call) {
+                        call.setObjectExpression(new ClassExpression(ClassHelper.make(System.class)));
+                        call.setMethod(new ConstantExpression("setProperty"));
+                        ArgumentListExpression arguments = (ArgumentListExpression) call.getArguments();
+                        arguments.addExpression(new ConstantExpression(TEST_EXPECTED_SYSTEMPROP_KEY));
+                        arguments.addExpression(new ConstantExpression(TEST_EXPECTED_SYSTEMPROP_VALUE));
+                    }
+                });
+            }
+        };
+
+        ScriptSource source = scriptSource("transformMe()");
+        scriptCompilationHandler.compileToDir(source, classLoader, scriptCacheDir, visitor, expectedScriptClass);
+        Script script = scriptCompilationHandler.loadFromDir(source, classLoader, scriptCacheDir, expectedScriptClass).newInstance();
+        evaluateScript(script);
+    }
+
+    private void checkScriptClassesInCache() {
+        assertTrue(scriptCacheDir.isDirectory());
+        assertTrue(cachedFile.isFile());
+        assertFalse(new File(scriptCacheDir, "emptyScript.txt").exists());
+    }
+
+    private void checkEmptyScriptInCache() {
+        assertTrue(scriptCacheDir.isDirectory());
+        assertTrue(new File(scriptCacheDir, "emptyScript.txt").isFile());
+    }
+
+    private void checkScriptCacheEmpty() {
+        assertFalse(scriptCacheDir.exists());
+    }
+
+    private void evaluateScript(Script script) {
+        assertThat(script, instanceOf(expectedScriptClass));
+        assertEquals(script.getClass().getSimpleName(), scriptClassName);
+        System.setProperty(TEST_EXPECTED_SYSTEMPROP_KEY, "not the expected value");
+        script.run();
+        assertEquals(TEST_EXPECTED_SYSTEMPROP_VALUE, System.getProperty(TEST_EXPECTED_SYSTEMPROP_KEY));
+    }
+
+    public abstract static class TestBaseScript extends Script {
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptRunnerFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptRunnerFactoryTest.java
new file mode 100644
index 0000000..f922418
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptRunnerFactoryTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.groovy.scripts.internal;
+
+import org.gradle.api.GradleScriptException;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.ScriptRunner;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.logging.StandardOutputCapture;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultScriptRunnerFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final Script scriptMock = context.mock(Script.class, "<script-to-string>");
+    private final StandardOutputCapture standardOutputCaptureMock = context.mock(StandardOutputCapture.class);
+    private final ClassLoader classLoaderDummy = context.mock(ClassLoader.class);
+    private final ScriptSource scriptSourceDummy = context.mock(ScriptSource.class);
+    private final ScriptExecutionListener scriptExecutionListenerMock = context.mock(ScriptExecutionListener.class);
+    private final DefaultScriptRunnerFactory factory = new DefaultScriptRunnerFactory(scriptExecutionListenerMock);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(scriptMock).getStandardOutputCapture();
+            will(returnValue(standardOutputCaptureMock));
+            allowing(scriptMock).getScriptSource();
+            will(returnValue(scriptSourceDummy));
+            allowing(scriptMock).getContextClassloader();
+            will(returnValue(classLoaderDummy));
+            ignoring(scriptSourceDummy);
+        }});
+    }
+
+    @Test
+    public void createsScriptRunner() {
+        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
+        assertThat(scriptRunner.getScript(), sameInstance(scriptMock));
+    }
+
+    @Test
+    public void redirectsStandardOutputAndSetsContextClassLoaderWhenScriptIsRun() {
+        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(scriptExecutionListenerMock).beforeScript(scriptMock);
+            inSequence(sequence);
+
+            one(standardOutputCaptureMock).start();
+            inSequence(sequence);
+
+            one(scriptMock).run();
+            inSequence(sequence);
+            will(doAll(new Action() {
+                public void describeTo(Description description) {
+                    description.appendValue("check context classloader");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(classLoaderDummy));
+                    return null;
+                }
+            }));
+
+            one(standardOutputCaptureMock).stop();
+            inSequence(sequence);
+
+            one(scriptExecutionListenerMock).afterScript(scriptMock, null);
+            inSequence(sequence);
+        }});
+
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        assertThat(originalClassLoader, not(sameInstance(classLoaderDummy)));
+
+        scriptRunner.run();
+
+        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(originalClassLoader));
+    }
+
+    @Test
+    public void wrapsExecutionExceptionAndRestoresStateWhenScriptFails() {
+        final RuntimeException failure = new RuntimeException();
+
+        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(scriptExecutionListenerMock).beforeScript(scriptMock);
+            inSequence(sequence);
+
+            one(standardOutputCaptureMock).start();
+            inSequence(sequence);
+
+            one(scriptMock).run();
+            inSequence(sequence);
+            will(throwException(failure));
+
+            one(standardOutputCaptureMock).stop();
+            inSequence(sequence);
+
+            one(scriptExecutionListenerMock).afterScript(with(sameInstance(scriptMock)), with(notNullValue(Throwable.class)));
+            inSequence(sequence);
+        }});
+
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        assertThat(originalClassLoader, not(sameInstance(classLoaderDummy)));
+
+        try {
+            scriptRunner.run();
+            fail();
+        } catch (GradleScriptException e) {
+            assertThat(e.getMessage(), equalTo("A problem occurred evaluating <script-to-string>."));
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+
+        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(originalClassLoader));
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy
new file mode 100644
index 0000000..17d81fc
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/FileCacheBackedScriptClassCompilerTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 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.groovy.scripts.internal
+
+import spock.lang.Specification
+import org.gradle.cache.CacheRepository
+import org.gradle.api.internal.resource.Resource
+import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.cache.PersistentCache
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.groovy.scripts.Transformer
+import org.gradle.groovy.scripts.Script
+import org.gradle.cache.CacheValidator
+
+class FileCacheBackedScriptClassCompilerTest extends Specification {
+    final ScriptCompilationHandler scriptCompilationHandler = Mock()
+    final CacheRepository cacheRepository = Mock()
+    final DirectoryCacheBuilder cacheBuilder = Mock()
+    final CacheValidator validator = Mock()
+    final PersistentCache cache = Mock()
+    final ScriptSource source = Mock()
+    final ClassLoader classLoader = Mock()
+    final Transformer transformer = Mock()
+    final File cacheDir = new File("base-dir")
+    final FileCacheBackedScriptClassCompiler compiler = new FileCacheBackedScriptClassCompiler(cacheRepository, validator, scriptCompilationHandler)
+
+    def setup() {
+        Resource resource = Mock()
+        _ * source.resource >> resource
+        _ * resource.text >> 'this is the script'
+        _ * source.className >> 'ScriptClassName'
+        _ * source.fileName >> 'ScriptFileName'
+        _ * transformer.id >> 'TransformerId'
+        _ * cache.baseDir >> cacheDir
+        _ * validator.isValid() >> true
+    }
+
+    def "loads classes from cache directory"() {
+        when:
+        def result = compiler.compile(source, classLoader, transformer, Script)
+
+        then:
+        result == Script
+        1 * cacheRepository.cache("scripts/ScriptClassName/Script/TransformerId") >> cacheBuilder
+        1 * cacheBuilder.withProperties(!null) >> { args ->
+            assert args[0].get('source.filename') == 'ScriptFileName'
+            assert args[0].containsKey('source.hash')
+            return cacheBuilder
+        }
+        1 * cacheBuilder.withInitializer(!null) >> cacheBuilder
+        1 * cacheBuilder.withDisplayName(!null) >> cacheBuilder
+        1 * cacheBuilder.withValidator(!null) >> cacheBuilder
+        1 * cacheBuilder.open() >> cache
+        1 * scriptCompilationHandler.loadFromDir(source, classLoader, new File(cacheDir, "classes"), Script) >> Script
+        0 * scriptCompilationHandler._
+    }
+
+    def "passes CacheValidator to cacheBuilder"() {
+        setup:
+        cacheRepository.cache("scripts/ScriptClassName/Script/TransformerId") >> cacheBuilder
+        cacheBuilder.withProperties(!null) >> cacheBuilder
+        cacheBuilder.withInitializer(!null) >> cacheBuilder
+        cacheBuilder.withDisplayName(!null) >> cacheBuilder
+        cacheBuilder.open() >> cache
+        scriptCompilationHandler.loadFromDir(source, classLoader, new File(cacheDir, "classes"), Script) >> Script
+
+        when:
+        compiler.compile(source, classLoader, transformer, Script)
+
+        then:
+        1 * cacheBuilder.withValidator(validator) >> cacheBuilder
+
+
+    }
+
+    def "compiles classes to cache directory when cache is invalid"() {
+        def initializer
+
+        when:
+        def result = compiler.compile(source, classLoader, transformer, Script)
+
+        then:
+        result == Script
+        1 * cacheRepository.cache("scripts/ScriptClassName/Script/TransformerId") >> cacheBuilder
+        1 * cacheBuilder.withProperties(!null) >> cacheBuilder
+        1 * cacheBuilder.withDisplayName(!null) >> cacheBuilder
+        1 * cacheBuilder.withValidator(!null) >> cacheBuilder
+        1 * cacheBuilder.withInitializer(!null) >> {args -> initializer = args[0]; return cacheBuilder}
+        1 * cacheBuilder.open() >> {initializer.execute(cache); return cache}
+        1 * scriptCompilationHandler.compileToDir(source, classLoader, new File(cacheDir, "classes"), transformer, Script)
+        1 * scriptCompilationHandler.loadFromDir(source, classLoader, new File(cacheDir, "classes"), Script) >> Script
+        0 * scriptCompilationHandler._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/ShortCircuitEmptyScriptCompilerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/ShortCircuitEmptyScriptCompilerTest.groovy
new file mode 100644
index 0000000..1719427
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/ShortCircuitEmptyScriptCompilerTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.groovy.scripts.internal
+
+import spock.lang.Specification
+import org.gradle.api.internal.resource.Resource
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.groovy.scripts.Transformer
+import org.gradle.groovy.scripts.Script
+import org.gradle.groovy.scripts.TestScript
+
+class ShortCircuitEmptyScriptCompilerTest extends Specification {
+    final EmptyScriptGenerator emptyScriptGenerator = Mock()
+    final ScriptClassCompiler target = Mock()
+    final ScriptSource source = Mock()
+    final Resource resource = Mock()
+    final ClassLoader classLoader = Mock()
+    final Transformer transformer = Mock()
+    final ShortCircuitEmptyScriptCompiler compiler = new ShortCircuitEmptyScriptCompiler(target, emptyScriptGenerator)
+
+    def setup() {
+        _ * source.resource >> resource
+    }
+
+    def "returns empty script object when script contains only whitespace"() {
+        given:
+        _ * resource.text >> '  \n\t'
+
+        when:
+        def result = compiler.compile(source, classLoader, transformer, Script)
+
+        then:
+        result == TestScript
+        1 * emptyScriptGenerator.generate(Script) >> TestScript
+        0 * emptyScriptGenerator._
+        0 * target._
+    }
+
+    def "compiles script when script contains anything other than whitespace"() {
+        given:
+        _ * resource.text >> 'some script'
+
+        when:
+        def result = compiler.compile(source, classLoader, transformer, Script)
+
+        then:
+        result == TestScript
+        1 * target.compile(source, classLoader, transformer, Script) >> TestScript
+        0 * emptyScriptGenerator._
+        0 * target._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/AbstractSettingsFinderStrategyTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/AbstractSettingsFinderStrategyTest.java
deleted file mode 100644
index 817fb46..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/AbstractSettingsFinderStrategyTest.java
+++ /dev/null
@@ -1,70 +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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.api.initialization.Settings;
-import org.gradle.util.TemporaryFolder;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractSettingsFinderStrategyTest {
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-    protected File testDir = tmpDir.getDir();
-    protected File currentDir;
-
-    protected abstract ISettingsFileSearchStrategy getStrategy();
-
-    protected File createSettingsFile(File dir) {
-        File file = new File(dir, Settings.DEFAULT_SETTINGS_FILE);
-        try {
-            file.createNewFile();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        file.deleteOnExit();
-        return file;
-    }
-
-    protected StartParameter createStartParams(boolean searchUpwards) {
-        StartParameter startParameter = new StartParameter();
-        startParameter.setCurrentDir(currentDir);
-        startParameter.setSearchUpwards(searchUpwards);
-        return startParameter;
-    }
-
-    @Test
-    public void findExistingSettingsInCurrentDirWithSearchUpwardsTrue() {
-        org.junit.Assert.assertEquals(createSettingsFile(currentDir), getStrategy().find(createStartParams(true)));
-    }
-
-    @Test
-    public void findExistingSettingsInCurrentDirWithSearchUpwardsFalse() {
-        org.junit.Assert.assertEquals(createSettingsFile(currentDir), getStrategy().find(createStartParams(false)));
-    }
-
-    @Test
-    public void findNonExistingSettingsInCurrentDirWithSearchUpwardsFalse() {
-        org.junit.Assert.assertEquals(null, getStrategy().find(createStartParams(false)));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildLoaderTest.groovy
deleted file mode 100644
index eb15c93..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildLoaderTest.groovy
+++ /dev/null
@@ -1,206 +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.initialization
-
-import org.gradle.StartParameter
-import org.gradle.api.GradleException
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.Project
-import org.gradle.api.initialization.ProjectDescriptor
-import org.gradle.api.internal.GradleInternal
-import org.gradle.api.internal.project.DefaultProject
-import org.gradle.api.internal.project.IProjectFactory
-import org.gradle.api.internal.project.IProjectRegistry
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.util.GUtil
-import org.gradle.util.HelperUtil
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TemporaryFolder
-import org.jmock.integration.junit4.JMock
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.initialization.*
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-class BuildLoaderTest {
-
-    BuildLoader buildLoader
-    IProjectFactory projectFactory
-    File testDir
-    File rootProjectDir
-    File childProjectDir
-    IProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
-    StartParameter startParameter = new StartParameter()
-    ProjectDescriptor rootDescriptor
-    ProjectInternal rootProject
-    ProjectDescriptor childDescriptor
-    ProjectInternal childProject
-    GradleInternal build
-    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
-
-    @Before public void setUp()  {
-        projectFactory = context.mock(IProjectFactory)
-        buildLoader = new BuildLoader(projectFactory)
-        testDir = tmpDir.dir
-        (rootProjectDir = new File(testDir, 'root')).mkdirs()
-        (childProjectDir = new File(rootProjectDir, 'child')).mkdirs()
-        startParameter.currentDir = rootProjectDir
-        rootDescriptor = descriptor('root', null, rootProjectDir)
-        rootProject = project(rootDescriptor, null)
-        childDescriptor = descriptor('child', rootDescriptor, childProjectDir)
-        childProject = project(childDescriptor, rootProject)
-        build = context.mock(GradleInternal)
-        context.checking {
-            allowing(build).getStartParameter()
-            will(returnValue(startParameter))
-        }
-    }
-
-    @Test public void createsBuildWithRootProject() {
-        ProjectDescriptor rootDescriptor = descriptor('root', null, rootProjectDir)
-        ProjectInternal rootProject = project(rootDescriptor, null)
-
-        context.checking {
-            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
-                    withParam(nullValue()),
-                    withParam(notNullValue()))
-            will(returnValue(rootProject))
-            one(build).setRootProject(rootProject)
-            allowing(build).getRootProject()
-            will(returnValue(rootProject))
-            one(build).setDefaultProject(rootProject)
-        }
-
-        buildLoader.load(rootDescriptor, build, [:])
-    }
-
-    @Test public void createsBuildWithMultipleProjects() {
-        expectProjectsCreated()
-
-        buildLoader.load(rootDescriptor, build, [:])
-
-        assertThat(rootProject.childProjects['child'], sameInstance(childProject))
-    }
-
-    @Test public void setsExternalPropertiesOnEachProject() {
-        expectProjectsCreated()
-
-        buildLoader.load(rootDescriptor, build, [buildDirName: 'target', prop: 'value'])
-
-        assertThat(rootProject.buildDirName, equalTo('target'))
-        assertThat(rootProject.prop, equalTo('value'))
-
-        assertThat(rootProject.project('child').buildDirName, equalTo('target'))
-        assertThat(rootProject.project('child').prop, equalTo('value'))
-    }
-
-    @Test public void setsProjectSpecificProperties() {
-        GUtil.saveProperties(new Properties([buildDirName: 'target/root', prop: 'rootValue']), new File(rootProjectDir, Project.GRADLE_PROPERTIES))
-        GUtil.saveProperties(new Properties([buildDirName: 'target/child', prop: 'childValue']), new File(childProjectDir, Project.GRADLE_PROPERTIES))
-
-        expectProjectsCreated()
-
-        buildLoader.load(rootDescriptor, build, [:])
-
-        assertThat(rootProject.buildDirName, equalTo('target/root'))
-        assertThat(rootProject.prop, equalTo('rootValue'))
-
-        assertThat(rootProject.project('child').buildDirName, equalTo('target/child'))
-        assertThat(rootProject.project('child').prop, equalTo('childValue'))
-    }
-
-    @Test public void selectsDefaultProject() {
-        expectProjectsCreatedNoDefaultProject()
-
-        ProjectSpec selector = context.mock(ProjectSpec)
-        startParameter.defaultProjectSelector = selector
-        context.checking {
-            one(selector).selectProject(withParam(instanceOf(IProjectRegistry)))
-            will(returnValue(childProject))
-
-            one(build).setDefaultProject(childProject)
-        }
-
-        buildLoader.load(rootDescriptor, build, [:])
-    }
-
-    @Test public void wrapsDefaultProjectSelectionException() {
-        expectProjectsCreatedNoDefaultProject()
-
-        ProjectSpec selector = context.mock(ProjectSpec)
-        startParameter.defaultProjectSelector = selector
-        context.checking {
-            one(selector).selectProject(withParam(instanceOf(IProjectRegistry)))
-            will(throwException(new InvalidUserDataException("<error>")))
-        }
-
-        try {
-            buildLoader.load(rootDescriptor, build, [:])
-            fail()
-        } catch (GradleException e) {
-            assertThat(e.message, equalTo('Could not select the default project for this build. <error>'))
-        }
-    }
-
-    private def expectProjectsCreatedNoDefaultProject() {
-        context.checking {
-            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
-                    withParam(nullValue()),
-                    withParam(notNullValue()))
-            will(returnValue(rootProject))
-
-            one(projectFactory).createProject(withParam(equalTo(childDescriptor)),
-                    withParam(equalTo(rootProject)),
-                    withParam(notNullValue()))
-            will(returnValue(childProject))
-
-            one(build).setRootProject(rootProject)
-            allowing(build).getRootProject()
-            will(returnValue(rootProject))
-        }
-    }
-
-    private def expectProjectsCreated() {
-        expectProjectsCreatedNoDefaultProject()
-
-        context.checking {
-            one(build).setDefaultProject(rootProject)
-        }
-    }
-
-    private ProjectDescriptor descriptor(String name, ProjectDescriptor parent, File projectDir) {
-        new DefaultProjectDescriptor(parent, name, projectDir, projectDescriptorRegistry)
-    }
-
-    private ProjectInternal project(ProjectDescriptor descriptor, ProjectInternal parent) {
-        DefaultProject project
-        if (parent) {
-            project = HelperUtil.createChildProject(parent, descriptor.name, descriptor.projectDir)
-        } else {
-            project = HelperUtil.createRootProject(descriptor.projectDir)
-        }
-        project
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
index 1f79093..68e5df3 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
@@ -13,16 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.initialization
 
-import static org.junit.Assert.*
-
 import org.gradle.BuildResult
 import org.gradle.GradleLauncher
-
 import org.gradle.StartParameter
 import org.gradle.api.Project
 import org.gradle.api.file.FileCollection
@@ -31,9 +25,8 @@ import org.gradle.api.invocation.Gradle
 import org.gradle.api.plugins.Convention
 import org.gradle.cache.CacheBuilder
 import org.gradle.cache.CacheRepository
-import org.gradle.cache.PersistentCache
+import org.gradle.cache.ObjectCacheBuilder
 import org.gradle.cache.PersistentStateCache
-import org.gradle.groovy.scripts.StringScriptSource
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
@@ -43,6 +36,7 @@ import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertEquals
 
 /**
  * @author Hans Dockter
@@ -67,7 +61,7 @@ class BuildSourceBuilderTest {
     EmbeddableJavaProject projectMetaInfo = context.mock(EmbeddableJavaProject.class)
 
     @Before public void setUp() {
-        buildSourceBuilder = new BuildSourceBuilder(gradleFactoryMock, context.mock(ClassLoaderFactory.class), cacheRepository)
+        buildSourceBuilder = new BuildSourceBuilder(gradleFactoryMock, context.mock(ClassLoaderRegistry.class), cacheRepository)
         expectedStartParameter = new StartParameter(currentDir: testBuildSrcDir)
         testDependencies = ['dep1' as File, 'dep2' as File]
         Convention convention = context.mock(Convention)
@@ -83,7 +77,7 @@ class BuildSourceBuilderTest {
         expectedBuildResult = new BuildResult(build, null)
     }
 
-    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndContainsBuildScript() {
+    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasNotBeenBuiltBefore() {
         expectValueFetchedFromCache(null)
         context.checking {
             one(projectMetaInfo).getRebuildTasks(); will(returnValue(['clean', 'build']))
@@ -99,37 +93,12 @@ class BuildSourceBuilderTest {
         assertEquals(testDependencies, actualClasspath)
     }
 
-    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndDoesNotContainBuildScript() {
-        expectValueFetchedFromCache(null)
-        context.checking {
-            one(projectMetaInfo).getRebuildTasks(); will(returnValue(['clean', 'build']))
-            one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()))
-            will { StartParameter param ->
-                assertThat(param.buildScriptSource, instanceOf(StringScriptSource.class))
-                assertThat(param.buildScriptSource.displayName, equalTo('default buildSrc build script'))
-                assertThat(param.buildScriptSource.resource.text, equalTo(BuildSourceBuilder.defaultScript))
-                return gradleMock
-            }
-            one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
-            one(gradleMock).run(); will(returnValue(expectedBuildResult))
-        }
-        expectValueWrittenToCache()
-
-        Set actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter)
-        assertEquals(testDependencies, actualClasspath)
-    }
-
     @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasBeenBuiltBefore() {
         expectValueFetchedFromCache(true)
         context.checking {
             one(projectMetaInfo).getBuildTasks(); will(returnValue(['build']))
             one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()))
-            will { StartParameter param ->
-                assertThat(param.buildScriptSource, instanceOf(StringScriptSource.class))
-                assertThat(param.buildScriptSource.displayName, equalTo('default buildSrc build script'))
-                assertThat(param.buildScriptSource.resource.text, equalTo(BuildSourceBuilder.defaultScript))
-                return gradleMock
-            }
+            will(returnValue(gradleMock))
             one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
             one(gradleMock).run(); will(returnValue(expectedBuildResult))
         }
@@ -147,21 +116,17 @@ class BuildSourceBuilderTest {
 
     private expectValueFetchedFromCache(def value) {
         context.checking {
-            CacheBuilder builder = context.mock(CacheBuilder.class)
-            PersistentCache cache = context.mock(PersistentCache.class)
-            one(cacheRepository).cache('buildSrc')
+            ObjectCacheBuilder<Boolean, PersistentStateCache<Boolean>> builder = context.mock(ObjectCacheBuilder.class)
+            one(cacheRepository).stateCache(Boolean.class, 'buildSrc')
             will(returnValue(builder))
 
             one(builder).forObject(testBuildSrcDir)
             will(returnValue(builder))
 
-            one(builder).invalidateOnVersionChange()
+            one(builder).withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange)
             will(returnValue(builder))
 
             one(builder).open()
-            will(returnValue(cache))
-
-            one(cache).openStateCache()
             will(returnValue(stateCache))
 
             one(stateCache).get()
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy
deleted file mode 100644
index dc0666b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/CommandLineParserTest.groovy
+++ /dev/null
@@ -1,549 +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.initialization
-
-import spock.lang.Specification
-import org.gradle.CommandLineArgumentException
-
-class CommandLineParserTest extends Specification {
-    private final CommandLineParser parser = new CommandLineParser()
-
-    def parsesEmptyCommandLine() {
-        parser.option('a')
-        parser.option('long-value')
-
-        expect:
-        def result = parser.parse([])
-        !result.hasOption('a')
-        !result.hasOption('long-value')
-        result.extraArguments == []
-    }
-
-    def parsesShortOption() {
-        parser.option('a')
-        parser.option('b')
-
-        expect:
-        def result = parser.parse(['-a'])
-        result.hasOption('a')
-        !result.hasOption('b')
-    }
-
-    def canUseDoubleDashesForShortOptions() {
-        parser.option('a')
-
-        expect:
-        def result = parser.parse(['--a'])
-        result.hasOption('a')
-    }
-
-    def parsesShortOptionWithArgument() {
-        parser.option('a').hasArgument()
-
-        expect:
-        def result = parser.parse(['-a', 'arg'])
-        result.hasOption('a')
-        result.option('a').value == 'arg'
-        result.option('a').values == ['arg']
-    }
-
-    def parsesShortOptionWithAttachedArgument() {
-        parser.option('a').hasArgument()
-
-        expect:
-        def result = parser.parse(['-aarg'])
-        result.hasOption('a')
-        result.option('a').value == 'arg'
-        result.option('a').values == ['arg']
-    }
-
-    def attachedArgumentTakesPrecedenceOverCombinedOption() {
-        parser.option('a').hasArgument()
-        parser.option('b')
-
-        expect:
-        def result = parser.parse(['-ab'])
-        result.hasOption('a')
-        result.option('a').value == 'b'
-        !result.hasOption('b')
-    }
-
-    def parsesShortOptionWithEqualArgument() {
-        parser.option('a').hasArgument()
-
-        expect:
-        def result = parser.parse(['-a=arg'])
-        result.hasOption('a')
-        result.option('a').value == 'arg'
-        result.option('a').values == ['arg']
-    }
-
-    def parsesShortOptionWithEqualsCharacterInAttachedArgument() {
-        parser.option('a').hasArgument()
-
-        expect:
-        def result = parser.parse(['-avalue=arg'])
-        result.hasOption('a')
-        result.option('a').value == 'value=arg'
-        result.option('a').values == ['value=arg']
-    }
-
-    def parsesShortOptionWithDashCharacterInAttachedArgument() {
-        parser.option('a').hasArgument()
-
-        expect:
-        def result = parser.parse(['-avalue-arg'])
-        result.hasOption('a')
-        result.option('a').value == 'value-arg'
-        result.option('a').values == ['value-arg']
-    }
-
-    def parsesCombinedShortOptions() {
-        parser.option('a')
-        parser.option('b')
-
-        expect:
-        def result = parser.parse(['-ab'])
-        result.hasOption('a')
-        result.hasOption('b')
-    }
-
-    def parsesLongOption() {
-        parser.option('long-option-a')
-        parser.option('long-option-b')
-
-        expect:
-        def result = parser.parse(['--long-option-a'])
-        result.hasOption('long-option-a')
-        !result.hasOption('long-option-b')
-    }
-
-    def canUseSingleDashForLongOptions() {
-        parser.option('long')
-        parser.option('other').hasArgument()
-
-        expect:
-        def result = parser.parse(['-long', '-other', 'arg'])
-        result.hasOption('long')
-        result.hasOption('other')
-        result.option('other').value == 'arg'
-    }
-
-    def parsesLongOptionWithArgument() {
-        parser.option('long-option-a').hasArgument()
-        parser.option('long-option-b')
-
-        expect:
-        def result = parser.parse(['--long-option-a', 'arg'])
-        result.hasOption('long-option-a')
-        result.option('long-option-a').value == 'arg'
-        result.option('long-option-a').values == ['arg']
-    }
-
-    def parsesLongOptionWithEqualsArgument() {
-        parser.option('long-option-a').hasArgument()
-
-        expect:
-        def result = parser.parse(['--long-option-a=arg'])
-        result.hasOption('long-option-a')
-        result.option('long-option-a').value == 'arg'
-        result.option('long-option-a').values == ['arg']
-    }
-
-    def parsesMultipleOptions() {
-        parser.option('a').hasArgument()
-        parser.option('long-option')
-
-        expect:
-        def result = parser.parse(['--long-option', '-a', 'arg'])
-        result.hasOption('long-option')
-        result.hasOption('a')
-        result.option('a').value == 'arg'
-    }
-
-    def parsesOptionWithMultipleAliases() {
-        parser.option('a', 'b', 'long-option-a')
-
-        expect:
-        def longOptionResult = parser.parse(['--long-option-a'])
-        longOptionResult.hasOption('a')
-        longOptionResult.hasOption('b')
-        longOptionResult.hasOption('long-option-a')
-        longOptionResult.option('a') == longOptionResult.option('long-option-a')
-        longOptionResult.option('a') == longOptionResult.option('b')
-
-        def shortOptionResult = parser.parse(['-a'])
-        shortOptionResult.hasOption('a')
-        shortOptionResult.hasOption('b')
-        shortOptionResult.hasOption('long-option-a')
-    }
-
-    def parsesCommandLineWhenOptionAppearsMultipleTimes() {
-        parser.option('a', 'b', 'long-option-a')
-
-        expect:
-        def result = parser.parse(['--long-option-a', '-a', '-a', '-b'])
-        result.hasOption('a')
-        result.hasOption('b')
-        result.hasOption('long-option-a')
-    }
-
-    def parsesOptionWithMultipleArguments() {
-        parser.option('a', 'long').hasArguments()
-
-        expect:
-        def result = parser.parse(['-a', 'arg1', '--long', 'arg2', '-aarg3', '--long=arg4'])
-        result.hasOption('a')
-        result.hasOption('long')
-        result.option('a').values == ['arg1', 'arg2', 'arg3', 'arg4']
-    }
-
-    def parsesCommandLineWithSubcommand() {
-        parser.option('a')
-
-        expect:
-        def singleArgResult = parser.parse(['a'])
-        singleArgResult.extraArguments == ['a']
-        !singleArgResult.hasOption('a')
-
-        def multipleArgsResult = parser.parse(['a', 'b'])
-        multipleArgsResult.extraArguments == ['a', 'b']
-        !multipleArgsResult.hasOption('a')
-    }
-
-    def parsesCommandLineWithOptionsAndSubcommand() {
-        parser.option('a')
-
-        expect:
-        def optionBeforeSubcommandResult = parser.parse(['-a', 'a'])
-        optionBeforeSubcommandResult.extraArguments == ['a']
-        optionBeforeSubcommandResult.hasOption('a')
-
-        def optionAfterSubcommandResult = parser.parse(['a', '-a'])
-        optionAfterSubcommandResult.extraArguments == ['a', '-a']
-        !optionAfterSubcommandResult.hasOption('a')
-    }
-
-    def parsesCommandLineWithOptionsAndSubcommandWhenMixedOptionsAllowed() {
-        parser.option('a')
-        parser.allowMixedSubcommandsAndOptions()
-
-        expect:
-        def optionBeforeSubcommandResult = parser.parse(['-a', 'a'])
-        optionBeforeSubcommandResult.extraArguments == ['a']
-        optionBeforeSubcommandResult.hasOption('a')
-
-        def optionAfterSubcommandResult = parser.parse(['a', '-a'])
-        optionAfterSubcommandResult.extraArguments == ['a']
-        optionAfterSubcommandResult.hasOption('a')
-    }
-
-    def parsesCommandLineWithSubcommandThatHasOptions() {
-        when:
-        def result = parser.parse(['a', '--option', 'b'])
-
-        then:
-        result.extraArguments == ['a', '--option', 'b']
-
-        when:
-        parser.allowMixedSubcommandsAndOptions()
-        result = parser.parse(['a', '--option', 'b'])
-
-        then:
-        result.extraArguments == ['a', '--option', 'b']
-    }
-
-    def canMapOptionToSubcommand() {
-        parser.option('a').mapsToSubcommand('subcmd')
-
-        expect:
-        def result = parser.parse(['-a', '--option', 'b'])
-        result.extraArguments == ['subcmd', '--option', 'b']
-        result.hasOption('a')
-    }
-
-    def canCombineSubcommandShortOptionWithOtherShortOptions() {
-        parser.option('a').mapsToSubcommand('subcmd')
-        parser.option('b')
-
-        when:
-        def result = parser.parse(['-abc', '--option', 'b'])
-
-        then:
-        result.extraArguments == ['subcmd', '-b', '-c', '--option', 'b']
-        result.hasOption('a')
-        !result.hasOption('b')
-
-        when:
-        result = parser.parse(['-bac', '--option', 'b'])
-
-        then:
-        result.extraArguments == ['subcmd', '-c', '--option', 'b']
-        result.hasOption('a')
-        result.hasOption('b')
-
-        when:
-        parser.allowMixedSubcommandsAndOptions()
-        result = parser.parse(['-abc', '--option', 'b'])
-
-        then:
-        result.extraArguments == ['subcmd', '-c', '--option', 'b']
-        result.hasOption('a')
-        result.hasOption('b')
-
-        when:
-        result = parser.parse(['-bac', '--option', 'b'])
-
-        then:
-        result.extraArguments == ['subcmd', '-c', '--option', 'b']
-        result.hasOption('a')
-        result.hasOption('b')
-    }
-
-    def singleDashIsNotConsideredAnOption() {
-        expect:
-        def result = parser.parse(['-'])
-        result.extraArguments == ['-']
-    }
-
-    def doubleDashMarksEndOfOptions() {
-        parser.option('a')
-
-        expect:
-        def result = parser.parse(['--', '-a'])
-        result.extraArguments == ['-a']
-        !result.hasOption('a')
-    }
-
-    def valuesEmptyWhenOptionIsNotPresentInCommandLine() {
-        parser.option('a').hasArgument()
-
-        expect:
-        def result = parser.parse([])
-        result.option('a').values == []
-    }
-
-    def formatsUsageMessage() {
-        parser.option('a', 'long-option').hasDescription('this is option a')
-        parser.option('b')
-        parser.option('another-long-option').hasDescription('this is a long option')
-        parser.option('z', 'y', 'last-option', 'end-option').hasDescription('this is the last option')
-        parser.option('B')
-        def outstr = new ByteArrayOutputStream()
-
-        expect:
-        parser.printUsage(outstr)
-        outstr.toString().readLines() == [
-                '-a, --long-option                    this is option a',
-                '--another-long-option                this is a long option',
-                '-B',
-                '-b',
-                '-y, -z, --end-option, --last-option  this is the last option'
-        ]
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownShortOption() {
-        when:
-        parser.parse(['-a'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownShortOptionWithDoubleDashes() {
-        when:
-        parser.parse(['--a'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'--a\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownShortOptionWithEqualsArgument() {
-        when:
-        parser.parse(['-a=arg'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownShortOptionWithAttachedArgument() {
-        when:
-        parser.parse(['-aarg'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownLongOption() {
-        when:
-        parser.parse(['--unknown'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'--unknown\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownLongOptionWithSingleDashes() {
-        when:
-        parser.parse(['-unknown'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'-u\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsUnknownLongOptionWithEqualsArgument() {
-        when:
-        parser.parse(['--unknown=arg'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'--unknown\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsLongOptionWithAttachedArgument() {
-        parser.option("long").hasArgument()
-
-        when:
-        parser.parse(['--longvalue'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'--longvalue\'.'
-    }
-
-    def parseFailsWhenCommandLineContainsDashAndEquals() {
-        parser.option("long").hasArgument()
-
-        when:
-        parser.parse(['-='])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'-=\'.'
-    }
-
-    def getOptionFailsForUnknownOption() {
-        def result = parser.parse(['other'])
-
-        when:
-        result.option('unknown')
-
-        then:
-        def e = thrown(IllegalArgumentException)
-        e.message == 'Option \'unknown\' not defined.'
-
-        when:
-        result.hasOption('unknown')
-
-        then:
-        e = thrown(IllegalArgumentException)
-        e.message == 'Option \'unknown\' not defined.'
-    }
-
-    def parseFailsWhenSingleValueOptionHasMultipleArguments() {
-        parser.option('a').hasArgument()
-
-        when:
-        parser.parse(['-a=arg1', '-a', 'arg2'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Multiple arguments were provided for command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenArgumentIsMissing() {
-        parser.option('a').hasArgument()
-
-        when:
-        parser.parse(['-a'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'No argument was provided for command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenArgumentIsMissingFromEqualsForm() {
-        parser.option('a').hasArgument()
-
-        when:
-        parser.parse(['-a='])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'An empty argument was provided for command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenEmptyArgumentIsProvided() {
-        parser.option('a').hasArgument()
-
-        when:
-        parser.parse(['-a', ''])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'An empty argument was provided for command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenArgumentIsMissingAndAnotherOptionFollows() {
-        parser.option('a').hasArgument()
-
-        when:
-        parser.parse(['-a', '-b'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'No argument was provided for command-line option \'-a\'.'
-    }
-
-    def parseFailsWhenArgumentIsMissingAndOptionsAreCombined() {
-        parser.option('a')
-        parser.option('b').hasArgument()
-
-        when:
-        parser.parse(['-ab'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'No argument was provided for command-line option \'-b\'.'
-    }
-
-    def parseFailsWhenAttachedArgumentIsProvidedForOptionWhichDoesNotTakeAnArgument() {
-        parser.option('a')
-
-        when:
-        parser.parse(['-aarg'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Unknown command-line option \'-r\'.'
-    }
-
-    def parseFailsWhenEqualsArgumentIsProvidedForOptionWhichDoesNotTakeAnArgument() {
-        parser.option('a')
-
-        when:
-        parser.parse(['-a=arg'])
-
-        then:
-        def e = thrown(CommandLineArgumentException)
-        e.message == 'Command-line option \'-a\' does not take an argument.'
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy
new file mode 100644
index 0000000..3765d00
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/CompositeInitScriptFinderTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.api.internal.GradleInternal
+
+class CompositeInitScriptFinderTest extends Specification {
+    final InitScriptFinder target1 = Mock()
+    final InitScriptFinder target2 = Mock()
+    final GradleInternal gradle = Mock()
+    final CompositeInitScriptFinder finder = new CompositeInitScriptFinder(target1, target2)
+    
+    def "collects up scripts from all finders"() {
+        def result = []
+
+        when:
+        finder.findScripts(gradle, result)
+
+        then:
+        1 * target1.findScripts(gradle, result)
+        1 * target2.findScripts(gradle, result)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
index db7f0e9..fdcc978 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -17,12 +17,11 @@
 package org.gradle.initialization;
 
 import org.gradle.CacheUsage;
-import org.gradle.CommandLineArgumentException;
+import org.gradle.RefreshOptions;
 import org.gradle.StartParameter;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.logging.LogLevel;
-import org.gradle.groovy.scripts.UriScriptSource;
-import org.gradle.util.GUtil;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.logging.ShowStacktrace;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
 import org.junit.Rule;
@@ -32,9 +31,9 @@ import java.io.File;
 import java.io.IOException;
 import java.util.*;
 
+import static java.util.Arrays.asList;
 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;
 
@@ -51,22 +50,27 @@ public class DefaultCommandLineConverterTest {
     private File expectedProjectDir = currentDir;
     private List<String> expectedTaskNames = toList();
     private Set<String> expectedExcludedTasks = toSet();
-    private ProjectDependenciesBuildInstruction expectedProjectDependenciesBuildInstruction
-            = new ProjectDependenciesBuildInstruction(true);
+    private boolean buildProjectDependencies = true;
     private Map<String, String> expectedSystemProperties = new HashMap<String, String>();
     private Map<String, String> expectedProjectProperties = new HashMap<String, String>();
     private List<File> expectedInitScripts = new ArrayList<File>();
     private CacheUsage expectedCacheUsage = CacheUsage.ON;
     private boolean expectedSearchUpwards = true;
     private boolean expectedDryRun;
-    private StartParameter.ShowStacktrace expectedShowStackTrace = StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+    private ShowStacktrace expectedShowStackTrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
     private String expectedEmbeddedScript = "somescript";
     private LogLevel expectedLogLevel = LogLevel.LIFECYCLE;
     private boolean expectedColorOutput = true;
     private StartParameter actualStartParameter;
     private boolean expectedProfile;
-
+    private File expectedProjectCacheDir;
+    private boolean expectedRefreshDependencies;
+    private boolean expectedRerunTasks;
     private final DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
+    private boolean expectedContinue;
+    private boolean expectedOffline;
+    private RefreshOptions expectedRefreshOptions = RefreshOptions.NONE;
+    private boolean expectedRecompileScripts;
 
     @Test
     public void withoutAnyOptions() {
@@ -74,21 +78,23 @@ public class DefaultCommandLineConverterTest {
     }
 
     private void checkConversion(String... args) {
-        checkConversion(false, args);
+        actualStartParameter = new StartParameter();
+        actualStartParameter.setCurrentDir(currentDir);
+        commandLineConverter.convert(asList(args), actualStartParameter);
+        // We check the params passed to the build factory
+        checkStartParameter(actualStartParameter);
     }
 
     private void checkStartParameter(StartParameter startParameter) {
         assertEquals(expectedBuildFile, startParameter.getBuildFile());
         assertEquals(expectedTaskNames, startParameter.getTaskNames());
-        assertEquals(expectedProjectDependenciesBuildInstruction,
-                startParameter.getProjectDependenciesBuildInstruction());
+        assertEquals(buildProjectDependencies, startParameter.isBuildProjectDependencies());
         assertEquals(expectedProjectDir.getAbsoluteFile(), startParameter.getCurrentDir().getAbsoluteFile());
         assertEquals(expectedCacheUsage, startParameter.getCacheUsage());
         assertEquals(expectedSearchUpwards, startParameter.isSearchUpwards());
         assertEquals(expectedProjectProperties, startParameter.getProjectProperties());
         assertEquals(expectedSystemProperties, startParameter.getSystemPropertiesArgs());
         assertEquals(expectedGradleUserHome.getAbsoluteFile(), startParameter.getGradleUserHomeDir().getAbsoluteFile());
-        assertEquals(expectedGradleUserHome.getAbsoluteFile(), startParameter.getGradleUserHomeDir().getAbsoluteFile());
         assertEquals(expectedLogLevel, startParameter.getLogLevel());
         assertEquals(expectedColorOutput, startParameter.isColorOutput());
         assertEquals(expectedDryRun, startParameter.isDryRun());
@@ -96,19 +102,13 @@ public class DefaultCommandLineConverterTest {
         assertEquals(expectedExcludedTasks, startParameter.getExcludedTaskNames());
         assertEquals(expectedInitScripts, startParameter.getInitScripts());
         assertEquals(expectedProfile, startParameter.isProfile());
-    }
-
-    private void checkConversion(final boolean embedded, String... 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) {
-            assertThat(actualStartParameter.getBuildScriptSource().getResource().getText(), equalTo(expectedEmbeddedScript));
-        } else {
-            assert !GUtil.isTrue(actualStartParameter.getBuildScriptSource());
-        }
+        assertEquals(expectedContinue, startParameter.isContinueOnFailure());
+        assertEquals(expectedOffline, startParameter.isOffline());
+        assertEquals(expectedRecompileScripts, startParameter.isRecompileScripts());
+        assertEquals(expectedRerunTasks, startParameter.isRerunTasks());
+        assertEquals(expectedRefreshOptions, startParameter.getRefreshOptions());
+        assertEquals(expectedRefreshDependencies, startParameter.isRefreshDependencies());
+        assertEquals(expectedProjectCacheDir, startParameter.getProjectCacheDir());
     }
 
     @Test
@@ -121,6 +121,12 @@ public class DefaultCommandLineConverterTest {
     }
 
     @Test
+    public void withSpecifiedProjectCacheDir() {
+        expectedProjectCacheDir = new File(currentDir, ".foo");
+        checkConversion("--project-cache-dir", ".foo");
+    }
+
+    @Test
     public void withSpecifiedProjectDirectory() {
         expectedProjectDir = testDir.file("project-dir");
         checkConversion("-p", expectedProjectDir.getAbsolutePath());
@@ -147,8 +153,7 @@ public class DefaultCommandLineConverterTest {
 
         checkConversion("-c", "somesettings");
 
-        assertThat(actualStartParameter.getSettingsScriptSource(), instanceOf(UriScriptSource.class));
-        assertThat(actualStartParameter.getSettingsScriptSource().getResource().getFile(), equalTo(expectedSettingsFile));
+        assertThat(actualStartParameter.getSettingsFile(), equalTo(expectedSettingsFile));
     }
 
     @Test
@@ -172,6 +177,24 @@ public class DefaultCommandLineConverterTest {
         expectedSystemProperties.put(prop2, valueProp2);
         checkConversion("-D", prop1 + "=" + valueProp1, "-D", prop2 + "=" + valueProp2);
     }
+    
+    @Test
+    public void withSpecifiedGradleUserHomeDirectoryBySystemProperty() {
+        expectedGradleUserHome = testDir.file("home");
+        String propName = "gradle.user.home";
+        String propValue = expectedGradleUserHome.getAbsolutePath();
+        expectedSystemProperties = toMap(propName, propValue);
+        checkConversion("-D", propName+"="+propValue);
+    }
+
+    @Test
+    public void privilegeCmdLineOptionOverSystemPrefForGradleUserHome() {
+        expectedGradleUserHome = testDir.file("home");
+        String propName = "gradle.user.home";
+        String propValue = "home2";
+        expectedSystemProperties = toMap(propName, propValue);
+        checkConversion("-D", propName+"="+propValue, "-g", expectedGradleUserHome.getAbsolutePath());
+    }
 
     @Test
     public void withStartProperties() {
@@ -214,16 +237,22 @@ public class DefaultCommandLineConverterTest {
 
     @Test
     public void withShowFullStacktrace() {
-        expectedShowStackTrace = StartParameter.ShowStacktrace.ALWAYS_FULL;
+        expectedShowStackTrace = ShowStacktrace.ALWAYS_FULL;
         checkConversion("-S");
     }
 
     @Test
     public void withShowStacktrace() {
-        expectedShowStackTrace = StartParameter.ShowStacktrace.ALWAYS;
+        expectedShowStackTrace = ShowStacktrace.ALWAYS;
         checkConversion("-s");
     }
 
+    @Test
+    public void withRerunTasks() {
+        expectedRerunTasks = true;
+        checkConversion("--rerun-tasks");
+    }
+
     @Test(expected = CommandLineArgumentException.class)
     public void withShowStacktraceAndShowFullStacktraceShouldThrowCommandLineArgumentEx() {
         checkConversion("-sf");
@@ -243,12 +272,6 @@ public class DefaultCommandLineConverterTest {
         checkConversion("-x", "excluded", "-x", "excluded2");
     }
 
-    @Test
-    public void withEmbeddedScript() {
-        expectedSearchUpwards = false;
-        checkConversion(true, "-e", expectedEmbeddedScript);
-    }
-
     @Test(expected = CommandLineArgumentException.class)
     public void withEmbeddedScriptAndConflictingNoSearchUpwardsOption() {
         checkConversion("-e", "someScript", "-u", "clean");
@@ -266,7 +289,7 @@ public class DefaultCommandLineConverterTest {
 
     @Test
     public void withNoProjectDependencyRebuild() {
-        expectedProjectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(false);
+        buildProjectDependencies = false;
         checkConversion("-a");
     }
 
@@ -294,37 +317,6 @@ public class DefaultCommandLineConverterTest {
         checkConversion("--no-color");
     }
 
-    @Test
-    public void withShowTasks() {
-        expectedTaskNames = toList("tasks");
-        checkConversion(false, "-t");
-    }
-
-    @Test
-    public void withShowAllTasks() {
-        expectedTaskNames = toList("tasks", "--all");
-        checkConversion(false, "-t", "--all");
-    }
-
-    @Test
-    public void withShowTasksAndEmbeddedScript() {
-        expectedSearchUpwards = false;
-        expectedTaskNames = toList("tasks");
-        checkConversion(true, "-e", expectedEmbeddedScript, "-t");
-    }
-
-    @Test
-    public void withShowProperties() {
-        expectedTaskNames = toList("properties");
-        checkConversion(false, "-r");
-    }
-
-    @Test
-    public void withShowDependencies() {
-        expectedTaskNames = toList("dependencies");
-        checkConversion(false, "-n");
-    }
-
     @Test(expected = CommandLineArgumentException.class)
     public void withLowerPParameterWithoutArgument() {
         checkConversion("-p");
@@ -346,6 +338,43 @@ public class DefaultCommandLineConverterTest {
         checkConversion("--profile");
     }
 
+    @Test
+    public void withContinue() {
+        expectedContinue = true;
+        checkConversion("--continue");
+    }
+
+    @Test
+    public void withOffline() {
+        expectedOffline = true;
+        checkConversion("--offline");
+    }
+
+    @Test
+    public void withRefreshDependencies() {
+        expectedRefreshDependencies = true;
+        expectedRefreshOptions = new RefreshOptions(asList(RefreshOptions.Option.DEPENDENCIES));
+        checkConversion("--refresh-dependencies");
+    }
+
+    @Test
+    public void withRecompileScripts() {
+        expectedRecompileScripts = true;
+        checkConversion("--recompile-scripts");
+    }
+
+    @Test
+    public void withRefreshDependenciesSet() {
+        expectedRefreshDependencies = true;
+        expectedRefreshOptions = new RefreshOptions(Arrays.asList(RefreshOptions.Option.DEPENDENCIES));
+        checkConversion("--refresh", "dependencies");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withUnknownRefreshOption() {
+        checkConversion("--refresh", "unknown");
+    }
+
     @Test(expected = CommandLineArgumentException.class)
     public void withUnknownOption() {
         checkConversion("--unknown");
@@ -356,5 +385,4 @@ public class DefaultCommandLineConverterTest {
         expectedTaskNames = toList("someTask", "--some-task-option");
         checkConversion("someTask", "--some-task-option");
     }
-
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
old mode 100644
new mode 100755
index f9ac57b..5b49ff0
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
@@ -1,287 +1,287 @@
-/*
- * 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.api.GradleScriptException;
-import org.gradle.api.LocationAwareException;
-import org.gradle.api.internal.Contextual;
-import org.gradle.api.internal.MultiCauseException;
-import org.gradle.api.tasks.TaskExecutionException;
-import org.gradle.groovy.scripts.Script;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.listener.ListenerManager;
-import org.gradle.listener.ListenerNotificationException;
-import org.gradle.util.JUnit4GroovyMockery;
-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.util.Arrays;
-import java.util.List;
-
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.WrapUtil.toArray;
-import static org.gradle.util.WrapUtil.toList;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.assertThat;
-
- at RunWith(JMock.class)
-public class DefaultExceptionAnalyserTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
-    private final StackTraceElement element = new StackTraceElement("class", "method", "filename", 7);
-    private final StackTraceElement callerElement = new StackTraceElement("class", "method", "filename", 11);
-    private final StackTraceElement otherElement = new StackTraceElement("class", "method", "otherfile", 11);
-    private final ScriptSource source = context.mock(ScriptSource.class);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(source).getFileName();
-            will(returnValue("filename"));
-        }});
-    }
-
-    @Test
-    public void usesOriginalExceptionWhenItIsNotAContextualException() {
-        Throwable failure = new RuntimeException();
-
-        DefaultExceptionAnalyser analyser = analyser();
-        assertThat(analyser.transform(failure), sameInstance(failure));
-    }
-
-    @Test
-    public void wrapsContextualExceptionWithLocationAwareException() {
-        Throwable failure = new ContextualException();
-
-        DefaultExceptionAnalyser analyser = analyser();
-
-        Throwable transformedFailure = analyser.transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(failure));
-        assertThat(gse.getCause(), sameInstance(failure));
-        assertThat(gse.getReportableCauses(), isEmpty());
-    }
-
-    @Test
-    public void wrapsDeepestContextualExceptionWithLocationAwareException() {
-        Throwable cause = new ContextualException();
-        Throwable failure = new ContextualException(new RuntimeException(cause));
-
-        DefaultExceptionAnalyser analyser = analyser();
-
-        Throwable transformedFailure = analyser.transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
-        assertThat(gse.getCause(), sameInstance(cause));
-        assertThat(gse.getReportableCauses(), isEmpty());
-    }
-
-    @Test
-    public void addsLocationInfoFromDeepestStackFrame() {
-        Throwable failure = new ContextualException();
-        failure.setStackTrace(toArray(element, otherElement, callerElement));
-
-        DefaultExceptionAnalyser analyser = analyser();
-        notifyAnalyser(analyser, source);
-
-        Throwable transformedFailure = analyser.transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getScriptSource(), sameInstance(source));
-        assertThat(gse.getLineNumber(), equalTo(7));
-    }
-
-    @Test
-    public void addsLocationInfoFromDeepestCause() {
-        RuntimeException cause = new RuntimeException();
-        ContextualException failure = new ContextualException(new RuntimeException(cause));
-        failure.setStackTrace(toArray(otherElement, callerElement));
-        cause.setStackTrace(toArray(element, otherElement, callerElement));
-
-        DefaultExceptionAnalyser analyser = analyser();
-        notifyAnalyser(analyser, source);
-
-        Throwable transformedFailure = analyser.transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getScriptSource(), sameInstance(source));
-        assertThat(gse.getLineNumber(), equalTo(7));
-    }
-
-    @Test
-    public void doesNotAddLocationWhenLocationCannotBeDetermined() {
-        Throwable failure = new ContextualException();
-        Throwable transformedFailure = analyser().transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getScriptSource(), nullValue());
-        assertThat(gse.getLineNumber(), nullValue());
-    }
-
-    @Test
-    public void wrapsContextualMultiCauseExceptionWithLocationAwareException() {
-        Throwable cause1 = new ContextualException();
-        Throwable cause2 = new ContextualException();
-        Throwable failure = new ContextualMultiCauseException(cause1, cause2);
-
-        Throwable transformedFailure = analyser().transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(failure));
-        assertThat(gse.getCause(), sameInstance(failure));
-        assertThat(gse.getReportableCauses(), equalTo(toList(cause1, cause2)));
-    }
-
-    @Test
-    public void unpacksListenerNotificationException() {
-        Throwable cause = new RuntimeException();
-        Throwable failure = new ListenerNotificationException("broken", cause);
-
-        Throwable transformedFailure = analyser().transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
-        assertThat(gse.getCause(), sameInstance(failure));
-        assertThat(gse.getReportableCauses(), isEmpty());
-    }
-
-    @Test
-    public void usesOriginalExceptionWhenItIsAlreadyLocationAware() {
-        Throwable failure = locationAwareException(null);
-
-        DefaultExceptionAnalyser analyser = analyser();
-        notifyAnalyser(analyser, source);
-        
-        assertThat(analyser.transform(failure), sameInstance(failure));
-    }
-
-    @Test
-    public void usesDeepestScriptExceptionException() {
-        Throwable cause = new GradleScriptException("broken", new RuntimeException());
-        Throwable failure = new GradleScriptException("broken", new RuntimeException(cause));
-
-        Throwable transformedFailure = analyser().transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
-        assertThat(gse.getCause(), sameInstance(cause));
-    }
-
-    @Test
-    public void usesDeepestLocationAwareException() {
-        Throwable cause = locationAwareException(null);
-        Throwable failure = locationAwareException(new RuntimeException(cause));
-
-        DefaultExceptionAnalyser analyser = analyser();
-
-        assertThat(analyser.transform(failure), sameInstance(cause));
-    }
-
-    @Test
-    public void prefersScriptExceptionOverContextualException() {
-        Throwable cause = new GradleScriptException("broken", new ContextualException());
-        Throwable failure = new TaskExecutionException(null, cause);
-
-        Throwable transformedFailure = analyser().transform(failure);
-        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
-
-        LocationAwareException gse = (LocationAwareException) transformedFailure;
-        assertThat(gse.getTarget(), sameInstance(cause));
-        assertThat(gse.getCause(), sameInstance(cause));
-    }
-
-    @Test
-    public void prefersLocationAwareExceptionOverScriptException() {
-        Throwable cause = locationAwareException(new GradleScriptException("broken", new RuntimeException()));
-        Throwable failure = new TaskExecutionException(null, cause);
-
-        DefaultExceptionAnalyser analyser = analyser();
-
-        assertThat(analyser.transform(failure), sameInstance(cause));
-    }
-
-    private Throwable locationAwareException(final Throwable cause) {
-        final Throwable failure = context.mock(TestException.class);
-        context.checking(new Expectations() {{
-            allowing(failure).getCause();
-            will(returnValue(cause));
-            allowing(failure).getStackTrace();
-            will(returnValue(toArray(element)));
-        }});
-        return failure;
-    }
-
-    private void notifyAnalyser(DefaultExceptionAnalyser analyser, final ScriptSource source) {
-        final Script script = context.mock(Script.class);
-        context.checking(new Expectations() {{
-            allowing(script).getScriptSource();
-            will(returnValue(source));
-        }});
-        analyser.beforeScript(script);
-    }
-
-    private DefaultExceptionAnalyser analyser() {
-        context.checking(new Expectations() {{
-            one(listenerManager).addListener(with(notNullValue(DefaultExceptionAnalyser.class)));
-        }});
-        return new DefaultExceptionAnalyser(listenerManager);
-    }
-
-    @Contextual
-    public static class ContextualException extends RuntimeException {
-        public ContextualException() {
-            super("failed");
-        }
-
-        public ContextualException(Throwable throwable) {
-            super(throwable);
-        }
-    }
-
-    @Contextual
-    public static class ContextualMultiCauseException extends RuntimeException implements MultiCauseException {
-        private List<Throwable> causes;
-
-        public ContextualMultiCauseException(Throwable... throwables) {
-            this.causes = Arrays.asList(throwables);
-        }
-
-        public List<? extends Throwable> getCauses() {
-            return causes;
-        }
-    }
-
-    @Contextual
-    public abstract static class TestException extends LocationAwareException {
-        protected TestException(Throwable cause, ScriptSource source, Integer lineNumber) {
-            super(cause, cause, source, lineNumber);
-        }
-    }
-}
+/*
+ * 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.api.GradleScriptException;
+import org.gradle.api.internal.LocationAwareException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.api.internal.MultiCauseException;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.listener.ListenerManager;
+import org.gradle.listener.ListenerNotificationException;
+import org.gradle.util.JUnit4GroovyMockery;
+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.util.Arrays;
+import java.util.List;
+
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.WrapUtil.toArray;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class DefaultExceptionAnalyserTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+    private final StackTraceElement element = new StackTraceElement("class", "method", "filename", 7);
+    private final StackTraceElement callerElement = new StackTraceElement("class", "method", "filename", 11);
+    private final StackTraceElement otherElement = new StackTraceElement("class", "method", "otherfile", 11);
+    private final ScriptSource source = context.mock(ScriptSource.class);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(source).getFileName();
+            will(returnValue("filename"));
+        }});
+    }
+
+    @Test
+    public void usesOriginalExceptionWhenItIsNotAContextualException() {
+        Throwable failure = new RuntimeException();
+
+        DefaultExceptionAnalyser analyser = analyser();
+        assertThat(analyser.transform(failure), sameInstance(failure));
+    }
+
+    @Test
+    public void wrapsContextualExceptionWithLocationAwareException() {
+        Throwable failure = new ContextualException();
+
+        DefaultExceptionAnalyser analyser = analyser();
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getTarget(), sameInstance(failure));
+        assertThat(gse.getCause(), sameInstance(failure));
+        assertThat(gse.getReportableCauses(), isEmpty());
+    }
+
+    @Test
+    public void wrapsHighestContextualExceptionWithLocationAwareException() {
+        Throwable cause = new ContextualException();
+        Throwable failure = new ContextualException(cause);
+
+        DefaultExceptionAnalyser analyser = analyser();
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getTarget(), sameInstance(failure));
+        assertThat(gse.getCause(), sameInstance(failure));
+        assertThat(gse.getReportableCauses(), equalTo(toList(cause)));
+    }
+
+    @Test
+    public void addsLocationInfoFromDeepestStackFrame() {
+        Throwable failure = new ContextualException();
+        failure.setStackTrace(toArray(element, otherElement, callerElement));
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), sameInstance(source));
+        assertThat(gse.getLineNumber(), equalTo(7));
+    }
+
+    @Test
+    public void addsLocationInfoFromDeepestCause() {
+        RuntimeException cause = new RuntimeException();
+        ContextualException failure = new ContextualException(new RuntimeException(cause));
+        failure.setStackTrace(toArray(otherElement, callerElement));
+        cause.setStackTrace(toArray(element, otherElement, callerElement));
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), sameInstance(source));
+        assertThat(gse.getLineNumber(), equalTo(7));
+    }
+
+    @Test
+    public void doesNotAddLocationWhenLocationCannotBeDetermined() {
+        Throwable failure = new ContextualException();
+        Throwable transformedFailure = analyser().transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), nullValue());
+        assertThat(gse.getLineNumber(), nullValue());
+    }
+
+    @Test
+    public void wrapsContextualMultiCauseExceptionWithLocationAwareException() {
+        Throwable cause1 = new ContextualException();
+        Throwable cause2 = new ContextualException();
+        Throwable failure = new ContextualMultiCauseException(cause1, cause2);
+
+        Throwable transformedFailure = analyser().transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getTarget(), sameInstance(failure));
+        assertThat(gse.getCause(), sameInstance(failure));
+        assertThat(gse.getReportableCauses(), equalTo(toList(cause1, cause2)));
+    }
+
+    @Test
+    public void unpacksListenerNotificationException() {
+        Throwable cause = new RuntimeException();
+        Throwable failure = new ListenerNotificationException("broken", cause);
+
+        Throwable transformedFailure = analyser().transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getTarget(), sameInstance(cause));
+        assertThat(gse.getCause(), sameInstance(failure));
+        assertThat(gse.getReportableCauses(), isEmpty());
+    }
+
+    @Test
+    public void usesOriginalExceptionWhenItIsAlreadyLocationAware() {
+        Throwable failure = locationAwareException(null);
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+        
+        assertThat(analyser.transform(failure), sameInstance(failure));
+    }
+
+    @Test
+    public void usesDeepestScriptExceptionException() {
+        Throwable cause = new GradleScriptException("broken", new RuntimeException());
+        Throwable failure = new GradleScriptException("broken", new RuntimeException(cause));
+
+        Throwable transformedFailure = analyser().transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getTarget(), sameInstance(cause));
+        assertThat(gse.getCause(), sameInstance(cause));
+    }
+
+    @Test
+    public void usesDeepestLocationAwareException() {
+        Throwable cause = locationAwareException(null);
+        Throwable failure = locationAwareException(new RuntimeException(cause));
+
+        DefaultExceptionAnalyser analyser = analyser();
+
+        assertThat(analyser.transform(failure), sameInstance(cause));
+    }
+
+    @Test
+    public void prefersScriptExceptionOverContextualException() {
+        Throwable cause = new GradleScriptException("broken", new ContextualException());
+        Throwable failure = new TaskExecutionException(null, cause);
+
+        Throwable transformedFailure = analyser().transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getTarget(), sameInstance(cause));
+        assertThat(gse.getCause(), sameInstance(cause));
+    }
+
+    @Test
+    public void prefersLocationAwareExceptionOverScriptException() {
+        Throwable cause = locationAwareException(new GradleScriptException("broken", new RuntimeException()));
+        Throwable failure = new TaskExecutionException(null, cause);
+
+        DefaultExceptionAnalyser analyser = analyser();
+
+        assertThat(analyser.transform(failure), sameInstance(cause));
+    }
+
+    private Throwable locationAwareException(final Throwable cause) {
+        final Throwable failure = context.mock(TestException.class);
+        context.checking(new Expectations() {{
+            allowing(failure).getCause();
+            will(returnValue(cause));
+            allowing(failure).getStackTrace();
+            will(returnValue(toArray(element)));
+        }});
+        return failure;
+    }
+
+    private void notifyAnalyser(DefaultExceptionAnalyser analyser, final ScriptSource source) {
+        final Script script = context.mock(Script.class);
+        context.checking(new Expectations() {{
+            allowing(script).getScriptSource();
+            will(returnValue(source));
+        }});
+        analyser.beforeScript(script);
+    }
+
+    private DefaultExceptionAnalyser analyser() {
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(with(notNullValue(DefaultExceptionAnalyser.class)));
+        }});
+        return new DefaultExceptionAnalyser(listenerManager);
+    }
+
+    @Contextual
+    public static class ContextualException extends RuntimeException {
+        public ContextualException() {
+            super("failed");
+        }
+
+        public ContextualException(Throwable throwable) {
+            super(throwable);
+        }
+    }
+
+    @Contextual
+    public static class ContextualMultiCauseException extends RuntimeException implements MultiCauseException {
+        private List<Throwable> causes;
+
+        public ContextualMultiCauseException(Throwable... throwables) {
+            this.causes = Arrays.asList(throwables);
+        }
+
+        public List<? extends Throwable> getCauses() {
+            return causes;
+        }
+    }
+
+    @Contextual
+    public abstract static class TestException extends LocationAwareException {
+        protected TestException(Throwable cause, ScriptSource source, Integer lineNumber) {
+            super(cause, cause, source, lineNumber);
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
index 5c5ad96..0a4f694 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.initialization
 
 import org.gradle.GradleLauncher
 import org.gradle.StartParameter
+import org.gradle.cli.CommandLineConverter
 import spock.lang.Specification
 
 class DefaultGradleLauncherFactoryTest extends Specification {
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
index f4b70a0..3942673 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
@@ -20,35 +20,30 @@ import org.gradle.BuildListener;
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
-import org.gradle.api.Task;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.ExceptionAnalyser;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.DefaultProject;
 import org.gradle.configuration.BuildConfigurer;
+import org.gradle.execution.BuildExecuter;
 import org.gradle.execution.TaskGraphExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.TemporaryFolder;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
-import static org.gradle.util.WrapUtil.*;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
@@ -60,18 +55,14 @@ public class DefaultGradleLauncherTest {
     private BuildLoader buildLoaderMock;
     private InitScriptHandler initscriptHandlerMock;
     private SettingsHandler settingsHandlerMock;
-    private IGradlePropertiesLoader gradlePropertiesLoaderMock;
     private BuildConfigurer buildConfigurerMock;
     private DefaultProject expectedRootProject;
     private DefaultProject expectedCurrentProject;
     private SettingsInternal settingsMock;
-    private List<String> expectedTaskNames;
-    private List<Iterable<Task>> expectedTasks;
     private StartParameter expectedStartParams;
     private GradleInternal gradleMock;
     private BuildListener buildBroadcaster;
-
-    private Map testGradleProperties = new HashMap();
+    private BuildExecuter buildExecuter;
 
     private GradleLauncher gradleLauncher;
 
@@ -79,28 +70,26 @@ public class DefaultGradleLauncherTest {
 
     private ProjectDescriptor expectedRootProjectDescriptor;
 
-    private JUnit4Mockery context = new JUnit4Mockery();
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
 
     private ExceptionAnalyser exceptionAnalyserMock = context.mock(ExceptionAnalyser.class);
-
     private LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
+    private ModelConfigurationListener modelListenerMock = context.mock(ModelConfigurationListener.class);
 
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
 
     @Before
     public void setUp() {
-        context.setImposteriser(ClassImposteriser.INSTANCE);
         initscriptHandlerMock = context.mock(InitScriptHandler.class);
         settingsHandlerMock = context.mock(SettingsHandler.class);
-        gradlePropertiesLoaderMock = context.mock(IGradlePropertiesLoader.class);
         settingsMock = context.mock(SettingsInternal.class);
         taskExecuterMock = context.mock(TaskGraphExecuter.class);
         buildLoaderMock = context.mock(BuildLoader.class);
         buildConfigurerMock = context.mock(BuildConfigurer.class);
         gradleMock = context.mock(GradleInternal.class);
         buildBroadcaster = context.mock(BuildListener.class);
-        testGradleProperties = toMap("prop1", "value1");
+        buildExecuter = context.mock(BuildExecuter.class);
         boolean expectedSearchUpwards = false;
 
         File expectedRootDir = tmpDir.file("rootDir");
@@ -110,21 +99,17 @@ public class DefaultGradleLauncherTest {
         expectedRootProject = HelperUtil.createRootProject(expectedRootDir);
         expectedCurrentProject = HelperUtil.createRootProject(expectedCurrentDir);
 
-        expectTasks("a", "b");
-
         expectedStartParams = new StartParameter();
-        expectedStartParams.setTaskNames(expectedTaskNames);
         expectedStartParams.setCurrentDir(expectedCurrentDir);
         expectedStartParams.setSearchUpwards(expectedSearchUpwards);
         expectedStartParams.setGradleUserHomeDir(tmpDir.createDir("gradleUserHome"));
 
         gradleLauncher = new DefaultGradleLauncher(gradleMock, initscriptHandlerMock, settingsHandlerMock,
-                gradlePropertiesLoaderMock, buildLoaderMock, buildConfigurerMock, buildBroadcaster, exceptionAnalyserMock, loggingManagerMock);
+                buildLoaderMock, buildConfigurerMock, buildBroadcaster, exceptionAnalyserMock, loggingManagerMock,
+                modelListenerMock, buildExecuter);
 
         context.checking(new Expectations() {
             {
-                allowing(gradlePropertiesLoaderMock).getGradleProperties();
-                will(returnValue(testGradleProperties));
                 allowing(settingsMock).getRootProject();
                 will(returnValue(expectedRootProjectDescriptor));
                 allowing(gradleMock).getRootProject();
@@ -139,14 +124,6 @@ public class DefaultGradleLauncherTest {
         });
     }
 
-    private void expectTasks(String... tasks) {
-        expectedTaskNames = toList(tasks);
-        expectedTasks = new ArrayList<Iterable<Task>>();
-        for (String task : tasks) {
-            expectedTasks.add(toSortedSet(expectedCurrentProject.createTask(task)));
-        }
-    }
-
     @Test
     public void testRun() {
         expectLoggingStartedAndStoped();
@@ -161,24 +138,6 @@ public class DefaultGradleLauncherTest {
     }
 
     @Test
-    public void testDryRun() {
-        expectLoggingStartedAndStoped();
-        expectInitScriptsExecuted();
-        expectSettingsBuilt();
-        expectDagBuilt();
-        expectTasksRun();
-        expectBuildListenerCallbacks();
-        context.checking(new Expectations() {{
-            one(taskExecuterMock).getAllTasks();
-            will(returnValue(toList()));
-        }});
-        expectedStartParams.setDryRun(true);
-        BuildResult buildResult = gradleLauncher.run();
-        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
-        assertThat(buildResult.getFailure(), nullValue());
-    }
-
-    @Test
     public void testGetBuildAndRunAnalysis() {
         expectLoggingStartedAndStoped();
         expectInitScriptsExecuted();
@@ -197,7 +156,7 @@ public class DefaultGradleLauncherTest {
         expectSettingsBuilt();
         expectBuildListenerCallbacks();
         context.checking(new Expectations() {{
-            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
             one(buildConfigurerMock).configure(gradleMock);
         }});
         BuildResult buildResult = gradleLauncher.getBuildAnalysis();
@@ -214,7 +173,7 @@ public class DefaultGradleLauncherTest {
         expectSettingsBuilt();
         context.checking(new Expectations() {{
             one(buildBroadcaster).buildStarted(gradleMock);
-            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
             will(throwException(exception));
             one(exceptionAnalyserMock).transform(exception);
             will(returnValue(transformedException));
@@ -232,7 +191,7 @@ public class DefaultGradleLauncherTest {
         expectSettingsBuilt();
         expectBuildListenerCallbacks();
         context.checking(new Expectations() {{
-            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
             one(buildConfigurerMock).configure(gradleMock);
         }});
 
@@ -259,7 +218,7 @@ public class DefaultGradleLauncherTest {
         expectInitScriptsExecuted();
         context.checking(new Expectations() {{
             one(buildBroadcaster).buildStarted(gradleMock);
-            one(settingsHandlerMock).findAndLoadSettings(gradleMock, gradlePropertiesLoaderMock);
+            one(settingsHandlerMock).findAndLoadSettings(gradleMock);
             will(throwException(failure));
             one(exceptionAnalyserMock).transform(failure);
             will(returnValue(transformedException));
@@ -283,6 +242,7 @@ public class DefaultGradleLauncherTest {
             one(buildBroadcaster).buildStarted(gradleMock);
             one(buildBroadcaster).projectsLoaded(gradleMock);
             one(buildBroadcaster).projectsEvaluated(gradleMock);
+            one(modelListenerMock).onConfigure(gradleMock);
             one(exceptionAnalyserMock).transform(failure);
             will(returnValue(transformedException));
             one(buildBroadcaster).buildFinished(with(result(sameInstance(transformedException))));
@@ -308,7 +268,7 @@ public class DefaultGradleLauncherTest {
     private void expectSettingsBuilt() {
         context.checking(new Expectations() {
             {
-                one(settingsHandlerMock).findAndLoadSettings(gradleMock, gradlePropertiesLoaderMock);
+                one(settingsHandlerMock).findAndLoadSettings(gradleMock);
                 will(returnValue(settingsMock));
                 one(buildBroadcaster).settingsEvaluated(settingsMock);
             }
@@ -321,16 +281,16 @@ public class DefaultGradleLauncherTest {
             one(buildBroadcaster).projectsLoaded(gradleMock);
             one(buildBroadcaster).projectsEvaluated(gradleMock);
             one(buildBroadcaster).buildFinished(with(result(nullValue(Throwable.class))));
+            one(modelListenerMock).onConfigure(gradleMock);
         }});
     }
 
     private void expectDagBuilt() {
         context.checking(new Expectations() {
             {
-                one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+                one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock);
                 one(buildConfigurerMock).configure(gradleMock);
-                one(taskExecuterMock).addTasks(expectedTasks.get(0));
-                one(taskExecuterMock).addTasks(expectedTasks.get(1));
+                one(buildExecuter).select(gradleMock);
             }
         });
     }
@@ -338,7 +298,7 @@ public class DefaultGradleLauncherTest {
     private void expectTasksRun() {
         context.checking(new Expectations() {
             {
-                one(taskExecuterMock).execute();
+                one(buildExecuter).execute();
             }
         });
     }
@@ -346,7 +306,7 @@ public class DefaultGradleLauncherTest {
     private void expectTasksRunWithFailure(final Throwable failure) {
         context.checking(new Expectations() {
             {
-                one(taskExecuterMock).execute();
+                one(buildExecuter).execute();
                 will(throwException(failure));
             }
         });
@@ -372,4 +332,4 @@ public class DefaultGradleLauncherTest {
             }
         };
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
index a9eafc9..0d63bd8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
@@ -26,6 +26,8 @@ import org.junit.Rule;
 import org.junit.Test;
 
 import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
@@ -36,11 +38,9 @@ public class DefaultGradlePropertiesLoaderTest {
     private DefaultGradlePropertiesLoader gradlePropertiesLoader;
     private File gradleUserHomeDir;
     private File settingsDir;
-    private Map<String, String> systemProperties;
-    private Map<String, String> envProperties;
-    private Map<String, String> userHomeProperties;
-    private Map<String, String> settingsDirProperties;
-    private StartParameter startParameter;
+    private Map<String, String> systemProperties = new HashMap<String, String>();
+    private Map<String, String> envProperties = new HashMap<String, String>();
+    private StartParameter startParameter = new StartParameter();
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
 
@@ -48,24 +48,8 @@ public class DefaultGradlePropertiesLoaderTest {
     public void setUp() {
         gradleUserHomeDir = tmpDir.createDir("gradleUserHome");
         settingsDir = tmpDir.createDir("settingsDir");
-        gradlePropertiesLoader = new DefaultGradlePropertiesLoader();
-        startParameter = new StartParameter();
+        gradlePropertiesLoader = new DefaultGradlePropertiesLoader(startParameter);
         startParameter.setGradleUserHomeDir(gradleUserHomeDir);
-        startParameter.setSystemPropertiesArgs(WrapUtil.toMap("systemPropArgKey", "systemPropArgValue"));
-        systemProperties = GUtil.map(
-                IGradlePropertiesLoader.SYSTEM_PROJECT_PROPERTIES_PREFIX + "systemProp1", "systemValue1");
-        envProperties = GUtil.map(
-                IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "systemProp1", "envValue1",
-                IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "envProp2", "envValue2");
-        writePropertyFile(gradleUserHomeDir, userHomeProperties = GUtil.map(
-                "envProp2", "userValue1",
-                "userProp2", "userValue2",
-                Project.SYSTEM_PROP_PREFIX + ".userSystemProp", "userSystemValue"));
-        writePropertyFile(settingsDir, settingsDirProperties = GUtil.map(
-                "userProp2", "settingsValue1",
-                "settingsProp2", "settingsValue2",
-                Project.SYSTEM_PROP_PREFIX + ".userSystemProp", "settingsSystemValue",
-                Project.SYSTEM_PROP_PREFIX + ".settingsSystemProp2", "settingsSystemValue2"));
     }
 
     private void writePropertyFile(File location, Map<String, String> propertiesMap) {
@@ -75,12 +59,145 @@ public class DefaultGradlePropertiesLoaderTest {
     }
 
     @Test
-    public void loadProperties() {
+    public void mergeAddsPropertiesFromUserPropertiesFile() {
+        writePropertyFile(gradleUserHomeDir, GUtil.map("userProp", "user value"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+
+        assertEquals("user value", properties.get("userProp"));
+    }
+
+    @Test
+    public void mergeAddsPropertiesFromSettingsPropertiesFile() {
+        writePropertyFile(settingsDir, GUtil.map("settingsProp", "settings value"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+
+        assertEquals("settings value", properties.get("settingsProp"));
+    }
+
+    @Test
+    public void mergeAddsPropertiesFromEnvironmentVariablesWithPrefix() {
+        envProperties = GUtil.map(
+                IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "envProp", "env value",
+                "ignoreMe", "ignored");
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+
+        assertEquals("env value", properties.get("envProp"));
+    }
+
+    @Test
+    public void mergeAddsPropertiesFromSystemPropertiesWithPrefix() {
+        systemProperties = GUtil.map(
+                IGradlePropertiesLoader.SYSTEM_PROJECT_PROPERTIES_PREFIX + "systemProp", "system value",
+                "ignoreMe", "ignored");
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+
+        assertEquals("system value", properties.get("systemProp"));
+    }
+
+    @Test
+    public void mergeAddsPropertiesFromStartParameter() {
+        startParameter.setProjectProperties(GUtil.map("paramProp", "param value"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+
+        assertEquals("param value", properties.get("paramProp"));
+    }
+
+    @Test
+    public void projectPropertiesHavePrecedenceOverSettingsPropertiesFile() {
+        writePropertyFile(settingsDir, GUtil.map("prop", "settings value"));
+        Map<String, String> projectProperties = GUtil.map("prop", "project value");
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(projectProperties);
+
+        assertEquals("project value", properties.get("prop"));
+    }
+
+    @Test
+    public void userPropertiesFileHasPrecedenceOverSettingsPropertiesFile() {
+        writePropertyFile(gradleUserHomeDir, GUtil.map("prop", "user value"));
+        writePropertyFile(settingsDir, GUtil.map("prop", "settings value"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+
+        assertEquals("user value", properties.get("prop"));
+    }
+
+    @Test
+    public void userPropertiesFileHasPrecedenceOverProjectProperties() {
+        writePropertyFile(gradleUserHomeDir, GUtil.map("prop", "user value"));
+        writePropertyFile(settingsDir, GUtil.map("prop", "settings value"));
+        Map<String, String> projectProperties = GUtil.map("prop", "project value");
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(projectProperties);
+
+        assertEquals("user value", properties.get("prop"));
+    }
+
+    @Test
+    public void environmentVariablesHavePrecedenceOverProjectProperties() {
+        writePropertyFile(gradleUserHomeDir, GUtil.map("prop", "user value"));
+        writePropertyFile(settingsDir, GUtil.map("prop", "settings value"));
+        Map<String, String> projectProperties = GUtil.map("prop", "project value");
+        envProperties = GUtil.map(IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "prop", "env value");
+
         gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
-        assertEquals("systemValue1", gradlePropertiesLoader.getGradleProperties().get("systemProp1"));
-        assertEquals("envValue2", gradlePropertiesLoader.getGradleProperties().get("envProp2"));
-        assertEquals("userValue2", gradlePropertiesLoader.getGradleProperties().get("userProp2"));
-        assertEquals("settingsValue2", gradlePropertiesLoader.getGradleProperties().get("settingsProp2"));
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(projectProperties);
+
+        assertEquals("env value", properties.get("prop"));
+    }
+
+    @Test
+    public void systemPropertiesHavePrecedenceOverEnvironmentVariables() {
+        writePropertyFile(gradleUserHomeDir, GUtil.map("prop", "user value"));
+        writePropertyFile(settingsDir, GUtil.map("prop", "settings value"));
+        Map<String, String> projectProperties = GUtil.map("prop", "project value");
+        envProperties = GUtil.map(IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "prop", "env value");
+        systemProperties = GUtil.map(IGradlePropertiesLoader.SYSTEM_PROJECT_PROPERTIES_PREFIX + "prop", "system value");
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(projectProperties);
+
+        assertEquals("system value", properties.get("prop"));
+    }
+
+    @Test
+    public void startParameterPropertiesHavePrecedenceOverSystemProperties() {
+        writePropertyFile(gradleUserHomeDir, GUtil.map("prop", "user value"));
+        writePropertyFile(settingsDir, GUtil.map("prop", "settings value"));
+        Map<String, String> projectProperties = GUtil.map("prop", "project value");
+        envProperties = GUtil.map(IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "prop", "env value");
+        systemProperties = GUtil.map(IGradlePropertiesLoader.SYSTEM_PROJECT_PROPERTIES_PREFIX + "prop", "system value");
+        startParameter.setProjectProperties(GUtil.map("prop", "param value"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(projectProperties);
+
+        assertEquals("param value", properties.get("prop"));
+    }
+
+    @Test
+    public void loadSetsSystemProperties() {
+        startParameter.setSystemPropertiesArgs(WrapUtil.toMap("systemPropArgKey", "systemPropArgValue"));
+        writePropertyFile(gradleUserHomeDir, GUtil.map(Project.SYSTEM_PROP_PREFIX + ".userSystemProp", "userSystemValue"));
+        writePropertyFile(settingsDir, GUtil.map(
+                Project.SYSTEM_PROP_PREFIX + ".userSystemProp", "settingsSystemValue",
+                Project.SYSTEM_PROP_PREFIX + ".settingsSystemProp2", "settingsSystemValue2"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+
         assertEquals("userSystemValue", System.getProperty("userSystemProp"));
         assertEquals("settingsSystemValue2", System.getProperty("settingsSystemProp2"));
         assertEquals("systemPropArgValue", System.getProperty("systemPropArgKey"));
@@ -100,12 +217,14 @@ public class DefaultGradlePropertiesLoaderTest {
         writePropertyFile(otherSettingsDir, GUtil.map("prop1", "otherValue"));
 
         gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
-        assertEquals("value", gradlePropertiesLoader.getGradleProperties().get("prop1"));
-        assertEquals("value", gradlePropertiesLoader.getGradleProperties().get("prop2"));
+        Map<String, String> properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+        assertEquals("value", properties.get("prop1"));
+        assertEquals("value", properties.get("prop2"));
 
         gradlePropertiesLoader.loadProperties(otherSettingsDir, startParameter, systemProperties, envProperties);
-        assertEquals("otherValue", gradlePropertiesLoader.getGradleProperties().get("prop1"));
-        assertNull(gradlePropertiesLoader.getGradleProperties().get("prop2"));
+        properties = gradlePropertiesLoader.mergeProperties(Collections.<String, String>emptyMap());
+        assertEquals("otherValue", properties.get("prop1"));
+        assertNull(properties.get("prop2"));
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultInitScriptFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultInitScriptFinderTest.java
deleted file mode 100644
index af4f5c7..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultInitScriptFinderTest.java
+++ /dev/null
@@ -1,67 +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.initialization;
-
-import org.gradle.util.GFileUtils;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.Expectations;
-import org.gradle.StartParameter;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
-
-import java.util.List;
-import java.util.ArrayList;
-import java.io.File;
-
-public class DefaultInitScriptFinderTest {
-    @Test
-    public void testFindScripts() {
-        JUnit4Mockery context = new JUnit4Mockery();
-
-        final GradleInternal gradleMock = context.mock(GradleInternal.class);
-        final StartParameter testStartParameter = new StartParameter();
-        testStartParameter.addInitScript(new File("some init script"));
-        testStartParameter.addInitScript(new File("/path/to/another init script"));
-
-        context.checking(new Expectations() {{
-            allowing(gradleMock).getStartParameter();
-            will(returnValue(testStartParameter));
-        }});
-
-        List<ScriptSource> sourceList = new DefaultInitScriptFinder().findScripts(gradleMock);
-        assertThat(getSourceFiles(sourceList), equalTo(canonicalise(testStartParameter.getInitScripts())));
-    }
-
-    private List<File> canonicalise(List<File> files) {
-        List<File> results = new ArrayList<File>();
-        for (File file : files) {
-            results.add(GFileUtils.canonicalise(file));
-        }
-        return results;
-    }
-
-    private List<File> getSourceFiles(List<ScriptSource> sources) {
-        List<File> results = new ArrayList<File>(sources.size());
-        for (ScriptSource source : sources) {
-            results.add(source.getResource().getFile());
-        }
-        return results;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsFinderTest.java
deleted file mode 100644
index dcb3acb..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsFinderTest.java
+++ /dev/null
@@ -1,90 +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.initialization;
-
-import org.gradle.StartParameter;
-import org.gradle.groovy.scripts.StringScriptSource;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class DefaultSettingsFinderTest {
-    private static final StartParameter TEST_START_PARAMETER = new StartParameter();
-    private static final File TEST_SETTINGSFILE = new File("parent", "testFile1");
-    private DefaultSettingsFinder defaultSettingsFinder;
-    private ISettingsFileSearchStrategy searchStrategyMock1;
-    private ISettingsFileSearchStrategy searchStrategyMock2;
-
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    @Before
-    public void setUp() {
-        searchStrategyMock1 = context.mock(ISettingsFileSearchStrategy.class, "strategy1");
-        searchStrategyMock2 = context.mock(ISettingsFileSearchStrategy.class, "strategy2");
-        defaultSettingsFinder = new DefaultSettingsFinder(WrapUtil.toList(searchStrategyMock1, searchStrategyMock2));
-    }
-
-    @Test
-    public void testFindWithStrategy1() {
-        context.checking(new Expectations() {{
-            allowing(searchStrategyMock1).find(TEST_START_PARAMETER);
-            will(returnValue(TEST_SETTINGSFILE));
-        }});
-        SettingsLocation settingsLocation = defaultSettingsFinder.find(TEST_START_PARAMETER);
-        assertEquals(TEST_SETTINGSFILE.getParentFile(), settingsLocation.getSettingsDir());
-        assertEquals(GFileUtils.canonicalise(TEST_SETTINGSFILE), settingsLocation.getSettingsScriptSource().getResource().getFile());
-    }
-
-    @Test
-    public void testFindWithStrategy2() {
-        context.checking(new Expectations() {{
-            allowing(searchStrategyMock1).find(TEST_START_PARAMETER);
-            will(returnValue(null));
-            allowing(searchStrategyMock2).find(TEST_START_PARAMETER);
-            will(returnValue(TEST_SETTINGSFILE));
-        }});
-        SettingsLocation settingsLocation = defaultSettingsFinder.find(TEST_START_PARAMETER);
-        assertEquals(TEST_SETTINGSFILE.getParentFile(), settingsLocation.getSettingsDir());
-        assertEquals(GFileUtils.canonicalise(TEST_SETTINGSFILE), settingsLocation.getSettingsScriptSource().getResource().getFile());
-    }
-
-    @Test
-    public void testNotFound() {
-        context.checking(new Expectations() {{
-            allowing(searchStrategyMock1).find(TEST_START_PARAMETER);
-            will(returnValue(null));
-            allowing(searchStrategyMock2).find(TEST_START_PARAMETER);
-            will(returnValue(null));
-        }});
-        SettingsLocation settingsLocation = defaultSettingsFinder.find(TEST_START_PARAMETER);
-        assertEquals(TEST_START_PARAMETER.getCurrentDir(), settingsLocation.getSettingsDir());
-        assertThat(settingsLocation.getSettingsScriptSource(), instanceOf(StringScriptSource.class));
-        assertThat(settingsLocation.getSettingsScriptSource().getResource().getText(), equalTo(""));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
new file mode 100644
index 0000000..28cec22
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.api.artifacts.ResolvableDependencies
+import org.gradle.logging.ProgressLoggerFactory
+import spock.lang.Specification
+import org.gradle.logging.ProgressLogger
+
+class DependencyResolutionLoggerTest extends Specification {
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final ResolvableDependencies dependencies = Mock()
+    final ProgressLogger progressLogger = Mock()
+    final DependencyResolutionLogger logger = new DependencyResolutionLogger(progressLoggerFactory)
+
+    def "generates progress logging events as dependency sets are resolved"() {
+        when:
+        logger.beforeResolve(dependencies)
+
+        then:
+        1 * progressLoggerFactory.newOperation(DependencyResolutionLogger) >> progressLogger
+        1 * progressLogger.setDescription("Resolve ${dependencies}")
+        1 * progressLogger.setShortDescription("Resolving ${dependencies}")
+        1 * progressLogger.started()
+        0 * progressLogger._
+
+        when:
+        logger.afterResolve(dependencies)
+
+        then:
+        1 * progressLogger.completed()
+        0 * progressLogger._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy
new file mode 100644
index 0000000..4c34165
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DistributionInitScriptFinderTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.api.internal.GradleDistributionLocator
+import org.gradle.api.internal.GradleInternal
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.groovy.scripts.UriScriptSource
+
+class DistributionInitScriptFinderTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final def distDir = tmpDir.createDir("gradle-home")
+    final GradleDistributionLocator locator = Mock()
+    final GradleInternal gradle = Mock()
+    final DistributionInitScriptFinder finder = new DistributionInitScriptFinder(locator)
+
+    def setup() {
+    }
+
+    def "does nothing when init.d directory does not exist there is no distribution"() {
+        def scripts = []
+
+        given:
+        _ * locator.gradleHome >> null
+
+        when:
+        finder.findScripts(gradle, scripts)
+
+        then:
+        scripts.empty
+    }
+
+    def "does nothing when init.d directory does not exist in distribution"() {
+        def scripts = []
+
+        given:
+        _ * locator.gradleHome >> distDir
+
+        when:
+        finder.findScripts(gradle, scripts)
+
+        then:
+        scripts.empty
+    }
+
+    def "locates each script from init.d directory"() {
+        def scripts = []
+
+        given:
+        def script1 = distDir.createFile("init.d/script1.gradle")
+        def script2 = distDir.createFile("init.d/script2.gradle")
+        distDir.createFile("init.d/readme.txt")
+        distDir.createFile("init.d/lib/test.jar")
+
+        and:
+        _ * locator.gradleHome >> distDir
+
+        when:
+        finder.findScripts(gradle, scripts)
+
+        then:
+        scripts.size() == 2
+        scripts.find {
+            it instanceof UriScriptSource && it.resource.sourceFile == script1
+        }
+        scripts.find {
+            it instanceof UriScriptSource && it.resource.sourceFile == script2
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinderTest.java
deleted file mode 100644
index 7f34671..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinderTest.java
+++ /dev/null
@@ -1,76 +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.StartParameter;
-import org.gradle.groovy.scripts.ScriptSource;
-import static org.hamcrest.Matchers.*;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.Expectations;
-import static org.junit.Assert.*;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class EmbeddedScriptSettingsFinderTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ISettingsFinder delegate = context.mock(ISettingsFinder.class);
-    private final ScriptSource settingsScriptSource = context.mock(ScriptSource.class);
-    private final EmbeddedScriptSettingsFinder settingsFinder = new EmbeddedScriptSettingsFinder(delegate);
-
-    @Test
-    public void usesProvidedScriptAsSettingsFileWhenSettingsFileSpecifiedInStartParam() {
-        StartParameter parameter = new StartParameter();
-        parameter.setSettingsScriptSource(settingsScriptSource);
-
-        SettingsLocation settingsLocation = settingsFinder.find(parameter);
-
-        assertThat(settingsLocation.getSettingsScriptSource(), sameInstance(settingsScriptSource));
-    }
-
-    @Test
-    public void usesCurrentDirAsSettingsDirWhenSettingsFileSpecifiedInStartParam() throws IOException {
-        StartParameter parameter = new StartParameter();
-        File currentDir = new File("current dir");
-
-        parameter.setSettingsScriptSource(settingsScriptSource);
-        parameter.setCurrentDir(currentDir);
-
-        SettingsLocation settingsLocation = settingsFinder.find(parameter);
-
-        assertThat(settingsLocation.getSettingsDir(), equalTo(currentDir.getCanonicalFile()));
-    }
-
-    @Test
-    public void delegatesWhenSettingsFileNotSpecifiedInStartParam() {
-        final StartParameter parameter = new StartParameter();
-        final File settingsDir = new File("settings dir");
-
-        context.checking(new Expectations() {{
-            one(delegate).find(parameter);
-            will(returnValue(new SettingsLocation(settingsDir, settingsScriptSource)));
-        }});
-
-        SettingsLocation settingsLocation = settingsFinder.find(parameter);
-
-        assertThat(settingsLocation.getSettingsDir(), sameInstance(settingsDir));
-        assertThat(settingsLocation.getSettingsScriptSource(), sameInstance(settingsScriptSource));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy
new file mode 100644
index 0000000..a90067d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.configuration.InitScriptProcessor
+import org.gradle.api.internal.GradleInternal
+import org.gradle.groovy.scripts.ScriptSource
+
+class InitScriptHandlerTest extends Specification {
+    final InitScriptFinder finder = Mock()
+    final InitScriptProcessor processor = Mock()
+    final GradleInternal gradle = Mock()
+    final InitScriptHandler handler = new InitScriptHandler(finder, processor)
+    
+    def "finds and processes init scripts"() {
+        ScriptSource script1 = Mock()
+        ScriptSource script2 = Mock()
+
+        when:
+        handler.executeScripts(gradle)
+        
+        then:
+        1 * finder.findScripts(gradle, !null) >> {gradle, scripts -> 
+            scripts << script1
+            scripts << script2
+        }
+        1 * processor.process(script1, gradle)
+        1 * processor.process(script2, gradle)
+        0 * _._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.java
deleted file mode 100644
index 4cddead..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.java
+++ /dev/null
@@ -1,53 +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.junit.Test;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.Expectations;
-import org.gradle.configuration.InitScriptProcessor;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.api.internal.GradleInternal;
-
-import java.util.List;
-import java.util.ArrayList;
-
-public class InitScriptHandlerTest {
-
-    @Test
-    public void testExecuteScripts() {
-        JUnit4Mockery context = new JUnit4Mockery();
-
-        final InitScriptFinder finderMock = context.mock(InitScriptFinder.class);
-        final InitScriptProcessor processorMock = context.mock(InitScriptProcessor.class);
-        final GradleInternal gradleMock = context.mock(GradleInternal.class);
-        final ScriptSource source1Mock = context.mock(ScriptSource.class, "source 1");
-        final ScriptSource source2Mock = context.mock(ScriptSource.class, "source 2");
-        final List<ScriptSource> testSources = new ArrayList<ScriptSource>();
-        testSources.add(source1Mock);
-        testSources.add(source2Mock);
-
-        context.checking(new Expectations() {{
-            one(finderMock).findScripts(gradleMock);
-            will(returnValue(testSources));
-            one(processorMock).process(source1Mock, gradleMock);
-            one(processorMock).process(source2Mock, gradleMock);
-        }});
-
-        InitScriptHandler handler = new InitScriptHandler(finderMock, processorMock);
-        handler.executeScripts(gradleMock);
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
new file mode 100644
index 0000000..63f5322
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/InstantiatingBuildLoaderTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.StartParameter
+import org.gradle.api.initialization.ProjectDescriptor
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.IProjectFactory
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.util.HelperUtil
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+class InstantiatingBuildLoaderTest {
+
+    InstantiatingBuildLoader buildLoader
+    IProjectFactory projectFactory
+    File testDir
+    File rootProjectDir
+    File childProjectDir
+    IProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
+    StartParameter startParameter = new StartParameter()
+    ProjectDescriptor rootDescriptor
+    ProjectInternal rootProject
+    ProjectDescriptor childDescriptor
+    ProjectInternal childProject
+    GradleInternal build
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before public void setUp()  {
+        projectFactory = context.mock(IProjectFactory)
+        buildLoader = new InstantiatingBuildLoader(projectFactory)
+        testDir = tmpDir.dir
+        (rootProjectDir = new File(testDir, 'root')).mkdirs()
+        (childProjectDir = new File(rootProjectDir, 'child')).mkdirs()
+        startParameter.currentDir = rootProjectDir
+        rootDescriptor = descriptor('root', null, rootProjectDir)
+        rootProject = project(rootDescriptor, null)
+        childDescriptor = descriptor('child', rootDescriptor, childProjectDir)
+        childProject = project(childDescriptor, rootProject)
+        build = context.mock(GradleInternal)
+        context.checking {
+            allowing(build).getStartParameter()
+            will(returnValue(startParameter))
+        }
+    }
+
+    @Test public void createsBuildWithRootProject() {
+        ProjectDescriptor rootDescriptor = descriptor('root', null, rootProjectDir)
+        ProjectInternal rootProject = project(rootDescriptor, null)
+
+        context.checking {
+            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
+                    withParam(nullValue()),
+                    withParam(notNullValue()))
+            will(returnValue(rootProject))
+            one(build).setRootProject(rootProject)
+            allowing(build).getRootProject()
+            will(returnValue(rootProject))
+            one(build).setDefaultProject(rootProject)
+        }
+
+        buildLoader.load(rootDescriptor, build)
+    }
+
+    @Test public void createsBuildWithMultipleProjects() {
+        expectProjectsCreated()
+
+        buildLoader.load(rootDescriptor, build)
+
+        assertThat(rootProject.childProjects['child'], sameInstance(childProject))
+    }
+
+    private def expectProjectsCreatedNoDefaultProject() {
+        context.checking {
+            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
+                    withParam(nullValue()),
+                    withParam(notNullValue()))
+            will(returnValue(rootProject))
+
+            one(projectFactory).createProject(withParam(equalTo(childDescriptor)),
+                    withParam(equalTo(rootProject)),
+                    withParam(notNullValue()))
+            will(returnValue(childProject))
+
+            one(build).setRootProject(rootProject)
+            allowing(build).getRootProject()
+            will(returnValue(rootProject))
+        }
+    }
+
+    private def expectProjectsCreated() {
+        expectProjectsCreatedNoDefaultProject()
+
+        context.checking {
+            one(build).setDefaultProject(rootProject)
+        }
+    }
+
+    private ProjectDescriptor descriptor(String name, ProjectDescriptor parent, File projectDir) {
+        new DefaultProjectDescriptor(parent, name, projectDir, projectDescriptorRegistry)
+    }
+
+    private ProjectInternal project(ProjectDescriptor descriptor, ProjectInternal parent) {
+        DefaultProject project
+        if (parent) {
+            project = HelperUtil.createChildProject(parent, descriptor.name, descriptor.projectDir)
+        } else {
+            project = HelperUtil.createRootProject(descriptor.projectDir)
+        }
+        project
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategyTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategyTest.java
deleted file mode 100644
index 83a2452..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategyTest.java
+++ /dev/null
@@ -1,55 +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.initialization;
-
-import static org.junit.Assert.assertEquals;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class MasterDirSettingsFinderStrategyTest extends AbstractSettingsFinderStrategyTest {
-
-    private MasterDirSettingsFinderStrategy masterDirSettingsFinderStrategy;
-    private File masterDir;
-
-    protected ISettingsFileSearchStrategy getStrategy() {
-        return masterDirSettingsFinderStrategy;
-    }
-
-    @Before
-    public void setUp() {
-        masterDirSettingsFinderStrategy = new MasterDirSettingsFinderStrategy();
-        masterDir = new File(testDir, MasterDirSettingsFinderStrategy.MASTER_DIR_NAME);
-        masterDir.mkdirs();
-        currentDir = new File(testDir, "current");
-        currentDir.mkdir();
-    }
-
-    @Test
-    public void findExistingSettingsInMasterDirWithSearchUpwardsFalse() {
-        createSettingsFile(masterDir);
-        assertEquals(null, masterDirSettingsFinderStrategy.find(createStartParams(false)));
-    }
-
-    @Test
-    public void findExistingSettingsInMasterDirWithSearchUpwardsTrue() {
-        assertEquals(createSettingsFile(masterDir), masterDirSettingsFinderStrategy.find(createStartParams(true)));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategyTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategyTest.java
deleted file mode 100644
index 5a3fb47..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategyTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.initialization;
-
-import static org.junit.Assert.assertEquals;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class ParentDirSettingsFinderStrategyTest extends AbstractSettingsFinderStrategyTest {
-    private ParentDirSettingsFinderStrategy parentDirSettingsFinderStrategy;
-    private File rootDir;
-
-    protected ISettingsFileSearchStrategy getStrategy() {
-        return parentDirSettingsFinderStrategy;
-    }
-
-    @Before
-    public void setUp() {
-        parentDirSettingsFinderStrategy = new ParentDirSettingsFinderStrategy();
-        rootDir = new File(testDir, "root");
-        rootDir.mkdirs();
-        currentDir = new File(rootDir, "current");
-        currentDir.mkdir();
-    }
-
-    @Test
-    public void findExistingSettingsInMasterDirWithSearchUpwardsFalse() {
-        createSettingsFile(rootDir);
-        assertEquals(null, parentDirSettingsFinderStrategy.find(createStartParams(false)));
-    }
-
-    @Test
-    public void findExistingSettingsInMasterDirWithSearchUpwardsTrue() {
-        assertEquals(createSettingsFile(rootDir), parentDirSettingsFinderStrategy.find(createStartParams(true)));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
new file mode 100644
index 0000000..45cc9c7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProjectPropertySettingBuildLoaderTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2011 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.api.Project
+import org.gradle.api.initialization.ProjectDescriptor
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.ExtensionContainer
+import org.gradle.api.plugins.ExtraPropertiesExtension
+import org.gradle.util.GUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectPropertySettingBuildLoaderTest extends Specification {
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    final BuildLoader target = Mock()
+    final ProjectDescriptor projectDescriptor = Mock()
+    final GradleInternal gradle = Mock()
+    final ProjectInternal rootProject = Mock()
+    final ProjectInternal childProject = Mock()
+    final IGradlePropertiesLoader propertiesLoader = Mock()
+    final File rootProjectDir = tmpDir.createDir('root')
+    final File childProjectDir = tmpDir.createDir('child')
+    final ProjectPropertySettingBuildLoader loader = new ProjectPropertySettingBuildLoader(propertiesLoader, target)
+    final ExtensionContainer rootExtension = Mock()
+    final ExtraPropertiesExtension rootProperties = Mock()
+    final ExtensionContainer childExtension = Mock()
+    final ExtraPropertiesExtension childProperties = Mock()
+
+    def setup() {
+        _ * gradle.rootProject >> rootProject
+        _ * rootProject.childProjects >> [child: childProject]
+        _ * childProject.childProjects >> [:]
+        _ * rootProject.projectDir >> rootProjectDir
+        _ * childProject.projectDir >> childProjectDir
+        _ * rootProject.extensions >> rootExtension
+        _ * childProject.extensions >> childExtension
+        _ * rootExtension.extraProperties >> rootProperties
+        _ * childExtension.extraProperties >> childProperties
+    }
+
+    def "delegates to build loader"() {
+        given:
+        _ * propertiesLoader.mergeProperties(!null) >> [:]
+        
+        when:
+        loader.load(projectDescriptor, gradle)
+
+        then:
+        1 * target.load(projectDescriptor, gradle)
+        0 * target._
+    }
+
+    def "sets project properties on each project in hierarchy"() {
+        given:
+        2 * propertiesLoader.mergeProperties([:]) >> [prop: 'value']
+
+        when:
+        loader.load(projectDescriptor, gradle)
+
+        then:
+        1 * rootProperties.set('prop', 'value')
+        1 * childProperties.set('prop', 'value')
+    }
+
+    def "loads project properties from gradle.properties file in project dir"() {
+        given:
+        GUtil.saveProperties(new Properties([prop: 'rootValue']), new File(rootProjectDir, Project.GRADLE_PROPERTIES))
+        GUtil.saveProperties(new Properties([prop: 'childValue']), new File(childProjectDir, Project.GRADLE_PROPERTIES))
+
+        when:
+        loader.load(projectDescriptor, gradle)
+
+        then:
+        1 * propertiesLoader.mergeProperties([prop: 'rootValue']) >> [prop: 'rootValue']
+        1 * propertiesLoader.mergeProperties([prop: 'childValue']) >> [prop: 'childValue']
+        1 * rootProperties.set('prop', 'rootValue')
+        1 * childProperties.set('prop', 'childValue')
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
index b564131..8b6440b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
@@ -15,21 +15,21 @@
  */
 package org.gradle.initialization;
 
+import org.gradle.StartParameter;
 import org.gradle.api.internal.GradleInternal;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.integration.junit4.JMock;
+import org.gradle.api.internal.SettingsInternal;
 import org.jmock.Expectations;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.gradle.StartParameter;
-import org.gradle.groovy.scripts.ScriptSource;
-import org.gradle.api.internal.SettingsInternal;
 
 import java.io.File;
-import java.net.URLClassLoader;
 import java.net.URL;
+import java.net.URLClassLoader;
+
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
 
 @RunWith(JMock.class)
 public class PropertiesLoadingSettingsProcessorTest {
@@ -43,18 +43,17 @@ public class PropertiesLoadingSettingsProcessorTest {
         final StartParameter startParameter = new StartParameter();
         final SettingsInternal settings = context.mock(SettingsInternal.class);
         final File settingsDir = new File("root");
-        final ScriptSource settingsScriptSource = context.mock(ScriptSource.class);
         final GradleInternal gradle = context.mock(GradleInternal.class);
-        final SettingsLocation settingsLocation = new SettingsLocation(settingsDir, settingsScriptSource);
+        final SettingsLocation settingsLocation = new SettingsLocation(settingsDir, new File("foo"));
 
-        PropertiesLoadingSettingsProcessor processor = new PropertiesLoadingSettingsProcessor(delegate);
+        PropertiesLoadingSettingsProcessor processor = new PropertiesLoadingSettingsProcessor(delegate, propertiesLoader);
 
         context.checking(new Expectations() {{
-            one(propertiesLoader).loadProperties(settingsDir, startParameter);
-            one(delegate).process(gradle, settingsLocation, urlClassLoader, startParameter, propertiesLoader);
+            one(propertiesLoader).loadProperties(settingsDir);
+            one(delegate).process(gradle, settingsLocation, urlClassLoader, startParameter);
             will(returnValue(settings));
         }});
 
-        assertThat(processor.process(gradle, settingsLocation, urlClassLoader, startParameter, propertiesLoader), sameInstance(settings));
+        assertThat(processor.process(gradle, settingsLocation, urlClassLoader, startParameter), sameInstance(settings));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ProvidedInitScriptFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/ProvidedInitScriptFinderTest.java
new file mode 100644
index 0000000..7ae8f40
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ProvidedInitScriptFinderTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.GFileUtils;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class ProvidedInitScriptFinderTest {
+    final JUnit4Mockery context = new JUnit4Mockery();
+    final ProvidedInitScriptFinder finder = new ProvidedInitScriptFinder();
+
+    @Test
+    public void testFindScripts() {
+
+        final GradleInternal gradleMock = context.mock(GradleInternal.class);
+        final StartParameter testStartParameter = new StartParameter();
+        testStartParameter.addInitScript(new File("some init script"));
+        testStartParameter.addInitScript(new File("/path/to/another init script"));
+
+        context.checking(new Expectations() {{
+            allowing(gradleMock).getStartParameter();
+            will(returnValue(testStartParameter));
+        }});
+
+        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
+        finder.findScripts(gradleMock, sourceList);
+        assertThat(getSourceFiles(sourceList), equalTo(canonicalise(testStartParameter.getInitScripts())));
+    }
+
+    private List<File> canonicalise(List<File> files) {
+        List<File> results = new ArrayList<File>();
+        for (File file : files) {
+            results.add(GFileUtils.canonicalise(file));
+        }
+        return results;
+    }
+
+    private List<File> getSourceFiles(List<ScriptSource> sources) {
+        List<File> results = new ArrayList<File>(sources.size());
+        for (ScriptSource source : sources) {
+            results.add(source.getResource().getFile());
+        }
+        return results;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategyTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategyTest.java
deleted file mode 100644
index a9eda9d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategyTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.initialization;
-
-import static org.junit.Assert.assertEquals;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class SameLevelDirSettingsFinderStrategyTest extends AbstractSettingsFinderStrategyTest {
-    private SameLevelDirSettingsFinderStrategy sameLevelDirSettingsFinderStrategy;
-    private File rootDir;
-
-    protected ISettingsFileSearchStrategy getStrategy() {
-        return sameLevelDirSettingsFinderStrategy;
-    }
-
-    @Before
-    public void setUp() {
-        sameLevelDirSettingsFinderStrategy = new SameLevelDirSettingsFinderStrategy();
-        rootDir = new File(testDir, "root");
-        rootDir.mkdirs();
-        currentDir = new File(testDir, "current");
-        currentDir.mkdir();
-    }
-
-    @Test
-    public void findExistingSettingsInMasterDirWithSearchUpwardsFalse() {
-        createSettingsFile(rootDir);
-        assertEquals(null, sameLevelDirSettingsFinderStrategy.find(createStartParams(false)));
-    }
-
-    @Test
-    public void findExistingSettingsInMasterDirWithSearchUpwardsTrue() {
-        assertEquals(createSettingsFile(rootDir), sameLevelDirSettingsFinderStrategy.find(createStartParams(true)));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
index 00102d8..41c5c60 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
@@ -18,16 +18,14 @@ package org.gradle.initialization
 
 import groovy.mock.interceptor.MockFor
 import org.gradle.StartParameter
+import org.gradle.api.internal.GradleInternal
+import org.gradle.configuration.ScriptPlugin
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.lib.legacy.ClassImposteriser
 import org.junit.Before
 import org.junit.Test
-import org.gradle.groovy.scripts.*
-
-import static org.junit.Assert.*
-import org.gradle.configuration.ScriptPluginFactory
-import org.gradle.configuration.ScriptPlugin
-import org.gradle.api.internal.GradleInternal
+import static org.junit.Assert.assertSame
 
 /**
  * @author Hans Dockter
@@ -47,20 +45,21 @@ class ScriptEvaluatingSettingsProcessorTest {
     Map expectedGradleProperties
     URLClassLoader urlClassLoader
     GradleInternal gradleMock
+    SettingsLocation settingsLocation
 
     JUnit4GroovyMockery context = new JUnit4GroovyMockery()
 
     @Before public void setUp() {
-        context.setImposteriser(ClassImposteriser.INSTANCE)
+        settingsLocation = context.mock(SettingsLocation)
         configurerFactoryMock = context.mock(ScriptPluginFactory)
         settingsFactory = context.mock(SettingsFactory)
-        settingsProcessor = new ScriptEvaluatingSettingsProcessor(configurerFactoryMock, settingsFactory)
+        propertiesLoaderMock = context.mock(IGradlePropertiesLoader)
+        settingsProcessor = new ScriptEvaluatingSettingsProcessor(configurerFactoryMock, settingsFactory, propertiesLoaderMock)
         expectedSettingsFinder = new DefaultSettingsFinder()
         scriptSourceMock = context.mock(ScriptSource)
         gradleMock = context.mock(GradleInternal)
         expectedStartParameter = new StartParameter()
         expectedGradleProperties = [a: 'b']
-        propertiesLoaderMock = [getGradleProperties: { expectedGradleProperties }] as IGradlePropertiesLoader
         urlClassLoader = new URLClassLoader(new URL[0]);
         initExpectedSettings()
     }
@@ -75,6 +74,11 @@ class ScriptEvaluatingSettingsProcessorTest {
         context.checking {
             one(settingsFactory).createSettings(gradleMock, TEST_ROOT_DIR, scriptSourceMock, expectedGradleProperties, expectedStartParameter, urlClassLoader)
             will(returnValue(expectedSettings))
+            
+            one(settingsLocation).getSettingsDir()
+            will(returnValue(TEST_ROOT_DIR))
+            allowing(settingsLocation).getSettingsScriptSource()
+            will(returnValue(scriptSourceMock))
         }
     }
 
@@ -85,13 +89,13 @@ class ScriptEvaluatingSettingsProcessorTest {
         context.checking {
             one(configurerFactoryMock).create(scriptSourceMock)
             will(returnValue(configurerMock))
-
+            one(propertiesLoaderMock).mergeProperties([:])
+            will(returnValue(expectedGradleProperties))
             one(configurerMock).setClassLoader(urlClassLoader)
             one(configurerMock).setScriptBaseClass(SettingsScript)
             one(configurerMock).apply(expectedSettings)
         }
-        
-        SettingsLocation settingsLocation = new SettingsLocation(TEST_ROOT_DIR, scriptSourceMock)
-        assertSame(expectedSettings, settingsProcessor.process(gradleMock, settingsLocation, urlClassLoader, expectedStartParameter, propertiesLoaderMock))
+
+        assertSame(expectedSettings, settingsProcessor.process(gradleMock, settingsLocation, urlClassLoader, expectedStartParameter))
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
index cc435c5..ab872db 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
@@ -21,14 +21,16 @@ import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.util.WrapUtil;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.jmock.lib.legacy.ClassImposteriser;
-import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.util.Map;
-import java.net.URLClassLoader;
 import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 
 /**
  * @author Hans Dockter
@@ -55,7 +57,10 @@ public class SettingsFactoryTest {
 
         assertSame(gradle, settings.getGradle());
         assertSame(expectedProjectDescriptorRegistry, settings.getProjectDescriptorRegistry());
-        assertEquals(expectedGradleProperties, settings.getAdditionalProperties());
+        for (Map.Entry<String, String> entry : expectedGradleProperties.entrySet()) {
+            assertEquals(entry.getValue(), settings.getDynamicObject().getProperty(entry.getKey()));
+        }
+
         assertSame(expectedSettingsDir, settings.getSettingsDir());
         assertSame(expectedScriptSource, settings.getSettingsScript());
         assertSame(expectedStartParameter, settings.getStartParameter());
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
index 11570d5..c50b8dc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
@@ -19,7 +19,9 @@ import org.gradle.StartParameter;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.SettingsInternal;
 import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.util.GFileUtils;
 import org.gradle.util.MultiParentClassLoader;
+import org.gradle.util.WrapUtil;
 import org.hamcrest.Description;
 import org.hamcrest.Factory;
 import org.hamcrest.Matcher;
@@ -33,8 +35,8 @@ import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
 
 /**
  * @author Hans Dockter
@@ -44,9 +46,8 @@ public class SettingsHandlerTest {
         setImposteriser(ClassImposteriser.INSTANCE);
     }};
     private GradleInternal gradle = context.mock(GradleInternal.class);
-    private IGradlePropertiesLoader gradlePropertiesLoader = context.mock(IGradlePropertiesLoader.class);
     private SettingsInternal settings = context.mock(SettingsInternal.class);
-    private SettingsLocation settingsLocation = new SettingsLocation(new File("someDir"), null);
+    private SettingsLocation settingsLocation = new SettingsLocation(GFileUtils.canonicalise(new File("someDir")), null);
     private StartParameter startParameter = new StartParameter();
     private URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
     private ISettingsFinder settingsFinder = context.mock(ISettingsFinder.class);
@@ -64,35 +65,40 @@ public class SettingsHandlerTest {
                     settingsLocation.getSettingsDir(), BaseSettings.DEFAULT_BUILD_SRC_DIR))));
             will(returnValue(urlClassLoader));
         }});
-        assertThat(settingsHandler.findAndLoadSettings(gradle, gradlePropertiesLoader), sameInstance(settings));
+        assertThat(settingsHandler.findAndLoadSettings(gradle), sameInstance(settings));
     }
 
     private void prepareForExistingSettings() {
-        final ProjectSpec projectSpec = context.mock(ProjectSpec.class);
         final IProjectRegistry projectRegistry = context.mock(IProjectRegistry.class);
-        startParameter.setDefaultProjectSelector(projectSpec);
+        final DefaultProjectDescriptor projectDescriptor = context.mock(DefaultProjectDescriptor.class);
+        startParameter.setCurrentDir(settingsLocation.getSettingsDir());
 
         context.checking(new Expectations() {{
             allowing(settings).getProjectRegistry();
             will(returnValue(projectRegistry));
 
+            allowing(projectRegistry).getAllProjects();
+            will(returnValue(WrapUtil.toSet(projectDescriptor)));
+
+            allowing(projectDescriptor).getProjectDir();
+            will(returnValue(settingsLocation.getSettingsDir()));
+
+            allowing(projectDescriptor).getBuildFile();
+            will(returnValue(new File(settingsLocation.getSettingsDir(), "build.gradle")));
+
             allowing(settings).getClassLoader();
             will(returnValue(urlClassLoader));
 
             allowing(gradle).getScriptClassLoader();
             will(returnValue(scriptClassLoader));
 
-            allowing(projectSpec).containsProject(projectRegistry);
-            will(returnValue(true));
-
             allowing(gradle).getStartParameter();
             will(returnValue(startParameter));
 
             allowing(settingsFinder).find(startParameter);
             will(returnValue(settingsLocation));
 
-            one(settingsProcessor).process(gradle, settingsLocation, urlClassLoader,
-                    startParameter, gradlePropertiesLoader);
+            one(settingsProcessor).process(gradle, settingsLocation, urlClassLoader, startParameter);
             will(returnValue(settings));
 
             one(scriptClassLoader).addParent(urlClassLoader);
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
index 981d572..78c9cb0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
@@ -21,26 +21,29 @@ import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
 import org.gradle.util.TemporaryFolder;
 import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
 
+ at RunWith(JMock.class)
 public class UserHomeInitScriptFinderTest {
     @Rule
     public final TemporaryFolder tmpDir = new TemporaryFolder();
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final GradleInternal gradleMock = context.mock(GradleInternal.class);
-    private final InitScriptFinder initScriptFinderMock = context.mock(InitScriptFinder.class);
     private final StartParameter testStartParameter = new StartParameter();
+    private final UserHomeInitScriptFinder finder = new UserHomeInitScriptFinder();
 
     @Before
     public void setup() {
@@ -52,28 +55,31 @@ public class UserHomeInitScriptFinderTest {
     }
 
     @Test
-    public void addsUserInitScriptWhenItExists() throws IOException {
-        File initScript = tmpDir.file("init.gradle").createFile();
+    public void addsUserInitScriptWhenItExists() {
+        File initScript = tmpDir.createFile("init.gradle");
 
-        context.checking(new Expectations() {{
-            allowing(initScriptFinderMock).findScripts(gradleMock);
-            will(returnValue(new ArrayList()));
-        }});
-
-        List<ScriptSource> sourceList = new UserHomeInitScriptFinder(initScriptFinderMock).findScripts(gradleMock);
+        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
+        finder.findScripts(gradleMock, sourceList);
         assertThat(sourceList.size(), equalTo(1));
         assertThat(sourceList.get(0), instanceOf(UriScriptSource.class));
         assertThat(sourceList.get(0).getResource().getFile(), equalTo(initScript));
     }
 
     @Test
-    public void doesNotAddUserInitScriptWhenItDoesNotExist() throws IOException {
-        context.checking(new Expectations() {{
-            allowing(initScriptFinderMock).findScripts(gradleMock);
-            will(returnValue(new ArrayList()));
-        }});
-
-        List<ScriptSource> sourceList = new UserHomeInitScriptFinder(initScriptFinderMock).findScripts(gradleMock);
+    public void doesNotAddUserInitScriptsWhenTheyDoNotExist() {
+        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
+        finder.findScripts(gradleMock, sourceList);
         assertThat(sourceList.size(), equalTo(0));
     }
+
+    @Test
+    public void addsInitScriptsFromInitDirectoryWhenItExists() {
+        File initScript = tmpDir.createFile("init.d/script.gradle");
+
+        List<ScriptSource> sourceList = new ArrayList<ScriptSource>();
+        finder.findScripts(gradleMock, sourceList);
+        assertThat(sourceList.size(), equalTo(1));
+        assertThat(sourceList.get(0), instanceOf(UriScriptSource.class));
+        assertThat(sourceList.get(0).getResource().getFile(), equalTo(initScript));
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutConfigurationTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutConfigurationTest.groovy
new file mode 100644
index 0000000..8ffdf18
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutConfigurationTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.layout
+
+import org.gradle.StartParameter
+import spock.lang.Specification
+
+class BuildLayoutConfigurationTest extends Specification {
+    def "uses specified settings script"() {
+        def startParameter = new StartParameter()
+        def settingsFile = new File("settings.gradle")
+        startParameter.settingsFile = settingsFile
+        def config = new BuildLayoutConfiguration(startParameter)
+
+        expect:
+        config.settingsFile == settingsFile.canonicalFile
+    }
+
+    def "uses default settings file when none specified"() {
+        def startParameter = new StartParameter()
+        def config = new BuildLayoutConfiguration(startParameter)
+
+        expect:
+        config.settingsFile == null
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy
new file mode 100644
index 0000000..53c2adc
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/layout/BuildLayoutFactoryTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2011 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.layout
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.groovy.scripts.UriScriptSource
+import org.gradle.groovy.scripts.StringScriptSource
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.StartParameter
+
+class BuildLayoutFactoryTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    final BuildLayoutFactory locator = new BuildLayoutFactory()
+
+    def "returns current directory when it contains a settings file"() {
+        def currentDir = tmpDir.dir
+        def settingsFile = currentDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == currentDir
+        layout.settingsDir == currentDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "looks for sibling directory called 'master' that it contains a settings file"() {
+        def currentDir = tmpDir.createDir("current")
+        def masterDir = tmpDir.createDir("master")
+        def settingsFile = masterDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == masterDir.parentFile
+        layout.settingsDir == masterDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "searches ancestors for a directory called 'master' that contains a settings file"() {
+        def currentDir = tmpDir.createDir("sub/current")
+        def masterDir = tmpDir.createDir("master")
+        def settingsFile = masterDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == masterDir.parentFile
+        layout.settingsDir == masterDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "ignores 'master' directory when it does not contain a settings file"() {
+        def currentDir = tmpDir.createDir("sub/current")
+        def masterDir = tmpDir.createDir("sub/master")
+        masterDir.createFile("gradle.properties")
+        def settingsFile = tmpDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == tmpDir.dir
+        layout.settingsDir == tmpDir.dir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "returns closest ancestor directory that contains a settings file"() {
+        def currentDir = tmpDir.createDir("sub/current")
+        def subDir = tmpDir.createDir("sub")
+        def settingsFile = subDir.createFile("settings.gradle")
+        tmpDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == subDir
+        layout.settingsDir == subDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "prefers the current directory as root directory"() {
+        def currentDir = tmpDir.createDir("sub/current")
+        def settingsFile = currentDir.createFile("settings.gradle")
+        tmpDir.createFile("sub/settings.gradle")
+        tmpDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == currentDir
+        layout.settingsDir == currentDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "prefers the 'master' directory over ancestor directory"() {
+        def currentDir = tmpDir.createDir("sub/current")
+        def masterDir = tmpDir.createDir("sub/master")
+        def settingsFile = masterDir.createFile("settings.gradle")
+        tmpDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, true)
+        layout.rootDirectory == masterDir.parentFile
+        layout.settingsDir == masterDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "returns start directory when search upwards is disabled"() {
+        def currentDir = tmpDir.createDir("sub/current")
+        tmpDir.createFile("sub/settings.gradle")
+        tmpDir.createFile("settings.gradle")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, false)
+        layout.rootDirectory == currentDir
+        layout.settingsDir == currentDir
+        isEmpty(layout.settingsScriptSource)
+    }
+
+    def "returns current directory when no settings or wrapper properties files found"() {
+        def currentDir = tmpDir.createDir("sub/current")
+
+        expect:
+        def layout = locator.getLayoutFor(currentDir, tmpDir.dir)
+        layout.rootDirectory == currentDir
+        layout.settingsDir == currentDir
+        isEmpty(layout.settingsScriptSource)
+    }
+
+    def "can override build layout by specifying the settings file"() {
+        def currentDir = tmpDir.createDir("current")
+        currentDir.createFile("settings.gradle")
+        def rootDir = tmpDir.createDir("root")
+        def settingsFile = rootDir.createDir("some-settings.gradle")
+        def startParameter = new StartParameter()
+        startParameter.currentDir = currentDir
+        startParameter.settingsFile = settingsFile
+        def config = new BuildLayoutConfiguration(startParameter)
+
+        expect:
+        def layout = locator.getLayoutFor(config)
+        layout.rootDirectory == rootDir
+        layout.settingsDir == rootDir
+        refersTo(layout.settingsScriptSource, settingsFile)
+    }
+
+    def "can override build layout by specifying an empty settings script"() {
+        def currentDir = tmpDir.createDir("current")
+        currentDir.createFile("settings.gradle")
+        def startParameter = new StartParameter()
+        startParameter.currentDir = currentDir
+        startParameter.useEmptySettings()
+        def config = new BuildLayoutConfiguration(startParameter)
+
+        expect:
+        def layout = locator.getLayoutFor(config)
+        layout.rootDirectory == currentDir
+        layout.settingsDir == currentDir
+        isEmpty(layout.settingsScriptSource)
+    }
+
+    void refersTo(ScriptSource scriptSource, File file) {
+        assert scriptSource instanceof UriScriptSource
+        assert scriptSource.resource.sourceFile == file
+    }
+
+    void isEmpty(ScriptSource scriptSource) {
+        assert scriptSource instanceof StringScriptSource
+        assert scriptSource.resource.contents == ''
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java b/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
index 89b885f..58342bf 100644
--- a/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
@@ -19,6 +19,8 @@ package org.gradle.invocation;
 import groovy.lang.Closure;
 import org.gradle.BuildListener;
 import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.initialization.dsl.ScriptHandler;
 import org.gradle.api.internal.GradleDistributionLocator;
@@ -29,29 +31,30 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.ServiceRegistryFactory;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
 import org.gradle.util.GradleVersion;
 import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.MultiParentClassLoader;
 import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 import java.io.File;
 import java.io.IOException;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
- at RunWith(JUnit4.class)
+ at RunWith(JMock.class)
 public class DefaultGradleTest {
-    private final JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final StartParameter parameter = new StartParameter();
     private final ScriptHandler scriptHandlerMock = context.mock(ScriptHandler.class);
     private final ServiceRegistryFactory serviceRegistryFactoryMock = context.mock(ServiceRegistryFactory.class, "parent");
@@ -63,6 +66,8 @@ public class DefaultGradleTest {
     private final Gradle parent = context.mock(Gradle.class, "parentBuild");
     private final MultiParentClassLoader scriptClassLoaderMock = context.mock(MultiParentClassLoader.class);
     private final GradleDistributionLocator gradleDistributionLocatorMock = context.mock(GradleDistributionLocator.class);
+    private final ListenerBroadcast<BuildListener> buildListenerBroadcast = new ListenerBroadcast<BuildListener>(BuildListener.class);
+    private final ListenerBroadcast<ProjectEvaluationListener> projectEvaluationListenerBroadcast = context.mock(ListenerBroadcast.class);
     private DefaultGradle gradle;
 
     @Before
@@ -86,6 +91,10 @@ public class DefaultGradleTest {
             will(returnValue(scriptClassLoaderMock));
             allowing(gradleServiceRegistryMock).get(GradleDistributionLocator.class);
             will(returnValue(gradleDistributionLocatorMock));
+            allowing(listenerManager).createAnonymousBroadcaster(BuildListener.class);
+            will(returnValue(buildListenerBroadcast));
+            allowing(listenerManager).createAnonymousBroadcaster(ProjectEvaluationListener.class);
+            will(returnValue(projectEvaluationListenerBroadcast));
         }});
         gradle = new DefaultGradle(parent, parameter, serviceRegistryFactoryMock);
     }
@@ -124,16 +133,12 @@ public class DefaultGradleTest {
 
     @Test
     public void broadcastsProjectEventsToListeners() {
-        final ProjectEvaluationListener listener = context.mock(ProjectEvaluationListener.class, "listener");
         final ProjectEvaluationListener broadcaster = context.mock(ProjectEvaluationListener.class, "broadcaster");
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(listener);
-            one(listenerManager).getBroadcaster(ProjectEvaluationListener.class);
+            one(projectEvaluationListenerBroadcast).getSource();
             will(returnValue(broadcaster));
         }});
 
-        gradle.addListener(listener);
-
         assertThat(gradle.getProjectEvaluationBroadcaster(), sameInstance(broadcaster));
     }
 
@@ -141,7 +146,7 @@ public class DefaultGradleTest {
     public void broadcastsBeforeProjectEvaluateEventsToClosures() {
         final Closure closure = HelperUtil.TEST_CLOSURE;
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(ProjectEvaluationListener.class, "beforeEvaluate", closure);
+            one(projectEvaluationListenerBroadcast).add("beforeEvaluate", closure);
         }});
 
         gradle.beforeProject(closure);
@@ -151,7 +156,7 @@ public class DefaultGradleTest {
     public void broadcastsAfterProjectEvaluateEventsToClosures() {
         final Closure closure = HelperUtil.TEST_CLOSURE;
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(ProjectEvaluationListener.class, "afterEvaluate", closure);
+            one(projectEvaluationListenerBroadcast).add("afterEvaluate", closure);
         }});
 
         gradle.afterProject(closure);
@@ -159,52 +164,59 @@ public class DefaultGradleTest {
 
     @Test
     public void broadcastsBuildStartedEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = closure();
+        gradle.buildStarted(closure);
+
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(BuildListener.class, "buildStarted", closure);
+            one(closure).call(new Object[0]);
         }});
-
-        gradle.buildStarted(closure);
+        gradle.getBuildListenerBroadcaster().buildStarted(gradle);
     }
 
     @Test
     public void broadcastsSettingsEvaluatedEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = closure();
+        gradle.settingsEvaluated(closure);
+
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(BuildListener.class, "settingsEvaluated", closure);
+            one(closure).call(new Object[0]);
         }});
 
-        gradle.settingsEvaluated(closure);
+        gradle.getBuildListenerBroadcaster().settingsEvaluated(null);
     }
 
     @Test
     public void broadcastsProjectsLoadedEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = closure();
+        gradle.projectsLoaded(closure);
+
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(BuildListener.class, "projectsLoaded", closure);
+            one(closure).call(new Object[0]);
         }});
 
-        gradle.projectsLoaded(closure);
+        gradle.getBuildListenerBroadcaster().projectsLoaded(gradle);
     }
 
     @Test
     public void broadcastsProjectsEvaluatedEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = closure();
+        gradle.projectsEvaluated(closure);
+
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(BuildListener.class, "projectsEvaluated", closure);
+            one(closure).call(new Object[0]);
         }});
-
-        gradle.projectsEvaluated(closure);
+        gradle.getBuildListenerBroadcaster().projectsEvaluated(gradle);
     }
 
     @Test
     public void broadcastsBuildFinishedEventsToClosures() {
-        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final Closure closure = closure();
+        gradle.buildFinished(closure);
+
         context.checking(new Expectations() {{
-            one(listenerManager).addListener(BuildListener.class, "buildFinished", closure);
+            one(closure).call(new Object[0]);
         }});
-
-        gradle.buildFinished(closure);
+        gradle.getBuildListenerBroadcaster().buildFinished(null);
     }
 
     @Test
@@ -217,6 +229,51 @@ public class DefaultGradleTest {
     }
 
     @Test
+    public void getRootProjectThrowsExceptionWhenRootProjectIsNotAvailable() {
+        try {
+            gradle.getRootProject();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo("The root project is not yet available for " + gradle + "."));
+        }
+
+        ProjectInternal rootProject = context.mock(ProjectInternal.class);
+        gradle.setRootProject(rootProject);
+        
+        assertThat(gradle.getRootProject(), sameInstance(rootProject));
+    }
+    
+    @Test
+    public void rootProjectActionIsExecutedWhenProjectsAreLoaded() {
+        final Action<Project> action = context.mock(Action.class);
+        final ProjectInternal rootProject = context.mock(ProjectInternal.class);
+
+        gradle.rootProject(action);
+
+        context.checking(new Expectations() {{
+            one(action).execute(rootProject);
+        }});
+        
+        gradle.setRootProject(rootProject);
+        gradle.getBuildListenerBroadcaster().projectsLoaded(gradle);
+    }
+
+    @Test
+    public void allprojectsActionIsExecutedWhenProjectAreLoaded() {
+        final Action<Project> action = context.mock(Action.class);
+        final ProjectInternal rootProject = context.mock(ProjectInternal.class);
+
+        gradle.allprojects(action);
+
+        context.checking(new Expectations() {{
+            one(rootProject).allprojects(action);
+        }});
+
+        gradle.setRootProject(rootProject);
+        gradle.getBuildListenerBroadcaster().projectsLoaded(gradle);
+    }
+
+    @Test
     public void hasToString() {
         assertThat(gradle.toString(), equalTo("build"));
 
@@ -228,4 +285,13 @@ public class DefaultGradleTest {
         gradle.setRootProject(project);
         assertThat(gradle.toString(), equalTo("build 'rootProject'"));
     }
+
+    private Closure closure() {
+        final Closure mock = context.mock(Closure.class);
+        context.checking(new Expectations(){{
+            allowing(mock).getMaximumNumberOfParameters();
+            will(returnValue(0));
+        }});
+        return mock;
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy b/subprojects/core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/LazyCreationProxyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/listener/LazyCreationProxyTest.groovy
new file mode 100644
index 0000000..e757aa0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/listener/LazyCreationProxyTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.internal.Factory
+import java.util.concurrent.Callable
+
+class LazyCreationProxyTest extends Specification {
+    final Factory<Callable<String>> factory = Mock()
+    final Callable<String> callable = Mock()
+
+    def "instantiates and caches object on first method invocation on source"() {
+        when:
+        def proxy = new LazyCreationProxy<Callable<String>>(Callable, factory)
+        def source = proxy.source
+
+        then:
+        0 * factory._
+
+        when:
+        def result = source.call()
+
+        then:
+        result == 'a'
+        1 * factory.create() >> callable
+        1 * callable.call() >> 'a'
+        0 * factory._
+        0 * callable._
+
+        when:
+        result = source.call()
+
+        then:
+        result == 'b'
+        1 * callable.call() >> 'b'
+        0 * factory._
+        0 * callable._
+    }
+
+    def "rethrows exception thrown by factory on creation"() {
+        def failure = new RuntimeException()
+        
+        given:
+        def proxy = new LazyCreationProxy<Callable<String>>(Callable, factory)
+        def source = proxy.source
+
+        when:
+        source.call()
+
+        then:
+        Exception e = thrown(Exception)
+        e == failure
+
+        and:
+        _ * factory.create() >> { throw failure }
+    }
+
+    def "rethrows checked exception thrown by method call on target object"() {
+        def failure = new IOException()
+
+        given:
+        def proxy = new LazyCreationProxy<Callable<String>>(Callable, factory)
+        def source = proxy.source
+        _ * factory.create() >> callable
+
+        when:
+        source.call()
+
+        then:
+        Exception e = thrown(Exception)
+        e == failure
+
+        and:
+        _ * callable.call() >> { throw failure }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
index 4c3f1cd..ef01f4d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
@@ -16,15 +16,15 @@
 
 package org.gradle.logging
 
+import org.gradle.cli.CommandLineConverter
+import org.gradle.internal.service.ServiceRegistry
 import org.gradle.logging.internal.DefaultLoggingManagerFactory
 import org.gradle.logging.internal.DefaultProgressLoggerFactory
 import org.gradle.logging.internal.DefaultStyledTextOutputFactory
-import spock.lang.Specification
-import org.gradle.initialization.CommandLineConverter
 import org.gradle.logging.internal.LoggingCommandLineConverter
-import org.junit.Rule
 import org.gradle.util.RedirectStdOutAndErr
-import org.gradle.api.internal.project.ServiceRegistry
+import org.junit.Rule
+import spock.lang.Specification
 
 class LoggingServiceRegistryTest extends Specification {
     @Rule RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
@@ -70,15 +70,4 @@ class LoggingServiceRegistryTest extends Specification {
         System.err != outputs.stdErrPrintStream
     }
     
-    def canDisableSystemOutAndErrCapture() {
-        def loggingManager = registry.newInstance(LoggingManagerInternal)
-        loggingManager.disableStandardOutputCapture()
-
-        when:
-        loggingManager.start()
-
-        then:
-        System.out == outputs.stdOutPrintStream
-        System.err == outputs.stdErrPrintStream
-    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutputTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutputTest.groovy
new file mode 100644
index 0000000..8e38ee2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractLineChoppingStyledTextOutputTest.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012 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.logging.internal
+
+import org.gradle.logging.StyledTextOutput
+import org.gradle.util.SetSystemProperties
+import org.gradle.internal.SystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+class AbstractLineChoppingStyledTextOutputTest extends Specification {
+    @Rule final SetSystemProperties systemProperties = new SetSystemProperties()
+    final StringBuilder result = new StringBuilder()
+    final String eol = SystemProperties.getLineSeparator()
+
+    def "appends text to current line"() {
+        def output = output()
+
+        when:
+        output.text("some text")
+
+        then:
+        result.toString() == "[some text]"
+    }
+
+    def "appends eol to current line"() {
+        def output = output()
+
+        when:
+        output.text(eol)
+
+        then:
+        result.toString() == "[${eol}]{eol}{finish}"
+    }
+
+    def "append text that contains multiple lines"() {
+        def output = output()
+
+        when:
+        output.text("a${eol}b")
+
+        then:
+        result.toString() == "[a${eol}]{eol}{finish}{start}[b]"
+    }
+
+    def "append text that ends with eol"() {
+        def output = output()
+
+        when:
+        output.text("a${eol}")
+
+        then:
+        result.toString() == "[a${eol}]{eol}{finish}"
+
+        when:
+        output.text("b${eol}")
+        output.text(eol)
+        output.text("c")
+
+        then:
+        result.toString() == "[a${eol}]{eol}{finish}{start}[b${eol}]{eol}{finish}{start}[${eol}]{eol}{finish}{start}[c]"
+    }
+
+    def "can append eol in chunks"() {
+        System.setProperty("line.separator", "----");
+        def output = output()
+
+        when:
+        output.text("a--")
+        
+        then:
+        result.toString() == "[a--]"
+        
+        when:
+        output.text("--b")
+        
+        then:
+        result.toString() == "[a--][--]{eol}{finish}{start}[b]"
+    }
+
+    def "can append eol prefix"() {
+        System.setProperty("line.separator", "----");
+        def output = output()
+
+        when:
+        output.text("--")
+        
+        then:
+        result.toString() == "[--]"
+
+        when:
+        output.text("a")
+
+        then:
+        result.toString() == "[--][a]"
+    }
+
+    def "can split eol across style changes"() {
+        System.setProperty("line.separator", "----");
+        def output = output()
+
+        when:
+        output.text("--")
+        output.style(StyledTextOutput.Style.Failure)
+        output.text("--")
+
+        then:
+        result.toString() == "[--]{style}[--]{eol}{finish}"
+    }
+
+    def output() {
+        final AbstractLineChoppingStyledTextOutput output = new AbstractLineChoppingStyledTextOutput() {
+            @Override
+            protected void doStyleChange(StyledTextOutput.Style style) {
+                result.append("{style}")
+            }
+
+            @Override
+            protected void doFinishLine() {
+                result.append("{finish}")
+            }
+
+            @Override
+            protected void doStartLine() {
+                result.append("{start}")
+            }
+
+            @Override
+            protected void doLineText(CharSequence text, boolean terminatesLine) {
+                result.append("[")
+                result.append(text)
+                result.append("]")
+                if (terminatesLine) {
+                    result.append("{eol}")
+                }
+            }
+        }
+        return output
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy
index 470998e..db1736d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.logging.internal
 
 import org.gradle.logging.StyledTextOutput.Style
-import org.gradle.util.SystemProperties
+import org.gradle.internal.SystemProperties
 
 class AbstractStyledTextOutputTest extends OutputSpecification {
     private final TestStyledTextOutput output = new TestStyledTextOutput()
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AnsiConsoleTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AnsiConsoleTest.groovy
index d776767..34a18fb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AnsiConsoleTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AnsiConsoleTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.logging.internal
 
 import org.fusesource.jansi.Ansi
 import org.fusesource.jansi.Ansi.Color
-import org.gradle.util.SystemProperties
+import org.gradle.internal.SystemProperties
 import org.gradle.logging.StyledTextOutput
 import org.gradle.logging.StyledTextOutput.Style
 import org.gradle.util.JUnit4GroovyMockery
@@ -49,8 +49,7 @@ class AnsiConsoleTest {
         console.mainArea.append('message')
 
         context.checking {
-            one(ansi).a('message2')
-            one(ansi).a(EOL)
+            one(ansi).a('message2' + EOL)
             one(ansi).a('message3')
         }
 
@@ -82,8 +81,7 @@ class AnsiConsoleTest {
     @Test
     public void displaysStatusBarWhenTextInMainArea() {
         context.checking {
-            one(ansi).a('message')
-            one(ansi).a(EOL)
+            one(ansi).a('message' + EOL)
         }
 
         console.mainArea.append("message${EOL}")
@@ -277,8 +275,7 @@ class AnsiConsoleTest {
         context.checking {
             one(ansi).cursorLeft(6)
             one(ansi).eraseLine(Ansi.Erase.FORWARD)
-            one(ansi).a('message')
-            one(ansi).a(EOL)
+            one(ansi).a('message' + EOL)
             one(ansi).a('status')
         }
 
@@ -336,8 +333,7 @@ class AnsiConsoleTest {
             one(ansi).eraseLine(Ansi.Erase.FORWARD)
             one(ansi).cursorUp(1)
             one(ansi).cursorRight(7)
-            one(ansi).a('message2')
-            one(ansi).a(EOL)
+            one(ansi).a('message2' + EOL)
             one(ansi).a('status')
         }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java
index d981bdb..ebc82e4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultLoggingManagerTest.java
@@ -43,7 +43,6 @@ public class DefaultLoggingManagerTest {
 
     @Test
     public void defaultValues() {
-        assertTrue(loggingManager.isStandardOutputCaptureEnabled());
         assertEquals(LogLevel.QUIET, loggingManager.getStandardOutputCaptureLevel());
         assertEquals(LogLevel.ERROR, loggingManager.getStandardErrorCaptureLevel());
         assertNull(loggingManager.getLevel());
@@ -52,7 +51,6 @@ public class DefaultLoggingManagerTest {
     @Test
     public void canChangeStdOutCaptureLogLevel() {
         loggingManager.captureStandardOutput(LogLevel.ERROR);
-        assertTrue(loggingManager.isStandardOutputCaptureEnabled());
         assertEquals(LogLevel.ERROR, loggingManager.getStandardOutputCaptureLevel());
     }
 
@@ -69,37 +67,6 @@ public class DefaultLoggingManagerTest {
     }
 
     @Test
-    public void canDisableCapture() {
-        loggingManager.disableStandardOutputCapture();
-        assertFalse(loggingManager.isStandardOutputCaptureEnabled());
-        assertNull(loggingManager.getStandardOutputCaptureLevel());
-    }
-
-    @Test
-    public void startStopWithCaptureDisabled() {
-        loggingManager.disableStandardOutputCapture();
-
-        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
-        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
-        context.checking(new Expectations() {{
-            ignoring(loggingSystem);
-            one(stdOutLoggingSystem).off();
-            will(returnValue(stdOutSnapshot));
-            one(stdErrLoggingSystem).off();
-            will(returnValue(stdErrSnapshot));
-        }});
-
-        loggingManager.start();
-
-        context.checking(new Expectations() {{
-            one(stdOutLoggingSystem).restore(stdOutSnapshot);
-            one(stdErrLoggingSystem).restore(stdErrSnapshot);
-        }});
-
-        loggingManager.stop();
-    }
-
-    @Test
     public void startStopWithCaptureEnabled() {
         loggingManager.captureStandardOutput(LogLevel.DEBUG);
         loggingManager.captureStandardError(LogLevel.INFO);
@@ -165,70 +132,6 @@ public class DefaultLoggingManagerTest {
     }
 
     @Test
-    public void disableCaptureWhileStarted() {
-        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
-        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
-        context.checking(new Expectations() {{
-            ignoring(loggingSystem);
-            one(stdOutLoggingSystem).on(LogLevel.DEBUG);
-            will(returnValue(stdOutSnapshot));
-            one(stdErrLoggingSystem).on(LogLevel.INFO);
-            will(returnValue(stdErrSnapshot));
-        }});
-
-        loggingManager.captureStandardOutput(LogLevel.DEBUG);
-        loggingManager.captureStandardError(LogLevel.INFO);
-
-        loggingManager.start();
-
-        context.checking(new Expectations() {{
-            one(stdOutLoggingSystem).off();
-            one(stdErrLoggingSystem).off();
-        }});
-
-        loggingManager.disableStandardOutputCapture();
-
-        context.checking(new Expectations() {{
-            one(stdOutLoggingSystem).restore(stdOutSnapshot);
-            one(stdErrLoggingSystem).restore(stdErrSnapshot);
-        }});
-
-        loggingManager.stop();
-    }
-
-    @Test
-    public void enableCaptureWhileStarted() {
-        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
-        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
-        context.checking(new Expectations() {{
-            ignoring(loggingSystem);
-            one(stdOutLoggingSystem).off();
-            will(returnValue(stdOutSnapshot));
-            one(stdErrLoggingSystem).off();
-            will(returnValue(stdErrSnapshot));
-        }});
-
-        loggingManager.disableStandardOutputCapture();
-
-        loggingManager.start();
-
-        context.checking(new Expectations() {{
-            one(stdOutLoggingSystem).on(LogLevel.DEBUG);
-            one(stdErrLoggingSystem).on(LogLevel.INFO);
-        }});
-
-        loggingManager.captureStandardOutput(LogLevel.DEBUG);
-        loggingManager.captureStandardError(LogLevel.INFO);
-
-        context.checking(new Expectations() {{
-            one(stdOutLoggingSystem).restore(stdOutSnapshot);
-            one(stdErrLoggingSystem).restore(stdErrSnapshot);
-        }});
-
-        loggingManager.stop();
-    }
-
-    @Test
     public void changeCaptureLevelWhileStarted() {
         final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
         final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirectorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirectorTest.groovy
index 73ca11e..355b1a9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirectorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultStandardOutputRedirectorTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.logging.internal
 
 import org.gradle.api.logging.StandardOutputListener
-import org.gradle.util.SystemProperties
+import org.gradle.internal.SystemProperties
 import org.gradle.util.RedirectStdOutAndErr
 import org.junit.Rule
 import spock.lang.Specification
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy
index 5a49b29..0ee9f1e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingCommandLineConverterTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.logging.internal
 
 import org.gradle.api.logging.LogLevel
 import org.gradle.logging.LoggingConfiguration
+import org.gradle.logging.ShowStacktrace
 import spock.lang.Specification
 
 class LoggingCommandLineConverterTest extends Specification {
@@ -59,9 +60,32 @@ class LoggingCommandLineConverterTest extends Specification {
         checkConversion(['--no-color'])
     }
 
+    def convertsShowStacktrace() {
+        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS;
+
+        expect:
+        checkConversion(['-s'])
+        checkConversion(['--stacktrace'])
+    }
+
+    def convertsShowFullStacktrace() {
+        expectedConfig.showStacktrace = ShowStacktrace.ALWAYS_FULL;
+
+        expect:
+        checkConversion(['-S'])
+        checkConversion(['--full-stacktrace'])
+    }
+
+    def providesLogLevelOptions() {
+        expect:
+        converter.logLevelOptions.containsAll(["d", "q", "i"])
+        converter.logLevelOptions.size() == 3
+    }
+
     void checkConversion(List<String> args) {
         def actual = converter.convert(args)
         assert actual.logLevel == expectedConfig.logLevel
         assert actual.colorOutput == expectedConfig.colorOutput
+        assert actual.showStacktrace == expectedConfig.showStacktrace
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputEventRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputEventRendererTest.groovy
index 9b28b73..cbd0041 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputEventRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/OutputEventRendererTest.groovy
@@ -17,9 +17,9 @@ package org.gradle.logging.internal
 
 import org.gradle.api.logging.LogLevel
 import org.gradle.api.logging.StandardOutputListener
+import org.gradle.internal.nativeplatform.TerminalDetector
 import org.gradle.util.RedirectStdOutAndErr
 import org.junit.Rule
-import org.gradle.api.specs.Spec
 
 class OutputEventRendererTest extends OutputSpecification {
     @Rule public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
@@ -27,7 +27,7 @@ class OutputEventRendererTest extends OutputSpecification {
     private OutputEventRenderer renderer
 
     def setup() {
-        renderer = new OutputEventRenderer(Mock(Spec))
+        renderer = new OutputEventRenderer(Mock(TerminalDetector))
         renderer.addStandardOutput(outputs.stdOutPrintStream)
         renderer.addStandardError(outputs.stdErrPrintStream)
         renderer.configure(LogLevel.INFO)
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/Slf4jLoggingConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/Slf4jLoggingConfigurerTest.groovy
deleted file mode 100644
index f526a4a..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/Slf4jLoggingConfigurerTest.groovy
+++ /dev/null
@@ -1,180 +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.logging.internal
-
-import spock.lang.Specification
-import ch.qos.logback.classic.LoggerContext
-import org.slf4j.LoggerFactory
-
-import org.gradle.api.logging.LogLevel
-import org.slf4j.Logger
-import org.gradle.api.logging.Logging
-
-class Slf4jLoggingConfigurerTest extends Specification {
-    private final Logger logger = LoggerFactory.getLogger("cat1");
-    private final OutputEventListener listener = Mock()
-    private final Slf4jLoggingConfigurer configurer = new Slf4jLoggingConfigurer(listener)
-    
-    def cleanup() {
-        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        lc.reset();
-    }
-
-    def routesSlf4jLogEventsToOutputEventListener() {
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message')
-
-        then:
-        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == null})
-        0 * listener._
-    }
-
-    def includesThrowableInLogEvent() {
-        def failure = new RuntimeException()
-
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message', failure)
-
-        then:
-        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == failure})
-        0 * listener._
-    }
-    
-    def mapsSlf4jLogLevelsToGradleLogLevels() {
-        when:
-        configurer.configure(LogLevel.DEBUG)
-
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
-        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def formatsLogMessage() {
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message {} {}', 'arg1', 'arg2')
-
-        then:
-        1 * listener.onOutput({it.message == 'message arg1 arg2'})
-        0 * listener._
-    }
-
-    def attachesATimestamp() {
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message')
-
-        then:
-        1 * listener.onOutput({it.timestamp >= System.currentTimeMillis() - 300})
-        0 * listener._
-    }
-
-    def filtersLifecycleAndLowerWhenConfiguredAtQuietLevel() {
-        when:
-        configurer.configure(LogLevel.QUIET)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def filtersInfoAndLowerWhenConfiguredAtLifecycleLevel() {
-        when:
-        configurer.configure(LogLevel.LIFECYCLE)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def filtersDebugAndLowerWhenConfiguredAtInfoLevel() {
-        when:
-        configurer.configure(LogLevel.INFO)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def filtersTraceWhenConfiguredAtDebugLevel() {
-        when:
-        configurer.configure(LogLevel.DEBUG)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
-        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy
new file mode 100755
index 0000000..560cda4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.logging.internal;
+
+
+import org.gradle.internal.nativeplatform.NoOpTerminalDetector
+import org.gradle.internal.nativeplatform.WindowsTerminalDetector
+import org.gradle.internal.nativeplatform.jna.JnaBootPathConfigurer
+import org.gradle.internal.nativeplatform.jna.LibCBackedTerminalDetector
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Issue
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 9/12/11
+ */
+public class TerminalDetectorFactoryTest extends Specification {
+    @Rule
+    TemporaryFolder temp = new TemporaryFolder()
+
+    @Requires([TestPrecondition.JNA, TestPrecondition.NOT_WINDOWS])
+    def "should configure JNA library"() {
+        when:
+        def spec = new TerminalDetectorFactory().create(new JnaBootPathConfigurer(temp.dir))
+
+        then:
+        spec instanceof LibCBackedTerminalDetector
+    }
+
+    @Requires([TestPrecondition.JNA, TestPrecondition.WINDOWS])
+    def "should configure JNA library on Windows"() {
+        when:
+        def spec = new TerminalDetectorFactory().create(new JnaBootPathConfigurer(temp.dir))
+
+        then:
+        spec instanceof WindowsTerminalDetector
+    }
+
+    @Issue("GRADLE-1776")
+    @Requires(TestPrecondition.NO_JNA)
+    def "should assume no terminal is available when JNA library is not available"() {
+        when:
+        def spec = new TerminalDetectorFactory().create(new JnaBootPathConfigurer(temp.dir))
+
+        then:
+        spec instanceof NoOpTerminalDetector
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurerTest.groovy
new file mode 100644
index 0000000..bd73d0f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurerTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * 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.logging.internal.slf4j
+
+import ch.qos.logback.classic.LoggerContext
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logging
+import org.gradle.logging.internal.OutputEventListener
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+
+class Slf4jLoggingConfigurerTest extends Specification {
+    private final Logger logger = LoggerFactory.getLogger("cat1");
+    private final OutputEventListener listener = Mock()
+    private final Slf4jLoggingConfigurer configurer = new Slf4jLoggingConfigurer(listener)
+    
+    def cleanup() {
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+        lc.reset();
+    }
+
+    def routesSlf4jLogEventsToOutputEventListener() {
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message')
+
+        then:
+        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == null})
+        0 * listener._
+    }
+
+    def includesThrowableInLogEvent() {
+        def failure = new RuntimeException()
+
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message', failure)
+
+        then:
+        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == failure})
+        0 * listener._
+    }
+    
+    def mapsSlf4jLogLevelsToGradleLogLevels() {
+        when:
+        configurer.configure(LogLevel.DEBUG)
+
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
+        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def formatsLogMessage() {
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message {} {}', 'arg1', 'arg2')
+
+        then:
+        1 * listener.onOutput({it.message == 'message arg1 arg2'})
+        0 * listener._
+    }
+
+    def attachesATimestamp() {
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message')
+
+        then:
+        1 * listener.onOutput({it.timestamp >= System.currentTimeMillis() - 300})
+        0 * listener._
+    }
+
+    def filtersLifecycleAndLowerWhenConfiguredAtQuietLevel() {
+        when:
+        configurer.configure(LogLevel.QUIET)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def filtersInfoAndLowerWhenConfiguredAtLifecycleLevel() {
+        when:
+        configurer.configure(LogLevel.LIFECYCLE)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def filtersDebugAndLowerWhenConfiguredAtInfoLevel() {
+        when:
+        configurer.configure(LogLevel.INFO)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def filtersTraceWhenConfiguredAtDebugLevel() {
+        when:
+        configurer.configure(LogLevel.DEBUG)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
+        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
index a445ee2..ec836b5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
@@ -27,6 +27,7 @@ import org.junit.Test
 import org.junit.runner.RunWith
 import static org.junit.Assert.*
 import static org.hamcrest.Matchers.*
+import org.gradle.internal.concurrent.ThreadSafe
 
 @RunWith(JMock.class)
 class DefaultActorFactoryTest extends MultithreadedTestCase {
@@ -40,12 +41,13 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void createsAnActorForATargetObject() {
-        factory.createActor(target) != null
+    public void createsANonBlockingActorForATargetObject() {
+        def actor = factory.createActor(target)
+        assertThat(actor, notNullValue())
     }
 
     @Test
-    public void cachesTheActorForATargetObject() {
+    public void cachesTheNonBlockingActorForATargetObject() {
         Actor actor1 = factory.createActor(target)
         Actor actor2 = factory.createActor(target)
         assertThat(actor2, sameInstance(actor1))
@@ -59,7 +61,79 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void actorDispatchesMethodInvocationToTargetObject() {
+    public void nonBlockingActorAndProxyAreBothMarkedAsThreadSafe() {
+        def actor = factory.createActor(target)
+        assertThat(actor, instanceOf(ThreadSafe))
+        assertThat(actor.getProxy(Runnable), instanceOf(ThreadSafe))
+    }
+
+    @Test
+    public void createsABlockingActorForATargetObject() {
+        def actor = factory.createBlockingActor(target)
+        assertThat(actor, notNullValue())
+    }
+
+    @Test
+    public void cachesTheBlockingActorForATargetObject() {
+        Actor actor1 = factory.createBlockingActor(target)
+        Actor actor2 = factory.createBlockingActor(target)
+        assertThat(actor2, sameInstance(actor1))
+    }
+
+    @Test
+    public void blockingActorAndProxyAreBothMarkedAsThreadSafe() {
+        def actor = factory.createBlockingActor(target)
+        assertThat(actor, instanceOf(ThreadSafe))
+        assertThat(actor.getProxy(Runnable), instanceOf(ThreadSafe))
+    }
+
+    @Test
+    public void blockingActorDispatchesMethodInvocationToTargetObjectAndWaitsForResult() {
+        Actor actor = factory.createBlockingActor(target)
+
+        context.checking {
+            one(target).doStuff('param')
+        }
+
+        actor.dispatch(new MethodInvocation(TargetObject.class.getMethod('doStuff', String.class), ['param'] as Object[]))
+    }
+
+    @Test
+    public void blockingActorProxyDispatchesMethodInvocationToTargetObjectAndWaitsForResult() {
+        Actor actor = factory.createBlockingActor(target)
+
+        context.checking {
+            one(target).doStuff('param')
+        }
+
+        actor.getProxy(TargetObject).doStuff('param')
+    }
+
+    @Test
+    public void blockingActorDispatchesMethodInvocationFromOneThreadAtATime() {
+        Actor actor = factory.createBlockingActor(target)
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                syncAt(1)
+            }
+            one(target).doStuff('param2')
+        }
+        
+        def proxy = actor.getProxy(TargetObject)
+        start {
+            proxy.doStuff('param')
+        }
+        run {
+            expectBlocksUntil(1) {
+                proxy.doStuff('param2')
+            }
+        }
+    }
+
+    @Test
+    public void nonBlockingActorDispatchesMethodInvocationToTargetObjectAndDoesNotWaitForResult() {
         Actor actor = factory.createActor(target)
 
         context.checking {
@@ -76,7 +150,7 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void actorProxyDispatchesMethodCallToTargetObject() {
+    public void nonBlockingActorProxyDispatchesMethodCallToTargetObjectAndDoesNotWaitForResult() {
         Actor actor = factory.createActor(target)
         TargetObject proxy = actor.getProxy(TargetObject.class)
 
@@ -94,7 +168,7 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
     }
 
     @Test
-    public void actorStopPropagatesMethodFailure() {
+    public void nonBlockingActorPropagatesMethodFailuresOnStop() {
         Actor actor = factory.createActor(target)
         TargetObject proxy = actor.getProxy(TargetObject.class)
         RuntimeException failure = new RuntimeException()
@@ -112,14 +186,14 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
                 actor.stop()
                 fail()
             } catch (DispatchException e) {
-                assertThat(e.message, startsWith('Failed to dispatch message'))
+                assertThat(e.message, startsWith("Could not dispatch message"))
                 assertThat(e.cause, sameInstance(failure))
             }
         }
     }
 
     @Test
-    public void actorStopBlocksUntilAllMethodCallsComplete() {
+    public void nonBlockingActorStopBlocksUntilAllMethodCallsComplete() {
         Actor actor = factory.createActor(target)
         TargetObject proxy = actor.getProxy(TargetObject.class)
 
@@ -139,6 +213,28 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
     }
 
     @Test
+    public void blockingActorStopBlocksUntilAllMethodCallsComplete() {
+        Actor actor = factory.createBlockingActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                syncAt(1)
+            }
+        }
+
+        start {
+            proxy.doStuff('param')
+        }
+        run {
+            expectBlocksUntil(1) {
+                actor.stop()
+            }
+        }
+    }
+
+    @Test
     public void factoryStopBlocksUntilAllMethodCallsComplete() {
         Actor actor = factory.createActor(target)
         TargetObject proxy = actor.getProxy(TargetObject.class)
@@ -157,6 +253,34 @@ class DefaultActorFactoryTest extends MultithreadedTestCase {
             }
         }
     }
+    
+    @Test
+    public void cannotDispatchToBlockingActorAfterStopped() {
+        Actor actor = factory.createBlockingActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+
+        actor.stop()
+        try {
+            proxy.doStuff('param')
+            fail()
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void cannotDispatchToNonBlockingActorAfterStopped() {
+        Actor actor = factory.createActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+
+        actor.stop()
+        try {
+            proxy.doStuff('param')
+            fail()
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+    }
 }
 
 interface TargetObject {
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/concurrent/CompositeStoppableTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/concurrent/CompositeStoppableTest.groovy
deleted file mode 100644
index 688c17d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/concurrent/CompositeStoppableTest.groovy
+++ /dev/null
@@ -1,87 +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.messaging.concurrent
-
-import spock.lang.Specification
-
-class CompositeStoppableTest extends Specification {
-    private final CompositeStoppable stoppable = new CompositeStoppable()
-
-    def stopsAllElementsOnStop() {
-        Stoppable a = Mock()
-        Stoppable b = Mock()
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop()
-        1 * b.stop()
-    }
-
-    def stopsAllElementsWhenOneFailsToStop() {
-        Stoppable a = Mock()
-        Stoppable b = Mock()
-        RuntimeException failure = new RuntimeException()
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop() >> { throw failure }
-        1 * b.stop()
-        def e = thrown(RuntimeException)
-        e == failure
-    }
-
-    def stopsAllElementsWhenMultipleFailToStop() {
-        Stoppable a = Mock()
-        Stoppable b = Mock()
-        RuntimeException failure1 = new RuntimeException()
-        RuntimeException failure2 = new RuntimeException()
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.stop() >> { throw failure1 }
-        1 * b.stop() >> { throw failure2 }
-        def e = thrown(RuntimeException)
-        e == failure1
-    }
-
-    def closesACloseableElement() {
-        Closeable a = Mock()
-        Stoppable b = Mock()
-
-        stoppable.add(a)
-        stoppable.add(b)
-
-        when:
-        stoppable.stop()
-
-        then:
-        1 * a.close()
-        1 * b.stop()
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
old mode 100644
new mode 100755
index 50a8deb..958870f
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
@@ -1,212 +1,210 @@
-/*
- * 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.dispatch
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
- at RunWith(JMock.class)
-public class AsyncDispatchTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Dispatch<String> target1 = context.mock(Dispatch.class, "target1")
-    private final Dispatch<String> target2 = context.mock(Dispatch.class, "target2")
-    private final AsyncDispatch<String> dispatch = new AsyncDispatch<String>(executor)
-
-    @Test
-    public void dispatchesMessageToAnIdleTarget() {
-        context.checking {
-            one(target1).dispatch('message1')
-            one(target1).dispatch('message2')
-        }
-
-        dispatch.dispatchTo(target1)
-
-        dispatch.dispatch('message1')
-        dispatch.dispatch('message2')
-
-        dispatch.stop()
-    }
-
-    @Test
-    public void dispatchDoesNotBlockWhileNoIdleTargetAvailable() {
-        context.checking {
-            one(target1).dispatch('message1')
-            will {
-                syncAt(1)
-                syncAt(2)
-            }
-            one(target2).dispatch('message2')
-            will {
-                syncAt(2)
-                syncAt(3)
-            }
-            one(target1).dispatch('message3')
-            will {
-                syncAt(3)
-            }
-        }
-
-        run {
-            dispatch.dispatchTo(target1)
-            dispatch.dispatch('message1')
-            syncAt(1)
-
-            dispatch.dispatchTo(target2)
-            dispatch.dispatch('message2')
-            syncAt(2)
-
-            dispatch.dispatch('message3')
-            syncAt(3)
-        }
-
-        dispatch.stop()
-    }
-
-    @Test
-    public void canStopFromMultipleThreads() {
-        dispatch.dispatchTo(target1)
-
-        start {
-            dispatch.stop()
-        }
-        start {
-            dispatch.stop()
-        }
-    }
-
-    @Test
-    public void canRequestStopFromMultipleThreads() {
-        dispatch.dispatchTo(target1)
-
-        start {
-            dispatch.requestStop()
-        }
-        start {
-            dispatch.requestStop()
-        }
-
-        waitForAll()
-        dispatch.stop()
-    }
-
-    @Test
-    public void stopBlocksUntilAllMessagesDispatched() {
-        context.checking {
-            one(target1).dispatch('message1')
-            will {
-                syncAt(1)
-                syncAt(2)
-                syncAt(3)
-            }
-        }
-
-        context.checking {
-            one(target2).dispatch('message2')
-            will {
-                syncAt(2)
-                syncAt(3)
-            }
-        }
-
-        run {
-            dispatch.dispatchTo(target1)
-            dispatch.dispatch('message1')
-            syncAt(1)
-
-            dispatch.dispatchTo(target2)
-            dispatch.dispatch('message2')
-            syncAt(2)
-
-            expectBlocksUntil(3) {
-                dispatch.stop()
-            }
-        }
-    }
-
-    @Test
-    public void requestStopDoesNotBlockWhenMessagesAreQueued() {
-        context.checking {
-            one(target1).dispatch('message1')
-            will {
-                syncAt(1)
-                syncAt(2)
-            }
-        }
-
-        run {
-            dispatch.dispatchTo(target1)
-            dispatch.dispatch('message1')
-            syncAt(1)
-            dispatch.requestStop()
-            shouldBeAt(1)
-            syncAt(2)
-        }
-
-        waitForAll()
-        dispatch.stop()
-    }
-
-    @Test
-    public void stopFailsWhenNoTargetsAvailableToDeliverQueuedMessages() {
-        dispatch.dispatch('message1')
-        try {
-            dispatch.stop()
-            fail()
-        } catch (IllegalStateException e) {
-            assertThat(e.message, equalTo('Cannot wait for messages to be dispatched, as there are no dispatch threads running.'))
-        }
-    }
-
-    @Test
-    public void stopFailsWhenAllTargetsHaveFailed() {
-        context.checking {
-            one(target1).dispatch('message1')
-            will {
-                RuntimeException failure = new RuntimeException()
-                willFailWith(sameInstance(failure))
-                throw failure
-            }
-        }
-        dispatch.dispatchTo(target1)
-        dispatch.dispatch('message1')
-        dispatch.dispatch('message2')
-
-        try {
-            dispatch.stop()
-            fail()
-        } catch (IllegalStateException e) {
-            assertThat(e.message, equalTo('Cannot wait for messages to be dispatched, as there are no dispatch threads running.'))
-        }
-    }
-    
-    @Test
-    public void cannotDispatchMessagesAfterStop() {
-        dispatch.stop()
-        try {
-            dispatch.dispatch('message')
-            fail()
-        } catch (IllegalStateException e) {
-            assertThat(e.message, equalTo('This message dispatch has been stopped.'))
-        }
-    }
-}
+/*
+ * 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.dispatch
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(JMock.class)
+public class AsyncDispatchTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Dispatch<String> target1 = context.mock(Dispatch.class, "target1")
+    private final Dispatch<String> target2 = context.mock(Dispatch.class, "target2")
+    private final AsyncDispatch<String> dispatch = new AsyncDispatch<String>(executor)
+
+    @Test
+    public void dispatchesMessageToAnIdleTarget() {
+        context.checking {
+            one(target1).dispatch('message1')
+            one(target1).dispatch('message2')
+        }
+
+        dispatch.dispatchTo(target1)
+
+        dispatch.dispatch('message1')
+        dispatch.dispatch('message2')
+
+        dispatch.stop()
+    }
+
+    @Test
+    public void dispatchDoesNotBlockWhileNoIdleTargetAvailable() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                syncAt(1)
+                syncAt(2)
+            }
+            one(target2).dispatch('message2')
+            will {
+                syncAt(2)
+                syncAt(3)
+            }
+            one(target1).dispatch('message3')
+            will {
+                syncAt(3)
+            }
+        }
+
+        run {
+            dispatch.dispatchTo(target1)
+            dispatch.dispatch('message1')
+            syncAt(1)
+
+            dispatch.dispatchTo(target2)
+            dispatch.dispatch('message2')
+            syncAt(2)
+
+            dispatch.dispatch('message3')
+            syncAt(3)
+        }
+
+        dispatch.stop()
+    }
+
+    @Test
+    public void canStopFromMultipleThreads() {
+        dispatch.dispatchTo(target1)
+
+        start {
+            dispatch.stop()
+        }
+        start {
+            dispatch.stop()
+        }
+    }
+
+    @Test
+    public void canRequestStopFromMultipleThreads() {
+        dispatch.dispatchTo(target1)
+
+        start {
+            dispatch.requestStop()
+        }
+        start {
+            dispatch.requestStop()
+        }
+
+        waitForAll()
+        dispatch.stop()
+    }
+
+    @Test
+    public void stopBlocksUntilAllMessagesDispatched() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                syncAt(1)
+                syncAt(2)
+                syncAt(3)
+            }
+        }
+
+        context.checking {
+            one(target2).dispatch('message2')
+            will {
+                syncAt(2)
+                syncAt(3)
+            }
+        }
+
+        run {
+            dispatch.dispatchTo(target1)
+            dispatch.dispatch('message1')
+            syncAt(1)
+
+            dispatch.dispatchTo(target2)
+            dispatch.dispatch('message2')
+            syncAt(2)
+
+            expectBlocksUntil(3) {
+                dispatch.stop()
+            }
+        }
+    }
+
+    @Test
+    public void requestStopDoesNotBlockWhenMessagesAreQueued() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                syncAt(1)
+                syncAt(2)
+            }
+        }
+
+        run {
+            dispatch.dispatchTo(target1)
+            dispatch.dispatch('message1')
+            syncAt(1)
+            dispatch.requestStop()
+            shouldBeAt(1)
+            syncAt(2)
+        }
+
+        waitForAll()
+        dispatch.stop()
+    }
+
+    @Test
+    public void stopFailsWhenNoTargetsAvailableToDeliverQueuedMessages() {
+        dispatch.dispatch('message1')
+        try {
+            dispatch.stop()
+            fail()
+        } catch (IllegalStateException e) {
+            assertThat(e.message, equalTo('Cannot wait for messages to be dispatched, as there are no dispatch threads running.'))
+        }
+    }
+
+    @Test
+    public void stopFailsWhenAllTargetsHaveFailed() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                RuntimeException failure = new RuntimeException()
+                willFailWith(sameInstance(failure))
+                throw failure
+            }
+        }
+        dispatch.dispatchTo(target1)
+        dispatch.dispatch('message1')
+        dispatch.dispatch('message2')
+
+        try {
+            dispatch.stop()
+            fail()
+        } catch (IllegalStateException e) {
+            assertThat(e.message, equalTo('Cannot wait for messages to be dispatched, as there are no dispatch threads running.'))
+        }
+    }
+    
+    @Test
+    public void cannotDispatchMessagesAfterStop() {
+        dispatch.stop()
+        try {
+            dispatch.dispatch('message')
+            fail()
+        } catch (IllegalStateException e) {
+            assertThat(e.message, equalTo('Cannot dispatch message, as this message dispatch has been stopped. Message: message'))
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
index ffd29d2..ab342dd 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
@@ -1,122 +1,142 @@
-/*
- * 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.dispatch
-
-import static org.hamcrest.Matchers.*
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.Sequence
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.*
-
- at RunWith(JMock.class)
-public class AsyncReceiveTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Dispatch<String> target1 = context.mock(Dispatch.class, "target1")
-    private final Dispatch<String> target2 = context.mock(Dispatch.class, "target2")
-    private final Receive<String> source1 = context.mock(Receive.class, "source1")
-    private final AsyncReceive<String> dispatch = new AsyncReceive<String>(executor, target1)
-
-    @Test
-    public void dispatchesReceivedMessagesToTargetUntilEndOfStreamReached() {
-        clockTick(1).hasParticipants(2)
-
-        context.checking {
-            Sequence receive = context.sequence('receive')
-            Sequence dispatch = context.sequence('dispatch')
-
-            one(source1).receive()
-            will(returnValue('message1'))
-            inSequence(receive)
-
-            one(source1).receive()
-            will(returnValue('message2'))
-            inSequence(receive)
-
-            one(source1).receive()
-            inSequence(receive)
-            will {
-                syncAt(1)
-                return null
-            }
-
-            one(target1).dispatch('message1')
-            inSequence(dispatch)
-
-            one(target1).dispatch('message2')
-            inSequence(dispatch)
-        }
-
-        dispatch.receiveFrom(source1)
-
-        run {
-            syncAt(1)
-        }
-        
-        dispatch.stop()
-    }
-
-    @Test
-    public void stopBlocksUntilAllReceiveCallsHaveReturned() {
-        context.checking {
-            one(source1).receive()
-            will {
-                syncAt(1)
-                return 'message'
-            }
-            one(target1).dispatch('message')
-            will {
-                syncAt(2)
-            }
-        }
-
-        run {
-            dispatch.receiveFrom(source1)
-            syncAt(1)
-            expectBlocksUntil(2) {
-                dispatch.stop()
-            }
-        }
-    }
-
-    @Test
-    public void requestStopDoesNotBlock() {
-        context.checking {
-            one(source1).receive()
-            will {
-                syncAt(1)
-                syncAt(2)
-                return 'message'
-            }
-            one(target1).dispatch('message')
-        }
-
-        run {
-            dispatch.receiveFrom(source1)
-            syncAt(1)
-            dispatch.requestStop()
-            syncAt(2)
-        }
-
-        dispatch.stop()
-    }
-}
+/*
+ * 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.dispatch
+
+import org.gradle.util.ConcurrentSpecification
+
+public class AsyncReceiveTest extends ConcurrentSpecification {
+    private final Dispatch<String> target1 = Mock()
+    private final Dispatch<String> target2 = Mock()
+    private final Receive<String> source1 = Mock()
+    private final Receive<String> source2 = Mock()
+    private final AsyncReceive<String> dispatch = new AsyncReceive<String>(executor)
+
+    def cleanup() {
+        dispatch?.stop()
+    }
+
+    def "dispatches message to target until end of stream reached"() {
+        def endOfStream = startsAsyncAction()
+
+        when:
+        endOfStream.started {
+            dispatch.dispatchTo(target1)
+            dispatch.receiveFrom(source1)
+        }
+        finished()
+
+        then:
+        3 * source1.receive() >>> ['message1', 'message2', null]
+        1 * target1.dispatch('message1')
+        1 * target1.dispatch('message2') >> { endOfStream.done() }
+    }
+
+    def "can receive from multiple sources"() {
+        def endOfStream = startsAsyncAction()
+
+        when:
+        endOfStream.started {
+            dispatch.dispatchTo(target1)
+            dispatch.receiveFrom(source1)
+            dispatch.receiveFrom(source2)
+        }
+        finished()
+
+        then:
+        2 * source1.receive() >>> ['message1', null]
+        2 * source2.receive() >>> ['message2', null]
+        1 * target1.dispatch('message1')
+        1 * target1.dispatch('message2') >> { endOfStream.done() }
+    }
+
+    def "receive waits until dispatch available"() {
+        def received = startsAsyncAction()
+
+        when:
+        received.started {
+            dispatch.receiveFrom(source1)
+            dispatch.dispatchTo(target1)
+        }
+        finished()
+
+        then:
+        3 * source1.receive() >>> ['message1', 'message2', null]
+        1 * target1.dispatch('message1')
+        1 * target1.dispatch('message2') >> { received.done() }
+    }
+
+    def "can dispatch to multiple targets"() {
+        def received = startsAsyncAction()
+
+        when:
+        received.started {
+            dispatch.receiveFrom(source1)
+            dispatch.dispatchTo(target1)
+            dispatch.dispatchTo(target2)
+        }
+        finished()
+
+        then:
+        3 * source1.receive() >>> ['message1', 'message2', null]
+        1 * _.dispatch('message1')
+        1 * _.dispatch('message2') >> { received.done() }
+        0 * target1._
+        0 * target2._
+    }
+
+    def "stop blocks until all receive calls have completed"() {
+        def receiving = startsAsyncAction()
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        receiving.started {
+            dispatch.dispatchTo(target1)
+            dispatch.receiveFrom(source1)
+        }
+        stopped.start {
+            dispatch.stop()
+        }
+
+        then:
+        1 * source1.receive() >> { receiving.done(); stopped.done(); return null }
+    }
+
+    def "stop blocks until all dispatch calls have completed"() {
+        def receiving = startsAsyncAction()
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        receiving.started {
+            dispatch.dispatchTo(target1)
+            dispatch.receiveFrom(source1)
+        }
+        stopped.start {
+            dispatch.stop()
+        }
+
+        then:
+        1 * source1.receive() >>> ['message', null]
+        1 * target1.dispatch('message') >> { receiving.done(); stopped.done() }
+    }
+
+    def "can stop when no dispatch provided"() {
+        given:
+        dispatch.receiveFrom(source1)
+
+        expect:
+        dispatch.stop()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatchTest.groovy
deleted file mode 100644
index 06f1aab..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatchTest.groovy
+++ /dev/null
@@ -1,40 +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.messaging.dispatch
-
-import org.gradle.api.Action
-import spock.lang.Specification
-
-class ExceptionTrackingDispatchTest extends Specification {
-    private final Dispatch<String> target = Mock()
-    private final Action<RuntimeException> action = Mock()
-    private final ExceptionTrackingDispatch<String> dispatch = new ExceptionTrackingDispatch<String>(target, action)
-
-    def executesActionOnDispatchFailure() {
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        dispatch.dispatch('message')
-
-        then:
-        1 * target.dispatch('message') >> { throw failure }
-        1 * action.execute(!null) >> { args ->
-            assert args[0] instanceof DispatchException
-            assert args[0].cause == failure
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy
new file mode 100644
index 0000000..ba34d9b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.dispatch
+
+import org.slf4j.Logger
+import spock.lang.Specification
+
+class ExceptionTrackingFailureHandlerTest extends Specification {
+    private final Logger logger = Mock()
+    private final ExceptionTrackingFailureHandler dispatch = new ExceptionTrackingFailureHandler(logger)
+
+    def stopRethrowsFailure() {
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        dispatch.dispatchFailed("message", failure)
+        dispatch.stop()
+
+        then:
+        def e = thrown(DispatchException)
+        e.cause == failure
+        0 * logger._
+    }
+
+    def logsAnySubsequentFailures() {
+        RuntimeException failure1 = new RuntimeException()
+        RuntimeException failure2 = new RuntimeException('broken2')
+
+        when:
+        dispatch.dispatchFailed("message1", failure1)
+        dispatch.dispatchFailed("message2", failure2)
+        dispatch.stop()
+
+        then:
+        def e = thrown(DispatchException)
+        e.cause == failure1
+        1 * logger.error('broken2', failure2)
+        0 * logger._
+    }
+
+    def stopDoesNothingWhenThereWereNoFailures() {
+        when:
+        dispatch.stop()
+
+        then:
+        0 * logger._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListenerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListenerTest.groovy
deleted file mode 100644
index 32e6d11..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListenerTest.groovy
+++ /dev/null
@@ -1,61 +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.messaging.dispatch
-
-import org.slf4j.Logger
-import spock.lang.Specification
-
-class ExceptionTrackingListenerTest extends Specification {
-    private final Logger logger = Mock()
-    private final ExceptionTrackingListener dispatch = new ExceptionTrackingListener(logger)
-
-    def stopRethrowsFailure() {
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        dispatch.execute(failure)
-        dispatch.stop()
-
-        then:
-        def e = thrown(RuntimeException)
-        e == failure
-        0 * logger._
-    }
-
-    def logsAnySubsequentFailures() {
-        RuntimeException failure1 = new RuntimeException()
-        RuntimeException failure2 = new RuntimeException('broken2')
-
-        when:
-        dispatch.execute(failure1)
-        dispatch.execute(failure2)
-        dispatch.stop()
-
-        then:
-        def e = thrown(RuntimeException)
-        e == failure1
-        1 * logger.error('broken2', failure2)
-        0 * logger._
-    }
-
-    def stopDoesNothingWhenThereWereNoFailures() {
-        when:
-        dispatch.stop()
-
-        then:
-        0 * logger._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy
new file mode 100644
index 0000000..77f0c3c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.dispatch
+
+import spock.lang.Specification
+
+class FailureHandlingDispatchTest extends Specification {
+    final Dispatch<String> target = Mock()
+    final DispatchFailureHandler<String> handler = Mock()
+    final FailureHandlingDispatch<String> dispatch = new FailureHandlingDispatch<String>(target, handler)
+
+    def "dispatches message to target"() {
+        when:
+        dispatch.dispatch("message")
+
+        then:
+        1 * target.dispatch("message")
+    }
+
+    def "notifies handler on failure"() {
+        def failure = new RuntimeException()
+
+        when:
+        dispatch.dispatch("message")
+
+        then:
+        1 * target.dispatch("message") >> { throw failure }
+        1 * handler.dispatchFailed("message", failure)
+    }
+
+    def "propagates exception thrown by handler"() {
+        def failure = new RuntimeException()
+        def adaptedFailure = new RuntimeException()
+
+        when:
+        dispatch.dispatch("message")
+
+        then:
+        RuntimeException e = thrown()
+        e == adaptedFailure
+        1 * target.dispatch("message") >> { throw failure }
+        1 * handler.dispatchFailed("message", failure) >> { throw adaptedFailure }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java
old mode 100644
new mode 100755
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
index 6ac9e26..f32b435 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
@@ -21,7 +21,7 @@ import static org.gradle.util.Matchers.*
 
 class ProxyDispatchAdapterTest extends Specification {
     private final Dispatch<MethodInvocation> dispatch = Mock()
-    private final ProxyDispatchAdapter<ProxyTest> adapter = new ProxyDispatchAdapter<ProxyTest>(ProxyTest.class, dispatch)
+    private final ProxyDispatchAdapter<ProxyTest> adapter = new ProxyDispatchAdapter<ProxyTest>(dispatch, ProxyTest.class)
 
     def proxyForwardsToDispatch() {
         when:
@@ -32,9 +32,9 @@ class ProxyDispatchAdapterTest extends Specification {
     }
     
     def proxyIsEqualWhenItHasTheSameTypeAndDispatch() {
-        def other = new ProxyDispatchAdapter<ProxyTest>(ProxyTest.class, dispatch)
-        def differentType = new ProxyDispatchAdapter<Runnable>(Runnable.class, dispatch)
-        def differentDispatch = new ProxyDispatchAdapter<ProxyTest>(ProxyTest.class, Mock(Dispatch.class))
+        def other = new ProxyDispatchAdapter<ProxyTest>(dispatch, ProxyTest.class)
+        def differentType = new ProxyDispatchAdapter<Runnable>(dispatch, Runnable.class)
+        def differentDispatch = new ProxyDispatchAdapter<ProxyTest>(Mock(Dispatch.class), ProxyTest.class)
 
         expect:
         ProxyTest proxy = adapter.getSource()
@@ -42,6 +42,18 @@ class ProxyDispatchAdapterTest extends Specification {
         proxy != differentType.getSource()
         proxy != differentDispatch.getSource()
     }
+    
+    def canProxyMultipleTypesFromMixedClassLoaders() {
+        assert Runnable.classLoader != ProxyTest.classLoader
+        def adapter1 = new ProxyDispatchAdapter<Runnable>(dispatch, Runnable, ProxyTest).source
+        def adapter2 = new ProxyDispatchAdapter<ProxyTest>(dispatch, ProxyTest, Runnable).source
+
+        expect:
+        adapter1 instanceof Runnable
+        adapter1 instanceof ProxyTest
+        adapter2 instanceof Runnable
+        adapter2 instanceof ProxyTest
+    }
 }
 
 interface ProxyTest {
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy
new file mode 100644
index 0000000..7564587
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.dispatch
+
+import org.gradle.util.ConcurrentSpecification
+import spock.lang.Ignore
+
+class QueuingDispatchTest extends ConcurrentSpecification {
+    final Dispatch<String> target = Mock()
+    final QueuingDispatch<String> dispatch = new QueuingDispatch<String>()
+
+    def "queues messages until receiving dispatch connected"() {
+        when:
+        dispatch.dispatch("a")
+        dispatch.dispatch("b")
+
+        then:
+        0 * target._
+
+        when:
+        dispatch.dispatchTo(target)
+
+        then:
+        1 * target.dispatch("a")
+        1 * target.dispatch("b")
+        0 * target._
+    }
+
+    def "dispatches messages directly to receiving dispatch after it has connected"() {
+        given:
+        dispatch.dispatchTo(target)
+
+        when:
+        dispatch.dispatch("a")
+
+        then:
+        1 * target.dispatch("a")
+        0 * target._
+    }
+
+    @Ignore
+    def "stop blocks until queued messages delivered to receiving dispatch"() {
+        expect: false
+    }
+
+    @Ignore
+    def "stop blocks until message delivered to receiving dispatch"() {
+        expect: false
+    }
+
+    @Ignore
+    def "stop does not block if no messages have been dispatched"() {
+        expect: false
+    }
+
+    @Ignore
+    def "cannot connect receiving dispatch after stop"() {
+        expect: false
+    }
+
+    @Ignore
+    def "delivers messages by a single thread at a time"() {
+        expect: false
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy
new file mode 100644
index 0000000..ee1438e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch
+import org.gradle.messaging.dispatch.DispatchFailureHandler
+import org.gradle.util.ConcurrentSpecification
+import spock.lang.Ignore
+
+ at Ignore
+class AsyncConnectionAdapterTest extends ConcurrentSpecification {
+    final Connection<String> connection = Mock()
+    final Dispatch<String> incoming = Mock()
+    final DispatchFailureHandler<String> failureHandler = Mock()
+    AsyncConnectionAdapter asyncConnection
+
+    def cleanup() {
+        asyncConnection?.stop()
+    }
+
+    def "dispatches messages to the connection"() {
+        def dispatched = startsAsyncAction()
+
+        when:
+        asyncConnection = new AsyncConnectionAdapter(connection, failureHandler, executorFactory)
+        dispatched.started {
+            asyncConnection.dispatch("message")
+        }
+
+        then:
+        1 * connection.dispatch("message") >> { dispatched.done() }
+    }
+
+    def "starts receiving messages when dispatchTo() called"() {
+        when:
+        asyncConnection = new AsyncConnectionAdapter(connection, failureHandler, executorFactory)
+        asyncConnection.dispatchTo(incoming)
+
+        then:
+        2 * connection.receive() >>> ["message", null]
+        1 * incoming.dispatch("message")
+        0 * incoming._
+    }
+
+    def "stops connection at end of receive stream"() {
+        when:
+        asyncConnection = new AsyncConnectionAdapter(connection, failureHandler, executorFactory)
+        asyncConnection.dispatchTo(incoming)
+
+        then:
+        1 * connection.receive() >> null
+        1 * connection.stop()
+        0 * incoming._
+    }
+
+    def "stop blocks until all outgoing messages dispatched"() {
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            asyncConnection.dispatch("message")
+            asyncConnection.stop()
+        }
+
+        then:
+        1 * connection.dispatch("message") >> { stopped.done() }
+    }
+
+    def "stop blocks until all incoming messages dispatched"() {
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            asyncConnection.dispatchTo(incoming)
+            asyncConnection.stop()
+        }
+
+        then:
+        2 * connection.receive() >>> ["message", null]
+        1 * incoming.dispatch("message") >> { stopped.done() }
+    }
+
+    def "stops connection on stop"() {
+        when:
+        asyncConnection.stop()
+
+        then:
+        1 * connection.stop()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy
new file mode 100644
index 0000000..ab01369
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.Request
+import org.gradle.messaging.remote.internal.protocol.ConsumerAvailable
+import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable
+import java.util.concurrent.TimeUnit
+
+class BroadcastSendProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final BroadcastSendProtocol protocol = new BroadcastSendProtocol()
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "queues outgoing messages until a consumer is available"() {
+        when:
+        protocol.handleOutgoing(new Request("channel", "message1"))
+        protocol.handleOutgoing(new Request("channel", "message2"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request("id", "message1"))
+        1 * context.dispatchOutgoing(new Request("id", "message2"))
+        0 * context._
+    }
+
+    def "dispatches outgoing message to each consumer"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("id1", "display", "channel"))
+        protocol.handleIncoming(new ConsumerAvailable("id2", "display", "channel"))
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request("id1", "message"))
+        1 * context.dispatchOutgoing(new Request("id2", "message"))
+        0 * context._
+    }
+
+    def "stops dispatching to a consumer when it becomes unavailable"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable("id"))
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        0 * context._
+    }
+
+    def "stop waits until for a consumer to become available and queued messages dispatched"() {
+        given:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopLater()
+        1 * context.callbackLater(5, TimeUnit.SECONDS, !null)
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request("id", "message"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stop waits until timeout for a consumer to become available and queued messages dispatched"() {
+        Runnable callback
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+        protocol.stopRequested()
+
+        then:
+        1 * context.callbackLater(5, TimeUnit.SECONDS, !null) >> { callback = it[2]; return null }
+
+        when:
+        callback.run()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when no messages have been dispatched"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when all messages have been dispatched"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy
new file mode 100644
index 0000000..1915ce8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.MessageCredits
+import spock.lang.Specification
+
+class BufferingProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final BufferingProtocol protocol = new BufferingProtocol(6)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "dispatches outgoing request credits on start"() {
+        when:
+        protocol.start(context)
+
+        then:
+        1 * context.dispatchOutgoing(new MessageCredits(6))
+        0 * context._
+    }
+
+    def "queues incoming message until outgoing credits received"() {
+        Message message = Mock()
+
+        when:
+        protocol.handleIncoming(message)
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new MessageCredits(1))
+
+        then:
+        1 * context.dispatchIncoming(message)
+        0 * context._
+    }
+
+    def "queues multiple incoming message until outgoing credits received"() {
+        Message message1 = Mock()
+        Message message2 = Mock()
+        Message message3 = Mock()
+
+        when:
+        protocol.handleIncoming(message1)
+        protocol.handleIncoming(message2)
+        protocol.handleIncoming(message3)
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new MessageCredits(2))
+
+        then:
+        1 * context.dispatchIncoming(message1)
+        1 * context.dispatchIncoming(message2)
+        0 * context._
+    }
+
+    def "dispatches incoming messages until already received credits used"() {
+        Message message1 = Mock()
+        Message message2 = Mock()
+        Message message3 = Mock()
+
+        given:
+        protocol.handleOutgoing(new MessageCredits(2))
+
+        when:
+        protocol.handleIncoming(message1)
+        protocol.handleIncoming(message2)
+        protocol.handleIncoming(message3)
+
+        then:
+        1 * context.dispatchIncoming(message1)
+        1 * context.dispatchIncoming(message2)
+        0 * context._
+    }
+
+    def "dispatches outgoing request credits when most incoming messages received and no rooom in queue for extra messages"() {
+        Message message1 = Mock()
+        Message message2 = Mock()
+        Message message3 = Mock()
+
+        when:
+        protocol.handleIncoming(message1)
+        protocol.handleIncoming(message2)
+        protocol.handleIncoming(message3)
+
+        then:
+        0 * context.dispatchOutgoing(_)
+
+        when:
+        protocol.handleOutgoing(new MessageCredits(3))
+
+        then:
+        1 * context.dispatchOutgoing(new MessageCredits(3))
+        0 * context.dispatchOutgoing(_)
+    }
+
+    def "dispatches outgoing request credits when most incoming messages received and queue is partially full"() {
+        Message message1 = Mock()
+        Message message2 = Mock()
+        Message message3 = Mock()
+
+        given:
+        protocol.handleOutgoing(new MessageCredits(1))
+        protocol.handleIncoming(message1)
+        protocol.handleIncoming(message2)
+
+        when:
+        protocol.handleIncoming(message3)
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new MessageCredits(2))
+
+        then:
+        1 * context.dispatchOutgoing(new MessageCredits(3))
+        0 * context.dispatchOutgoing(_)
+    }
+
+    def "dispatches outgoing request credits when credits granted"() {
+        Message message1 = Mock()
+        Message message2 = Mock()
+
+        given:
+        protocol.handleIncoming(message1)
+        protocol.handleIncoming(message2)
+
+        when:
+        protocol.handleOutgoing(new MessageCredits(8))
+
+        then:
+        1 * context.dispatchOutgoing(new MessageCredits(8))
+        0 * context.dispatchOutgoing(_)
+    }
+
+    def "stop waits until queued messages dispatched"() {
+        Message message1 = Mock()
+        Message message2 = Mock()
+
+        given:
+        protocol.handleIncoming(message1)
+        protocol.handleIncoming(message2)
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new MessageCredits(2))
+
+        then:
+        1 * context.dispatchIncoming(message1)
+        1 * context.dispatchIncoming(message2)
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when no messages queued"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy
new file mode 100644
index 0000000..1db4381
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.DiscoveryMessage
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.LookupRequest
+import org.gradle.messaging.remote.internal.protocol.ChannelAvailable
+import org.gradle.messaging.remote.Address
+import java.util.concurrent.TimeUnit
+import org.gradle.messaging.remote.internal.protocol.ChannelUnavailable
+
+class ChannelLookupProtocolTest extends Specification {
+    final Address address = Mock()
+    final MessageOriginator messageSource = Mock()
+    final ProtocolContext.Callback timeout = Mock()
+    final ProtocolContext<DiscoveryMessage> context = Mock()
+    final ChannelLookupProtocol protocol = new ChannelLookupProtocol()
+
+    def setup() {
+        protocol.start(context)
+        _ * context.callbackLater(_, _, _) >> timeout
+    }
+
+    def "forwards lookup request"() {
+        def request = new LookupRequest(messageSource, "group", "channel")
+
+        when:
+        protocol.handleOutgoing(request)
+
+        then:
+        1 * context.dispatchOutgoing(request)
+    }
+
+    def "forwards channel available response"() {
+        def request = new LookupRequest(messageSource, "group", "channel")
+        def response = new ChannelAvailable(messageSource, "group", "channel", address)
+
+        given:
+        protocol.handleOutgoing(request)
+
+        when:
+        protocol.handleIncoming(response)
+
+        then:
+        1 * context.dispatchIncoming(response)
+        0 * context._
+    }
+
+    def "resends lookup request if no response received within timeout"() {
+        def request = new LookupRequest(messageSource, "group", "channel")
+        def callback
+
+        when:
+        protocol.handleOutgoing(request)
+
+        then:
+        1 * context.callbackLater(1, TimeUnit.SECONDS, !null) >> { callback = it[2]; return timeout }
+
+        when:
+        callback.run()
+
+        then:
+        1 * context.dispatchOutgoing(request)
+        1 * context.callbackLater(1, TimeUnit.SECONDS, callback)
+        0 * context._
+    }
+
+    def "cancels timeout when response received"() {
+        def request = new LookupRequest(messageSource, "group", "channel")
+        def response = new ChannelAvailable(messageSource, "group", "channel", address)
+
+        when:
+        protocol.handleOutgoing(request)
+
+        then:
+        1 * context.callbackLater(1, TimeUnit.SECONDS, !null) >> { return timeout }
+
+        when:
+        protocol.handleIncoming(response)
+
+        then:
+        1 * timeout.cancel()
+    }
+
+    def "forwards each channel available message received"() {
+        final Address address2 = Mock()
+        def request = new LookupRequest(messageSource, "group", "channel")
+        def response1 = new ChannelAvailable(messageSource, "group", "channel", address)
+        def response2 = new ChannelAvailable(messageSource, "group", "channel", address2)
+
+        given:
+        protocol.handleOutgoing(request)
+
+        when:
+        protocol.handleIncoming(response1)
+        protocol.handleIncoming(response2)
+
+        then:
+        1 * context.dispatchIncoming(response1)
+        1 * context.dispatchIncoming(response2)
+        0 * context._
+    }
+
+    def "ignores message for unknown channel"() {
+        when:
+        MessageOriginator messageSource = Mock()
+        protocol.handleIncoming(message)
+
+        then:
+        0 * context._
+
+        where:
+        message << [
+                new ChannelAvailable(messageSource, "group", "channel", {} as Address),
+                new ChannelUnavailable(messageSource, "group", "channel", {} as Address)
+        ]
+    }
+
+    def "ignores lookup requests"() {
+        when:
+        protocol.handleIncoming(new LookupRequest(messageSource, "group", "channel"))
+
+        then:
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
deleted file mode 100644
index ad7843d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
+++ /dev/null
@@ -1,76 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class ChannelMessageMarshallingDispatchTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Dispatch<Object> delegate = context.mock(Dispatch.class);
-    private final ChannelMessageMarshallingDispatch dispatch = new ChannelMessageMarshallingDispatch(delegate);
-
-    @Test
-    public void mapsChannelKeyToIntegerChannelId() {
-        final Message message1 = new TestMessage();
-        final Message message2 = new TestMessage();
-
-        context.checking(new Expectations() {{
-            one(delegate).dispatch(new ChannelMetaInfo("channel", 0));
-            one(delegate).dispatch(new ChannelMessage(0, message1));
-            one(delegate).dispatch(new ChannelMessage(0, message2));
-        }});
-
-        dispatch.dispatch(new ChannelMessage("channel", message1));
-        dispatch.dispatch(new ChannelMessage("channel", message2));
-    }
-
-    @Test
-    public void mapsMultipleChannelsToDifferentIds() {
-        final Message message1 = new TestMessage();
-        final Message message2 = new TestMessage();
-
-        context.checking(new Expectations() {{
-            one(delegate).dispatch(new ChannelMetaInfo("channel1", 0));
-            one(delegate).dispatch(new ChannelMessage(0, message1));
-            one(delegate).dispatch(new ChannelMetaInfo("channel2", 1));
-            one(delegate).dispatch(new ChannelMessage(1, message2));
-        }});
-
-        dispatch.dispatch(new ChannelMessage("channel1", message1));
-        dispatch.dispatch(new ChannelMessage("channel2", message2));
-    }
-
-    @Test
-    public void forwardsUnknownMessages() {
-        final Message message = new TestMessage();
-
-        context.checking(new Expectations() {{
-            one(delegate).dispatch(message);
-        }});
-
-        dispatch.dispatch(message);
-    }
-
-    private static class TestMessage extends Message {
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
deleted file mode 100644
index 7646be8..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
+++ /dev/null
@@ -1,76 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class ChannelMessageUnmarshallingDispatchTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Dispatch<Object> delegate = context.mock(Dispatch.class);
-    private final ChannelMessageUnmarshallingDispatch dispatch = new ChannelMessageUnmarshallingDispatch(delegate);
-
-    @Test
-    public void mapsChannelIdToChannelKey() {
-        final Message message1 = new TestMessage();
-        final Message message2 = new TestMessage();
-
-        context.checking(new Expectations() {{
-            one(delegate).dispatch(new ChannelMessage("channel", message1));
-            one(delegate).dispatch(new ChannelMessage("channel", message2));
-        }});
-
-        dispatch.dispatch(new ChannelMetaInfo("channel", 12));
-        dispatch.dispatch(new ChannelMessage(12, message1));
-        dispatch.dispatch(new ChannelMessage(12, message2));
-    }
-
-    @Test
-    public void mapsMultipleChannelsToDifferentKeys() {
-        final Message message1 = new TestMessage();
-        final Message message2 = new TestMessage();
-
-        context.checking(new Expectations() {{
-            one(delegate).dispatch(new ChannelMessage("channel1", message1));
-            one(delegate).dispatch(new ChannelMessage("channel2", message2));
-        }});
-
-        dispatch.dispatch(new ChannelMetaInfo("channel1", 0));
-        dispatch.dispatch(new ChannelMessage(0, message1));
-        dispatch.dispatch(new ChannelMetaInfo("channel2", 1));
-        dispatch.dispatch(new ChannelMessage(1, message2));
-    }
-
-    @Test
-    public void forwardsUnknownMessages() {
-        final Message message = new TestMessage();
-
-        context.checking(new Expectations() {{
-            one(delegate).dispatch(message);
-        }});
-
-        dispatch.dispatch(message);
-    }
-
-    private static class TestMessage extends Message {
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy
new file mode 100644
index 0000000..6354434
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 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.messaging.remote.Address
+import org.gradle.messaging.remote.internal.protocol.ChannelAvailable
+import org.gradle.messaging.remote.internal.protocol.ChannelUnavailable
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage
+import org.gradle.messaging.remote.internal.protocol.LookupRequest
+import spock.lang.Specification
+
+class ChannelRegistrationProtocolTest extends Specification {
+    final Address address = Mock()
+    final ProtocolContext<DiscoveryMessage> context = Mock()
+    final MessageOriginator messageSource = Mock()
+    final protocol = new ChannelRegistrationProtocol(messageSource)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "forwards channel available message when channel registered"() {
+        def message = new ChannelAvailable(messageSource, "group", "channel", address)
+
+        when:
+        protocol.handleOutgoing(message)
+
+        then:
+        1 * context.dispatchOutgoing(message)
+        0 * context._
+    }
+
+    def "sends channel unavailable message for all available channels when registry stopped"() {
+        def availableMessage = new ChannelAvailable(messageSource, "group", "channel", address)
+        def unavailableMessage = new ChannelUnavailable(messageSource, "group", "channel", address)
+
+        protocol.handleOutgoing(availableMessage)
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(unavailableMessage)
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "sends channel available message when lookup request received"() {
+        def lookupRequest = new LookupRequest(messageSource, "group", "channel")
+        def availableMessage = new ChannelAvailable(messageSource, "group", "channel", address)
+
+        protocol.handleOutgoing(availableMessage)
+
+        when:
+        protocol.handleIncoming(lookupRequest)
+
+        then:
+        1 * context.dispatchOutgoing(availableMessage)
+        0 * context._
+    }
+
+    def "ignores incoming broadcast messages"() {
+        when:
+        MessageOriginator messageSource = Mock()
+        protocol.handleIncoming(message)
+
+        then:
+        0 * context._
+
+        where:
+        message << [
+            new ChannelAvailable(messageSource, "group", "channel", null),
+            new ChannelUnavailable(messageSource, "group", "channel", null)
+        ]
+    }
+
+    def "ignores lookup request for unknown channel"() {
+        when:
+        protocol.handleIncoming(new LookupRequest(messageSource, "group", "other"))
+
+        then:
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
new file mode 100644
index 0000000..a392eb8
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.Address
+import org.gradle.util.Matchers
+
+class CompositeAddressTest extends Specification {
+    def "has useful display name"() {
+        def target = { "<target>" } as Address
+        def qualifier = "<qualifier>"
+        def address = new CompositeAddress(target, qualifier)
+
+        expect:
+        address.displayName == "<target>:<qualifier>"
+        address.toString() == "<target>:<qualifier>"
+    }
+
+    def "equal when address and qualifier are equal"() {
+        def target = { "<target>" } as Address
+        def target2 = { "<target2>" } as Address
+        def address = new CompositeAddress(target, "qualifier")
+        def same = new CompositeAddress(target, "qualifier")
+        def differentAddress = new CompositeAddress(target2, "qualifier")
+        def differentQualifier = new CompositeAddress(target, "other")
+
+        expect:
+        address Matchers.strictlyEqual(same)
+        address != differentAddress
+        address != differentQualifier
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
new file mode 100644
index 0000000..be484d4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.Address
+
+class DefaultMessagingClientTest extends Specification {
+    final MultiChannelConnector connector = Mock()
+    final DefaultMessagingClient client = new DefaultMessagingClient(connector, getClass().classLoader)
+
+    def "creates connection and stops connection on stop"() {
+        Address address = Mock()
+        MultiChannelConnection<Object> connection = Mock()
+
+        when:
+        def result = client.getConnection(address)
+
+        then:
+        1 * connector.connect(address) >> connection
+        result instanceof DefaultObjectConnection
+
+        when:
+        client.stop()
+
+        then:
+        1 * connection.stop()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.java
deleted file mode 100644
index 1efca05..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.java
+++ /dev/null
@@ -1,51 +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.messaging.remote.internal;
-
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.URI;
-
-import static org.hamcrest.Matchers.*;
-
- at RunWith(JMock.class)
-public class DefaultMessagingClientTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final MultiChannelConnector connector = context.mock(MultiChannelConnector.class);
-
-    @Test
-    public void createsConnectionOnConstructionAndStopsOnStop() throws Exception {
-        final URI serverAddress = new URI("test:somestuff");
-        final MultiChannelConnection<Message> connection = context.mock(MultiChannelConnection.class);
-
-        context.checking(new Expectations() {{
-            one(connector).connect(with(equalTo(serverAddress)));
-            will(returnValue(connection));
-        }});
-
-        DefaultMessagingClient client = new DefaultMessagingClient(connector, getClass().getClassLoader(), serverAddress);
-
-        context.checking(new Expectations() {{
-            one(connection).stop();
-        }});
-        client.stop();
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
index e12948f..6099101 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
@@ -22,33 +22,35 @@ import spock.lang.Specification
 import org.gradle.api.Action
 import org.gradle.messaging.remote.ConnectEvent
 import org.gradle.messaging.remote.ObjectConnection
+import org.gradle.messaging.remote.Address
 
 class DefaultMessagingServerTest extends Specification {
+    final Address remoteAddress = Mock()
+    final Address localAddress = Mock()
     private final MultiChannelConnector multiChannelConnector = Mock()
     private final DefaultMessagingServer server = new DefaultMessagingServer(multiChannelConnector, getClass().classLoader)
 
-
     def createsConnection() {
         Action<ConnectEvent<ObjectConnection>> action = Mock()
         Action<ConnectEvent<MultiChannelConnection<Message>>> wrappedAction = Mock()
         MultiChannelConnection<Message> connection = Mock()
 
         when:
-        def uri = server.accept(action)
+        def address = server.accept(action)
 
         then:
-        uri == new URI("test:dest")
-        1 * multiChannelConnector.accept(!null) >> { wrappedAction = it[0]; return new URI("test:dest") }
+        address == localAddress
+        1 * multiChannelConnector.accept(!null) >> { wrappedAction = it[0]; return localAddress }
 
         when:
-        wrappedAction.execute(new ConnectEvent(connection, new URI("test:local"), new URI("test:remote")))
+        wrappedAction.execute(new ConnectEvent(connection, localAddress, remoteAddress))
 
         then:
         1 * action.execute(!null) >> {
             ConnectEvent event = it[0]
             assert event.connection instanceof DefaultObjectConnection
-            assert event.localAddress == new URI("test:local")
-            assert event.remoteAddress == new URI("test:remote")
+            assert event.localAddress == localAddress
+            assert event.remoteAddress == remoteAddress
         }
     }
 
@@ -86,7 +88,7 @@ class DefaultMessagingServerTest extends Specification {
 
         1 * multiChannelConnector.accept(!null) >> {
             def wrappedAction = it[0]
-            wrappedAction.execute(new ConnectEvent(channelConnection, new URI("test:local"), new URI("test:remote")))
+            wrappedAction.execute(new ConnectEvent(channelConnection, localAddress, remoteAddress))
         }
         1 * action.execute(!null) >> {
             connection = it[0].connection
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnectionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnectionTest.groovy
deleted file mode 100644
index 467e024..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnectionTest.groovy
+++ /dev/null
@@ -1,276 +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.messaging.remote.internal
-
-import org.gradle.messaging.dispatch.Dispatch
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-
- at RunWith(JMock.class)
-public class DefaultMultiChannelConnectionTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Connection<Message> target = context.mock(Connection.class)
-    private final TestMessage message = new TestMessage()
-    private DefaultMultiChannelConnection connection
-
-    @Test
-    public void dispatchesOutgoingMessageToTargetConnection() {
-        clockTick(1).hasParticipants(2)
-        context.checking {
-            one(target).receive()
-            will {
-                syncAt(1)
-                return null
-            }
-            one(target).dispatch(new ChannelMetaInfo('channel1', 0))
-            one(target).dispatch(new ChannelMessage(0, message))
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-        run {
-            connection.addOutgoingChannel('channel1').dispatch(message)
-            syncAt(1)
-        }
-
-        connection.stop()
-    }
-
-    @Test
-    public void dispatchesIncomingMessageToHandler() {
-        clockTick(1).hasParticipants(2)
-        Dispatch<Message> handler = context.mock(Dispatch.class)
-        context.checking {
-            one(target).receive()
-            will(returnValue(new ChannelMetaInfo('channel1', 0)))
-            one(target).receive()
-            will(returnValue(new ChannelMessage(0, message)))
-            one(handler).dispatch(message)
-            one(target).receive()
-            will {
-                syncAt(1)
-                return null
-            }
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-
-        run {
-            connection.addIncomingChannel('channel1', handler)
-
-            syncAt(1)
-        }
-
-        connection.stop()
-    }
-
-    @Test
-    public void stuckHandlerDoesNotBlockOtherHandlers() {
-        Dispatch<Message> handler1 = context.mock(Dispatch.class, 'handler1')
-        Dispatch<Message> handler2 = context.mock(Dispatch.class, 'handler2')
-        TestMessage message2 = new TestMessage()
-        clockTick(1).hasParticipants(3)
-        clockTick(2).hasParticipants(3)
-
-        context.checking {
-            one(target).receive()
-            will(returnValue(new ChannelMetaInfo('channel1', 0)))
-            one(target).receive()
-            will(returnValue(new ChannelMessage(0, message)))
-            one(handler1).dispatch(message)
-            will {
-                syncAt(1)
-                syncAt(2)
-            }
-
-            one(target).receive()
-            will(returnValue(new ChannelMetaInfo('channel2', 1)))
-            one(target).receive()
-            will {
-                syncAt(1)
-                return new ChannelMessage(1, message2)
-            }
-            one(handler2).dispatch(message2)
-            will {
-                shouldBeAt(1)
-                syncAt(2)
-            }
-
-            one(target).receive()
-            will {
-                return null
-            }
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-
-        run {
-            connection.addIncomingChannel('channel1', handler1)
-            connection.addIncomingChannel('channel2', handler2)
-
-            syncAt(1)
-            syncAt(2)
-        }
-
-        connection.stop()
-    }
-
-    @Test
-    public void discardsMessageWhenHandlerIsBroken() {
-        clockTick(1).hasParticipants(2)
-        Dispatch<Message> handler = context.mock(Dispatch.class)
-        context.checking {
-            one(target).receive()
-            will(returnValue(new ChannelMetaInfo('channel1', 1)))
-            one(target).receive()
-            will(returnValue(new ChannelMessage(1, message)))
-            one(handler).dispatch(message)
-            will(throwException(new RuntimeException()))
-            one(target).receive()
-            will {
-                syncAt(1)
-                return null
-            }
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-
-        run {
-            connection.addIncomingChannel('channel1', handler)
-
-            syncAt(1)
-        }
-
-        connection.stop()
-    }
-
-    @Test
-    public void queuesIncomingChannelMessageUntilHandlerIsAvailable() {
-        Dispatch<Message> handler = context.mock(Dispatch.class)
-        TestMessage message2 = new TestMessage()
-
-        clockTick(1).hasParticipants(2)
-
-        context.checking {
-            one(target).receive()
-            will(returnValue(new ChannelMetaInfo('channel1', 1)))
-            one(target).receive()
-            will(returnValue(new ChannelMessage(1, message)))
-            one(target).receive()
-            will {
-                syncAt(1)
-                return new ChannelMessage(1, message2)
-            }
-            one(handler).dispatch(message)
-            one(handler).dispatch(message2)
-            one(target).receive()
-            will {
-                return null
-            }
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-
-        run {
-            syncAt(1)
-            connection.addIncomingChannel('channel1', handler)
-        }
-
-        connection.stop()
-    }
-
-    @Test
-    public void stopBlocksUntilAllIncomingMessagesAreHandled() {
-        Dispatch<Message> handler = context.mock(Dispatch.class)
-        clockTick(1).hasParticipants(2)
-
-        context.checking {
-            one(target).receive()
-            will(returnValue(new ChannelMetaInfo('channel1', 1)))
-            one(target).receive()
-            will(returnValue(new ChannelMessage(1, message)))
-            one(handler).dispatch(message)
-            will {
-                syncAt(1)
-            }
-            one(target).receive()
-            will(returnValue(null))
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-
-        run {
-            connection.addIncomingChannel('channel1', handler)
-
-            expectBlocksUntil(1) {
-                connection.stop()
-            }
-        }
-    }
-
-    @Test
-    public void stopBlocksUntilAllOutgoingMessagesAreDispatched() {
-        clockTick(1).hasParticipants(3)
-
-        context.checking {
-            one(target).receive()
-            will {
-                syncAt(1)
-                return null
-            }
-
-            one(target).dispatch(new ChannelMetaInfo('channel1', 0))
-            one(target).dispatch(new ChannelMessage(0, message))
-            will {
-                syncAt(1)
-            }
-            one(target).dispatch(new EndOfStreamEvent())
-            one(target).stop()
-        }
-
-        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
-
-        run {
-            connection.addOutgoingChannel('channel1').dispatch(message)
-
-            expectBlocksUntil(1) {
-                connection.stop()
-            }
-        }
-    }
-}
-
-class TestMessage extends Message {
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
old mode 100644
new mode 100755
index fcd6ca8..8bfbb83
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
@@ -1,234 +1,237 @@
-/*
- * 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.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Addressable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-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.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-
-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;
-
- at RunWith(JMock.class)
-public class DefaultObjectConnectionTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private DefaultObjectConnection sender;
-    private DefaultObjectConnection receiver;
-    private final Addressable messageConnection = context.mock(Addressable.class);
-    private final AsyncStoppable stopControl = context.mock(AsyncStoppable.class);
-    private final TestConnection connection = new TestConnection();
-
-    @Before
-    public void setUp() {
-        IncomingMethodInvocationHandler senderIncoming = new IncomingMethodInvocationHandler(getClass().getClassLoader(), connection.getSender());
-        IncomingMethodInvocationHandler receiverIncoming = new IncomingMethodInvocationHandler(
-                getClass().getClassLoader(), connection.getReceiver());
-        OutgoingMethodInvocationHandler senderOutgoing = new OutgoingMethodInvocationHandler(connection.getSender());
-        OutgoingMethodInvocationHandler receiverOutgoing = new OutgoingMethodInvocationHandler(connection.getReceiver());
-        sender = new DefaultObjectConnection(messageConnection, stopControl, senderOutgoing, senderIncoming);
-        receiver = new DefaultObjectConnection(messageConnection, stopControl, receiverOutgoing, receiverIncoming);
-    }
-
-    @Test
-    public void createsProxyForOutgoingType() throws Exception {
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        assertThat(proxy, strictlyEqual(proxy));
-        assertThat(proxy.toString(), equalTo("TestRemote broadcast"));
-    }
-
-    @Test
-    public void deliversMethodInvocationsOnOutgoingObjectToHandlerObject() throws Exception {
-        final TestRemote handler = context.mock(TestRemote.class);
-        context.checking(new Expectations() {{
-            one(handler).doStuff("param");
-        }});
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        proxy.doStuff("param");
-    }
-
-    @Test
-    public void deliversMethodInvocationsOnOutgoingObjectToHandlerDispatch() throws Exception {
-        final Dispatch<MethodInvocation> handler = context.mock(Dispatch.class);
-        context.checking(new Expectations() {{
-            one(handler).dispatch(new MethodInvocation(TestRemote.class.getMethod("doStuff", String.class),
-                    new Object[]{"param"}));
-        }});
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        proxy.doStuff("param");
-    }
-
-    @Test
-    public void canHaveMultipleOutgoingTypes() throws Exception {
-        final TestRemote handler1 = context.mock(TestRemote.class);
-        final TestRemote2 handler2 = context.mock(TestRemote2.class);
-
-        context.checking(new Expectations() {{
-            one(handler1).doStuff("handler 1");
-            one(handler2).doStuff("handler 2");
-        }});
-        receiver.addIncoming(TestRemote.class, handler1);
-        receiver.addIncoming(TestRemote2.class, handler2);
-
-        TestRemote remote1 = sender.addOutgoing(TestRemote.class);
-        TestRemote2 remote2 = sender.addOutgoing(TestRemote2.class);
-
-        remote1.doStuff("handler 1");
-        remote2.doStuff("handler 2");
-    }
-
-    @Test
-    public void handlesTypesWithSuperTypes() {
-        final TestRemote3 handler = context.mock(TestRemote3.class);
-
-        context.checking(new Expectations() {{
-            one(handler).doStuff("handler 1");
-        }});
-        receiver.addIncoming(TestRemote3.class, handler);
-
-        TestRemote3 remote1 = sender.addOutgoing(TestRemote3.class);
-
-        remote1.doStuff("handler 1");
-    }
-
-    @Test
-    public void cannotRegisterMultipleHandlerObjectsWithSameType() {
-        TestRemote handler = context.mock(TestRemote.class);
-        receiver.addIncoming(TestRemote.class, handler);
-
-        try {
-            receiver.addIncoming(TestRemote.class, handler);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
-        }
-    }
-
-    @Test
-    public void cannotRegisterMultipleHandlerObjectsWithOverlappingMethods() {
-        receiver.addIncoming(TestRemote3.class, context.mock(TestRemote3.class));
-
-        try {
-            receiver.addIncoming(TestRemote.class, context.mock(TestRemote.class));
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
-        }
-    }
-
-    @Test
-    public void canCreateMultipleOutgoingObjectsWithSameType() {
-        sender.addOutgoing(TestRemote.class);
-        sender.addOutgoing(TestRemote.class);
-    }
-
-    @Test
-    public void stopsConnectionOnStop() {
-        context.checking(new Expectations() {{
-            one(stopControl).stop();
-        }});
-
-        receiver.stop();
-    }
-
-    private class TestConnection {
-        Map<Object, Dispatch<Object>> channels = new HashMap<Object, Dispatch<Object>>();
-
-        MultiChannelConnection<Object> getSender() {
-            return new MultiChannelConnection<Object>() {
-                public Dispatch<Object> addOutgoingChannel(Object channelKey) {
-                    return channels.get(channelKey);
-                }
-
-                public void addIncomingChannel(Object channelKey, Dispatch<Object> dispatch) {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void requestStop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void stop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public URI getLocalAddress() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public URI getRemoteAddress() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-
-        MultiChannelConnection<Object> getReceiver() {
-            return new MultiChannelConnection<Object>() {
-                public Dispatch<Object> addOutgoingChannel(Object channelKey) {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void addIncomingChannel(Object channelKey, Dispatch<Object> dispatch) {
-                    channels.put(channelKey, dispatch);
-                }
-
-                public void requestStop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void stop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public URI getLocalAddress() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public URI getRemoteAddress() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-    }
-
-    public interface TestRemote {
-        void doStuff(String param);
-    }
-
-    public interface TestRemote2 {
-        void doStuff(String param);
-    }
-
-    public interface TestRemote3 extends TestRemote {
-    }
-}
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.Addressable;
+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.util.HashMap;
+import java.util.Map;
+
+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;
+
+ at RunWith(JMock.class)
+public class DefaultObjectConnectionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private DefaultObjectConnection sender;
+    private DefaultObjectConnection receiver;
+    private final Addressable messageConnection = context.mock(Addressable.class);
+    private final AsyncStoppable stopControl = context.mock(AsyncStoppable.class);
+    private final TestConnection connection = new TestConnection();
+
+    @Before
+    public void setUp() {
+        IncomingMethodInvocationHandler senderIncoming = new IncomingMethodInvocationHandler(connection.getSender());
+        IncomingMethodInvocationHandler receiverIncoming = new IncomingMethodInvocationHandler(connection.getReceiver());
+        OutgoingMethodInvocationHandler senderOutgoing = new OutgoingMethodInvocationHandler(connection.getSender());
+        OutgoingMethodInvocationHandler receiverOutgoing = new OutgoingMethodInvocationHandler(connection.getReceiver());
+        sender = new DefaultObjectConnection(messageConnection, stopControl, senderOutgoing, senderIncoming);
+        receiver = new DefaultObjectConnection(messageConnection, stopControl, receiverOutgoing, receiverIncoming);
+    }
+
+    @Test
+    public void createsProxyForOutgoingType() throws Exception {
+        // Setup
+        final TestRemote handler = context.mock(TestRemote.class);
+        receiver.addIncoming(TestRemote.class, handler);
+
+        TestRemote proxy = sender.addOutgoing(TestRemote.class);
+        assertThat(proxy, strictlyEqual(proxy));
+        assertThat(proxy.toString(), equalTo("TestRemote broadcast"));
+    }
+
+    @Test
+    public void deliversMethodInvocationsOnOutgoingObjectToHandlerObject() throws Exception {
+        final TestRemote handler = context.mock(TestRemote.class);
+        context.checking(new Expectations() {{
+            one(handler).doStuff("param");
+        }});
+        receiver.addIncoming(TestRemote.class, handler);
+
+        TestRemote proxy = sender.addOutgoing(TestRemote.class);
+        proxy.doStuff("param");
+    }
+
+    @Test
+    public void deliversMethodInvocationsOnOutgoingObjectToHandlerDispatch() throws Exception {
+        final Dispatch<MethodInvocation> handler = context.mock(Dispatch.class);
+        context.checking(new Expectations() {{
+            one(handler).dispatch(new MethodInvocation(TestRemote.class.getMethod("doStuff", String.class),
+                    new Object[]{"param"}));
+        }});
+        receiver.addIncoming(TestRemote.class, handler);
+
+        TestRemote proxy = sender.addOutgoing(TestRemote.class);
+        proxy.doStuff("param");
+    }
+
+    @Test
+    public void canHaveMultipleOutgoingTypes() throws Exception {
+        final TestRemote handler1 = context.mock(TestRemote.class);
+        final TestRemote2 handler2 = context.mock(TestRemote2.class);
+
+        context.checking(new Expectations() {{
+            one(handler1).doStuff("handler 1");
+            one(handler2).doStuff("handler 2");
+        }});
+        receiver.addIncoming(TestRemote.class, handler1);
+        receiver.addIncoming(TestRemote2.class, handler2);
+
+        TestRemote remote1 = sender.addOutgoing(TestRemote.class);
+        TestRemote2 remote2 = sender.addOutgoing(TestRemote2.class);
+
+        remote1.doStuff("handler 1");
+        remote2.doStuff("handler 2");
+    }
+
+    @Test
+    public void handlesTypesWithSuperTypes() {
+        final TestRemote3 handler = context.mock(TestRemote3.class);
+
+        context.checking(new Expectations() {{
+            one(handler).doStuff("handler 1");
+        }});
+        receiver.addIncoming(TestRemote3.class, handler);
+
+        TestRemote3 remote1 = sender.addOutgoing(TestRemote3.class);
+
+        remote1.doStuff("handler 1");
+    }
+
+    @Test
+    public void cannotRegisterMultipleHandlerObjectsWithSameType() {
+        TestRemote handler = context.mock(TestRemote.class);
+        receiver.addIncoming(TestRemote.class, handler);
+
+        try {
+            receiver.addIncoming(TestRemote.class, handler);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
+        }
+    }
+
+    @Test
+    public void cannotRegisterMultipleHandlerObjectsWithOverlappingMethods() {
+        receiver.addIncoming(TestRemote3.class, context.mock(TestRemote3.class));
+
+        try {
+            receiver.addIncoming(TestRemote.class, context.mock(TestRemote.class));
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
+        }
+    }
+
+    @Test
+    public void canCreateMultipleOutgoingObjectsWithSameType() {
+        sender.addOutgoing(TestRemote.class);
+        sender.addOutgoing(TestRemote.class);
+    }
+
+    @Test
+    public void stopsConnectionOnStop() {
+        context.checking(new Expectations() {{
+            one(stopControl).stop();
+        }});
+
+        receiver.stop();
+    }
+
+    private class TestConnection {
+        Map<Object, Dispatch<Object>> channels = new HashMap<Object, Dispatch<Object>>();
+
+        MultiChannelConnection<Object> getSender() {
+            return new MultiChannelConnection<Object>() {
+                public Dispatch<Object> addOutgoingChannel(String channelKey) {
+                    return channels.get(channelKey);
+                }
+
+                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void requestStop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void stop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getLocalAddress() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getRemoteAddress() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+
+        MultiChannelConnection<Object> getReceiver() {
+            return new MultiChannelConnection<Object>() {
+                public Dispatch<Object> addOutgoingChannel(String channelKey) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
+                    channels.put(channelKey, dispatch);
+                }
+
+                public void requestStop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void stop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getLocalAddress() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getRemoteAddress() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+
+    public interface TestRemote {
+        void doStuff(String param);
+    }
+
+    public interface TestRemote2 {
+        void doStuff(String param);
+    }
+
+    public interface TestRemote3 extends TestRemote {
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
new file mode 100644
index 0000000..fe36ab4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2011 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.messaging.concurrent.DefaultExecutorFactory
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.CountDownLatch
+
+import spock.lang.*
+import spock.util.concurrent.*
+import org.spockframework.runtime.SpockTimeoutError
+
+class DisconnectAwareConnectionDecoratorTest extends Specification {
+
+    def messageQueue = new LinkedBlockingQueue()
+
+    def rawConnection = new Connection() {
+        void stop() { disconnect() }
+        void requestStop() { disconnect() }
+        def receive() { 
+            def val = messageQueue.take().first()
+            if (val == null) {
+                messageQueue.put([null])
+            }
+            val
+        }
+        void dispatch(message) {}
+    }
+    def connection
+    
+    def connection() {
+        new DisconnectAwareConnectionDecorator(rawConnection, new DefaultExecutorFactory().create("test"))
+    }
+
+    void sendMessage(message = 1) {
+        messageQueue.put([message])
+    }
+
+    void disconnect() {
+        messageQueue.put([null])
+    }
+
+    def receive() {
+        connection.receive()
+    }
+
+    def disconnectedHolder = new BlockingVariable(3) // allow 3 seconds for the disconnect handler to fire
+
+    def onDisconnect(Closure action = { disconnectedHolder.set(true) }) {
+        connection.onDisconnect(action)
+    }
+
+    boolean isDisconnectHandlerDidFire() {
+        try {
+            disconnectedHolder.get()
+        } catch (SpockTimeoutError e) {
+            false
+        }
+    }
+
+    def setup() {
+        connection = connection()
+        onDisconnect() // install the default handler
+    }
+
+    def "normal send and receive"() {
+        when:
+        sendMessage(1)
+
+        then:
+        receive() == 1
+    }
+
+    def "disconnect after send and before message"() {
+        when:
+        sendMessage(1)
+
+        and:
+        disconnect()
+
+        then:
+        disconnectHandlerDidFire
+
+        and:
+        receive() == 1
+    }
+
+    def "disconnect before sending any messages"() {
+        when:
+        disconnect()
+
+        then:
+        disconnectHandlerDidFire
+
+        and:
+        receive() == null
+    }
+
+    def "stopping connection does not fire handler"() {
+        given:
+        sendMessage(1)
+        sendMessage(2)
+
+        when:
+        sleep 1000 // wait for the messages to be consumed by the buffer
+        connection.stop()
+
+        then:
+        receive() == 1
+        receive() == 2
+        receive() == null
+
+        and:
+        !disconnectHandlerDidFire
+    }
+
+
+    @Timeout(10)
+    def "receive does not return null until disconnect handler set and complete"() {
+        given:
+        connection = connection() // default connection has the default disconnect handler, create a new one with no handler
+        disconnect()
+        
+        def disconnectLatch = new CountDownLatch(1)
+        def receiveLatch = new CountDownLatch(1)
+        
+        and:
+        def received = []
+        Thread.start { received << receive(); receiveLatch.countDown() }
+
+        when:
+        sleep 1000
+
+        then:
+        received.empty // receive() should be blocked, waiting for the disconnect handler
+
+        when:
+        onDisconnect { disconnectLatch.await(); }
+
+        then:
+        received.empty // receive() should still be blocked because the disconnect handler hasn't completed
+
+        when:
+        disconnectLatch.countDown()
+        receiveLatch.await()
+
+        then:
+        received.size() == 1
+        received[0] == null
+    }
+
+    def cleanup() {
+        connection.stop()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy
new file mode 100644
index 0000000..99612d9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Receive
+import org.gradle.messaging.concurrent.DefaultExecutorFactory
+
+import spock.lang.*
+
+class EagerReceiveBufferTest extends Specification {
+
+    def bufferSize = null
+    def receivers = []
+    def buffer
+
+    def receiver(Object... messages) {
+        def list = messages as LinkedList
+        receiver { list.poll() }
+    }
+
+    def receiver(Closure receiveImpl) {
+        receivers << (receiveImpl as Receive)
+    }
+
+    def executor() {
+        new DefaultExecutorFactory().create("test")
+    }
+
+    void bufferSize(int bufferSize) {
+        this.bufferSize = bufferSize
+    }
+
+    def buffer(Receive... receivers) {
+        if (bufferSize == null) {
+            new EagerReceiveBuffer(executor(), receivers as List)
+        } else {
+            new EagerReceiveBuffer(executor(), bufferSize, receivers as List)
+        }
+    }
+
+    def receive() {
+        if (buffer == null) {
+            buffer = buffer(*receivers)
+            buffer.start()
+        }
+
+        buffer.receive()
+    }
+
+    def "messages are consumed in order"() {
+        when:
+        receiver 1, 2, 3
+
+        then:
+        receive() == 1
+        receive() == 2
+        receive() == 3
+        receive() == null
+        receive() == null
+    }
+
+    def "messages are consumed from all receivers"() {
+        when:
+        receiver 1,2,3
+        receiver 4,5,6
+
+        then:
+        def messages = (1..6).collect { receive() }
+        def grouped = messages.groupBy { it < 4 ? "first" : "second" }
+        grouped.first == [1,2,3]
+        grouped.second == [4,5,6]
+    }
+
+    def "consumption blocks while the buffer is full"() {
+        given:
+        bufferSize 1
+
+        when:
+        def messages = new LinkedList([1,2,3,4])
+        receiver { messages.poll() }
+
+        then:
+        receive() == 1 // triggers consumption
+        sleep 1000 // enough time for the consumer thread to receive from our receiver, and block waiting for free buffer space
+        messages == [4] // 2 is on the buffer, 3 is being held waiting for space, 4 hasn't been received yet
+        receive() == 2
+        sleep 1000 // enough time for 3 to be put on the queue, and 4 to be received and held waiting for space
+        messages.empty
+        receive() == 3
+        receive() == 4
+    }
+
+    def "filling the buffer doesn't cause problems"() {
+        given:
+        bufferSize 1
+
+        when:
+        receiver 1,2,3
+        receiver 4,5,6
+        receiver 7,8,9
+
+        then:
+        9.times { assert receive() in 1..9; sleep 100 }
+    }
+
+    def "messages held while waiting for buffer space are discarded when stopped"() {
+        given:
+        bufferSize 1
+
+        when:
+        receiver 1,2,3,4
+
+        then:
+        receive() == 1
+        sleep 1000
+        buffer.stop()
+        receive() == 2
+        receive() == 3
+        receive() == null
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatchTest.groovy
deleted file mode 100644
index b3b876b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatchTest.groovy
+++ /dev/null
@@ -1,99 +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.messaging.remote.internal
-
-import org.gradle.messaging.dispatch.Dispatch
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.*
-
- at RunWith(JMock.class)
-public class EndOfStreamDispatchTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Dispatch<Message> target = context.mock(Dispatch.class)
-    private final EndOfStreamDispatch dispatch = new EndOfStreamDispatch(target)
-
-    @Test
-    public void writesEndOfStreamMessageOnStop() {
-        context.checking {
-            one(target).dispatch(new EndOfStreamEvent())
-        }
-
-        dispatch.stop()
-    }
-
-    @Test
-    public void stopBlocksUntilCurrentlyPendingMessageDelivered() {
-        Message message = new Message() {}
-
-        context.checking {
-            one(target).dispatch(message)
-            will {
-                syncAt(1)
-                syncAt(2)
-            }
-            one(target).dispatch(new EndOfStreamEvent())
-        }
-
-        start {
-            dispatch.dispatch(message)
-        }
-
-        run {
-            syncAt(1)
-            expectBlocksUntil(2) {
-                dispatch.stop()
-            }
-        }
-    }
-
-    @Test
-    public void cannotDispatchAfterStop() {
-        context.checking {
-            one(target).dispatch(new EndOfStreamEvent())
-        }
-
-        dispatch.stop()
-
-        try {
-            dispatch.dispatch(new Message(){})
-            fail()
-        } catch (IllegalStateException e) {
-            // expected
-        }
-    }
-
-    @Test
-    public void canStopFromMultipleThreads() {
-        context.checking {
-            one(target).dispatch(new EndOfStreamEvent())
-        }
-
-        start {
-            dispatch.stop()
-        }
-        start {
-            dispatch.stop()
-        }
-
-        waitForAll()
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilterTest.groovy
deleted file mode 100644
index b7dde8f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilterTest.groovy
+++ /dev/null
@@ -1,68 +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.messaging.remote.internal
-
-import org.gradle.messaging.dispatch.Dispatch
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.jmock.lib.legacy.ClassImposteriser
-import org.junit.Test
-import org.junit.runner.RunWith
-
- at RunWith(JMock.class)
-public class EndOfStreamFilterTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery() {{ imposteriser = ClassImposteriser.INSTANCE }}
-    private final Dispatch<Message> target = context.mock(Dispatch.class)
-    private final Runnable action = context.mock(Runnable.class)
-    private final EndOfStreamFilter filter = new EndOfStreamFilter(target, action)
-    
-    @Test
-    public void stopBlocksUntilEndOfStreamReceived() {
-        context.checking {
-            one(action).run()
-        }
-
-        start {
-            expectBlocksUntil(1) {
-                filter.stop()
-            }
-        }
-
-        run {
-            syncAt(1)
-            filter.dispatch(new EndOfStreamEvent())
-        }
-    }
-
-    @Test
-    public void canStopFromMultipleThreads() {
-        context.checking {
-            one(action).run()
-        }
-
-        filter.dispatch(new EndOfStreamEvent())
-
-        start {
-            filter.stop()
-        }
-        run {
-            filter.stop()
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceiveTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceiveTest.groovy
deleted file mode 100644
index 33d05c6..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceiveTest.groovy
+++ /dev/null
@@ -1,50 +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.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.dispatch.Receive
-
-class EndOfStreamReceiveTest extends Specification {
-    private final Receive<Message> target = Mock()
-    private final EndOfStreamReceive receive = new EndOfStreamReceive(target)
-
-    def receivesMessageFromTarget() {
-        Message message = Mock()
-
-        when:
-        def m = receive.receive()
-
-        then:
-        m == message
-        1 * target.receive() >> message
-    }
-    
-    def returnsEndOfStreamMessageThenNullWhenTargetReturnsNull() {
-        when:
-        def m = receive.receive()
-
-        then:
-        m instanceof EndOfStreamEvent
-        1 * target.receive() >> null
-
-        expect:
-        receive.receive() == null
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy
new file mode 100644
index 0000000..5a8d177
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.messaging.remote.internal.protocol.UnknownMessage
+
+class GroupMessageFilterTest extends Specification {
+    final Dispatch<DiscoveryMessage> target = Mock()
+    final MessageOriginator messageSource = Mock()
+    final GroupMessageFilter filter = new GroupMessageFilter("group", target)
+
+    def "forwards message for known group"() {
+        def message = new DiscoveryMessage(messageSource, "group")
+
+        when:
+        filter.dispatch(message)
+
+        then:
+        1 * target.dispatch(message)
+    }
+
+    def "discards message for unknown group"() {
+        def message = new DiscoveryMessage(messageSource, "unknown")
+
+        when:
+        filter.dispatch(message)
+
+        then:
+        0 * target._
+    }
+
+    def "discard unknown message"() {
+        def message = new UnknownMessage("unknown")
+
+        when:
+        filter.dispatch(message)
+
+        then:
+        0 * target._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
index 281cf2f..efd77c8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
@@ -22,8 +22,11 @@ import spock.lang.Specification
 import org.gradle.api.Action
 import java.util.concurrent.Executor
 import org.gradle.messaging.remote.ConnectEvent
+import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.protocol.ConnectRequest
 
 class HandshakeIncomingConnectorTest extends Specification {
+    private final Address localAddress = Mock()
     private final Executor executor = Mock()
     private final IncomingConnector target = Mock()
     private final Connection connection = Mock()
@@ -33,36 +36,39 @@ class HandshakeIncomingConnectorTest extends Specification {
         Action<ConnectEvent<Connection<Message>>> action = Mock()
 
         when:
-        def address = connector.accept(action)
+        def address = connector.accept(action, false)
 
         then:
-        1 * target.accept(!null) >> new URI("test:source")
-        address == new URI("channel:test:source!0")
+        address == new CompositeAddress(localAddress, 0L)
+        1 * target.accept(!null, false) >> localAddress
+        0 * target._
     }
 
-    def eachCallToAcceptAllocatesADifferentUri() {
+    def eachCallToAcceptAllocatesADifferentAddress() {
         Action<ConnectEvent<Connection<Message>>> action = Mock()
-        1 * target.accept(!null) >> new URI("test:source")
 
         when:
-        def address1 = connector.accept(action)
-        def address2 = connector.accept(action)
+        def address1 = connector.accept(action, false)
+        def address2 = connector.accept(action, false)
 
         then:
-        address1 == new URI("channel:test:source!0")
-        address2 == new URI("channel:test:source!1")
+        address1 == new CompositeAddress(localAddress, 0L)
+        address2 == new CompositeAddress(localAddress, 1L)
+        1 * target.accept(!null, false) >> localAddress
+        0 * target._
     }
     
     def performsHandshakeOnAccept() {
         Action<ConnectEvent<Connection<Message>>> action = Mock()
+        Address remoteAddress = Mock()
         def wrappedAction
         def handshakeRunnable
-        1 * target.accept(!null) >> { wrappedAction = it[0]; new URI("test:source") }
+        1 * target.accept(!null, false) >> { wrappedAction = it[0]; localAddress }
 
-        def address = connector.accept(action)
+        def address = connector.accept(action, false)
 
         when:
-        wrappedAction.execute(new ConnectEvent<Connection<Message>>(connection, new URI("test:source"), new URI("test:dest")))
+        wrappedAction.execute(new ConnectEvent<Connection<Message>>(connection, localAddress, remoteAddress))
 
         then:
         1 * executor.execute(!null) >> { handshakeRunnable = it[0] }
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
index 993ffd2..9cd24cb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
@@ -19,42 +19,47 @@
 package org.gradle.messaging.remote.internal
 
 import spock.lang.Specification
+import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.protocol.ConnectRequest
 
 class HandshakeOutgoingConnectorTest extends Specification {
-    private final OutgoingConnector target = Mock()
+    private final Address targetAddress = Mock()
+    private final OutgoingConnector<Message> target = Mock()
     private final Connection<Message> connection = Mock()
     private final HandshakeOutgoingConnector connector = new HandshakeOutgoingConnector(target)
 
     def createsConnectionAndPerformsHandshake() {
+        def remoteAddress = new CompositeAddress(targetAddress, 0)
+
         when:
-        def connection = connector.connect(new URI("channel:test:dest!0"))
+        def connection = connector.connect(remoteAddress)
 
         then:
         connection == this.connection
-        1 * target.connect(new URI("test:dest")) >> connection
-        1 * connection.dispatch({it instanceof ConnectRequest && it.destinationAddress == new URI("channel:test:dest!0")})
+        1 * target.connect(targetAddress) >> connection
+        1 * connection.dispatch({it instanceof ConnectRequest && it.destinationAddress == remoteAddress})
     }
 
     def stopsConnectionOnFailureToPerformHandshake() {
         RuntimeException failure = new RuntimeException()
 
         when:
-        connector.connect(new URI("channel:test:dest!0"))
+        connector.connect(new CompositeAddress(targetAddress, 0))
 
         then:
         def e = thrown(RuntimeException)
         e == failure
-        1 * target.connect(new URI("test:dest")) >> connection
+        1 * target.connect(targetAddress) >> connection
         1 * connection.dispatch({it instanceof ConnectRequest}) >> { throw failure }
         1 * connection.stop()
     }
-    
+
     def failsWhenURIHasUnknownScheme() {
         when:
-        connector.connect(new URI("unknown:dest"))
+        connector.connect(targetAddress)
 
         then:
         def e = thrown(IllegalArgumentException)
-        e.message == 'Cannot create a connection to destination URI with unknown scheme: unknown:dest.'
+        e.message == "Cannot create a connection to address of unknown type: ${targetAddress}."
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/InputForwarderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/InputForwarderTest.groovy
new file mode 100644
index 0000000..a63d2e4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/InputForwarderTest.groovy
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2011 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.concurrent.DefaultExecutorFactory
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import static org.gradle.util.TextUtil.*
+
+import spock.lang.*
+import spock.util.concurrent.BlockingVariable
+
+class InputForwarderTest extends Specification {
+
+    def bufferSize = 1024
+    def executerFactory = new DefaultExecutorFactory()
+
+    def source = new PipedOutputStream()
+    def inputStream = new PipedInputStream(source)
+
+    def received = new LinkedBlockingQueue()
+    def finishedHolder = new BlockingVariable(2)
+
+    def action = action { received << it }
+    def onFinish = finished { finishedHolder.set(true) }
+
+    def action(Closure action) {
+        this.action = action as Action
+    }
+
+    def finished(Closure runnable) {
+        this.onFinish = runnable
+    }
+
+    def forwarder
+
+    def createForwarder() {
+        forwarder = new InputForwarder(inputStream, action, onFinish, executerFactory, bufferSize)
+        forwarder.start()
+    }
+
+    def receive(receive) {
+        assert received.poll(5, TimeUnit.SECONDS) == toPlatformLineSeparators(receive)
+        true
+    }
+
+    void input(str) {
+        source << toPlatformLineSeparators(str)
+    }
+    
+    boolean isFinished() {
+        finishedHolder.get() == true
+    }
+
+    boolean isNoMoreInput() {
+        receive null
+    }
+
+    def setup() {
+        createForwarder()
+    }
+
+    def closeInput() {
+        inputStream.close()
+        source.close()
+    }
+
+    def waitForForwarderToCollect() {
+        sleep 1000
+    }
+
+    def "input from source is forwarded until forwarder is stopped"() {
+        when:
+        input "abc\ndef\njkl"
+        waitForForwarderToCollect()
+        forwarder.stop()
+
+        then:
+        receive "abc\n"
+        receive "def\n"
+        receive "jkl"
+        noMoreInput
+
+        and:
+        finished
+    }
+
+    def "input from source is forwarded until source input stream is closed"() {
+        when:
+        input "abc\ndef\njkl"
+        waitForForwarderToCollect()
+        closeInput()
+
+        then:
+        receive "abc\n"
+        receive "def\n"
+        receive "jkl"
+        noMoreInput
+
+        and:
+        finished
+    }
+
+    def "output is buffered by line"() {
+        when:
+        input "a"
+
+        then:
+        noMoreInput
+
+        when:
+        input "b"
+
+        then:
+        noMoreInput
+
+        when:
+        input "\n"
+
+        then:
+        receive "ab\n"
+    }
+
+    def "one partial line when input stream closed gets forwarded"() {
+        when:
+        input "abc"
+        waitForForwarderToCollect()
+
+        and:
+        closeInput()
+
+        then:
+        receive "abc"
+
+        and:
+        noMoreInput
+    }
+
+    def "one partial line when forwarder stopped gets forwarded"() {
+        when:
+        input "abc"
+        waitForForwarderToCollect()
+
+        and:
+        forwarder.stop()
+
+        then:
+        receive "abc"
+
+        and:
+        noMoreInput
+    }
+
+    def "forwarder can be closed before receiving any output"() {
+        when:
+        forwarder.stop()
+
+        then:
+        noMoreInput
+    }
+
+    def "can handle lines larger than the buffer size"() {
+        given:
+        def longLine = "a" * (bufferSize * 10) + "\n"
+
+        when:
+        input longLine
+        input longLine
+
+        then:
+        receive longLine
+        receive longLine
+        noMoreInput
+    }
+
+    def cleanup() {
+        closeInput()
+        forwarder.stop()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
index 485ea59..2b59d94 100644
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
@@ -16,21 +16,23 @@
 package org.gradle.messaging.remote.internal
 
 import spock.lang.Specification
+import spock.lang.Issue
+import spock.lang.Ignore
 
 class MessageTest extends Specification {
-    private final GroovyClassLoader source = new GroovyClassLoader(getClass().getClassLoader());
-    private final GroovyClassLoader dest = new GroovyClassLoader(getClass().getClassLoader());
+    GroovyClassLoader source = new GroovyClassLoader(getClass().classLoader)
+    GroovyClassLoader dest = new GroovyClassLoader(getClass().classLoader)
 
-    def replacesUnserializableExceptionWithPlaceholder() {
-        RuntimeException cause = new RuntimeException("nested");
-        UnserializableException original = new UnserializableException("message", cause);
+    def "replaces unserializable exception with placeholder"() {
+        def cause = new RuntimeException("nested")
+        def original = new UnserializableException("message", cause)
 
         when:
-        Object transported = transport(original);
+        def transported = transport(original)
 
         then:
         transported instanceof PlaceholderException
-        transported.message == UnserializableException.class.name + ": " + original.message
+        transported.message == original.message
         transported.stackTrace == original.stackTrace
 
         transported.cause.class == RuntimeException.class
@@ -38,21 +40,22 @@ class MessageTest extends Specification {
         transported.cause.stackTrace == cause.getStackTrace()
     }
 
-    def replacesNestedUnserializableExceptionWithPlaceholder() {
-        Exception cause = new IOException("nested");
-        UnserializableException original = new UnserializableException("message", cause);
-        RuntimeException outer = new RuntimeException('message', original)
+    def "replaces nested unserializable exception with placeholder"() {
+        def cause = new IOException("nested")
+        def original = new UnserializableException("message", cause)
+        def outer = new RuntimeException("message", original)
 
         when:
-        Object transported = transport(outer);
+        def transported = transport(outer)
 
         then:
+        transported instanceof RuntimeException
         transported.class == RuntimeException.class
-        transported.message == 'message'
+        transported.message == "message"
         transported.stackTrace == outer.stackTrace
 
         transported.cause instanceof PlaceholderException
-        transported.cause.message == UnserializableException.class.name + ": " + original.message
+        transported.cause.message == original.message
         transported.cause.stackTrace == original.stackTrace
 
         transported.cause.cause.class == IOException
@@ -60,16 +63,16 @@ class MessageTest extends Specification {
         transported.cause.cause.stackTrace == cause.stackTrace
     }
 
-    def replacesUndeserializableExceptionWithPlaceholder() {
-        RuntimeException cause = new RuntimeException("nested");
-        UndeserializableException original = new UndeserializableException("message", cause);
+    def "replaces undeserializable exception with placeholder"() {
+        def cause = new RuntimeException("nested")
+        def original = new UndeserializableException("message", cause)
 
         when:
-        Object transported = transport(original);
+        def transported = transport(original)
 
         then:
         transported instanceof PlaceholderException
-        transported.message == UndeserializableException.class.name + ": " + original.message
+        transported.message == original.message
         transported.stackTrace == original.stackTrace
 
         transported.cause.class == RuntimeException.class
@@ -77,21 +80,22 @@ class MessageTest extends Specification {
         transported.cause.stackTrace == cause.stackTrace
     }
 
-    def replacesNestedUndeserializableExceptionWithPlaceholder() {
-        RuntimeException cause = new RuntimeException("nested");
-        UndeserializableException original = new UndeserializableException("message", cause);
-        RuntimeException outer = new RuntimeException('message', original)
+    def "replaces nested undeserializable exception with placeholder"() {
+        def cause = new RuntimeException("nested")
+        def original = new UndeserializableException("message", cause)
+        def outer = new RuntimeException("message", original)
 
         when:
-        Object transported = transport(outer);
+        def transported = transport(outer)
 
         then:
+        transported instanceof RuntimeException
         transported.class == RuntimeException
-        transported.message == 'message'
+        transported.message == "message"
         transported.stackTrace == outer.stackTrace
 
         transported.cause instanceof PlaceholderException
-        transported.cause.message == UndeserializableException.class.name + ": " + original.message
+        transported.cause.message == original.message
         transported.cause.stackTrace == original.stackTrace
 
         transported.cause.cause.class == RuntimeException.class
@@ -99,37 +103,38 @@ class MessageTest extends Specification {
         transported.cause.cause.stackTrace == cause.stackTrace
     }
 
-    def replacesUnserializableExceptionFieldWithPlaceholder() {
-        RuntimeException cause = new RuntimeException()
-        UndeserializableException original = new UndeserializableException("message", cause);
-        ExceptionWithExceptionField outer = new ExceptionWithExceptionField("nested", original)
+    def "replaces unserializable exception field with placeholder"() {
+        def cause = new RuntimeException()
+        def original = new UndeserializableException("message", cause)
+        def outer = new ExceptionWithExceptionField("nested", original)
 
         when:
-        Object transported = transport(outer);
+        def transported = transport(outer)
 
         then:
         transported instanceof ExceptionWithExceptionField
 
         transported.throwable instanceof PlaceholderException
-        transported.throwable.message == UndeserializableException.class.name + ": " + original.message
+        transported.throwable.message == original.message
         transported.throwable.stackTrace == original.stackTrace
 
         transported.throwable == transported.cause
     }
 
-    def replacesIncompatibleExceptionWithLocalVersion() {
-        RuntimeException cause = new RuntimeException("nested");
-        Class<? extends RuntimeException> sourceExceptionType = source.parseClass(
-                "package org.gradle; public class TestException extends RuntimeException { public TestException(String msg, Throwable cause) { super(msg, cause); } }");
-        Class<? extends RuntimeException> destExceptionType = dest.parseClass(
-                "package org.gradle; public class TestException extends RuntimeException { private String someField; public TestException(String msg) { super(msg); } }");
+    def "replaces incompatible exception with local version"() {
+        def cause = new RuntimeException("nested")
+        def sourceExceptionType = source.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { public TestException(String msg, Throwable cause) { super(msg, cause); } }")
+        def destExceptionType = dest.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { private String someField; public TestException(String msg) { super(msg); } }")
 
-        RuntimeException original = sourceExceptionType.newInstance("message", cause);
+        def original = sourceExceptionType.newInstance("message", cause)
 
         when:
-        RuntimeException transported = transport(original);
+        def transported = transport(original)
 
         then:
+        transported instanceof RuntimeException
         transported.class == destExceptionType
         transported.message == original.message
         transported.stackTrace == original.stackTrace
@@ -139,20 +144,20 @@ class MessageTest extends Specification {
         transported.cause.stackTrace == cause.stackTrace
     }
 
-    def usesPlaceholderWhenLocalExceptionCannotBeConstructed() {
-        RuntimeException cause = new RuntimeException("nested");
-        Class<? extends RuntimeException> sourceExceptionType = source.parseClass(
-                "package org.gradle; public class TestException extends RuntimeException { public TestException(String msg, Throwable cause) { super(msg, cause); } }");
-        dest.parseClass("package org.gradle; public class TestException extends RuntimeException { private String someField; }");
+    def "uses placeholder when local exception cannot be constructed"() {
+        def cause = new RuntimeException("nested")
+        def sourceExceptionType = source.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { public TestException(String msg, Throwable cause) { super(msg, cause); } }")
+        dest.parseClass("package org.gradle; public class TestException extends RuntimeException { private String someField; }")
 
-        RuntimeException original = sourceExceptionType.newInstance("message", cause);
+        def original = sourceExceptionType.newInstance("message", cause)
 
         when:
-        Object transported = transport(original);
+        def transported = transport(original)
 
         then:
-        transported  instanceof PlaceholderException
-        transported.message == original.toString()
+        transported instanceof PlaceholderException
+        transported.message == original.message
         transported.stackTrace == original.stackTrace
 
         transported.cause.class == RuntimeException.class
@@ -160,45 +165,70 @@ class MessageTest extends Specification {
         transported.cause.stackTrace == cause.stackTrace
     }
 
+    @Ignore
+    @Issue("GRADLE-1996")
+    def "can transport exception that implements writeReplace()"() {
+        def original = new WriteReplaceException("original")
+
+        when:
+        def transported = transport(original)
+
+        then:
+        noExceptionThrown()
+        transported instanceof WriteReplaceException
+        transported.message == "replaced"
+    }
+
     private Object transport(Object arg) {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        Message.send(new TestPayloadMessage(payload: arg), outputStream);
+        def outputStream = new ByteArrayOutputStream()
+        Message.send(new TestPayloadMessage(payload: arg), outputStream)
 
-        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
-        def message = Message.receive(inputStream, dest);
+        def inputStream = new ByteArrayInputStream(outputStream.toByteArray())
+        def message = Message.receive(inputStream, dest)
         return message.payload
     }
-}
 
-private class TestPayloadMessage extends Message {
-    def payload
-}
+    static class TestPayloadMessage extends Message {
+        def payload
+    }
 
-private class ExceptionWithExceptionField extends RuntimeException {
-    def Throwable throwable
+    static class ExceptionWithExceptionField extends RuntimeException {
+        Throwable throwable
 
-    def ExceptionWithExceptionField(String message, Throwable cause) {
-        super(message, cause)
-        throwable = cause
+        ExceptionWithExceptionField(String message, Throwable cause) {
+            super(message, cause)
+            throwable = cause
+        }
     }
-}
 
-private class UnserializableException extends RuntimeException {
-    public UnserializableException(String message, Throwable cause) {
-        super(message, cause);
-    }
+    static class UnserializableException extends RuntimeException {
+        UnserializableException(String message, Throwable cause) {
+            super(message, cause)
+        }
 
-    private void writeObject(ObjectOutputStream outstr) throws IOException {
-        outstr.writeObject(new Object());
+        private void writeObject(ObjectOutputStream outstr) throws IOException {
+            outstr.writeObject(new Object())
+        }
     }
-}
 
-private class UndeserializableException extends RuntimeException {
-    public UndeserializableException(String message, Throwable cause) {
-        super(message, cause);
+    static class UndeserializableException extends RuntimeException {
+        UndeserializableException(String message, Throwable cause) {
+            super(message, cause)
+        }
+
+        private void readObject(ObjectInputStream outstr) throws ClassNotFoundException {
+            throw new ClassNotFoundException()
+        }
     }
 
-    private void readObject(ObjectInputStream outstr) throws ClassNotFoundException {
-        throw new ClassNotFoundException();
+    static class WriteReplaceException extends Exception {
+        WriteReplaceException(String message) {
+            super(message)
+        }
+
+        private Object writeReplace() {
+            return new WriteReplaceException("replaced")
+        }
     }
 }
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy
new file mode 100644
index 0000000..0ee7767
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.messaging.remote.MessagingClient
+import org.gradle.messaging.remote.MessagingServer
+import spock.lang.Specification
+
+class MessagingServicesTest extends Specification {
+    final MessagingServices services = new MessagingServices(getClass().classLoader)
+
+    def cleanup() {
+        services?.stop()
+    }
+
+    def "provides a messaging client"() {
+        expect:
+        services.get(MessagingClient.class) != null
+    }
+
+    def "provides a messaging server"() {
+        expect:
+        services.get(MessagingServer.class) != null
+    }
+
+    def "provides an incoming broadcast"() {
+        expect:
+        services.get(IncomingBroadcast.class) != null
+    }
+
+    def "provides an outgoing broadcast"() {
+        expect:
+        services.get(OutgoingBroadcast.class) != null
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy
new file mode 100644
index 0000000..ab56e7b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch
+import org.gradle.messaging.dispatch.MethodInvocation
+import org.gradle.messaging.remote.internal.protocol.MethodMetaInfo
+import org.gradle.messaging.remote.internal.protocol.RemoteMethodInvocation
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.PayloadMessage
+import java.lang.reflect.Method
+
+class MethodInvocationMarshallingDispatchTest extends Specification {
+    final Method method = String.class.getMethod("charAt", Integer.TYPE)
+    final Dispatch<Message> target = Mock()
+    final MethodInvocationMarshallingDispatch dispatch = new MethodInvocationMarshallingDispatch(target)
+
+    def "sends a method meta message when a method is first referenced as a method payload"() {
+        TestPayload message1 = Mock()
+        Message transformed1 = Mock()
+        TestPayload message2 = Mock()
+        Message transformed2 = Mock()
+
+        given:
+        message1.nestedPayload >> new MethodInvocation(method, [17] as Object[])
+        message2.nestedPayload >> new MethodInvocation(method, [12] as Object[])
+
+        when:
+        dispatch.dispatch(message1)
+
+        then:
+        1 * target.dispatch(new MethodMetaInfo(0, method))
+
+        and:
+        1 * message1.withNestedPayload(new RemoteMethodInvocation(0, [17] as Object[])) >> transformed1
+        1 * target.dispatch(transformed1)
+
+        when:
+        dispatch.dispatch(message2)
+
+        then:
+        1 * message2.withNestedPayload(new RemoteMethodInvocation(0, [12] as Object[])) >> transformed2
+        1 * target.dispatch(transformed2)
+    }
+
+    def "does not transform other types of messages"() {
+        Message message = Mock()
+
+        when:
+        dispatch.dispatch(message)
+
+        then:
+        1 * target.dispatch(message)
+    }
+
+    def "does not transform other types of payloads"() {
+        TestPayload message = Mock()
+
+        given:
+        message.nestedPayload >> 'payload'
+
+        when:
+        dispatch.dispatch(message)
+
+        then:
+        1 * target.dispatch(message)
+    }
+}
+
+abstract class TestPayload extends Message implements PayloadMessage {
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.java
deleted file mode 100644
index 3ab9f9f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.java
+++ /dev/null
@@ -1,55 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Method;
-
- at RunWith(JMock.class)
-public class MethodInvocationMarshallingDispatchTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Dispatch<Message> target = context.mock(Dispatch.class);
-    private final MethodInvocationMarshallingDispatch dispatch = new MethodInvocationMarshallingDispatch(target);
-
-    @Test
-    public void sendsAMethodMetaInfoMessageWhenAMethodIsFirstReferenced() throws Exception {
-        final Method method = String.class.getMethod("charAt", Integer.TYPE);
-
-        context.checking(new Expectations() {{
-            Sequence sequence = context.sequence("seq");
-
-            one(target).dispatch(new MethodMetaInfo(0, method));
-            inSequence(sequence);
-
-            one(target).dispatch(new RemoteMethodInvocation(0, new Object[]{17}));
-            inSequence(sequence);
-
-            one(target).dispatch(new RemoteMethodInvocation(0, new Object[]{12}));
-            inSequence(sequence);
-        }});
-
-        dispatch.dispatch(new MethodInvocation(method, new Object[]{17}));
-        dispatch.dispatch(new MethodInvocation(method, new Object[]{12}));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy
new file mode 100644
index 0000000..9ba196d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 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.lang.reflect.Method
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.messaging.dispatch.MethodInvocation
+import org.gradle.messaging.remote.internal.protocol.MethodMetaInfo
+import org.gradle.messaging.remote.internal.protocol.RemoteMethodInvocation
+import spock.lang.Specification
+
+class MethodInvocationUnmarshallingDispatchTest extends Specification {
+    final Method method = String.class.getMethod("charAt", Integer.TYPE)
+    final Dispatch<Message> target = Mock()
+    final MethodInvocationUnmarshallingDispatch dispatch = new MethodInvocationUnmarshallingDispatch(target, getClass().classLoader)
+
+    def "replaces remote method invocation with local method invocation"() {
+        TestPayload message1 = Mock()
+        Message transformed1 = Mock()
+        TestPayload message2 = Mock()
+        Message transformed2 = Mock()
+
+        given:
+        message1.nestedPayload >> new RemoteMethodInvocation(1, [17] as Object[])
+        message2.nestedPayload >> new RemoteMethodInvocation(1, [3] as Object[])
+
+        when:
+        dispatch.dispatch(new MethodMetaInfo(1, method));
+        dispatch.dispatch(message1)
+        dispatch.dispatch(message2)
+
+        then:
+        1 * message1.withNestedPayload(new MethodInvocation(method, [17] as Object[])) >> transformed1
+        1 * target.dispatch(transformed1)
+        1 * message2.withNestedPayload(new MethodInvocation(method, [3] as Object[])) >> transformed2
+        1 * target.dispatch(transformed2)
+        0 * target._
+    }
+
+    def "does not forward method meta-data message"() {
+        when:
+        dispatch.dispatch(new MethodMetaInfo(1, method));
+
+        then:
+        0 * target._
+    }
+
+    def "does not transform other types of payloads"() {
+        TestPayload message = Mock()
+
+        given:
+        message.nestedPayload >> 'hi'
+
+        when:
+        dispatch.dispatch(message)
+
+        then:
+        1 * target.dispatch(message)
+    }
+
+    def "does not transform other types of messages"() {
+        Message message = Mock()
+
+        when:
+        dispatch.dispatch(message)
+
+        then:
+        1 * target.dispatch(message)
+    }
+
+    def "fails when remote method invocation message received for unknown method"() {
+        TestPayload message = Mock()
+
+        given:
+        message.nestedPayload >> new RemoteMethodInvocation(1, [17] as Object[])
+
+        when:
+        dispatch.dispatch(message)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == 'Received a method invocation message for an unknown method.'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.java
deleted file mode 100644
index 067b1fd..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.java
+++ /dev/null
@@ -1,76 +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.messaging.remote.internal;
-
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Method;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class MethodInvocationUnmarshallingDispatchTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Dispatch<MethodInvocation> target = context.mock(Dispatch.class);
-    private final MethodInvocationUnmarshallingDispatch dispatch = new MethodInvocationUnmarshallingDispatch(target, getClass().getClassLoader());
-
-    @Test
-    public void doesNotForwardMethodMetaInfoMessages() throws Exception {
-        dispatch.dispatch(new MethodMetaInfo(1, String.class.getMethod("charAt", Integer.TYPE)));
-    }
-
-    @Test
-    public void transformsRemoteMethodInvocationMessage() throws Exception {
-        final Method method = String.class.getMethod("charAt", Integer.TYPE);
-
-        context.checking(new Expectations() {{
-            one(target).dispatch(new MethodInvocation(method, new Object[]{17}));
-        }});
-
-        dispatch.dispatch(new MethodMetaInfo(1, method));
-        dispatch.dispatch(new RemoteMethodInvocation(1, new Object[]{17}));
-    }
-
-    @Test
-    public void failsWhenRemoteMethodInvocationMessageReceivedForUnknownMethod() {
-        try {
-            dispatch.dispatch(new RemoteMethodInvocation(1, new Object[]{17}));
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), equalTo("Received a method invocation message for an unknown method."));
-        }
-    }
-
-    @Test
-    public void failsWhenUnexpectedMessageReceived() {
-        final Message message = new Message() {
-        };
-
-        try {
-            dispatch.dispatch(message);
-            fail();
-        } catch (IllegalStateException e) {
-            assertThat(e.getMessage(), startsWith("Received an unknown message "));
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
new file mode 100644
index 0000000..e8b3c95
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import spock.lang.Issue
+
+ at Issue("GRADLE-1905")
+class PlaceholderExceptionTest extends Specification {
+    def "toString() generally produces same output as original exception"() {
+        def original = new Exception("original exception")
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.cause)
+        
+        expect:
+        placeholder.toString() == original.toString()
+    }
+    
+    def "toString() doesn't produce same output as original exception if the latter overrides toString()"() {
+        def original = new Exception("original exception") {
+            String toString() {
+                "fancy customized toString"
+            }
+        }
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.cause)
+        
+        expect:
+        placeholder.toString() != original.toString()
+    }
+    
+    def "toString() doesn't produce same output as original exception if the latter has a localized message"() {
+        def original = new Exception("original exception") {
+            String getLocalizedMessage() {
+                "lokalisierte nachricht"
+            }
+        }
+        def placeholder = new PlaceholderException(original.getClass().name, original.message, original.cause)
+
+        expect:
+        placeholder.toString() != original.toString()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
new file mode 100644
index 0000000..d2cca47
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2011 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.util.concurrent.TimeUnit
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.messaging.dispatch.DispatchFailureHandler
+import org.gradle.util.ConcurrentSpecification
+import spock.lang.Timeout
+
+class ProtocolStackTest extends ConcurrentSpecification {
+    final Protocol<String> top = Mock()
+    final Protocol<String> bottom = Mock()
+    final Dispatch<String> outgoing = Mock()
+    final Dispatch<String> incoming = Mock()
+    final DispatchFailureHandler<String> outgoingFailureHandler = Mock()
+    final DispatchFailureHandler<String> incomingFailureHandler = Mock()
+    ProtocolContext<String> topContext
+    ProtocolContext<String> bottomContext
+    ProtocolStack<String> stack
+
+    def setup() {
+        _ * top.start(!null) >> {
+            topContext = it[0]
+        }
+        _ * bottom.start(!null) >> {
+            bottomContext = it[0]
+        }
+        _ * outgoingFailureHandler.dispatchFailed(!null, !null) >> { throw it[1] }
+        _ * incomingFailureHandler.dispatchFailed(!null, !null) >> { throw it[1] }
+
+        stack = new ProtocolStack<String>(executor, outgoingFailureHandler, incomingFailureHandler, top, bottom)
+        stack.bottom.dispatchTo(outgoing)
+    }
+
+    def cleanup() {
+        stack?.stop()
+    }
+
+    def "starts protocol on construction"() {
+        Protocol<String> protocol = Mock()
+        def started = startsAsyncAction()
+
+        when:
+        def stack
+        started.started {
+            stack = new ProtocolStack<String>(executor, outgoingFailureHandler, incomingFailureHandler, protocol)
+        }
+
+        then:
+        1 * protocol.start(!null) >> { started.done() }
+
+        cleanup:
+        stack?.stop()
+    }
+
+    @Timeout(5)
+    def "top protocol can dispatch incoming message during start"() {
+        Protocol<String> protocol = Mock()
+        def dispatched = startsAsyncAction()
+
+        when:
+        def stack
+        dispatched.started {
+            stack = new ProtocolStack<String>(executor, outgoingFailureHandler, incomingFailureHandler, protocol)
+            stack.top.dispatchTo(incoming)
+        }
+
+        then:
+        1 * protocol.start(!null) >> { it[0].dispatchIncoming("message") }
+        1 * incoming.dispatch("message") >> { dispatched.done() }
+
+        cleanup:
+        stack?.stop()
+    }
+
+    def "outgoing message is dispatched to top protocol"() {
+        def dispatched = startsAsyncAction()
+
+        when:
+        dispatched.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> { dispatched.done() }
+    }
+
+    def "incoming message is dispatched to bottom protocol"() {
+        def dispatched = startsAsyncAction()
+
+        when:
+        dispatched.started {
+            stack.bottom.dispatch("message")
+        }
+
+        then:
+        1 * bottom.handleIncoming("message") >> { dispatched.done() }
+    }
+
+    def "incoming message dispatched by protocol is dispatch to next higher protocol"() {
+        def dispatched = startsAsyncAction()
+
+        when:
+        dispatched.started {
+            stack.bottom.dispatch("message")
+        }
+
+        then:
+        1 * bottom.handleIncoming("message") >> { bottomContext.dispatchIncoming("transformed") }
+        1 * top.handleIncoming("transformed") >> { dispatched.done() }
+    }
+
+    def "outgoing message dispatch by protocol is dispatched to next lower protocol"() {
+        def dispatched = startsAsyncAction()
+
+        when:
+        dispatched.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> { topContext.dispatchOutgoing("transformed") }
+        1 * bottom.handleOutgoing("transformed") >> { dispatched.done() }
+    }
+
+    def "incoming message dispatched by top protocol is dispatch to a handler"() {
+        stack.top.dispatchTo(incoming)
+        def dispatched = startsAsyncAction()
+
+        when:
+        dispatched.started {
+            stack.bottom.dispatch("message")
+        }
+
+        then:
+        1 * bottom.handleIncoming("message") >> { bottomContext.dispatchIncoming("incoming1") }
+        1 * top.handleIncoming("incoming1") >> { topContext.dispatchIncoming("incoming2") }
+        1 * incoming.dispatch("incoming2") >> { dispatched.done() }
+    }
+
+    def "outgoing message dispatch by bottom protocol is dispatched to connection"() {
+        def dispatched = startsAsyncAction()
+
+        when:
+        dispatched.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> { topContext.dispatchOutgoing("outgoing1") }
+        1 * bottom.handleOutgoing("outgoing1") >> { bottomContext.dispatchOutgoing("outgoing2") }
+        1 * outgoing.dispatch("outgoing2") >> { dispatched.done() }
+    }
+
+    def "protocol callback after timeout"() {
+        Runnable callback = Mock()
+        def calledBack = startsAsyncAction()
+
+        when:
+        calledBack.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> {
+            topContext.callbackLater(500, TimeUnit.MILLISECONDS, callback)
+        }
+        1 * callback.run() >> { calledBack.done() }
+    }
+
+    def "protocol callback is not called after it is cancelled"() {
+        Runnable callback = Mock()
+        def calledBack = startsAsyncAction()
+
+        when:
+        calledBack.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> {
+            topContext.callbackLater(200, TimeUnit.MILLISECONDS, callback).cancel()
+            Thread.sleep(500)
+            calledBack.done()
+        }
+        0 * callback._
+    }
+
+    def "protocol callback with long delay is not called after protocol is stopped"() {
+        Runnable callback = Mock()
+        def callbackRegistered = startsAsyncAction()
+
+        when:
+        callbackRegistered.started {
+            stack.top.dispatch("message")
+        }
+        stack.stop()
+
+        then:
+        1 * top.handleOutgoing("message") >> {
+            topContext.callbackLater(5, TimeUnit.SECONDS, callback)
+            callbackRegistered.done()
+        }
+        0 * callback._
+    }
+
+    def "protocol callback with short delay is not called after protocol is stopped"() {
+        Runnable callback = Mock()
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            stack.stop()
+        }
+
+        then:
+        1 * top.stopRequested() >> {
+            topContext.callbackLater(0, TimeUnit.MILLISECONDS, callback)
+            stopped.done()
+        }
+        0 * callback._
+    }
+
+    def "notifies failure handler when protocol fails to handle outgoing"() {
+        def failure = new RuntimeException()
+        def notified = startsAsyncAction()
+
+        when:
+        notified.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> { throw failure }
+        1 * outgoingFailureHandler.dispatchFailed("message", failure) >> { notified.done() }
+    }
+
+    def "notifies failure handler when protocol fails to handle incoming"() {
+        def failure = new RuntimeException()
+        def notified = startsAsyncAction()
+
+        when:
+        notified.started {
+            stack.bottom.dispatch("message")
+        }
+
+        then:
+        1 * bottom.handleIncoming("message") >> { throw failure }
+        1 * incomingFailureHandler.dispatchFailed("message", failure) >> { notified.done() }
+    }
+
+    def "notifies failure handler when connection fails to dispatch outgoing"() {
+        def failure = new RuntimeException()
+        def notified = startsAsyncAction()
+
+        when:
+        notified.started {
+            stack.top.dispatch("message")
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> { topContext.dispatchOutgoing("message") }
+        1 * bottom.handleOutgoing("message") >> { bottomContext.dispatchOutgoing("message") }
+        1 * outgoing.dispatch("message") >> { throw failure }
+        1 * outgoingFailureHandler.dispatchFailed("message", failure) >> { notified.done() }
+    }
+
+    def "notifies failure handler when incoming handler throws exception"() {
+        stack.top.dispatchTo(incoming)
+        def failure = new RuntimeException()
+        def notified = startsAsyncAction()
+
+        when:
+        notified.started {
+            stack.bottom.dispatch("message")
+        }
+
+        then:
+        1 * bottom.handleIncoming("message") >> { bottomContext.dispatchIncoming("message") }
+        1 * top.handleIncoming("message") >> { topContext.dispatchIncoming("message") }
+        1 * incoming.dispatch("message") >> { throw failure }
+        1 * incomingFailureHandler.dispatchFailed("message", failure) >> { notified.done() }
+    }
+
+    def "requests protocols stop from top to bottom"() {
+        def stopRequested = startsAsyncAction()
+
+        when:
+        stopRequested.started {
+            stack.requestStop()
+        }
+
+        then:
+        1 * top.stopRequested()
+        1 * bottom.stopRequested() >> { stopRequested.done() }
+    }
+
+    def "stop blocks until protocols have stopped"() {
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            stack.stop()
+        }
+
+        then:
+        1 * top.stopRequested() >> {
+            topContext.stopLater()
+            topContext.dispatchOutgoing("stopping")
+        }
+        1 * bottom.handleOutgoing("stopping") >> { bottomContext.dispatchIncoming("ok") }
+        1 * top.handleIncoming("ok") >> { topContext.stopped() }
+        1 * bottom.stopRequested() >> {
+            stopped.done()
+        }
+    }
+
+    def "stop blocks until all incoming messages handled"() {
+        stack.top.dispatchTo(incoming)
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            stack.bottom.dispatch("message")
+            stack.stop()
+        }
+
+        then:
+        1 * bottom.handleIncoming("message") >> { bottomContext.dispatchIncoming("message") }
+        1 * top.handleIncoming("message") >> { topContext.dispatchIncoming("message") }
+        1 * incoming.dispatch("message") >> { stopped.done() }
+    }
+
+    def "stop blocks until all outgoing messages dispatched"() {
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            stack.top.dispatch("message")
+            stack.stop()
+        }
+
+        then:
+        1 * top.handleOutgoing("message") >> { topContext.dispatchOutgoing("message") }
+        1 * bottom.handleOutgoing("message") >> { bottomContext.dispatchOutgoing("message") }
+        1 * outgoing.dispatch("message") >> { stopped.done() }
+    }
+
+    def "protocols can dispatch outgoing messages on stop"() {
+        def stopped = waitsForAsyncActionToComplete()
+
+        when:
+        stopped.start {
+            stack.stop()
+        }
+
+        then:
+        1 * top.stopRequested() >> { topContext.dispatchOutgoing("top stopped") }
+        1 * bottom.handleOutgoing("top stopped") >> { bottomContext.dispatchOutgoing("top stopped") }
+        1 * bottom.stopRequested() >> { bottomContext.dispatchOutgoing("bottom stopped") }
+
+        and:
+        1 * outgoing.dispatch("top stopped")
+
+        and:
+        1 * outgoing.dispatch("bottom stopped") >> { stopped.done() }
+    }
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy
new file mode 100644
index 0000000..a379613
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.*
+
+class ReceiveProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final ReceiveProtocol protocol = new ReceiveProtocol("id", "display", "channel")
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "dispatches incoming consumer available message on start"() {
+        when:
+        protocol.start(context)
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerAvailable("id", "display", "channel"))
+        0 * context._
+    }
+
+    def "acknowledges outgoing producer ready message"() {
+        when:
+        protocol.handleIncoming(new ProducerReady("producer", "id"))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerReady("id", "producer"))
+        0 * context._
+    }
+
+    def "forwards outgoing request to consumer"() {
+        Message message = Mock()
+        def request = new Request("id", message)
+
+        when:
+        protocol.handleIncoming(request)
+
+        then:
+        1 * context.dispatchIncoming(request)
+        0 * context._
+    }
+
+    def "dispatches incoming consumer stopping to all producers on worker stop and waits for acknowledgements"() {
+        given:
+        protocol.handleIncoming(new ProducerReady("producer1", "id"))
+        protocol.handleIncoming(new ProducerReady("producer2", "id"))
+
+        when:
+        protocol.handleOutgoing(new WorkerStopping())
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopping("id", "producer1"))
+        1 * context.dispatchOutgoing(new ConsumerStopping("id", "producer2"))
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ProducerStopped("producer1", "id"))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopped("id", "producer1"))
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ProducerStopped("producer2", "id"))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopped("id", "producer2"))
+        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "acknowledges outgoing producer stopped message"() {
+        given:
+        protocol.handleIncoming(new ProducerReady("producer", "id"))
+
+        when:
+        protocol.handleIncoming(new ProducerStopped("producer", "id"))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopped("id", "producer"))
+        0 * context._
+    }
+
+    def "worker stop does not dispatch consumer stopping to producer which has stopped"() {
+        given:
+        protocol.handleIncoming(new ProducerReady("producer", "id"))
+        protocol.handleIncoming(new ProducerStopped("producer", "id"))
+
+        when:
+        protocol.handleOutgoing(new WorkerStopping())
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "worker stop does not dispatch consumer stopping to producer which becomes unavailable"() {
+        given:
+        protocol.handleIncoming(new ProducerReady("producer", "id"))
+        protocol.handleIncoming(new ProducerUnavailable("producer"))
+
+        when:
+        protocol.handleOutgoing(new WorkerStopping())
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "worker stop does not wait for producer which becomes unavailable during stop"() {
+        given:
+        protocol.handleIncoming(new ProducerReady("producer", "id"))
+        protocol.handleOutgoing(new WorkerStopping())
+
+        when:
+        protocol.handleIncoming(new ProducerUnavailable("producer"))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy
new file mode 100644
index 0000000..6079fe3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.protocol.EndOfStreamEvent
+import spock.lang.Specification
+
+class RemoteDisconnectProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final RemoteDisconnectProtocol protocol = new RemoteDisconnectProtocol()
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "stop dispatches outgoing end-of-stream request and waits for end-of-stream"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new EndOfStreamEvent())
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new EndOfStreamEvent())
+
+        then:
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new EndOfStreamEvent())
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "dispatches incoming end-of-stream when incoming disconnect request received"() {
+        when:
+        protocol.handleIncoming(new EndOfStreamEvent())
+
+        then:
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new EndOfStreamEvent())
+
+        then:
+        1 * context.dispatchOutgoing(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "stop waits until outgoing end-of-stream received when incoming previously end-of-stream received"() {
+        when:
+        protocol.handleIncoming(new EndOfStreamEvent())
+
+        then:
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new EndOfStreamEvent())
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new EndOfStreamEvent())
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stop does not wait when outgoing end-of-stream has already been received"() {
+        when:
+        protocol.handleIncoming(new EndOfStreamEvent())
+
+        then:
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new EndOfStreamEvent())
+
+        then:
+        1 * context.dispatchOutgoing(new EndOfStreamEvent())
+        0 * context._
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "discards outgoing messages after outgoing end-of-stream dispatched"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new EndOfStreamEvent())
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleOutgoing(new Message() {})
+
+        then:
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocationTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocationTest.java
deleted file mode 100644
index aaf3769..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocationTest.java
+++ /dev/null
@@ -1,37 +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.messaging.remote.internal;
-
-import org.junit.Test;
-
-import static org.gradle.util.Matchers.strictlyEqual;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertThat;
-
-public class RemoteMethodInvocationTest {
-    @Test
-    public void equalsAndHashCode() throws Exception {
-        RemoteMethodInvocation invocation = new RemoteMethodInvocation(1, new Object[]{"param"});
-        RemoteMethodInvocation equalInvocation = new RemoteMethodInvocation(1, new Object[]{"param"});
-        RemoteMethodInvocation differentMethod = new RemoteMethodInvocation(2, new Object[]{"param"});
-        RemoteMethodInvocation differentArgs = new RemoteMethodInvocation(1, new Object[]{"a", "b"});
-        assertThat(invocation, strictlyEqual(equalInvocation));
-        assertThat(invocation, not(equalTo(differentMethod)));
-        assertThat(invocation, not(equalTo(differentArgs)));
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy
new file mode 100644
index 0000000..1db4be6
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch
+import org.gradle.messaging.dispatch.DispatchFailureHandler
+import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent
+import org.gradle.messaging.remote.internal.protocol.RoutableMessage
+import org.gradle.messaging.remote.internal.protocol.RouteAvailableMessage
+import org.gradle.messaging.remote.internal.protocol.RouteUnavailableMessage
+import org.gradle.util.ConcurrentSpecification
+
+class RouterTest extends ConcurrentSpecification {
+    final DispatchFailureHandler<Message> handler = Mock()
+    final Router router = new Router(executor, handler)
+
+    def cleanup() {
+        router?.stop()
+    }
+
+    def "forwards local route available messages to remote connection"() {
+        TestRouteAvailableMessage localMessage = routeAvailable("local", [])
+        Dispatch<Message> remoteReceiver = Mock()
+        def forwarded = startsAsyncAction()
+
+        given:
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+        remote.dispatchTo(remoteReceiver)
+
+        when:
+        forwarded.started {
+            local.dispatch(localMessage)
+        }
+
+        then:
+        1 * remoteReceiver.dispatch(localMessage) >> { forwarded.done() }
+    }
+
+    def "forwards local route available messages to newly added remote connection"() {
+        TestRouteAvailableMessage localMessage = routeAvailable("local", [])
+        Dispatch<Message> remoteReceiver = Mock()
+        def forwarded = startsAsyncAction()
+
+        given:
+        def local = router.createLocalConnection()
+        local.dispatch(localMessage)
+
+        when:
+        forwarded.started {
+            def remote = router.createRemoteConnection()
+            remote.dispatchTo(remoteReceiver)
+        }
+
+        then:
+        1 * remoteReceiver.dispatch(localMessage) >> { forwarded.done() }
+    }
+
+    def "forwards remote route available messages to local route"() {
+        TestRouteAvailableMessage localMessage = routeAvailable("local", ["remote"])
+        TestRouteAvailableMessage remoteMessage = routeAvailable("remote", [])
+        Dispatch<Message> localReceiver = Mock()
+        def forwarded = startsAsyncAction()
+
+        given:
+        def local = router.createLocalConnection()
+        local.dispatchTo(localReceiver)
+        def remote = router.createRemoteConnection()
+
+        when:
+        forwarded.started {
+            local.dispatch(localMessage)
+            remote.dispatch(remoteMessage)
+        }
+
+        then:
+        1 * localReceiver.dispatch(remoteMessage) >> { forwarded.done() }
+    }
+
+    def "forwards remote route available message to newly added local route"() {
+        TestRouteAvailableMessage localMessage = routeAvailable("local", ["remote"])
+        TestRouteAvailableMessage remoteMessage = routeAvailable("remote", [])
+        Dispatch<Message> localReceiver = Mock()
+        def forwarded = startsAsyncAction()
+
+        given:
+        def local = router.createLocalConnection()
+        local.dispatchTo(localReceiver)
+        def remote = router.createRemoteConnection()
+
+        when:
+        forwarded.started {
+            remote.dispatch(remoteMessage)
+            local.dispatch(localMessage)
+        }
+
+        then:
+        1 * localReceiver.dispatch(remoteMessage) >> { forwarded.done() }
+    }
+
+    def "does not forward remote route available messages to local route which does not accept new route"() {
+        TestRouteAvailableMessage localMessage = routeAvailable("local", [])
+        TestRouteAvailableMessage remoteMessage = routeAvailable("remote", [])
+        Dispatch<Message> localReceiver = Mock()
+
+        given:
+        def local = router.createLocalConnection()
+        local.dispatchTo(localReceiver)
+        def remote = router.createRemoteConnection()
+
+        when:
+        local.dispatch(localMessage)
+        remote.dispatch(remoteMessage)
+        router.stop()
+
+        then:
+        0 * localReceiver._
+    }
+
+    def "forwards local route messages to remote route"() {
+        Dispatch<Message> remoteOutgoing = Mock()
+        def received = startsAsyncAction()
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+        def message = routeableMessage("remote")
+
+        given:
+        local.dispatch(routeAvailable("local", []))
+        remote.dispatch(routeAvailable("remote", ["local"]))
+        remote.dispatchTo(remoteOutgoing)
+
+        when:
+        received.started {
+            local.dispatch(message)
+        }
+
+        then:
+        1 * remoteOutgoing.dispatch(message) >> { received.done() }
+    }
+
+    def "broadcasts local route unavailable message to remote connections"() {
+        Dispatch<Message> remoteOutgoing = Mock()
+        def received = startsAsyncAction()
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+        def unavailable = routeUnavailable("local")
+
+        local.dispatch(routeAvailable("local", []))
+        remote.dispatchTo(remoteOutgoing)
+
+        when:
+        received.started {
+            local.dispatch(unavailable)
+        }
+
+        then:
+        1 * remoteOutgoing.dispatch({it instanceof TestRouteUnavailableMessage && it.id == 'local'}) >> { received.done() }
+    }
+
+    def "broadcasts route unavailable message on local end-of-stream"() {
+        Dispatch<Message> remoteOutgoing = Mock()
+        def received = startsAsyncAction()
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+
+        local.dispatch(routeAvailable("local", []))
+        remote.dispatchTo(remoteOutgoing)
+
+        when:
+        received.started {
+            local.dispatch(new EndOfStreamEvent())
+        }
+
+        then:
+        1 * remoteOutgoing.dispatch({it instanceof TestRouteUnavailableMessage && it.id == 'local'}) >> { received.done() }
+    }
+
+    def "does not route messages to local connection once route unavailable received from it"() {
+        Dispatch<Message> localIncoming = Mock()
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+        def available = routeAvailable("remote", [])
+        def connected = startsAsyncAction()
+        local.dispatchTo(localIncoming)
+
+        when:
+        connected.started {
+            local.dispatch(routeAvailable("local", ["remote"]))
+            remote.dispatch(available)
+        }
+
+        then:
+        1 * localIncoming.dispatch(available) >> { connected.done() }
+
+        when:
+        local.dispatch(routeUnavailable("local"))
+        remote.dispatch(new EndOfStreamEvent())
+        router.stop()
+
+        then:
+        0 * localIncoming._
+    }
+
+    def "does not route messages to local connection once end-of-stream received from it"() {
+        Dispatch<Message> localIncoming = Mock()
+        def connected = startsAsyncAction()
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+        def available = routeAvailable("remote", [])
+        local.dispatchTo(localIncoming)
+
+        when:
+        connected.started {
+            local.dispatch(routeAvailable("local", ["remote"]))
+            remote.dispatch(available)
+        }
+
+        then:
+        1 * localIncoming.dispatch(available) >> { connected.done() }
+
+        when:
+        local.dispatch(new EndOfStreamEvent())
+        remote.dispatch(routeUnavailable("remote"))
+        router.stop()
+
+        then:
+        1 * localIncoming.dispatch(new EndOfStreamEvent())
+        0 * localIncoming._
+    }
+
+    def "does not route messages to remote connection once end-of-stream received from it"() {
+        Dispatch<Message> remoteIncoming = Mock()
+        def local = router.createLocalConnection()
+        def remote = router.createRemoteConnection()
+
+        remote.dispatchTo(remoteIncoming)
+
+        when:
+        remote.dispatch(new EndOfStreamEvent())
+        local.dispatch(routeAvailable("local", []))
+        router.stop()
+
+        then:
+        1 * remoteIncoming.dispatch(new EndOfStreamEvent())
+        0 * remoteIncoming._
+    }
+
+    def routeAvailable(Object id, List<Object> accepts) {
+        return new TestRouteAvailableMessage(id, accepts)
+    }
+
+    def routeUnavailable(Object id) {
+        return new TestRouteUnavailableMessage(id)
+    }
+
+    def routeableMessage(Object destination) {
+        TestRoutableMessage message = Mock()
+        _ * message.destination >> destination
+        return message
+    }
+}
+
+abstract class TestRoutableMessage extends Message implements RoutableMessage {
+}
+
+class TestRouteUnavailableMessage extends Message implements RouteUnavailableMessage {
+    final def id
+
+    TestRouteUnavailableMessage(id) {
+        this.id = id
+    }
+}
+
+class TestRouteAvailableMessage extends Message implements RouteAvailableMessage {
+    final def id
+    final List<Object> accepts
+
+    TestRouteAvailableMessage(Object id, List<Object> accepts) {
+        this.id = id
+        this.accepts = accepts
+    }
+
+    boolean acceptIncoming(RouteAvailableMessage message) {
+        return accepts.contains(message.id)
+    }
+
+    RouteUnavailableMessage getUnavailableMessage() {
+        return new TestRouteUnavailableMessage(id)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy
new file mode 100644
index 0000000..f19abef
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.*
+
+class SendProtocolTest extends Specification {
+    final ProtocolContext<Object> context = Mock()
+    final SendProtocol protocol = new SendProtocol("id", "display", "channel")
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "dispatches outgoing producer available message on start"() {
+        when:
+        protocol.start(context)
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerAvailable("id", "display", "channel"))
+        0 * context._
+    }
+
+    def "dispatches outgoing producer ready when incoming consumer available received"() {
+        when:
+        protocol.handleIncoming(new ConsumerAvailable("consumer", "consumer-display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerReady("id", "consumer"))
+        0 * context._
+    }
+
+    def "dispatches incoming consumer available when consumer ready received"() {
+        def available = new ConsumerAvailable("consumer", "display", "channel")
+
+        given:
+        protocol.handleIncoming(available)
+
+        when:
+        protocol.handleIncoming(new ConsumerReady("consumer", "id"))
+
+        then:
+        1 * context.dispatchIncoming(available)
+        0 * context._
+    }
+
+    def "dispatches incoming consumer unavailable and outgoing producer stopped when consumer stopping received"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerStopping("consumer", "id"))
+
+        then:
+        1 * context.dispatchIncoming(new ConsumerUnavailable("consumer"))
+        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer"))
+        0 * context._
+    }
+
+    def "stop dispatches outgoing producer stopped to all consumers and waits for acknowledgement"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("consumer1", "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady("consumer1", "id"))
+        protocol.handleIncoming(new ConsumerAvailable("consumer2", "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady("consumer2", "id"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer1"))
+        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer2"))
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerStopped("consumer1", "id"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerStopped("consumer2", "id"))
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops when no consumers"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "does not dispatch stopped message to consumer which has stopped"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
+        protocol.handleIncoming(new ConsumerStopping("consumer", "id"))
+        protocol.handleIncoming(new ConsumerStopped("consumer", "id"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "handles consumer which becomes unavailable while waiting for consumer ready"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable("consumer"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "handles consumer which becomes unavailable while waiting for consumer stopped"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady("consumer", "id"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer"))
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable("consumer"))
+
+        then:
+        1 * context.dispatchIncoming(new ConsumerUnavailable("consumer"))
+        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "handles consumer which becomes unavailable without consumer stopping message received"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady("consumer", "id"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable("consumer"))
+
+        then:
+        1 * context.dispatchIncoming(new ConsumerUnavailable("consumer"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
deleted file mode 100644
index 2c32462..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
+++ /dev/null
@@ -1,49 +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.messaging.remote.internal
-
-import org.gradle.api.Action
-import org.gradle.util.MultithreadedTestCase
-import org.junit.Test
-import static org.junit.Assert.*
-
-class TcpConnectorTest extends MultithreadedTestCase {
-    @Test
-    public void canConnectToServer() {
-        TcpOutgoingConnector outgoingConnector = new TcpOutgoingConnector(getClass().classLoader)
-        TcpIncomingConnector incomingConnector = new TcpIncomingConnector(executorFactory, getClass().classLoader)
-
-        Action action = { syncAt(1) } as Action
-        def address = incomingConnector.accept(action)
-
-        def connection = outgoingConnector.connect(address)
-        assertNotNull(connection)
-        run { syncAt(1) }
-
-        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/core/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy
new file mode 100644
index 0000000..0bb563d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2011 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 spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.ConsumerAvailable
+import org.gradle.messaging.remote.internal.protocol.Request
+import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable
+
+class UnicastSendProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final UnicastSendProtocol protocol = new UnicastSendProtocol()
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "queues outgoing messages until a consumer is available"() {
+        when:
+        protocol.handleOutgoing(new Request("channel", "message1"))
+        protocol.handleOutgoing(new Request("channel", "message2"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request("id", "message1"))
+        1 * context.dispatchOutgoing(new Request("id", "message2"))
+        0 * context._
+    }
+
+    def "forwards messages when a consumer is available"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request("id", "message"))
+    }
+
+    def "stop waits until a consumer is available and messages dispatched"() {
+        given:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channnel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request("id", "message"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when no messages have been dispatched"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when all messages have been dispatched"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "discards messages after consumer becomes unavailable"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+        protocol.handleIncoming(new ConsumerUnavailable("id"))
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stop ignores consumer unavailable when everything dispatched"() {
+        given:
+        protocol.handleOutgoing(new Request("channel", "message"))
+        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable("id"))
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy
new file mode 100644
index 0000000..6c88aed
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 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.messaging.dispatch.Dispatch
+import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent
+import org.gradle.messaging.remote.internal.protocol.Request
+import org.gradle.messaging.remote.internal.protocol.WorkerStopping
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.MessageCredits
+
+class WorkerProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final Dispatch<Object> worker = Mock()
+    final WorkerProtocol protocol = new WorkerProtocol(worker)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "dispatches outgoing request credit on start"() {
+        when:
+        protocol.start(context)
+
+        then:
+        1 * context.dispatchOutgoing(new MessageCredits(1))
+        0 * context._
+    }
+
+    def "dispatches incoming request to worker and outgoing response when complete"() {
+        when:
+        protocol.handleIncoming(new Request("id", "message"))
+
+        then:
+        1 * worker.dispatch("message")
+        1 * context.dispatchOutgoing(new MessageCredits(1))
+        0 * context._
+    }
+
+    def "dispatches response when dispatch to worker fails"() {
+        def failure = new RuntimeException()
+
+        when:
+        protocol.handleIncoming(new Request("id", "message"))
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * worker.dispatch("message") >> { throw failure }
+        1 * context.dispatchOutgoing(new MessageCredits(1))
+        0 * context._
+    }
+
+    def "dispatches outgoing worker stopping on stop and waits for acknowledgement"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new WorkerStopping())
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new EndOfStreamEvent())
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "continues to dispatch incoming requests while waiting for stop acknowledgement"() {
+        given:
+        protocol.stopRequested()
+
+        when:
+        protocol.handleIncoming(new Request("id", "message"))
+
+        then:
+        1 * worker.dispatch("message")
+        1 * context.dispatchOutgoing(new MessageCredits(1))
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy
new file mode 100644
index 0000000..b2b4bab
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.inet
+
+import org.gradle.util.Matchers
+import spock.lang.Specification
+
+class MultiChoiceAddressTest extends Specification {
+    def "has useful display name"() {
+        InetAddress candidate = Mock()
+        def address = new MultiChoiceAddress('<canonical>', 1234, [candidate])
+
+        given:
+        candidate.toString() >> '<address>'
+
+        expect:
+        address.displayName == '[<canonical> port:1234, addresses:[<address>]]'
+        address.toString() == '[<canonical> port:1234, addresses:[<address>]]'
+    }
+
+    def "addresses are equal when their canonical addresses are equal"() {
+        InetAddress address1 = Mock()
+        InetAddress address2 = Mock()
+        def address = new MultiChoiceAddress('canonical', 1234, [address1])
+        def same = new MultiChoiceAddress('canonical', 1234, [address1])
+        def differentPort = new MultiChoiceAddress('canonical', 1567, [address1])
+        def differentCandidates = new MultiChoiceAddress('canonical', 1234, [address2])
+        def differentCanonical = new MultiChoiceAddress('other', 1234, [address1])
+
+        expect:
+        address Matchers.strictlyEqual(same)
+        address Matchers.strictlyEqual(differentCandidates)
+        address Matchers.strictlyEqual(differentPort)
+        address != differentCanonical
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy
new file mode 100644
index 0000000..bd223d4
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.inet
+
+import spock.lang.Specification
+import org.gradle.util.Matchers
+
+class SocketInetAddressTest extends Specification {
+    def localhost = java.net.InetAddress.getByName("localhost")
+
+    def "has useful display name"() {
+        def address = new SocketInetAddress(localhost, 234)
+
+        expect:
+        address.displayName == "localhost/${localhost.hostAddress}:234"
+        address.toString() == "localhost/${localhost.hostAddress}:234"
+    }
+
+    def "equal when address and port are equal"() {
+        def address = new SocketInetAddress(localhost, 234)
+        def same = new SocketInetAddress(localhost, 234)
+        def differentPort = new SocketInetAddress(localhost, 45)
+        def differentAddress = new SocketInetAddress(java.net.InetAddress.getByName("192.168.1.1"), 45)
+
+        expect:
+        address Matchers.strictlyEqual(same)
+        address != differentAddress
+        address != differentPort
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
new file mode 100644
index 0000000..cd35ce5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * 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.messaging.remote.internal.inet
+
+import java.util.concurrent.atomic.AtomicInteger
+import org.gradle.api.Action
+import org.gradle.api.logging.Logging
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer
+import org.gradle.util.ConcurrentSpecification
+import org.gradle.util.UUIDGenerator
+import spock.lang.Ignore
+import spock.lang.Timeout
+import static java.util.Collections.synchronizedList
+
+class TcpConnectorConcurrencyTest extends ConcurrentSpecification {
+
+    final static LOGGER = Logging.getLogger(TcpConnectorConcurrencyTest)
+
+    //sharing serializer adds extra flavor...
+    final serializer = new DefaultMessageSerializer<Object>(getClass().classLoader)
+    final outgoingConnector = new TcpOutgoingConnector<Object>(serializer)
+    final incomingConnector = new TcpIncomingConnector<Object>(executorFactory, serializer, new InetAddressFactory(), new UUIDGenerator())
+
+    @Timeout(60)
+    @Ignore
+    //TODO SF exposes concurrency issue
+    def "can dispatch from multiple threads"() {
+        def number = new AtomicInteger(1)
+        def threads = 20
+        def messages = synchronizedList([])
+
+        Action action = new Action() {
+            void execute(event) {
+                while (true) {
+                    def message = event.connection.receive()
+                    LOGGER.debug("*** received: $message")
+                    messages << message
+                    if (messages.size() == threads || message == null) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        def address = incomingConnector.accept(action, false)
+        def connection = outgoingConnector.connect(address)
+
+        when:
+        def all = []
+        threads.times {
+            all << start {
+                //exceptions carry lots of information so serialization/deserialization is slower
+                //and hence better chance of reproducing the concurrency bugs
+                def message = new RuntimeException("Message #" + number.getAndIncrement())
+                connection.dispatch(message)
+                LOGGER.debug("*** dispatched: $message")
+            }
+        }
+
+        all*.completed()
+
+        then:
+        //let's give some time for the messages to arrive to the sink
+        poll(20) {
+            messages.size() == threads
+            messages.each { it.toString().contains("Message #") }
+        }
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
new file mode 100644
index 0000000..1105f66
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * 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.inet
+
+import org.gradle.api.Action
+import org.gradle.messaging.remote.internal.ConnectException
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer
+import org.gradle.util.ConcurrentSpecification
+import org.gradle.util.UUIDGenerator
+
+class TcpConnectorTest extends ConcurrentSpecification {
+    final def serializer = new DefaultMessageSerializer<String>(getClass().classLoader)
+    final def idGenerator = new UUIDGenerator()
+    final def addressFactory = new InetAddressFactory()
+    final def outgoingConnector = new TcpOutgoingConnector<String>(serializer)
+    final def incomingConnector = new TcpIncomingConnector<String>(executorFactory, serializer, addressFactory, idGenerator)
+
+    def "client can connect to server"() {
+        Action action = Mock()
+
+        when:
+        def address = incomingConnector.accept(action, false)
+        def connection = outgoingConnector.connect(address)
+
+        then:
+        connection != null
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+
+    def "client can connect to server using remote addresses"() {
+        Action action = Mock()
+
+        when:
+        def address = incomingConnector.accept(action, true)
+        def connection = outgoingConnector.connect(address)
+
+        then:
+        connection != null
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+
+    def "server executes action when incoming connection received"() {
+        def connectionReceived = startsAsyncAction()
+        Action action = Mock()
+
+        when:
+        connectionReceived.started {
+            def address = incomingConnector.accept(action, false)
+            outgoingConnector.connect(address)
+        }
+
+        then:
+        1 * action.execute(!null) >> { connectionReceived.done() }
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+
+    def "client throws exception when cannot connect to server"() {
+        def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost")])
+
+        when:
+        outgoingConnector.connect(address)
+
+        then:
+        ConnectException e = thrown()
+        e.message.startsWith "Could not connect to server ${address}."
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy
new file mode 100644
index 0000000..34496bb
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 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.protocol
+
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.Message
+
+class AbstractPayloadMessageTest extends Specification {
+    def "can get payload"() {
+        def message = new TestPayloadMessage("payload")
+
+        expect:
+        message.nestedPayload == "payload"
+    }
+
+    def "can get nested payload"() {
+        def nested = new TestPayloadMessage("payload")
+        def message = new TestPayloadMessage(nested)
+
+        expect:
+        message.nestedPayload == "payload"
+    }
+
+    def "can create copy with new payload"() {
+        def message = new TestPayloadMessage("payload")
+
+        expect:
+        def copy = message.withNestedPayload("new")
+        copy instanceof TestPayloadMessage
+        copy.nestedPayload == "new"
+    }
+
+    def "can create nested copy with new payload"() {
+        def nested = new TestPayloadMessage("payload")
+        def message = new TestPayloadMessage(nested)
+
+        expect:
+        def copy = message.withNestedPayload("new")
+        copy instanceof TestPayloadMessage
+        copy.payload instanceof  TestPayloadMessage
+        copy.payload.payload == "new"
+    }
+}
+
+class TestPayloadMessage extends AbstractPayloadMessage {
+    final Object payload;
+
+    TestPayloadMessage(Object payload) {
+        this.payload = payload
+    }
+
+    @Override
+    Message withPayload(Object payload) {
+        return new TestPayloadMessage(payload)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
new file mode 100644
index 0000000..7cbb932
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 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.protocol
+
+import org.gradle.messaging.remote.internal.inet.MultiChoiceAddress
+import org.gradle.util.UUIDGenerator
+import spock.lang.Shared
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.MessageOriginator
+import org.gradle.messaging.remote.internal.inet.SocketInetAddress
+
+class DiscoveryProcotolSerializerTest extends Specification {
+    final DiscoveryProtocolSerializer serializer = new DiscoveryProtocolSerializer()
+    @Shared def uuidGenerator = new UUIDGenerator()
+    @Shared MessageOriginator messageOriginator = new MessageOriginator(uuidGenerator.generateId(), "source display name")
+    @Shared InetAddress address = InetAddress.getByName(null)
+    final InetAddress receivedAddress = Mock()
+
+    def "writes and reads message types"() {
+        when:
+        def result = send(original)
+
+        then:
+        result == original
+
+        where:
+        original << [
+                new LookupRequest(messageOriginator, "group", "channel"),
+                new ChannelUnavailable(messageOriginator, "group", "channel", new MultiChoiceAddress(UUID.randomUUID(), 8091, [address]))
+        ]
+    }
+
+    def "mixes in remote address to received ChannelAvailable message"() {
+        def originatorId = UUID.randomUUID()
+        def original = new ChannelAvailable(messageOriginator, "group", "channel", new MultiChoiceAddress(originatorId, 8091, [address]))
+        def expected = new ChannelAvailable(messageOriginator, "group", "channel", new MultiChoiceAddress(originatorId, 8091, [receivedAddress, address]))
+
+        when:
+        def result = send(original)
+
+        then:
+        result == expected
+    }
+
+    def "can read message for unknown protocol version"() {
+        expect:
+        def result = send { outstr ->
+            outstr.writeByte(90)
+        }
+        result instanceof UnknownMessage
+        result.toString() == "unknown protocol version 90"
+    }
+
+    def "can read unknown message type"() {
+        expect:
+        def result = send { outstr ->
+            outstr.writeByte(DiscoveryProtocolSerializer.PROTOCOL_VERSION);
+            outstr.writeByte(90)
+        }
+        result instanceof UnknownMessage
+        result.toString() == "unknown message type 90"
+    }
+
+    def send(Closure cl) {
+        def bytesOut = new ByteArrayOutputStream()
+        def outstr = new DataOutputStream(bytesOut)
+        cl.call(outstr)
+        outstr.close()
+
+        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
+        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
+    }
+
+    def send(DiscoveryMessage message) {
+        def bytesOut = new ByteArrayOutputStream()
+        def outstr = new DataOutputStream(bytesOut)
+        serializer.write(message, outstr)
+        outstr.close()
+
+        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
+        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java
new file mode 100755
index 0000000..d888116
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.protocol;
+
+import org.junit.Test;
+
+import static org.gradle.util.Matchers.strictlyEqual;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+public class RemoteMethodInvocationTest {
+    @Test
+    public void equalsAndHashCode() throws Exception {
+        RemoteMethodInvocation invocation = new RemoteMethodInvocation(1, new Object[]{"param"});
+        RemoteMethodInvocation equalInvocation = new RemoteMethodInvocation(1, new Object[]{"param"});
+        RemoteMethodInvocation differentMethod = new RemoteMethodInvocation(2, new Object[]{"param"});
+        RemoteMethodInvocation differentArgs = new RemoteMethodInvocation(1, new Object[]{"a", "b"});
+        assertThat(invocation, strictlyEqual(equalInvocation));
+        assertThat(invocation, not(equalTo(differentMethod)));
+        assertThat(invocation, not(equalTo(differentArgs)));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
index 89b00d5..16422ab 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
@@ -16,8 +16,9 @@
 
 package org.gradle.process.internal;
 
+import org.apache.commons.io.output.CloseShieldOutputStream;
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.ExecResult;
-import org.gradle.util.Jvm;
 import org.gradle.util.TemporaryFolder;
 import org.junit.Rule;
 import org.junit.Test;
@@ -74,8 +75,8 @@ public class DefaultExecHandleTest {
                         "-cp",
                         System.getProperty("java.class.path"),
                         BrokenApp.class.getName()), System.getenv(),
-                System.out,
-                System.err,
+                new CloseShieldOutputStream(System.out),
+                new CloseShieldOutputStream(System.err),
                 new ByteArrayInputStream(new byte[0]),
                 Collections.<ExecHandleListener>emptyList()
         );
@@ -123,8 +124,8 @@ public class DefaultExecHandleTest {
                         "-cp",
                         System.getProperty("java.class.path"),
                         SlowApp.class.getName()), System.getenv(),
-                System.out,
-                System.err,
+                new CloseShieldOutputStream(System.out),
+                new CloseShieldOutputStream(System.err),
                 new ByteArrayInputStream(new byte[0]),
                 Collections.<ExecHandleListener>emptyList()
         );
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
index 10aeecd..701bf58 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
@@ -17,11 +17,14 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.Action;
+import org.gradle.util.ClassPath;
 import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.inet.SocketInetAddress;
 import org.gradle.process.internal.child.IsolatedApplicationClassLoaderWorker;
 import org.gradle.process.internal.launcher.GradleWorkerMain;
 import org.gradle.util.IdGenerator;
@@ -35,13 +38,13 @@ import org.junit.runner.RunWith;
 import java.io.File;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
-import java.net.URI;
+import java.net.InetAddress;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Set;
 
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
 
 @RunWith(JMock.class)
 public class DefaultWorkerProcessFactoryTest {
@@ -56,9 +59,12 @@ public class DefaultWorkerProcessFactoryTest {
     @Test
     public void createsAndConfiguresAWorkerProcess() throws Exception {
         final Set<File> processClassPath = Collections.singleton(new File("something.jar"));
+        final ClassPath classPath = context.mock(ClassPath.class);
 
         context.checking(new Expectations() {{
-            one(classPathRegistry).getClassPathFiles("WORKER_PROCESS");
+            one(classPathRegistry).getClassPath("WORKER_PROCESS");
+            will(returnValue(classPath));
+            allowing(classPath).getAsFiles();
             will(returnValue(processClassPath));
             allowing(fileResolver).resolveLater(".");
             allowing(fileResolver).resolveFiles(with(Matchers.<Object>notNullValue()));
@@ -74,7 +80,7 @@ public class DefaultWorkerProcessFactoryTest {
         builder.applicationClasspath(Arrays.asList(new File("app.jar")));
         builder.sharedPackages("package1", "package2");
 
-        final URI serverAddress = new URI("test:something");
+        final Address serverAddress = new SocketInetAddress(InetAddress.getByName("127.0.0.1"), 40);
 
         context.checking(new Expectations(){{
             one(messagingServer).accept(with(notNullValue(Action.class)));
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
index f972cb0..53c3f7f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
@@ -16,13 +16,13 @@
 package org.gradle.process.internal;
 
 
-import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.file.FileResolver
 import org.gradle.api.internal.file.IdentityFileResolver
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection
-import org.gradle.util.Jvm
+import org.gradle.internal.jvm.Jvm
 import spock.lang.Specification
 import static java.util.Arrays.asList
+import java.nio.charset.Charset
+import spock.lang.Unroll
 
 public class JavaExecHandleBuilderTest extends Specification {
     FileResolver fileResolver = new IdentityFileResolver()
@@ -36,30 +36,41 @@ public class JavaExecHandleBuilderTest extends Specification {
         thrown(UnsupportedOperationException)
     }
 
+    @Unroll("buildsCommandLineForJavaProcess - input encoding #inputEncoding")
     public void buildsCommandLineForJavaProcess() {
         File jar1 = new File("file1.jar").canonicalFile
         File jar2 = new File("file2.jar").canonicalFile
 
-        FileCollection classpath = new DefaultConfigurableFileCollection(fileResolver, null, jar1, jar2)
-
         builder.main = 'mainClass'
         builder.args("arg1", "arg2")
         builder.jvmArgs("jvm1", "jvm2")
         builder.classpath(jar1, jar2)
         builder.systemProperty("prop", "value")
+        builder.minHeapSize = "64m"
+        builder.maxHeapSize = "1g"
+        builder.defaultCharacterEncoding = inputEncoding
 
         when:
         List jvmArgs = builder.getAllJvmArgs()
 
         then:
-        jvmArgs == ['jvm1', 'jvm2', '-Dprop=value', '-cp', "$jar1$File.pathSeparator$jar2"]
+        jvmArgs == ['-Dprop=value', 'jvm1', 'jvm2', '-Xms64m', '-Xmx1g', fileEncodingProperty(expectedEncoding), '-cp', "$jar1$File.pathSeparator$jar2"]
 
         when:
         List commandLine = builder.getCommandLine()
 
         then:
         String executable = Jvm.current().getJavaExecutable().getAbsolutePath()
-        commandLine == [executable, 'jvm1', 'jvm2', '-Dprop=value', '-cp', "$jar1$File.pathSeparator$jar2",
-                'mainClass', 'arg1', 'arg2']
+        commandLine == [executable,  '-Dprop=value', 'jvm1', 'jvm2', '-Xms64m', '-Xmx1g', fileEncodingProperty(expectedEncoding),
+                '-cp', "$jar1$File.pathSeparator$jar2", 'mainClass', 'arg1', 'arg2']
+        
+        where:
+        inputEncoding | expectedEncoding
+        null          | Charset.defaultCharset().name()
+        "UTF-16"      | "UTF-16"
+    }
+
+    private String fileEncodingProperty(String encoding = Charset.defaultCharset().name()) {
+        return "-Dfile.encoding=$encoding"
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
new file mode 100644
index 0000000..1a3e447
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * 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.process.internal
+
+import org.gradle.api.internal.file.IdentityFileResolver
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 2/13/12
+ */
+class JvmOptionsTest extends Specification {
+    
+    def "reads options from String"() {
+        expect:
+        JvmOptions.fromString("") == []
+        JvmOptions.fromString("-Xmx512m") == ["-Xmx512m"]
+        JvmOptions.fromString("\t-Xmx512m\n") == ["-Xmx512m"]
+        JvmOptions.fromString(" -Xmx512m   -Dfoo=bar\n-XDebug  ") == ["-Xmx512m", "-Dfoo=bar", "-XDebug"]
+    }
+
+    def "reads quoted options from String"() {
+        expect:
+        JvmOptions.fromString("-Dfoo=bar -Dfoo2=\"hey buddy\" -Dfoo3=baz") ==
+                ["-Dfoo=bar", "-Dfoo2=hey buddy", "-Dfoo3=baz"]
+
+        JvmOptions.fromString("  -Dfoo=\" bar \"  " ) == ["-Dfoo= bar "]
+        JvmOptions.fromString("  -Dx=\"\"  -Dy=\"\n\" " ) == ["-Dx=", "-Dy=\n"]
+        JvmOptions.fromString(" \"-Dx= a b c \" -Dy=\" x y z \" ") == ["-Dx= a b c ", "-Dy= x y z "]
+    }
+    
+    def "understands quoted system properties and jvm opts"() {
+        expect:
+        parse("  -Dfoo=\" hey man! \"  " ).getSystemProperties().get("foo") == " hey man! "
+    }
+
+    def "understands 'empty' system properties and jvm opts"() {
+        expect:
+        parse("-Dfoo= -Dbar -Dbaz=\"\"" ).getSystemProperties() == [foo: '', bar: '', baz: '']
+        parse("-XXfoo=").allJvmArgs.contains('-XXfoo=')
+        parse("-XXbar=\"\"").allJvmArgs.contains('-XXbar=')
+    }
+
+    def "understands quoted jvm options"() {
+        expect:
+        parse('  -XX:HeapDumpPath="/tmp/with space" ').jvmArgs.contains('-XX:HeapDumpPath=/tmp/with space')
+    }
+
+    def "can parse file encoding property"() {
+        expect:
+        parse("-Dfile.encoding=UTF-8 -Dfoo.encoding=blah -Dfile.encoding=UTF-16").defaultCharacterEncoding == "UTF-16"
+    }
+
+    def "system properties are always before the symbolic arguments"() {
+        expect:
+        parse("-Xms1G -Dfile.encoding=UTF-8 -Dfoo.encoding=blah -Dfile.encoding=UTF-16").allJvmArgs == ["-Dfoo.encoding=blah", "-Xms1G", "-Dfile.encoding=UTF-16"]
+    }
+
+    def "provides managed jvm args"() {
+        expect:
+        parse("-Xms1G -XX:-PrintClassHistogram -Dfile.encoding=UTF-8 -Dfoo.encoding=blah").managedJvmArgs == ["-Xms1G", "-Dfile.encoding=UTF-8"]
+    }
+
+    private JvmOptions createOpts() {
+        return new JvmOptions(new IdentityFileResolver())
+    }
+    
+    private JvmOptions parse(String optsString) {
+        def opts = createOpts()
+        opts.jvmArgs(JvmOptions.fromString(optsString))
+        opts
+    }
+    
+    
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
index 2b58ce1..1cc3a33 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
@@ -17,9 +17,12 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
+import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingClient;
 import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.MessagingServices;
 import org.gradle.process.internal.WorkerProcessContext;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -33,29 +36,37 @@ import static org.junit.Assert.fail;
 
 @RunWith(JMock.class)
 public class ActionExecutionWorkerTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final Action<WorkerProcessContext> action = context.mock(Action.class);
+    private final ObjectConnection connection = context.mock(ObjectConnection.class);
+    private final MessagingServices messagingServices = context.mock(MessagingServices.class);
     private final MessagingClient client = context.mock(MessagingClient.class);
     private final WorkerContext workerContext = context.mock(WorkerContext.class);
+    private final Address serverAddress = context.mock(Address.class);
     private final ClassLoader appClassLoader = new ClassLoader() {
     };
-    private final ActionExecutionWorker main = new ActionExecutionWorker(action, 12, "<display name>", null) {
+    private final ActionExecutionWorker main = new ActionExecutionWorker(action, 12, "<display name>", serverAddress) {
         @Override
-        MessagingClient createClient() {
-            return client;
+        MessagingServices createClient() {
+            return messagingServices;
         }
     };
 
     @Test
     public void createsConnectionAndExecutesAction() throws Exception {
-        final ObjectConnection connection = context.mock(ObjectConnection.class);
         final Collector<WorkerProcessContext> collector = collector();
 
         context.checking(new Expectations() {{
+            allowing(messagingServices).get(MessagingClient.class);
+            will(returnValue(client));
+
+            one(client).getConnection(serverAddress);
+            will(returnValue(connection));
+
             one(action).execute(with(notNullValue(WorkerProcessContext.class)));
             will(collectTo(collector));
 
-            one(client).stop();
+            one(messagingServices).stop();
         }});
 
         main.execute(workerContext);
@@ -63,8 +74,6 @@ public class ActionExecutionWorkerTest {
         context.checking(new Expectations() {{
             allowing(workerContext).getApplicationClassLoader();
             will(returnValue(appClassLoader));
-            allowing(client).getConnection();
-            will(returnValue(connection));
         }});
 
         assertThat(collector.get().getServerConnection(), sameInstance(connection));
@@ -77,12 +86,17 @@ public class ActionExecutionWorkerTest {
     public void cleansUpWhenActionThrowsException() throws Exception {
         final RuntimeException failure = new RuntimeException();
 
-
         context.checking(new Expectations() {{
+            allowing(messagingServices).get(MessagingClient.class);
+            will(returnValue(client));
+
+            one(client).getConnection(serverAddress);
+            will(returnValue(connection));
+
             one(action).execute(with(notNullValue(WorkerProcessContext.class)));
             will(throwException(failure));
 
-            one(client).stop();
+            one(messagingServices).stop();
         }});
 
         try {
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy
new file mode 100644
index 0000000..869df3b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 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.process.internal.child
+
+import org.gradle.util.RedirectStdIn
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+import java.security.AllPermission
+import java.security.Permission
+
+class BootstrapSecurityManagerTest extends Specification {
+    @Rule SetSystemProperties systemProperties
+    @Rule RedirectStdIn stdIn
+
+    def cleanup() {
+        System.securityManager = null
+    }
+
+    def "reads classpath from System.in and sets up system classpath on first permission check"() {
+        def entry1 = new File("a.jar")
+        def entry2 = new File("b.jar")
+        TestClassLoader cl = Mock()
+
+        given:
+        System.in = createStdInContent(entry1, entry2)
+
+        when:
+        def securityManager = new BootstrapSecurityManager(cl)
+
+        then:
+        0 * cl._
+
+        when:
+        securityManager.checkPermission(new AllPermission())
+
+        then:
+        1 * cl.addURL(entry1.toURI().toURL())
+        1 * cl.addURL(entry2.toURI().toURL())
+        0 * cl._
+        System.getProperty("java.class.path") == [entry1.absolutePath, entry2.absolutePath].join(File.pathSeparator)
+
+        when:
+        securityManager.checkPermission(new AllPermission())
+
+        then:
+        0 * cl._
+        System.getProperty("java.class.path") == [entry1.absolutePath, entry2.absolutePath].join(File.pathSeparator)
+    }
+
+    def "installs custom SecurityManager"() {
+        System.setProperty("org.gradle.security.manager", TestSecurityManager.class.name)
+        URLClassLoader cl = new URLClassLoader([] as URL[], getClass().classLoader)
+
+        given:
+        System.in = createStdInContent()
+
+        when:
+        def securityManager = new BootstrapSecurityManager(cl)
+        securityManager.checkPermission(new AllPermission())
+
+        then:
+        System.securityManager instanceof TestSecurityManager
+    }
+
+    def createStdInContent(File... classpath) {
+        def out = new ByteArrayOutputStream()
+        def dataOut = new DataOutputStream(out)
+        dataOut.writeInt(classpath.length)
+        classpath.each { dataOut.writeUTF(it.absolutePath) }
+        return new ByteArrayInputStream(out.toByteArray())
+    }
+
+    static class TestClassLoader extends URLClassLoader {
+        TestClassLoader(URL[] urls) {
+            super(urls)
+        }
+
+        @Override
+        void addURL(URL url) {
+        }
+    }
+
+    static class TestSecurityManager extends SecurityManager {
+        @Override
+        void checkPermission(Permission permission) {
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
index 80a6de1..9572957 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
@@ -20,7 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.ObservableUrlClassLoader;
+import org.gradle.util.MutableURLClassLoader;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -39,7 +39,7 @@ public class ImplementationClassLoaderWorkerTest {
     private final JUnit4Mockery context = new JUnit4GroovyMockery();
     private final ClassLoader applicationClassLoader = getClass().getClassLoader();
     private final LoggingManagerInternal loggingManager = context.mock(LoggingManagerInternal.class);
-    private final ObservableUrlClassLoader implementationClassLoader = new ObservableUrlClassLoader(applicationClassLoader);
+    private final MutableURLClassLoader implementationClassLoader = new MutableURLClassLoader(applicationClassLoader);
     private final WorkerContext workerContext = context.mock(WorkerContext.class);
     private final SerializableMockHelper helper = new SerializableMockHelper();
 
@@ -76,7 +76,7 @@ public class ImplementationClassLoaderWorkerTest {
         }
 
         @Override
-        protected ObservableUrlClassLoader createImplementationClassLoader(ClassLoader system,
+        protected MutableURLClassLoader createImplementationClassLoader(ClassLoader system,
                                                                            ClassLoader application) {
             return implementationClassLoader;
         }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
index 842d231..065970d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
@@ -22,11 +22,14 @@ import org.junit.Rule
 import org.gradle.cache.CacheRepository
 import org.gradle.cache.CacheBuilder
 import org.gradle.cache.PersistentCache
+import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.api.internal.classpath.ModuleRegistry
 
 class WorkerProcessClassPathProviderTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
-    private final CacheRepository cacheRepository = Mock()
-    private final WorkerProcessClassPathProvider provider = new WorkerProcessClassPathProvider(cacheRepository)
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final CacheRepository cacheRepository = Mock()
+    final ModuleRegistry moduleRegistry = Mock()
+    final WorkerProcessClassPathProvider provider = new WorkerProcessClassPathProvider(cacheRepository, moduleRegistry)
 
     def returnsNullForUnknownClasspath() {
         expect:
@@ -36,27 +39,27 @@ class WorkerProcessClassPathProviderTest extends Specification {
     def createsTheWorkerClasspathOnDemand() {
         def cacheDir = tmpDir.dir
         def classesDir = cacheDir.file('classes')
-        CacheBuilder cacheBuilder = Mock()
+        DirectoryCacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
+        def initializer = null
 
         when:
         def classpath = provider.findClassPath('WORKER_MAIN')
 
         then:
         1 * cacheRepository.cache('workerMain') >> cacheBuilder
-        1 * cacheBuilder.open() >> cache
-        1 * cache.isValid() >> false
-        1 * cache.markValid()
+        1 * cacheBuilder.withInitializer(!null) >> { args -> initializer = args[0]; return cacheBuilder }
+        1 * cacheBuilder.open() >> { initializer.execute(cache); return cache }
         _ * cache.getBaseDir() >> cacheDir
         0 * cache._
-        classpath == [classesDir] as Set
+        classpath.asFiles == [classesDir]
         classesDir.listFiles().length != 0
     }
 
-    def reusesTheCacheClasspath() {
+    def reusesTheCachedClasspath() {
         def cacheDir = tmpDir.dir
         def classesDir = cacheDir.file('classes')
-        CacheBuilder cacheBuilder = Mock()
+        DirectoryCacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
 
         when:
@@ -64,10 +67,10 @@ class WorkerProcessClassPathProviderTest extends Specification {
 
         then:
         1 * cacheRepository.cache('workerMain') >> cacheBuilder
+        1 * cacheBuilder.withInitializer(!null) >> cacheBuilder
         1 * cacheBuilder.open() >> cache
-        1 * cache.isValid() >> true
         _ * cache.getBaseDir() >> cacheDir
         0 * cache._
-        classpath == [classesDir] as Set
+        classpath.asFiles == [classesDir]
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy b/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy
new file mode 100644
index 0000000..68a51da
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/profile/BuildProfileTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.profile
+
+import spock.lang.Specification
+import org.gradle.api.invocation.Gradle
+import org.gradle.api.artifacts.ResolvableDependencies
+import org.gradle.api.Project
+
+class BuildProfileTest extends Specification {
+    final Gradle gradle = Mock()
+    final BuildProfile profile = new BuildProfile(gradle)
+
+    def "creates dependency set profile on first get"() {
+        given:
+        ResolvableDependencies deps = dependencySet("path")
+
+        expect:
+        def dependencyProfile = profile.getDependencySetProfile(deps)
+        dependencyProfile != null
+        profile.getDependencySetProfile(deps) == dependencyProfile
+    }
+
+    def "can get all dependency set profiles"() {
+        given:
+        def a = profile.getDependencySetProfile(dependencySet("a"))
+        def b = profile.getDependencySetProfile(dependencySet("b"))
+
+        expect:
+        profile.dependencySets.operations == [a, b]
+    }
+
+    def "can get all project configuration profiles"() {
+        given:
+        def a = profile.getProjectProfile(project("a"))
+        def b = profile.getProjectProfile(project("b"))
+
+        expect:
+        profile.projectConfiguration.operations == [a.evaluation, b.evaluation]
+    }
+
+    def dependencySet(String path) {
+        ResolvableDependencies dependencies = Mock()
+        _ * dependencies.path >> path
+        return dependencies
+    }
+
+    def project(String path) {
+        Project project = Mock()
+        _ * project.path >> path
+        return project
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/profile/CompositeOperationTest.groovy b/subprojects/core/src/test/groovy/org/gradle/profile/CompositeOperationTest.groovy
new file mode 100644
index 0000000..27bf071
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/profile/CompositeOperationTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.profile
+
+import spock.lang.Specification
+
+class CompositeOperationTest extends Specification {
+
+    def "execution time is sum of execution time of child operations"() {
+        given:
+        Operation child1 = operation(12)
+        Operation child2 = operation(10)
+        def operation = new CompositeOperation<Operation>([child1, child2])
+
+        expect:
+        operation.elapsedTime == 22
+    }
+
+    def operation(long elapsedTime) {
+        Operation operation = Mock()
+        _ * operation.elapsedTime >> elapsedTime
+        return operation
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/DurationFormatterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/DurationFormatterTest.groovy
new file mode 100644
index 0000000..3f7c89a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/DurationFormatterTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.reporting
+
+import spock.lang.Specification
+
+class DurationFormatterTest extends Specification {
+    final DurationFormatter formatter = new DurationFormatter()
+
+    def formatsShortDurations() {
+        expect:
+        formatter.format(0) == '0s'
+        formatter.format(7) == '0.007s'
+        formatter.format(1200) == '1.200s'
+        formatter.format(59202) == '59.202s'
+    }
+
+    def formatsLongDuration() {
+        expect:
+        formatter.format(60 * 1000) == '1m0.00s'
+        formatter.format(60 * 1000 + 12 * 1000 + 310) == '1m12.31s'
+        formatter.format(23 * 60 * 1000 + 12 * 1000 + 310) == '23m12.31s'
+        formatter.format(23 * 60 * 1000 + 310) == '23m0.31s'
+
+        and:
+        formatter.format(60 * 60 * 1000) == '1h0m0.00s'
+        formatter.format(60 * 60 * 1000 + 20) == '1h0m0.02s'
+
+        and:
+        formatter.format(24 * 60 * 60 * 1000) == '1d0h0m0.00s'
+        formatter.format(24 * 60 * 60 * 1000 + 23 * 1000) == '1d0h0m23.00s'
+    }
+
+    def roundsMillisWhenDurationIsGreaterThanOneMinute() {
+        expect:
+        formatter.format(60 * 1000 + 12 * 1000 + 300) == '1m12.30s'
+        formatter.format(60 * 1000 + 12 * 1000 + 301) == '1m12.30s'
+        formatter.format(60 * 1000 + 12 * 1000 + 305) == '1m12.31s'
+        formatter.format(60 * 1000 + 12 * 1000 + 309) == '1m12.31s'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
new file mode 100644
index 0000000..8efd977
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.reporting
+
+import spock.lang.Specification
+import org.w3c.dom.Element
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TextUtil
+
+class HtmlReportRendererTest extends Specification {
+    final DomReportRenderer<String> domRenderer = new DomReportRenderer<String>() {
+        @Override
+        void render(String model, Element parent) {
+            parent.appendChild(parent.ownerDocument.createElement(model))
+        }
+    }
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final HtmlReportRenderer renderer = new HtmlReportRenderer()
+
+    def "renders report to stream"() {
+        StringWriter writer = new StringWriter()
+
+        when:
+        renderer.renderer(domRenderer).writeTo("test", writer)
+
+        then:
+        writer.toString() == TextUtil.toPlatformLineSeparators('''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<test></test>
+</html>
+''')
+    }
+
+    def "copies resources into output directory"() {
+        File destFile = tmpDir.file('report.txt')
+
+        given:
+        renderer.requireResource(getClass().getResource("base-style.css"))
+
+        when:
+        renderer.renderer(domRenderer).writeTo("test", destFile)
+
+        then:
+        tmpDir.file("base-style.css").file
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy
new file mode 100644
index 0000000..7cf1791
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.reporting
+
+import spock.lang.Specification
+import org.w3c.dom.Element
+import javax.xml.parsers.DocumentBuilderFactory
+
+class TabsRendererTest extends Specification {
+    final DomReportRenderer<String> contentRenderer = new DomReportRenderer<String>() {
+        @Override
+        void render(String model, Element parent) {
+            parent.appendChild(parent.ownerDocument.createTextNode(model))
+        }
+    }
+    final TabsRenderer renderer = new TabsRenderer()
+
+    def "renders tabs"() {
+        given:
+        def doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
+        def parent = doc.createElement("parent")
+
+        and:
+        renderer.add('tab 1', contentRenderer)
+        renderer.add('tab 2', contentRenderer)
+
+        when:
+        renderer.render("test", parent)
+
+        then:
+        parent.childNodes.length == 1
+        parent.childNodes.item(0) instanceof Element
+        parent.childNodes.item(0).nodeName == 'div'
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/TextDomReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/TextDomReportRendererTest.groovy
new file mode 100644
index 0000000..a781fb2
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/TextDomReportRendererTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.reporting
+
+import spock.lang.Specification
+import javax.xml.parsers.DocumentBuilderFactory
+import org.w3c.dom.Element
+import org.w3c.dom.Text
+
+class TextDomReportRendererTest extends Specification {
+    final TextReportRenderer<String> textRenderer = new TextReportRenderer<String>() {
+        @Override protected void writeTo(String model, Writer out) {
+            out.write("<html><p>$model</p></html>")
+        }
+    }
+    final TextDomReportRenderer<String> renderer = new TextDomReportRenderer<String>(textRenderer)
+
+    def "converts text to DOM elements"() {
+        given:
+        def doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
+        def parent = doc.createElement("parent")
+
+        when:
+        renderer.render("test", parent)
+
+        then:
+        parent.childNodes.length == 1
+        parent.childNodes.item(0) instanceof Element
+        parent.childNodes.item(0).nodeName == 'p'
+        parent.childNodes.item(0).childNodes.length == 1
+        parent.childNodes.item(0).childNodes.item(0) instanceof Text
+        parent.childNodes.item(0).childNodes.item(0).nodeValue == "test"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy
new file mode 100644
index 0000000..5f9bc7d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/TextReportRendererTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.reporting
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+class TextReportRendererTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final TextReportRenderer<String> renderer = new TextReportRenderer<String>() {
+        @Override protected void writeTo(String model, Writer out) {
+            out.write("[")
+            out.write(model)
+            out.write("]")
+        }
+    }
+
+    def "writes report to output file"() {
+        def reportFile = tmpDir.file("dir/report.txt")
+
+        when:
+        renderer.writeTo("test", reportFile)
+
+        then:
+        reportFile.text == "[test]"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
index 2d1b30b..45514b4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
@@ -56,7 +56,7 @@ class ProjectBuilderTest extends Specification {
         project.gradle.gradleUserHomeDir == project.file('userHome')
     }
 
-    def canApplyACustomPlugin() {
+    def canApplyACustomPluginByType() {
         when:
         def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
         project.apply plugin: CustomPlugin
@@ -65,6 +65,15 @@ class ProjectBuilderTest extends Specification {
         project.tasks.hello instanceof DefaultTask
     }
 
+    def canApplyACustomPluginById() {
+        when:
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        project.apply plugin: 'custom-plugin'
+
+        then:
+        project.tasks.hello instanceof DefaultTask
+    }
+
     def canCreateAndExecuteACustomTask() {
         when:
         def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/AvailablePortFinderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/AvailablePortFinderTest.groovy
new file mode 100644
index 0000000..b5886f7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/AvailablePortFinderTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.util
+
+import spock.lang.Specification
+
+class AvailablePortFinderTest extends Specification {
+    AvailablePortFinder portFinder
+
+    def "can test for and find an available port"() {
+        portFinder = AvailablePortFinder.createPrivate()
+
+        expect:
+        portFinder.available(portFinder.nextAvailable)
+    }
+
+    def "tries to return different ports on successive invocations"() {
+        portFinder = AvailablePortFinder.createPrivate()
+
+        expect:
+        portFinder.nextAvailable != portFinder.nextAvailable
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
new file mode 100644
index 0000000..9177e11
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.util
+
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+import org.gradle.api.Transformer
+
+import spock.lang.*
+
+class CollectionUtilsTest extends Specification {
+
+    def "list filtering"() {
+        given:
+        def spec = Specs.convertClosureToSpec { it < 5 }
+        def filter = { Integer[] nums -> CollectionUtils.filter(nums as List, spec) }
+        
+        expect:
+        filter(1,2,3) == [1,2,3]
+        filter(7,8,9) == []
+        filter() == []
+        filter(4,5,6) == [4]
+    }
+    
+    def "list collecting"() {
+        def transformer = new Transformer() { def transform(i) { i * 2 } }
+        def collect = { Integer[] nums -> CollectionUtils.collect(nums as List, transformer) }
+        
+        expect:
+        collect(1,2,3) == [2,4,6]
+        collect() == []
+    }
+
+    def "set filtering"() {
+        given:
+        def spec = Specs.convertClosureToSpec { it < 5 }
+        def filter = { Integer[] nums -> CollectionUtils.filter(nums as Set, spec) }
+        
+        expect:
+        filter(1,2,3) == [1,2,3] as Set
+        filter(7,8,9).empty
+        filter().empty
+        filter(4,5,6) == [4] as Set
+    }
+
+    def toStringList() {
+        def list = [42, "string"]
+
+        expect:
+        CollectionUtils.toStringList([]) == []
+        CollectionUtils.toStringList(list) == ["42", "string"]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecification.groovy b/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecification.groovy
deleted file mode 100644
index 660aead..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecification.groovy
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * Copyright 2011 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.util
-
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.locks.Condition
-import java.util.concurrent.locks.Lock
-import java.util.concurrent.locks.ReentrantLock
-import org.gradle.messaging.concurrent.ExecutorFactory
-import org.gradle.messaging.concurrent.StoppableExecutor
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import spock.lang.Specification
-
-/**
- * <p>A base class for writing specifications which exercise concurrent code.
- *
- * <p>See {@link ConcurrentSpecificationTest} for some examples.
- */
-class ConcurrentSpecification extends Specification {
-    private static final Logger LOG = LoggerFactory.getLogger(ConcurrentSpecification.class)
-    private final Lock lock = new ReentrantLock()
-    private final Condition threadsChanged = lock.newCondition()
-    private final Set<DeferredActionImpl> mocks = [] as Set
-    private final Set<TestThread> threads = [] as Set
-    private final List<Throwable> failures = []
-
-    def cleanup() {
-        finished()
-    }
-
-    ExecutorFactory getExecutorFactory() {
-        return new ExecutorFactory() {
-            StoppableExecutor create(String displayName) {
-                return new StoppableExecutorStub(ConcurrentSpecification.this)
-            }
-        }
-    }
-
-    void startThread(Runnable cl) {
-        lock.lock()
-        try {
-            TestThread thread = new TestThread(this, lock, cl)
-            thread.start()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    /**
-     * Creates an action which will be used by a mock object to synchronise with the SUT.
-     *
-     * @return The action.
-     */
-    DeferredAction later() {
-        lock.lock()
-        try {
-            DeferredActionImpl mock = new DeferredActionImpl(this, lock)
-            mocks << mock
-            return mock
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    /**
-     * Starts a thread which executes the given action/closure. Blocks until all deferred actions are activated. Does not wait for the thread to complete.
-     *
-     * @return A handle to the test thread.
-     */
-    TestParticipant start(Runnable cl) {
-        lock.lock()
-        try {
-            TestThread thread = new TestThread(this, lock, cl)
-            thread.start()
-            waitForAllMocks()
-            return new TestParticipantImpl(this, thread)
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    /**
-     * Returns a composite participant, which you can use to perform atomic operations on.
-     *
-     * @return A handle to the composite participant.
-     */
-    TestParticipant all(TestParticipant... participants) {
-        return new CompositeTestParticipant(this, lock, participants as List)
-    }
-
-    private void waitForAllMocks() {
-        LOG.info("Waiting for all mocks to block.")
-        Date timeout = shortTimeout()
-        mocks.each { mock ->
-            mock.waitUntilActivated(timeout)
-        }
-    }
-
-    /**
-     * Activates and executes all deferred actions and waits for all threads to complete. Asserts that the threads complete in a 'short' time. Rethrows any exceptions thrown by test threads.
-     */
-    void finished() {
-        Date timeout = shortTimeout()
-        lock.lock()
-        try {
-            LOG.info("Waiting for actions to complete.")
-            mocks.each { mock ->
-                mock.run(timeout)
-            }
-
-            LOG.info("Waiting for test threads to complete.")
-            while (!threads.isEmpty()) {
-                if (!threadsChanged.awaitUntil(timeout)) {
-                    failures << new IllegalStateException("Timeout waiting for test threads to complete.")
-                    break;
-                }
-            }
-            threads.each { thread ->
-                thread.interrupt()
-            }
-
-            LOG.info("Finishing up.")
-            if (!failures.isEmpty()) {
-                throw failures[0]
-            }
-        } finally {
-            threads.clear()
-            mocks.clear()
-            failures.clear()
-            lock.unlock()
-        }
-
-    }
-
-    static Date shortTimeout() {
-        return new Date(System.currentTimeMillis() + 5000)
-    }
-
-    void run(Closure cl, Date timeout) {
-        def thread = new TestThread(this, lock, cl)
-        thread.start()
-        thread.completesBefore(timeout)
-    }
-
-    void onThreadStart(TestThread thread) {
-        lock.lock()
-        try {
-            threads << thread
-            threadsChanged.signalAll()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void onThreadComplete(TestThread thread, Throwable failure) {
-        lock.lock()
-        try {
-            threads.remove(thread)
-            if (failure) {
-                failures << failure
-            }
-            threadsChanged.signalAll()
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-class TestThread extends Thread {
-    private static final Logger LOG = LoggerFactory.getLogger(TestThread.class)
-    private final ConcurrentSpecification owner
-    private final Runnable action
-    private final Lock lock
-    private final Condition stateChanged
-    private boolean complete
-
-    TestThread(ConcurrentSpecification owner, Lock lock, Runnable action) {
-        this.owner = owner
-        this.action = action
-        this.lock = lock
-        this.stateChanged = lock.newCondition()
-    }
-
-    @Override
-    void start() {
-        LOG.info("$this started.")
-
-        lock.lock()
-        try {
-            owner.onThreadStart(this)
-            stateChanged.signalAll()
-        } finally {
-            lock.unlock()
-        }
-
-        super.start()
-    }
-
-    void running() {
-        lock.lock()
-        try {
-            if (complete) {
-                throw new IllegalStateException("$this should still be running, but is not.")
-            }
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void completesBefore(Date timeout) {
-        lock.lock()
-        try {
-            LOG.info("Waiting for $this to complete.")
-            while (!complete) {
-                if (!stateChanged.awaitUntil(timeout)) {
-                    throw new IllegalStateException("Timeout waiting for $this to complete.")
-                }
-            }
-            LOG.info("$this completed.")
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    @Override
-    void run() {
-        Throwable failure = null
-        try {
-            action.run()
-        } catch (Throwable t) {
-            failure = t
-        }
-
-        lock.lock()
-        try {
-            complete = true
-            stateChanged.signalAll()
-            owner.onThreadComplete(this, failure)
-            LOG.info("$this completed.")
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-/**
- * Some potentially long running operation.
- */
-interface LongRunningAction {
-    /**
-     * Blocks until this action has completed. Asserts that the action completes in a 'short' time. Rethrows any exception from the action.
-     */
-    void completed()
-
-    /**
-     * Blocks until this action has completed. Asserts that the action completes within the specified time. Rethrows any exception from the action.
-     */
-    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits)
-
-    /**
-     * Blocks until this action has completed. Asserts that the action completes before the given time. Rethrows any exception from the action.
-     */
-    void completesBefore(Date timeout)
-}
-
-/**
- * An action which runs at some point in the future. A {@code DeferredAction} must be activated before it can run, by calling {@link DeferredAction#finishLater(Closure)}.
- */
-interface DeferredAction extends LongRunningAction {
-    /**
-     * Registers that the target sync point has been reached, and this action is ready to execute. This method does not block.
-     *
-     * This action is started once this method has been called, and one of the following have been executed:
-     *
-     * <ul>
-     * <li>{@link TestParticipant#waitsFor(LongRunningAction)}</li>
-     * <li>{@link TestParticipant#doesNotWaitFor(LongRunningAction)}</li>
-     * <li>{@link ConcurrentSpecification#finished}</li>
-     * </ul>
-     */
-    void finishLater(Closure action)
-
-    /**
-     * Registers that the target sync point has been reached, and this action is ready to execute. This method blocks until the action completes.
-     *
-     * This action is started once this method has been called, and one of the following have been executed:
-     *
-     * <ul>
-     * <li>{@link TestParticipant#waitsFor(LongRunningAction)}</li>
-     * <li>{@link TestParticipant#doesNotWaitFor(LongRunningAction)}</li>
-     * <li>{@link ConcurrentSpecification#finished}</li>
-     * </ul>
-     */
-    void finish()
-}
-
-interface TestParticipant extends LongRunningAction {
-    /**
-     * Asserts that this test participant is running.
-     */
-    void running()
-    /**
-     * Asserts that this test participant blocks until the given actions complete. If any action is a {@link DeferredAction}, the action is started.
-     *
-     * This method blocks until both this participant and the actions have completed, and asserts that everything completes in a 'short' time.
-     */
-    void waitsFor(LongRunningAction... targets)
-
-    /**
-     * Asserts that this test participant blocks until the given action completes.
-     *
-     * This method blocks until both this participant and the action have completed, and asserts that everything completes in a 'short' time.
-     */
-    void waitsFor(Closure action)
-
-    /**
-     * Asserts that this test participant does not block while the given actions are executing. If any action is a {@link DeferredAction}, it must be activated first.
-     *
-     * This method blocks until this participant has completed, and asserts that it completes in a 'short' time.
-     */
-    void doesNotWaitFor(LongRunningAction... targets)
-}
-
-abstract class AbstractAction implements LongRunningAction {
-    void completed() {
-        Date expiry = ConcurrentSpecification.shortTimeout()
-        completesBefore(expiry)
-    }
-
-    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits) {
-        Date expiry = new Date(System.currentTimeMillis() + maxWaitUnits.toMillis(maxWaitValue))
-        completesBefore(expiry + 500)
-    }
-
-    abstract void completesBefore(Date timeout)
-}
-
-class DeferredActionImpl extends AbstractAction implements DeferredAction {
-    private static final Logger LOG = LoggerFactory.getLogger(DeferredActionImpl.class)
-    private final ConcurrentSpecification owner
-    private final Lock lock
-    private final Condition stateChange = lock.newCondition()
-    private Closure action
-    private boolean activated
-    private boolean complete
-
-    DeferredActionImpl(ConcurrentSpecification owner, Lock lock) {
-        this.owner = owner
-        this.lock = lock
-    }
-
-    void activated() {
-        lock.lock()
-        try {
-            if (!activated) {
-                throw new IllegalStateException("Action has not been activated.")
-            }
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void running() {
-        lock.lock()
-        try {
-            if (complete) {
-                throw new IllegalStateException("Action has completed.")
-            }
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    @Override
-    void completesBefore(Date timeout) {
-        activated()
-        run(timeout)
-    }
-
-    void run(Date timeout) {
-        lock.lock()
-        try {
-            if (!activated || complete) {
-                return
-            }
-        } finally {
-            lock.unlock()
-        }
-
-        LOG.info("Running deferred action")
-        owner.run(action, timeout)
-        LOG.info("Deferred action complete")
-
-        lock.lock()
-        try {
-            complete = true
-            action = null
-            stateChange.signalAll()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void waitUntilActivated(Date timeout) {
-        lock.lock()
-        try {
-            while (!activated) {
-                if (!stateChange.awaitUntil(timeout)) {
-                    throw new IllegalStateException("Timeout waiting for action to be activated.")
-                }
-            }
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void finishLater(Closure action) {
-        lock.lock()
-        try {
-            if (activated) {
-                throw new IllegalStateException("This action has already been activated.")
-            }
-
-            activated = true
-            this.action = action
-            stateChange.signalAll()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    void finish() {
-        finishLater {}
-        completed()
-    }
-}
-
-abstract class AbstractTestParticipant extends AbstractAction implements TestParticipant {
-    private final ConcurrentSpecification owner
-
-    AbstractTestParticipant(ConcurrentSpecification owner) {
-        this.owner = owner
-    }
-
-    void doesNotWaitFor(LongRunningAction... targets) {
-        targets*.activated()
-        completed()
-        targets*.running()
-    }
-
-    void waitsFor(LongRunningAction... targets) {
-        targets*.activated()
-        Thread.sleep(500)
-        running()
-        targets*.completed()
-        completed()
-    }
-
-    void waitsFor(Closure action) {
-        Thread.sleep(500)
-        running()
-        owner.run(action, owner.shortTimeout())
-        completed()
-    }
-}
-
-class TestParticipantImpl extends AbstractTestParticipant {
-    private final TestThread thread
-
-    TestParticipantImpl(ConcurrentSpecification owner, TestThread thread) {
-        super(owner)
-        this.thread = thread
-    }
-
-    @Override
-    void completesBefore(Date timeout) {
-        thread.completesBefore(timeout)
-    }
-
-    void running() {
-        thread.running()
-    }
-}
-
-class CompositeTestParticipant extends AbstractTestParticipant {
-    private final List<TestParticipant> participants
-    private final Lock lock
-
-    CompositeTestParticipant(ConcurrentSpecification owner, Lock lock, List<TestParticipant> participants) {
-        super(owner)
-        this.participants = participants
-        this.lock = lock
-    }
-
-    void running() {
-        lock.lock()
-        try {
-            participants*.running()
-        } finally {
-            lock.unlock()
-        }
-    }
-
-    @Override
-    void completesBefore(Date timeout) {
-        lock.lock()
-        try {
-            participants*.completesBefore(timeout)
-        } finally {
-            lock.unlock()
-        }
-    }
-}
-
-class StoppableExecutorStub implements StoppableExecutor {
-    final ConcurrentSpecification owner
-
-    StoppableExecutorStub(ConcurrentSpecification owner) {
-        this.owner = owner
-    }
-
-    void stop() {
-        throw new UnsupportedOperationException()
-    }
-
-    void stop(int timeoutValue, TimeUnit timeoutUnits) {
-        throw new UnsupportedOperationException()
-    }
-
-    void requestStop() {
-        throw new UnsupportedOperationException()
-    }
-
-    void execute(Runnable runnable) {
-        owner.startThread(runnable)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecificationTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecificationTest.groovy
index 38ebdb8..0e0e838 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecificationTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/ConcurrentSpecificationTest.groovy
@@ -16,185 +16,357 @@
 package org.gradle.util
 
 import java.util.concurrent.CountDownLatch
-import java.util.concurrent.SynchronousQueue
 import java.util.concurrent.TimeUnit
-import spock.lang.Ignore
+import org.spockframework.mock.TooManyInvocationsError
+import spock.lang.FailsWith
 
 class ConcurrentSpecificationTest extends ConcurrentSpecification {
-    
-    def canCheckThatMethodCallWaitsUntilAsyncCallbackIsComplete() {
-        SomeAsyncWorker worker = Mock()
-        SomeSyncClass target = new SomeSyncClass(worker: worker)
+    def setup() {
+        shortTimeout = 1000
+    }
 
-        def notifyListener = later()
+    def "can check that an action calls a mock method asynchronously"() {
+        Runnable action = Mock()
+        def executed = startsAsyncAction()
 
         when:
-        def result
-        def action = start {
-            result = target.doWork('value')
+        executed.started {
+            executor.execute { action.run() }
         }
 
         then:
-        action.waitsFor(notifyListener)
-        1 * worker.doLater(!null) >> { args -> notifyListener.finishLater { args[0].call('result') } }
+        1 * action.run() >> { executed.done() }
+    }
+
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected async action to complete, but it did not.')
+    def "async action fails when expected mock method is never called"() {
+        Runnable action = Mock()
+        def executed = startsAsyncAction()
 
         when:
-        finished()
+        executed.started {}
 
         then:
-        result == 'result'
+        1 * action.run() >> { executed.done() }
     }
 
-    def canCheckThatMethodCallDoesNotWaitUntilAsyncCallbackIsComplete() {
-        SomeAsyncWorker worker = Mock()
-        Closure handler = Mock()
-        SomeSyncClass target = new SomeSyncClass(worker: worker)
+    @FailsWithMessage(type = IllegalStateException, message = 'Cannot wait for action to complete from the thread that is executing it.')
+    def "async action fails when expected mock method is called from start action thread"() {
+        Runnable action = Mock()
+        def executed = startsAsyncAction()
+
+        when:
+        executed.started {
+            action.run()
+        }
+
+        then:
+        1 * action.run() >> { executed.done() }
+    }
 
-        def notifyListener = later()
+    @FailsWith(TooManyInvocationsError)
+    def "async action fails when start action throws an exception"() {
+        Runnable action = Mock()
+        def executed = startsAsyncAction()
 
         when:
-        def action = start {
-            target.startWork(handler)
+        executed.started {
+            action.run()
         }
 
         then:
-        action.doesNotWaitFor(notifyListener)
-        1 * worker.doLater(!null) >> { args -> notifyListener.finishLater { args[0].call('result') } }
+        0 * action.run()
+    }
+
+    @FailsWith(TooManyInvocationsError)
+    def "async action fails when async action throws an exception"() {
+        Runnable action = Mock()
 
         when:
-        finished()
+        startsAsyncAction().started {
+            executor.execute { action.run() }
+        }
 
         then:
-        1 * handler.call('[result]')
+        0 * action.run()
     }
 
-    def canHaveMultipleAsyncActions() {
-        SomeAsyncWorker worker = Mock()
-        Closure handler = Mock()
-        SomeSyncClass target = new SomeSyncClass(worker: worker)
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to complete quickly, but it did not.')
+    def "async action fails when start action never finishes"() {
+        def latch = new CountDownLatch(1)
+        Runnable action = Mock()
+
+        when:
+        startsAsyncAction().started {
+            latch.await()
+        }
+
+        then:
+        1 * action.run()
+
+        cleanup:
+        latch.countDown()
+    }
 
-        Closure listener
-        def notifyListener = later()
-        def notifyListenerAgain = later()
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to complete quickly, but it did not.')
+    def "async action fails when start action blocks waiting for async action to complete"() {
+        def latch = new CountDownLatch(1)
+        def executed = startsAsyncAction()
+        Runnable action = Mock()
 
         when:
-        def action = start {
-            target.startWork(handler)
-            target.startWork(handler)
+        executed.started {
+            start { action.run() }
+            latch.await()
+        }
+
+        then:
+        1 * action.run() >> { executed.done(); latch.countDown() }
+
+        cleanup:
+        latch.countDown()
+    }
+
+    @FailsWithMessage(type = IllegalStateException, message = 'Action has not been started.')
+    def "async action fails when done called before start action is called"() {
+        expect:
+        startsAsyncAction().done()
+    }
+
+    def "can check that an action blocks until an asynchronous callback is made"() {
+        Runnable action = Mock()
+        def latch = new CountDownLatch(1)
+        def operation = waitsForAsyncCallback()
+
+        when:
+        operation.start {
+            action.run()
+            latch.await()
         }
 
         then:
-        action.doesNotWaitFor(notifyListener, notifyListenerAgain)
-        2 * worker.doLater(!null) >> { args ->
-            if (!listener) {
-                listener = args[0];
-                notifyListener.finishLater { args[0].call('result1') }
-            } else {
-                notifyListenerAgain.finishLater { args[0].call('result2') }
-            }
+        1 * action.run() >> { operation.callbackLater { latch.countDown() } }
+    }
+
+    @FailsWith(TooManyInvocationsError)
+    def "blocking action fails when blocking action throws exception"() {
+        Runnable action = Mock()
+
+        when:
+        waitsForAsyncCallback().start {
+            action.run()
         }
 
+        then:
+        0 * action.run()
+    }
+
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to block, but it did not.')
+    def "blocking action fails when blocking action finishes without waiting for callback action"() {
+        Runnable action = Mock()
+        def operation = waitsForAsyncCallback()
+
         when:
-        notifyListener.completed()
+        operation.start {
+            action.run()
+        }
 
         then:
-        1 * handler.call('[result1]')
+        1 * action.run() >> { operation.callbackLater { } }
+    }
+
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to unblock, but it did not.')
+    def "blocking action fails when blocking action never finishes"() {
+        Runnable action = Mock()
+        def latch = new CountDownLatch(1)
+        def operation = waitsForAsyncCallback()
 
         when:
-        finished()
+        operation.start {
+            action.run()
+            latch.await()
+        }
 
         then:
-        1 * handler.call('[result2]')
+        1 * action.run() >> { operation.callbackLater {} }
+
+        cleanup:
+        latch.countDown()
     }
 
-    def canCheckThatMethodCallsBlockUntilAnotherMethodIsCalled() {
-        SomeConditionClass condition = new SomeConditionClass()
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to register a callback action, but it did not.')
+    def "blocking action fails when blocking action never registers callback action"() {
+        Runnable action = Mock()
+        def latch = new CountDownLatch(1)
+        def operation = waitsForAsyncCallback()
 
         when:
-        def action1 = start {
-            condition.waitUntilComplete()
+        operation.start {
+            latch.await()
         }
-        def action2 = start {
-            condition.waitUntilComplete()
+
+        then:
+        1 * action.run() >> { operation.callbackLater {} }
+
+        cleanup:
+        latch.countDown()
+    }
+
+    @FailsWith(TooManyInvocationsError)
+    def "blocking action fails when action throws exception"() {
+        Runnable action = Mock()
+
+        when:
+        waitsForAsyncCallback().start {
+            action.run()
         }
 
         then:
-        all(action1, action2).waitsFor {
-            condition.complete()
+        0 * action.run()
+    }
+
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected callback action to complete, but it did not.')
+    def "blocking action fails when callback action never finishes"() {
+        def operation = waitsForAsyncCallback()
+        def latch = new CountDownLatch(1)
+        Runnable action = Mock()
+
+        when:
+        operation.start {
+            action.run()
+            latch.await()
         }
+
+        then:
+        1 * action.run() >> { operation.callbackLater { latch.await() } }
+
+        cleanup:
+        latch.countDown()
     }
 
-    def canCheckThatMethodCompletesInSpecifiedTime() {
-        SomeConditionClass condition = new SomeConditionClass()
+    @FailsWith(TooManyInvocationsError)
+    def "blocking action fails when callback action method throws exception"() {
+        def operation = waitsForAsyncCallback()
+        def latch = new CountDownLatch(1)
+        Runnable action = Mock()
 
         when:
-        def timedOut = false
-        def action = start {
-            timedOut = condition.waitUntilComplete(200)
+        operation.start {
+            action.run()
+            latch.await()
         }
 
         then:
-        action.completesWithin(200, TimeUnit.MILLISECONDS)
+        1 * action.run() >> { operation.callbackLater { action.run() } }
+
+        cleanup:
+        latch.countDown()
+    }
+
+    @FailsWithMessage(type = IllegalStateException, message = 'Action has not been started.')
+    def "blocking action fails when callback made before blocking action started"() {
+        expect:
+        waitsForAsyncCallback().callbackLater { }
+    }
+
+    def "can check that an action blocks until an asynchronous action is finished"() {
+        Runnable action = Mock()
+        def operation = waitsForAsyncActionToComplete()
+        def latch = new CountDownLatch(1)
 
         when:
+        operation.start {
+            start { action.run() }
+            latch.await()
+        }
         finished()
 
         then:
-        timedOut
+        1 * action.run() >> { args -> operation.done(); latch.countDown() }
+
+        cleanup:
+        latch.countDown()
     }
 
-    def finishRethrowsExceptionThrownByTestThread() {
-        RuntimeException failure = new RuntimeException()
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to block, but it did not.')
+    def "blocking action fails when action does not wait for async action to start"() {
+        Runnable action = Mock()
+        def operation = waitsForAsyncActionToComplete()
 
         when:
-        start {
-            failure.fillInStackTrace()
-            throw failure
+        operation.start {
+            start { action.run() }
         }
         finished()
 
         then:
-        RuntimeException e = thrown()
-        e.is(failure)
+        _ * action.run() >> { operation.done() }
     }
 
-    @Ignore
-    def completeRethrowsExceptionThrownByTestThread() {
-        RuntimeException failure = new RuntimeException()
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to block, but it did not.')
+    def "blocking action fails when action does not wait for async action to complete"() {
+        Runnable action = Mock()
+        def started = new CountDownLatch(1)
+        def operation = waitsForAsyncActionToComplete()
 
         when:
-        def action = start {
-            failure.fillInStackTrace()
-            throw failure
+        operation.start {
+            start { action.run() }
+            started.await()
         }
-        action.completed()
+        finished()
 
         then:
-        RuntimeException e = thrown()
-        e.is(failure)
+        1 * action.run() >> { started.countDown(); operation.done() }
+
+        cleanup:
+        started.countDown()
     }
-}
 
-interface SomeAsyncWorker {
-    void doLater(Closure cl)
-}
+    @FailsWithMessage(type = IllegalStateException, message = 'Expected action to block, but it did not.')
+    def "blocking action fails when action does not start async action"() {
+        Runnable action = Mock()
+        def operation = waitsForAsyncActionToComplete()
+
+        when:
+        operation.start { }
 
-class SomeSyncClass {
-    SomeAsyncWorker worker
+        then:
+        1 * action.run() >> { operation.done() }
+    }
+
+    def "can check that some method completes in expected time"() {
+        SomeConditionClass condition = new SomeConditionClass()
 
-    def doWork(Object value) {
-        SynchronousQueue queue = new SynchronousQueue()
-        worker.doLater { arg ->
-            queue.put(arg)
+        when:
+        def timedOut = false
+        def action = start {
+            timedOut = condition.waitUntilComplete(200)
         }
-        return queue.take()
+        action.completesWithin(200, TimeUnit.MILLISECONDS)
+
+        then:
+        timedOut
     }
 
-    def startWork(Closure handler) {
-        worker.doLater { result -> handler.call("[$result]" as String) }
+    @FailsWith(TestException)
+    def "finish rethrows exception thrown by test thread"() {
+        Runnable action = Mock()
+
+        when:
+        start {
+            throw new TestException()
+        }
+        finished()
+
+        then:
+        1 * action.run()
     }
 }
 
+class TestException extends RuntimeException {
+}
+
 class SomeConditionClass {
     final CountDownLatch latch = new CountDownLatch(1)
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
old mode 100644
new mode 100755
index d57b323..afd1979
--- a/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
@@ -1,82 +1,129 @@
-/*
- * 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.util
-
-import org.junit.Test
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-
-class ConfigureUtilTest {
-    @Test
-    public void canConfigureObjectUsingClosure() {
-        List obj = []
-        def cl = {
-            add('a');
-            assertThat(size(), equalTo(1));
-            assertThat(obj, equalTo(['a']))
-        }
-        ConfigureUtil.configure(cl, obj)
-        assertThat(obj, equalTo(['a']))
-    }
-
-    @Test
-    public void passesConfiguredObjectToClosureAsParameter() {
-        List obj = []
-        def cl = {
-            assertThat(it, sameInstance(obj))
-        }
-        def cl2 = {List list ->
-            assertThat(list, sameInstance(obj))
-        }
-        def cl3 = {->
-            assertThat(delegate, sameInstance(obj))
-        }
-        ConfigureUtil.configure(cl, obj)
-        ConfigureUtil.configure(cl2, obj)
-        ConfigureUtil.configure(cl3, obj)
-    }
-
-    @Test
-    public void canConfigureObjectPropertyUsingMap() {
-        Bean obj = new Bean()
-
-        ConfigureUtil.configureByMap(obj, prop: 'value')
-        assertThat(obj.prop, equalTo('value'))
-
-        ConfigureUtil.configureByMap(obj, method: 'value2')
-        assertThat(obj.prop, equalTo('value2'))
-    }
-
-    @Test
-    public void throwsExceptionForUnknownProperty() {
-        Bean obj = new Bean()
-
-        try {
-            ConfigureUtil.configureByMap(obj, unknown: 'value')
-            fail()
-        } catch (MissingPropertyException e) {
-            assertThat(e.type, equalTo(Bean.class))
-            assertThat(e.property, equalTo('unknown'))
-        }
-    }
-}
-
-class Bean {
-    String prop
-    def method(String value) {
-        prop = value
-    }
-}
+/*
+ * 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.util
+
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.util.ConfigureUtil.IncompleteInputException
+import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.fail
+
+class ConfigureUtilTest {
+    @Test
+    public void canConfigureObjectUsingClosure() {
+        List obj = []
+        def cl = {
+            add('a');
+            assertThat(size(), equalTo(1));
+            assertThat(obj, equalTo(['a']))
+        }
+        ConfigureUtil.configure(cl, obj)
+        assertThat(obj, equalTo(['a']))
+    }
+
+    @Test
+    public void passesConfiguredObjectToClosureAsParameter() {
+        List obj = []
+        def cl = {
+            assertThat(it, sameInstance(obj))
+        }
+        def cl2 = {List list ->
+            assertThat(list, sameInstance(obj))
+        }
+        def cl3 = {->
+            assertThat(delegate, sameInstance(obj))
+        }
+        ConfigureUtil.configure(cl, obj)
+        ConfigureUtil.configure(cl2, obj)
+        ConfigureUtil.configure(cl3, obj)
+    }
+
+    @Test
+    public void canConfigureObjectPropertyUsingMap() {
+        Bean obj = new Bean()
+
+        ConfigureUtil.configureByMap(obj, prop: 'value')
+        assertThat(obj.prop, equalTo('value'))
+
+        ConfigureUtil.configureByMap(obj, method: 'value2')
+        assertThat(obj.prop, equalTo('value2'))
+    }
+
+    @Test
+    public void canConfigureAndValidateObjectUsingMap() {
+        Bean obj = new Bean()
+
+        try {
+            //when
+            ConfigureUtil.configureByMap([prop: 'value'], obj, ['foo'])
+            //then
+            fail();
+        } catch (IncompleteInputException e) {
+            assert e.missingKeys.contains('foo')
+        }
+
+        //when
+        ConfigureUtil.configureByMap([prop: 'value'], obj, ['prop'])
+        //then
+        assert obj.prop == 'value'
+    }
+
+    @Test
+    public void throwsExceptionForUnknownProperty() {
+        Bean obj = new Bean()
+
+        try {
+            ConfigureUtil.configureByMap(obj, unknown: 'value')
+            fail()
+        } catch (MissingPropertyException e) {
+            assertThat(e.type, equalTo(Bean.class))
+            assertThat(e.property, equalTo('unknown'))
+        }
+    }
+    
+    static class TestConfigurable implements Configurable {
+        def props = [:]
+        
+        TestConfigurable configure(Closure closure) {
+            props.with(closure)
+            this
+        }
+    }
+    
+    @Test
+    void testConfigurableAware() {
+        def c = new TestConfigurable()
+        ConfigureUtil.configure({ a = 1 }, c)
+        assert c.props.a == 1
+    }
+    
+    @Test
+    void configureByMapTriesMethodForExtensibleObjects() {
+        Bean bean = ThreadGlobalInstantiator.getOrCreate().newInstance(Bean)
+        ConfigureUtil.configureByMap(bean, method:  "foo")
+        assert bean.prop == "foo"
+    }
+    
+}
+
+class Bean {
+    String prop
+    def method(String value) {
+        prop = value
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTest.groovy
new file mode 100644
index 0000000..38737a6
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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.util
+
+import spock.lang.Specification
+
+class DefaultClassLoaderFactoryTest extends Specification {
+    final DefaultClassLoaderFactory factory = new DefaultClassLoaderFactory()
+    ClassLoader original
+
+    def setup() {
+        original = Thread.currentThread().contextClassLoader
+    }
+
+    def cleanup() {
+        Thread.currentThread().contextClassLoader = original
+    }
+
+    def "classes from specified URLs are visible in isolated ClassLoader"() {
+        when:
+        def cl = factory.createIsolatedClassLoader(classpath)
+        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
+
+        then:
+        c.name == DefaultClassLoaderFactoryTestHelper.name
+        c != DefaultClassLoaderFactoryTestHelper
+    }
+
+    def "application classes are not visible in isolated ClassLoader"() {
+        when:
+        def cl = factory.createIsolatedClassLoader(classpath)
+        cl.loadClass(Closure.name)
+
+        then:
+        thrown(ClassNotFoundException)
+    }
+
+    def "can use XML APIs from isolated ClassLoader when application classes include an XML provider"() {
+        assert ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory")
+
+        when:
+        def cl = factory.createIsolatedClassLoader(classpath)
+        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
+
+        then:
+        c != DefaultClassLoaderFactoryTestHelper
+
+        when:
+        Thread.currentThread().contextClassLoader = cl
+        c.newInstance().doStuff()
+
+        then:
+        notThrown()
+    }
+
+    def "can use XML APIs from filtering ClassLoader when application classes include an XML provider"() {
+        assert ClassLoader.getSystemResource("META-INF/services/javax.xml.parsers.SAXParserFactory")
+
+        when:
+        def cl = new URLClassLoader(classpath.collect { it.toURL() } as URL[], factory.createFilteringClassLoader(getClass().classLoader))
+        def c = cl.loadClass(DefaultClassLoaderFactoryTestHelper.name)
+
+        then:
+        c != DefaultClassLoaderFactoryTestHelper
+
+        when:
+        Thread.currentThread().contextClassLoader = cl
+        c.newInstance().doStuff()
+
+        then:
+        notThrown()
+    }
+
+    def getClasspath() {
+        return [ClasspathUtil.getClasspathForClass(DefaultClassLoaderFactoryTestHelper)].collect { it.toURI() }
+    }
+}
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTestHelper.java b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTestHelper.java
new file mode 100644
index 0000000..0d52d2b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassLoaderFactoryTestHelper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.util;
+
+import javax.xml.XMLConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.xpath.XPathFactory;
+
+public class DefaultClassLoaderFactoryTestHelper {
+    public void doStuff() throws Exception {
+        SAXParserFactory.newInstance().newSAXParser();
+        DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        DatatypeFactory.newInstance().newXMLGregorianCalendar();
+        TransformerFactory.newInstance().newTransformer();
+        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+        XPathFactory.newInstance().newXPath();
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassPathTest.groovy
new file mode 100644
index 0000000..af3edb1
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassPathTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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.util
+
+import spock.lang.Specification
+
+class DefaultClassPathTest extends Specification {
+    def "can add classpaths together"() {
+        def file1 = new File("a.jar")
+        def file2 = new File("b.jar")
+        def cp1 = new DefaultClassPath(file1)
+        def cp2 = new DefaultClassPath(file2)
+
+        expect:
+        def cp3 = cp1 + cp2
+        cp3.asFiles == [file1, file2]
+    }
+
+    def "add returns lhs when rhs is empty"() {
+        def cp1 = new DefaultClassPath(new File("a.jar"))
+        def cp2 = new DefaultClassPath()
+
+        expect:
+        (cp1 + cp2).is(cp1)
+    }
+
+    def "add returns rhs when lhs is empty"() {
+        def cp1 = new DefaultClassPath()
+        def cp2 = new DefaultClassPath(new File("a.jar"))
+
+        expect:
+        (cp1 + cp2).is(cp2)
+    }
+
+    def "can add collection of files to classpath"() {
+        def file1 = new File("a.jar")
+        def file2 = new File("a.jar")
+        def cp = new DefaultClassPath(file1)
+
+        expect:
+        (cp + [file2]).asFiles == [file1, file2]
+        (cp + []).is(cp)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
index 3be139f..fd45840 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
@@ -129,4 +129,26 @@ class FilteringClassLoaderTest {
         assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
         assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
     }
+
+    @Test
+    public void passesThroughResourcesWithSpecifiedPrefix() {
+        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
+
+        classLoader.allowResources('org/gradle')
+
+        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
+        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
+        assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
+    }
+
+    @Test
+    public void passesThroughSpecifiedResources() {
+        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
+
+        classLoader.allowResource('org/gradle/util/ClassLoaderTest.txt')
+
+        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
+        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
+        assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
new file mode 100644
index 0000000..084e643
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GFileUtilsTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.util
+
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 2/28/12
+ */
+class GFileUtilsTest extends Specification {
+    
+    @Rule TemporaryFolder temp
+
+    def "can read the file's tail"() {
+        def f = temp.file("foo.txt") << """
+one
+two
+three
+"""
+        when:
+        def out = GFileUtils.tail(f, 2)
+
+        then:
+        out == """two
+three
+"""
+    }
+
+    def "createDirectory() succeeds if directory already exists"() {
+        def dir = temp.createDir("foo")
+        assert dir.exists()
+
+        when:
+        GFileUtils.createDirectory(dir)
+
+        then:
+        noExceptionThrown()
+        dir.exists()
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
new file mode 100644
index 0000000..6e13d04
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * 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.util;
+
+
+import static org.gradle.util.GUtil.*
+
+public class GUtilTest extends spock.lang.Specification {
+    
+    def convertStringToCamelCase() {
+        expect:
+        toCamelCase(null) == null
+        toCamelCase("") == ""
+        toCamelCase("word") == "Word"
+        toCamelCase("twoWords") == "TwoWords"
+        toCamelCase("TwoWords") == "TwoWords"
+        toCamelCase("two-words") == "TwoWords"
+        toCamelCase("two.words") == "TwoWords"
+        toCamelCase("two words") == "TwoWords"
+        toCamelCase("two Words") == "TwoWords"
+        toCamelCase("Two Words") == "TwoWords"
+        toCamelCase(" Two  \t words\n") == "TwoWords"
+        toCamelCase("four or so Words") == "FourOrSoWords"
+        toCamelCase("trailing-") == "Trailing"
+        toCamelCase("ABC") == "ABC"
+        toCamelCase(".") == ""
+        toCamelCase("-") == ""
+    }
+
+    
+    def convertStringToConstantName() {
+        expect:
+        toConstant(null) == null
+        toConstant("") == ""
+        toConstant("word") == "WORD"
+        toConstant("twoWords") == "TWO_WORDS"
+        toConstant("TwoWords") == "TWO_WORDS"
+        toConstant("two-words") == "TWO_WORDS"
+        toConstant("TWO_WORDS") == "TWO_WORDS"
+        toConstant("two.words") == "TWO_WORDS"
+        toConstant("two words") == "TWO_WORDS"
+        toConstant("two Words") == "TWO_WORDS"
+        toConstant("Two Words") == "TWO_WORDS"
+        toConstant(" Two  \t words\n") == "TWO_WORDS"
+        toConstant("four or so Words") == "FOUR_OR_SO_WORDS"
+        toConstant("trailing-") == "TRAILING"
+        toConstant("a") == "A"
+        toConstant("A") == "A"
+        toConstant("aB") == "A_B"
+        toConstant("ABC") == "ABC"
+        toConstant("ABCThing") == "ABC_THING"
+        toConstant("ABC Thing") == "ABC_THING"
+        toConstant(".") == ""
+        toConstant("-") == ""
+    }
+
+    def convertStringToWords() {
+        expect:
+        toWords(null) == null
+        toWords("") == ""
+        toWords("word") == "word"
+        toWords("twoWords") == "two words"
+        toWords("TwoWords") == "two words"
+        toWords("two words") == "two words"
+        toWords("Two Words") == "two words"
+        toWords(" Two  \t words\n") == "two words"
+        toWords("two_words") == "two words"
+        toWords("two.words") == "two words"
+        toWords("two,words") == "two words"
+        toWords("trailing-") == "trailing"
+        toWords("a") == "a"
+        toWords("aB") == "a b"
+        toWords("ABC") == "abc"
+        toWords("ABCThing") == "abc thing"
+        toWords("ABC Thing") == "abc thing"
+        toWords(".") == ""
+        toWords("_") == ""
+    }
+
+    def "flattens maps and arrays"() {
+        expect:
+        flatten([[1], [foo: 'bar']]) == [1, 'bar']
+
+        Object[] array = [1]
+        flatten([array, [foo: 'bar']]) == [1, 'bar']
+    }
+
+    def "flattening of maps controls flatting of arrays"() {
+        //documenting current functionality, not sure if the functionality is a good one
+        when:
+        def out = []
+        Object[] array = [1]
+        then:
+        flatten([array, [foo: 'bar']], out, true) == [1, 'bar']
+
+        when:
+        out = []
+        array = [1]
+        then:
+        flatten([array, [foo: 'bar']], out, false) == [[1], [foo:'bar']]
+    }
+
+    def "normalizes to collection"() {
+        expect:
+        collectionize(null) == []
+        collectionize(10) == [10]
+        collectionize("a") == ["a"]
+
+        List list = [2, 2, "three"] as List
+        collectionize(list) == [2, 2, "three"]
+
+        Object[] array = [1, 1, "three"]
+        collectionize(array) == [1, 1, "three"]
+
+        collectionize([list, array]) == [2, 2, "three", 1, 1, "three"]
+
+        collectionize([[1], [hey: 'man']]) == [1, [hey: 'man']]
+    }
+
+    def "flattens"() {
+        expect:
+        flattenElements(1, [2,3]) == [1,2,3]
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.java b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.java
deleted file mode 100644
index 53b8507..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.java
+++ /dev/null
@@ -1,92 +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.util;
-
-import static org.gradle.util.GUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-public class GUtilTest {
-    @Test
-    public void convertStringToCamelCase() {
-        assertThat(toCamelCase(null), equalTo(null));
-        assertThat(toCamelCase(""), equalTo(""));
-        assertThat(toCamelCase("word"), equalTo("Word"));
-        assertThat(toCamelCase("twoWords"), equalTo("TwoWords"));
-        assertThat(toCamelCase("TwoWords"), equalTo("TwoWords"));
-        assertThat(toCamelCase("two-words"), equalTo("TwoWords"));
-        assertThat(toCamelCase("two.words"), equalTo("TwoWords"));
-        assertThat(toCamelCase("two words"), equalTo("TwoWords"));
-        assertThat(toCamelCase("two Words"), equalTo("TwoWords"));
-        assertThat(toCamelCase("Two Words"), equalTo("TwoWords"));
-        assertThat(toCamelCase(" Two  \t words\n"), equalTo("TwoWords"));
-        assertThat(toCamelCase("four or so Words"), equalTo("FourOrSoWords"));
-        assertThat(toCamelCase("trailing-"), equalTo("Trailing"));
-        assertThat(toCamelCase("ABC"), equalTo("ABC"));
-        assertThat(toCamelCase("."), equalTo(""));
-        assertThat(toCamelCase("-"), equalTo(""));
-    }
-
-    @Test
-    public void convertStringToConstantName() {
-        assertThat(toConstant(null), equalTo(null));
-        assertThat(toConstant(""), equalTo(""));
-        assertThat(toConstant("word"), equalTo("WORD"));
-        assertThat(toConstant("twoWords"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("TwoWords"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("two-words"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("TWO_WORDS"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("two.words"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("two words"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("two Words"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("Two Words"), equalTo("TWO_WORDS"));
-        assertThat(toConstant(" Two  \t words\n"), equalTo("TWO_WORDS"));
-        assertThat(toConstant("four or so Words"), equalTo("FOUR_OR_SO_WORDS"));
-        assertThat(toConstant("trailing-"), equalTo("TRAILING"));
-        assertThat(toConstant("a"), equalTo("A"));
-        assertThat(toConstant("A"), equalTo("A"));
-        assertThat(toConstant("aB"), equalTo("A_B"));
-        assertThat(toConstant("ABC"), equalTo("ABC"));
-        assertThat(toConstant("ABCThing"), equalTo("ABC_THING"));
-        assertThat(toConstant("ABC Thing"), equalTo("ABC_THING"));
-        assertThat(toConstant("."), equalTo(""));
-        assertThat(toConstant("-"), equalTo(""));
-    }
-
-    @Test
-    public void convertStringToWords() {
-        assertThat(toWords(null), equalTo(null));
-        assertThat(toWords(""), equalTo(""));
-        assertThat(toWords("word"), equalTo("word"));
-        assertThat(toWords("twoWords"), equalTo("two words"));
-        assertThat(toWords("TwoWords"), equalTo("two words"));
-        assertThat(toWords("two words"), equalTo("two words"));
-        assertThat(toWords("Two Words"), equalTo("two words"));
-        assertThat(toWords(" Two  \t words\n"), equalTo("two words"));
-        assertThat(toWords("two_words"), equalTo("two words"));
-        assertThat(toWords("two.words"), equalTo("two words"));
-        assertThat(toWords("two,words"), equalTo("two words"));
-        assertThat(toWords("trailing-"), equalTo("trailing"));
-        assertThat(toWords("a"), equalTo("a"));
-        assertThat(toWords("aB"), equalTo("a b"));
-        assertThat(toWords("ABC"), equalTo("abc"));
-        assertThat(toWords("ABCThing"), equalTo("abc thing"));
-        assertThat(toWords("ABC Thing"), equalTo("abc thing"));
-        assertThat(toWords("."), equalTo(""));
-        assertThat(toWords("_"), equalTo(""));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
index 5272d38..bc8447d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
@@ -19,7 +19,10 @@ package org.gradle.util
 import org.apache.ivy.Ivy
 import org.apache.tools.ant.Main
 import org.codehaus.groovy.runtime.InvokerHelper
-import spock.lang.Specification
+import org.gradle.internal.os.OperatingSystem
+
+import spock.lang.*
+import org.gradle.internal.jvm.Jvm
 
 /**
  * @author Hans Dockter
@@ -27,6 +30,12 @@ import spock.lang.Specification
 class GradleVersionTest extends Specification {
     final GradleVersion version = GradleVersion.current()
 
+    def currentVersionHasNonNullVersionAndBuildTime() {
+        expect:
+        version.version
+        version.buildTime
+    }
+
     def equalsAndHashCode() {
         expect:
         Matchers.strictlyEquals(GradleVersion.version('0.9'), GradleVersion.version('0.9'))
@@ -65,11 +74,11 @@ class GradleVersionTest extends Specification {
         GradleVersion.version(b) == GradleVersion.version(b)
 
         where:
-        a | b
-        '0.9' | '0.8'
-        '1.0' | '0.10'
+        a      | b
+        '0.9'  | '0.8'
+        '1.0'  | '0.10'
         '10.0' | '2.1'
-        '2.5' | '2.4'
+        '2.5'  | '2.4'
     }
 
     def canComparePointVersions() {
@@ -80,9 +89,12 @@ class GradleVersionTest extends Specification {
         GradleVersion.version(b) == GradleVersion.version(b)
 
         where:
-        a | b
-        '0.9.2' | '0.9.1'
-        '0.10.1' | '0.9.2'
+        a                   | b
+        '0.9.2'             | '0.9.1'
+        '0.10.1'            | '0.9.2'
+        '1.2.3.40'          | '1.2.3.8'
+        '1.2.3.1'           | '1.2.3'
+        '1.2.3.1.4.12.9023' | '1.2.3'
     }
 
     def canComparePointVersionAndMajorVersions() {
@@ -93,9 +105,9 @@ class GradleVersionTest extends Specification {
         GradleVersion.version(b) == GradleVersion.version(b)
 
         where:
-        a | b
+        a       | b
         '0.9.1' | '0.9'
-        '0.10' | '0.9.1'
+        '0.10'  | '0.9.1'
     }
 
     def canComparePreviewsMilestonesAndRCVersions() {
@@ -106,13 +118,13 @@ class GradleVersionTest extends Specification {
         GradleVersion.version(b) == GradleVersion.version(b)
 
         where:
-        a | b
+        a                 | b
         '1.0-milestone-2' | '1.0-milestone-1'
-        '1.0-preview-2' | '1.0-preview-1'
-        '1.0-rc-2' | '1.0-rc-1'
-        '1.0-preview-1' | '1.0-milestone-7'
-        '1.0-rc-7' | '1.0-rc-1'
-        '1.0' | '1.0-rc-7'
+        '1.0-preview-2'   | '1.0-preview-1'
+        '1.0-rc-2'        | '1.0-rc-1'
+        '1.0-preview-1'   | '1.0-milestone-7'
+        '1.0-rc-7'        | '1.0-rc-1'
+        '1.0'             | '1.0-rc-7'
     }
 
     def canCompareSnapshotVersions() {
@@ -123,13 +135,20 @@ class GradleVersionTest extends Specification {
         GradleVersion.version(b) == GradleVersion.version(b)
 
         where:
-        a | b
+        a                         | b
         '0.9-20101220110000+1100' | '0.9-20101220100000+1100'
         '0.9-20101220110000+1000' | '0.9-20101220100000+1100'
         '0.9-20101220110000-0100' | '0.9-20101220100000+0000'
-        '0.9' | '0.9-20101220100000+1000'
+        '0.9'                     | '0.9-20101220100000+1000'
     }
 
+    @Issue("http://issues.gradle.org/browse/GRADLE-1892")
+    def "build time should always print in UTC"() {
+        expect:
+        version.buildTime.endsWith("UTC")
+    }
+    
+    
     def defaultValuesForGradleVersion() {
         expect:
         version.version != null
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/core/src/test/groovy/org/gradle/util/HelperUtil.groovy
deleted file mode 100644
index d1eea95..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/HelperUtil.groovy
+++ /dev/null
@@ -1,182 +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.util
-
-import java.rmi.server.UID
-import org.apache.ivy.core.module.descriptor.Configuration
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
-import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.apache.ivy.core.module.id.ArtifactId
-import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher
-import org.apache.ivy.plugins.matcher.PatternMatcher
-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.artifacts.configurations.DefaultConfiguration
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.api.internal.project.DefaultProject
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
-import org.gradle.api.internal.project.taskfactory.ITaskFactory
-import org.gradle.api.internal.project.taskfactory.TaskFactory
-import org.gradle.api.specs.AndSpec
-import org.gradle.api.specs.Spec
-import org.gradle.groovy.scripts.DefaultScript
-import org.gradle.groovy.scripts.Script
-import org.gradle.groovy.scripts.ScriptSource
-import org.gradle.groovy.scripts.StringScriptSource
-import org.gradle.testfixtures.ProjectBuilder
-
-/**
- * @author Hans Dockter
- */
-class HelperUtil {
-
-    public static final Closure TEST_CLOSURE = {}
-    public static final Spec TEST_SEPC  = new AndSpec()
-    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) {
-        return createTask(type, createRootProject())
-    }
-    
-    static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
-        return createTask(type, project, 'name')
-    }
-
-    static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
-        return TASK_FACTORY.createTask(project, [name: name, type: type])
-    }
-
-    static DefaultProject createRootProject() {
-        createRootProject(TemporaryFolder.newInstance().dir)
-    }
-
-    static DefaultProject createRootProject(File rootDir) {
-        return ProjectBuilder.builder().withProjectDir(rootDir).build()
-    }
-
-    static DefaultProject createChildProject(DefaultProject parentProject, String name, File projectDir = null) {
-        DefaultProject project = CLASS_GENERATOR.newInstance(
-                DefaultProject.class,
-                name,
-                parentProject,
-                projectDir ?: new File(parentProject.getProjectDir(), name),
-                new StringScriptSource("test build file", null),
-                parentProject.gradle,
-                parentProject.gradle.services
-        )
-        parentProject.addChildProject project
-        parentProject.projectRegistry.addProject project
-        return project
-    }
-
-    static def pureStringTransform(def collection) {
-        collection.collect {
-            it.toString()
-        }
-    }
-
-    static DefaultExcludeRule getTestExcludeRule() {
-        new DefaultExcludeRule(new ArtifactId(
-                new ModuleId('org', 'module'), PatternMatcher.ANY_EXPRESSION,
-                PatternMatcher.ANY_EXPRESSION,
-                PatternMatcher.ANY_EXPRESSION),
-                ExactPatternMatcher.INSTANCE, null)
-    }
-
-    static DefaultDependencyDescriptor getTestDescriptor() {
-        new DefaultDependencyDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), false)
-    }
-
-    static DefaultModuleDescriptor createModuleDescriptor(Set confs) {
-        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), "status", null)
-        confs.each { moduleDescriptor.addConfiguration(new Configuration(it)) }
-        return moduleDescriptor;
-    }
-
-    static BuildResult createBuildResult(Throwable t) {
-        return new BuildResult(null, t);
-    }
-
-    static ModuleDependency createDependency(String group, String name, String version) {
-      new DefaultExternalModuleDependency(group, name, version)
-    }
-
-    static DefaultPublishArtifact createPublishArtifact(String name, String extension, String type, String classifier) {
-      new DefaultPublishArtifact(name, extension, type, classifier, new Date(), new File(""))
-    }
-
-    static groovy.lang.Script createScript(String code) {
-        new GroovyShell().parse(code)
-    }
-
-    static Object call(String text, Object params) {
-        toClosure(text).call(params)
-    }
-    
-    static Closure toClosure(String text) {
-        return new GroovyShell().evaluate("return " + text)
-    }
-
-    static Closure toClosure(ScriptSource source) {
-        CompilerConfiguration configuration = new CompilerConfiguration();
-        configuration.setScriptBaseClass(TestScript.getName());
-
-        GroovyShell shell = new GroovyShell(configuration)
-        Script script = shell.parse(source.resource.text)
-        script.setScriptSource(source)
-        return script.run()
-    }
-
-    static Closure toClosure(TestClosure closure) {
-        return { param -> closure.call(param) }
-    }
-
-    static Closure returns(Object value) {
-        return { value }
-    }
-
-    static Closure createSetterClosure(String name, String value) {
-        return {
-            "set$name"(value)
-        }
-    }
-
-    static String createUniqueId() {
-        return new UID().toString();
-    }
-
-    static org.gradle.api.artifacts.Configuration createConfiguration(String name) {
-        return new DefaultConfiguration(name, name, null, null)
-    }
-}
-
-public interface TestClosure {
-    Object call(Object param);
-}
-
-public abstract class TestScript extends DefaultScript {
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/JUnit4GroovyMockery.java b/subprojects/core/src/test/groovy/org/gradle/util/JUnit4GroovyMockery.java
deleted file mode 100644
index 71da9a0..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/JUnit4GroovyMockery.java
+++ /dev/null
@@ -1,118 +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.util;
-
-/**
- * @author Hans Dockter
- */
-
-import groovy.lang.Closure;
-import org.codehaus.groovy.runtime.InvokerInvocationException;
-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.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class JUnit4GroovyMockery extends JUnit4Mockery {
-    private final ConcurrentMap<String, AtomicInteger> names = new ConcurrentHashMap<String, AtomicInteger>();
-
-    public JUnit4GroovyMockery() {
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }
-
-    @Override
-    public <T> T mock(Class<T> typeToMock) {
-        return mock(typeToMock, typeToMock.getSimpleName());
-    }
-
-    @Override
-    public <T> T mock(Class<T> typeToMock, String name) {
-        names.putIfAbsent(name, new AtomicInteger());
-        int count = names.get(name).getAndIncrement();
-        T mock;
-        if (count == 0) {
-            mock = super.mock(typeToMock, name);
-        } else {
-            mock = super.mock(typeToMock, name + count);
-        }
-        if (mock instanceof ClassLoader) {
-            for (Field field : ClassLoader.class.getDeclaredFields()) {
-                if (field.getName().equals("initialized")) {
-                    field.setAccessible(true);
-                    try {
-                        field.set(mock, true);
-                    } catch (IllegalAccessException e) {
-                        throw UncheckedException.asUncheckedException(e);
-                    }
-                    break;
-                }
-            }
-        }
-        return mock;
-    }
-
-    class ClosureExpectations extends Expectations {
-        void closureInit(Closure cl, Object delegate) {
-            cl.setDelegate(delegate);
-            cl.call();
-        }
-
-        <T> void withParam(Matcher<T> matcher) {
-            this.with(matcher);
-        }
-
-        void will(final Closure cl) {
-            will(new Action() {
-                public void describeTo(Description description) {
-                    description.appendText("execute closure");
-                }
-
-                public Object invoke(Invocation invocation) throws Throwable {
-                    List<Object> params = Arrays.asList(invocation.getParametersAsArray());
-                    Object result;
-                    try {
-                        List<Object> subParams = params.subList(0, Math.min(invocation.getParametersAsArray().length,
-                                cl.getMaximumNumberOfParameters()));
-                        result = cl.call(subParams.toArray(new Object[subParams.size()]));
-                    } catch (InvokerInvocationException e) {
-                        throw e.getCause();
-                    }
-                    if (invocation.getInvokedMethod().getReturnType().isInstance(result)) {
-                        return result;
-                    }
-                    return null;
-                }
-            });
-        }
-    }
-
-    public void checking(Closure c) {
-        ClosureExpectations expectations = new ClosureExpectations();
-        expectations.closureInit(c, expectations);
-        super.checking(expectations);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java b/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java
index 7699ffd..850778b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/util/JavaMethodTest.java
@@ -30,7 +30,7 @@ public class JavaMethodTest {
 
     @Test
     public void invokesMethodOnObject() {
-        JavaMethod<CharSequence, CharSequence> method = new JavaMethod<CharSequence, CharSequence>(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
+        JavaMethod<CharSequence, CharSequence> method = JavaMethod.create(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
         assertThat(method.invoke("string", 0, 3), equalTo((CharSequence) "str"));
     }
     
@@ -43,7 +43,7 @@ public class JavaMethodTest {
             will(throwException(failure));
         }});
 
-        JavaMethod<CharSequence, CharSequence> method = new JavaMethod<CharSequence, CharSequence>(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
+        JavaMethod<CharSequence, CharSequence> method = JavaMethod.create(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
         try {
             method.invoke(mock, 0, 3);
             fail();
@@ -62,7 +62,7 @@ public class JavaMethodTest {
             }
         };
 
-        JavaMethod<ClassLoader, Package[]> method = new JavaMethod<ClassLoader, Package[]>(ClassLoader.class, Package[].class, "getPackages");
+        JavaMethod<ClassLoader, Package[]> method = JavaMethod.create(ClassLoader.class, Package[].class, "getPackages");
         assertThat(method.invoke(classLoader), sameInstance(packages));
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/JvmTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/JvmTest.groovy
deleted file mode 100644
index 57af7b1..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/JvmTest.groovy
+++ /dev/null
@@ -1,114 +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.util
-
-import spock.lang.Specification
-import org.junit.Rule
-
-class JvmTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
-    @Rule public final SetSystemProperties sysProp = new SetSystemProperties()
-    final OperatingSystem os = Mock()
-
-    def usesSystemPropertyToDetermineIfCompatibleWithJava5() {
-        System.properties['java.version'] = '1.5'
-
-        expect:
-        def jvm = Jvm.current()
-        jvm.java5Compatible
-        !jvm.java6Compatible
-    }
-
-    def usesSystemPropertyToDetermineIfCompatibleWithJava6() {
-        System.properties['java.version'] = '1.6'
-
-        expect:
-        def jvm = Jvm.current()
-        jvm.java5Compatible
-        jvm.java6Compatible
-    }
-
-    def looksForToolsJarInJavaHomeDirectory() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
-        System.properties['java.home'] = javaHomeDir.absolutePath
-
-        expect:
-        def jvm = Jvm.current()
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == toolsJar
-    }
-
-    def looksForToolsJarInParentOfJavaHomeDirectory() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
-        System.properties['java.home'] = javaHomeDir.file('jre').absolutePath
-
-        expect:
-        def jvm = Jvm.current()
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == toolsJar
-    }
-
-    def looksForToolsJarInSiblingOfJavaHomeDirectoryOnWindows() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk1.6.0')
-        TestFile toolsJar = javaHomeDir.file('lib/tools.jar').createFile()
-        System.properties['java.home'] = tmpDir.createDir('jre6').absolutePath
-        System.properties['java.version'] = '1.6.0'
-        _ * os.windows >> true
-
-        expect:
-        def jvm = new Jvm(os)
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == toolsJar
-    }
-
-    def usesSystemPropertyToLocateJavaHomeWhenToolsJarNotFound() {
-        TestFile javaHomeDir = tmpDir.createDir('jdk')
-        System.properties['java.home'] = javaHomeDir.absolutePath
-
-        expect:
-        def jvm = Jvm.current()
-        jvm.javaHome == javaHomeDir
-        jvm.toolsJar == null
-    }
-
-    def usesSystemPropertyToDetermineIfAppleJvm() {
-
-        when:
-        System.properties['java.vm.vendor'] = 'Apple Inc.'
-        def jvm = Jvm.current()
-
-        then:
-        jvm.class == Jvm.AppleJvm
-
-        when:
-        System.properties['java.vm.vendor'] = 'Sun'
-        jvm = Jvm.current()
-
-        then:
-        jvm.class == Jvm
-    }
-
-    def appleJvmFiltersEnvironmentVariables() {
-        Map<String, String> env = ['APP_NAME_1234': 'App', 'JAVA_MAIN_CLASS_1234': 'MainClass', 'OTHER': 'value']
-
-        expect:
-        def jvm = new Jvm.AppleJvm(os)
-        jvm.getInheritableEnvironmentVariables(env) == ['OTHER': 'value']
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java b/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
index 0a69ded..71bb37a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
@@ -16,6 +16,7 @@
 package org.gradle.util;
 
 import org.gradle.api.Action;
+import org.gradle.internal.SystemProperties;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/Matchers.java b/subprojects/core/src/test/groovy/org/gradle/util/Matchers.java
deleted file mode 100644
index 6230a50..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/Matchers.java
+++ /dev/null
@@ -1,375 +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.util;
-
-import org.gradle.api.Buildable;
-import org.gradle.api.Task;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.file.UnionFileCollection;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.hamcrest.*;
-import org.jmock.api.Action;
-import org.jmock.api.Invocation;
-import org.jmock.internal.ReturnDefaultValueAction;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.*;
-import java.util.regex.Pattern;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertTrue;
-
-public class Matchers {
-    @Factory
-    public static <T> Matcher<T> reflectionEquals(T equalsTo) {
-        return new ReflectionEqualsMatcher<T>(equalsTo);
-    }
-
-    @Factory
-    public static <T, S extends Iterable<? extends T>> Matcher<S> hasSameItems(final S items) {
-        return new BaseMatcher<S>() {
-            public boolean matches(Object o) {
-                Iterable<? extends T> iterable = (Iterable<? extends T>) o;
-                List<T> actual = new ArrayList<T>();
-                for (T t : iterable) {
-                    actual.add(t);
-                }
-                List<T> expected = new ArrayList<T>();
-                for (T t : items) {
-                    expected.add(t);
-                }
-
-                return expected.equals(actual);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an Iterable that has same items as ").appendValue(items);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends CharSequence> Matcher<T> matchesRegexp(final String pattern) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                return Pattern.compile(pattern).matcher((CharSequence) o).matches();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends CharSequence> Matcher<T> matchesRegexp(final Pattern pattern) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                return pattern.matcher((CharSequence) o).matches();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
-            }
-        };
-    }
-
-    @Factory
-    public static <T> Matcher<T> strictlyEqual(final T other) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                return strictlyEquals(o, other);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an Object that strictly equals ").appendValue(other);
-            }
-        };
-    }
-
-    public static boolean strictlyEquals(Object a, Object b) {
-        if (!a.equals(b)) {
-            return false;
-        }
-        if (!b.equals(a)) {
-            return false;
-        }
-        if (!a.equals(a)) {
-            return false;
-        }
-        if (b.equals(null)) {
-            return false;
-        }
-        if (b.equals(new Object())) {
-            return false;
-        }
-        if (a.hashCode() != b.hashCode()) {
-            return false;
-        }
-        return true;
-
-    }
-
-    @Factory
-    public static Matcher<String> containsLine(final String line) {
-        return new BaseMatcher<String>() {
-            public boolean matches(Object o) {
-                return containsLine(equalTo(line)).matches(o);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a String that contains line ").appendValue(line);
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<String> containsLine(final Matcher<? super String> matcher) {
-        return new BaseMatcher<String>() {
-            public boolean matches(Object o) {
-                String str = (String) o;
-                return containsLine(str, matcher);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a String that contains line that is ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    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<?>>() {
-            public boolean matches(Object o) {
-                Iterable<?> iterable = (Iterable<?>) o;
-                return iterable != null && !iterable.iterator().hasNext();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an empty Iterable");
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Map<?, ?>> isEmptyMap() {
-        return new BaseMatcher<Map<?, ?>>() {
-            public boolean matches(Object o) {
-                Map<?, ?> map = (Map<?, ?>) o;
-                return map != null && map.isEmpty();
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an empty map");
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Object[]> isEmptyArray() {
-        return new BaseMatcher<Object[]>() {
-            public boolean matches(Object o) {
-                Object[] array = (Object[]) o;
-                return array != null && array.length == 0;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an empty array");
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
-        return new BaseMatcher<Throwable>() {
-            public boolean matches(Object o) {
-                Throwable t = (Throwable) o;
-                return matcher.matches(t.getMessage());
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("an exception with message that is ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    @Factory
-    public static Matcher<Task> dependsOn(final String... tasks) {
-        return dependsOn(equalTo(new HashSet<String>(Arrays.asList(tasks))));
-    }
-
-    @Factory
-    public static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher) {
-        return new BaseMatcher<Task>() {
-            public boolean matches(Object o) {
-                Task task = (Task) o;
-                Set<String> names = new HashSet<String>();
-                Set<? extends Task> depTasks = task.getTaskDependencies().getDependencies(task);
-                for (Task depTask : depTasks) {
-                    names.add(depTask.getName());
-                }
-                boolean matches = matcher.matches(names);
-                if (!matches) {
-                    StringDescription description = new StringDescription();
-                    matcher.describeTo(description);
-                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
-                }
-                return matches;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a Task that depends on ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends Buildable> Matcher<T> builtBy(String... tasks) {
-        return builtBy(equalTo(new HashSet<String>(Arrays.asList(tasks))));
-    }
-
-    @Factory
-    public static <T extends Buildable> Matcher<T> builtBy(final Matcher<? extends Iterable<String>> matcher) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                Buildable task = (Buildable) o;
-                Set<String> names = new HashSet<String>();
-                Set<? extends Task> depTasks = task.getBuildDependencies().getDependencies(null);
-                for (Task depTask : depTasks) {
-                    names.add(depTask.getName());
-                }
-                boolean matches = matcher.matches(names);
-                if (!matches) {
-                    StringDescription description = new StringDescription();
-                    matcher.describeTo(description);
-                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
-                }
-                return matches;
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("a Buildable that is built by ").appendDescriptionOf(matcher);
-            }
-        };
-    }
-
-    @Factory
-    public static <T extends FileCollection> Matcher<T> sameCollection(final FileCollection expected) {
-        return new BaseMatcher<T>() {
-            public boolean matches(Object o) {
-                FileCollection actual = (FileCollection) o;
-                List<? extends FileCollection> actualCollections = unpack(actual);
-                List<? extends FileCollection> expectedCollections = unpack(expected);
-                boolean equals = actualCollections.equals(expectedCollections);
-                if (!equals) {
-                    System.out.println("expected: " + expectedCollections);
-                    System.out.println("actual: " + actualCollections);
-                }
-                return equals;
-            }
-
-            private List<? extends FileCollection> unpack(FileCollection expected) {
-                if (expected instanceof UnionFileCollection) {
-                    UnionFileCollection collection = (UnionFileCollection) expected;
-                    return new ArrayList<FileCollection>(collection.getSources());
-                }
-                if (expected instanceof DefaultConfigurableFileCollection) {
-                    DefaultConfigurableFileCollection collection = (DefaultConfigurableFileCollection) expected;
-                    return new ArrayList<FileCollection>((Set) collection.getFrom());
-                }
-                throw new RuntimeException("Cannot get children of " + expected);
-            }
-
-            public void describeTo(Description description) {
-                description.appendText("same file collection as ").appendValue(expected);
-            }
-        };
-    }
-
-    /**
-     * Returns a placeholder for a mock method parameter.
-     */
-    public static <T> Collector<T> collector() {
-        return new Collector<T>();
-    }
-
-    /**
-     * Returns an action which collects the first parameter into the given placeholder.
-     */
-    public static CollectAction collectTo(Collector<?> collector) {
-        return new CollectAction(collector);
-    }
-
-    public static class CollectAction implements Action {
-        private Action action = new ReturnDefaultValueAction();
-        private final Collector<?> collector;
-
-        public CollectAction(Collector<?> collector) {
-            this.collector = collector;
-        }
-
-        public Action then(Action action) {
-            this.action = action;
-            return this;
-        }
-
-        public void describeTo(Description description) {
-            description.appendText("collect parameter then ").appendDescriptionOf(action);
-        }
-
-        public Object invoke(Invocation invocation) throws Throwable {
-            collector.setValue(invocation.getParameter(0));
-            return action.invoke(invocation);
-        }
-    }
-
-    public static class Collector<T> {
-        private T value;
-        private boolean set;
-
-        public T get() {
-            assertTrue(set);
-            return value;
-        }
-
-        void setValue(Object parameter) {
-            value = (T) parameter;
-            set = true;
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
index aec53b3..6efd696 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
@@ -37,6 +37,11 @@ class MultiParentClassLoaderTest {
     }
 
     @Test
+    public void parentsAreNotVisibleViaSuperClass() {
+        assertThat(loader.parent, nullValue())
+    }
+
+    @Test
     public void loadsClassFromParentsInOrderSpecified() {
         Class stringClass = String.class
         Class integerClass = Integer.class
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/MultithreadedTestCase.java b/subprojects/core/src/test/groovy/org/gradle/util/MultithreadedTestCase.java
deleted file mode 100644
index ac9483d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/MultithreadedTestCase.java
+++ /dev/null
@@ -1,665 +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.util;
-
-import groovy.lang.Closure;
-import junit.framework.AssertionFailedError;
-import org.codehaus.groovy.runtime.InvokerInvocationException;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.hamcrest.Matcher;
-import org.junit.After;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * <p>A base class for testing concurrent code.</p>
- *
- * <p>Provides several ways to start and manage threads. You can use the {@link #start(groovy.lang.Closure)} or {@link
- * #run(groovy.lang.Closure)} methods to execute test code in other threads. You can use {@link #waitForAll()} to wait
- * for all test threads to complete. In addition, the test tear-down method blocks until all test threads have stopped
- * and ensures that no exceptions were thrown in any test threads.</p>
- *
- * <p>Provides an {@link java.util.concurrent.Executor} implementation, which uses test threads to execute any tasks
- * submitted to it.</p>
- *
- * <p>You can use {@link #syncAt(int)} and {@link #expectBlocksUntil(int, groovy.lang.Closure)} to synchronise between
- * test threads.</p>
- */
-public class MultithreadedTestCase {
-    private static final Logger LOGGER = LoggerFactory.getLogger(MultithreadedTestCase.class);
-    private static final int MAX_WAIT_TIME = 5000;
-    private ExecutorImpl executor;
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private final Set<Thread> active = new HashSet<Thread>();
-    private final Set<Thread> synching = new HashSet<Thread>();
-    private final List<Throwable> failures = new ArrayList<Throwable>();
-    private final Map<Integer, ClockTickImpl> ticks = new HashMap<Integer, ClockTickImpl>();
-    private ClockTickImpl currentTick = getTick(0);
-    private boolean stopped;
-    private final ThreadLocal<Matcher<? extends Throwable>> expectedFailure
-            = new ThreadLocal<Matcher<? extends Throwable>>();
-    private final SyncPoint syncPoint = new SyncPoint();
-
-    /**
-     * Creates an Executor which the test can control.
-     */
-    protected ExecutorService getExecutor() {
-        if (executor == null) {
-            executor = new ExecutorImpl();
-        }
-        return executor;
-    }
-
-    /**
-     * Creates an ExecutorFactory for the test to use.
-     */
-    protected ExecutorFactory getExecutorFactory() {
-        return new DefaultExecutorFactory() {
-            @Override
-            protected ExecutorService createExecutor(String displayName) {
-                return getExecutor();
-            }
-        };
-    }
-
-    /**
-     * Executes the given closure in a test thread.
-     */
-    protected ThreadHandle start(final Closure closure) {
-        Runnable task = new Runnable() {
-            public void run() {
-                closure.call();
-            }
-        };
-
-        return start(task);
-    }
-
-    /**
-     * Executes the given closure in a test thread and waits for it to complete.
-     */
-    protected ThreadHandle run(final Closure closure) {
-        Runnable task = new Runnable() {
-            public void run() {
-                closure.call();
-            }
-        };
-
-        return start(task).waitFor();
-    }
-
-    protected ThreadHandle expectTimesOut(int value, TimeUnit units, Closure closure) {
-        Date start = new Date();
-        ThreadHandle threadHandle = start(closure);
-        threadHandle.waitFor();
-        Date end = new Date();
-        long actual = end.getTime() - start.getTime();
-        long expected = units.toMillis(value);
-        if (actual < expected - 200) {
-            throw new RuntimeException(String.format(
-                    "Action did not block for expected time. Expected ~ %d ms, was %d ms.", expected, actual));
-        }
-        if (actual > expected + 500) {
-            throw new RuntimeException(String.format(
-                    "Action did not complete within expected time. Expected ~ %d ms, was %d ms.", expected, actual));
-        }
-        return threadHandle;
-    }
-
-    /**
-     * Executes the given runnable in a test thread.
-     */
-    protected ThreadHandle start(final Runnable task) {
-        final Thread thread = new Thread() {
-            @Override
-            public String toString() {
-                return "test thread " + getId();
-            }
-
-            public void run() {
-                Throwable failure = null;
-                try {
-                    try {
-                        task.run();
-                    } catch (InvokerInvocationException e) {
-                        failure = e.getCause();
-                    } catch (Throwable throwable) {
-                        failure = throwable;
-                    }
-                } finally {
-                    testThreadFinished(this, failure);
-                }
-            }
-        };
-
-        testThreadStarted(thread);
-        thread.start();
-        return new ThreadHandleImpl(thread);
-    }
-
-    private void testThreadStarted(Thread thread) {
-        lock.lock();
-        try {
-            if (stopped) {
-                throw new IllegalStateException("Cannot start new threads, as this test case has been stopped.");
-            }
-            LOGGER.debug("Started {}", thread);
-            active.add(thread);
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void testThreadFinished(Thread thread, Throwable failure) {
-        lock.lock();
-        try {
-            active.remove(thread);
-            Matcher<? extends Throwable> matcher = expectedFailure.get();
-            if (failure != null) {
-                if (matcher != null && matcher.matches(failure)) {
-                    LOGGER.debug("Finished {} with expected failure.", thread);
-                } else {
-                    LOGGER.error(String.format("Failure in %s", thread), failure);
-                    failures.add(failure);
-                }
-            } else {
-                if (matcher != null) {
-                    String message = String.format("Did not get expected failure in %s", thread);
-                    LOGGER.error(message);
-                    failures.add(new AssertionFailedError(message));
-                } else {
-                    LOGGER.debug("Finished {}", thread);
-                }
-            }
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Waits for all asynchronous activity to complete. Applies a timeout, and re-throws any exceptions which occurred.
-     */
-    public void waitForAll() {
-        Date expiry = new Date(System.currentTimeMillis() + 2 * MAX_WAIT_TIME);
-        lock.lock();
-        try {
-            LOGGER.debug("Waiting for test threads complete.");
-
-            if (active.contains(Thread.currentThread())) {
-                throw new RuntimeException("A test thread cannot wait for test threads to complete.");
-            }
-            try {
-                while (!active.isEmpty()) {
-                    boolean signaled = condition.awaitUntil(expiry);
-                    if (!signaled) {
-                        failures.add(new RuntimeException("Timeout waiting for threads to finish."));
-                        break;
-                    }
-                }
-            } catch (InterruptedException e) {
-                throw UncheckedException.asUncheckedException(e);
-            }
-
-            LOGGER.debug("All test threads complete.");
-
-            if (!failures.isEmpty()) {
-                Throwable failure = failures.get(0);
-                failures.clear();
-                if (failure instanceof RuntimeException) {
-                    throw (RuntimeException) failure;
-                }
-                if (failure instanceof Error) {
-                    throw (Error) failure;
-                }
-                throw new RuntimeException("An unexpected exception occurred in a test thread.", failure);
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    @After
-    public void waitForStop() {
-        lock.lock();
-        try {
-            stopped = true;
-        } finally {
-            lock.unlock();
-        }
-        waitForAll();
-    }
-
-    /**
-     * Returns the meta-info for the given clock tick.
-     */
-    public ClockTick clockTick(int tick) {
-        lock.lock();
-        try {
-            return getTick(tick);
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private ClockTickImpl getTick(int tick) {
-        ClockTickImpl clockTick = ticks.get(tick);
-        if (clockTick == null) {
-            clockTick = new ClockTickImpl(tick);
-            ticks.put(tick, clockTick);
-        }
-        return clockTick;
-    }
-
-    /**
-     * Blocks until the clock has reached the given tick. The clock advances to the given tick when all test threads
-     * have called {@link #syncAt(int)} or {@link #expectBlocksUntil(int, groovy.lang.Closure)} with the given tick, and
-     * there are least 2 test threads.
-     *
-     * @param tick The expected clock tick
-     */
-    public void syncAt(int tick) {
-        LOGGER.debug("Thread {} synching at tick {}", Thread.currentThread(), tick);
-
-        lock.lock();
-        try {
-            ClockTickImpl clockTick = getTick(tick);
-            if (!clockTick.isImmediatelyAfter(currentTick)) {
-                throw new RuntimeException(String.format("Cannot wait for %s, as clock is currently at %s.", clockTick,
-                        currentTick));
-            }
-            if (!active.contains(Thread.currentThread())) {
-                throw new RuntimeException("Cannot wait for clock tick from a thread which is not a test thread.");
-            }
-
-            Date expiry = new Date(System.currentTimeMillis() + MAX_WAIT_TIME);
-            synching.add(Thread.currentThread());
-            condition.signalAll();
-            while (failures.isEmpty() && currentTick != clockTick && !clockTick.allThreadsSynced(synching, active)) {
-                try {
-                    boolean signalled = condition.awaitUntil(expiry);
-                    if (!signalled) {
-                        throw new RuntimeException(String.format(
-                                "Timeout waiting for all threads to reach %s. Currently at %s.", clockTick,
-                                currentTick));
-                    }
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            if (!failures.isEmpty()) {
-                throw new RuntimeException(String.format(
-                        "Could not wait for all threads to reach %s, as a failure has occurred in another test thread.",
-                        clockTick));
-            }
-            if (clockTick.isImmediatelyAfter(currentTick)) {
-                currentTick = clockTick;
-                synching.clear();
-            }
-        } finally {
-            lock.unlock();
-        }
-
-        LOGGER.debug("Thread {} sync done", Thread.currentThread());
-    }
-
-    /**
-     * Expects that the given tick will be reached at some point in the future. Does not block until the tick has been
-     * reached.
-     *
-     * @param tick The expected clock tick.
-     */
-    public void expectLater(final int tick) {
-        final Thread targetThread = Thread.currentThread();
-        LOGGER.debug("Thread {} expecting tick {}", targetThread, tick);
-        start(new Runnable() {
-            public void run() {
-                try {
-                    Thread.sleep(200L);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                lock.lock();
-                try {
-                    ClockTickImpl clockTick = getTick(tick);
-                    if (!clockTick.isImmediatelyAfter(currentTick)) {
-                        throw new RuntimeException(String.format("Cannot wait for %s, as clock is currently at %s.",
-                                clockTick, currentTick));
-                    }
-                    if (!active.contains(targetThread)) {
-                        throw new RuntimeException(
-                                "Cannot wait for clock tick from a thread which is not a test thread.");
-                    }
-
-                    synching.add(targetThread);
-                    condition.signalAll();
-                } finally {
-                    lock.unlock();
-                }
-            }
-        });
-    }
-
-    /**
-     * Asserts that the given closure blocks until the given clock tick is reached.
-     *
-     * @param tick The expected clock tick when the closure completes.
-     * @param closure The closure to execute.
-     */
-    public void expectBlocksUntil(int tick, Closure closure) {
-        expectLater(tick);
-        closure.call();
-        shouldBeAt(tick);
-    }
-
-    /**
-     * Asserts that the clock is at the given tick.
-     *
-     * @param tick The expected clock tick.
-     */
-    public void shouldBeAt(int tick) {
-        lock.lock();
-        try {
-            if (currentTick != getTick(tick)) {
-                throw new RuntimeException(String.format("Expected clock to be at %s, but is at %s.", tick,
-                        currentTick));
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Indicates that the current test thread will fail with an exception that matches the given criteria.
-     */
-    public void willFailWith(Matcher<? extends Throwable> matcher) {
-        expectedFailure.set(matcher);
-    }
-
-    /**
-     * Executes the given action in another thread, and asserts that the action blocks until all actions provided to
-     * {@link #expectUnblocks(groovy.lang.Closure)} have been executed.
-     *
-     * @param action The action to execute.
-     */
-    public void expectBlocks(Closure action) {
-        syncPoint.expectBlocks(action);
-    }
-
-    /**
-     * Executes the given action, asserting that it unblocks all actions provided to {@link
-     * #expectBlocks(groovy.lang.Closure)}
-     *
-     * @param action The action to execute.
-     */
-    public void expectUnblocks(Closure action) {
-        syncPoint.expectUnblocks(action);
-    }
-
-    private class ExecutorImpl extends AbstractExecutorService {
-        private final Set<ThreadHandle> threads = new CopyOnWriteArraySet<ThreadHandle>();
-
-        public void execute(Runnable command) {
-            threads.add(start(command));
-        }
-
-        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
-            Date expiry = new Date(System.currentTimeMillis() + unit.toMillis(timeout));
-            for (ThreadHandle thread : threads) {
-                if (!thread.waitUntil(expiry)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        public void shutdown() {
-        }
-
-        public List<Runnable> shutdownNow() {
-            return new ArrayList<Runnable>();
-        }
-
-        public boolean isShutdown() {
-            throw new UnsupportedOperationException();
-        }
-
-        public boolean isTerminated() {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    public interface ThreadHandle {
-        ThreadHandle waitFor();
-
-        boolean waitUntil(Date expiry);
-
-        boolean isCurrentThread();
-
-        void waitUntilBlocked();
-    }
-
-    public interface ClockTick {
-        ClockTick hasParticipants(int count);
-    }
-
-    private static class ClockTickImpl implements ClockTick {
-        private final int number;
-        private int participants;
-
-        private ClockTickImpl(int number) {
-            this.number = number;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("tick %d", number);
-        }
-
-        public ClockTick hasParticipants(int count) {
-            participants = count;
-            return this;
-        }
-
-        public boolean allThreadsSynced(Set<Thread> synching, Set<Thread> active) {
-            if (participants > 0) {
-                return synching.size() == participants;
-            }
-            return synching.equals(active) && synching.size() > 1;
-        }
-
-        public boolean isImmediatelyAfter(ClockTickImpl other) {
-            return number == other.number + 1;
-        }
-    }
-
-    private enum State {
-        Idle, Blocking, Blocked, Unblocking, Unblocked, Failed
-    }
-
-    private class SyncPoint {
-        private final Lock lock = new ReentrantLock();
-        private final Condition condition = lock.newCondition();
-        private State state = State.Idle;
-        private ThreadHandle blockingThread;
-
-        public void expectBlocks(Closure action) {
-            try {
-                setState(State.Idle, State.Blocking);
-                setBlockingThread(start(action));
-                setState(State.Blocking, State.Blocked);
-                waitForState(State.Unblocked, State.Failed);
-            } catch (InterruptedException e) {
-                throw UncheckedException.asUncheckedException(e);
-            }
-        }
-
-        public void expectUnblocks(Closure action) {
-            try {
-                waitForState(State.Blocked);
-
-                ThreadHandle thread = getBlockingThread();
-                if (thread.isCurrentThread()) {
-                    throw new IllegalStateException("The blocking thread cannot unblock itself.");
-                }
-
-                setState(State.Blocked, State.Unblocking);
-                try {
-                    thread.waitUntilBlocked();
-                    action.call();
-                    boolean completed = thread.waitUntil(new Date(System.currentTimeMillis() + 500L));
-                    if (!completed) {
-                        throw new IllegalStateException("Expected blocking action to unblock, but it did not.");
-                    }
-                    setState(State.Unblocking, State.Unblocked);
-                } catch (Throwable e) {
-                    setState(State.Unblocking, State.Failed);
-                    throw UncheckedException.asUncheckedException(e);
-                } finally {
-                    setBlockingThread(null);
-                }
-            } catch (InterruptedException e) {
-                throw UncheckedException.asUncheckedException(e);
-            }
-        }
-
-        private ThreadHandle getBlockingThread() {
-            lock.lock();
-            try {
-                return blockingThread;
-            } finally {
-                lock.unlock();
-            }
-        }
-
-        private void setBlockingThread(ThreadHandle thread) {
-            lock.lock();
-            try {
-                blockingThread = thread;
-            } finally {
-                lock.unlock();
-            }
-        }
-
-        private State waitForState(State... states) throws InterruptedException {
-            Date expiry = new Date(System.currentTimeMillis() + 4000L);
-            Collection<State> expectedStates = Arrays.asList(states);
-            lock.lock();
-            try {
-                while (!expectedStates.contains(state)) {
-                    if (!condition.awaitUntil(expiry)) {
-                        throw new IllegalStateException(String.format("Timeout waiting for one of: %s",
-                                expectedStates));
-                    }
-                }
-                return state;
-            } finally {
-                lock.unlock();
-            }
-        }
-
-        private void setState(State expected, State newState) {
-            lock.lock();
-            try {
-                if (state != expected) {
-                    throw new IllegalStateException(String.format("In unexpected state. Expected %s, actual %s",
-                            expected, state));
-                }
-                state = newState;
-                condition.signalAll();
-            } finally {
-                lock.unlock();
-            }
-        }
-    }
-
-    private class ThreadHandleImpl implements ThreadHandle {
-        private final Thread thread;
-        private final Set<Thread.State> blockedStates = EnumSet.of(Thread.State.BLOCKED, Thread.State.TIMED_WAITING,
-                Thread.State.WAITING);
-
-        public ThreadHandleImpl(Thread thread) {
-            this.thread = thread;
-        }
-
-        public ThreadHandle waitFor() {
-            Date expiry = new Date(System.currentTimeMillis() + 2 * MAX_WAIT_TIME);
-            if (!waitUntil(expiry)) {
-                throw new RuntimeException("timeout waiting for test thread to stop.");
-            }
-            return this;
-        }
-
-        public boolean waitUntil(Date expiry) {
-            if (isCurrentThread()) {
-                throw new RuntimeException("A test thread cannot wait for itself to complete.");
-            }
-
-            lock.lock();
-            try {
-                while (active.contains(thread)) {
-                    try {
-                        boolean signalled = condition.awaitUntil(expiry);
-                        if (!signalled) {
-                            return false;
-                        }
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-            } finally {
-                lock.unlock();
-            }
-
-            return true;
-        }
-
-        public boolean isCurrentThread() {
-            return Thread.currentThread() == thread;
-        }
-
-        public boolean isBlocked() {
-            return blockedStates.contains(thread.getState());
-        }
-
-        public void waitUntilBlocked() {
-            long expiry = System.currentTimeMillis() + 2000L;
-            while (!isBlocked()) {
-                if (System.currentTimeMillis() > expiry) {
-                    throw new IllegalStateException("Timeout waiting for thread to block.");
-                }
-                try {
-                    Thread.sleep(200L);
-                } catch (InterruptedException e) {
-                    throw UncheckedException.asUncheckedException(e);
-                }
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/NameMatcherTest.java b/subprojects/core/src/test/groovy/org/gradle/util/NameMatcherTest.java
index 4659a21..2ec253c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/NameMatcherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/util/NameMatcherTest.java
@@ -15,15 +15,20 @@
  */
 package org.gradle.util;
 
+import org.junit.Test;
+
 import java.util.List;
 import java.util.Map;
-import org.junit.Test;
-import static java.util.Collections.*;
-import static org.gradle.util.GUtil.*;
-import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
+
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.singletonMap;
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.WrapUtil.toList;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
 
 public class NameMatcherTest {
     private final NameMatcher matcher = new NameMatcher();
@@ -198,7 +203,7 @@ public class NameMatcherTest {
     }
 
     private void assertMatches(String name, String match, String... extraItems) {
-        List<String> allItems = addLists(toList(match), toList(extraItems));
+        List<String> allItems = newArrayList(concat(toList(match), toList(extraItems)));
         assertThat(matcher.find(name, allItems), equalTo(match));
         assertThat(matcher.getMatches(), equalTo(toSet(match)));
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ObservableUrlClassLoaderTest.java b/subprojects/core/src/test/groovy/org/gradle/util/ObservableUrlClassLoaderTest.java
deleted file mode 100644
index adf1b19..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/ObservableUrlClassLoaderTest.java
+++ /dev/null
@@ -1,44 +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.util;
-
-import org.gradle.api.Action;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
- at RunWith(JMock.class)
-public class ObservableUrlClassLoaderTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ObservableUrlClassLoader loader = new ObservableUrlClassLoader(null);
-
-    @Test
-    public void notifiesListenerWhenUrlAdded() throws MalformedURLException {
-        final Action<ClassLoader> action = context.mock(Action.class);
-        loader.whenUrlAdded(action);
-
-        context.checking(new Expectations() {{
-            one(action).execute(loader);
-        }});
-
-        loader.addURL(new URL("file:file.txt"));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/Resources.java b/subprojects/core/src/test/groovy/org/gradle/util/Resources.java
deleted file mode 100644
index 57ca170..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/Resources.java
+++ /dev/null
@@ -1,68 +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.util;
-
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-import java.net.URISyntaxException;
-import java.net.URL;
-
-import static org.junit.Assert.*;
-
-/**
- * A JUnit rule which helps locate test resources.
- */
-public class Resources implements MethodRule {
-    private Class<?> testClass;
-
-    /**
-     * Locates the resource with the given name, relative to the current test class. Asserts that the resource exists.
-     */
-    public TestFile getResource(String name) {
-        assertNotNull(testClass);
-        TestFile file = findResource(name);
-        assertNotNull(String.format("Could not locate resource '%s' for test class %s.", name, testClass.getName()), file);
-        return file;
-    }
-
-    /**
-     * Locates the resource with the given name, relative to the current test class.
-     * @return the resource, or null if not found.
-     */
-    public TestFile findResource(String name) {
-        assertNotNull(testClass);
-        URL resource = testClass.getResource(name);
-        if (resource == null) {
-            return null;
-        }
-        assertEquals("file", resource.getProtocol());
-        File file;
-        try {
-            file = new File(resource.toURI());
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
-        }
-        return new TestFile(file);
-    }
-
-    public Statement apply(final Statement statement, FrameworkMethod frameworkMethod, Object o) {
-        testClass = frameworkMethod.getMethod().getDeclaringClass();
-        return statement;
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ServiceLocatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/ServiceLocatorTest.groovy
new file mode 100644
index 0000000..3f1082e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/ServiceLocatorTest.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2011 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.util
+
+import spock.lang.Specification
+import org.gradle.internal.service.UnknownServiceException
+
+class ServiceLocatorTest extends Specification {
+    final ClassLoader classLoader = Mock()
+    final ServiceLocator serviceLocator = new ServiceLocator(classLoader)
+
+    def "locates service implementation class using resources of given ClassLoader"() {
+        def serviceFile = stream('org.gradle.ImplClass')
+
+        when:
+        def result = serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        result == String
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
+    }
+
+    def "findServiceImplementationClass() returns null when no service meta data resource available"() {
+        when:
+        def result = serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        result == null
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> null
+    }
+
+    def "wraps implementation class load failure"() {
+        def serviceFile = stream('org.gradle.ImplClass')
+        def failure = new ClassNotFoundException()
+
+        when:
+        serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not load implementation class 'org.gradle.ImplClass' for service 'java.lang.String'."
+        e.cause == failure
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> { throw failure }
+    }
+
+    def "ignores comments and whitespace in service meta data resource"() {
+        def serviceFile = stream('''#comment
+
+    org.gradle.ImplClass  
+''')
+
+        when:
+        def result = serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        result == String
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
+    }
+
+    def "findServiceImplementationClass() fails when no implementation class specified in service meta data resource"() {
+        def serviceFile = stream('#empty!')
+
+        when:
+        serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not determine implementation class for service 'java.lang.String'."
+        e.cause.message == "No implementation class for service 'java.lang.String' specified in resource '${serviceFile}'."
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+    }
+
+    def "findServiceImplementationClass() fails when implementation class specified in service meta data resource is not assignable to service type"() {
+        given:
+        implementationDeclared(String, Integer)
+
+        when:
+        serviceLocator.findServiceImplementationClass(String)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not load implementation class 'java.lang.Integer' for service 'java.lang.String'."
+        e.cause.message == "Implementation class 'java.lang.Integer' is not assignable to service class 'java.lang.String'."
+    }
+
+    def "get() creates an instance of specified service implementation class"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def result = serviceLocator.get(CharSequence)
+
+        then:
+        result instanceof String
+    }
+
+    def "get() caches service implementation instances"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def obj1 = serviceLocator.get(CharSequence)
+        def obj2 = serviceLocator.get(CharSequence)
+
+        then:
+        obj1.is(obj2)
+    }
+
+    def "get() fails when no meta-data file found for service type"() {
+        when:
+        serviceLocator.get(CharSequence)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
+    }
+
+    def "getFactory() returns a factory which creates instances of implementation class"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def factory = serviceLocator.getFactory(CharSequence)
+        def obj1 = factory.create()
+        def obj2 = factory.create()
+
+        then:
+        obj1 instanceof String
+        obj2 instanceof String
+        !obj1.is(obj2)
+    }
+
+    def "getFactory() fails when no meta-data file found for service type"() {
+        when:
+        serviceLocator.getFactory(CharSequence)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
+    }
+
+    def stream(String contents) {
+        URLStreamHandler handler = Mock()
+        URLConnection connection = Mock()
+        URL url = new URL("custom", "host", 12, "file", handler)
+        _ * handler.openConnection(url) >> connection
+        _ * connection.getInputStream() >> new ByteArrayInputStream(contents.bytes)
+        return url
+    }
+
+    def "newInstance() creates instances of implementation class"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def result = serviceLocator.newInstance(CharSequence)
+
+        then:
+        result instanceof String
+    }
+    
+    def implementationDeclared(Class<?> serviceType, Class<?> implementationType) {
+        def serviceFile = stream(implementationType.name)
+        _ * classLoader.getResource("META-INF/services/${serviceType.name}") >> serviceFile
+        _ * classLoader.loadClass(implementationType.name) >> implementationType
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/StdinSwapperTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/StdinSwapperTest.groovy
new file mode 100644
index 0000000..3640b72
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/StdinSwapperTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.util
+
+import spock.lang.*
+import java.util.concurrent.Callable
+
+class StdinSwapperTest extends Specification {
+
+    def "can swap stdin"() {
+        given:
+        def text = "abc"
+
+        expect:
+        new StdinSwapper().swap(new ByteArrayInputStream("abc".bytes), { System.in.text } as Callable)  == text
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/StdoutSwapperTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/StdoutSwapperTest.groovy
new file mode 100644
index 0000000..c860a6c
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/StdoutSwapperTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.util
+
+import spock.lang.*
+import java.util.concurrent.Callable
+
+class StdoutSwapperTest extends Specification {
+
+    def "can swap stdout"() {
+        given:
+        def original = System.out
+        def bytes = new ByteArrayOutputStream()
+        def stream = new PrintStream(bytes)
+        def text = "abc"
+
+        when:
+        new StdoutSwapper().swap(stream, { 
+            assert System.out.is(stream)
+            print text 
+        } as Callable)
+        
+        then:
+        bytes.toString() == text
+        
+        and:
+        System.out.is(original)
+    }
+
+
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/SwapperTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/SwapperTest.groovy
new file mode 100644
index 0000000..234ee6d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/SwapperTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.util
+
+import spock.lang.*
+import org.gradle.api.Action
+import java.util.concurrent.Callable
+
+class SwapperTest extends Specification {
+
+    def originalValue = 1
+    def value = originalValue
+    def getter = { value }
+    def setter = { this.value = it }
+
+    def swap(newValue, whileSwapped) {
+        new Swapper(getter as Callable, setter as Action).swap(newValue, whileSwapped as Callable)
+    }
+    
+    def "can swap values"() {
+        given:
+        def newValue = 2
+        def returnValue = 3
+        
+        expect:
+        returnValue == swap(newValue) { 
+            assert value == newValue
+            returnValue
+        } 
+        
+        and:
+        value == originalValue
+    }
+    
+    def "value is swapped back when exception thrown"() {        
+        when:
+        swap(2) { 
+            throw new IllegalStateException()
+        } 
+        
+        then:
+        value == originalValue
+        
+        and:
+        thrown IllegalStateException
+        
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TemporaryFolder.java b/subprojects/core/src/test/groovy/org/gradle/util/TemporaryFolder.java
deleted file mode 100644
index 15f9774..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/TemporaryFolder.java
+++ /dev/null
@@ -1,107 +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.util;
-
-import org.apache.commons.lang.StringUtils;
-import org.junit.rules.MethodRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-
-/**
- * A JUnit rule which provides a unique temporary folder for the test.
- */
-public class TemporaryFolder implements MethodRule, TestFileContext {
-    private TestFile dir;
-    private String prefix;
-    private static TestFile root;
-
-    static {
-        root = new TestFile(new File("build/tmp/tests"));
-    }
-
-    public TestFile getDir() {
-        if (dir == null) {
-            if (prefix == null) {
-                // This can happen if this is used in a constructor or a @Before method. It also happens when using
-                // @RunWith(SomeRunner) when the runner does not support rules.
-                prefix = determinePrefix();
-            }
-            for (int counter = 1; true; counter++) {
-                dir = root.file(counter == 1 ? prefix : String.format("%s%d", prefix, counter));
-                if (dir.mkdirs()) {
-                    break;
-                }
-            }
-        }
-        return dir;
-    }
-
-    private String determinePrefix() {
-        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
-        for (StackTraceElement element : stackTrace) {
-            if (element.getClassName().endsWith("Test")) {
-                return StringUtils.substringAfterLast(element.getClassName(), ".") + "/unknown-test";
-            }
-        }
-        return "unknown-test-class";
-    }
-
-    public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
-        init(method, target);
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                getDir().maybeDeleteDir();
-                // Don't delete on failure
-            }
-        };
-    }
-
-    private void init(FrameworkMethod method, Object target) {
-        if (prefix == null) {
-            prefix = String.format("%s/%s", target.getClass().getSimpleName(), method.getName());
-        }
-    }
-
-    public static TemporaryFolder newInstance() {
-        return new TemporaryFolder();
-    }
-
-    public static TemporaryFolder newInstance(FrameworkMethod method, Object target) {
-        TemporaryFolder temporaryFolder = new TemporaryFolder();
-        temporaryFolder.init(method, target);
-        return temporaryFolder;
-    }
-
-    public TestFile getTestDir() {
-        return getDir();
-    }
-
-    public TestFile file(Object... path) {
-        return getDir().file((Object[]) path);
-    }
-
-    public TestFile createFile(Object... path) {
-        return file((Object[]) path).createFile();
-    }
-
-    public TestFile createDir(Object... path) {
-        return file((Object[]) path).createDir();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TestFile.java b/subprojects/core/src/test/groovy/org/gradle/util/TestFile.java
deleted file mode 100644
index 54d6e28..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/TestFile.java
+++ /dev/null
@@ -1,467 +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.util;
-
-import groovy.lang.Closure;
-import org.apache.commons.io.FileUtils;
-import org.apache.tools.ant.taskdefs.Tar;
-import org.apache.tools.ant.taskdefs.Zip;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.file.DeleteAction;
-import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.api.internal.file.copy.DeleteActionImpl;
-import org.hamcrest.Matcher;
-
-import java.io.*;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.*;
-import java.util.jar.JarFile;
-import java.util.jar.Manifest;
-
-import static org.junit.Assert.*;
-
-public class TestFile extends File implements TestFileContext {
-    private boolean useNativeTools;
-
-    public TestFile(File file, Object... path) {
-        super(join(file, path).getAbsolutePath());
-    }
-
-    public TestFile(URI uri) {
-        this(new File(uri));
-    }
-
-    public TestFile(String path) {
-        this(new File(path));
-    }
-
-    public TestFile(URL url) {
-        this(toUri(url));
-    }
-
-    public TestFile getTestDir() {
-        return this;
-    }
-
-    public TestFile usingNativeTools() {
-        useNativeTools = true;
-        return this;
-    }
-
-    private static URI toUri(URL url) {
-        try {
-            return url.toURI();
-        } catch (URISyntaxException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static File join(File file, Object[] path) {
-        File current = file.getAbsoluteFile();
-        for (Object p : path) {
-            current = new File(current, p.toString());
-        }
-        return GFileUtils.canonicalise(current);
-    }
-
-    public TestFile file(Object... path) {
-        try {
-            return new TestFile(this, path);
-        } catch (RuntimeException e) {
-            throw new UncheckedIOException(String.format("Could not locate file '%s' relative to '%s'.", Arrays.toString(path), this), e);
-        }
-    }
-
-    public List<TestFile> files(Object... paths) {
-        List<TestFile> files = new ArrayList<TestFile>();
-        for (Object path : paths) {
-            files.add(file(path));
-        }
-        return files;
-    }
-
-    public TestFile writelns(String... lines) {
-        return writelns(Arrays.asList(lines));
-    }
-
-    public TestFile write(Object content) {
-        try {
-            FileUtils.writeStringToFile(this, content.toString());
-        } catch (IOException e) {
-            throw new UncheckedIOException(String.format("Could not write to test file '%s'", this), e);
-        }
-        return this;
-    }
-
-    public TestFile leftShift(Object content) {
-        getParentFile().mkdirs();
-        return write(content);
-    }
-
-    public String getText() {
-        assertIsFile();
-        try {
-            return FileUtils.readFileToString(this);
-        } catch (IOException e) {
-            throw new UncheckedIOException(String.format("Could not read from test file '%s'", this), e);
-        }
-    }
-
-    public Map<String, String> getProperties() {
-        assertIsFile();
-        Properties properties = new Properties();
-        try {
-            FileInputStream inStream = new FileInputStream(this);
-            try {
-                properties.load(inStream);
-            } finally {
-                inStream.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        Map<String, String> map = new HashMap<String, String>();
-        for (Object key : properties.keySet()) {
-            map.put(key.toString(), properties.getProperty(key.toString()));
-        }
-        return map;
-    }
-
-    public Manifest getManifest() {
-        assertIsFile();
-        try {
-            JarFile jarFile = new JarFile(this);
-            try {
-                return jarFile.getManifest();
-            } finally {
-                jarFile.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public List<String> linesThat(Matcher<? super String> matcher) {
-        try {
-            BufferedReader reader = new BufferedReader(new FileReader(this));
-            try {
-                List<String> lines = new ArrayList<String>();
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    if (matcher.matches(line)) {
-                        lines.add(line);
-                    }
-                }
-                return lines;
-            } finally {
-                reader.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public void unzipTo(File target) {
-        assertIsFile();
-        new TestFileHelper(this).unzipTo(target, useNativeTools);
-    }
-
-    public void untarTo(File target) {
-        assertIsFile();
-
-        new TestFileHelper(this).untarTo(target, useNativeTools);
-    }
-
-    public void copyTo(File target) {
-        if (isDirectory()) {
-            try {
-                FileUtils.copyDirectory(this, target);
-            } catch (IOException e) {
-                throw new UncheckedIOException(String.format("Could not copy test directory '%s' to '%s'", this,
-                        target), e);
-            }
-        } else {
-            try {
-                FileUtils.copyFile(this, target);
-            } catch (IOException e) {
-                throw new UncheckedIOException(String.format("Could not copy test file '%s' to '%s'", this, target), e);
-            }
-        }
-    }
-
-    public void copyFrom(File target) {
-        new TestFile(target).copyTo(this);
-    }
-    
-    public void copyFrom(URL resource) {
-        try {
-            FileUtils.copyURLToFile(resource, this);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public TestFile linkTo(File target) {
-        getParentFile().createDir();
-        int retval = PosixUtil.current().symlink(target.getAbsolutePath(), getAbsolutePath());
-        if (retval != 0) {
-            throw new UncheckedIOException(String.format("Could not create link from '%s' to '%s'", target, this));
-        }
-        return this;
-    }
-
-    public TestFile touch() {
-        try {
-            FileUtils.touch(this);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        assertIsFile();
-        return this;
-    }
-
-    /**
-     * Creates a directory structure specified by the given closure.
-     * <pre>
-     * dir.create {
-     *     subdir1 {
-     *        file 'somefile.txt'
-     *     }
-     *     subdir2 { nested { file 'someFile' } }
-     * }
-     * </pre>
-     */
-    public TestFile create(Closure structure) {
-        assertTrue(isDirectory() || mkdirs());
-        new TestDirHelper(this).apply(structure);
-        return this;
-    }
-
-    @Override
-    public TestFile getParentFile() {
-        return super.getParentFile() == null ? null : new TestFile(super.getParentFile());
-    }
-
-    @Override
-    public String toString() {
-        return getPath();
-    }
-
-    public TestFile writelns(Iterable<String> lines) {
-        Formatter formatter = new Formatter();
-        for (String line : lines) {
-            formatter.format("%s%n", line);
-        }
-        return write(formatter);
-    }
-
-    public TestFile assertExists() {
-        assertTrue(String.format("%s does not exist", this), exists());
-        return this;
-    }
-
-    public TestFile assertIsFile() {
-        assertTrue(String.format("%s is not a file", this), isFile());
-        return this;
-    }
-
-    public TestFile assertIsDir() {
-        assertTrue(String.format("%s is not a directory", this), isDirectory());
-        return this;
-    }
-
-    public TestFile assertDoesNotExist() {
-        assertFalse(String.format("%s should not exist", this), exists());
-        return this;
-    }
-
-    public TestFile assertContents(Matcher<String> matcher) {
-        assertThat(getText(), matcher);
-        return this;
-    }
-
-    public TestFile assertIsCopyOf(TestFile other) {
-        assertIsFile();
-        other.assertIsFile();
-        assertEquals(other.length(), this.length());
-        assertTrue(Arrays.equals(HashUtil.createHash(this), HashUtil.createHash(other)));
-        return this;
-    }
-
-    public TestFile assertPermissions(Matcher<String> matcher) {
-        if (OperatingSystem.current().isUnix()) {
-            assertThat(String.format("mismatched permissions for '%s'", this), getPermissions(), matcher);
-        }
-        return this;
-    }
-
-    private String getPermissions() {
-        assertExists();
-        return new TestFileHelper(this).getPermissions();
-    }
-
-    public TestFile setPermissions(String permissions) {
-        assertExists();
-        new TestFileHelper(this).setPermissions(permissions);
-        return this;
-    }
-
-    /**
-     * Asserts that this file contains exactly the given set of descendants.
-     */
-    public TestFile assertHasDescendants(String... descendants) {
-        Set<String> actual = new TreeSet<String>();
-        assertIsDir();
-        visit(actual, "", this);
-        Set<String> expected = new TreeSet<String>(Arrays.asList(descendants));
-
-        Set<String> extras = new TreeSet<String>(actual);
-        extras.removeAll(expected);
-        Set<String> missing = new TreeSet<String>(expected);
-        missing.removeAll(actual);
-
-        assertEquals(String.format("Extra files: %s, missing files: %s, expected: %s", extras, missing, expected), expected, actual);
-
-        return this;
-    }
-
-    public TestFile assertIsEmptyDir() {
-        if (exists()) {
-            assertIsDir();
-            assertHasDescendants();
-        }
-        return this;
-    }
-
-    private void visit(Set<String> names, String prefix, File file) {
-        for (File child : file.listFiles()) {
-            if (child.isFile()) {
-                names.add(prefix + child.getName());
-            } else if (child.isDirectory()) {
-                visit(names, prefix + child.getName() + "/", child);
-            }
-        }
-    }
-
-    public boolean isSelfOrDescendent(File file) {
-        if (file.getAbsolutePath().equals(getAbsolutePath())) {
-            return true;
-        }
-        return file.getAbsolutePath().startsWith(getAbsolutePath() + File.separatorChar);
-    }
-
-    public TestFile createDir() {
-        assertTrue(isDirectory() || mkdirs());
-        return this;
-    }
-
-    public TestFile createDir(Object path) {
-        return new TestFile(this, path).createDir();
-    }
-
-    public TestFile deleteDir() {
-        DeleteAction delete = new DeleteActionImpl(new IdentityFileResolver());
-        delete.delete(this);
-        return this;
-    }
-
-    /**
-     * Attempts to delete this directory, ignoring failures to do so.
-     * @return this
-     */
-    public TestFile maybeDeleteDir() {
-        try {
-            deleteDir();
-        } catch (UncheckedIOException e) {
-            // Ignore
-        }
-        return this;
-    }
-
-    public TestFile createFile() {
-        new TestFile(getParentFile()).createDir();
-        try {
-            assertTrue(isFile() || createNewFile());
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        return this;
-    }
-
-    public TestFile createFile(Object path) {
-        return file(path).createFile();
-    }
-
-    public TestFile zipTo(TestFile zipFile) {
-        Zip zip = new Zip();
-        zip.setBasedir(this);
-        zip.setDestFile(zipFile);
-        AntUtil.execute(zip);
-        return this;
-    }
-
-    public TestFile tarTo(TestFile zipFile) {
-        Tar tar = new Tar();
-        tar.setBasedir(this);
-        tar.setDestFile(zipFile);
-        AntUtil.execute(tar);
-        return this;
-    }
-
-    public Snapshot snapshot() {
-        assertIsFile();
-        return new Snapshot();
-    }
-
-    public void assertHasChangedSince(Snapshot snapshot) {
-        Snapshot now = snapshot();
-        assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash));
-    }
-
-    public void assertHasNotChangedSince(Snapshot snapshot) {
-        Snapshot now = snapshot();
-        assertEquals(String.format("last modified time of %s has changed", this), snapshot.modTime, now.modTime);
-        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
-    }
-
-    public void writeProperties(Map<?, ?> properties) {
-        Properties props = new Properties();
-        props.putAll(properties);
-        try {
-            FileOutputStream stream = new FileOutputStream(this);
-            try {
-                props.store(stream, "comment");
-            } finally {
-                stream.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public class Snapshot {
-        private final long modTime;
-        private final byte[] hash;
-
-        public Snapshot() {
-            modTime = lastModified();
-            hash = HashUtil.createHash(TestFile.this);
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TestFileHelper.groovy b/subprojects/core/src/test/groovy/org/gradle/util/TestFileHelper.groovy
deleted file mode 100644
index 128d661..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/TestFileHelper.groovy
+++ /dev/null
@@ -1,116 +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.util
-
-import java.util.zip.ZipEntry
-import java.util.zip.ZipInputStream
-import org.apache.commons.lang.StringUtils
-import org.apache.tools.ant.taskdefs.Expand
-import org.apache.tools.ant.taskdefs.Untar
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.jruby.ext.posix.FileStat
-import org.gradle.api.UncheckedIOException
-
-class TestFileHelper {
-    TestFile file
-
-    public TestFileHelper(TestFile file) {
-        this.file = file
-    }
-
-    public void unzipTo(File target, boolean nativeTools) {
-        // Check that each directory in hierarchy is present
-        file.withInputStream {InputStream instr ->
-            Set<String> dirs = [] as Set
-            ZipInputStream zipStr = new ZipInputStream(instr)
-            ZipEntry entry
-            while (entry = zipStr.getNextEntry()) {
-                if (entry.isDirectory()) {
-                    assertTrue("Duplicate directory '$entry.name'", dirs.add(entry.name))
-                }
-                if (!entry.name.contains('/')) {
-                    continue
-                }
-                String parent = StringUtils.substringBeforeLast(entry.name, '/') + '/'
-                assertTrue("Missing dir '$parent'", dirs.contains(parent))
-            }
-        }
-
-        if (nativeTools && OperatingSystem.current().isUnix()) {
-            Process process = ['unzip', '-o', file.absolutePath, '-d', target.absolutePath].execute()
-            process.consumeProcessOutput(System.out, System.err)
-            assertThat(process.waitFor(), equalTo(0))
-            return
-        }
-
-        Expand unzip = new Expand();
-        unzip.src = file;
-        unzip.dest = target;
-        AntUtil.execute(unzip);
-    }
-
-    public void untarTo(File target, boolean nativeTools) {
-        if (nativeTools && OperatingSystem.current().isUnix()) {
-            target.mkdirs()
-            ProcessBuilder builder = new ProcessBuilder(['tar', '-xf', file.absolutePath])
-            builder.directory(target)
-            Process process = builder.start()
-            process.consumeProcessOutput()
-            assertThat(process.waitFor(), equalTo(0))
-            return
-        }
-
-        Untar untar = new Untar();
-        untar.setSrc(file);
-        untar.setDest(target);
-
-        if (file.getName().endsWith(".tgz")) {
-            Untar.UntarCompressionMethod method = new Untar.UntarCompressionMethod();
-            method.setValue("gzip");
-            untar.setCompression(method);
-        } else if (file.getName().endsWith(".tbz2")) {
-            Untar.UntarCompressionMethod method = new Untar.UntarCompressionMethod();
-            method.setValue("bzip2");
-            untar.setCompression(method);
-        }
-
-        AntUtil.execute(untar);
-    }
-
-    public String getPermissions() {
-        FileStat stat = PosixUtil.current().stat(file.absolutePath)
-        [6, 3, 0].collect {
-            def m = stat.mode() >> it
-            [m & 4 ? 'r' : '-', m & 2 ? 'w' : '-', m & 1 ? 'x' : '-']
-        }.flatten().join('')
-    }
-
-    def setPermissions(String permissions) {
-        def m = [6, 3, 0].inject(0) { mode, pos ->
-            mode |= permissions[9 - pos - 3] == 'r' ? 4 << pos : 0
-            mode |= permissions[9 - pos - 2] == 'w' ? 2 << pos : 0
-            mode |= permissions[9 - pos - 1] == 'x' ? 1 << pos : 0
-            return mode
-        }
-        int retval = PosixUtil.current().chmod(file.absolutePath, m)
-        if (retval != 0) {
-            throw new UncheckedIOException("Could not set permissions of '${file}' to '${permissions}'.")
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
index 80f30df..55409ae 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
@@ -36,7 +36,22 @@ class TextUtilTest extends Specification {
         TextUtil.toPlatformLineSeparators(original) == converted
 
         where:
-        original | converted
+        original                          | converted
         "one\rtwo\nthree\r\nfour\n\rfive" | "one${platformSep}two${platformSep}three${platformSep}four${platformSep}${platformSep}five"
+        "\n\n"                            | "${platformSep}${platformSep}"
+    }
+
+    def containsWhitespace() {
+        expect:
+        TextUtil.containsWhitespace(str) == whitespace
+
+        where:
+        str       | whitespace
+        "abcde"   | false
+        "abc de"  | true
+        " abcde"  | true
+        "abcde "  | true
+        "abc\tde" | true
+        "abc\nde" | true
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/hash/HashValueTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/hash/HashValueTest.groovy
new file mode 100644
index 0000000..34d5826
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/hash/HashValueTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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.util.hash
+
+import spock.lang.Specification
+
+class HashValueTest extends Specification {
+    def "parses hash value from input strings"() {
+        expect:
+        def digest = HashValue.parse(inputString)
+        digest.asHexString() == hexString
+
+        where:
+        hexString                                  | inputString
+        "1234"                                     | "1234"
+        "abc123"                                   | "ABC123"
+        "1"                                        | "000000000000001"
+        "123456"                                   | "md5 = 123456"
+        "123456"                                   | "sha1 = 123456"
+        "76be4c7459d7fb64bf638bac7accd9b6df728f2b" | "SHA1 (dummy.gz) = 76be4c7459d7fb64bf638bac7accd9b6df728f2b"
+        "687cab044c8f937b8957166272f1da3c"         | "fontbox-0.8.0-incubating.jar: 68 7C AB 04 4C 8F 93 7B  89 57 16 62 72 F1 DA 3C" // http://repo2.maven.org/maven2/org/apache/pdfbox/fontbox/0.8.0-incubator/fontbox-0.8.0-incubator.jar.md5
+        "f951934aa5ae5a88d7e6dfaa6d32307d834a88be" | "f951934aa5ae5a88d7e6dfaa6d32307d834a88be  /home/maven/repository-staging/to-ibiblio/maven2/commons-collections/commons-collections/3.2/commons-collections-3.2.jar"
+    }
+
+    def "creates compact string representation"() {
+        expect:
+        new HashValue(hexString).asCompactString() == compactString
+
+        where:
+        hexString                          | compactString
+        "1234"                             | "4hk"
+        "abc123"                           | "ang93"
+        "d41d8cd98f00b204e9800998ecf8427e" | "6k3m6dj3o0m82ej009j3mfggju"
+        "FFF"                              | "3vv"
+    }
+
+    def "can roundtrip compact sha1 representation"() {
+        given:
+        def hash = new HashValue("1234")
+        
+        expect:
+        hash.equals(new HashValue(hash.asHexString()))
+    }
+    
+    def "creates short MD5 for string input"() {
+        expect:
+        HashUtil.createCompactMD5("") == "6k3m6dj3o0m82ej009j3mfggju"
+        HashUtil.createCompactMD5("a") == "co5qrjg7hmqk33gsps9kne9j1"
+        HashUtil.createCompactMD5("i") == "46bg60milgs1hubil371u1l1q1"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy
new file mode 100644
index 0000000..4d88fd7
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/internal/ArgumentsSplitterTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * 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.util.internal
+
+import spock.lang.Specification
+import static org.gradle.util.internal.ArgumentsSplitter.split
+
+/**
+ * by Szczepan Faber, created at: 2/29/12
+ */
+class ArgumentsSplitterTest extends Specification {
+
+    def breaksUpEmptyCommandLineIntoEmptyList() {
+        expect:
+        split('') == []
+    }
+
+    def breaksUpWhitespaceOnlyCommandLineIntoEmptyList() {
+        expect:
+        split(' \t ') == []
+    }
+
+    def breaksUpCommandLineIntoSpaceSeparatedArgument() {
+        expect:
+        split('a') == ['a']
+        split('a b\tc') == ['a', 'b', 'c']
+    }
+
+    def ignoresExtraWhiteSpaceBetweenArguments() {
+        expect:
+        split('  a \t') == ['a']
+        split('a  \t\t b ') == ['a', 'b']
+    }
+
+    def breaksUpCommandLineIntoDoubleQuotedArguments() {
+        expect:
+        split('"a b c"') == ['a b c']
+        split('a "b c d" e') == ['a', 'b c d', 'e']
+        split('a "  b c d  "') == ['a', '  b c d  ']
+    }
+
+    def breaksUpCommandLineIntoSingleQuotedArguments() {
+        expect:
+        split("'a b c'") == ['a b c']
+        split("a 'b c d' e") == ['a', 'b c d', 'e']
+        split("a '  b c d  '") == ['a', '  b c d  ']
+    }
+
+    def canHaveEmptyQuotedArgument() {
+        expect:
+        split('""') == ['']
+        split("''") == ['']
+    }
+
+    def canHaveQuoteInsideQuotedArgument() {
+        expect:
+        split('"\'quoted\'"') == ['\'quoted\'']
+        split("'\"quoted\"'") == ['"quoted"']
+    }
+
+    def argumentCanHaveQuotedAndUnquotedParts() {
+        expect:
+        split('a"b "c') == ['ab c']
+        split("a'b 'c") == ['ab c']
+    }
+
+    def canHaveMissingEndQuote() {
+        expect:
+        split('"a b c') == ['a b c']
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy
new file mode 100644
index 0000000..e31a6ce
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/internal/LimitedDescriptionTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * 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.util.internal
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 2/28/12
+ */
+class LimitedDescriptionTest extends Specification {
+
+    def desc = new LimitedDescription(2)
+
+    def "has limited description"() {
+        when:
+        desc.append("0").append("one").append("2").append("three !")
+
+        then:
+        desc.toString() == """2
+three !
+"""
+    }
+
+    def "is described even when empty"() {
+        expect:
+        desc.toString().length() != 0
+    }
+}
diff --git a/subprojects/core/src/test/resources/META-INF/gradle-plugins/custom-plugin.properties b/subprojects/core/src/test/resources/META-INF/gradle-plugins/custom-plugin.properties
new file mode 100644
index 0000000..8150c44
--- /dev/null
+++ b/subprojects/core/src/test/resources/META-INF/gradle-plugins/custom-plugin.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.testfixtures.CustomPlugin
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/core/src/test/resources/org/gradle/api/file/symlinks/file
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/core/src/test/resources/org/gradle/api/file/symlinks/file
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/core/src/test/resources/org/gradle/api/file/symlinks/symlink
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/core/src/test/resources/org/gradle/api/file/symlinks/symlink
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/core/src/test/resources/org/gradle/api/file/symlinks/symlinked
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/core/src/test/resources/org/gradle/api/file/symlinks/symlinked
diff --git a/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/permissions.tar b/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/permissions.tar
new file mode 100644
index 0000000..30518f1
Binary files /dev/null and b/subprojects/core/src/test/resources/org/gradle/api/internal/file/archive/permissions.tar differ
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/core/src/test/resources/org/gradle/api/internal/xml-transformer-test.dtd
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/core/src/test/resources/org/gradle/api/internal/xml-transformer-test.dtd
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
new file mode 100644
index 0000000..3172e06
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.internal.ConventionAwareHelper;
+import org.gradle.api.internal.ConventionTask;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractConventionTaskTest extends AbstractTaskTest {
+
+    public abstract ConventionTask getTask();
+    
+    @Test
+    public void testConventionAwareness() {
+        ConventionTask task = getTask();
+        assertThat(task.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
+        ConventionAwareHelper conventionMapping = (ConventionAwareHelper) task.getConventionMapping();
+        assertThat(conventionMapping.getConvention(), sameInstance(getProject().getConvention()));
+    }
+}
+
+
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
new file mode 100644
index 0000000..4d35c83
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
@@ -0,0 +1,312 @@
+/*
+ * 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 java.util.concurrent.atomic.AtomicBoolean
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.internal.AbstractTask
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.project.AbstractProject
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.api.internal.project.taskfactory.TaskFactory
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskStateInternal
+import org.gradle.api.specs.Spec
+import org.gradle.util.GUtil
+import org.gradle.util.HelperUtil
+import org.gradle.util.Matchers
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import static org.junit.Assert.assertFalse
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractSpockTaskTest extends Specification {
+    public static final String TEST_TASK_NAME = "taskname"
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder()
+
+    private AbstractProject project = HelperUtil.createRootProject()
+
+    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()))
+
+    public abstract AbstractTask getTask();
+
+    public <T extends AbstractTask> T createTask(Class<T> type) {
+        return createTask(type, project, TEST_TASK_NAME);
+    }
+
+    public Task createTask(Project project, String name) {
+        return createTask(getTask().getClass(), project, name);
+    }
+
+    public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
+        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
+                GUtil.map(Task.TASK_TYPE, type,
+                        Task.TASK_NAME, name))
+        assert type.isAssignableFrom(task.getClass())
+        return type.cast(task);
+    }
+
+    def testTask() {
+        expect:
+        getTask().isEnabled()
+        TEST_TASK_NAME ==  getTask().getName()
+        getTask().getDescription() == null
+        project.is( getTask().getProject())
+        getTask().getStandardOutputCapture() != null
+        getTask().getInputs() != null
+        getTask().getOutputs() != null
+        getTask().getOnlyIf() != null
+        getTask().getOnlyIf().isSatisfiedBy(getTask())
+    }
+
+    def testPath() {
+        DefaultProject rootProject = HelperUtil.createRootProject();
+        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
+        childProject.getProjectDir().mkdirs();
+        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
+        childchildProject.getProjectDir().mkdirs();
+
+        when:
+        Task task = createTask(rootProject, TEST_TASK_NAME);
+
+        then:
+        Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
+
+        when:
+        task = createTask(childProject, TEST_TASK_NAME);
+
+        then:
+        Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
+
+        when:
+        task = createTask(childchildProject, TEST_TASK_NAME);
+
+        then:
+        Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
+    }
+
+    def testDependsOn() {
+        Task dependsOnTask = createTask(project, "somename");
+        Task task = createTask(project, TEST_TASK_NAME);
+        project.getTasks().add("path1");
+        project.getTasks().add("path2");
+
+        when:
+        task.dependsOn(Project.PATH_SEPARATOR + "path1");
+
+        then:
+        Matchers.dependsOn("path1").matches(task)
+
+        when:
+        task.dependsOn("path2", dependsOnTask);
+
+        then:
+        Matchers.dependsOn("path1", "path2", "somename").matches(task)
+    }
+
+    def testToString() {
+        "task '" + getTask().getPath() + "'" ==  getTask().toString()
+    }
+
+    def testDeleteAllActions() {
+        when:
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+        getTask().doLast(action1);
+        getTask().doLast(action2);
+
+        then:
+        getTask().is( getTask().deleteAllActions())
+        new ArrayList() ==  getTask().getActions()
+    }
+
+    def testAddActionWithNull() {
+        when:
+        getTask().doLast((Closure) null)
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def testExecuteDelegatesToTaskExecuter() {
+        final AbstractTask task = getTask()
+        TaskExecuter executer = Mock()
+        task.setExecuter(executer);
+
+        when:
+        task.execute()
+
+        then:
+        1 * executer.execute(task, _ as TaskStateInternal)
+
+    }
+
+    public AbstractProject getProject() {
+        return project;
+    }
+
+    public void setProject(AbstractProject project) {
+        this.project = project;
+    }
+
+    def setGetDescription() {
+        when:
+        String testDescription = "testDescription";
+        getTask().setDescription(testDescription);
+
+        then:
+        testDescription ==  getTask().getDescription()
+    }
+
+    def canSpecifyOnlyIfPredicateUsingClosure() {
+        AbstractTask task = getTask();
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    def canSpecifyOnlyIfPredicateUsingSpec() {
+        final Spec<Task> spec = Mock()
+        final AbstractTask task = getTask();
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        spec.isSatisfiedBy(task) >> false
+        task.onlyIf(spec);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    def onlyIfPredicateIsTrueWhenTaskIsEnabledAndAllPredicatesAreTrue() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        final AtomicBoolean condition2 = new AtomicBoolean(true);
+
+        AbstractTask task = getTask();
+        task.onlyIf {
+            condition1.get()
+        }
+        task.onlyIf {
+            condition2.get()
+        }
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        task.setEnabled(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        task.setEnabled(true);
+        condition1.set(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        condition1.set(true);
+        condition2.set(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        condition2.set(true);
+
+        then:
+        task.getOnlyIf().isSatisfiedBy(task)
+    }
+
+    def canReplaceOnlyIfSpec() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        AbstractTask task = getTask();
+        task.onlyIf(Mock(Spec))
+        task.setOnlyIf {
+            return condition1.get();
+        }
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        task.setEnabled(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        task.setEnabled(true);
+        condition1.set(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        condition1.set(true);
+
+        then:
+        task.getOnlyIf().isSatisfiedBy(task)
+    }
+
+    def testDependentTaskDidWork() {
+        Task task1 = Mock()
+        Task task2 = Mock()
+        TaskDependency dependencyMock = Mock()
+        getTask().dependsOn(dependencyMock)
+        dependencyMock.getDependencies(getTask()) >> [task1, task2] 
+
+        when:
+        task1.getDidWork() >> false
+        task2.getDidWork() >>> [false, true]
+
+
+        then:
+        !getTask().dependsOnTaskDidWork()
+        getTask().dependsOnTaskDidWork()
+    }
+
+    public static Action<Task> createTaskAction() {
+        return new Action<Task>() {
+            public void execute(Task task) {
+
+            }
+        };
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
new file mode 100644
index 0000000..2625127
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractTaskTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.project.taskfactory.TaskFactory;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.*;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import spock.lang.Issue;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.gradle.util.Matchers.dependsOn;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractTaskTest {
+    public static final String TEST_TASK_NAME = "taskname";
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    private AbstractProject project;
+
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()));
+
+    @Before
+    public void setUp() {
+        project = HelperUtil.createRootProject();
+    }
+
+    public abstract AbstractTask getTask();
+
+    public <T extends AbstractTask> T createTask(Class<T> type) {
+        return createTask(type, project, TEST_TASK_NAME);
+    }
+
+    public Task createTask(Project project, String name) {
+        return createTask(getTask().getClass(), project, name);
+    }
+
+    public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
+        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
+                GUtil.map(Task.TASK_TYPE, type,
+                        Task.TASK_NAME, name));
+        assertTrue(type.isAssignableFrom(task.getClass()));
+        return type.cast(task);
+    }
+
+    @Test
+    public void testTask() {
+        assertTrue(getTask().isEnabled());
+        assertEquals(TEST_TASK_NAME, getTask().getName());
+        assertNull(getTask().getDescription());
+        assertSame(project, getTask().getProject());
+        assertNotNull(getTask().getStandardOutputCapture());
+        assertNotNull(getTask().getInputs());
+        assertNotNull(getTask().getOutputs());
+        assertNotNull(getTask().getOnlyIf());
+        assertTrue(getTask().getOnlyIf().isSatisfiedBy(getTask()));
+    }
+
+    @Test
+    public void testPath() {
+        DefaultProject rootProject = HelperUtil.createRootProject();
+        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
+        childProject.getProjectDir().mkdirs();
+        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
+        childchildProject.getProjectDir().mkdirs();
+
+        Task task = createTask(rootProject, TEST_TASK_NAME);
+        assertEquals(Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
+        task = createTask(childProject, TEST_TASK_NAME);
+        assertEquals(Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
+        task = createTask(childchildProject, TEST_TASK_NAME);
+        assertEquals(Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
+    }
+
+    @Test
+    public void testDependsOn() {
+        Task dependsOnTask = createTask(project, "somename");
+        Task task = createTask(project, TEST_TASK_NAME);
+        project.getTasks().add("path1");
+        project.getTasks().add("path2");
+
+        task.dependsOn(Project.PATH_SEPARATOR + "path1");
+        assertThat(task, dependsOn("path1"));
+        task.dependsOn("path2", dependsOnTask);
+        assertThat(task, dependsOn("path1", "path2", "somename"));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("task '" + getTask().getPath() + "'", getTask().toString());
+    }
+
+    @Test
+    public void testDeleteAllActions() {
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+        getTask().doLast(action1);
+        getTask().doLast(action2);
+        assertSame(getTask(), getTask().deleteAllActions());
+        assertTrue(getTask().getActions().isEmpty());
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void testAddActionWithNull() {
+        getTask().doLast((Closure) null);
+    }
+
+    @Test
+    public void testExecuteDelegatesToTaskExecuter() {
+        final AbstractTask task = getTask();
+
+        final TaskExecuter executer = context.mock(TaskExecuter.class);
+        task.setExecuter(executer);
+
+        context.checking(new Expectations(){{
+            one(executer).execute(with(sameInstance(task)), with(notNullValue(TaskStateInternal.class)));
+        }});
+
+        task.execute();
+    }
+
+    public AbstractProject getProject() {
+        return project;
+    }
+
+    public void setProject(AbstractProject project) {
+        this.project = project;
+    }
+
+    @Test
+    public void setGetDescription() {
+        String testDescription = "testDescription";
+        getTask().setDescription(testDescription);
+        assertEquals(testDescription, getTask().getDescription());
+    }
+
+    @Test
+    public void canSpecifyOnlyIfPredicateUsingClosure() {
+        AbstractTask task = getTask();
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void canSpecifyOnlyIfPredicateUsingSpec() {
+        final Spec<Task> spec = context.mock(Spec.class);
+
+        final AbstractTask task = getTask();
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        context.checking(new Expectations() {{
+            allowing(spec).isSatisfiedBy(task);
+            will(returnValue(false));
+        }});
+
+        task.onlyIf(spec);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void onlyIfPredicateIsTrueWhenTaskIsEnabledAndAllPredicatesAreTrue() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        final AtomicBoolean condition2 = new AtomicBoolean(true);
+
+        AbstractTask task = getTask();
+        task.onlyIf(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return condition1.get();
+            }
+        });
+        task.onlyIf(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return condition2.get();
+            }
+        });
+
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(true);
+        condition1.set(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        condition1.set(true);
+        condition2.set(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        condition2.set(true);
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void canReplaceOnlyIfSpec() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        AbstractTask task = getTask();
+        task.onlyIf(context.mock(Spec.class, "spec1"));
+        task.setOnlyIf(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return condition1.get();
+            }
+        });
+
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(true);
+        condition1.set(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        condition1.set(true);
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void testDependentTaskDidWork() {
+        final Task task1 = context.mock(Task.class, "task1");
+        final Task task2 = context.mock(Task.class, "task2");
+        final TaskDependency dependencyMock = context.mock(TaskDependency.class);
+        getTask().dependsOn(dependencyMock);
+        context.checking(new Expectations() {{
+            allowing(dependencyMock).getDependencies(getTask()); will(returnValue(WrapUtil.toSet(task1, task2)));
+
+            exactly(2).of(task1).getDidWork();
+            will(returnValue(false));
+
+            exactly(2).of(task2).getDidWork();
+            will(onConsecutiveCalls(returnValue(false), returnValue(true)));
+        }});
+
+        assertFalse(getTask().dependsOnTaskDidWork());
+
+        assertTrue(getTask().dependsOnTaskDidWork());
+    }
+
+    public static Action<Task> createTaskAction() {
+        return new Action<Task>() {
+            public void execute(Task task) {
+            }
+        };
+    }
+    
+    @Test
+    @Issue("http://issues.gradle.org/browse/GRADLE-2022")
+    public void testGoodErrorMessageWhenTaskInstantiatedDirectly() {
+        try {
+            Class<? extends AbstractTask> clazz = getTask().getClass();
+            clazz.newInstance();
+            throw new RuntimeException("Direct instantiation of " + clazz + " should have produced an exception");
+        } catch (Exception e) {
+            assertEquals(TaskInstantiationException.class, e.getClass());
+            assert e.getMessage().contains("has been instantiated directly which is not supported");
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy
rename to subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
rename to subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/messaging/actor/TestActorFactory.java b/subprojects/core/src/testFixtures/groovy/org/gradle/messaging/actor/TestActorFactory.java
new file mode 100644
index 0000000..c7b0fb2
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/messaging/actor/TestActorFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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.actor;
+
+import org.gradle.internal.concurrent.ThreadSafe;
+import org.gradle.messaging.dispatch.DispatchException;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+
+public class TestActorFactory implements ActorFactory {
+    public Actor createActor(Object target) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Actor createBlockingActor(final Object target) {
+        return new Actor() {
+            public <T> T getProxy(Class<T> type) {
+                return new ProxyDispatchAdapter<T>(new ReflectionDispatch(target), type, ThreadSafe.class).getSource();
+            }
+
+            public void stop() throws DispatchException {
+            }
+
+            public void dispatch(MethodInvocation message) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
new file mode 100644
index 0000000..cd11107
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
@@ -0,0 +1,771 @@
+/*
+ * Copyright 2011 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.tests.fixtures
+
+import java.util.concurrent.CopyOnWriteArraySet
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+import org.gradle.messaging.concurrent.ExecutorFactory
+import org.gradle.messaging.concurrent.StoppableExecutor
+import org.junit.rules.ExternalResource
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * <p>A base class for writing specifications which exercise concurrent code.
+ *
+ * <p>See {@link org.gradle.util.ConcurrentSpecificationTest} for some examples.
+ *
+ * <p>Provides {@link Executor} and {@link ExecutorFactory} implementations for use during the test. These provide real concurrency.
+ * The test threads are cleaned up at the end of the test, and any exceptions thrown by those tests are propagated.
+ *
+ * <p>Provides some fixtures for testing:</p>
+ *
+ * <ul>
+ * <li>An action starts another action asynchronously without waiting for the result.</li>
+ * <li>An action starts another action asynchronously and waits for the result.</li>
+ * </ul>
+ */
+class ConcurrentTestUtil extends ExternalResource {
+    private static final Logger LOG = LoggerFactory.getLogger(ConcurrentTestUtil.class)
+
+    private Lock lock = new ReentrantLock()
+    private Condition threadsChanged = lock.newCondition()
+    private Set<TestThread> threads = [] as Set
+    private Closure failureHandler
+    private List<Throwable> failures = []
+    private timeout = 5000
+
+    ConcurrentTestUtil() {}
+
+    ConcurrentTestUtil(int timeout) {
+        this.timeout = timeout
+    }
+
+    @Override
+    protected void after() {
+        finished()
+    }
+
+    //simplistic polling assertion. attempts asserting every x millis up to some max timeout
+    static void poll(int timeout = 10, Closure assertion) {
+        def expiry = System.currentTimeMillis() + timeout * 1000 // convert to ms
+        while(true) {
+            try {
+                assertion()
+                return
+            } catch (Throwable t) {
+                if (System.currentTimeMillis() > expiry) {
+                    throw t
+                }
+                Thread.sleep(100);
+            }
+        }
+    }
+
+    void setShortTimeout(int millis) {
+        this.timeout = millis
+    }
+
+    ExecutorFactory getExecutorFactory() {
+        return new ExecutorFactory() {
+            StoppableExecutor create(String displayName) {
+                return new StoppableExecutorStub(ConcurrentTestUtil.this)
+            }
+        }
+    }
+
+    Executor getExecutor() {
+        return new Executor() {
+            void execute(Runnable runnable) {
+                startThread(runnable)
+            }
+        }
+    }
+
+    TestThread startThread(Runnable cl) {
+        lock.lock()
+        try {
+            TestThread thread = new TestThread(this, lock, cl)
+            thread.start()
+            return thread
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    /**
+     * Starts a thread which executes the given action/closure. Does not wait for the thread to complete.
+     *
+     * @return A handle to the test thread.
+     */
+    TestParticipant start(Runnable cl) {
+        lock.lock()
+        try {
+            TestThread thread = new TestThread(this, lock, cl)
+            thread.start()
+            return new TestParticipantImpl(this, thread)
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    /**
+     * Creates a new asynchronous action.
+     */
+    StartAsyncAction startsAsyncAction() {
+        return new StartAsyncAction(this)
+    }
+
+    /**
+     * Creates a new blocking action.
+     */
+    WaitForAsyncCallback waitsForAsyncCallback() {
+        return new WaitForAsyncCallback(this)
+    }
+
+    /**
+     * Creates a new action which waits until an async. action is complete.
+     */
+    WaitForAsyncAction waitsForAsyncActionToComplete() {
+        return new WaitForAsyncAction(this)
+    }
+
+    /**
+     * Returns a composite participant, which you can use to perform atomic operations on.
+     *
+     * @return A handle to the composite participant.
+     */
+    TestParticipant all(TestParticipant... participants) {
+        return new CompositeTestParticipant(this, lock, participants as List)
+    }
+
+    void onFailure(Closure cl) {
+        lock.lock()
+        try {
+            failureHandler = cl
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    private void onFailure(Throwable t) {
+        lock.lock()
+        try {
+            if (failureHandler != null) {
+                failureHandler.call(t)
+            } else {
+                failures << t
+            }
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    /**
+     * Waits for all threads to complete. Asserts that the threads complete in a 'short' time. Rethrows any exceptions thrown by test threads.
+     */
+    void finished() {
+        Date timeout = shortTimeout()
+        lock.lock()
+        try {
+            LOG.info("Waiting for test threads to complete.")
+            while (!threads.isEmpty()) {
+                if (!threadsChanged.awaitUntil(timeout)) {
+                    onFailure(new IllegalStateException("Timeout waiting for test threads to complete."))
+                    break;
+                }
+            }
+            threads.each { thread ->
+                thread.interrupt()
+            }
+
+            LOG.info("Finishing up.")
+            if (!failures.isEmpty()) {
+                throw failures[0]
+            }
+        } finally {
+            failureHandler = null
+            threads.clear()
+            failures.clear()
+            lock.unlock()
+        }
+
+    }
+
+    Date shortTimeout() {
+        return new Date(System.currentTimeMillis() + timeout)
+    }
+
+    void run(Closure cl, Date timeout) {
+        def thread = new TestThread(this, lock, cl)
+        thread.start()
+        thread.completesBefore(timeout)
+    }
+
+    void onThreadStart(TestThread thread) {
+        lock.lock()
+        try {
+            threads << thread
+            threadsChanged.signalAll()
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    void onThreadComplete(TestThread thread, Throwable failure) {
+        lock.lock()
+        try {
+            threads.remove(thread)
+            if (failure) {
+                onFailure(failure)
+            }
+            threadsChanged.signalAll()
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+class TestThread extends Thread {
+    private static final Logger LOG = LoggerFactory.getLogger(TestThread.class)
+    private final ConcurrentTestUtil owner
+    private final Runnable action
+    private final Lock lock
+    private final Condition stateChanged
+    private boolean complete
+
+    TestThread(ConcurrentTestUtil owner, Lock lock, Runnable action) {
+        this.owner = owner
+        this.action = action
+        this.lock = lock
+        this.stateChanged = lock.newCondition()
+    }
+
+    @Override
+    void start() {
+        LOG.info("$this started.")
+
+        lock.lock()
+        try {
+            owner.onThreadStart(this)
+            stateChanged.signalAll()
+        } finally {
+            lock.unlock()
+        }
+
+        super.start()
+    }
+
+    void running() {
+        lock.lock()
+        try {
+            if (complete) {
+                throw new IllegalStateException("$this should still be running, but is not.")
+            }
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    void completesBefore(Date timeout) {
+        lock.lock()
+        try {
+            LOG.info("Waiting for $this to complete.")
+            while (!complete) {
+                if (!stateChanged.awaitUntil(timeout)) {
+                    interrupt()
+                    throw new IllegalStateException("Timeout waiting for $this to complete.")
+                }
+            }
+            LOG.info("$this completed.")
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    @Override
+    void run() {
+        Throwable failure = null
+        try {
+            action.run()
+        } catch (Throwable t) {
+            failure = t
+        }
+
+        lock.lock()
+        try {
+            complete = true
+            stateChanged.signalAll()
+            owner.onThreadComplete(this, failure)
+            LOG.info("$this completed.")
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+/**
+ * Some potentially long running operation.
+ */
+interface LongRunningAction {
+    /**
+     * Blocks until this action has completed. Asserts that the action completes in a 'short' time. Rethrows any exception from the action.
+     */
+    void completed()
+
+    /**
+     * Blocks until this action has completed. Asserts that the action completes within the specified time. Rethrows any exception from the action.
+     */
+    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits)
+
+    /**
+     * Blocks until this action has completed. Asserts that the action completes before the given time. Rethrows any exception from the action.
+     */
+    void completesBefore(Date timeout)
+}
+
+interface TestParticipant extends LongRunningAction {
+    /**
+     * Asserts that this test participant is running.
+     */
+    void running()
+}
+
+abstract class AbstractAction implements LongRunningAction {
+    
+    Date defaultExpiry
+
+    AbstractAction(Date defaultExpiry) {
+        this.defaultExpiry = defaultExpiry
+    }
+    
+    void completed() {
+        completesBefore(defaultExpiry)
+    }
+
+    void completesWithin(long maxWaitValue, TimeUnit maxWaitUnits) {
+        Date expiry = new Date(System.currentTimeMillis() + maxWaitUnits.toMillis(maxWaitValue))
+        completesBefore(expiry + 500)
+    }
+
+    abstract void completesBefore(Date timeout)
+}
+
+abstract class AbstractTestParticipant extends AbstractAction implements TestParticipant {
+    private final ConcurrentTestUtil owner
+
+    AbstractTestParticipant(ConcurrentTestUtil owner) {
+        super(owner.shortTimeout())
+        this.owner = owner
+    }
+}
+
+class TestParticipantImpl extends AbstractTestParticipant {
+    private final TestThread thread
+
+    TestParticipantImpl(ConcurrentTestUtil owner, TestThread thread) {
+        super(owner)
+        this.thread = thread
+    }
+
+    @Override
+    void completesBefore(Date timeout) {
+        thread.completesBefore(timeout)
+    }
+
+    void running() {
+        thread.running()
+    }
+}
+
+class CompositeTestParticipant extends AbstractTestParticipant {
+    private final List<TestParticipant> participants
+    private final Lock lock
+
+    CompositeTestParticipant(ConcurrentTestUtil owner, Lock lock, List<TestParticipant> participants) {
+        super(owner)
+        this.participants = participants
+        this.lock = lock
+    }
+
+    void running() {
+        lock.lock()
+        try {
+            participants*.running()
+        } finally {
+            lock.unlock()
+        }
+    }
+
+    @Override
+    void completesBefore(Date timeout) {
+        lock.lock()
+        try {
+            participants*.completesBefore(timeout)
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+class StoppableExecutorStub implements StoppableExecutor {
+    final ConcurrentTestUtil owner
+    final Set<TestThread> threads = new CopyOnWriteArraySet<TestThread>()
+
+    StoppableExecutorStub(ConcurrentTestUtil owner) {
+        this.owner = owner
+    }
+
+    void stop() {
+        def timeout = owner.shortTimeout()
+        threads.each { it.completesBefore(timeout) }
+    }
+
+    void stop(int timeoutValue, TimeUnit timeoutUnits) {
+        throw new UnsupportedOperationException()
+    }
+
+    void requestStop() {
+        throw new UnsupportedOperationException()
+    }
+
+    void execute(Runnable runnable) {
+        threads.add(owner.startThread(runnable))
+    }
+}
+
+class AbstractAsyncAction {
+    protected final ConcurrentTestUtil owner
+    private final Lock lock = new ReentrantLock()
+    protected final Condition condition = lock.newCondition()
+    protected Throwable failure
+
+    AbstractAsyncAction(ConcurrentTestUtil owner) {
+        this.owner = owner
+    }
+
+    protected Date shortTimeout() {
+        return owner.shortTimeout()
+    }
+
+    protected void onFailure(Throwable throwable) {
+        withLock {
+            failure = throwable
+            condition.signalAll()
+        }
+    }
+
+    protected def withLock(Closure cl) {
+        lock.lock()
+        try {
+            return cl.call()
+        } finally {
+            lock.unlock()
+        }
+    }
+}
+
+class StartAsyncAction extends AbstractAsyncAction {
+    private boolean started
+    private boolean completed
+    private Thread startThread
+
+    StartAsyncAction(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    /**
+     * Runs the given action, and then waits until another another thread calls {@link #done()}.  Asserts that the start action does not block waiting for
+     * the async action to complete.
+     *
+     * @param action The start action
+     * @return this
+     */
+    StartAsyncAction started(Runnable action) {
+        owner.onFailure this.&onFailure
+        doStart(action)
+        waitForStartToComplete()
+        waitForFinish()
+        return this
+    }
+
+    /**
+     * Marks that the async. action is now finished.
+     */
+    void done() {
+        waitForStartToComplete()
+        doFinish()
+    }
+
+
+    private void doStart(Runnable action) {
+        owner.startThread {
+            withLock {
+                if (startThread != null) {
+                    throw new IllegalStateException("Cannot start action multiple times.")
+                }
+                startThread = Thread.currentThread()
+                condition.signalAll()
+            }
+
+            action.run()
+
+            withLock {
+                started = true
+                condition.signalAll()
+            }
+        }
+
+        withLock {
+            while (startThread == null) {
+                condition.await()
+            }
+        }
+    }
+
+    private void doFinish() {
+        withLock {
+            if (completed) {
+                throw new IllegalStateException("Cannot run async action multiple times.")
+            }
+            completed = true
+            condition.signalAll()
+        }
+    }
+
+    private void waitForStartToComplete() {
+        Date timeout = shortTimeout()
+        withLock {
+            if (startThread == null) {
+                def e = new IllegalStateException("Action has not been started.")
+                e.printStackTrace()
+                throw e
+            }
+            if (Thread.currentThread() == startThread) {
+                def e = new IllegalStateException("Cannot wait for action to complete from the thread that is executing it.")
+                e.printStackTrace()
+                throw e
+            }
+            while (!started && !failure) {
+                if (!condition.awaitUntil(timeout)) {
+                    throw new IllegalStateException("Expected action to complete quickly, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+
+    private void waitForFinish() {
+        Date timeout = shortTimeout()
+        withLock {
+            while (!completed && !failure) {
+                if (!condition.awaitUntil(timeout)) {
+                    throw new IllegalStateException("Expected async action to complete, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+}
+
+abstract class AbstractWaitAction extends AbstractAsyncAction {
+    protected boolean started
+    protected boolean completed
+
+    AbstractWaitAction(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    protected void waitForBlockingActionToComplete() {
+        Date expiry = shortTimeout()
+        withLock {
+            while (!completed && !failure) {
+                if (!condition.awaitUntil(expiry)) {
+                    throw new IllegalStateException("Expected action to unblock, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+
+    protected void startBlockingAction(Runnable action) {
+        owner.startThread {
+            withLock {
+                started = true
+                condition.signalAll()
+            }
+
+            action.run()
+
+            withLock {
+                completed = true
+                condition.signalAll()
+            }
+        }
+
+        withLock {
+            while (!started) {
+                condition.await()
+            }
+        }
+    }
+
+    protected void assertBlocked() {
+        withLock {
+            if (completed) {
+                throw new IllegalStateException("Expected action to block, but it did not.")
+            }
+        }
+    }
+}
+
+class WaitForAsyncCallback extends AbstractWaitAction {
+    private boolean callbackCompleted
+    private Runnable callback
+
+    WaitForAsyncCallback(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    /**
+     * Runs the given action. Asserts that it blocks until after asynchronous callback is made. The action must register the callback using {@link #callbackLater(Runnable)}.
+     */
+    WaitForAsyncCallback start(Runnable action) {
+        owner.onFailure this.&onFailure
+
+        startBlockingAction(action)
+        waitForCallbackToBeRegistered()
+
+        Thread.sleep(500)
+
+        assertBlocked()
+        runCallbackAction()
+        waitForBlockingActionToComplete()
+
+        return this
+    }
+
+    /**
+     * Registers the callback which will unblock the action.
+     */
+    public void callbackLater(Runnable action) {
+        withLock {
+            if (callback) {
+                throw new IllegalStateException("Cannot register callback action multiple times.")
+            }
+            if (!started) {
+                throw new IllegalStateException("Action has not been started.")
+            }
+            callback = action
+            condition.signalAll()
+        }
+    }
+
+    private def runCallbackAction() {
+        owner.startThread {
+            callback.run()
+
+            withLock {
+                callbackCompleted = true
+                condition.signalAll()
+            }
+        }
+
+        Date timeout = shortTimeout()
+        withLock {
+            while (!callbackCompleted && !failure) {
+                if (!condition.awaitUntil(timeout)) {
+                    throw new IllegalStateException("Expected callback action to complete, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+        }
+    }
+
+    private void waitForCallbackToBeRegistered() {
+        Date expiry = shortTimeout()
+        withLock {
+            while (!callback && !failure && !completed) {
+                if (!condition.awaitUntil(expiry)) {
+                    throw new IllegalStateException("Expected action to register a callback action, but it did not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+            if (completed) {
+                throw new IllegalStateException("Expected action to block, but it did not.")
+            }
+        }
+    }
+}
+
+class WaitForAsyncAction extends AbstractWaitAction {
+    boolean asyncActionComplete
+
+    WaitForAsyncAction(ConcurrentTestUtil owner) {
+        super(owner)
+    }
+
+    WaitForAsyncAction start(Runnable action) {
+        owner.onFailure this.&onFailure
+        startBlockingAction(action)
+        waitForAsyncAction()
+        waitForBlockingActionToComplete()
+        return this
+    }
+
+    WaitForAsyncAction done() {
+        Thread.sleep(500)
+        assertBlocked()
+
+        withLock {
+            asyncActionComplete = true
+            condition.signalAll()
+        }
+
+        return this
+    }
+
+    def waitForAsyncAction() {
+        Date expiry = shortTimeout()
+        withLock {
+            while (!asyncActionComplete && !completed && !failure) {
+                if (!condition.awaitUntil(expiry)) {
+                    throw new IllegalStateException("Expected async action to be started, but it was not.")
+                }
+            }
+            if (failure) {
+                throw failure
+            }
+            if (!asyncActionComplete && completed) {
+                throw new IllegalStateException("Expected action to block, but it did not.")
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
new file mode 100644
index 0000000..bf61c9c
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/ConcurrentSpecification.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.util
+
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/8/11
+ */
+class ConcurrentSpecification extends Specification {
+    @Rule @Delegate ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
new file mode 100644
index 0000000..d6b416d
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
@@ -0,0 +1,172 @@
+/*
+ * 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.util
+
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.AndSpec
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
+import org.gradle.api.internal.project.taskfactory.TaskFactory
+import org.gradle.api.Task
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.testfixtures.ProjectBuilder
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
+import org.apache.ivy.core.module.id.ArtifactId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.apache.ivy.core.module.descriptor.Configuration
+import org.gradle.BuildResult
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.groovy.scripts.ScriptSource
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.gradle.groovy.scripts.Script
+import java.rmi.server.UID
+import org.gradle.groovy.scripts.DefaultScript
+
+/**
+ * @author Hans Dockter
+ */
+class HelperUtil {
+
+     public static final Closure TEST_CLOSURE = {}
+     public static final Spec TEST_SPEC = new AndSpec()
+     private static final AsmBackedClassGenerator 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) {
+         return createTask(type, createRootProject())
+     }
+
+     static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
+         return createTask(type, project, 'name')
+     }
+
+     static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
+         return TASK_FACTORY.createTask(project, [name: name, type: type])
+     }
+
+     static DefaultProject createRootProject() {
+         createRootProject(TemporaryFolder.newInstance().dir)
+     }
+
+     static DefaultProject createRootProject(File rootDir) {
+         return ProjectBuilder
+                 .builder()
+                 .withProjectDir(rootDir)
+                 .build()
+     }
+
+     static DefaultProject createChildProject(DefaultProject parent, String name, File projectDir = null) {
+         return ProjectBuilder
+                 .builder()
+                 .withName(name)
+                 .withParent(parent)
+                 .withProjectDir(projectDir)
+                 .build();
+     }
+
+     static pureStringTransform(def collection) {
+         collection.collect {
+             it.toString()
+         }
+     }
+
+     static DefaultExcludeRule getTestExcludeRule(def module = 'module') {
+         new DefaultExcludeRule(new ArtifactId(
+                 new ModuleId('org', module), PatternMatcher.ANY_EXPRESSION,
+                 PatternMatcher.ANY_EXPRESSION,
+                 PatternMatcher.ANY_EXPRESSION),
+                 ExactPatternMatcher.INSTANCE, null)
+     }
+
+     static DefaultDependencyDescriptor getTestDescriptor() {
+         new DefaultDependencyDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), false)
+     }
+
+     static DefaultModuleDescriptor createModuleDescriptor(Set confs) {
+         DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), "status", null)
+         confs.each { moduleDescriptor.addConfiguration(new Configuration(it)) }
+         return moduleDescriptor;
+     }
+
+     static BuildResult createBuildResult(Throwable t) {
+         return new BuildResult(null, t);
+     }
+
+     static ModuleDependency createDependency(String group, String name, String version) {
+       new DefaultExternalModuleDependency(group, name, version)
+     }
+
+     static DefaultPublishArtifact createPublishArtifact(String name, String extension, String type, String classifier) {
+       new DefaultPublishArtifact(name, extension, type, classifier, new Date(), new File(""))
+     }
+
+     static groovy.lang.Script createScript(String code) {
+         new GroovyShell().parse(code)
+     }
+
+     static Object call(String text, Object... params) {
+         toClosure(text).call(*params)
+     }
+
+     static Closure toClosure(String text) {
+         return new GroovyShell().evaluate("return " + text)
+     }
+
+     static Closure toClosure(ScriptSource source) {
+         CompilerConfiguration configuration = new CompilerConfiguration();
+         configuration.setScriptBaseClass(TestScript.getName());
+
+         GroovyShell shell = new GroovyShell(configuration)
+         Script script = shell.parse(source.resource.text)
+         script.setScriptSource(source)
+         return script.run()
+     }
+
+     static Closure toClosure(TestClosure closure) {
+         return { param -> closure.call(param) }
+     }
+
+     static Closure returns(Object value) {
+         return { value }
+     }
+
+     static Closure createSetterClosure(String name, String value) {
+         return {
+             "set$name"(value)
+         }
+     }
+
+     static String createUniqueId() {
+         return new UID().toString();
+     }
+ }
+
+
+interface TestClosure {
+    Object call(Object param);
+}
+
+abstract class TestScript extends DefaultScript {
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
new file mode 100644
index 0000000..7001422
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
@@ -0,0 +1,405 @@
+/*
+ * 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.util;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.CompositeFileCollection;
+import org.gradle.api.internal.file.UnionFileCollection;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.internal.file.collections.DefaultFileCollectionResolveContext;
+import org.hamcrest.*;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.internal.ReturnDefaultValueAction;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.Pattern;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertTrue;
+
+public class Matchers {
+    @Factory
+    public static <T> Matcher<T> reflectionEquals(T equalsTo) {
+        return new ReflectionEqualsMatcher<T>(equalsTo);
+    }
+
+    @Factory
+    public static <T, S extends Iterable<? extends T>> Matcher<S> hasSameItems(final S items) {
+        return new BaseMatcher<S>() {
+            public boolean matches(Object o) {
+                Iterable<? extends T> iterable = (Iterable<? extends T>) o;
+                List<T> actual = new ArrayList<T>();
+                for (T t : iterable) {
+                    actual.add(t);
+                }
+                List<T> expected = new ArrayList<T>();
+                for (T t : items) {
+                    expected.add(t);
+                }
+
+                return expected.equals(actual);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an Iterable that has same items as ").appendValue(items);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends CharSequence> Matcher<T> matchesRegexp(final String pattern) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return Pattern.compile(pattern).matcher((CharSequence) o).matches();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends CharSequence> Matcher<T> matchesRegexp(final Pattern pattern) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return pattern.matcher((CharSequence) o).matches();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
+            }
+        };
+    }
+
+    @Factory
+    public static <T> Matcher<T> strictlyEqual(final T other) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return strictlyEquals(o, other);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an Object that strictly equals ").appendValue(other);
+            }
+        };
+    }
+
+    public static boolean strictlyEquals(Object a, Object b) {
+        if (!a.equals(b)) {
+            return false;
+        }
+        if (!b.equals(a)) {
+            return false;
+        }
+        if (!a.equals(a)) {
+            return false;
+        }
+        if (b.equals(null)) {
+            return false;
+        }
+        if (b.equals(new Object())) {
+            return false;
+        }
+        if (a.hashCode() != b.hashCode()) {
+            return false;
+        }
+        return true;
+
+    }
+
+    @Factory
+    @Deprecated
+    /**
+     * Please avoid using as the hamcrest way of reporting error wraps a multi-line
+     * text into a single line and makes hard to understand the problem.
+     * Instead, please try to use the spock/groovy assert and {@link #containsLine(String, String)}
+     */
+    public static Matcher<String> containsLine(final String line) {
+        return new BaseMatcher<String>() {
+            public boolean matches(Object o) {
+                return containsLine(equalTo(line)).matches(o);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a String that contains line ").appendValue(line);
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<String> containsLine(final Matcher<? super String> matcher) {
+        return new BaseMatcher<String>() {
+            public boolean matches(Object o) {
+                String str = (String) o;
+                return containsLine(str, matcher);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a String that contains line that is ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    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<?>>() {
+            public boolean matches(Object o) {
+                Iterable<?> iterable = (Iterable<?>) o;
+                return iterable != null && !iterable.iterator().hasNext();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty Iterable");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Map<?, ?>> isEmptyMap() {
+        return new BaseMatcher<Map<?, ?>>() {
+            public boolean matches(Object o) {
+                Map<?, ?> map = (Map<?, ?>) o;
+                return map != null && map.isEmpty();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty map");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Object[]> isEmptyArray() {
+        return new BaseMatcher<Object[]>() {
+            public boolean matches(Object o) {
+                Object[] array = (Object[]) o;
+                return array != null && array.length == 0;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty array");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Object> isSerializable() {
+        return new BaseMatcher<Object>() {
+            public boolean matches(Object o) {
+                try {
+                    new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(o);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+                return true;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("is serializable");
+            }
+        };
+    }
+    
+    @Factory
+    public static Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
+        return new BaseMatcher<Throwable>() {
+            public boolean matches(Object o) {
+                Throwable t = (Throwable) o;
+                return matcher.matches(t.getMessage());
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an exception with message that is ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Task> dependsOn(final String... tasks) {
+        return dependsOn(equalTo(new HashSet<String>(Arrays.asList(tasks))));
+    }
+
+    @Factory
+    public static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher) {
+        return new BaseMatcher<Task>() {
+            public boolean matches(Object o) {
+                Task task = (Task) o;
+                Set<String> names = new HashSet<String>();
+                Set<? extends Task> depTasks = task.getTaskDependencies().getDependencies(task);
+                for (Task depTask : depTasks) {
+                    names.add(depTask.getName());
+                }
+                boolean matches = matcher.matches(names);
+                if (!matches) {
+                    StringDescription description = new StringDescription();
+                    matcher.describeTo(description);
+                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
+                }
+                return matches;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a Task that depends on ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends Buildable> Matcher<T> builtBy(String... tasks) {
+        return builtBy(equalTo(new HashSet<String>(Arrays.asList(tasks))));
+    }
+
+    @Factory
+    public static <T extends Buildable> Matcher<T> builtBy(final Matcher<? extends Iterable<String>> matcher) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                Buildable task = (Buildable) o;
+                Set<String> names = new HashSet<String>();
+                Set<? extends Task> depTasks = task.getBuildDependencies().getDependencies(null);
+                for (Task depTask : depTasks) {
+                    names.add(depTask.getName());
+                }
+                boolean matches = matcher.matches(names);
+                if (!matches) {
+                    StringDescription description = new StringDescription();
+                    matcher.describeTo(description);
+                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
+                }
+                return matches;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a Buildable that is built by ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends FileCollection> Matcher<T> sameCollection(final FileCollection expected) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                FileCollection actual = (FileCollection) o;
+                List<? extends FileCollection> actualCollections = unpack(actual);
+                List<? extends FileCollection> expectedCollections = unpack(expected);
+                boolean equals = actualCollections.equals(expectedCollections);
+                if (!equals) {
+                    System.out.println("expected: " + expectedCollections);
+                    System.out.println("actual: " + actualCollections);
+                }
+                return equals;
+            }
+
+            private List<? extends FileCollection> unpack(FileCollection expected) {
+                if (expected instanceof UnionFileCollection) {
+                    UnionFileCollection collection = (UnionFileCollection) expected;
+                    return new ArrayList<FileCollection>(collection.getSources());
+                }
+                if (expected instanceof DefaultConfigurableFileCollection) {
+                    DefaultConfigurableFileCollection collection = (DefaultConfigurableFileCollection) expected;
+                    return new ArrayList<FileCollection>((Set) collection.getFrom());
+                }
+                if (expected instanceof CompositeFileCollection) {
+                    CompositeFileCollection collection = (CompositeFileCollection) expected;
+                    DefaultFileCollectionResolveContext context = new DefaultFileCollectionResolveContext();
+                    collection.resolve(context);
+                    return context.resolveAsFileCollections();
+                }
+                throw new RuntimeException("Cannot get children of " + expected);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("same file collection as ").appendValue(expected);
+            }
+        };
+    }
+
+    /**
+     * Returns a placeholder for a mock method parameter.
+     */
+    public static <T> Collector<T> collector() {
+        return new Collector<T>();
+    }
+
+    /**
+     * Returns an action which collects the first parameter into the given placeholder.
+     */
+    public static CollectAction collectTo(Collector<?> collector) {
+        return new CollectAction(collector);
+    }
+
+    public static class CollectAction implements Action {
+        private Action action = new ReturnDefaultValueAction();
+        private final Collector<?> collector;
+
+        public CollectAction(Collector<?> collector) {
+            this.collector = collector;
+        }
+
+        public Action then(Action action) {
+            this.action = action;
+            return this;
+        }
+
+        public void describeTo(Description description) {
+            description.appendText("collect parameter then ").appendDescriptionOf(action);
+        }
+
+        public Object invoke(Invocation invocation) throws Throwable {
+            collector.setValue(invocation.getParameter(0));
+            return action.invoke(invocation);
+        }
+    }
+
+    public static class Collector<T> {
+        private T value;
+        private boolean set;
+
+        public T get() {
+            assertTrue(set);
+            return value;
+        }
+
+        void setValue(Object parameter) {
+            value = (T) parameter;
+            set = true;
+        }
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
new file mode 100755
index 0000000..557221b
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
@@ -0,0 +1,666 @@
+/*
+ * 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.util;
+
+import groovy.lang.Closure;
+import junit.framework.AssertionFailedError;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>A base class for testing concurrent code.</p>
+ *
+ * <p>Provides several ways to start and manage threads. You can use the {@link #start(groovy.lang.Closure)} or {@link
+ * #run(groovy.lang.Closure)} methods to execute test code in other threads. You can use {@link #waitForAll()} to wait
+ * for all test threads to complete. In addition, the test tear-down method blocks until all test threads have stopped
+ * and ensures that no exceptions were thrown in any test threads.</p>
+ *
+ * <p>Provides an {@link java.util.concurrent.Executor} implementation, which uses test threads to execute any tasks
+ * submitted to it.</p>
+ *
+ * <p>You can use {@link #syncAt(int)} and {@link #expectBlocksUntil(int, groovy.lang.Closure)} to synchronise between
+ * test threads.</p>
+ */
+public class MultithreadedTestCase {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MultithreadedTestCase.class);
+    private static final int MAX_WAIT_TIME = 5000;
+    private ExecutorImpl executor;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final Set<Thread> active = new HashSet<Thread>();
+    private final Set<Thread> synching = new HashSet<Thread>();
+    private final List<Throwable> failures = new ArrayList<Throwable>();
+    private final Map<Integer, ClockTickImpl> ticks = new HashMap<Integer, ClockTickImpl>();
+    private ClockTickImpl currentTick = getTick(0);
+    private boolean stopped;
+    private final ThreadLocal<Matcher<? extends Throwable>> expectedFailure
+            = new ThreadLocal<Matcher<? extends Throwable>>();
+    private final SyncPoint syncPoint = new SyncPoint();
+
+    /**
+     * Creates an Executor which the test can control.
+     */
+    protected ExecutorService getExecutor() {
+        if (executor == null) {
+            executor = new ExecutorImpl();
+        }
+        return executor;
+    }
+
+    /**
+     * Creates an ExecutorFactory for the test to use.
+     */
+    protected ExecutorFactory getExecutorFactory() {
+        return new DefaultExecutorFactory() {
+            @Override
+            protected ExecutorService createExecutor(String displayName) {
+                return new ExecutorImpl();
+            }
+        };
+    }
+
+    /**
+     * Executes the given closure in a test thread.
+     */
+    protected ThreadHandle start(final Closure closure) {
+        Runnable task = new Runnable() {
+            public void run() {
+                closure.call();
+            }
+        };
+
+        return start(task);
+    }
+
+    /**
+     * Executes the given closure in a test thread and waits for it to complete.
+     */
+    protected ThreadHandle run(final Closure closure) {
+        Runnable task = new Runnable() {
+            public void run() {
+                closure.call();
+            }
+        };
+
+        return start(task).waitFor();
+    }
+
+    protected ThreadHandle expectTimesOut(int value, TimeUnit units, Closure closure) {
+        Date start = new Date();
+        ThreadHandle threadHandle = start(closure);
+        threadHandle.waitFor();
+        Date end = new Date();
+        long actual = end.getTime() - start.getTime();
+        long expected = units.toMillis(value);
+        if (actual < expected - 200) {
+            throw new RuntimeException(String.format(
+                    "Action did not block for expected time. Expected ~ %d ms, was %d ms.", expected, actual));
+        }
+        if (actual > expected + 500) {
+            throw new RuntimeException(String.format(
+                    "Action did not complete within expected time. Expected ~ %d ms, was %d ms.", expected, actual));
+        }
+        return threadHandle;
+    }
+
+    /**
+     * Executes the given runnable in a test thread.
+     */
+    protected ThreadHandle start(final Runnable task) {
+        final Thread thread = new Thread() {
+            @Override
+            public String toString() {
+                return "test thread " + getId();
+            }
+
+            public void run() {
+                Throwable failure = null;
+                try {
+                    try {
+                        task.run();
+                    } catch (InvokerInvocationException e) {
+                        failure = e.getCause();
+                    } catch (Throwable throwable) {
+                        failure = throwable;
+                    }
+                } finally {
+                    testThreadFinished(this, failure);
+                }
+            }
+        };
+
+        testThreadStarted(thread);
+        thread.start();
+        return new ThreadHandleImpl(thread);
+    }
+
+    private void testThreadStarted(Thread thread) {
+        lock.lock();
+        try {
+            if (stopped) {
+                throw new IllegalStateException("Cannot start new threads, as this test case has been stopped.");
+            }
+            LOGGER.debug("Started {}", thread);
+            active.add(thread);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void testThreadFinished(Thread thread, Throwable failure) {
+        lock.lock();
+        try {
+            active.remove(thread);
+            Matcher<? extends Throwable> matcher = expectedFailure.get();
+            if (failure != null) {
+                if (matcher != null && matcher.matches(failure)) {
+                    LOGGER.debug("Finished {} with expected failure.", thread);
+                } else {
+                    LOGGER.error(String.format("Failure in %s", thread), failure);
+                    failures.add(failure);
+                }
+            } else {
+                if (matcher != null) {
+                    String message = String.format("Did not get expected failure in %s", thread);
+                    LOGGER.error(message);
+                    failures.add(new AssertionFailedError(message));
+                } else {
+                    LOGGER.debug("Finished {}", thread);
+                }
+            }
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Waits for all asynchronous activity to complete. Applies a timeout, and re-throws any exceptions which occurred.
+     */
+    public void waitForAll() {
+        Date expiry = new Date(System.currentTimeMillis() + 2 * MAX_WAIT_TIME);
+        lock.lock();
+        try {
+            LOGGER.debug("Waiting for test threads complete.");
+
+            if (active.contains(Thread.currentThread())) {
+                throw new RuntimeException("A test thread cannot wait for test threads to complete.");
+            }
+            try {
+                while (!active.isEmpty()) {
+                    boolean signaled = condition.awaitUntil(expiry);
+                    if (!signaled) {
+                        failures.add(new RuntimeException("Timeout waiting for threads to finish."));
+                        break;
+                    }
+                }
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+
+            LOGGER.debug("All test threads complete.");
+
+            if (!failures.isEmpty()) {
+                Throwable failure = failures.get(0);
+                failures.clear();
+                if (failure instanceof RuntimeException) {
+                    throw (RuntimeException) failure;
+                }
+                if (failure instanceof Error) {
+                    throw (Error) failure;
+                }
+                throw new RuntimeException("An unexpected exception occurred in a test thread.", failure);
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    @After
+    public void waitForStop() {
+        lock.lock();
+        try {
+            stopped = true;
+        } finally {
+            lock.unlock();
+        }
+        waitForAll();
+    }
+
+    /**
+     * Returns the meta-info for the given clock tick.
+     */
+    public ClockTick clockTick(int tick) {
+        lock.lock();
+        try {
+            return getTick(tick);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private ClockTickImpl getTick(int tick) {
+        ClockTickImpl clockTick = ticks.get(tick);
+        if (clockTick == null) {
+            clockTick = new ClockTickImpl(tick);
+            ticks.put(tick, clockTick);
+        }
+        return clockTick;
+    }
+
+    /**
+     * Blocks until the clock has reached the given tick. The clock advances to the given tick when all test threads
+     * have called {@link #syncAt(int)} or {@link #expectBlocksUntil(int, groovy.lang.Closure)} with the given tick, and
+     * there are least 2 test threads.
+     *
+     * @param tick The expected clock tick
+     */
+    public void syncAt(int tick) {
+        LOGGER.debug("Thread {} synching at tick {}", Thread.currentThread(), tick);
+
+        lock.lock();
+        try {
+            ClockTickImpl clockTick = getTick(tick);
+            if (!clockTick.isImmediatelyAfter(currentTick)) {
+                throw new RuntimeException(String.format("Cannot wait for %s, as clock is currently at %s.", clockTick,
+                        currentTick));
+            }
+            if (!active.contains(Thread.currentThread())) {
+                throw new RuntimeException("Cannot wait for clock tick from a thread which is not a test thread.");
+            }
+
+            Date expiry = new Date(System.currentTimeMillis() + MAX_WAIT_TIME);
+            synching.add(Thread.currentThread());
+            condition.signalAll();
+            while (failures.isEmpty() && currentTick != clockTick && !clockTick.allThreadsSynced(synching, active)) {
+                try {
+                    boolean signalled = condition.awaitUntil(expiry);
+                    if (!signalled) {
+                        throw new RuntimeException(String.format(
+                                "Timeout waiting for all threads to reach %s. Currently at %s.", clockTick,
+                                currentTick));
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            if (!failures.isEmpty()) {
+                throw new RuntimeException(String.format(
+                        "Could not wait for all threads to reach %s, as a failure has occurred in another test thread.",
+                        clockTick));
+            }
+            if (clockTick.isImmediatelyAfter(currentTick)) {
+                currentTick = clockTick;
+                synching.clear();
+            }
+        } finally {
+            lock.unlock();
+        }
+
+        LOGGER.debug("Thread {} sync done", Thread.currentThread());
+    }
+
+    /**
+     * Expects that the given tick will be reached at some point in the future. Does not block until the tick has been
+     * reached.
+     *
+     * @param tick The expected clock tick.
+     */
+    public void expectLater(final int tick) {
+        final Thread targetThread = Thread.currentThread();
+        LOGGER.debug("Thread {} expecting tick {}", targetThread, tick);
+        start(new Runnable() {
+            public void run() {
+                try {
+                    Thread.sleep(200L);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                lock.lock();
+                try {
+                    ClockTickImpl clockTick = getTick(tick);
+                    if (!clockTick.isImmediatelyAfter(currentTick)) {
+                        throw new RuntimeException(String.format("Cannot wait for %s, as clock is currently at %s.",
+                                clockTick, currentTick));
+                    }
+                    if (!active.contains(targetThread)) {
+                        throw new RuntimeException(
+                                "Cannot wait for clock tick from a thread which is not a test thread.");
+                    }
+
+                    synching.add(targetThread);
+                    condition.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        });
+    }
+
+    /**
+     * Asserts that the given closure blocks until the given clock tick is reached.
+     *
+     * @param tick The expected clock tick when the closure completes.
+     * @param closure The closure to execute.
+     */
+    public void expectBlocksUntil(int tick, Closure closure) {
+        expectLater(tick);
+        closure.call();
+        shouldBeAt(tick);
+    }
+
+    /**
+     * Asserts that the clock is at the given tick.
+     *
+     * @param tick The expected clock tick.
+     */
+    public void shouldBeAt(int tick) {
+        lock.lock();
+        try {
+            if (currentTick != getTick(tick)) {
+                throw new RuntimeException(String.format("Expected clock to be at %s, but is at %s.", tick,
+                        currentTick));
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Indicates that the current test thread will fail with an exception that matches the given criteria.
+     */
+    public void willFailWith(Matcher<? extends Throwable> matcher) {
+        expectedFailure.set(matcher);
+    }
+
+    /**
+     * Executes the given action in another thread, and asserts that the action blocks until all actions provided to
+     * {@link #expectUnblocks(groovy.lang.Closure)} have been executed.
+     *
+     * @param action The action to execute.
+     */
+    public void expectBlocks(Closure action) {
+        syncPoint.expectBlocks(action);
+    }
+
+    /**
+     * Executes the given action, asserting that it unblocks all actions provided to {@link
+     * #expectBlocks(groovy.lang.Closure)}
+     *
+     * @param action The action to execute.
+     */
+    public void expectUnblocks(Closure action) {
+        syncPoint.expectUnblocks(action);
+    }
+
+    private class ExecutorImpl extends AbstractExecutorService {
+        private final Set<ThreadHandle> threads = new CopyOnWriteArraySet<ThreadHandle>();
+
+        public void execute(Runnable command) {
+            threads.add(start(command));
+        }
+
+        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+            Date expiry = new Date(System.currentTimeMillis() + unit.toMillis(timeout));
+            for (ThreadHandle thread : threads) {
+                if (!thread.waitUntil(expiry)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void shutdown() {
+        }
+
+        public List<Runnable> shutdownNow() {
+            return new ArrayList<Runnable>();
+        }
+
+        public boolean isShutdown() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isTerminated() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public interface ThreadHandle {
+        ThreadHandle waitFor();
+
+        boolean waitUntil(Date expiry);
+
+        boolean isCurrentThread();
+
+        void waitUntilBlocked();
+    }
+
+    public interface ClockTick {
+        ClockTick hasParticipants(int count);
+    }
+
+    private static class ClockTickImpl implements ClockTick {
+        private final int number;
+        private int participants;
+
+        private ClockTickImpl(int number) {
+            this.number = number;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("tick %d", number);
+        }
+
+        public ClockTick hasParticipants(int count) {
+            participants = count;
+            return this;
+        }
+
+        public boolean allThreadsSynced(Set<Thread> synching, Set<Thread> active) {
+            if (participants > 0) {
+                return synching.size() == participants;
+            }
+            return synching.equals(active) && synching.size() > 1;
+        }
+
+        public boolean isImmediatelyAfter(ClockTickImpl other) {
+            return number == other.number + 1;
+        }
+    }
+
+    private enum State {
+        Idle, Blocking, Blocked, Unblocking, Unblocked, Failed
+    }
+
+    private class SyncPoint {
+        private final Lock lock = new ReentrantLock();
+        private final Condition condition = lock.newCondition();
+        private State state = State.Idle;
+        private ThreadHandle blockingThread;
+
+        public void expectBlocks(Closure action) {
+            try {
+                setState(State.Idle, State.Blocking);
+                setBlockingThread(start(action));
+                setState(State.Blocking, State.Blocked);
+                waitForState(State.Unblocked, State.Failed);
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        public void expectUnblocks(Closure action) {
+            try {
+                waitForState(State.Blocked);
+
+                ThreadHandle thread = getBlockingThread();
+                if (thread.isCurrentThread()) {
+                    throw new IllegalStateException("The blocking thread cannot unblock itself.");
+                }
+
+                setState(State.Blocked, State.Unblocking);
+                try {
+                    thread.waitUntilBlocked();
+                    action.call();
+                    boolean completed = thread.waitUntil(new Date(System.currentTimeMillis() + 500L));
+                    if (!completed) {
+                        throw new IllegalStateException("Expected blocking action to unblock, but it did not.");
+                    }
+                    setState(State.Unblocking, State.Unblocked);
+                } catch (Throwable e) {
+                    setState(State.Unblocking, State.Failed);
+                    throw UncheckedException.throwAsUncheckedException(e);
+                } finally {
+                    setBlockingThread(null);
+                }
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        private ThreadHandle getBlockingThread() {
+            lock.lock();
+            try {
+                return blockingThread;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        private void setBlockingThread(ThreadHandle thread) {
+            lock.lock();
+            try {
+                blockingThread = thread;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        private State waitForState(State... states) throws InterruptedException {
+            Date expiry = new Date(System.currentTimeMillis() + 4000L);
+            Collection<State> expectedStates = Arrays.asList(states);
+            lock.lock();
+            try {
+                while (!expectedStates.contains(state)) {
+                    if (!condition.awaitUntil(expiry)) {
+                        throw new IllegalStateException(String.format("Timeout waiting for one of: %s",
+                                expectedStates));
+                    }
+                }
+                return state;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        private void setState(State expected, State newState) {
+            lock.lock();
+            try {
+                if (state != expected) {
+                    throw new IllegalStateException(String.format("In unexpected state. Expected %s, actual %s",
+                            expected, state));
+                }
+                state = newState;
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private class ThreadHandleImpl implements ThreadHandle {
+        private final Thread thread;
+        private final Set<Thread.State> blockedStates = EnumSet.of(Thread.State.BLOCKED, Thread.State.TIMED_WAITING,
+                Thread.State.WAITING);
+
+        public ThreadHandleImpl(Thread thread) {
+            this.thread = thread;
+        }
+
+        public ThreadHandle waitFor() {
+            Date expiry = new Date(System.currentTimeMillis() + 2 * MAX_WAIT_TIME);
+            if (!waitUntil(expiry)) {
+                throw new RuntimeException("timeout waiting for test thread to stop.");
+            }
+            return this;
+        }
+
+        public boolean waitUntil(Date expiry) {
+            if (isCurrentThread()) {
+                throw new RuntimeException("A test thread cannot wait for itself to complete.");
+            }
+
+            lock.lock();
+            try {
+                while (active.contains(thread)) {
+                    try {
+                        boolean signalled = condition.awaitUntil(expiry);
+                        if (!signalled) {
+                            return false;
+                        }
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+
+            return true;
+        }
+
+        public boolean isCurrentThread() {
+            return Thread.currentThread() == thread;
+        }
+
+        public boolean isBlocked() {
+            return blockedStates.contains(thread.getState());
+        }
+
+        public void waitUntilBlocked() {
+            long expiry = System.currentTimeMillis() + 2000L;
+            while (!isBlocked()) {
+                if (System.currentTimeMillis() > expiry) {
+                    throw new IllegalStateException("Timeout waiting for thread to block.");
+                }
+                try {
+                    Thread.sleep(200L);
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TestTask.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestTask.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/util/TestTask.groovy
rename to subprojects/core/src/testFixtures/groovy/org/gradle/util/TestTask.groovy
diff --git a/subprojects/cpp/cpp.gradle b/subprojects/cpp/cpp.gradle
new file mode 100644
index 0000000..b2b4296
--- /dev/null
+++ b/subprojects/cpp/cpp.gradle
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+    compile project(':core')
+    compile project(":plugins")
+    compile project(":ide")
+    integTestRuntime project(":maven")
+}
+
+useTestFixtures()
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
new file mode 100755
index 0000000..b8c5838
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AbstractBinariesIntegrationSpec.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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.cpp
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.util.TestFile
+import org.junit.runner.RunWith
+
+ at RunWith(CppIntegrationTestRunner)
+abstract class AbstractBinariesIntegrationSpec extends AbstractIntegrationSpec {
+    def TestFile executable(Object path) {
+        return file(OperatingSystem.current().getExecutableName(path.toString()))
+    }
+
+    def TestFile sharedLibrary(Object path) {
+        return file(OperatingSystem.current().getSharedLibraryName(path.toString()))
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
new file mode 100755
index 0000000..79ecb00
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2012 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.cpp;
+
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.plugins.cpp.gpp.internal.version.GppVersionDeterminer;
+
+import java.io.File;
+import java.util.*;
+
+public class AvailableCompilers {
+    static List<CompilerCandidate> getCompilers() {
+        List<CompilerCandidate> compilers = new ArrayList<CompilerCandidate>();
+        if (OperatingSystem.current().isWindows()) {
+            compilers.add(findVisualCpp());
+            compilers.add(findMinGW());
+        } else {
+            compilers.add(findGpp("3"));
+            compilers.add(findGpp("4"));
+        }
+        return compilers;
+    }
+
+    static private CompilerCandidate findVisualCpp() {
+        // Search first in path, then in the standard installation locations
+        File compilerExe = OperatingSystem.current().findInPath("cl.exe");
+        if (compilerExe != null) {
+            return new InstalledCompiler("visual c++");
+        }
+
+        compilerExe = new File("C:/Program Files (x86)/Microsoft Visual Studio 10.0/VC/bin/cl.exe");
+        if (compilerExe.isFile()) {
+            File binDir = compilerExe.getParentFile();
+            File vcDir = binDir.getParentFile();
+            File baseDir = vcDir.getParentFile();
+            File sdkDir = new File(baseDir.getParentFile(), "Microsoft SDKs/Windows/v7.0A");
+            return new InstalledCompiler("visual c++",
+                    new File(baseDir, "Common7/IDE"), 
+                    binDir, 
+                    new File(baseDir, "Common7/Tools"), 
+                    new File(vcDir, "VCPackages"),
+                    new File(sdkDir, "Bin"))
+                    .envVar("INCLUDE", new File(vcDir, "include").getAbsolutePath())
+                    .envVar("LIB", new File(vcDir, "lib").getAbsolutePath() + File.pathSeparator + new File(sdkDir, "lib").getAbsolutePath());
+        }
+        
+        return new UnavailableCompiler("visual c++");
+    }
+
+    static private CompilerCandidate findMinGW() {
+        // Search in the standard installation locations (doesn't yet work with cygwin g++ in path)
+        File compilerExe = new File("C:/MinGW/bin/g++.exe");
+        if (compilerExe.isFile()) {
+            return new InstalledCompiler("mingw", compilerExe.getParentFile());
+        }
+
+        return new UnavailableCompiler("mingw");
+    }
+
+    static private CompilerCandidate findGpp(String versionPrefix) {
+        String name = String.format("g++ (%s)", versionPrefix);
+        GppVersionDeterminer versionDeterminer = new GppVersionDeterminer();
+        for (File candidate : OperatingSystem.current().findAllInPath("g++")) {
+            if (versionDeterminer.transform(candidate).startsWith(versionPrefix)) {
+                return new InstalledCompiler(name, candidate.getParentFile());
+            }
+        }
+
+        return new UnavailableCompiler(name);
+    }
+
+    public static abstract class CompilerCandidate {
+        @Override
+        public String toString() {
+            return getDisplayName();
+        }
+
+        public abstract String getDisplayName();
+        
+        public abstract boolean isAvailable();
+
+        public abstract List<File> getPathEntries();
+
+        public abstract Map<String, String> getEnvironmentVars();
+    }
+    
+    public static class InstalledCompiler extends CompilerCandidate {
+        private final List<File> pathEntries;
+        private final Map<String, String> environmentVars = new HashMap<String, String>();
+        private final String name;
+
+        public InstalledCompiler(String name, File... pathEntries) {
+            this.name = name;
+            this.pathEntries = Arrays.asList(pathEntries);
+        }
+
+        InstalledCompiler envVar(String key, String value) {
+            environmentVars.put(key, value);
+            return this;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return name;
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return true;
+        }
+
+        @Override
+        public List<File> getPathEntries() {
+            return pathEntries;
+        }
+
+        @Override
+        public Map<String, String> getEnvironmentVars() {
+            return environmentVars;
+        }
+    }
+
+    public static class UnavailableCompiler extends CompilerCandidate {
+        private final String name;
+
+        public UnavailableCompiler(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return name;
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return false;
+        }
+
+        @Override
+        public List<File> getPathEntries() {
+            throw new UnsupportedOperationException("This compiler is not installed.");
+        }
+
+        @Override
+        public Map<String, String> getEnvironmentVars() {
+            throw new UnsupportedOperationException("This compiler is not installed.");
+        }
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppExePluginGoodBehaviourTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppExePluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..1401efb
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppExePluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.cpp
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class CppExePluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    def String getPluginId() {
+        return "cpp-exe"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
new file mode 100755
index 0000000..49863d8
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 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.cpp;
+
+import com.google.common.base.Joiner;
+import org.gradle.integtests.fixtures.AbstractMultiTestRunner;
+import org.gradle.internal.nativeplatform.*;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.os.OperatingSystem;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+public class CppIntegrationTestRunner extends AbstractMultiTestRunner {
+    public CppIntegrationTestRunner(Class<?> target) {
+        super(target);
+    }
+
+    @Override
+    protected void createExecutions() {
+        List<AvailableCompilers.CompilerCandidate> compilers = AvailableCompilers.getCompilers();
+        for (AvailableCompilers.CompilerCandidate compiler : compilers) {
+            add(new CompilerExecution(compiler));
+        }
+    }
+
+    private static class CompilerExecution extends Execution {
+        private static final ProcessEnvironment PROCESS_ENVIRONMENT = new NativeServices().get(ProcessEnvironment.class);
+        private final AvailableCompilers.CompilerCandidate compiler;
+        private String originalPath;
+        private final String pathVarName;
+
+
+        public CompilerExecution(AvailableCompilers.CompilerCandidate compiler) {
+            this.compiler = compiler;
+            this.pathVarName = !OperatingSystem.current().isWindows() ? "Path" : "PATH";
+        }
+
+        @Override
+        protected boolean isEnabled() {
+            return compiler.isAvailable();
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return compiler.getDisplayName();
+        }
+
+        @Override
+        protected void before() {
+            System.out.println(String.format("Using compiler %s", compiler.getDisplayName()));
+
+            String compilerPath = Joiner.on(File.pathSeparator).join(compiler.getPathEntries());
+
+
+            if (compilerPath.length() > 0) {
+                originalPath = System.getenv(pathVarName);
+                String path = compilerPath + File.pathSeparator + originalPath;
+                System.out.println(String.format("Using path %s", path));
+                PROCESS_ENVIRONMENT.setEnvironmentVariable(pathVarName, path);
+            }
+
+            for (Map.Entry<String, String> entry : compiler.getEnvironmentVars().entrySet()) {
+                System.out.println(String.format("Using environment var %s -> %s", entry.getKey(), entry.getValue()));
+                PROCESS_ENVIRONMENT.setEnvironmentVariable(entry.getKey(), entry.getValue());
+            }
+        }
+
+        @Override
+        protected void after() {
+            if (originalPath != null) {
+                PROCESS_ENVIRONMENT.setEnvironmentVariable(pathVarName, originalPath);
+            }
+        }
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppLibPluginGoodBehaviourTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppLibPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..134b5d5
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppLibPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.cpp
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class CppLibPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    def String getPluginId() {
+        return "cpp-lib"
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppPluginIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppPluginIntegrationTest.groovy
new file mode 100755
index 0000000..a4a3f22
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppPluginIntegrationTest.groovy
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2012 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.cpp
+
+import static org.gradle.util.TextUtil.escapeString
+
+class CppPluginIntegrationTest extends AbstractBinariesIntegrationSpec {
+
+    static final HELLO_WORLD = "Hello, World!"
+
+    def "build and execute simple cpp program"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp-exe"
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src", "main", "cpp", "helloworld.cpp") << """
+            #include <iostream>
+
+            int main () {
+              std::cout << "${escapeString(HELLO_WORLD)}";
+              return 0;
+            }
+        """
+
+        when:
+        run "compileMain"
+
+        then:
+        def executable = executable("build/binaries/test")
+        executable.isFile()
+        executable.exec().out == HELLO_WORLD
+    }
+
+    def "build simple cpp library"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp-lib"
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src", "main", "cpp", "helloworld.cpp") << """
+            #include <iostream>
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            int DLL_FUNC main () {
+              std::cout << "${escapeString(HELLO_WORLD)}";
+              return 0;
+            }
+        """
+
+        when:
+        run "compileMain"
+
+        then:
+        sharedLibrary("build/binaries/test").isFile()
+    }
+
+    def "build fails when compilation fails"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp-exe"
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src", "main", "cpp", "helloworld.cpp") << """
+            #include <iostream>
+
+            'broken
+        """
+
+        expect:
+        fails "compileMain"
+    }
+
+    def "build fails when link fails"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp-exe"
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src", "main", "cpp", "helloworld.cpp") << """
+            int thing() { return 0; }
+        """
+
+        expect:
+        fails "compileMain"
+    }
+
+    def "build and execute program from multiple source files"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp-exe"
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src", "main", "cpp", "hello.cpp") << """
+            #include <iostream>
+
+            void hello () {
+              std::cout << "${escapeString(HELLO_WORLD)}";
+            }
+        """
+
+        and:
+        file("src", "main", "headers", "hello.h") << """
+            void hello();
+        """
+
+        and:
+        file("src", "main", "cpp", "main.cpp") << """
+            #include "hello.h"
+
+            int main () {
+              hello();
+              return 0;
+            }
+        """
+
+        when:
+        run "compileMain"
+
+        then:
+        executable("build/binaries/test").exec().out == HELLO_WORLD
+    }
+
+    def "build, install and execute program with shared library"() {
+        given:
+        buildFile << """
+            apply plugin: "cpp-exe"
+
+            cpp {
+                sourceSets {
+                    hello {}
+                }
+            }
+            libraries {
+                hello {
+                    sourceSets << cpp.sourceSets.hello
+                }
+            }
+            cpp.sourceSets.main.libs << libraries.hello
+        """
+        settingsFile << "rootProject.name = 'test'"
+
+        and:
+        file("src/hello/cpp/hello.cpp") << """
+            #include <iostream>
+            #ifdef _WIN32
+            #define DLL_FUNC __declspec(dllexport)
+            #else
+            #define DLL_FUNC
+            #endif
+
+            void DLL_FUNC hello(const char* str) {
+              std::cout << str;
+            }
+        """
+
+        and:
+        file("src/hello/headers/hello.h") << """
+            void hello(const char* str);
+        """
+
+        and:
+        file("src/main/cpp/main.cpp") << """
+            #include <iostream>
+            #include "hello.h"
+
+            int main (int argc, char** argv) {
+              hello("${escapeString(HELLO_WORLD)}");
+              for ( int i = 1; i < argc; i++ ) {
+                std::cout << "[" << argv[i] << "]";
+              }
+              return 0;
+            }
+        """
+
+        when:
+        run "installMain"
+
+        then:
+        sharedLibrary("build/binaries/hello").isFile()
+        executable("build/binaries/test").isFile()
+
+        executable("build/install/main/test").exec().out == HELLO_WORLD
+        executable("build/install/main/test").exec("a", "1 2 3").out.contains("[a][1 2 3]")
+
+        // Ensure installed binary is not dependent on the libraries in their original locations
+        when:
+        file("build/binaries").deleteDir()
+
+        then:
+        executable("build/install/main/test").exec().out == HELLO_WORLD
+    }
+}
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
new file mode 100755
index 0000000..e87815c
--- /dev/null
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 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.cpp
+
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class CppSamplesIntegrationTest extends AbstractBinariesIntegrationSpec {
+    @Rule public final Sample exewithlib = new Sample('cpp/exewithlib')
+    @Rule public final Sample dependencies = new Sample('cpp/dependencies')
+    @Rule public final Sample exe = new Sample('cpp/exe')
+
+    def "exe with lib"() {
+        given:
+        sample exewithlib
+
+        when:
+        run "installMain"
+
+        then:
+        ":exe:compileMain" in executedTasks
+
+        and:
+        sharedLibrary("cpp/exewithlib/lib/build/binaries/lib").isFile()
+        executable("cpp/exewithlib/exe/build/binaries/exe").isFile()
+        executable("cpp/exewithlib/exe/build/install/main/exe").exec().out == toPlatformLineSeparators("Hello, World!\n")
+    }
+
+    // Does not work on windows, due to GRADLE-2118
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "dependencies"() {
+        given:
+        sample dependencies
+        
+        when:
+        run ":lib:uploadArchives", ":exe:uploadArchives"
+        
+        then:
+        ":exe:mainExtractHeaders" in nonSkippedTasks
+        ":exe:compileMain" in nonSkippedTasks
+        
+        and:
+        sharedLibrary("cpp/dependencies/lib/build/binaries/lib").isFile()
+        executable("cpp/dependencies/exe/build/binaries/exe").isFile()
+        file("cpp/dependencies/exe/build/repo/dependencies/exe/1.0/exe-1.0.exe").exists()
+    }
+    
+    def "exe"() {
+        given:
+        sample exe
+        
+        when:
+        run "installMain"
+        
+        then:
+        ":compileMain" in nonSkippedTasks
+        
+        and:
+        executable("cpp/exe/build/binaries/exe").exec().out == toPlatformLineSeparators("Hello, World!\n")
+        executable("cpp/exe/build/install/main/exe").exec().out == toPlatformLineSeparators("Hello, World!\n")
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
new file mode 100644
index 0000000..e8c2ddc
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.binaries;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.FactoryNamedDomainObjectContainer;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.ReflectiveNamedDomainObjectFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.plugins.binaries.model.Executable;
+import org.gradle.plugins.binaries.model.Library;
+import org.gradle.plugins.binaries.model.internal.DefaultCompilerRegistry;
+import org.gradle.plugins.binaries.model.internal.DefaultExecutable;
+import org.gradle.plugins.binaries.model.internal.DefaultLibrary;
+
+/**
+ * temp plugin, not sure what will provide the binaries container and model elements
+ */
+public class BinariesPlugin implements Plugin<ProjectInternal> {
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(BasePlugin.class);
+
+        Instantiator instantiator = project.getServices().get(Instantiator.class);
+        project.getExtensions().add("compilers", instantiator.newInstance(
+                DefaultCompilerRegistry.class,
+                instantiator
+        ));
+        DefaultCompilerRegistry registry = project.getExtensions().getByType(DefaultCompilerRegistry.class);
+
+        project.getExtensions().add("executables", instantiator.newInstance(
+                FactoryNamedDomainObjectContainer.class,
+                Executable.class,
+                instantiator,
+                new ReflectiveNamedDomainObjectFactory<Executable>(DefaultExecutable.class, project, registry)
+        ));
+        project.getExtensions().add("libraries", instantiator.newInstance(
+                FactoryNamedDomainObjectContainer.class,
+                Library.class,
+                instantiator,
+                new ReflectiveNamedDomainObjectFactory<Library>(DefaultLibrary.class, project, registry)
+        ));
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Binary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Binary.java
new file mode 100644
index 0000000..1dd4a8e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Binary.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Named;
+import org.gradle.api.Project;
+import org.gradle.api.DomainObjectSet;
+
+/**
+ * Something to be created.
+ */
+public interface Binary extends Named, Buildable {
+
+    CompileSpec getSpec();
+
+    /**
+     * Returns the project that this binary is built by.
+     *
+     * @deprecated No replacement
+     */
+    @Deprecated
+    Project getProject();
+    
+    DomainObjectSet<SourceSet> getSourceSets();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompileSpec.java
new file mode 100644
index 0000000..f990edb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompileSpec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.Named;
+
+import java.io.File;
+
+/**
+ * A high level interface to the compiler, specifying what is to be compiled and how.
+ */
+public interface CompileSpec extends Named {
+
+    /**
+     * The ultimate output of the compilation.
+     */
+    File getOutputFile();
+    
+    /**
+     * Do the compile.
+     *
+     * @deprecated No replacement
+     */
+    @Deprecated
+    void compile();
+    
+    /**
+     * Configures the spec to include the source set 
+     */
+    // void from(SourceSet sourceSet);
+    /*
+        notes on from():
+        
+        The CompileSpec interface is likely to just have from(SourceSet) which the default impl of which would be to throw
+        unsupported operation exception, with implementations overriding this method to handle different kinds of source sets
+    */
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Compiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Compiler.java
new file mode 100644
index 0000000..fefdcfa
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Compiler.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.Named;
+
+/**
+ * A kind of compiler
+ */
+public interface Compiler extends Named {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompilerRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompilerRegistry.java
new file mode 100644
index 0000000..019563c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/CompilerRegistry.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * A container for compiler adapters
+ */
+public interface CompilerRegistry extends NamedDomainObjectSet<Compiler> {
+
+    /**
+     * Somehow picks what the default compiler to use is.
+     *
+     * @return null when there is no default compiler available.
+     */
+    Compiler getDefaultCompiler();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Executable.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Executable.java
new file mode 100644
index 0000000..1f64a13
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Executable.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+/**
+ * An executable binary
+ */
+public interface Executable extends Binary {
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/HeaderExportingSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/HeaderExportingSourceSet.java
new file mode 100644
index 0000000..0725aa6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/HeaderExportingSourceSet.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.file.SourceDirectorySet;
+
+/**
+ * A source set that exposes headers
+ */
+public interface HeaderExportingSourceSet extends SourceSet {
+
+    SourceDirectorySet getExportedHeaders();
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Library.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Library.java
new file mode 100644
index 0000000..73dacda
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/Library.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.file.SourceDirectorySet;
+
+/**
+ * A library
+ */
+public interface Library extends Binary {
+    LibraryCompileSpec getSpec();
+
+    SourceDirectorySet getHeaders();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/LibraryCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/LibraryCompileSpec.java
new file mode 100644
index 0000000..111e091
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/LibraryCompileSpec.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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.binaries.model;
+
+/**
+ * A high level interface to the compiler, specifying what is to be compiled and how.
+ */
+public interface LibraryCompileSpec extends CompileSpec {
+    /**
+     * <p>Returns the <i>install name</i> for the library. This is the location where this library will be installed on the target
+     * system, and where clients of this library should look for the library.
+     *
+     * <p>On Linux systems, this corresponds to the <i>soname</i> for the library.</p>
+     */
+    String getInstallName();
+
+    void setInstallName(String path);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencyCapableSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencyCapableSourceSet.java
new file mode 100644
index 0000000..d834aba
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencyCapableSourceSet.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.DomainObjectSet;
+
+/**
+ * Source set capability
+ */
+public interface NativeDependencyCapableSourceSet extends SourceSet {
+    DomainObjectSet<NativeDependencySet> getNativeDependencySets();
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencySet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencySet.java
new file mode 100644
index 0000000..5db2b81
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/NativeDependencySet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.file.FileCollection;
+
+/**
+ * Models a collection of native type dependencies.
+ */
+public interface NativeDependencySet {
+
+    FileCollection getIncludeRoots();
+    FileCollection getFiles();
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/SourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/SourceSet.java
new file mode 100644
index 0000000..6bba25a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/SourceSet.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.binaries.model;
+
+import org.gradle.api.Named;
+
+/**
+ * A generic model of a collection of source
+ */
+public interface SourceSet extends Named {
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpec.java
new file mode 100644
index 0000000..8d0734e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpec.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.binaries.model.internal;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+
+public interface BinaryCompileSpec extends org.gradle.plugins.binaries.model.CompileSpec, CompileSpec, Buildable {
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpecFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpecFactory.java
new file mode 100644
index 0000000..de9fe6d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/BinaryCompileSpecFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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.binaries.model.internal;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.plugins.binaries.model.Binary;
+
+/**
+ * This is ugly. Currently, the CompileSpec impls need access to a Compiler, so we need use this interface to provide one at construction time.
+ */
+public interface BinaryCompileSpecFactory {
+    BinaryCompileSpec create(Binary binary, Compiler<?> compiler);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileSpecFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileSpecFactory.java
new file mode 100644
index 0000000..2f010fb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileSpecFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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.binaries.model.internal;
+
+import org.gradle.plugins.binaries.model.Binary;
+
+/**
+ * Producer of compile specs
+ */
+public interface CompileSpecFactory {
+
+    /**
+     * Create a new spec to compile this binary
+     */
+    BinaryCompileSpec create(Binary binary);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.java
new file mode 100644
index 0000000..b09a809
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompileTaskAware.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.binaries.model.internal;
+
+import org.gradle.plugins.binaries.tasks.Compile;
+
+public interface CompileTaskAware {
+    void configure(Compile compileTask);
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompilerAdapter.java
new file mode 100644
index 0000000..b3726b8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/CompilerAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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.binaries.model.internal;
+
+import org.gradle.plugins.binaries.model.Binary;
+import org.gradle.plugins.binaries.model.Compiler;
+
+public interface CompilerAdapter<T extends BinaryCompileSpec> extends Compiler {
+    /**
+     * Creates a compiler which can compile the given binary. Should only be called if {@link #isAvailable()} has returned true.
+     */
+    org.gradle.api.internal.tasks.compile.Compiler<T> createCompiler(Binary binary);
+
+    /**
+     * Returns true if this compiler is available.
+     */
+    boolean isAvailable();
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/ConfigurationBasedNativeDependencySet.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/ConfigurationBasedNativeDependencySet.groovy
new file mode 100644
index 0000000..d9d46ab
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/ConfigurationBasedNativeDependencySet.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.binaries.model.internal
+
+import org.gradle.plugins.binaries.model.NativeDependencySet
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.file.FileCollection
+import org.gradle.api.artifacts.Configuration
+
+class ConfigurationBasedNativeDependencySet implements NativeDependencySet {
+
+    private final String baseName
+    final String headersConfigurationName
+    final String filesConfigurationName // files is a bad name
+    final Project project
+    private Task headerExtractionTask
+
+    ConfigurationBasedNativeDependencySet(Project project, String baseName = "main") {
+        this.baseName = baseName
+        this.headersConfigurationName = baseName + "HeaderDependencies"
+        this.filesConfigurationName = baseName + "FileDependencies"
+        this.project = project
+
+        createConfigurations()
+        initHeaderExtractionTask()
+    }
+
+    private createConfigurations() {
+        project.configurations.with {
+            create(headersConfigurationName)
+            create(filesConfigurationName)
+        }
+    }
+
+    private initHeaderExtractionTask() {
+        def headersConfiguration = getHeadersConfiguration()
+        def dir = project.file("$project.buildDir/dependency-headers/$baseName")
+        headerExtractionTask = project.task(baseName + "ExtractHeaders") {
+            inputs.files headersConfiguration
+            outputs.files { dir.listFiles() }
+            doLast {
+                headersConfiguration.each { headerZip ->
+                    project.copy {
+                        from project.zipTree(headerZip)
+                        into "$dir/${headerZip.name - '.zip'}"
+                    }
+                }
+            }
+        }
+    }
+
+    Configuration getHeadersConfiguration() {
+        project.configurations[headersConfigurationName]
+    }
+
+    FileCollection getIncludeRoots() {
+        headerExtractionTask.outputs.files
+    }
+
+    FileCollection getFiles() {
+        project.configurations[filesConfigurationName]
+    }
+
+    void add(Map dep) {
+        // hackity hack hack
+        project.dependencies {
+            def m = { classifier, ext -> [classifier: classifier, ext: ext] }
+            delegate."$headersConfigurationName"(dep + m("headers", "zip"))
+            delegate."$filesConfigurationName"(dep + m("so", "so"))
+        }
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultBinary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultBinary.java
new file mode 100644
index 0000000..64d04f0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultBinary.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.binaries.model.internal;
+
+import groovy.lang.Closure;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.plugins.binaries.model.Binary;
+import org.gradle.plugins.binaries.model.CompileSpec;
+import org.gradle.plugins.binaries.model.SourceSet;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.DeprecationLogger;
+
+public class DefaultBinary implements Binary {
+    private final String name;
+    private final ProjectInternal project;
+    private final BinaryCompileSpec spec;
+    private final DomainObjectSet<SourceSet> sourceSets;
+
+    public DefaultBinary(String name, ProjectInternal project, CompileSpecFactory specFactory) {
+        this.name = name;
+        this.project = project;
+        this.sourceSets = new DefaultDomainObjectSet<SourceSet>(SourceSet.class);
+        this.spec = specFactory.create(this);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return spec.getBuildDependencies();
+    }
+
+    public ProjectInternal getProject() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("Binary.getProject()");
+        return project;
+    }
+
+    public CompileSpec getSpec() {
+        return spec;
+    }
+
+    public DomainObjectSet<SourceSet> getSourceSets() {
+        return sourceSets;
+    }
+    
+    public CompileSpec spec(Closure closure) {
+        return ConfigureUtil.configure(closure, spec);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
new file mode 100755
index 0000000..e65b3ac
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.binaries.model.internal;
+
+import com.google.common.base.Joiner;
+import org.gradle.api.Action;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.plugins.binaries.model.Binary;
+import org.gradle.plugins.binaries.model.Compiler;
+import org.gradle.plugins.binaries.model.CompilerRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultCompilerRegistry extends DefaultNamedDomainObjectSet<Compiler> implements CompilerRegistry, CompileSpecFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCompilerRegistry.class);
+    private BinaryCompileSpecFactory specFactory;
+    private final List<Compiler> searchOrder = new ArrayList<Compiler>();
+
+    public DefaultCompilerRegistry(Instantiator instantiator) {
+        super(Compiler.class, instantiator);
+        whenObjectAdded(new Action<Compiler>() {
+            public void execute(Compiler compiler) {
+                searchOrder.add(compiler);
+            }
+        });
+        whenObjectRemoved(new Action<Compiler>() {
+            public void execute(Compiler compiler) {
+                searchOrder.remove(compiler);
+            }
+        });
+    }
+
+    public List<Compiler> getSearchOrder() {
+        return searchOrder;
+    }
+
+    public void setSpecFactory(BinaryCompileSpecFactory specFactory) {
+        this.specFactory = specFactory;
+    }
+
+    public CompilerAdapter<BinaryCompileSpec> getDefaultCompiler() {
+        for (Compiler compiler : searchOrder) {
+            CompilerAdapter<BinaryCompileSpec> adapter = (CompilerAdapter<BinaryCompileSpec>) compiler;
+            if (adapter.isAvailable()) {
+                return adapter;
+            }
+        }
+        return null;
+    }
+
+    public BinaryCompileSpec create(final Binary binary) {
+        org.gradle.api.internal.tasks.compile.Compiler<BinaryCompileSpec> lazyCompiler = new LazyCompiler(binary);
+        return specFactory.create(binary, lazyCompiler);
+    }
+
+    private class LazyCompiler implements org.gradle.api.internal.tasks.compile.Compiler<BinaryCompileSpec> {
+        private final Binary binary;
+
+        public LazyCompiler(Binary binary) {
+            this.binary = binary;
+        }
+
+        public WorkResult execute(BinaryCompileSpec spec) {
+            CompilerAdapter<BinaryCompileSpec> compiler = getDefaultCompiler();
+            if (compiler == null) {
+                throw new IllegalStateException(String.format("No compiler is available to compile %s. Searched for %s.", binary, Joiner.on(", ").join(searchOrder)));
+            }
+            LOGGER.info("Using " + compiler + " to compile " + binary);
+            return compiler.createCompiler(binary).execute(spec);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultExecutable.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultExecutable.java
new file mode 100755
index 0000000..e7a4f73
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultExecutable.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.binaries.model.internal;
+
+import org.gradle.plugins.binaries.model.Executable;
+
+import org.gradle.api.internal.project.ProjectInternal;
+
+public class DefaultExecutable extends DefaultBinary implements Executable {
+    public DefaultExecutable(String name, ProjectInternal project, CompileSpecFactory specFactory) {
+        super(name, project, specFactory);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("executable '%s'", getName());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultLibrary.java
new file mode 100755
index 0000000..fb15888
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultLibrary.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.binaries.model.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.plugins.binaries.model.HeaderExportingSourceSet;
+import org.gradle.plugins.binaries.model.Library;
+import org.gradle.plugins.binaries.model.LibraryCompileSpec;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultLibrary extends DefaultBinary implements Library {
+    private final DefaultSourceDirectorySet headers;
+
+    public DefaultLibrary(String name, ProjectInternal project, CompileSpecFactory specFactory) {
+        super(name, project, specFactory);
+        this.headers = new DefaultSourceDirectorySet("headers", String.format("Exported headers for native library '%s'", name), project.getFileResolver());
+
+        initExportedHeaderTracking();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("library '%s'", getName());
+    }
+
+    public LibraryCompileSpec getSpec() {
+        return (LibraryCompileSpec) super.getSpec();
+    }
+
+    public SourceDirectorySet getHeaders() {
+        return headers;
+    }
+
+    private void initExportedHeaderTracking() {
+        // TODO - headers.srcDirs() should allow a Callable<SourceDirectorySet> for lazy calculation
+        final DomainObjectSet<HeaderExportingSourceSet> headerExportingSourceSets = getSourceSets().withType(HeaderExportingSourceSet.class);
+        headerExportingSourceSets.all(new Action<HeaderExportingSourceSet>() {
+            public void execute(HeaderExportingSourceSet headerExportingSourceSet) {
+                updateHeaderDirs(headerExportingSourceSets, headers);
+            }
+        });
+        headerExportingSourceSets.whenObjectRemoved(new Action<HeaderExportingSourceSet>() {
+            public void execute(HeaderExportingSourceSet headerExportingSourceSet) {
+                updateHeaderDirs(headerExportingSourceSets, headers);
+            }
+        });
+    }
+
+    private void updateHeaderDirs(DomainObjectSet<HeaderExportingSourceSet> sourceSets, DefaultSourceDirectorySet headers) {
+        List<SourceDirectorySet> headerDirs = new ArrayList<SourceDirectorySet>();
+        for (HeaderExportingSourceSet sourceSet : sourceSets) {
+            headerDirs.add(sourceSet.getExportedHeaders());
+        }
+        headers.setSrcDirs(headerDirs);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/package-info.java
new file mode 100644
index 0000000..75ff69f
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Implementations of the native model classes.
+ */
+package org.gradle.plugins.binaries.model.internal;
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/package-info.java
new file mode 100644
index 0000000..73d2a48
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes that model aspects of C++ projects.
+ */
+package org.gradle.plugins.binaries.model;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/package-info.java
new file mode 100644
index 0000000..a260db0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+ 
+/**
+ * Provides the binaries container and some generic model elements
+ */
+package org.gradle.plugins.binaries;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/Compile.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/Compile.groovy
new file mode 100644
index 0000000..8c5ea2d
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/Compile.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.binaries.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.tasks.TaskAction
+import org.gradle.plugins.binaries.model.CompileSpec
+
+class Compile extends DefaultTask {
+    CompileSpec spec
+    Compiler compiler
+
+    @TaskAction
+    void compile() {
+        def result = compiler.execute(spec)
+        didWork = result.didWork
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/package-info.java
new file mode 100644
index 0000000..8a2cad0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Tasks for building native projects.
+ */
+package org.gradle.plugins.binaries.tasks;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExeConventionPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExeConventionPlugin.groovy
new file mode 100644
index 0000000..80320d8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExeConventionPlugin.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.cpp
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
+
+class CppExeConventionPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.plugins.apply(CppPlugin)
+        
+        project.with {
+            cpp {
+                sourceSets {
+                    main {}
+                }
+            }
+            executables {
+                main {
+                    spec.baseName = project.name
+                    sourceSets << project.cpp.sourceSets.main
+                }
+            }
+            
+            def exeArtifact = new DefaultPublishArtifact(
+                archivesBaseName, // name
+                "exe", // ext
+                "exe", // type
+                null, // classifier
+                null, // date
+
+                // needs to be more general and not peer into the spec
+                executables.main.spec.outputFile,
+                executables.main
+            )
+
+            extensions.getByType(DefaultArtifactPublicationSet).addCandidate(exeArtifact)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
new file mode 100644
index 0000000..9d66019
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.cpp;
+
+import groovy.lang.Closure;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.internal.FactoryNamedDomainObjectContainer;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.ReflectiveNamedDomainObjectFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.plugins.cpp.internal.DefaultCppSourceSet;
+
+/**
+ * Adds a source set container.
+ */
+public class CppExtension {
+
+    final private NamedDomainObjectContainer<CppSourceSet> sourceSets;
+
+    public CppExtension(final ProjectInternal project) {
+        Instantiator instantiator = project.getServices().get(Instantiator.class);
+        sourceSets = instantiator.newInstance(
+                FactoryNamedDomainObjectContainer.class,
+                CppSourceSet.class,
+                instantiator,
+                new ReflectiveNamedDomainObjectFactory<CppSourceSet>(DefaultCppSourceSet.class, project)
+        );
+    }
+
+    public NamedDomainObjectContainer<CppSourceSet> sourceSets(Closure closure) {
+        return sourceSets.configure(closure);
+    }
+    
+    public NamedDomainObjectContainer<CppSourceSet> getSourceSets() {
+        return sourceSets;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppLibConventionPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppLibConventionPlugin.groovy
new file mode 100644
index 0000000..9eacc50
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppLibConventionPlugin.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.cpp
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+import org.gradle.api.tasks.bundling.Zip
+
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
+
+class CppLibConventionPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.plugins.apply(CppPlugin)
+
+        project.with {
+            cpp {
+                sourceSets {
+                    main {}
+                }
+            }
+            libraries {
+                main {
+                    sourceSets << project.cpp.sourceSets.main
+                    spec.baseName = project.name
+                }
+            }
+
+            def libArtifact = new DefaultPublishArtifact(
+                archivesBaseName, // name
+                "so", // ext
+                "so", // type
+                "so", // classifier
+                null, // date
+
+                // needs to be more general and not peer into the spec
+                libraries.main.spec.outputFile,
+                libraries.main
+            )
+
+            task("assembleHeaders", type: Zip) {
+                from libraries.main.headers
+                classifier = "headers"
+            }
+
+            def headerArtifact = new ArchivePublishArtifact(assembleHeaders)
+
+            extensions.getByType(DefaultArtifactPublicationSet).addCandidate(libArtifact)
+            extensions.getByType(DefaultArtifactPublicationSet).addCandidate(headerArtifact)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy
new file mode 100644
index 0000000..9aa3b4a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppPlugin.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2011 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.cpp
+
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.api.tasks.Sync
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.plugins.binaries.BinariesPlugin
+import org.gradle.plugins.binaries.model.Binary
+import org.gradle.plugins.binaries.model.Executable
+import org.gradle.plugins.binaries.model.internal.DefaultCompilerRegistry
+import org.gradle.plugins.binaries.tasks.Compile
+import org.gradle.plugins.cpp.gpp.GppCompilerPlugin
+import org.gradle.plugins.cpp.gpp.internal.GppCompileSpecFactory
+import org.gradle.plugins.cpp.msvcpp.MicrosoftVisualCppPlugin
+import org.gradle.util.GUtil
+
+class CppPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(BinariesPlugin)
+        project.plugins.apply(MicrosoftVisualCppPlugin)
+        project.plugins.apply(GppCompilerPlugin)
+        project.extensions.create("cpp", CppExtension, project)
+
+        project.extensions.getByType(DefaultCompilerRegistry).specFactory = new GppCompileSpecFactory(project)
+
+        // Defaults for all cpp source sets
+        project.cpp.sourceSets.all { sourceSet ->
+            sourceSet.source.srcDir "src/${sourceSet.name}/cpp"
+            sourceSet.exportedHeaders.srcDir "src/${sourceSet.name}/headers"
+        }
+
+        // Defaults for all executables
+        project.executables.all { executable ->
+            configureExecutable(project, executable)
+        }
+
+        // Defaults for all libraries
+        project.libraries.all { library ->
+            configureBinary(project, library)
+        }
+    }
+
+    def configureExecutable(ProjectInternal project, Executable executable) {
+        configureBinary(project, executable)
+
+        def baseName = GUtil.toCamelCase(executable.name).capitalize()
+        project.task("install${baseName}", type: Sync) {
+            description = "Installs a development image of $executable"
+            into { project.file("${project.buildDir}/install/$executable.name") }
+            dependsOn executable
+            if (OperatingSystem.current().windows) {
+                from { executable.spec.outputFile }
+                from { executable.sourceSets*.libs*.spec*.outputFile }
+            } else {
+                into("lib") {
+                    from { executable.spec.outputFile }
+                    from { executable.sourceSets*.libs*.spec*.outputFile }
+                }
+                doLast {
+                    def script = new File(destinationDir, executable.spec.outputFile.name)
+                    script.text = """
+#/bin/sh
+APP_BASE_NAME=`dirname "\$0"`
+export DYLD_LIBRARY_PATH="\$APP_BASE_NAME/lib"
+export LD_LIBRARY_PATH="\$APP_BASE_NAME/lib"
+exec "\$APP_BASE_NAME/lib/${executable.spec.outputFile.name}" \"\$@\"
+                    """
+                    ant.chmod(perm: 'u+x', file: script)
+                }
+            }
+        }
+    }
+
+    def configureBinary(ProjectInternal project, Binary binary) {
+        def baseName = GUtil.toCamelCase(binary.name).capitalize()
+
+        def task = project.task("compile${baseName}", type: Compile) {
+            description = "Compiles and links $binary"
+            group = BasePlugin.BUILD_GROUP
+        }
+        binary.spec.configure(task)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppSourceSet.java
new file mode 100644
index 0000000..0d0d066
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppSourceSet.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.cpp;
+
+import org.gradle.plugins.binaries.model.Library;
+import org.gradle.plugins.binaries.model.HeaderExportingSourceSet;
+import org.gradle.plugins.binaries.model.NativeDependencyCapableSourceSet;
+
+import org.gradle.api.Named;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.file.SourceDirectorySet;
+
+import groovy.lang.Closure;
+
+import java.util.Map;
+
+/**
+ * A representation of a unit of cpp source
+ */
+public interface CppSourceSet extends HeaderExportingSourceSet, NativeDependencyCapableSourceSet, Named {
+
+    /**
+     * The headers.
+     */
+    SourceDirectorySet getExportedHeaders();
+
+    /**
+     * The headers.
+     */
+    CppSourceSet exportedHeaders(Closure closure);
+
+    /**
+     * The source.
+     */
+    SourceDirectorySet getSource();
+
+    /**
+     * The source.
+     */
+    CppSourceSet source(Closure closure);
+
+    /**
+     * Libs this source set requires
+     */
+    DomainObjectSet<Library> getLibs();
+    
+    /**
+     * Add a dependency to this source set
+     */
+    void dependency(Map<?, ?> dep);
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy
new file mode 100644
index 0000000..d17dc4c
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/CdtIdePlugin.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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.cpp.cdt
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.tasks.Delete
+
+import org.gradle.plugins.cpp.cdt.model.ProjectSettings
+import org.gradle.plugins.cpp.cdt.model.ProjectDescriptor
+import org.gradle.plugins.cpp.cdt.model.CprojectSettings
+import org.gradle.plugins.cpp.cdt.model.CprojectDescriptor
+
+import org.gradle.plugins.cpp.cdt.tasks.GenerateMetadataFileTask
+
+class CdtIdePlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.apply(plugin: "binaries")
+        def metadataFileTasks = [addCreateProjectDescriptor(project), addCreateCprojectDescriptor(project)]
+
+        project.task("cleanCdt", type: Delete) {
+            delete metadataFileTasks*.outputs*.files
+        }
+
+        project.task("cdt", dependsOn: metadataFileTasks)
+    }
+
+    private addCreateProjectDescriptor(Project project) {
+        project.task("cdtProject", type: GenerateMetadataFileTask) {
+            inputFile = project.file(".project")
+            outputFile = project.file(".project")
+            factory { new ProjectDescriptor() }
+            onConfigure { new ProjectSettings(name: project.name).applyTo(it) }
+        }
+    }
+
+    private addCreateCprojectDescriptor(Project project) {
+        project.task("cdtCproject", type: GenerateMetadataFileTask) { task ->
+            
+            [project.executables, project.libraries]*.all { binary ->
+                if (binary.name == "main") {
+                    task.settings = new CprojectSettings(binary, project)
+                }
+            }
+            
+            doFirst {
+                if (task.settings == null) {
+                    throw new InvalidUserDataException("There is neither a main binary or library")
+                }
+            }
+            
+            inputs.files { task.settings.includeRoots }
+            inputFile = project.file(".cproject")
+            outputFile = project.file(".cproject")
+            factory { new CprojectDescriptor() }
+            onConfigure { descriptor ->
+                task.settings.applyTo(descriptor)
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
new file mode 100644
index 0000000..bd9dd51
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectDescriptor.groovy
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 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.cpp.cdt.model
+
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+
+/**
+ * The actual .cproject descriptor file.
+ */
+class CprojectDescriptor extends XmlPersistableConfigurationObject {
+
+    private static final boolean LINUX_NOT_MACOS = true
+    
+    static public final String GNU_COMPILER_TOOL_ID_PREFIX = "cdt.managedbuild.tool.gnu.cpp.compiler"
+    static public final String GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX = "gnu.cpp.compiler.option.include.paths"
+
+    // linux
+    static public final String GNU_LINKER_TOOL_ID_PREFIX = LINUX_NOT_MACOS ? "cdt.managedbuild.tool.gnu.cpp.linker" : "cdt.managedbuild.tool.macosx.cpp.linker.macosx"
+    static public final String GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX = LINUX_NOT_MACOS ? "gnu.cpp.link.option.userobjs" : "macosx.cpp.link.option.userobjs"
+
+    CprojectDescriptor() {
+        super(new XmlTransformer())
+    }
+
+    protected String getDefaultResourceName() {
+        LINUX_NOT_MACOS ? 'defaultCproject-linux.xml' : 'defaultCproject-macos.xml'
+    }
+
+    NodeList getConfigurations() {
+        new NodeList(xml.storageModule.cconfiguration.storageModule.findAll { it. at moduleId == "cdtBuildSystem" }.collect { it.configuration[0] })
+    }
+
+    NodeList getRootToolChains() {
+        new NodeList(configurations.folderInfo.findAll { it. at resourcePath == "" }).toolChain
+    }
+
+    NodeList getRootCppCompilerTools() {
+        new NodeList(rootToolChains.tool.findAll { isGnuCompilerTool(it) })
+    }
+
+    NodeList getRootCppLinkerTools() {
+        new NodeList(rootToolChains.tool.findAll { isGnuLinkerTool(it) })
+    }
+
+    boolean isGnuCompilerTool(Node node) {
+        node.name() == "tool" && node. at id.startsWith(GNU_COMPILER_TOOL_ID_PREFIX)
+    }
+
+    boolean isGnuLinkerTool(Node node) {
+        node.name() == "tool" && node. at id.startsWith(GNU_LINKER_TOOL_ID_PREFIX)
+    }
+
+    Node getOrCreateIncludePathsOption(compilerToolNode) {
+        if (!isGnuCompilerTool(compilerToolNode)) {
+            throw new IllegalArgumentException("Arg must be a gnu compiler tool def, was $compilerToolNode")
+        }
+
+        def includePathsOption = compilerToolNode.option.find { it. at id.startsWith(GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX) }
+        if (!includePathsOption) {
+            includePathsOption = compilerToolNode.appendNode(
+                "option", [
+                    id: createId(GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX),
+                    superClass: GNU_COMPILER_TOOL_INCLUDE_PATHS_OPTION_PREFIX,
+                    valueType: "includePath"
+                ]
+            )
+        }
+
+        includePathsOption
+    }
+
+    Node getOrCreateLibsOption(linkerToolNode) {
+        if (!isGnuLinkerTool(linkerToolNode)) {
+            throw new IllegalArgumentException("Arg must be a gnu linker tool def, was $linkerToolNode")
+        }
+
+        def libsOption = linkerToolNode.option.find { it. at id.startsWith(GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX) }
+        if (!libsOption) {
+            libsOption = linkerToolNode.appendNode(
+                "option", [
+                    id: createId(GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX),
+                    superClass: GNU_LINKER_TOOL_LIBS_PATHS_OPTION_PREFIX,
+                    valueType: "userObjs"
+                ]
+            )
+        }
+
+        libsOption
+    }
+
+    String createId(String prefix) {
+        prefix + "." + new java.text.SimpleDateFormat("yyMMddHHmmssS").format(new Date())
+    }
+
+    protected void store(Node xml) {
+        transformAction {
+            StringBuilder xmlString = it.asString()
+            xmlString.insert(xmlString.indexOf("\n") + 1, "<?fileVersion 4.0.0?>\n")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettings.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettings.groovy
new file mode 100644
index 0000000..c39a8bb
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettings.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 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.cpp.cdt.model
+
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.plugins.cpp.CppSourceSet
+import org.gradle.plugins.binaries.model.*
+
+/**
+ * Exposes a more logical view of the actual .cproject descriptor file
+ */
+class CprojectSettings {
+
+    Binary binary
+    private final ConfigurableFileCollection includeRoots
+    private final ConfigurableFileCollection libs
+
+    CprojectSettings(Binary binary, ProjectInternal project) {
+        this.binary = binary
+        includeRoots = project.files()
+        libs = project.files()
+
+        binary.sourceSets.withType(HeaderExportingSourceSet).all {
+            includeRoots.builtBy(it.exportedHeaders) // have to manually add because we use srcDirs in from, not the real collection
+            includeRoots.from(it.exportedHeaders.srcDirs)
+        }
+
+        binary.sourceSets.withType(NativeDependencyCapableSourceSet).all {
+            it.nativeDependencySets.all {
+                this.includeRoots.from(it.includeRoots)
+                this.libs.from(it.files)
+            }
+        }
+
+        binary.sourceSets.withType(CppSourceSet).all { sourceSet ->
+            sourceSet.libs.all { lib ->
+                this.libs.from(lib.spec.outputFile)
+                this.libs.builtBy(lib.spec.task)
+                this.includeRoots.from(lib.headers.srcDirs)
+            }
+        }
+    }
+
+    void applyTo(CprojectDescriptor descriptor) {
+        if (binary) {
+            applyBinaryTo(descriptor)
+        } else {
+            throw new IllegalStateException("no binary set")
+        }
+    }
+
+    private applyBinaryTo(CprojectDescriptor descriptor) {
+        descriptor.rootCppCompilerTools.each { compiler ->
+            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
+            new LinkedList(includePathsOption.children()).each { includePathsOption.remove(it) }
+            includeRoots.each { includeRoot ->
+                includePathsOption.appendNode("listOptionValue", [builtIn: "false", value: includeRoot.absolutePath])
+            }
+        }
+
+        descriptor.rootCppLinkerTools.each { linker ->
+            def libsOption = descriptor.getOrCreateLibsOption(linker)
+            new LinkedList(libsOption.children()).each { libsOption.remove(it) }
+            libs.each { lib ->
+                libsOption.appendNode("listOptionValue", [builtIn: "false", value: lib.absolutePath])
+            }
+        }
+
+        def extension = binary.spec.extension ?: ""
+        def type 
+        if (binary instanceof Library) {
+            type = "org.eclipse.cdt.build.core.buildArtefactType.sharedLib"
+        } else if (binary instanceof Executable) {
+            type = "org.eclipse.cdt.build.core.buildArtefactType.exe"
+        } else {
+            throw new IllegalStateException("The binary $binary is of a type that we don't know about")
+        }
+        
+        descriptor.configurations.each { conf ->
+            conf. at buildArtefactType = type
+            conf. at artifactExtension = extension
+            def buildPropsPairs = conf. at buildProperties.split(",")
+            def buildProps = [:]
+            buildPropsPairs.each {
+                def parts = it.split("=", 2)
+                buildProps[parts[0]] = parts[1]
+            }
+            buildProps["org.eclipse.cdt.build.core.buildArtefactType"] = type
+            buildPropsPairs = buildProps.collect { k, v -> "$k=$v"}
+            conf. at buildProperties = buildPropsPairs.join(",")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
new file mode 100644
index 0000000..3b4f4f4
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptor.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.cpp.cdt.model
+
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+
+/**
+ * The actual .project descriptor file.
+ */
+class ProjectDescriptor extends XmlPersistableConfigurationObject {
+
+    ProjectDescriptor() {
+        super(new XmlTransformer())
+    }
+
+    protected String getDefaultResourceName() {
+        'defaultProject.xml'
+    }
+
+    Node getOrCreate(String name) {
+        getOrCreate(xml, name)
+    }
+
+    Node findBuildCommand(Closure predicate) {
+        xml.buildSpec[0].buildCommand.find(predicate)
+    }
+
+    Node getOrCreate(Node parent, String name) {
+        def node = parent.get(name)
+        node ? node.first() : parent.appendNode(name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectSettings.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectSettings.groovy
new file mode 100644
index 0000000..6a8726a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/model/ProjectSettings.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.cpp.cdt.model
+
+/**
+ * Gradle model element, the configurable parts of the .project file.
+ */
+class ProjectSettings {
+    String name
+
+    /**
+     * Apply this logical model to the physical descriptor
+     */
+    void applyTo(ProjectDescriptor descriptor) {
+        descriptor.getOrCreate("name").value = name
+
+        // not anywhere close to right at all, very hardcoded.
+        def genMakeBuilderBuildCommand = descriptor.findBuildCommand { it.name[0].text() == "org.eclipse.cdt.managedbuilder.core.genmakebuilder" }
+        if (genMakeBuilderBuildCommand) {
+            def dict = genMakeBuilderBuildCommand.arguments[0].dictionary.find { it.key[0].text() == "org.eclipse.cdt.make.core.buildLocation" }
+            if (dict) {
+                dict.value[0].value = "\${workspace_loc:/$name/Debug}"
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
new file mode 100644
index 0000000..b34fa54
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/cdt/tasks/GenerateMetadataFileTask.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.cpp.cdt.tasks
+
+import org.gradle.internal.Factory
+import org.gradle.listener.ActionBroadcast
+import org.gradle.plugins.cpp.cdt.model.CprojectSettings
+import org.gradle.plugins.ide.api.GeneratorTask
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator
+
+class GenerateMetadataFileTask<T extends PersistableConfigurationObject> extends GeneratorTask<T> {
+
+    Factory<T> factory
+    ActionBroadcast<T> configures = new ActionBroadcast<T>()
+    CprojectSettings settings
+
+    GenerateMetadataFileTask() {
+        generator = new PersistableConfigurationObjectGenerator() {
+            public create() {
+                GenerateMetadataFileTask.this.factory.create();
+            }
+
+            public void configure(object) {
+                GenerateMetadataFileTask.this.configures.execute(object);
+            }
+        }
+    }
+
+    void factory(Closure factory) {
+        this.factory = factory as Factory
+    }
+
+    void onConfigure(Closure configure) {
+        configures.add(configure)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/AgainstLibrary.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/AgainstLibrary.java
new file mode 100644
index 0000000..aba90f3
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/AgainstLibrary.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.cpp.compiler.capability;
+
+import org.gradle.plugins.binaries.model.Library;
+
+/**
+ * Can compile against libraries
+ */
+public interface AgainstLibrary {
+    
+    /**
+     * Compile against the given libs (collection must be live, i.e. changes respected)
+     */
+    void libs(Iterable<Library> libs);
+    
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/CompilesCpp.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/CompilesCpp.java
new file mode 100644
index 0000000..1165fb8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/CompilesCpp.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.cpp.compiler.capability;
+
+import org.gradle.plugins.cpp.CppSourceSet;
+
+/**
+ * Capable of compiling cpp source from a cpp source set
+ */
+public interface CompilesCpp {
+
+    /**
+     * Use the source set
+     */
+    void from(CppSourceSet sourceSet);
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/StandardCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/StandardCppCompiler.java
new file mode 100644
index 0000000..eee8d78
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/StandardCppCompiler.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.cpp.compiler.capability;
+
+/**
+ * Wraps up the standard capabilities you'd expect a cpp compiler to have
+ */
+public interface StandardCppCompiler extends AgainstLibrary, CompilesCpp {
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/package-info.java
new file mode 100644
index 0000000..bfb0501
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/capability/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Interfaces for compiler capabilities for cpp
+ */
+package org.gradle.plugins.cpp.compiler.capability;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgCollector.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgCollector.java
new file mode 100644
index 0000000..56e1c2a
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgCollector.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+public interface ArgCollector {
+    
+    ArgCollector args(Object... args);
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriter.java
new file mode 100755
index 0000000..cd1b861
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import org.gradle.api.Transformer;
+
+import java.io.PrintWriter;
+import java.util.regex.Pattern;
+
+public class ArgWriter implements ArgCollector {
+    private static final Pattern WHITESPACE = Pattern.compile("\\s");
+    private final PrintWriter writer;
+    private final boolean backslashEscape;
+
+    private ArgWriter(PrintWriter writer, boolean backslashEscape) {
+        this.writer = writer;
+        this.backslashEscape = backslashEscape;
+    }
+
+    public static ArgWriter unixStyle(PrintWriter writer) {
+        return new ArgWriter(writer, true);
+    }
+
+    public static Transformer<ArgWriter, PrintWriter> unixStyleFactory() {
+        return new Transformer<ArgWriter, PrintWriter>() {
+            public ArgWriter transform(PrintWriter original) {
+                return unixStyle(original);
+            }
+        };
+    }
+
+    public static ArgWriter windowsStyle(PrintWriter writer) {
+        return new ArgWriter(writer, false);
+    }
+
+    public static Transformer<ArgWriter, PrintWriter> windowsStyleFactory() {
+        return new Transformer<ArgWriter, PrintWriter>() {
+            public ArgWriter transform(PrintWriter original) {
+                return windowsStyle(original);
+            }
+        };
+    }
+
+    /**
+     * Writes a set of args on a single line, escaping and quoting as required.
+     */
+    public ArgWriter args(Object... args) {
+        for (int i = 0; i < args.length; i++) {
+            Object arg = args[i];
+            if (i > 0) {
+                writer.print(' ');
+            }
+            String str = arg.toString();
+            if (backslashEscape) {
+                str = str.replace("\\", "\\\\").replace("\"", "\\\"");
+            }
+            if (WHITESPACE.matcher(str).find()) {
+                writer.print('\"');
+                writer.print(str);
+                writer.print('\"');
+            } else {
+                writer.print(str);
+            }
+        }
+        writer.println();
+        return this;
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLinCppCompilerArgumentsApplicator.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLinCppCompilerArgumentsApplicator.java
new file mode 100644
index 0000000..6e5b3d0
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLinCppCompilerArgumentsApplicator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+
+public class CommandLinCppCompilerArgumentsApplicator<T extends GppCompileSpec> implements Transformer<Iterable<String>, T> {
+
+    private final CompileSpecToArguments<T> toArguments;
+
+    public CommandLinCppCompilerArgumentsApplicator(CompileSpecToArguments<T> toArguments) {
+        this.toArguments = toArguments;
+    }
+
+    public Iterable<String> transform(T spec) {
+        ListArgCollector collector = new ListArgCollector();
+        toArguments.collectArguments(spec, collector);
+        return collector.getFlattened();
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
new file mode 100755
index 0000000..cce9294
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import groovy.lang.Closure;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.tasks.compile.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.Factory;
+import org.gradle.plugins.cpp.internal.CppCompileSpec;
+import org.gradle.process.internal.ExecAction;
+
+import java.io.File;
+
+public class CommandLineCppCompiler<T extends CppCompileSpec> implements CppCompiler<T> {
+    private final File executable;
+    private final Factory<ExecAction> execActionFactory;
+    private final Transformer<Iterable<String>, T> toArguments;
+
+    public CommandLineCppCompiler(File executable, Factory<ExecAction> execActionFactory, Transformer<Iterable<String>, T> toArguments) {
+        this.executable = executable;
+        this.execActionFactory = execActionFactory;
+        this.toArguments = toArguments;
+    }
+
+    public WorkResult execute(T spec) {
+        File workDir = spec.getWorkDir();
+
+        ensureDirsExist(workDir, spec.getOutputFile().getParentFile());
+
+        ExecAction compiler = execActionFactory.create();
+        compiler.executable(executable);
+        compiler.workingDir(workDir);
+
+        compiler.args(toArguments.transform(spec));
+
+        // Apply all of the settings
+        for (Closure closure : spec.getSettings()) {
+            closure.call(compiler);
+        }
+
+        compiler.execute();
+        return new SimpleWorkResult(true);
+    }
+
+    private void ensureDirsExist(File... dirs) {
+        for (File dir : dirs) {
+            dir.mkdirs();
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerAdapter.java
new file mode 100644
index 0000000..24e5dfa
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.plugins.binaries.model.internal.CompilerAdapter;
+import org.gradle.plugins.cpp.internal.CppCompileSpec;
+import org.gradle.process.internal.ExecAction;
+
+import java.io.File;
+
+abstract public class CommandLineCppCompilerAdapter<T extends CppCompileSpec> implements CompilerAdapter<T> {
+
+    private final File executable;
+    private final Factory<ExecAction> execActionFactory;
+    private OperatingSystem operatingSystem;
+
+    protected CommandLineCppCompilerAdapter(String executableName, OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
+        this(operatingSystem.findInPath(executableName), operatingSystem, execActionFactory);
+    }
+
+    protected CommandLineCppCompilerAdapter(File executable, OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
+        this.executable = executable;
+        this.operatingSystem = operatingSystem;
+        this.execActionFactory = execActionFactory;
+    }
+
+    protected File getExecutable() {
+        return executable;
+    }
+
+    protected Factory<ExecAction> getExecActionFactory() {
+        return execActionFactory;
+    }
+
+    public OperatingSystem getOperatingSystem() {
+        return operatingSystem;
+    }
+
+    public boolean isAvailable() {
+        return executable != null && executable.exists();
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
new file mode 100644
index 0000000..227d703
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.plugins.cpp.internal.CppCompileSpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+
+public class CommandLineCppCompilerArgumentsToOptionFile<T extends CppCompileSpec> implements Transformer<Iterable<String>, T> {
+
+    private final Transformer<ArgWriter, PrintWriter> argWriterFactory;
+    private final CompileSpecToArguments<T> toArguments;
+
+    public CommandLineCppCompilerArgumentsToOptionFile(Transformer<ArgWriter, PrintWriter> argWriterFactory, CompileSpecToArguments<T> toArguments) {
+        this.argWriterFactory = argWriterFactory;
+        this.toArguments = toArguments;
+    }
+
+    public Iterable<String> transform(T spec) {
+        File optionsFile = new File(spec.getWorkDir(), "compiler-options.txt");
+        try {
+            PrintWriter writer = new PrintWriter(optionsFile);
+            ArgWriter argWriter = argWriterFactory.transform(writer);
+            try {
+                toArguments.collectArguments(spec, argWriter);
+            } finally {
+                writer.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Could not write compiler options file '%s'.", optionsFile.getAbsolutePath()), e);
+        }
+
+        return Collections.singletonList(String.format("@%s", optionsFile.getAbsolutePath()));
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CompileSpecToArguments.java
new file mode 100644
index 0000000..d647c3b
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CompileSpecToArguments.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import org.gradle.plugins.cpp.internal.CppCompileSpec;
+
+public interface CompileSpecToArguments<T extends CppCompileSpec> {
+    
+    public void collectArguments(T spec, ArgCollector collector);
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CppCompiler.java
new file mode 100644
index 0000000..b4347c2
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CppCompiler.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.plugins.cpp.internal.CppCompileSpec;
+
+public interface CppCompiler<T extends CppCompileSpec> extends Compiler<T> {
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ListArgCollector.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ListArgCollector.java
new file mode 100644
index 0000000..a23a342
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ListArgCollector.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ListArgCollector implements ArgCollector {
+    
+    private final List<List<String>> groups = new LinkedList<List<String>>();
+
+    public ArgCollector args(Object... args) {
+        List<String> group = new ArrayList<String>(args.length);
+        for (Object arg : args) {
+            group.add(arg.toString());
+        }
+        groups.add(group);
+        
+        return this;
+    }
+    
+    public List<List<String>> getGroups() {
+        return groups;
+    }
+    
+    public List<String> getFlattened() {
+        List<String> flattened = new LinkedList<String>();
+        for (List<String> group : groups) {
+            for (String arg : group) {
+                flattened.add(arg);
+            }
+        }
+
+        return flattened;
+    }
+    
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
new file mode 100755
index 0000000..df33e05
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpec.groovy
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2011 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.cpp.gpp
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.plugins.binaries.model.Binary
+import org.gradle.plugins.binaries.model.CompileSpec
+import org.gradle.plugins.binaries.model.Library
+import org.gradle.plugins.binaries.model.internal.CompileTaskAware
+import org.gradle.plugins.binaries.tasks.Compile
+import org.gradle.plugins.cpp.CppSourceSet
+import org.gradle.plugins.cpp.compiler.capability.StandardCppCompiler
+import org.gradle.plugins.cpp.internal.CppCompileSpec
+import org.gradle.util.DeprecationLogger
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.internal.tasks.DefaultTaskDependency
+
+class GppCompileSpec implements CompileSpec, StandardCppCompiler, CompileTaskAware, CppCompileSpec {
+    Binary binary
+
+    Compile task
+    List<Closure> settings = []
+
+    String outputFileName
+    String baseName
+    String extension
+    private final Compiler<? super GppCompileSpec> compiler
+    private final ProjectInternal project
+    private final ConfigurableFileCollection libs
+    private final ConfigurableFileCollection includes
+    private final ConfigurableFileCollection source
+
+    GppCompileSpec(Binary binary, Compiler<? super GppCompileSpec> compiler, ProjectInternal project) {
+        this.binary = binary
+        this.compiler = compiler
+        this.project = project
+        libs = project.files()
+        includes = project.files()
+        source = project.files()
+    }
+
+    void configure(Compile task) {
+        this.task = task
+        task.spec = this
+        task.compiler = compiler
+
+        task.onlyIf { !task.inputs.files.empty }
+        task.outputs.file { getOutputFile() }
+
+        // problem: will break if a source set is removed
+        binary.sourceSets.withType(CppSourceSet).all { from(it) }
+    }
+
+    String getName() {
+        binary.name
+    }
+
+    TaskDependency getBuildDependencies() {
+        return new DefaultTaskDependency().add(task)
+    }
+
+    File getWorkDir() {
+        project.file "$project.buildDir/compileWork/$name"
+    }
+
+    Iterable<File> getLibs() {
+        return libs
+    }
+
+    Iterable<File> getIncludeRoots() {
+        return includes
+    }
+
+    Iterable<File> getSource() {
+        return source
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    Compile getTask() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.getTask()")
+        return task
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    void setTask(Compile task) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.setTask()")
+        this.task = task
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    String getExtension() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.getExtension()")
+        return extension
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    void setExtension(String extension) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.setExtension()")
+        this.extension = extension
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    void setBinary(Binary binary) {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("GppCompileSpec.setBinary()")
+        this.binary = binary
+    }
+
+    File getOutputFile() {
+        project.file "$project.buildDir/binaries/${getOutputFileName()}"
+    }
+
+    String getOutputFileName() {
+        if (outputFileName) {
+            return outputFileName
+        } else if (extension) {
+            return "${getBaseName()}.${extension}"
+        } else {
+            return getDefaultOutputFileName()
+        }
+    }
+
+    protected String getDefaultOutputFileName() {
+        return OperatingSystem.current().getExecutableName(getBaseName())
+    }
+
+    String getBaseName() {
+        baseName ?: name
+    }
+
+    void setting(Closure closure) {
+        settings << closure
+    }
+
+    void from(CppSourceSet sourceSet) {
+        includes sourceSet.exportedHeaders
+        source sourceSet.source
+        libs sourceSet.libs
+
+        sourceSet.nativeDependencySets.all { deps ->
+            includes deps.includeRoots
+            source deps.files
+        }
+    }
+
+    void includes(SourceDirectorySet dirs) {
+        task.inputs.files dirs
+        includes.from({dirs.srcDirs})
+    }
+
+    // special filecollection version because filecollection may be buildable
+    void includes(FileCollection includeRoots) {
+        task.inputs.files includeRoots
+        includes.from(includeRoots)
+    }
+
+    void includes(Iterable<File> includeRoots) {
+        includeRoots.each { task.inputs.dir(it) }
+        includes.from(includeRoots)
+    }
+
+    void source(Iterable<File> files) {
+        task.inputs.files files
+        source.from files
+    }
+
+    // special filecollection version because filecollection may be buildable
+    void source(FileCollection files) {
+        task.inputs.source files
+        source.from files
+    }
+
+    void libs(Iterable<Library> libs) {
+        task.dependsOn libs
+        this.libs.from({ libs*.spec*.outputFile })
+        includes(project.files { libs*.headers*.srcDirs })
+    }
+
+    void args(Object... args) {
+        setting {
+            it.args args
+        }
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    void sharedLibrary() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("CompileSpec.sharedLibrary()")
+    }
+
+    /**
+     * @deprecated No replacement
+     */
+    @Deprecated
+    void compile() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("CompileSpec.compile()")
+        compiler.execute(this)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
new file mode 100644
index 0000000..cdad9ae
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppCompilerPlugin.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.cpp.gpp
+
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.Factory
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.plugins.binaries.BinariesPlugin
+import org.gradle.plugins.binaries.model.CompilerRegistry
+import org.gradle.plugins.cpp.gpp.internal.GppCompilerAdapter
+import org.gradle.process.internal.DefaultExecAction
+import org.gradle.process.internal.ExecAction
+
+/**
+ * A {@link Plugin} which makes the <a href="http://gcc.gnu.org/">GNU G++ compiler</a> available for compiling C/C++ code.
+ */
+class GppCompilerPlugin implements Plugin<ProjectInternal> {
+
+    void apply(ProjectInternal project) {
+        project.plugins.apply(BinariesPlugin)
+        project.extensions.getByType(CompilerRegistry).add(new GppCompilerAdapter(
+                OperatingSystem.current(),
+                new Factory<ExecAction>() {
+                    ExecAction create() {
+                        new DefaultExecAction(project.getFileResolver())
+                    }
+                }))
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpec.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpec.groovy
new file mode 100755
index 0000000..e1ea17e
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpec.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.cpp.gpp
+
+import org.gradle.plugins.binaries.model.LibraryCompileSpec
+import org.gradle.plugins.binaries.model.Binary
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.api.internal.project.ProjectInternal
+
+class GppLibraryCompileSpec extends GppCompileSpec implements LibraryCompileSpec {
+    String installName
+
+    GppLibraryCompileSpec(Binary binary, Compiler<? super GppCompileSpec> compiler, ProjectInternal project) {
+        super(binary, compiler, project)
+    }
+
+    @Override
+    protected String getDefaultOutputFileName() {
+        return OperatingSystem.current().getSharedLibraryName(getBaseName())
+    }
+
+    String getInstallName() {
+        return installName ?: getOutputFileName()
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecFactory.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecFactory.java
new file mode 100644
index 0000000..16a1ac6
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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.cpp.gpp.internal;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.plugins.binaries.model.Binary;
+import org.gradle.plugins.binaries.model.Library;
+import org.gradle.plugins.binaries.model.internal.BinaryCompileSpec;
+import org.gradle.plugins.binaries.model.internal.BinaryCompileSpecFactory;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+import org.gradle.plugins.cpp.gpp.GppLibraryCompileSpec;
+
+public class GppCompileSpecFactory implements BinaryCompileSpecFactory {
+    private ProjectInternal project;
+
+    public GppCompileSpecFactory(ProjectInternal project) {
+        this.project = project;
+    }
+
+    public BinaryCompileSpec create(Binary binary, org.gradle.api.internal.tasks.compile.Compiler<?> compiler) {
+        Compiler<? super GppCompileSpec> typed = (Compiler<? super GppCompileSpec>) compiler;
+        if (binary instanceof Library) {
+            return new GppLibraryCompileSpec(binary, typed, project);
+        }
+        return new GppCompileSpec(binary, typed, project);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
new file mode 100644
index 0000000..0507d54
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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.cpp.gpp.internal;
+
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.plugins.binaries.model.LibraryCompileSpec;
+import org.gradle.plugins.cpp.compiler.internal.ArgCollector;
+import org.gradle.plugins.cpp.compiler.internal.CompileSpecToArguments;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+
+import java.io.File;
+
+public class GppCompileSpecToArguments implements CompileSpecToArguments<GppCompileSpec> {
+
+    public void collectArguments(GppCompileSpec spec, ArgCollector collector) {
+        collector.args("-o", spec.getOutputFile().getAbsolutePath());
+        if (spec instanceof LibraryCompileSpec) {
+            LibraryCompileSpec librarySpec = (LibraryCompileSpec) spec;
+            collector.args("-shared");
+            if (!OperatingSystem.current().isWindows()) {
+                collector.args("-fPIC");
+                if (OperatingSystem.current().isMacOsX()) {
+                    collector.args("-Wl,-install_name," + librarySpec.getInstallName());
+                } else {
+                    collector.args("-Wl,-soname," + librarySpec.getInstallName());
+                }
+            }
+        }
+        for (File file : spec.getIncludeRoots()) {
+            collector.args("-I");
+            collector.args(file.getAbsolutePath());
+        }
+        for (File file : spec.getSource()) {
+            collector.args(file.getAbsolutePath());
+        }
+        for (File file : spec.getLibs()) {
+            collector.args(file.getAbsolutePath());
+        }
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
new file mode 100755
index 0000000..db54d52
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.cpp.gpp.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.plugins.cpp.compiler.internal.ArgWriter;
+import org.gradle.plugins.cpp.compiler.internal.CommandLinCppCompilerArgumentsApplicator;
+import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompiler;
+import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerArgumentsToOptionFile;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+import org.gradle.process.internal.ExecAction;
+
+import java.io.File;
+
+public class GppCompiler extends CommandLineCppCompiler<GppCompileSpec> {
+
+    public GppCompiler(File executable, Factory<ExecAction> execActionFactory, boolean useCommandFile) {
+        super(executable, execActionFactory, useCommandFile ? viaCommandFile() : withoutCommandFile());
+    }
+
+    private static CommandLinCppCompilerArgumentsApplicator<GppCompileSpec> withoutCommandFile() {
+        return new CommandLinCppCompilerArgumentsApplicator<GppCompileSpec>(new GppCompileSpecToArguments());
+    }
+
+    private static CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec> viaCommandFile() {
+        return new CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec>(
+            ArgWriter.unixStyleFactory(), new GppCompileSpecToArguments()
+        );
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
new file mode 100755
index 0000000..d0329d5
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 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.cpp.gpp.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.Factory;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.plugins.binaries.model.Binary;
+import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerAdapter;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+import org.gradle.plugins.cpp.gpp.internal.version.GppVersionDeterminer;
+import org.gradle.process.internal.ExecAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * Compiler adapter for g++
+ */
+public class GppCompilerAdapter extends CommandLineCppCompilerAdapter<GppCompileSpec> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GppCompilerAdapter.class);
+
+    static final String EXECUTABLE = "g++";
+    
+    public static final String NAME = "gpp";
+
+    private boolean determinedVersion;
+    private String version;
+
+    private final Transformer<String, File> versionDeterminer;
+
+    public GppCompilerAdapter(OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
+        this(operatingSystem, execActionFactory, new GppVersionDeterminer());
+    }
+
+    GppCompilerAdapter(OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory, Transformer<String, File> versionDeterminer) {
+        super(EXECUTABLE, operatingSystem, execActionFactory);
+        this.versionDeterminer = versionDeterminer;
+    }
+
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("GNU G++ (%s)", getOperatingSystem().getExecutableName(EXECUTABLE));
+    }
+
+    public boolean isAvailable() {
+        String version = getVersion();
+        return version != null;
+    }
+
+    public Compiler<GppCompileSpec> createCompiler(Binary binary) {
+        String version = getVersion();
+        if (version == null) {
+            throw new IllegalStateException("Cannot create gpp compiler when it is not available");
+        }
+        
+        String[] components = version.split("\\.");
+
+        int majorVersion;
+        try {
+            majorVersion = Integer.valueOf(components[0]);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(String.format("Unable to determine major g++ version from version number {}", version), e);
+        }
+
+        return new GppCompiler(getExecutable(), getExecActionFactory(), majorVersion >= 4);
+    }
+
+    private String getVersion() {
+        if (!determinedVersion) {
+            determinedVersion = true;
+            version = determineVersion(getExecutable());
+            if (version == null) {
+                LOGGER.info("Did not find {} on system", EXECUTABLE);
+            } else {
+                LOGGER.info("Found {} with version {}", EXECUTABLE, version);
+            }
+        }
+        return version;
+    }
+
+    private String determineVersion(File executable) {
+        return executable == null ? null : versionDeterminer.transform(executable);
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminer.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminer.java
new file mode 100644
index 0000000..efc9863
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminer.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 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.cpp.gpp.internal.version;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.internal.Factory;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Given a File pointing to an (existing) g++ binary, extracts the version number by running with -v and scraping the output.
+ */
+public class GppVersionDeterminer implements Transformer<String, File> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GppVersionDeterminer.class);
+
+    private final Transformer<String, String> outputScraper;
+    private final Transformer<String, File> outputProducer;
+
+    public GppVersionDeterminer() {
+        this(new GppVersionOutputProducer(new Factory<ExecHandleBuilder>() {
+            public ExecHandleBuilder create() {
+                return new ExecHandleBuilder(new IdentityFileResolver());
+            }
+        }), new GppVersionOutputScraper());
+    }
+
+    GppVersionDeterminer(Transformer<String, File> outputProducer, Transformer<String, String> outputScraper) {
+        this.outputProducer = outputProducer;
+        this.outputScraper = outputScraper;
+    }
+
+    static class GppVersionOutputScraper implements Transformer<String, String> {
+        public String transform(String output) {
+            Pattern pattern = Pattern.compile(".*gcc version (\\S+).*", Pattern.DOTALL);
+            Matcher matcher = pattern.matcher(output);
+            if (matcher.matches()) {
+                String scrapedVersion = matcher.group(1);
+                LOGGER.debug("Extracted version {} from g++ -v output", scrapedVersion);
+                return scrapedVersion;
+            } else {
+                LOGGER.warn("Unable to extract g++ version number from \"{}\" with pattern \"{}\"", output, pattern);
+                return null;
+            }
+        }
+    }
+
+    static class GppVersionOutputProducer implements Transformer<String, File> {
+        
+        private final Factory<ExecHandleBuilder> execHandleBuilderFactory;
+
+        GppVersionOutputProducer(Factory<ExecHandleBuilder> execHandleBuilderFactory) {
+            this.execHandleBuilderFactory = execHandleBuilderFactory;
+        }
+
+        public String transform(File gppBinary) {
+            ExecHandleBuilder exec = execHandleBuilderFactory.create();
+            exec.executable(gppBinary.getAbsolutePath());
+            exec.setWorkingDir(gppBinary.getParentFile());
+            exec.args("-v");
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            exec.setErrorOutput(baos);
+            ExecHandle handle = exec.build();
+            ExecResult result = handle.start().waitForFinish();
+
+            int exitValue = result.getExitValue();
+            if (exitValue == 0) {
+                String output = new String(baos.toByteArray());
+                LOGGER.debug("Output from '{} -v {}", gppBinary.getPath(), output);    
+                return output;                
+            } else {
+                LOGGER.warn("Executing '{} -v' return exit code {}, cannot use", gppBinary.getPath(), exitValue);
+                return null;
+            }
+        }
+    }
+
+    public String transform(File gppBinary) {
+        String output = outputProducer.transform(gppBinary);
+        return output == null ? null : outputScraper.transform(output);
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/package-info.java
new file mode 100644
index 0000000..23db2ad
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Integration with the gpp (gcc frontend) compiler.
+ */
+package org.gradle.plugins.cpp.gpp;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/CppCompileSpec.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/CppCompileSpec.java
new file mode 100644
index 0000000..1699745
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/CppCompileSpec.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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.cpp.internal;
+
+import groovy.lang.Closure;
+import org.gradle.plugins.binaries.model.internal.BinaryCompileSpec;
+import org.gradle.plugins.cpp.compiler.capability.StandardCppCompiler;
+
+import java.io.File;
+import java.util.List;
+
+public interface CppCompileSpec extends StandardCppCompiler, BinaryCompileSpec {
+
+    File getWorkDir();
+
+    // This needs to go
+    List<Closure> getSettings();
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/DefaultCppSourceSet.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/DefaultCppSourceSet.java
new file mode 100644
index 0000000..1b03333
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/internal/DefaultCppSourceSet.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 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.cpp.internal;
+
+import org.gradle.plugins.cpp.CppSourceSet;
+
+import org.gradle.plugins.binaries.model.Library;
+import org.gradle.plugins.binaries.model.NativeDependencySet;
+import org.gradle.plugins.binaries.model.internal.ConfigurationBasedNativeDependencySet;
+
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.file.SourceDirectorySet;
+
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.util.ConfigureUtil;
+
+import groovy.lang.Closure;
+import java.util.Map;
+
+public class DefaultCppSourceSet implements CppSourceSet {
+
+    private final String name;
+    private final ProjectInternal project;
+
+    private final DefaultSourceDirectorySet exportedHeaders;
+    private final DefaultSourceDirectorySet source;
+    private final DefaultDomainObjectSet<Library> libs;
+    private final DefaultDomainObjectSet<NativeDependencySet> nativeDependencySets;
+    private final ConfigurationBasedNativeDependencySet configurationDependencySet;
+
+    public DefaultCppSourceSet(String name, ProjectInternal project) {
+        this.name = name;
+        this.project = project;
+
+        this.exportedHeaders = new DefaultSourceDirectorySet("exported headers", project.getFileResolver());
+        this.source = new DefaultSourceDirectorySet("source", project.getFileResolver());
+        this.libs = new DefaultDomainObjectSet<Library>(Library.class);
+        this.nativeDependencySets = new DefaultDomainObjectSet<NativeDependencySet>(NativeDependencySet.class);
+        this.configurationDependencySet = new ConfigurationBasedNativeDependencySet(project, name);
+        
+        nativeDependencySets.add(configurationDependencySet);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public SourceDirectorySet getExportedHeaders() {
+        return exportedHeaders;
+    }
+
+    public DefaultCppSourceSet exportedHeaders(Closure closure) {
+        ConfigureUtil.configure(closure, exportedHeaders);
+        return this;
+    }
+
+    public SourceDirectorySet getSource() {
+        return source;
+    }
+
+    public DefaultCppSourceSet source(Closure closure) {
+        ConfigureUtil.configure(closure, source);
+        return this;
+    }
+
+    public DomainObjectSet<Library> getLibs() {
+        return libs;
+    }
+
+    public DomainObjectSet<NativeDependencySet> getNativeDependencySets() {
+        return nativeDependencySets;
+    }
+
+    public void dependency(Map<?, ?> dep) {
+        configurationDependencySet.add(dep);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
new file mode 100755
index 0000000..d4dc235
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/MicrosoftVisualCppPlugin.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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.cpp.msvcpp
+
+import org.gradle.api.Plugin
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.Factory
+import org.gradle.plugins.binaries.BinariesPlugin
+import org.gradle.plugins.binaries.model.CompilerRegistry
+import org.gradle.plugins.cpp.msvcpp.internal.VisualCppCompilerAdapter
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.internal.DefaultExecAction
+import org.gradle.internal.os.OperatingSystem
+
+/**
+ * A {@link Plugin} which makes the Microsoft Visual C++ compiler available to compile C/C++ code.
+ */
+class MicrosoftVisualCppPlugin implements Plugin<ProjectInternal> {
+    void apply(ProjectInternal project) {
+        project.plugins.apply(BinariesPlugin)
+        project.extensions.getByType(CompilerRegistry).add(new VisualCppCompilerAdapter(
+                OperatingSystem.current(),
+                new Factory<ExecAction>() {
+                    ExecAction create() {
+                        new DefaultExecAction(project.fileResolver)
+                    }
+                }
+        ))
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
new file mode 100644
index 0000000..6eb3417
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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.cpp.msvcpp.internal;
+
+import org.gradle.plugins.binaries.model.LibraryCompileSpec;
+import org.gradle.plugins.cpp.compiler.internal.ArgCollector;
+import org.gradle.plugins.cpp.compiler.internal.CompileSpecToArguments;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+
+import java.io.File;
+
+public class VisualCppCompileSpecToArguments implements CompileSpecToArguments<GppCompileSpec> {
+
+    public void collectArguments(GppCompileSpec spec, ArgCollector collector) {
+        collector.args("/nologo");
+        collector.args("/EHsc");
+        collector.args("/Fe" + spec.getOutputFile().getAbsolutePath());
+        if (spec instanceof LibraryCompileSpec) {
+            collector.args("/LD");
+        }
+        for (File file : spec.getIncludeRoots()) {
+            collector.args("/I", file.getAbsolutePath());
+        }
+        for (File file : spec.getSource()) {
+            collector.args(file);
+        }
+        // Link options need to be on one line in the options file
+        for (File file : spec.getLibs()) {
+            collector.args("/link", file.getAbsolutePath().replaceFirst("\\.dll$", ".lib"));
+        }
+
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java
new file mode 100755
index 0000000..1ac1ec8
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.cpp.msvcpp.internal;
+
+import org.gradle.internal.Factory;
+import org.gradle.plugins.cpp.compiler.internal.ArgWriter;
+import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompiler;
+import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerArgumentsToOptionFile;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+import org.gradle.process.internal.ExecAction;
+
+import java.io.File;
+
+class VisualCppCompiler extends CommandLineCppCompiler<GppCompileSpec> {
+
+    VisualCppCompiler(File executable, Factory<ExecAction> execActionFactory) {
+        super(executable, execActionFactory, new CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec>(
+                ArgWriter.windowsStyleFactory(), new VisualCppCompileSpecToArguments()
+        ));
+    }
+
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompilerAdapter.java
new file mode 100755
index 0000000..6fb7532
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompilerAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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.cpp.msvcpp.internal;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.Factory;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.plugins.binaries.model.Binary;
+import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerAdapter;
+import org.gradle.plugins.cpp.gpp.GppCompileSpec;
+import org.gradle.process.internal.ExecAction;
+
+public class VisualCppCompilerAdapter extends CommandLineCppCompilerAdapter<GppCompileSpec> {
+
+    static final String EXECUTABLE = "cl.exe";
+
+    public VisualCppCompilerAdapter(OperatingSystem operatingSystem, Factory<ExecAction> execActionFactory) {
+        super(EXECUTABLE, operatingSystem, execActionFactory);
+    }
+
+    public String getName() {
+        return "visualCpp";
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Visual C++ (%s)", getOperatingSystem().getExecutableName(EXECUTABLE));
+    }
+
+    public boolean isAvailable() {
+        return getOperatingSystem().isWindows() && super.isAvailable();
+    }
+
+    public Compiler<GppCompileSpec> createCompiler(Binary binary) {
+        return new VisualCppCompiler(getExecutable(), getExecActionFactory());
+    }
+}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/package-info.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/package-info.java
new file mode 100644
index 0000000..578bd12
--- /dev/null
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * A {@link org.gradle.api.Plugin} for building C++ projects with Gradle.
+ */
+package org.gradle.plugins.cpp;
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/binaries.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/binaries.properties
new file mode 100644
index 0000000..fe8bfdc
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/binaries.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.binaries.BinariesPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-exe.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-exe.properties
new file mode 100644
index 0000000..23e9515
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-exe.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.cpp.CppExeConventionPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-lib.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-lib.properties
new file mode 100644
index 0000000..7ca7910
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp-lib.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.cpp.CppLibConventionPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties
new file mode 100644
index 0000000..cad2a5f
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/cpp.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.cpp.CppPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties
new file mode 100644
index 0000000..7ec9e10
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/eclipse-cdt.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.cpp.cdt.CdtIdePlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/gpp-compiler.properties b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/gpp-compiler.properties
new file mode 100644
index 0000000..a135aac
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/META-INF/gradle-plugins/gpp-compiler.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.cpp.gpp.GppCompilerPlugin
\ No newline at end of file
diff --git a/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-linux.xml b/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-linux.xml
new file mode 100644
index 0000000..770dbb9
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-linux.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?fileVersion 4.0.0?>
+
+<cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
+	<storageModule moduleId="org.eclipse.cdt.core.settings">
+		<cconfiguration id="cdt.managedbuild.config.gnu.exe.debug.1495088800">
+			<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.exe.debug.1495088800" moduleId="org.eclipse.cdt.core.settings" name="Debug">
+				<externalSettings/>
+				<extensions>
+					<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+					<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+				</extensions>
+			</storageModule>
+			<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+				<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.exe.debug.1495088800" name="Debug" parent="cdt.managedbuild.config.gnu.exe.debug">
+					<folderInfo id="cdt.managedbuild.config.gnu.exe.debug.1495088800." name="/" resourcePath="">
+						<toolChain id="cdt.managedbuild.toolchain.gnu.exe.debug.422456247" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.debug">
+							<targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.debug.1841936862" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.debug"/>
+							<builder buildPath="${workspace_loc:/test/Debug}" id="cdt.managedbuild.target.gnu.builder.exe.debug.57049896" managedBuildOn="true" name="Gnu Make Builder.Debug" superClass="cdt.managedbuild.target.gnu.builder.exe.debug"/>
+							<tool id="cdt.managedbuild.tool.gnu.archiver.base.1750188054" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
+							<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.1085422857" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug">
+								<option id="gnu.cpp.compiler.exe.debug.option.optimization.level.1907442310" superClass="gnu.cpp.compiler.exe.debug.option.optimization.level" value="gnu.cpp.compiler.optimization.level.none" valueType="enumerated"/>
+								<option id="gnu.cpp.compiler.exe.debug.option.debugging.level.599044079" superClass="gnu.cpp.compiler.exe.debug.option.debugging.level" value="gnu.cpp.compiler.debugging.level.max" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1662071269" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.c.compiler.exe.debug.686407244" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.exe.debug">
+								<option defaultValue="gnu.c.optimization.level.none" id="gnu.c.compiler.exe.debug.option.optimization.level.484836174" superClass="gnu.c.compiler.exe.debug.option.optimization.level" valueType="enumerated"/>
+								<option id="gnu.c.compiler.exe.debug.option.debugging.level.574978489" superClass="gnu.c.compiler.exe.debug.option.debugging.level" value="gnu.c.debugging.level.max" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.2142675521" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.debug.756073884" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.debug"/>
+							<tool id="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug.1488597666" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug">
+								<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1147276650" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+									<additionalInput kind="additionalinput" paths="$(LIBS)"/>
+								</inputType>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.assembler.exe.debug.1948012713" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.exe.debug">
+								<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1630796599" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+							</tool>
+						</toolChain>
+					</folderInfo>
+				</configuration>
+			</storageModule>
+			<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+		</cconfiguration>
+		<cconfiguration id="cdt.managedbuild.config.gnu.exe.release.1741372829">
+			<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.exe.release.1741372829" moduleId="org.eclipse.cdt.core.settings" name="Release">
+				<externalSettings/>
+				<extensions>
+					<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+					<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+				</extensions>
+			</storageModule>
+			<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+				<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.exe.release.1741372829" name="Release" parent="cdt.managedbuild.config.gnu.exe.release">
+					<folderInfo id="cdt.managedbuild.config.gnu.exe.release.1741372829." name="/" resourcePath="">
+						<toolChain id="cdt.managedbuild.toolchain.gnu.exe.release.1697941089" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.release">
+							<targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.release.910743200" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.release"/>
+							<builder buildPath="${workspace_loc:/test/Release}" id="cdt.managedbuild.target.gnu.builder.exe.release.1417264999" managedBuildOn="true" name="Gnu Make Builder.Release" superClass="cdt.managedbuild.target.gnu.builder.exe.release"/>
+							<tool id="cdt.managedbuild.tool.gnu.archiver.base.2112892354" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
+							<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.release.1294051984" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.release">
+								<option id="gnu.cpp.compiler.exe.release.option.optimization.level.357629137" superClass="gnu.cpp.compiler.exe.release.option.optimization.level" value="gnu.cpp.compiler.optimization.level.most" valueType="enumerated"/>
+								<option id="gnu.cpp.compiler.exe.release.option.debugging.level.1890554856" superClass="gnu.cpp.compiler.exe.release.option.debugging.level" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.576790498" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.c.compiler.exe.release.195122955" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.exe.release">
+								<option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.exe.release.option.optimization.level.1336921237" superClass="gnu.c.compiler.exe.release.option.optimization.level" valueType="enumerated"/>
+								<option id="gnu.c.compiler.exe.release.option.debugging.level.11750902" superClass="gnu.c.compiler.exe.release.option.debugging.level" value="gnu.c.debugging.level.none" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.919272435" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.c.linker.exe.release.2043171786" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.release"/>
+							<tool id="cdt.managedbuild.tool.gnu.cpp.linker.exe.release.1729923580" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.release">
+								<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.191816342" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+									<additionalInput kind="additionalinput" paths="$(LIBS)"/>
+								</inputType>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.assembler.exe.release.1042787141" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.exe.release">
+								<inputType id="cdt.managedbuild.tool.gnu.assembler.input.575111458" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+							</tool>
+						</toolChain>
+					</folderInfo>
+				</configuration>
+			</storageModule>
+		</cconfiguration>
+	</storageModule>
+	<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+		<project id="test.cdt.managedbuild.target.gnu.exe.1759528612" name="Executable" projectType="cdt.managedbuild.target.gnu.exe"/>
+	</storageModule>
+	<storageModule moduleId="scannerConfiguration">
+		<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.debug.1495088800;cdt.managedbuild.config.gnu.exe.debug.1495088800.;cdt.managedbuild.tool.gnu.c.compiler.exe.debug.686407244;cdt.managedbuild.tool.gnu.c.compiler.input.2142675521">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"/>
+		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.release.1741372829;cdt.managedbuild.config.gnu.exe.release.1741372829.;cdt.managedbuild.tool.gnu.cpp.compiler.exe.release.1294051984;cdt.managedbuild.tool.gnu.cpp.compiler.input.576790498">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP"/>
+		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.release.1741372829;cdt.managedbuild.config.gnu.exe.release.1741372829.;cdt.managedbuild.tool.gnu.c.compiler.exe.release.195122955;cdt.managedbuild.tool.gnu.c.compiler.input.919272435">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"/>
+		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.debug.1495088800;cdt.managedbuild.config.gnu.exe.debug.1495088800.;cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.1085422857;cdt.managedbuild.tool.gnu.cpp.compiler.input.1662071269">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP"/>
+		</scannerConfigBuildInfo>
+	</storageModule>
+</cproject>
diff --git a/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-macos.xml b/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-macos.xml
new file mode 100644
index 0000000..b88a55f
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultCproject-macos.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?fileVersion 4.0.0?>
+
+<cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
+	<storageModule moduleId="org.eclipse.cdt.core.settings">
+		<cconfiguration id="cdt.managedbuild.config.gnu.macosx.exe.debug.183562651">
+			<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.macosx.exe.debug.183562651" moduleId="org.eclipse.cdt.core.settings" name="Debug">
+				<externalSettings/>
+				<extensions>
+					<extension id="org.eclipse.cdt.core.MachO64" point="org.eclipse.cdt.core.BinaryParser"/>
+					<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+				</extensions>
+			</storageModule>
+			<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+				<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.macosx.exe.debug.183562651" name="Debug" parent="cdt.managedbuild.config.gnu.macosx.exe.debug">
+					<folderInfo id="cdt.managedbuild.config.gnu.macosx.exe.debug.183562651." name="/" resourcePath="">
+						<toolChain id="cdt.managedbuild.toolchain.gnu.macosx.exe.debug.1331160853" name="MacOSX GCC" superClass="cdt.managedbuild.toolchain.gnu.macosx.exe.debug">
+							<targetPlatform id="cdt.managedbuild.target.gnu.platform.macosx.exe.debug.1048063726" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.macosx.exe.debug"/>
+							<builder buildPath="${workspace_loc:/${ProjName}/Debug}" id="cdt.managedbuild.target.gnu.builder.macosx.exe.debug.1867667444" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.macosx.exe.debug"/>
+							<tool id="cdt.managedbuild.tool.macosx.c.linker.macosx.exe.debug.2010723970" name="MacOS X C Linker" superClass="cdt.managedbuild.tool.macosx.c.linker.macosx.exe.debug"/>
+							<tool id="cdt.managedbuild.tool.macosx.cpp.linker.macosx.exe.debug.65528086" name="MacOS X C++ Linker" superClass="cdt.managedbuild.tool.macosx.cpp.linker.macosx.exe.debug">
+								<inputType id="cdt.managedbuild.tool.macosx.cpp.linker.input.860590491" superClass="cdt.managedbuild.tool.macosx.cpp.linker.input">
+									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+									<additionalInput kind="additionalinput" paths="$(LIBS)"/>
+								</inputType>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.assembler.macosx.exe.debug.1678403873" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.macosx.exe.debug">
+								<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1433127358" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.archiver.macosx.base.1118492971" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.macosx.base"/>
+							<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.macosx.exe.debug.604000447" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.macosx.exe.debug">
+								<option id="gnu.cpp.compilermacosx.exe.debug.option.optimization.level.756163739" name="Optimization Level" superClass="gnu.cpp.compilermacosx.exe.debug.option.optimization.level" value="gnu.cpp.compiler.optimization.level.none" valueType="enumerated"/>
+								<option id="gnu.cpp.compiler.macosx.exe.debug.option.debugging.level.1597868679" name="Debug Level" superClass="gnu.cpp.compiler.macosx.exe.debug.option.debugging.level" value="gnu.cpp.compiler.debugging.level.max" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.347568326" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.c.compiler.macosx.exe.debug.494964109" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.macosx.exe.debug">
+								<option defaultValue="gnu.c.optimization.level.none" id="gnu.c.compiler.macosx.exe.debug.option.optimization.level.833106575" name="Optimization Level" superClass="gnu.c.compiler.macosx.exe.debug.option.optimization.level" valueType="enumerated"/>
+								<option id="gnu.c.compiler.macosx.exe.debug.option.debugging.level.996742079" name="Debug Level" superClass="gnu.c.compiler.macosx.exe.debug.option.debugging.level" value="gnu.c.debugging.level.max" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.2031091459" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+							</tool>
+						</toolChain>
+					</folderInfo>
+				</configuration>
+			</storageModule>
+			<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+		</cconfiguration>
+		<cconfiguration id="cdt.managedbuild.config.macosx.exe.release.1999911390">
+			<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.macosx.exe.release.1999911390" moduleId="org.eclipse.cdt.core.settings" name="Release">
+				<externalSettings/>
+				<extensions>
+					<extension id="org.eclipse.cdt.core.MachO64" point="org.eclipse.cdt.core.BinaryParser"/>
+					<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+					<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+				</extensions>
+			</storageModule>
+			<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+				<configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.macosx.exe.release.1999911390" name="Release" parent="cdt.managedbuild.config.macosx.exe.release">
+					<folderInfo id="cdt.managedbuild.config.macosx.exe.release.1999911390." name="/" resourcePath="">
+						<toolChain id="cdt.managedbuild.toolchain.gnu.macosx.exe.release.42907815" name="MacOSX GCC" superClass="cdt.managedbuild.toolchain.gnu.macosx.exe.release">
+							<targetPlatform id="cdt.managedbuild.target.gnu.platform.macosx.exe.release.547885235" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.macosx.exe.release"/>
+							<builder buildPath="${workspace_loc:/${ProjName}/Release}" id="cdt.managedbuild.target.gnu.builder.macosx.exe.release.2067056402" managedBuildOn="true" name="Gnu Make Builder.Release" superClass="cdt.managedbuild.target.gnu.builder.macosx.exe.release"/>
+							<tool id="cdt.managedbuild.tool.macosx.c.linker.macosx.exe.release.2109125163" name="MacOS X C Linker" superClass="cdt.managedbuild.tool.macosx.c.linker.macosx.exe.release"/>
+							<tool id="cdt.managedbuild.tool.macosx.cpp.linker.macosx.exe.release.2101721056" name="MacOS X C++ Linker" superClass="cdt.managedbuild.tool.macosx.cpp.linker.macosx.exe.release">
+								<inputType id="cdt.managedbuild.tool.macosx.cpp.linker.input.1179423156" superClass="cdt.managedbuild.tool.macosx.cpp.linker.input">
+									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+									<additionalInput kind="additionalinput" paths="$(LIBS)"/>
+								</inputType>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.assembler.macosx.exe.release.597762137" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.macosx.exe.release">
+								<inputType id="cdt.managedbuild.tool.gnu.assembler.input.12476064" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.archiver.macosx.base.1802416188" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.macosx.base"/>
+							<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.macosx.exe.release.855940126" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.macosx.exe.release">
+								<option id="gnu.cpp.compiler.macosx.exe.release.option.optimization.level.1838155783" superClass="gnu.cpp.compiler.macosx.exe.release.option.optimization.level" value="gnu.cpp.compiler.optimization.level.most" valueType="enumerated"/>
+								<option id="gnu.cpp.compiler.macosx.exe.release.option.debugging.level.1083019858" superClass="gnu.cpp.compiler.macosx.exe.release.option.debugging.level" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1899995482" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+							</tool>
+							<tool id="cdt.managedbuild.tool.gnu.c.compiler.macosx.exe.release.714011498" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.macosx.exe.release">
+								<option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.macosx.exe.release.option.optimization.level.1507079747" superClass="gnu.c.compiler.macosx.exe.release.option.optimization.level" valueType="enumerated"/>
+								<option id="gnu.c.compiler.macosx.exe.release.option.debugging.level.203561305" superClass="gnu.c.compiler.macosx.exe.release.option.debugging.level" value="gnu.c.debugging.level.none" valueType="enumerated"/>
+								<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1754479510" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+							</tool>
+						</toolChain>
+					</folderInfo>
+				</configuration>
+			</storageModule>
+			<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+		</cconfiguration>
+	</storageModule>
+	<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+		<project id="basic2.cdt.managedbuild.target.macosx.exe.476010221" name="Executable" projectType="cdt.managedbuild.target.macosx.exe"/>
+	</storageModule>
+	<storageModule moduleId="scannerConfiguration">
+		<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.macosx.exe.release.1999911390;cdt.managedbuild.config.macosx.exe.release.1999911390.;cdt.managedbuild.tool.gnu.cpp.compiler.macosx.exe.release.855940126;cdt.managedbuild.tool.gnu.cpp.compiler.input.1899995482">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP"/>
+		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.macosx.exe.release.1999911390;cdt.managedbuild.config.macosx.exe.release.1999911390.;cdt.managedbuild.tool.gnu.c.compiler.macosx.exe.release.714011498;cdt.managedbuild.tool.gnu.c.compiler.input.1754479510">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"/>
+		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.macosx.exe.debug.183562651;cdt.managedbuild.config.gnu.macosx.exe.debug.183562651.;cdt.managedbuild.tool.gnu.cpp.compiler.macosx.exe.debug.604000447;cdt.managedbuild.tool.gnu.cpp.compiler.input.347568326">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP"/>
+		</scannerConfigBuildInfo>
+		<scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.macosx.exe.debug.183562651;cdt.managedbuild.config.gnu.macosx.exe.debug.183562651.;cdt.managedbuild.tool.gnu.c.compiler.macosx.exe.debug.494964109;cdt.managedbuild.tool.gnu.c.compiler.input.2031091459">
+			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"/>
+		</scannerConfigBuildInfo>
+	</storageModule>
+</cproject>
diff --git a/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultProject.xml b/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultProject.xml
new file mode 100644
index 0000000..9e69a2b
--- /dev/null
+++ b/subprojects/cpp/src/main/resources/org/gradle/plugins/cpp/cdt/model/defaultProject.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>@project-name@</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
+			<triggers>clean,full,incremental,</triggers>
+			<arguments>
+				<dictionary>
+					<key>?name?</key>
+					<value></value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.append_environment</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.autoBuildTarget</key>
+					<value>all</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.buildArguments</key>
+					<value></value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.buildCommand</key>
+					<value>make</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.buildLocation</key>
+					<value>${workspace_loc:/@project-name@/Debug}</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
+					<value>clean</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.contents</key>
+					<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
+					<value>false</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.enableFullBuild</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.fullBuildTarget</key>
+					<value>all</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.stopOnError</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
+					<value>true</value>
+				</dictionary>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
+			<triggers>full,incremental,</triggers>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.cdt.core.cnature</nature>
+		<nature>org.eclipse.cdt.core.ccnature</nature>
+		<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
+		<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
+	</natures>
+</projectDescription>
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
new file mode 100644
index 0000000..6204e77
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 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.binaries.model.internal
+
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.plugins.binaries.model.Binary
+import spock.lang.Specification
+
+class DefaultCompilerRegistryTest extends Specification {
+    final BinaryCompileSpecFactory specFactory = Mock()
+    final DefaultCompilerRegistry registry = new DefaultCompilerRegistry(new DirectInstantiator())
+
+    def setup() {
+        registry.specFactory = specFactory
+    }
+
+    def "search order defaults to the order that adapters are added"() {
+        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("z")
+        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("b")
+        CompilerAdapter<BinaryCompileSpec> compiler3 = compiler("a")
+
+        expect:
+        registry.searchOrder == []
+
+        when:
+        registry.add(compiler2)
+        registry.add(compiler1)
+        registry.add(compiler3)
+
+        then:
+        registry.searchOrder == [compiler2, compiler1, compiler3]
+
+        when:
+        registry.remove(compiler1)
+
+        then:
+        registry.searchOrder == [compiler2, compiler3]
+    }
+
+    def "compilation searches adapters in the order added and uses the first available"() {
+        Binary binary = Mock()
+        BinaryCompileSpec compileSpec = Mock()
+        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("z")
+        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("b")
+        CompilerAdapter<BinaryCompileSpec> compiler3 = compiler("a")
+        Compiler<BinaryCompileSpec> compiler = Mock()
+        Compiler<BinaryCompileSpec> lazyCompiler
+
+        given:
+        registry.add(compiler1)
+        registry.add(compiler2)
+        registry.add(compiler3)
+
+        and:
+        compiler2.available >> true
+
+        when:
+        registry.create(binary)
+
+        then:
+        1 * specFactory.create(binary, !null) >> { lazyCompiler = it[1]; return compileSpec }
+        
+        when:
+        lazyCompiler.execute(compileSpec)
+
+        then:
+        1 * compiler2.createCompiler(binary) >> compiler
+        1 * compiler.execute(compileSpec)
+    }
+
+    def "compilation fails when no adapter is available"() {
+        Binary binary = Mock()
+        BinaryCompileSpec compileSpec = Mock()
+        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("z")
+        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("b")
+        CompilerAdapter<BinaryCompileSpec> compiler3 = compiler("a")
+        Compiler<BinaryCompileSpec> lazyCompiler
+
+        given:
+        registry.add(compiler1)
+        registry.add(compiler2)
+        registry.add(compiler3)
+
+        when:
+        registry.create(binary)
+
+        then:
+        1 * specFactory.create(binary, !null) >> { lazyCompiler = it[1]; return compileSpec }
+        
+        when:
+        lazyCompiler.execute(compileSpec)
+
+        then:
+        IllegalStateException e = thrown()
+        e.message == "No compiler is available to compile $binary. Searched for $compiler1, $compiler2, $compiler3."
+    }
+
+    def "there is no default compiler when no adapters are available"() {
+        CompilerAdapter<BinaryCompileSpec> compiler1 = compiler("c1")
+        CompilerAdapter<BinaryCompileSpec> compiler2 = compiler("c2")
+
+        given:
+        registry.add(compiler1)
+        registry.add(compiler2)
+
+        expect:
+        registry.defaultCompiler == null
+    }
+
+    def compiler(String name) {
+        CompilerAdapter<BinaryCompileSpec> compiler = Mock()
+        _ * compiler.name >> name
+        return compiler
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppExeConventionPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppExeConventionPluginTest.groovy
new file mode 100644
index 0000000..9aee384
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppExeConventionPluginTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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.cpp
+
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class CppExeConventionPluginTest extends Specification {
+    final def project = HelperUtil.createRootProject()
+
+    def "adds and configures main executable"() {
+        given:
+        project.plugins.apply(CppExeConventionPlugin)
+
+        expect:
+        def executable = project.executables.main
+        def sourceSet = project.cpp.sourceSets.main
+        executable.spec.baseName == project.name
+        executable.sourceSets as List == [sourceSet]
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppLibConventionPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppLibConventionPluginTest.groovy
new file mode 100644
index 0000000..76105de
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppLibConventionPluginTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.cpp
+
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class CppLibConventionPluginTest extends Specification {
+    final def project = HelperUtil.createRootProject()
+
+    def "adds and configures main library"() {
+        given:
+        project.plugins.apply(CppLibConventionPlugin)
+
+        expect:
+        def library = project.libraries.main
+        def sourceSet = project.cpp.sourceSets.main
+        library.spec.baseName == project.name
+        library.sourceSets as List == [sourceSet]
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy
new file mode 100644
index 0000000..1bc819a
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/CppPluginTest.groovy
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2012 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.cpp
+
+import spock.lang.Specification
+import org.gradle.util.HelperUtil
+import org.gradle.plugins.cpp.gpp.GppCompileSpec
+import org.gradle.plugins.cpp.gpp.GppLibraryCompileSpec
+import org.gradle.plugins.binaries.tasks.Compile
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.tasks.Sync
+import org.gradle.util.Matchers
+
+class CppPluginTest extends Specification {
+    final def project = HelperUtil.createRootProject()
+
+    def "extensions are available"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        expect:
+        project.cpp instanceof CppExtension
+        project.executables instanceof NamedDomainObjectContainer
+        project.libraries instanceof NamedDomainObjectContainer
+    }
+
+    def "compiler adapters are available"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        expect:
+        project.compilers.collect { it.name } == ['gpp', 'visualCpp']
+        project.compilers.searchOrder.collect { it.name } == ['visualCpp', 'gpp']
+    }
+
+    def "can create some cpp source sets"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.cpp {
+            sourceSets {
+                s1 {}
+                s2 {}
+            }
+        }
+
+        then:
+        def sourceSets = project.cpp.sourceSets
+        sourceSets.size() == 2
+        sourceSets*.name == ["s1", "s2"]
+        sourceSets.s1 instanceof CppSourceSet
+    }
+
+    def "configure source sets"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.cpp {
+            sourceSets {
+                ss1 {
+                    source {
+                        srcDirs "d1", "d2"
+                    }
+                    exportedHeaders {
+                        srcDirs "h1", "h2"
+                    }
+                }
+                ss2 {
+                    source {
+                        srcDirs "d3"
+                    }
+                    exportedHeaders {
+                        srcDirs "h3"
+                    }
+                }
+            }
+        }
+
+        then:
+        def sourceSets = project.cpp.sourceSets
+        def ss1 = sourceSets.ss1
+        def ss2 = sourceSets.ss2
+
+        // cpp dir automatically added by convention
+        ss1.source.srcDirs*.name == ["cpp", "d1", "d2"]
+        ss2.source.srcDirs*.name == ["cpp", "d3"]
+
+        // headers dir automatically added by convention
+        ss1.exportedHeaders.srcDirs*.name == ["headers", "h1", "h2"]
+        ss2.exportedHeaders.srcDirs*.name == ["headers", "h3"]
+    }
+
+    @Requires(TestPrecondition.UNIX)
+    def "creates domain objects for executable on unix"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.executables {
+            test
+        }
+
+        then:
+        def executable = project.executables.test
+        executable.spec instanceof GppCompileSpec
+        executable.spec.outputFile == project.file("build/binaries/test")
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "creates domain objects for executable on windows"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.executables {
+            test
+        }
+
+        then:
+        def executable = project.executables.test
+        executable.spec instanceof GppCompileSpec
+        executable.spec.outputFile == project.file("build/binaries/test.exe")
+    }
+
+    def "creates tasks for each executable"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.executables {
+            test
+        }
+
+        then:
+        def compile = project.tasks['compileTest']
+        compile instanceof Compile
+        compile.spec == project.executables.test.spec
+
+        def install = project.tasks['installTest']
+        install instanceof Sync
+        install.destinationDir == project.file('build/install/test')
+        install Matchers.dependsOn("compileTest")
+    }
+
+    @Requires(TestPrecondition.MAC_OS_X)
+    def "creates domain objects for library on os x"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.libraries {
+            test
+        }
+
+        then:
+        def lib = project.libraries.test
+        lib.spec instanceof GppLibraryCompileSpec
+        lib.spec.outputFile == project.file("build/binaries/libtest.dylib")
+    }
+
+    @Requires(TestPrecondition.LINUX)
+    def "creates domain objects for library on linux"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.libraries {
+            test
+        }
+
+        then:
+        def lib = project.libraries.test
+        lib.spec instanceof GppLibraryCompileSpec
+        lib.spec.outputFile == project.file("build/binaries/libtest.so")
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "creates domain objects for library on windows"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.libraries {
+            test
+        }
+
+        then:
+        def lib = project.libraries.test
+        lib.spec instanceof GppLibraryCompileSpec
+        lib.spec.outputFile == project.file("build/binaries/test.dll")
+    }
+
+    def "creates tasks for each library"() {
+        given:
+        project.plugins.apply(CppPlugin)
+
+        when:
+        project.libraries {
+            test
+        }
+
+        then:
+        def compile = project.tasks['compileTest']
+        compile instanceof Compile
+        compile.spec == project.libraries.test.spec
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettingsSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettingsSpec.groovy
new file mode 100644
index 0000000..3811190
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/CprojectSettingsSpec.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.cpp.cdt.model
+
+import spock.lang.*
+
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+
+// very loose test, but I'm not expecting it to stay around
+ at Ignore
+class CprojectSettingsSpec extends Specification {
+
+    Project project = HelperUtil.createRootProject()
+
+    def descriptor = new CprojectDescriptor()
+    def settings = new CprojectSettings()
+
+    def "wire in includes"() {
+        given:
+        project.apply plugin: 'cpp-exe'
+        settings.binary = project.executables.main
+        descriptor.loadDefaults()
+
+        expect:
+        descriptor.getRootCppCompilerTools().each { compiler ->
+            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
+            assert includePathsOption.listOptionValue.size() == 0
+        }
+
+        when:
+        settings.applyTo(descriptor)
+        def baos = new ByteArrayOutputStream()
+        descriptor.store(baos)
+        descriptor.load(new ByteArrayInputStream(baos.toByteArray()))
+
+        then:
+        descriptor.getRootCppCompilerTools().each { compiler ->
+            def includePathsOption = descriptor.getOrCreateIncludePathsOption(compiler)
+            assert includePathsOption.listOptionValue.size() == 1
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
new file mode 100644
index 0000000..4880a5c
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/cdt/model/ProjectDescriptorSpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.cpp.cdt.model
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectDescriptorSpec extends Specification {
+
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    ProjectDescriptor descriptor = new ProjectDescriptor()
+
+    def "method"() {
+        given:
+        descriptor.loadDefaults()
+
+        when:
+        new ProjectSettings(name: "test").applyTo(descriptor)
+
+        then:
+        def dict = xml.buildSpec[0].buildCommand[0].arguments[0].dictionary.key.find { it.text() == "org.eclipse.cdt.make.core.buildLocation" }.parent()
+        dict.value[0].text() == "\${workspace_loc:/test/Debug}"
+    }
+
+    def getString() {
+        def baos = new ByteArrayOutputStream()
+        descriptor.store(baos)
+        baos.toString()
+    }
+    
+    def getXml() {
+        new XmlParser().parseText(getString())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriterSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriterSpec.groovy
new file mode 100755
index 0000000..59d78fb
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriterSpec.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.cpp.compiler.internal
+
+import spock.lang.Specification
+import static org.gradle.util.TextUtil.*
+
+class ArgWriterSpec extends Specification {
+    final StringWriter writer = new StringWriter()
+    final PrintWriter printWriter = new PrintWriter(writer, true)
+    final ArgWriter argWriter = ArgWriter.unixStyle(printWriter)
+
+    def "writes single argument to line"() {
+        when:
+        argWriter.args("-nologo")
+
+        then:
+        writer.toString() == toPlatformLineSeparators("-nologo\n")
+    }
+
+    def "writes multiple arguments to line"() {
+        when:
+        argWriter.args("-I", "some/dir")
+
+        then:
+        writer.toString() == toPlatformLineSeparators("-I some/dir\n")
+    }
+
+    def "quotes argument with whitespace"() {
+        when:
+        argWriter.args("ab c", "d e f")
+
+        then:
+        writer.toString() == toPlatformLineSeparators('"ab c" "d e f"\n')
+    }
+
+    def "escapes double quotes in argument"() {
+        when:
+        argWriter.args('"abc"', 'a" bc')
+
+        then:
+        writer.toString() == toPlatformLineSeparators('\\"abc\\" "a\\" bc"\n')
+    }
+
+    def "escapes backslash in argument"() {
+        when:
+        argWriter.args('a\\b', 'a \\ bc')
+
+        then:
+        writer.toString() == toPlatformLineSeparators('a\\\\b "a \\\\ bc"\n')
+    }
+
+    def "does not escape characters in windows style"() {
+        def argWriter = ArgWriter.windowsStyle(printWriter)
+
+        when:
+        argWriter.args('a\\b', 'a "\\" bc')
+
+        then:
+        writer.toString() == toPlatformLineSeparators('a\\b "a "\\" bc"\n')
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpecTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpecTest.groovy
new file mode 100644
index 0000000..abf084a
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppCompileSpecTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.cpp.gpp
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.plugins.binaries.model.internal.DefaultBinary
+import org.gradle.plugins.binaries.tasks.Compile
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import org.gradle.plugins.binaries.model.internal.CompileSpecFactory
+
+class GppCompileSpecTest extends Specification {
+    final ProjectInternal project = HelperUtil.createRootProject()
+    
+    def "is built by the compile task"() {
+        given:
+        def binary = new DefaultBinary("binary", project, Mock(CompileSpecFactory))
+        def spec = new GppCompileSpec(binary, Mock(Compiler), project)
+        def compileTask = project.tasks.add("compile", Compile)
+        spec.configure(compileTask)
+
+        expect:
+        spec.buildDependencies.getDependencies(null) == [compileTask] as Set
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpecTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpecTest.groovy
new file mode 100644
index 0000000..4c31e63
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/GppLibraryCompileSpecTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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.cpp.gpp
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.plugins.binaries.model.internal.CompileSpecFactory
+import org.gradle.plugins.binaries.model.internal.DefaultLibrary
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class GppLibraryCompileSpecTest extends Specification {
+    final ProjectInternal project = HelperUtil.createRootProject()
+
+    def "has default installPath"() {
+        given:
+        def library = new DefaultLibrary("binary", project, Mock(CompileSpecFactory))
+        def spec = new GppLibraryCompileSpec(library, Mock(Compiler), project)
+
+        expect:
+        spec.installName == spec.outputFileName
+    }
+}
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminerTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminerTest.groovy
new file mode 100644
index 0000000..028c8b1
--- /dev/null
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/gpp/internal/version/GppVersionDeterminerTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2012 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.cpp.gpp.internal.version
+
+import org.gradle.api.Transformer
+import org.gradle.internal.Factory
+import org.gradle.process.internal.ExecHandleBuilder
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.gradle.process.internal.ExecHandle
+import org.gradle.process.ExecResult
+
+class GppVersionDeterminerTest extends Specification {
+
+    @Unroll
+    "can scrape ok output"() {
+        expect:
+        version == output(output)
+
+        where:
+        [version, output] << OUTPUTS.collect { [it.value, it.key] }
+    }
+
+    def "null output (errored execution) ok"() {
+        expect:
+        output(null) == null
+    }
+
+    def "null scraped ok (can't parse output)"() {
+        expect:
+        scraped(null) == null
+    }
+
+    def "g++ -v execution error ok"() {
+        given:
+        def builder = Mock(ExecHandleBuilder)
+        def handle = Mock(ExecHandle)
+        def result = Mock(ExecResult)
+
+        and:
+        def determiner = new GppVersionDeterminer(producer(builder), new GppVersionDeterminer.GppVersionOutputScraper())
+        def binary = new File("g++")
+        
+        when:
+        String version = determiner.transform(binary)
+        
+        then:
+        1 * builder.build() >> handle
+        1 * handle.start() >> handle
+        1 * handle.waitForFinish() >> result
+        1 * result.getExitValue() >> 1
+
+        and:
+        version == null
+    }
+
+    def "happy day case"() {
+        given:
+        def builder = Mock(ExecHandleBuilder)
+        def handle = Mock(ExecHandle)
+        def result = Mock(ExecResult)
+        def output = """Reading specs from /opt/gcc/3.4.6/usr/local/bin/../lib/gcc/i686-pc-linux-gnu/3.4.6/specs
+Configured with: /home/ld/Downloads/gcc-3.4.6/configure
+Thread model: posix
+gcc version 3.4.6"""
+
+        and:
+        def determiner = new GppVersionDeterminer(producer(builder), new GppVersionDeterminer.GppVersionOutputScraper())
+        def binary = new File("g++")
+
+        when:
+        String version = determiner.transform(binary)
+
+        then:
+        1 * builder.build() >> handle
+        1 * builder.setErrorOutput(_) >> { OutputStream out -> out << output; builder }
+        1 * handle.start() >> handle
+        1 * handle.waitForFinish() >> result
+        1 * result.getExitValue() >> 0
+
+        and:
+        version == "3.4.6"
+    }
+
+    Transformer<String, File> producer(ExecHandleBuilder builder) {
+        new GppVersionDeterminer.GppVersionOutputProducer(new Factory() {
+            def create() {
+                builder
+            }
+        })
+    }
+
+    String output(String output) {
+        new GppVersionDeterminer(transformer(output), new GppVersionDeterminer.GppVersionOutputScraper()).transform(new File("."))
+    }
+
+    String scraped(String scraped) {
+        new GppVersionDeterminer(transformer("doesntmatter"), transformer(scraped)).transform(new File("."))
+    }
+
+    Transformer transformer(constant) {
+        transformer { constant }
+    }
+
+    Transformer transformer(Closure closure) {
+        new Transformer() {
+            String transform(original) {
+                closure.call(original)
+            }
+        }
+    }
+
+    static final OUTPUTS = [
+            """Using built-in specs.
+Target: i686-apple-darwin11
+Configured with: /private/var/tmp/llvmgcc42/llvmgcc42-2336.1~22/src/configure
+Thread model: posix
+gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.1.00)""": "4.2.1",
+            """Reading specs from /opt/gcc/3.4.6/usr/local/bin/../lib/gcc/i686-pc-linux-gnu/3.4.6/specs
+Configured with: /home/ld/Downloads/gcc-3.4.6/configure
+Thread model: posix
+gcc version 3.4.6""": "3.4.6",
+            """Reading specs from /opt/gcc/3.4.6/usr/local/bin/../lib/gcc/i686-pc-linux-gnu/3.4.6/specs
+Configured with: /home/ld/Downloads/gcc-3.4.6/configure
+Thread model: posix
+gcc version 3.4.6-sometag""": "3.4.6-sometag"
+    ]
+}
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
old mode 100644
new mode 100755
index 4042c52..83d9557
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -17,11 +17,15 @@
 import org.gradle.build.docs.UserGuideTransformTask
 import org.gradle.build.docs.ExtractSnippetsTask
 import org.gradle.build.docs.AssembleSamplesDocTask
+import org.gradle.build.docs.Docbook2Xhtml
 import org.gradle.build.docs.dsl.docbook.AssembleDslDocTask
 import org.gradle.build.docs.dsl.ExtractDslMetaDataTask
+import org.gradle.build.GenerateReleasesXml
+import org.gradle.internal.os.OperatingSystem
 
 apply plugin: 'base'
-apply from: "${rootDir}/gradle/ssh.gradle"
+apply plugin: 'pegdown'
+apply plugin: 'jsoup'
 
 configurations {
     userGuideStyleSheets
@@ -38,20 +42,29 @@ dependencies {
     userGuideStyleSheets 'docbook:docbook-xsl:1.75.2 at zip'
 }
 
-RemoteLocations remoteLocations = new RemoteLocations(version: version)
-
-srcDocsDir = file('src/docs')
-userguideSrcDir = new File(srcDocsDir, 'userguide')
-cssSrcDir = new File(srcDocsDir, 'css')
-dslSrcDir = new File(srcDocsDir, 'dsl')
+ext {
+    srcDocsDir = file('src/docs')
+    userguideSrcDir = new File(srcDocsDir, 'userguide')
+    cssSrcDir = new File(srcDocsDir, 'css')
+    dslSrcDir = new File(srcDocsDir, 'dsl')
+    docsDir = file("$buildDir/docs")
+    userguideDir = new File(docsDir, 'userguide')
+    distDocsDir = new File(buildDir, 'distDocs')
+    samplesDir = file("$buildDir/samples")
+    docbookSrc = new File(project.buildDir, 'src')
+    samplesSrcDir = file('src/samples')
+}
 
-docsDir = file("$buildDir/docs")
-userguideDir = new File(docsDir, 'userguide')
-distDocsDir = new File(buildDir, 'distDocs')
-samplesDir = file("$buildDir/samples")
-docbookSrc = new File(project.buildDir, 'src')
-samplesSrcDir = file('src/samples')
-websiteDocs = new File(buildDir, 'websiteDocs')
+ext.outputs = [:]
+outputs.samples = files(samplesDir) {
+    builtBy 'samples'
+}
+outputs.distDocs = files(distDocsDir) {
+    builtBy 'distDocs'
+}
+outputs.docs = files(docsDir) {
+    builtBy 'javadoc', 'groovydoc', 'userguide', 'dslHtml', 'releaseNotes'
+}
 
 tasks.withType(Docbook2Xhtml) {
     dependsOn userguideStyleSheets
@@ -59,13 +72,11 @@ tasks.withType(Docbook2Xhtml) {
     stylesheetsDir = userguideStyleSheets.destinationDir
 }
 tasks.withType(UserGuideTransformTask) {
-    classpath = configurations.userGuideTask
     dependsOn samples, dslDocbook
     snippetsDir = samples.snippetsDir
     linksFile = dslDocbook.linksFile
 }
 tasks.withType(AssembleDslDocTask) {
-    classpath = configurations.userGuideTask
     classDocbookDir = dslSrcDir
 }
 
@@ -106,13 +117,13 @@ task samplesDocbook(type: AssembleSamplesDocTask) {
     destFile = new File(docbookSrc, 'samplesList.xml')
 }
 
-task samplesDocs(type: Docbook2Xhtml, dependsOn: samplesDocbook) {
-    source samplesDocbook.destFile
+task samplesDocs(type: Docbook2Xhtml) {
+    source samplesDocbook
     destFile = new File(samples.destDir, 'readme.html')
     stylesheetName = 'standaloneHtml.xsl'
 }
 
-task dslMetaData(type: ExtractDslMetaDataTask) {
+task dslMetaData(type: ExtractDslMetaDataTask) {  //TODO SF: parseSourceCode
     source { groovydoc.source }
     destFile = new File(docbookSrc, 'dsl-meta-data.bin')
 }
@@ -127,8 +138,6 @@ task dslDocbook(type: AssembleDslDocTask, dependsOn: [dslMetaData]) {
 }
 
 task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook]) {
-    inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
-    inputs.files fileTree(dir: docbookSrc, includes: ['*.xml'])
     sourceFile = dslDocbook.destFile
     destFile = new File(docbookSrc, 'dsl-standalone.xml')
     javadocUrl = '../javadoc'
@@ -137,97 +146,99 @@ task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook])
     websiteUrl = 'http://www.gradle.org'
 }
 
-task dslHtml(type: Docbook2Xhtml, dependsOn: dslStandaloneDocbook) {
-    source dslStandaloneDocbook.destFile
+task dslHtml(type: Docbook2Xhtml) {
+    source dslStandaloneDocbook
     destDir = new File(docsDir, 'dsl')
     stylesheetName = 'dslHtml.xsl'
-    resources = fileTree {
-        from cssSrcDir
+    resources = fileTree(cssSrcDir) {
         include '*.css'
-    } + fileTree {
-        from dslSrcDir
+    } + fileTree(dslSrcDir) {
         include '*.js'
     }
 }
 
+// This is used in the distribution and for the online version
 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')
     destFile = new File(docbookSrc, 'userguide.xml')
     javadocUrl = '../javadoc'
     groovydocUrl = '../groovydoc'
     dsldocUrl = '../dsl'
-    websiteUrl = 'http://www.gradle.org'
 }
 
-task remoteUserguideDocbook(type: UserGuideTransformTask, dependsOn: samples) {
-    inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
-    inputs.files fileTree(dir: docbookSrc, includes: ['*.xml'])
-    sourceFile = new File(userguideSrcDir, 'userguide.xml')
+// This is used for the PDF, where we need absolute links to the javadoc etc.
+task pdfUserguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
     destFile = new File(docbookSrc, 'remoteUserguide.xml')
-    gradle.taskGraph.whenReady {
-        javadocUrl = remoteLocations.javadocUrl
-        groovydocUrl = remoteLocations.groovydocUrl
-        dsldocUrl = remoteLocations.dsldocUrl
-        websiteUrl = 'http://www.gradle.org'
-    }
+    javadocUrl = project.version.javadocUrl
+    groovydocUrl = project.version.groovydocUrl
+    dsldocUrl = project.version.dsldocUrl
 }
 
-task userguideHtml(type: Docbook2Xhtml, dependsOn: userguideDocbook) {
-    source userguideDocbook.destFile
+configure([userguideDocbook, pdfUserguideDocbook]) {
+    // The master userguide.xml pulls these files in via xi:include, making them input
+    inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
+
+    sourceFile new File(userguideSrcDir, 'userguide.xml')
+    websiteUrl 'http://www.gradle.org'
+}
+
+task userguideHtml(type: Docbook2Xhtml) {
+    source userguideDocbook
     destDir = userguideDir
     stylesheetName = 'userGuideHtml.xsl'
-    resources = fileTree {
-        from userguideSrcDir
+    resources = fileTree(userguideSrcDir) {
         include 'img/*.png'
     }
-    resources += fileTree {
-        from cssSrcDir
+    resources += fileTree(cssSrcDir) {
         include '*.css'
     }
 }
 
-task userguideSingleHtml(type: Docbook2Xhtml, dependsOn: userguideDocbook) {
-    source userguideDocbook.destFile
+task userguideSingleHtml(type: Docbook2Xhtml) {
+    source userguideDocbook
     destFile = new File(userguideDir, 'userguide_single.html')
     stylesheetName = 'userGuideSingleHtml.xsl'
-    resources = fileTree {
-        from userguideSrcDir
+    resources = fileTree(userguideSrcDir) {
         include 'img/*.png'
     }
-    resources += fileTree {
-        from cssSrcDir
+    resources += fileTree(cssSrcDir) {
         include '*.css'
     }
 }
 
-task userguideXhtml(type: Docbook2Xhtml, dependsOn: remoteUserguideDocbook) {
-    source remoteUserguideDocbook.destFile
+task pdfUserguideXhtml(type: Docbook2Xhtml) {
+    source pdfUserguideDocbook
     destFile = new File(buildDir, 'tmp/userguidePdf/userguidePdf.html')
     stylesheetName = 'userGuidePdf.xsl'
-    resources = fileTree {
-        from userguideSrcDir
+    resources = fileTree(userguideSrcDir) {
         include 'img/*.png'
     }
-    resources += fileTree {
-        from cssSrcDir
+    resources += fileTree(cssSrcDir) {
         include '*.css'
     }
 }
 
-task userguidePdf(type: Xhtml2Pdf, dependsOn: userguideXhtml) {
+task userguidePdf(type: Xhtml2Pdf, dependsOn: pdfUserguideXhtml) {
     inputs.dir cssSrcDir
-    sourceFile = userguideXhtml.destFile
+    sourceFile = pdfUserguideXhtml.destFile
     destFile = new File(userguideDir, 'userguide.pdf')
     classpath = configurations.userGuideTask
 }
 
+def javaApiUrl = "http://docs.oracle.com/javase/1.5.0/docs/api/"
+def groovyApiUrl = "http://groovy.codehaus.org/gapi/"
+
 task javadoc(type: Javadoc) {
+    ext.stylesheetFile = file("src/docs/css/javadoc.css")
+    inputs.file stylesheetFile
+
     group = 'documentation'
+    options.encoding = 'utf-8'
+    options.docEncoding = 'utf-8'
+    options.charSet = 'utf-8'
+    options.addStringOption "stylesheetfile", stylesheetFile.absolutePath
     source groovyProjects().collect {project -> project.sourceSets.main.allJava }
     destinationDir = new File(docsDir, 'javadoc')
-    classpath = files(groovyProjects().collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.classes] })
+    classpath = files(groovyProjects().collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.output] })
     include 'org/gradle/api/**'
     include 'org/gradle/*'
     include 'org/gradle/external/javadoc/**'
@@ -236,21 +247,27 @@ task javadoc(type: Javadoc) {
     include 'org/gradle/testfixtures/**'
     include 'org/gradle/tooling/**'
     exclude '**/internal/**'
-    options.links("http://download.oracle.com/javase/1.5.0/docs/api", "http://groovy.codehaus.org/gapi/", "http://maven.apache.org/ref/2.2.1/maven-core/apidocs",
+    options.links(javaApiUrl, groovyApiUrl, "http://maven.apache.org/ref/2.2.1/maven-core/apidocs",
         "http://maven.apache.org/ref/2.2.1/maven-model/apidocs")
     doFirst {
         title = "Gradle API $version"
     }
 }
 
-task checkstyleApi(type: Checkstyle) {
-    source javadoc.source
-    configFile = file("$checkstyleConfigDir/checkstyle-api.xml")
-    classpath = files()
-    resultFile = file("$checkstyleResultsDir/checkstyle-api.xml")
+task configureGroovydoc {
+    doFirst {
+        project.configure(groovydoc) {
+            [javaApiUrl, groovyApiUrl].each {
+                link(it, *(new URL("$it/package-list").text.tokenize("\n")))
+            }
+            docTitle = "Gradle API $version"
+            windowTitle = "Gradle API $version"
+            footer = "Gradle API $version"
+        }
+    }
 }
 
-task groovydoc(type: Groovydoc) {
+task groovydoc(type: Groovydoc, dependsOn: configureGroovydoc) {
     group = 'documentation'
     source groovyProjects().collect {project -> project.sourceSets.main.groovy + project.sourceSets.main.java }
     destinationDir = new File(docsDir, 'groovydoc')
@@ -258,71 +275,50 @@ task groovydoc(type: Groovydoc) {
     includes = javadoc.includes
     excludes = javadoc.excludes
     doFirst {
-        title = "Gradle API $version"
+        windowTitle = "Gradle API $version"
+        docTitle = windowTitle
     }
     groovyClasspath = project(':core').configurations.groovy
-}
-
-task userguideFragmentSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples]) {
-    tags << 'standalone'
-    sourceFile = new File(userguideSrcDir, 'installation.xml')
-    destFile = new File(docbookSrc, 'installation.xml')
-    gradle.taskGraph.whenReady {
-        javadocUrl = remoteLocations.javadocUrl
-        groovydocUrl = remoteLocations.groovydocUrl
-        dsldocUrl = remoteLocations.dsldocUrl
-        websiteUrl = 'http://www.gradle.org'
+    doLast {
+        def index = new File(destinationDir, "index.html")
+        index.text = index.text.replace("{todo.title}", windowTitle) // workaround groovydoc bug
     }
-}
-
-task distDocs(type: Docbook2Xhtml, dependsOn: userguideFragmentSrc) {
-    source userguideFragmentSrc.destFile
-    destFile = new File(distDocsDir, 'getting-started.html')
-    stylesheetName = 'standaloneHtml.xsl'
-}
 
-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')
-    destFile = new File(docbookSrc, 'website.xml')
-    tags << 'website'
     gradle.taskGraph.whenReady {
-        javadocUrl = remoteLocations.javadocUrl
-        groovydocUrl = remoteLocations.groovydocUrl
-        dsldocUrl = remoteLocations.dsldocUrl
-        websiteUrl = ''
+        if (it.hasTask(groovydoc)) {
+            def systemCharset = java.nio.charset.Charset.defaultCharset().name()
+            if (systemCharset != "UTF-8") {
+                if (isReleaseBuild()) {
+                    throw new InvalidUserDataException("Cannot run $groovydoc.name task unless system charset is UTF-8 (it's $systemCharset, set -Dfile.encoding=UTF-8) in GRADLE_OPTS")
+                } else {
+                    logger.warn("Groovydoc will be generated in this build, but the default character encoding is '$systemCharset'. It should be 'UTF-8'. This is ok for a non release build.")
+                }
+            }
+        }
     }
 }
 
-task websiteUserguide(type: Docbook2Xhtml, dependsOn: websiteUserguideSrc) {
-    source websiteUserguideSrc.destFile
-    destFile = new File(websiteDocs, 'website.html')
-    stylesheetName = 'websiteHtml.xsl'
-    resources = fileTree {
-        from userguideSrcDir
-        include 'img/*.png'
-    }
-    resources += fileTree {
-        from cssSrcDir
-        include '*.css'
-    }
+task checkstyleApi(type: Checkstyle) {
+    source javadoc.source
+    configFile = file("$checkstyleConfigDir/checkstyle-api.xml")
+    classpath = files()
+    reports.xml.destination = file("$checkstyle.reportsDir/checkstyle-api.xml")
 }
 
-task websiteProperties {
-    def propertiesFile = new File(websiteDocs, 'version.properties')
-    inputs.property 'version', { version.toString() }
-    outputs.files propertiesFile
-    doLast {
-        def properties = new Properties()
-        properties.version = version.toString()
-        propertiesFile.parentFile.mkdirs()
-        propertiesFile.withOutputStream { outputStream -> properties.store(outputStream, 'documentation version properties') }
-    }
+task userguideFragmentSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples]) {
+    tags << 'standalone'
+    sourceFile = new File(userguideSrcDir, 'installation.xml')
+    destFile = new File(docbookSrc, 'installation.xml')
+    javadocUrl = project.version.javadocUrl
+    groovydocUrl = project.version.groovydocUrl
+    dsldocUrl = project.version.dsldocUrl
+    websiteUrl = 'http://www.gradle.org'
 }
 
-task websiteDocs {
-    dependsOn websiteUserguide, websiteProperties
+task distDocs(type: Docbook2Xhtml) {
+    source userguideFragmentSrc
+    destFile = new File(distDocsDir, 'getting-started.html')
+    stylesheetName = 'standaloneHtml.xsl'
 }
 
 task userguide {
@@ -337,118 +333,46 @@ task docs {
     group = 'documentation'
 }
 
-task uploadDocs(dependsOn: docs, type: Scp) {
-    sourceDir = docsDir
-    host = 'gradle01.managed.contegix.com'
-    gradle.taskGraph.whenReady {
-        destinationDir = remoteLocations.docsUploadDir
-    }
-    userName = project.hasProperty('websiteScpUserName') ? project.websiteScpUserName : null
-    password = project.hasProperty('websiteScpUserPassword') ? project.websiteScpUserPassword : null
-}
-gradle.taskGraph.whenReady { graph ->
-    if (graph.hasTask(uploadDocs)) {
-        // fail early
-        project.websiteScpUserName
-        project.websiteScpUserPassword
-    }
+task docsZip(type: Zip) {
+    from project.outputs.docs
 }
 
-class RemoteLocations {
-    def version
+import org.gradle.plugins.pegdown.PegDown
+import org.gradle.plugins.jsoup.Jsoup
 
-    static final GRADLE_ORG_URL = "http://www.gradle.org"
-
-    String getDistributionUploadUrl() {
-        version.isRelease() ? 'https://dav.codehaus.org/dist/gradle' : 'https://dav.codehaus.org/snapshots.dist/gradle'
-    }
-
-    String getDocsUploadDir() {
-        '/var/www/domains/gradle.org/www/htdocs/releases/' + docsRemoteDir
-    }
-
-    String getDocsRemoteDir() {
-        (version.isRelease() ? version.toString() : 'latest') + '/docs'
-    }
-
-    String getJavadocUrl() {
-        "$GRADLE_ORG_URL/${getDocsRemoteDir()}/javadoc"
-    }
-
-    String getGroovydocUrl() {
-        "$GRADLE_ORG_URL/${getDocsRemoteDir()}/groovydoc"
-    }
-
-    String getDsldocUrl() {
-        "$GRADLE_ORG_URL/${getDocsRemoteDir()}/dsl"
-    }
+task editReleaseNotes() << {
+    new java.awt.Desktop().edit(file("src/docs/release/notes.md"))
 }
 
-class Docbook2Xhtml extends SourceTask {
-    @InputFiles
-    FileCollection classpath
-
-    @OutputFile @Optional
-    File destFile
-
-    @OutputDirectory @Optional
-    File destDir
-
-    @InputDirectory
-    File stylesheetsDir
-
-    String stylesheetName
-
-    @InputFiles @Optional
-    FileCollection resources
-
-    @TaskAction
-    def transform() {
-        if (!((destFile != null) ^ (destDir != null))) {
-            throw new InvalidUserDataException("Must specify exactly 1 of output file or dir.")
-        }
-
-        source.visit { FileVisitDetails fvd ->
-            if (fvd.isDirectory()) {
-                return
-            }
+task releaseNotesMarkdown(type: PegDown) {
+    source "src/docs/release/notes.md"
+    destination "$buildDir/release-notes/notes-raw.html"
+}
 
-            ant.java(classname: 'org.apache.xalan.xslt.Process', failonerror: true, fork: true) {
-                jvmarg(value: '-Xmx256m')
-                arg(value: '-in')
-                arg(value: fvd.file)
-                if (destFile) {
-                    arg(value: '-out')
-                    arg(value: destFile)
-                } else {
-                    arg(value: '-out')
-                    File outFile = fvd.relativePath.replaceLastName(fvd.file.name.replaceAll('.xml$', '.html')).getFile(destDir)
-                    outFile.parentFile.mkdirs()
-                    arg(value: outFile)
-                }
-                arg(value: '-xsl')
-                arg(value: new File(stylesheetsDir, stylesheetName))
-                if (destDir) {
-                    arg(line: "-param base.dir ${destDir}/")
-                }
-                sysproperty(key: 'xslthl.config', value: new File("$stylesheetsDir/highlighting/xslthl-config.xml").toURI())
-                sysproperty(key: 'org.apache.xerces.xni.parser.XMLParserConfiguration', value: 'org.apache.xerces.parsers.XIncludeParserConfiguration')
-                classpath {
-                    path(path: classpath.asPath)
-                    path(location: new File(stylesheetsDir, 'extensions/xalan27.jar'))
-                }
-            }
-        }
+task decorateReleaseNotes(type: Jsoup) {
+    source releaseNotesMarkdown
+    destination "$buildDir/release-notes/notes-decorated.html"
+    
+    inputs.file "release-notes-transform.gradle"
+    apply from: "release-notes-transform.gradle"
+}
 
-        if (resources) {
-            project.copy {
-                into this.destDir ?: destFile.parentFile
-                from resources
-            }
+import org.apache.tools.ant.filters.*
+task releaseNotes(type: Copy) {
+    ext.fileName = "release-notes.html"
+    into "$docsDir"
+    from decorateReleaseNotes, {
+        rename ".+", "release-notes.html"
+        doFirst {
+            owner.filter(ReplaceTokens, tokens: [version: project.version.toString()])
         }
     }
 }
 
+task viewReleaseNotes(dependsOn: releaseNotes) << {
+    new java.awt.Desktop().browse(new File(releaseNotes.destinationDir, releaseNotes.fileName).toURI())
+}
+
 class Xhtml2Pdf extends DefaultTask {
     @InputFile
     File sourceFile
@@ -459,10 +383,6 @@ class Xhtml2Pdf extends DefaultTask {
     @InputFiles
     FileCollection classpath
 
-    def Xhtml2Pdf() {
-        onlyIf { !OperatingSystem.current().isWindows() }
-    }
-
     @TaskAction
     def transform() {
         def uris = classpath.files.collect {it.toURI().toURL()}
diff --git a/subprojects/docs/release-notes-transform.gradle b/subprojects/docs/release-notes-transform.gradle
new file mode 100644
index 0000000..2dcc005
--- /dev/null
+++ b/subprojects/docs/release-notes-transform.gradle
@@ -0,0 +1,157 @@
+import org.jsoup.nodes.Element
+import org.jsoup.select.Elements
+
+decorateReleaseNotes {
+    
+    ext {
+        styleFile = file("src/docs/release/content/style.css")
+        fontRegularFile = file("src/docs/release/content/Lato-regular.woff")
+        fontBoldFile = file("src/docs/release/content/Lato-bold.woff")
+        logoFile = file("src/docs/release/content/logo.gif")
+        jqueryFile = file("src/docs/release/content/jquery-1.7.2-min.js")
+        scriptFile = file("src/docs/release/content/script.js")
+    }
+    
+    inputs.files([styleFile, fontRegularFile, fontBoldFile, logoFile, jqueryFile, scriptFile])
+    
+    transform {
+        outputSettings().indentAmount(2).prettyPrint(true)
+        
+        prependChild(new org.jsoup.nodes.DocumentType("html", "", "", ""))
+
+        head().
+            append("<meta charset='utf-8'>").
+            append("<title>Gradle @version@ Release Notes</title>")
+        
+        def styleText = styleFile.text.
+            replace("@regular-font-base64@", fontRegularFile.bytes.encodeBase64().toString()).
+            replace("@bold-font-base64@", fontBoldFile.bytes.encodeBase64().toString())
+        
+        head().append("<style/>").children().last().text(styleText)
+        
+        head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", jqueryFile.text)
+        head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", scriptFile.text)
+    }
+    
+    // wrap each h2 section in section.topic
+    transform {
+        def heading = body().select("h2").first()
+        def inSection = [heading]
+        Element next = heading.nextElementSibling()
+        while (true) {
+            if (next == null || next.tagName() == "h2") {
+                def section = heading.before("<section class='topic'/>").previousElementSibling()
+                Elements inSectionElements = new Elements(inSection)
+                section.html(inSectionElements.outerHtml())
+                inSectionElements.remove()
+
+                if (next == null) {
+                    break
+                } else {
+                    inSection = [next]
+                    heading = next
+                }
+            } else {
+                inSection << next
+            }
+
+            next = next.nextElementSibling()
+        }
+    }
+    
+    // wrap all content after the first element after a h3 (up to the next same level heading)
+    // in a section.major-detail block
+    transform {
+        for (heading in body().select(".topic").first().select("h3")) {
+            def detail = []
+            
+            Element next = heading.nextElementSibling()
+            while (next != null && next.tagName() != "h4") {
+                next = next.nextElementSibling()
+            }
+            
+            while (true) {
+                if (next == null || next.tagName() ==~ /h[123]/) {
+                    break
+                }
+                detail << next
+                next = next.nextElementSibling()
+            }
+
+            if (detail) {
+                def section = detail.first().before("<section class='major-detail'/>").previousElementSibling()
+                Elements detailElements = new Elements(detail)
+                section.html(detailElements.outerHtml())
+                detailElements.remove()
+            }
+        }
+    }
+    
+    // wrap all content after a h4 until the next heading in a section.minor-detail
+    transform {
+        for (heading in body().select("h4")) {
+            def detail = []
+            Element next = heading.nextElementSibling()
+            while (true) {
+                if (next == null || next.tagName() ==~ /h[1234]/) {
+                    break
+                }
+                detail << next
+                next = next.nextElementSibling()
+            }
+
+            if (detail) {
+                def section = detail.first().before("<section class='minor-detail'/>").previousElementSibling()
+                Elements detailElements = new Elements(detail)
+                section.html(detailElements.outerHtml())
+                detailElements.remove()
+            }
+        }
+    }
+    
+    // add anchors for all of the headings
+    transform {
+        for (heading in body().select("h2,h3")) {
+            def anchorName = heading.text().toLowerCase().replaceAll(' ', '-')
+            heading.attr("id", anchorName)
+        }
+    }
+    
+    // Add the TOC
+    transform {
+        def tocSection = body().select("section.topic").first().before("<section class='table-of-contents'/>").previousElementSibling()
+        tocSection.append("<h2>Table Of Contents</h2>")
+        def toc = tocSection.append("<ul class='toc'/>").children().last()
+        
+        for (topic in body().select(".topic")) {
+            def topicHeading = topic.select("h2").first()
+            def name = topicHeading.text()
+            def anchor = topicHeading.attr("id")
+            
+            toc.append("<li><a href='#$anchor'>$name</a></li>").children().last()
+            
+            def subs = topic.select("h3")
+            if (subs) {
+                def sublist = toc.children().last().append("<ul class='toc-sub'/>").children().last()
+                subs.each {
+                    def subName = it.text()
+                    def subAnchorName = it.attr("id")
+                    sublist.append("<li><a href='#$subAnchorName'>$subName</a></li>")
+                }
+            }
+            
+        }
+    }
+    
+    // Add the heading
+    transform {
+        body().prepend("<h1>@version@ Release Notes</h1>")
+        body().prepend("<img class='logo' alt='Gradle Logo' src='data:image/gif;base64,${logoFile.bytes.encodeBase64()}' />")
+    }
+    
+    // Add the footer
+    transform {
+        def footer = body().append("<section class='footer'/>").children().last()
+        footer.html("Gradle @version@ Release Notes<br />— <a href='http://gradle.org'>www.gradle.org</a> —")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/css/base.css b/subprojects/docs/src/docs/css/base.css
index bc82563..6fce32a 100644
--- a/subprojects/docs/src/docs/css/base.css
+++ b/subprojects/docs/src/docs/css/base.css
@@ -55,6 +55,13 @@ code, pre {
  */
 
 /*
+ * Links
+ */
+a.link {
+    white-space: nowrap;
+}
+
+/*
  * Lists
  */
 
diff --git a/subprojects/docs/src/docs/css/javadoc.css b/subprojects/docs/src/docs/css/javadoc.css
new file mode 100644
index 0000000..8ab8dfb
--- /dev/null
+++ b/subprojects/docs/src/docs/css/javadoc.css
@@ -0,0 +1,592 @@
+/*
+Gradle javadoc stylesheet
+-------------------------
+This file is in 2 sections.
+SECTION 1: is an exact copy of the default stylesheet generated by Java7 javadoc.
+SECTION 2: contains Gradle-specific tweaks to the styles.
+
+TODO: I have not yet worked out how to include the original Java7 stylesheet.css as a separate file, and use @import here.
+*/
+
+/* SECTION 1 - Default Java7 Style Sheet */
+/* Javadoc style sheet */
+/*
+Overall document style
+*/
+body {
+    background-color:#ffffff;
+    color:#353833;
+    font-family:Arial, Helvetica, sans-serif;
+    font-size:76%;
+    margin:0;
+}
+a:link, a:visited {
+    text-decoration:none;
+    color:#4c6b87;
+}
+a:hover, a:focus {
+    text-decoration:none;
+    color:#bb7a2a;
+}
+a:active {
+    text-decoration:none;
+    color:#4c6b87;
+}
+a[name] {
+    color:#353833;
+}
+a[name]:hover {
+    text-decoration:none;
+    color:#353833;
+}
+pre {
+    font-size:1.3em;
+}
+h1 {
+    font-size:1.8em;
+}
+h2 {
+    font-size:1.5em;
+}
+h3 {
+    font-size:1.4em;
+}
+h4 {
+    font-size:1.3em;
+}
+h5 {
+    font-size:1.2em;
+}
+h6 {
+    font-size:1.1em;
+}
+ul {
+    list-style-type:disc;
+}
+code, tt {
+    font-size:1.2em;
+}
+dt code {
+    font-size:1.2em;
+}
+table tr td dt code {
+    font-size:1.2em;
+    vertical-align:top;
+}
+sup {
+    font-size:.6em;
+}
+/*
+Document title and Copyright styles
+*/
+.clear {
+    clear:both;
+    height:0px;
+    overflow:hidden;
+}
+.aboutLanguage {
+    float:right;
+    padding:0px 21px;
+    font-size:.8em;
+    z-index:200;
+    margin-top:-7px;
+}
+.legalCopy {
+    margin-left:.5em;
+}
+.bar a, .bar a:link, .bar a:visited, .bar a:active {
+    color:#FFFFFF;
+    text-decoration:none;
+}
+.bar a:hover, .bar a:focus {
+    color:#bb7a2a;
+}
+.tab {
+    background-color:#0066FF;
+    background-image:url(resources/titlebar.gif);
+    background-position:left top;
+    background-repeat:no-repeat;
+    color:#ffffff;
+    padding:8px;
+    width:5em;
+    font-weight:bold;
+}
+/*
+Navigation bar styles
+*/
+.bar {
+    background-image:url(resources/background.gif);
+    background-repeat:repeat-x;
+    color:#FFFFFF;
+    padding:.8em .5em .4em .8em;
+    height:auto;/*height:1.8em;*/
+    font-size:1em;
+    margin:0;
+}
+.topNav {
+    background-image:url(resources/background.gif);
+    background-repeat:repeat-x;
+    color:#FFFFFF;
+    float:left;
+    padding:0;
+    width:100%;
+    clear:right;
+    height:2.8em;
+    padding-top:10px;
+    overflow:hidden;
+}
+.bottomNav {
+    margin-top:10px;
+    background-image:url(resources/background.gif);
+    background-repeat:repeat-x;
+    color:#FFFFFF;
+    float:left;
+    padding:0;
+    width:100%;
+    clear:right;
+    height:2.8em;
+    padding-top:10px;
+    overflow:hidden;
+}
+.subNav {
+    background-color:#dee3e9;
+    border-bottom:1px solid #9eadc0;
+    float:left;
+    width:100%;
+    overflow:hidden;
+}
+.subNav div {
+    clear:left;
+    float:left;
+    padding:0 0 5px 6px;
+}
+ul.navList, ul.subNavList {
+    float:left;
+    margin:0 25px 0 0;
+    padding:0;
+}
+ul.navList li{
+    list-style:none;
+    float:left;
+    padding:3px 6px;
+}
+ul.subNavList li{
+    list-style:none;
+    float:left;
+    font-size:90%;
+}
+.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {
+    color:#FFFFFF;
+    text-decoration:none;
+}
+.topNav a:hover, .bottomNav a:hover {
+    text-decoration:none;
+    color:#bb7a2a;
+}
+.navBarCell1Rev {
+    background-image:url(resources/tab.gif);
+    background-color:#a88834;
+    color:#FFFFFF;
+    margin: auto 5px;
+    border:1px solid #c9aa44;
+}
+/*
+Page header and footer styles
+*/
+.header, .footer {
+    clear:both;
+    margin:0 20px;
+    padding:5px 0 0 0;
+}
+.indexHeader {
+    margin:10px;
+    position:relative;
+}
+.indexHeader h1 {
+    font-size:1.3em;
+}
+.title {
+    color:#2c4557;
+    margin:10px 0;
+}
+.subTitle {
+    margin:5px 0 0 0;
+}
+.header ul {
+    margin:0 0 25px 0;
+    padding:0;
+}
+.footer ul {
+    margin:20px 0 5px 0;
+}
+.header ul li, .footer ul li {
+    list-style:none;
+    font-size:1.2em;
+}
+/*
+Heading styles
+*/
+div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 {
+    background-color:#dee3e9;
+    border-top:1px solid #9eadc0;
+    border-bottom:1px solid #9eadc0;
+    margin:0 0 6px -8px;
+    padding:2px 5px;
+}
+ul.blockList ul.blockList ul.blockList li.blockList h3 {
+    background-color:#dee3e9;
+    border-top:1px solid #9eadc0;
+    border-bottom:1px solid #9eadc0;
+    margin:0 0 6px -8px;
+    padding:2px 5px;
+}
+ul.blockList ul.blockList li.blockList h3 {
+    padding:0;
+    margin:15px 0;
+}
+ul.blockList li.blockList h2 {
+    padding:0px 0 20px 0;
+}
+/*
+Page layout container styles
+*/
+.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer {
+    clear:both;
+    padding:10px 20px;
+    position:relative;
+}
+.indexContainer {
+    margin:10px;
+    position:relative;
+    font-size:1.0em;
+}
+.indexContainer h2 {
+    font-size:1.1em;
+    padding:0 0 3px 0;
+}
+.indexContainer ul {
+    margin:0;
+    padding:0;
+}
+.indexContainer ul li {
+    list-style:none;
+}
+.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt {
+    font-size:1.1em;
+    font-weight:bold;
+    margin:10px 0 0 0;
+    color:#4E4E4E;
+}
+.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd {
+    margin:10px 0 10px 20px;
+}
+.serializedFormContainer dl.nameValue dt {
+    margin-left:1px;
+    font-size:1.1em;
+    display:inline;
+    font-weight:bold;
+}
+.serializedFormContainer dl.nameValue dd {
+    margin:0 0 0 1px;
+    font-size:1.1em;
+    display:inline;
+}
+/*
+List styles
+*/
+ul.horizontal li {
+    display:inline;
+    font-size:0.9em;
+}
+ul.inheritance {
+    margin:0;
+    padding:0;
+}
+ul.inheritance li {
+    display:inline;
+    list-style:none;
+}
+ul.inheritance li ul.inheritance {
+    margin-left:15px;
+    padding-left:15px;
+    padding-top:1px;
+}
+ul.blockList, ul.blockListLast {
+    margin:10px 0 10px 0;
+    padding:0;
+}
+ul.blockList li.blockList, ul.blockListLast li.blockList {
+    list-style:none;
+    margin-bottom:25px;
+}
+ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList {
+    padding:0px 20px 5px 10px;
+    border:1px solid #9eadc0;
+    background-color:#f9f9f9;
+}
+ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList {
+    padding:0 0 5px 8px;
+    background-color:#ffffff;
+    border:1px solid #9eadc0;
+    border-top:none;
+}
+ul.blockList ul.blockList ul.blockList ul.blockList li.blockList {
+    margin-left:0;
+    padding-left:0;
+    padding-bottom:15px;
+    border:none;
+    border-bottom:1px solid #9eadc0;
+}
+ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast {
+    list-style:none;
+    border-bottom:none;
+    padding-bottom:0;
+}
+table tr td dl, table tr td dl dt, table tr td dl dd {
+    margin-top:0;
+    margin-bottom:1px;
+}
+/*
+Table styles
+*/
+.contentContainer table, .classUseContainer table, .constantValuesContainer table {
+    border-bottom:1px solid #9eadc0;
+    width:100%;
+}
+.contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table {
+    width:100%;
+}
+.contentContainer .description table, .contentContainer .details table {
+    border-bottom:none;
+}
+.contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td{
+    vertical-align:top;
+    padding-right:20px;
+}
+.contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast,.constantValuesContainer ul li table th.colLast,
+.contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast,.constantValuesContainer ul li table td.colLast,
+.contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne,
+.contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne {
+    padding-right:3px;
+}
+.overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption {
+    position:relative;
+    text-align:left;
+    background-repeat:no-repeat;
+    color:#FFFFFF;
+    font-weight:bold;
+    clear:none;
+    overflow:hidden;
+    padding:0px;
+    margin:0px;
+}
+caption a:link, caption a:hover, caption a:active, caption a:visited {
+    color:#FFFFFF;
+}
+.overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span {
+    white-space:nowrap;
+    padding-top:8px;
+    padding-left:8px;
+    display:block;
+    float:left;
+    background-image:url(resources/titlebar.gif);
+    height:18px;
+}
+.overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd {
+    width:10px;
+    background-image:url(resources/titlebar_end.gif);
+    background-repeat:no-repeat;
+    background-position:top right;
+    position:relative;
+    float:left;
+}
+ul.blockList ul.blockList li.blockList table {
+    margin:0 0 12px 0px;
+    width:100%;
+}
+.tableSubHeadingColor {
+    background-color: #EEEEFF;
+}
+.altColor {
+    background-color:#eeeeef;
+}
+.rowColor {
+    background-color:#ffffff;
+}
+.overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td {
+    text-align:left;
+    padding:3px 3px 3px 7px;
+}
+th.colFirst, th.colLast, th.colOne, .constantValuesContainer th {
+    background:#dee3e9;
+    border-top:1px solid #9eadc0;
+    border-bottom:1px solid #9eadc0;
+    text-align:left;
+    padding:3px 3px 3px 7px;
+}
+td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover {
+    font-weight:bold;
+}
+td.colFirst, th.colFirst {
+    border-left:1px solid #9eadc0;
+    white-space:nowrap;
+}
+td.colLast, th.colLast {
+    border-right:1px solid #9eadc0;
+}
+td.colOne, th.colOne {
+    border-right:1px solid #9eadc0;
+    border-left:1px solid #9eadc0;
+}
+table.overviewSummary  {
+    padding:0px;
+    margin-left:0px;
+}
+table.overviewSummary td.colFirst, table.overviewSummary th.colFirst,
+table.overviewSummary td.colOne, table.overviewSummary th.colOne {
+    width:25%;
+    vertical-align:middle;
+}
+table.packageSummary td.colFirst, table.overviewSummary th.colFirst {
+    width:25%;
+    vertical-align:middle;
+}
+/*
+Content styles
+*/
+.description pre {
+    margin-top:0;
+}
+.deprecatedContent {
+    margin:0;
+    padding:10px 0;
+}
+.docSummary {
+    padding:0;
+}
+/*
+Formatting effect styles
+*/
+.sourceLineNo {
+    color:green;
+    padding:0 30px 0 0;
+}
+h1.hidden {
+    visibility:hidden;
+    overflow:hidden;
+    font-size:.9em;
+}
+.block {
+    display:block;
+    margin:3px 0 0 0;
+}
+.strong {
+    font-weight:bold;
+}
+
+
+/* SECTION 2 - Gradle style overrides */
+/*
+Overall document style
+*/
+body {
+    background-color: white;
+    margin:0;
+    line-height: 150%;
+}
+body, td, div {
+    font-family: 'DejaVu Sans', 'Lucida Grande', 'Verdana', sans-serif;
+    font-size: 11pt;
+    color:#444444;
+}
+a {
+    color: #444444;
+    text-decoration: none;
+    border-bottom: dotted 1px;
+}
+a:visited, a:link {
+    color: #444444;
+}
+a:active {
+    text-decoration:none;
+    color:#4c6b87;
+}
+code, pre, tt, dt code, table tr td dt code {
+    font-family: 'Lucida Console', 'DejaVu Sans Mono', Courier, monospace;
+    font-size: inherit;
+}
+/** Formatting for code descriptions */
+.block h1, .block h2, .block h3, .block h4, .block h5 {
+    color: #4A9935;
+    line-height: 150%;
+    font-weight: bold;
+    font-family: 'DejaVu Sans', 'Lucida Grande', helvetica, sans-serif;
+}
+
+/*
+Navigation bar styles
+*/
+.bar, .topNav, .bottomNav {
+    background-image: none;
+    background-color: #ACD180;
+    color: #444444;
+}
+
+.subNav ul.navList {
+    font-size: 90%;
+}
+.subNav ul.subNavList {
+    font-size: 80%;
+}
+.subNav div {
+    padding-bottom: 0;
+}
+.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {
+    color:#444444;
+}
+ul.navList li, ul.subNavList li{
+    color: #B3B3B3;
+}
+ul.navList li.navBarCell1Rev {
+    background-image:none;
+    border: none;
+    background-color:#007042;
+    color:#FFFFFF;
+    margin: auto 5px;
+}
+
+/*
+Page header and footer styles
+*/
+.header ul li, .footer ul li {
+    font-size:inherit;
+}
+
+/*
+Table styles
+*/
+.overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption {
+    background-color: #ACD180;
+    color: #444444;
+    padding-bottom: 5px;
+}
+.overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span {
+    background-image: none;
+}
+.overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd {
+    background-image: none;
+}
+/** Don't show superfluous table headers */
+.overviewSummary th, .packageSummary th, .contentContainer ul.blockList li.blockList th, .summary th, .classUseContainer th, .constantValuesContainer th {
+    display: none;
+}
+
+/*
+Formatting effect styles
+*/
+.title {
+    color: #4A9935;
+}
+.subTitle {
+    color: #4A9935;
+}
diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml
index 822af3f..2f54fc5 100644
--- a/subprojects/docs/src/docs/dsl/dsl.xml
+++ b/subprojects/docs/src/docs/dsl/dsl.xml
@@ -1,3 +1,19 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
 <book id="dsl">
     <bookinfo>
         <title>Gradle Build Language Reference</title>
@@ -56,9 +72,12 @@
 
     <!--
       -
+      - 1. Adding new types:
       - 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.
       -
+      - 2. Adding new sections:
+      - The section title should end with 'types' (see AssembleDslDocTask.mergeContent)
       -->
 
     <section>
@@ -123,8 +142,40 @@
                 <td>org.gradle.api.tasks.SourceSet</td>
             </tr>
             <tr>
+                <td>org.gradle.api.tasks.SourceSetOutput</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.artifacts.Configuration</td>
             </tr>
+            <tr>
+                <td>org.gradle.api.artifacts.ResolutionStrategy</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.dsl.ConventionProperty</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.plugins.ExtensionAware</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.plugins.ExtraPropertiesExtension</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Container types</title>
+        <para>Container types that handle various declarative elements (e.g. dependencies, configurations, artifacts, etc.):</para>
+        <table>
+            <title>Container types</title>
+            <tr>
+                <td>org.gradle.api.artifacts.ConfigurationContainer</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.artifacts.dsl.DependencyHandler</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.artifacts.dsl.ArtifactHandler</td>
+            </tr>
         </table>
     </section>
 
@@ -161,22 +212,13 @@
                 <td>org.gradle.api.tasks.diagnostics.DependencyReportTask</td>
             </tr>
             <tr>
-                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseClasspath</td>
+                <td>org.gradle.plugins.ear.Ear</td>
             </tr>
             <tr>
-                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseJdt</td>
-            </tr>
-            <tr>
-                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseProject</td>
-            </tr>
-            <tr>
-                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent</td>
-            </tr>
-            <tr>
-                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet</td>
+                <td>org.gradle.api.tasks.Exec</td>
             </tr>
             <tr>
-                <td>org.gradle.api.tasks.Exec</td>
+                <td>org.gradle.api.plugins.quality.FindBugs</td>
             </tr>
             <tr>
                 <td>org.gradle.api.tasks.GradleBuild</td>
@@ -188,15 +230,6 @@
                 <td>org.gradle.api.tasks.javadoc.Groovydoc</td>
             </tr>
             <tr>
-                <td>org.gradle.plugins.ide.idea.GenerateIdeaModule</td>
-            </tr>
-            <tr>
-                <td>org.gradle.plugins.ide.idea.GenerateIdeaProject</td>
-            </tr>
-            <tr>
-                <td>org.gradle.plugins.ide.idea.GenerateIdeaWorkspace</td>
-            </tr>
-            <tr>
                 <td>org.gradle.api.tasks.bundling.Jar</td>
             </tr>
             <tr>
@@ -206,6 +239,9 @@
                 <td>org.gradle.api.tasks.JavaExec</td>
             </tr>
             <tr>
+                <td>org.gradle.api.plugins.quality.JDepend</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.plugins.jetty.JettyRun</td>
             </tr>
             <tr>
@@ -215,6 +251,9 @@
                 <td>org.gradle.api.plugins.jetty.JettyStop</td>
             </tr>
             <tr>
+                <td>org.gradle.api.plugins.quality.Pmd</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.diagnostics.PropertyReportTask</td>
             </tr>
             <tr>
@@ -227,7 +266,10 @@
                 <td>org.gradle.api.tasks.scala.ScalaDoc</td>
             </tr>
             <tr>
-                <td>org.gradle.api.plugins.sonar.Sonar</td>
+                <td>org.gradle.plugins.signing.Sign</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.plugins.sonar.SonarAnalyze</td>
             </tr>
             <tr>
                 <td>org.gradle.api.tasks.Sync</td>
@@ -255,4 +297,83 @@
             </tr>
         </table>
     </section>
+
+    <section>
+        <title>Eclipse/IDEA model types</title>
+        <para>Used to configure Eclipse or IDEA plugins</para>
+        <table>
+            <title>Eclipse/IDEA model types</title>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseModel</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseProject</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseClasspath</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseJdt</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseWtp</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.model.IdeaModel</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.model.IdeaProject</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.model.IdeaModule</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.model.IdeaWorkspace</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.api.XmlFileContentMerger</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.api.FileContentMerger</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Eclipse/IDEA task types</title>
+        <para>Tasks contributed by IDE plugins. To configure IDE plugins please use IDE model types.</para>
+        <table>
+            <title>Eclipse/IDEA task types</title>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseProject</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseClasspath</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseJdt</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.GenerateIdeaModule</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.GenerateIdeaProject</td>
+            </tr>
+            <tr>
+                <td>org.gradle.plugins.ide.idea.GenerateIdeaWorkspace</td>
+            </tr>
+        </table>
+    </section>
 </book>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml
index 52e0ecb..fe76585 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml
@@ -47,6 +47,9 @@
                 <td>description</td>
             </tr>
             <tr>
+                <td>extensions</td>
+            </tr>
+            <tr>
                 <td>gradle</td>
             </tr>
             <tr>
@@ -103,6 +106,9 @@
             <tr>
                 <td>version</td>
             </tr>
+            <tr>
+                <td>resources</td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.Script.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.Script.xml
index c98eaeb..f8d49e8 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.Script.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.Script.xml
@@ -16,6 +16,9 @@
             <tr>
                 <td>logging</td>
             </tr>
+            <tr>
+                <td>resources</td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml
index 05f6222..40950c3 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.Configuration.xml
@@ -35,9 +35,6 @@
                 <td>buildDependencies</td>
             </tr>
             <tr>
-                <td>buildArtifacts</td>
-            </tr>
-            <tr>
                 <td>dependencies</td>
             </tr>
             <tr>
@@ -50,10 +47,10 @@
                 <td>allArtifacts</td>
             </tr>
             <tr>
-                <td>allArtifactFiles</td>
+                <td>excludeRules</td>
             </tr>
             <tr>
-                <td>excludeRules</td>
+                <td>resolutionStrategy</td>
             </tr>
         </table>
     </section>
@@ -75,24 +72,6 @@
                 <td>fileCollection</td>
             </tr>
             <tr>
-                <td>publish</td>
-            </tr>
-            <tr>
-                <td>getDependencies</td>
-            </tr>
-            <tr>
-                <td>getAllDependencies</td>
-            </tr>
-            <tr>
-                <td>addDependency</td>
-            </tr>
-            <tr>
-                <td>addArtifact</td>
-            </tr>
-            <tr>
-                <td>removeArtifact</td>
-            </tr>
-            <tr>
                 <td>exclude</td>
             </tr>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ConfigurationContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ConfigurationContainer.xml
new file mode 100644
index 0000000..247511a
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ConfigurationContainer.xml
@@ -0,0 +1,34 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>getByName</td>
+            </tr>
+            <tr>
+                <td>getAt</td>
+            </tr>
+            <tr>
+                <td>add</td>
+            </tr>
+            <tr>
+                <td>detachedConfiguration</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
new file mode 100644
index 0000000..ddde2ac
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.ResolutionStrategy.xml
@@ -0,0 +1,37 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>forcedModules</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>failOnVersionConflict</td>
+            </tr>
+            <tr>
+                <td>force</td>
+            </tr>
+            <tr>
+                <td>cacheDynamicVersionsFor</td>
+            </tr>
+            <tr>
+                <td>cacheChangingModulesFor</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.ArtifactHandler.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.ArtifactHandler.xml
new file mode 100644
index 0000000..5bf3ebb
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.ArtifactHandler.xml
@@ -0,0 +1,25 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>add</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml
new file mode 100644
index 0000000..635cd25
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.xml
@@ -0,0 +1,40 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>add</td>
+            </tr>
+            <tr>
+                <td>create</td>
+            </tr>
+            <tr>
+                <td>module</td>
+            </tr>
+            <tr>
+                <td>project</td>
+            </tr>
+            <tr>
+                <td>gradleApi</td>
+            </tr>
+            <tr>
+                <td>localGroovy</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.dsl.ConventionProperty.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.dsl.ConventionProperty.xml
new file mode 100644
index 0000000..f46d04e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.dsl.ConventionProperty.xml
@@ -0,0 +1,22 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml
index 78776cd..7e58105 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.invocation.Gradle.xml
@@ -57,6 +57,9 @@
                 <td>projectsEvaluated</td>
             </tr>
             <tr>
+                <td>projectsLoaded</td>
+            </tr>
+            <tr>
                 <td>buildFinished</td>
             </tr>
             <tr>
@@ -71,6 +74,12 @@
             <tr>
                 <td>useLogger</td>
             </tr>
+            <tr>
+                <td>rootProject</td>
+            </tr>
+            <tr>
+                <td>allprojects</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml
index f9188a9..98ddc78 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ApplicationPluginConvention.xml
@@ -4,7 +4,7 @@
         <table>
             <thead>
                 <tr><td>Name</td>
-                    <td>Default values</td>
+                    <td>Default value</td>
                 </tr>
             </thead>
             <tr>
@@ -15,6 +15,10 @@
                 <td>mainClassName</td>
                 <td><literal>null</literal></td>
             </tr>
+            <tr>
+                <td>applicationDistribution</td>
+                <td>A copy spec that; includes all of the contents of <literal>src/dist</literal>, copies the start scripts into <literal>bin</literal>, and copies the built jar and all dependencies into <literal>lib</literal></td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ExtensionAware.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ExtensionAware.xml
new file mode 100644
index 0000000..38645ed
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ExtensionAware.xml
@@ -0,0 +1,25 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>extensions</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.xml
new file mode 100644
index 0000000..a4fd6a6
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.xml
@@ -0,0 +1,49 @@
+<!--
+  ~ Copyright 2012 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.
+  -->
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>properties</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>get</td>
+            </tr>
+            <tr>
+                <td>set</td>
+            </tr>
+            <tr>
+                <td>has</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.JavaPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.JavaPluginConvention.xml
index 5a3a489..c43fb60 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.JavaPluginConvention.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.JavaPluginConvention.xml
@@ -30,7 +30,7 @@
             </tr>
             <tr>
                 <td>testReportDir</td>
-                <td><literal><replaceable>${project.reportsDir}</replaceable>/<replaceable>${project.testReportDirName}</replaceable></literal></td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/<replaceable>${project.testReportDirName}</replaceable></literal></td>
             </tr>
             <tr>
                 <td>sourceSets</td>
@@ -38,7 +38,7 @@
             </tr>
             <tr>
                 <td>sourceCompatibility</td>
-                <td><literal>JavaVersion.JAVA_1_5</literal></td>
+                <td>Value of the current used JVM (e.g. <literal>JavaVersion.JAVA_1_6</literal> on a 1.6 JVM).</td>
             </tr>
             <tr>
                 <td>targetCompatibility</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenPluginConvention.xml
index 9924332..753a302 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenPluginConvention.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenPluginConvention.xml
@@ -9,12 +9,8 @@
                 </tr>
             </thead>
             <tr>
-                <td>pomDirName</td>
-                <td><literal>poms</literal></td>
-            </tr>
-            <tr>
-                <td>pomDir</td>
-                <td><literal><replaceable>${project.buildDir}</replaceable>/<replaceable>${project.pomDirName}</replaceable></literal></td>
+                <td>mavenPomDir</td>
+                <td><literal><replaceable>${project.buildDir}</replaceable>/poms</literal></td>
             </tr>
             <tr>
                 <td>conf2ScopeMappings</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ProjectReportsPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ProjectReportsPluginConvention.xml
index 66e009c..2ecb489 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ProjectReportsPluginConvention.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.ProjectReportsPluginConvention.xml
@@ -14,7 +14,7 @@
             </tr>
             <tr>
                 <td>projectReportDir</td>
-                <td><literal><replaceable>${project.reportsDir}</replaceable>/<replaceable>${project.projectReportDirName}</replaceable></literal></td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/<replaceable>${project.projectReportDirName}</replaceable></literal></td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.announce.AnnouncePluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.announce.AnnouncePluginConvention.xml
deleted file mode 100644
index 64490bd..0000000
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.announce.AnnouncePluginConvention.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<section>
-    <section>
-        <title>Properties</title>
-        <table>
-            <thead>
-                <tr>
-                    <td>Name</td>
-                    <td>Default with <literal>announce</literal> plugin</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>username</td>
-                <td><literal>null</literal></td>
-            </tr>
-            <tr>
-                <td>password</td>
-                <td><literal>null</literal></td>
-            </tr>
-        </table>
-    </section>
-    <section>
-        <title>Methods</title>
-        <table>
-            <thead>
-                <tr>
-                    <td>Name</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>announce</td>
-            </tr>
-        </table>
-    </section>
-</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.announce.AnnouncePluginExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.announce.AnnouncePluginExtension.xml
new file mode 100644
index 0000000..aa660c3
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.announce.AnnouncePluginExtension.xml
@@ -0,0 +1,38 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>announce</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>username</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>password</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>local</td>
+                <td>-</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>announce</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml
index 3a88251..3b8c1e0 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Checkstyle.xml
@@ -5,19 +5,37 @@
             <thead>
                 <tr>
                     <td>Name</td>
-                    <td>Default with <literal>code-quality</literal> plugin</td>
+                    <td>Default with <literal>checkstyle</literal> plugin</td>
                 </tr>
             </thead>
-            <tr><td>classpath</td><td><literal><replaceable>sourceSet</replaceable>.compileClasspath</literal></td></tr>
-            <tr><td>configFile</td><td><literal>project.checkstyleConfigFile</literal></td></tr>
-            <tr><td>ignoreFailures</td><td><literal>false</literal></td></tr>
-            <tr><td>properties</td><td><literal>project.checkstyleProperties</literal></td></tr>
-            <tr>
-                <td>resultFile</td>
-                <td>
-                    <filename><replaceable>${project.checkstyleResultsDir}</replaceable>/<replaceable>${sourceSet.name}</replaceable>.xml</filename>
-                </td></tr>
-            <tr><td>source</td><td><literal><replaceable>sourceSet</replaceable>.allJava</literal></td></tr>
+            <tr>
+                <td>classpath</td>
+                <td><literal><replaceable>sourceSet</replaceable>.output</literal></td>
+            </tr>
+            <tr>
+                <td>configFile</td>
+                <td><literal>project.checkstyle.configFile</literal></td>
+            </tr>
+            <tr>
+                <td>ignoreFailures</td>
+                <td><literal>project.checkstyle.ignoreFailures</literal></td>
+            </tr>
+            <tr>
+                <td>configProperties</td>
+                <td><literal>project.checkstyle.configProperties</literal></td>
+            </tr>
+            <tr>
+                <td>source</td>
+                <td><literal><replaceable>sourceSet</replaceable>.allJava</literal></td>
+            </tr>
+            <tr>
+                <td>checkstyleClasspath</td>
+                <td><literal>project.configurations.checkstyle</literal></td>
+            </tr>
+            <tr>
+                <td>reports</td>
+                <td></td>
+            </tr>
         </table>
     </section>
     <section>
@@ -28,6 +46,9 @@
                     <td>Name</td>
                 </tr>
             </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml
new file mode 100644
index 0000000..d2f4d8f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CheckstyleExtension.xml
@@ -0,0 +1,44 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>checkstyle</literal> plugin</td>
+                    <td>Default with <literal>code-quality</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>toolVersion</td>
+                <td><literal>5.5</literal></td>
+                <td><literal>5.5</literal></td>
+            </tr>
+            <tr>
+                <td>reportsDir</td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/checkstyle</literal></td>
+                <td><literal>project.checkstyleResultsDir</literal></td>
+            </tr>
+            <tr>
+                <td>configFile</td>
+                <td><literal><replaceable>${project.projectDir}</replaceable>/config/checkstyle/checkstyle.xml</literal></td>
+                <td><literal>project.checkstyleConfigFile</literal></td>
+            </tr>
+            <tr>
+                <td>configProperties</td>
+                <td><literal>[:]</literal></td>
+                <td><literal>project.checkstyleProperties</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml
index df58a0d..94773f1 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarc.xml
@@ -5,15 +5,29 @@
             <thead>
                 <tr>
                     <td>Name</td>
-                    <td>Default with <literal>code-quality</literal> plugin</td>
+                    <td>Default with <literal>codenarc</literal> plugin</td>
                 </tr>
             </thead>
-            <tr><td>configFile</td><td><literal>project.codeNarcConfigFile</literal></td></tr>
-            <tr><td>reportFile</td><td>
-                <filename><replaceable>${project.codeNarcReportsDir}</replaceable>/<replaceable>${sourceSet.name}</replaceable>.html</filename>
-            </td></tr>
-            <tr><td>ignoreFailures</td><td><literal>false</literal></td></tr>
-            <tr><td>source</td><td><literal><replaceable>sourceSet</replaceable>.allGroovy</literal></td></tr>
+            <tr>
+                <td>configFile</td>
+                <td><literal>project.codenarc.configFile</literal></td>
+            </tr>
+            <tr>
+                <td>ignoreFailures</td>
+                <td><literal>project.codenarc.ignoreFailures</literal></td>
+            </tr>
+            <tr>
+                <td>source</td>
+                <td><literal><replaceable>sourceSet</replaceable>.allGroovy</literal></td>
+            </tr>
+            <tr>
+                <td>codenarcClasspath</td>
+                <td><literal>project.configurations.codenarc</literal></td>
+            </tr>
+            <tr>
+                <td>reports</td>
+                <td></td>
+            </tr>
         </table>
     </section>
     <section>
@@ -24,6 +38,9 @@
                     <td>Name</td>
                 </tr>
             </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml
new file mode 100644
index 0000000..3fdba2c
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeNarcExtension.xml
@@ -0,0 +1,44 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>codenarc</literal> plugin</td>
+                    <td>Default with <literal>code-quality</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>toolVersion</td>
+                <td><literal>0.16.1</literal></td>
+                <td><literal>0.16.1</literal></td>
+            </tr>
+            <tr>
+                <td>reportsDir</td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/codenarc</literal></td>
+                <td><literal>project.codeNarcReportsDir</literal></td>
+            </tr>
+            <tr>
+                <td>configFile</td>
+                <td><literal><replaceable>${project.projectDir}</replaceable>/config/codenarc/codenarc.xml</literal></td>
+                <td><literal>project.codeNarcConfigFile</literal></td>
+            </tr>
+            <tr>
+                <td>reportFormat</td>
+                <td><literal>html</literal></td>
+                <td><literal>project.codeNarcReportsFormat</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeQualityExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeQualityExtension.xml
new file mode 100644
index 0000000..37c9c8f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.CodeQualityExtension.xml
@@ -0,0 +1,27 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default</td>
+                </tr>
+            </thead>
+            <tr><td>toolVersion</td><td></td></tr>
+            <tr><td>reportsDir</td><td></td></tr>
+            <tr><td>sourceSets</td><td><literal>project.sourceSets</literal></td></tr>
+            <tr><td>ignoreFailures</td><td><literal>false</literal></td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
new file mode 100644
index 0000000..fd28dd6
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
@@ -0,0 +1,54 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>pmd</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>classes</td>
+                <td><literal><replaceable>sourceSet</replaceable>.output</literal></td>
+            </tr>
+            <tr>
+                <td>classpath</td>
+                <td><literal><replaceable>sourceSet</replaceable>.compileClasspath</literal></td>
+            </tr>
+            <tr>
+                <td>source</td>
+                <td><literal><replaceable>sourceSet</replaceable>.allJava</literal></td>
+            </tr>
+            <tr>
+                <td>findbugsClasspath</td>
+                <td><literal>project.configurations.findbugs</literal></td>
+            </tr>
+            <tr>
+                <td>pluginClasspath</td>
+                <td><literal>project.configurations.findbugsPlugins</literal></td>
+            </tr>
+            <tr>
+                <td>ignoreFailures</td>
+                <td><literal>project.findbugs.ignoreFailures</literal></td>
+            </tr>
+            <tr>
+                <td>reports</td>
+                <td></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugsExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugsExtension.xml
new file mode 100644
index 0000000..2d34681
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugsExtension.xml
@@ -0,0 +1,31 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>toolVersion</td>
+                <td><literal>2.0.0</literal></td>
+            </tr>
+            <tr>
+                <td>reportsDir</td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/findbugs</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention.xml
index 0a1a78c..5ac5d1e 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention.xml
@@ -22,7 +22,7 @@
             </tr>
             <tr>
                 <td>codeNarcReportsDir</td>
-                <td><literal><replaceable>${project.reportsDir}</replaceable>/<replaceable>${project.codeNarcReportsDirName}</replaceable></literal></td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/<replaceable>${project.codeNarcReportsDirName}</replaceable></literal></td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.JDepend.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.JDepend.xml
new file mode 100644
index 0000000..3709580
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.JDepend.xml
@@ -0,0 +1,38 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>jdepend</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>jdependClasspath</td>
+                <td><literal>project.configurations.jdepend</literal></td>
+            </tr>
+            <tr>
+                <td>classesDir</td>
+                <td><literal><replaceable>sourceSet</replaceable>.output.classesDir</literal></td>
+            </tr>
+            <tr>
+                <td>reports</td>
+                <td></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.JDependExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.JDependExtension.xml
new file mode 100644
index 0000000..38ccb82
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.JDependExtension.xml
@@ -0,0 +1,31 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>toolVersion</td>
+                <td><literal>2.9.1</literal></td>
+            </tr>
+            <tr>
+                <td>reportsDir</td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/jdepend</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml
new file mode 100644
index 0000000..a777dba
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml
@@ -0,0 +1,50 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>pmd</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>pmdClasspath</td>
+                <td><literal>project.configurations.pmd</literal></td>
+            </tr>
+            <tr>
+                <td>ruleSets</td>
+                <td><literal>project.pmd.ruleSets</literal></td>
+            </tr>
+            <tr>
+                <td>ruleSetFiles</td>
+                <td><literal>project.pmd.ruleSetFiles</literal></td>
+            </tr>
+            <tr>
+                <td>ignoreFailures</td>
+                <td><literal>project.pmd.ignoreFailures</literal></td>
+            </tr>
+            <tr>
+                <td>source</td>
+                <td><literal><replaceable>sourceSet</replaceable>.allJava</literal></td>
+            </tr>
+            <tr>
+                <td>reports</td>
+                <td></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml
new file mode 100644
index 0000000..b88bd6d
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml
@@ -0,0 +1,45 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>toolVersion</td>
+                <td><literal>4.3</literal></td>
+            </tr>
+            <tr>
+                <td>reportsDir</td>
+                <td><literal><replaceable>${project.reporting.baseDir}</replaceable>/pmd</literal></td>
+            </tr>
+            <tr>
+                <td>ruleSets</td>
+                <td><literal>["basic"]</literal></td>
+            </tr>
+            <tr>
+                <td>ruleSetFiles</td>
+                <td><literal>[]</literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>ruleSets</td>
+            </tr>
+            <tr>
+                <td>ruleSetFiles</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.sonar.Sonar.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.sonar.Sonar.xml
deleted file mode 100644
index a874b70..0000000
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.sonar.Sonar.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<section>
-    <section>
-        <title>Properties</title>
-        <table>
-            <thead>
-                <tr>
-                    <td>Name</td>
-                    <td>Default with <literal>sonar</literal> and <literal>java</literal> plugins</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>serverUrl</td>
-                <td><literal>http://localhost:9000</literal></td>
-            </tr>
-            <tr>
-                <td>bootstrapDir</td>
-                <td>Gradle-managed per-project cache directory</td>
-            </tr>
-            <tr>
-                <td>projectDir</td>
-                <td><literal>project.projectDir</literal></td>
-            </tr>
-            <tr>
-                <td>projectMainSourceDirs</td>
-                <td><literal>project.sourceSets.main.java.srcDirs</literal></td>
-            </tr>
-            <tr>
-                <td>projectTestSourceDirs</td>
-                <td>
-                    <literal>project.sourceSets.main.java.srcDirs</literal>
-                </td>
-            </tr>
-            <tr>
-                <td>projectClassesDirs</td>
-                <td><literal>project.sourceSets.main.classesDir</literal></td>
-            </tr>
-            <tr>
-                <td>projectDependencies</td>
-                <td><literal>project.configurations.compile.resolve()</literal></td></tr>
-            <tr>
-                <td>projectKey</td>
-                <td><literal><replaceable>$project.group</replaceable>:<replaceable>$project.name</replaceable></literal></td>
-            </tr>
-            <tr>
-                <td>projectName</td>
-                <td><literal>project.name</literal></td>
-            </tr>
-            <tr>
-                <td>projectDescription</td>
-                <td><literal>project.description</literal></td>
-            </tr>
-            <tr>
-                <td>projectVersion</td>
-                <td><literal>project.version</literal></td>
-            </tr>
-            <tr>
-                <td>projectProperties</td>
-                <td>
-                    <literal>["sonar.java.source": project.sourceCompatibility,
-                        "sonar.java.target": project.targetCompatibility,
-                        "sonar.dynamicAnalysis": "reuseReports",
-                        "sonar.surefire.reportsPath": project.test.testResultsDir]
-                    </literal>
-                </td>
-            </tr>
-        </table>
-    </section>
-    <section>
-        <title>Methods</title>
-        <table>
-            <thead>
-                <tr><td>Name</td></tr>
-            </thead>
-            <tr><td>projectMainSourceDir</td></tr>
-            <tr><td>projectMainSourceDirs</td></tr>
-            <tr><td>projectTestSourceDir</td></tr>
-            <tr><td>projectTestSourceDirs</td></tr>
-            <tr><td>projectClassesDir</td> </tr>
-            <tr><td>projectClassesDirs</td></tr>
-            <tr><td>projectDependency</td></tr>
-            <tr><td>projectDependencies</td></tr>
-            <tr><td>globalProperty</td></tr>
-            <tr><td>globalProperties</td></tr>
-            <tr><td>projectProperty</td></tr>
-            <tr><td>projectProperties</td></tr>
-        </table>
-    </section>
-</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.sonar.SonarAnalyze.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.sonar.SonarAnalyze.xml
new file mode 100644
index 0000000..5c873cb
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.sonar.SonarAnalyze.xml
@@ -0,0 +1,23 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>rootModel</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr><td>Name</td></tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml
index 91d6886..9245214 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.AbstractCopyTask.xml
@@ -26,11 +26,15 @@
             </tr>
             <tr>
                 <td>dirMode</td>
-                <td><literal>0755</literal></td>
+                <td><literal>null</literal></td>
             </tr>
             <tr>
                 <td>fileMode</td>
-                <td><literal>0644</literal></td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>includeEmptyDirs</td>
+                <td><literal>true</literal></td>
             </tr>
         </table>
     </section>
@@ -71,4 +75,4 @@
             </tr>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSet.xml
index 7b7bb39..251b60c 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSet.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSet.xml
@@ -1,3 +1,4 @@
+
 <section>
     <section>
         <title>Properties</title>
@@ -21,12 +22,8 @@
                 <td/>
             </tr>
             <tr>
-                <td>classes</td>
-                <td><literal>[classesDir]</literal></td>
-            </tr>
-            <tr>
-                <td>classesDir</td>
-                <td><literal><replaceable>${project.buildDir}</replaceable>/classes/<replaceable>${sourceSet.name}</replaceable></literal></td>
+                <td>output</td>
+                <td>See <apilink class="org.gradle.api.tasks.SourceSetOutput"/></td>
             </tr>
             <tr>
                 <td>compileClasspath</td>
@@ -42,7 +39,7 @@
             </tr>
             <tr>
                 <td>runtimeClasspath</td>
-                <td><literal>sourceSet.classes + project.configurations.runtime</literal> (or <literal>sourceSet.classes + project.configurations.testRuntime</literal> for the <literal>test</literal> source set).</td>
+                <td><literal>sourceSet.output + project.configurations.runtime</literal> (or <literal>sourceSet.output + project.configurations.testRuntime</literal> for the <literal>test</literal> source set).</td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSetOutput.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSetOutput.xml
new file mode 100644
index 0000000..972eb1a
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.SourceSetOutput.xml
@@ -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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>java</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>classesDir</td>
+                <td><literal><replaceable>${project.buildDir}</replaceable>/classes/<replaceable>${sourceSet.name}</replaceable></literal></td>
+            </tr>
+            <tr>
+                <td>resourcesDir</td>
+                <td><literal><replaceable>${project.buildDir}</replaceable>/classes/<replaceable>${sourceSet.name}</replaceable></literal></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>dir</td>
+            </tr>
+            <tr>
+                <td>getDirs</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml
index b6bce4f..298d51c 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml
@@ -50,6 +50,9 @@
                     <td>Name</td>
                 </tr>
             </thead>
+                <tr>
+                    <td>into</td>
+                </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.diagnostics.DependencyReportTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.diagnostics.DependencyReportTask.xml
index 4ff5b58..bc9d16d 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.diagnostics.DependencyReportTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.diagnostics.DependencyReportTask.xml
@@ -10,7 +10,7 @@
             </thead>
             <tr>
                 <td>configurations</td>
-                <td><literal>project.configurations.all</literal></td>
+                <td><literal>project.configurations</literal></td>
             </tr>
             <tr>
                 <td>outputFile</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.javadoc.Groovydoc.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.javadoc.Groovydoc.xml
index 957c45a..c9201f0 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.javadoc.Groovydoc.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.javadoc.Groovydoc.xml
@@ -17,6 +17,10 @@
                 <td><literal>project.configurations.groovy</literal></td>
             </tr>
             <tr>
+                <td>classpath</td>
+                <td><literal>sourceSets.main.output + sourceSets.main.compileClasspath</literal></td>
+            </tr>
+            <tr>
                 <td>use</td>
                 <td><literal>false</literal></td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml
index 425963c..313f7c1 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.scala.ScalaDoc.xml
@@ -14,7 +14,7 @@
             </tr>
             <tr>
                 <td>classpath</td>
-                <td><literal>project.sourceSets.main.classes + project.sourceSets.main.compileClasspath</literal></td>
+                <td><literal>project.sourceSets.main.output + project.sourceSets.main.compileClasspath</literal></td>
             </tr>
             <tr>
                 <td>scalaClasspath</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
index 70e48f4..5dd5877 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.Test.xml
@@ -100,6 +100,10 @@
                 <td>excludes</td>
                 <td>[]</td>
             </tr>
+            <tr>
+                <td>testLogging</td>
+                <td/>
+            </tr>
         </table>
     </section>
     <section>
@@ -117,6 +121,12 @@
                 <td>removeTestListener</td>
             </tr>
             <tr>
+                <td>addTestOutputListener</td>
+            </tr>
+            <tr>
+                <td>removeTestOutputListener</td>
+            </tr>
+            <tr>
                 <td>beforeSuite</td>
             </tr>
             <tr>
@@ -129,6 +139,12 @@
                 <td>afterTest</td>
             </tr>
             <tr>
+                <td>include</td>
+            </tr>
+            <tr>
+                <td>exclude</td>
+            </tr>
+            <tr>
                 <td>useJUnit</td>
             </tr>
             <tr>
@@ -155,6 +171,12 @@
             <tr>
                 <td>copyTo</td>
             </tr>
+            <tr>
+                <td>onOutput</td>
+            </tr>
+            <tr>
+                <td>testLogging</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml
index d8fd6f8..bf35017 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.wrapper.Wrapper.xml
@@ -38,9 +38,9 @@
             </tr>
             <tr>
                 <td>distributionUrl</td>
-                <td><literal>'http://repo.gradle.org/gradle/distributions/gradle-${gradleVersion}-bin.zip'</literal>
+                <td><literal>'http\://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip'</literal>
                     (or
-                    <literal>'http://repo.gradle.org/gradle/distributions/gradle-snapshots/gradle-${gradleVersion}-bin.zip'</literal>
+                    <literal>'http\://services.gradle.org/distributions-snapshots/gradle-${gradleVersion}-bin.zip'</literal>
                     for snapshot versions).
                 </td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ear.Ear.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ear.Ear.xml
new file mode 100644
index 0000000..5e749f8
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ear.Ear.xml
@@ -0,0 +1,34 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>ear</literal> plugin <overrides>Default with <literal>java</literal> plugin</overrides></td>
+                </tr>
+            </thead>
+            <tr>
+                <td>extension</td>
+                <td><literal>ear</literal></td>
+            </tr>
+            <tr>
+                <td>source</td>
+                <td><literal>[project.appDir]</literal>, not transitive</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>lib</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ear.EarPluginConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ear.EarPluginConvention.xml
new file mode 100644
index 0000000..91f8b0d
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ear.EarPluginConvention.xml
@@ -0,0 +1,44 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>ear</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>appDirName</td>
+                <td><literal>'src/main/application'</literal></td>
+            </tr>
+            <tr>
+                <td>libDirName</td>
+                <td><literal>'lib'</literal></td>
+            </tr>
+            <tr>
+                <td>deploymentDescriptor</td>
+                <td>A deployment descriptor initialized with sensible defaults</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>deploymentDescriptor</td>
+            </tr>
+            <tr>
+                <td>appDirName</td>
+            </tr>
+            <tr>
+                <td>libDirName</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.FileContentMerger.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.FileContentMerger.xml
new file mode 100644
index 0000000..32c1a2f
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.FileContentMerger.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>beforeMerged</td>
+            </tr>
+            <tr>
+                <td>whenMerged</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.GeneratorTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.GeneratorTask.xml
index 1d83c14..6de91aa 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.GeneratorTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.GeneratorTask.xml
@@ -23,12 +23,6 @@
                     <td>Name</td>
                 </tr>
             </thead>
-            <tr>
-                <td>beforeConfigured</td>
-            </tr>
-            <tr>
-                <td>whenConfigured</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.PropertiesFileContentMerger.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.PropertiesFileContentMerger.xml
new file mode 100644
index 0000000..4dd76eb
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.PropertiesFileContentMerger.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>withProperties</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.PropertiesGeneratorTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.PropertiesGeneratorTask.xml
new file mode 100644
index 0000000..79f878d
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.PropertiesGeneratorTask.xml
@@ -0,0 +1,38 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlFileContentMerger.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlFileContentMerger.xml
new file mode 100644
index 0000000..cddb4f9
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlFileContentMerger.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>withXml</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlGeneratorTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlGeneratorTask.xml
index fbf89fd..f46d04e 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlGeneratorTask.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.api.XmlGeneratorTask.xml
@@ -17,9 +17,6 @@
                     <td>Name</td>
                 </tr>
             </thead>
-            <tr>
-                <td>withXml</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseJdt.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseJdt.xml
index 9a13daf..d7fcf44 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseJdt.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseJdt.xml
@@ -9,14 +9,6 @@
                 </tr>
             </thead>
             <tr>
-                <td>sourceCompatibility</td>
-                <td><literal>project.sourceCompatibility</literal></td>
-            </tr>
-            <tr>
-                <td>targetCompatibility</td>
-                <td><literal>project.targetCompatibility</literal></td>
-            </tr>
-            <tr>
                 <td>outputFile</td>
                 <td><filename><replaceable>${project.projectDir}</replaceable>/.settings/org.eclipse.jdt.core.prefs</filename></td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseProject.xml
index 34e95fd..da281c8 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseProject.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseProject.xml
@@ -9,30 +9,6 @@
                 </tr>
             </thead>
             <tr>
-                <td>projectName</td>
-                <td><literal>project.name</literal></td>
-            </tr>
-            <tr>
-                <td>comment</td>
-                <td><literal>project.description</literal></td>
-            </tr>
-            <tr>
-                <td>referencedProjects</td>
-                <td>[]</td>
-            </tr>
-            <tr>
-                <td>natures</td>
-                <td>Java nature, plus Groovy, Scala and Web natures as appropriate.</td>
-            </tr>
-            <tr>
-                <td>buildCommands</td>
-                <td>Java builder, plus Scala and Web builders as appropriate.</td>
-            </tr>
-            <tr>
-                <td>links</td>
-                <td>[]</td>
-            </tr>
-            <tr>
                 <td>outputFile</td>
                 <td><filename><replaceable>${project.projectDir}</replaceable>/.project</filename></td>
             </tr>
@@ -46,18 +22,6 @@
                     <td>Name</td>
                 </tr>
             </thead>
-            <tr>
-                <td>natures</td>
-            </tr>
-            <tr>
-                <td>referencedProjects</td>
-            </tr>
-            <tr>
-                <td>buildCommand</td>
-            </tr>
-            <tr>
-                <td>link</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent.xml
index ff7d9bd..1800966 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent.xml
@@ -6,47 +6,53 @@
                 <tr>
                     <td>Name</td>
                     <td>Default with <literal>eclipse</literal> and <literal>war</literal> plugins</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>ear</literal> plugins</td>
                 </tr>
             </thead>
             <tr>
                 <td>inputFile</td>
                 <td/>
+                <td/>
             </tr>
             <tr>
                 <td>outputFile</td>
                 <td><filename><replaceable>${project.projectDir}</replaceable>/.settings/org.eclipse.wst.common.component</filename></td>
+                <td>(same)</td>
             </tr>
             <tr>
                 <td>sourceDirs</td>
                 <td>source dirs from <literal>project.sourceSets.main.allSource</literal></td>
-            </tr>
-            <tr>
-                <td>plusConfigurations</td>
-                <td><literal>[project.configurations.runtime]</literal></td>
+                <td>(same)</td>
             </tr>
             <tr>
                 <td>minusConfigurations</td>
                 <td><literal>[project.configurations.providedRuntime]</literal></td>
+                <td><literal>[]</literal></td>
             </tr>
             <tr>
                 <td>deployName</td>
                 <td><literal>project.name</literal></td>
+                <td><literal>project.name</literal></td>
             </tr>
             <tr>
                 <td>variables</td>
                 <td><literal>[:]</literal></td>
+                <td><literal>[:]</literal></td>
             </tr>
             <tr>
                 <td>resources</td>
                 <td><literal>[deployPath: '/', sourcePath: project.webAppDirName]</literal></td>
+                <td><literal>[]</literal></td>
             </tr>
             <tr>
                 <td>properties</td>
                 <td><literal>[]</literal></td>
+                <td><literal>[]</literal></td>
             </tr>
             <tr>
                 <td>contextPath</td>
                 <td><literal>project.war.baseName</literal></td>
+                <td/>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet.xml
index 81025d7..c999272 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet.xml
@@ -16,10 +16,6 @@
                 <td>outputFile</td>
                 <td><filename><replaceable>${project.projectDir}</replaceable>/.settings/org.eclipse.wst.common.project.facet.core.xml</filename></td>
             </tr>
-            <tr>
-                <td>facets</td>
-                <td>Java and web facets.</td>
-            </tr>
         </table>
     </section>
     <section>
@@ -30,9 +26,6 @@
                     <td>Name</td>
                 </tr>
             </thead>
-            <tr>
-                <td>facet</td>
-            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseClasspath.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseClasspath.xml
new file mode 100644
index 0000000..5dd8cd1
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseClasspath.xml
@@ -0,0 +1,57 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>java</literal> plugins</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>plusConfigurations</td>
+                <td><literal>project.configurations.testRuntime</literal></td>
+            </tr>
+            <tr>
+                <td>minusConfigurations</td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>containers</td>
+                <td>[JRE container]</td>
+            </tr>
+            <tr>
+                <td>defaultOutputDir</td>
+                <td><filename><replaceable>${project.projectDir}</replaceable>/bin</filename></td>
+            </tr>
+            <tr>
+                <td>downloadSources</td>
+                <td><literal>true</literal></td>
+            </tr>
+            <tr>
+                <td>downloadJavadoc</td>
+                <td><literal>false</literal></td>
+            </tr>
+            <tr>
+                <td>file</td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>containers</td>
+            </tr>
+            <tr>
+                <td>file</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseJdt.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseJdt.xml
new file mode 100644
index 0000000..c16434b
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseJdt.xml
@@ -0,0 +1,38 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>java</literal> plugins</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sourceCompatibility</td>
+                <td><literal>project.sourceCompatibility</literal></td>
+            </tr>
+            <tr>
+                <td>targetCompatibility</td>
+                <td><literal>project.targetCompatibility</literal></td>
+            </tr>
+            <tr>
+                <td>file</td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>file</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseModel.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseModel.xml
new file mode 100644
index 0000000..6cb2b60
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseModel.xml
@@ -0,0 +1,65 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>project</td>
+            </tr>
+            <tr>
+                <td>classpath</td>
+            </tr>
+            <tr>
+                <td>jdt</td>
+            </tr>
+            <tr>
+                <td>wtp</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>project</td>
+            </tr>
+            <tr>
+                <td>classpath</td>
+            </tr>
+            <tr>
+                <td>jdt</td>
+            </tr>
+            <tr>
+                <td>wtp</td>
+            </tr>
+            <tr>
+                <td>pathVariables</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseProject.xml
new file mode 100644
index 0000000..6c838f9
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseProject.xml
@@ -0,0 +1,66 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>java</literal> plugins</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>name</td>
+                <td><literal><replaceable>${project.name}</replaceable> (sometimes prefixed with parts of <replaceable>${project.path}</replaceable> to guarantee uniqeness)</literal></td>
+            </tr>
+            <tr>
+                <td>comment</td>
+                <td><literal>project.description</literal></td>
+            </tr>
+            <tr>
+                <td>referencedProjects</td>
+                <td>[]</td>
+            </tr>
+            <tr>
+                <td>natures</td>
+                <td>Java nature, plus Groovy, Scala and Web natures as appropriate.</td>
+            </tr>
+            <tr>
+                <td>buildCommands</td>
+                <td>Java builder, plus Scala and Web builders as appropriate.</td>
+            </tr>
+            <tr>
+                <td>linkedResources</td>
+                <td>[]</td>
+            </tr>
+            <tr>
+                <td>file</td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>natures</td>
+            </tr>
+            <tr>
+                <td>referencedProjects</td>
+            </tr>
+            <tr>
+                <td>buildCommand</td>
+            </tr>
+            <tr>
+                <td>linkedResource</td>
+            </tr>
+            <tr>
+                <td>file</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtp.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtp.xml
new file mode 100644
index 0000000..509e087
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtp.xml
@@ -0,0 +1,42 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>component</td></tr>
+            <tr><td>facet</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>component</td></tr>
+            <tr><td>facet</td></tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent.xml
new file mode 100644
index 0000000..27ecaa2
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent.xml
@@ -0,0 +1,88 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>war</literal> plugins</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>ear</literal> plugins</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sourceDirs</td>
+                <td>source dirs from <literal>project.sourceSets.main.allSource</literal></td>
+                <td>The same unless <literal>java</literal> plugin not applied, then: <literal>[project.appDirName]</literal></td>
+            </tr>
+            <tr>
+                <td>rootConfigurations</td>
+                <td><literal>[]</literal></td>
+                <td><literal>[project.configurations.deploy]]</literal></td>
+            </tr>
+            <tr>
+                <td>libConfigurations</td>
+                <td><literal>[project.configurations.runtime]</literal></td>
+                <td><literal>[project.configurations.earlib]</literal></td>
+            </tr>
+            <tr>
+                <td>minusConfigurations</td>
+                <td><literal>[project.configurations.providedRuntime]</literal></td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>deployName</td>
+                <td><literal>eclipse.project.name</literal></td>
+                <td><literal>eclipse.project.name</literal></td>
+            </tr>
+            <tr>
+                <td>resources</td>
+                <td><literal>[deployPath: '/', sourcePath: project.webAppDirName]</literal></td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>properties</td>
+                <td><literal>[]</literal></td>
+                <td><literal>[]</literal></td>
+            </tr>
+            <tr>
+                <td>contextPath</td>
+                <td><literal>project.war.baseName</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>classesDeployPath</td>
+                <td><literal>/WEB-INF/classes</literal></td>
+                <td><literal>/</literal></td>
+            </tr>
+            <tr>
+                <td>libDeployPath</td>
+                <td><literal>/WEB-INF/lib</literal></td>
+                <td><literal>/lib</literal></td>
+            </tr>
+            <tr>
+                <td>file</td>
+                <td/>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>property</td>
+            </tr>
+            <tr>
+                <td>resource</td>
+            </tr>
+            <tr>
+                <td>file</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet.xml
new file mode 100644
index 0000000..7b5634b
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet.xml
@@ -0,0 +1,37 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>eclipse</literal> and <literal>war</literal> plugins</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>facets</td>
+                <td>Java and web facets.</td>
+            </tr>
+            <tr>
+                <td>file</td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>facet</td>
+            </tr>
+            <tr>
+                <td>file</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaModule.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaModule.xml
index 9af8844..ee127b3 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaModule.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaModule.xml
@@ -10,72 +10,6 @@
                 </tr>
             </thead>
             <tr>
-                <td>moduleDir</td>
-                <td><literal>project.projectDir</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>sourceDirs</td>
-                <td><literal>[]</literal></td>
-                <td>source dirs from <literal>project.sourceSets.main.allSource</literal></td>
-            </tr>
-            <tr>
-                <td>testSourceDirs</td>
-                <td><literal>[]</literal></td>
-                <td>source dirs from <literal>project.sourceSets.test.allSource</literal></td>
-            </tr>
-            <tr>
-                <td>excludeDirs</td>
-                <td><literal>[project.buildDir, project.file('.gradle')]</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>inheritOutputDirs</td>
-                <td><literal>null</literal></td>
-                <td><literal>null</literal></td>
-            </tr>
-            <tr>
-                <td>outputDir</td>
-                <td><literal>null</literal></td>
-                <td><literal>null</literal></td>
-            </tr>
-            <tr>
-                <td>testOutputDir</td>
-                <td><literal>null</literal></td>
-                <td><literal>null</literal></td>
-            </tr>
-            <tr>
-                <td>javaVersion</td>
-                <td><literal>'inherited'</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>downloadSources</td>
-                <td><literal>true</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>downloadJavadoc</td>
-                <td><literal>false</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>variables</td>
-                <td><literal>[:]</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>scopes</td>
-                <td><literal>[:]</literal></td>
-                <td>
-                    <itemizedlist>
-                        <listitem><literal>COMPILE</literal> -> <literal>project.configurations.compile</literal></listitem>
-                        <listitem><literal>RUNTIME</literal> -> <literal>project.configurations.runtime - project.configurations.compile</literal></listitem>
-                        <listitem><literal>TEST</literal> -> <literal>project.configurations.testRuntime - project.configurations.runtime</literal></listitem>
-                    </itemizedlist>
-                </td>
-            </tr>
-            <tr>
                 <td>outputFile</td>
                 <td><literal><replaceable>${project.projectDir}</replaceable>/<replaceable>${project.name}</replaceable>.iml</literal>
                     (sometimes the <replaceable>project.name</replaceable> is prefixed with parts of <replaceable>${project.path}</replaceable> to guarantee uniqeness).
@@ -83,11 +17,6 @@
                 </td>
                 <td/>
             </tr>
-            <tr>
-                <td>moduleName</td>
-                <td><literal><replaceable>${project.name}</replaceable> (sometimes prefixed with parts of <replaceable>${project.path}</replaceable> to guarantee uniqeness)</literal></td>
-                <td/>
-            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaProject.xml
index 9acd2be..ac7581f 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaProject.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.GenerateIdeaProject.xml
@@ -10,21 +10,6 @@
                 </tr>
             </thead>
             <tr>
-                <td>javaVersion</td>
-                <td><literal>'1.6'</literal></td>
-                <td><literal>project.sourceCompatibility</literal></td>
-            </tr>
-            <tr>
-                <td>subprojects</td>
-                <td><literal>project.allprojects</literal></td>
-                <td/>
-            </tr>
-            <tr>
-                <td>wildcards</td>
-                <td><literal>['!?*.java', '!?*.groovy']</literal></td>
-                <td/>
-            </tr>
-            <tr>
                 <td>outputFile</td>
                 <td><literal><replaceable>${project.projectDir}</replaceable>/<replaceable>${project.name}</replaceable>.ipr</literal></td>
                 <td/>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaModel.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaModel.xml
new file mode 100644
index 0000000..6933825
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaModel.xml
@@ -0,0 +1,58 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>project</td>
+            </tr>
+            <tr>
+                <td>module</td>
+            </tr>
+            <tr>
+                <td>workspace</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>project</td>
+            </tr>
+            <tr>
+                <td>module</td>
+            </tr>
+            <tr>
+                <td>workspace</td>
+            </tr>
+            <tr>
+                <td>pathVariables</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaModule.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaModule.xml
new file mode 100644
index 0000000..19f9370
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaModule.xml
@@ -0,0 +1,104 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>idea</literal> plugin</td>
+                    <td>Default with <literal>idea</literal> and <literal>java</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>name</td>
+                <td><literal><replaceable>${project.name}</replaceable> (sometimes prefixed with parts of <replaceable>${project.path}</replaceable> to guarantee uniqeness)</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>sourceDirs</td>
+                <td><literal>[]</literal></td>
+                <td>source dirs from <literal>project.sourceSets.main.allSource</literal></td>
+            </tr>
+            <tr>
+                <td>scopes</td>
+                <td><literal>[:]</literal></td>
+                <td>
+                    <itemizedlist>
+                        <listitem><literal>COMPILE</literal> -> <literal>project.configurations.compile</literal></listitem>
+                        <listitem><literal>RUNTIME</literal> -> <literal>project.configurations.runtime - project.configurations.compile</literal></listitem>
+                        <listitem><literal>TEST</literal> -> <literal>project.configurations.testRuntime - project.configurations.runtime</literal></listitem>
+                        <listitem><literal>PROVIDED</literal></listitem>
+                    </itemizedlist>
+                </td>
+            </tr>
+            <tr>
+                <td>downloadSources</td>
+                <td><literal>true</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>downloadJavadoc</td>
+                <td><literal>false</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>contentRoot</td>
+                <td><literal>project.projectDir</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>testSourceDirs</td>
+                <td><literal>[]</literal></td>
+                <td>source dirs from <literal>project.sourceSets.test.allSource</literal></td>
+            </tr>
+            <tr>
+                <td>excludeDirs</td>
+                <td><literal>[project.buildDir, project.file('.gradle')]</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>inheritOutputDirs</td>
+                <td><literal>null</literal></td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>outputDir</td>
+                <td><literal>null</literal></td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>testOutputDir</td>
+                <td><literal>null</literal></td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
+                <td>jdkName</td>
+                <td><literal>'inherited'</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>iml</td>
+                <td/>
+                <td/>
+            </tr>
+            <tr>
+                <td>outputFile</td>
+                <td>#name + <literal>'.iml'</literal></td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>iml</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
new file mode 100644
index 0000000..5e84298
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
@@ -0,0 +1,57 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Default with <literal>idea</literal> plugin</td>
+                    <td>Default with <literal>idea</literal> and <literal>java</literal> plugin</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>modules</td>
+                <td><literal>project.allprojects*idea.module</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>jdkName</td>
+                <td><literal>'1.6'</literal></td>
+                <td><literal>project.sourceCompatibility</literal></td>
+            </tr>
+            <tr>
+                <td>languageLevel</td>
+                <td><literal>'JDK_1_6'</literal></td>
+                <td><literal>project.sourceCompatibility</literal></td>
+            </tr>
+            <tr>
+                <td>wildcards</td>
+                <td><literal>['!?*.java', '!?*.groovy']</literal></td>
+                <td/>
+            </tr>
+            <tr>
+                <td>ipr</td>
+                <td/>
+                <td/>
+            </tr>
+            <tr>
+                <td>outputFile</td>
+                <td><literal><replaceable>${project.projectDir}</replaceable>/<replaceable>${project.name}</replaceable>.ipr</literal></td>
+                <td/>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>ipr</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaWorkspace.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaWorkspace.xml
new file mode 100644
index 0000000..389f099
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaWorkspace.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>iws</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>iws</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.signing.Sign.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.signing.Sign.xml
new file mode 100644
index 0000000..9c8f78e
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.signing.Sign.xml
@@ -0,0 +1,31 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>signatory</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>signatory</td>
+            </tr>
+            <tr>
+                <td>sign</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.signing.SigningExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.signing.SigningExtension.xml
new file mode 100644
index 0000000..c8d2cd4
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.signing.SigningExtension.xml
@@ -0,0 +1,37 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>signatory</td>
+            </tr>
+            <tr>
+                <td>signatureType</td>
+            </tr>
+            <tr>
+                <td>required</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sign</td>
+            </tr>
+            <tr>
+                <td>signPom</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/plugins.xml b/subprojects/docs/src/docs/dsl/plugins.xml
index 95fe0af..49e0909 100644
--- a/subprojects/docs/src/docs/dsl/plugins.xml
+++ b/subprojects/docs/src/docs/dsl/plugins.xml
@@ -1,41 +1,69 @@
 <plugins>
     <plugin id="java" description="Java Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.BasePluginConvention"/>
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.ReportingBasePluginConvention"/>
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.JavaPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.BasePluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.ReportingBasePluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.JavaPluginConvention"/>
     </plugin>
     <plugin id="groovy" description="Groovy Plugin">
-        <extends targetClass="org.gradle.api.tasks.SourceSet" extensionClass="org.gradle.api.tasks.GroovySourceSet"/>
+        <extends targetClass="org.gradle.api.tasks.SourceSet" mixinClass="org.gradle.api.tasks.GroovySourceSet"/>
     </plugin>
     <plugin id="scala" description="Scala Plugin">
-        <extends targetClass="org.gradle.api.tasks.SourceSet" extensionClass="org.gradle.api.tasks.ScalaSourceSet"/>
+        <extends targetClass="org.gradle.api.tasks.SourceSet" mixinClass="org.gradle.api.tasks.ScalaSourceSet"/>
     </plugin>
     <plugin id="antlr" description="Antlr Plugin">
-        <extends targetClass="org.gradle.api.tasks.SourceSet" extensionClass="org.gradle.api.plugins.antlr.AntlrSourceVirtualDirectory"/>
+        <extends targetClass="org.gradle.api.tasks.SourceSet" mixinClass="org.gradle.api.plugins.antlr.AntlrSourceVirtualDirectory"/>
     </plugin>
     <plugin id="code-quality" description="Code Quality Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.quality.JavaCodeQualityPluginConvention"/>
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.quality.JavaCodeQualityPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention"/>
     </plugin>
     <plugin id="maven" description="Maven Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.MavenPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.MavenPluginConvention"/>
+        <extends targetClass="org.gradle.api.artifacts.dsl.RepositoryHandler" mixinClass="org.gradle.api.plugins.MavenRepositoryHandlerConvention"/>
     </plugin>
     <plugin id="war" description="War Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.WarPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.WarPluginConvention"/>
+    </plugin>
+    <plugin id="ear" description="Ear Plugin">
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.plugins.ear.EarPluginConvention"/>
     </plugin>
     <plugin id="jetty" description="Jetty Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.jetty.JettyPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.jetty.JettyPluginConvention"/>
     </plugin>
     <plugin id="project-report" description="Project Report Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.ProjectReportsPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.ProjectReportsPluginConvention"/>
     </plugin>
     <plugin id="announce" description="Announce Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.announce.AnnouncePluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" id="announce" extensionClass="org.gradle.api.plugins.announce.AnnouncePluginExtension"/>
     </plugin>
     <plugin id="osgi" description="OSGi Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.osgi.OsgiPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.osgi.OsgiPluginConvention"/>
     </plugin>
     <plugin id="application" description="Application Plugin">
-        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.api.plugins.ApplicationPluginConvention"/>
+        <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.ApplicationPluginConvention"/>
+    </plugin>
+    <plugin id="signing" description="Signing Plugin">
+        <extends targetClass="org.gradle.api.Project" extensionClass="org.gradle.plugins.signing.SigningExtension"/>
+    </plugin>
+    <plugin id="eclipse" description="Eclipse Plugin">
+        <extends targetClass="org.gradle.api.Project" id="eclipse" extensionClass="org.gradle.plugins.ide.eclipse.model.EclipseModel"/>
+    </plugin>
+    <plugin id="idea" description="Idea Plugin">
+        <extends targetClass="org.gradle.api.Project" id="idea" extensionClass="org.gradle.plugins.ide.idea.model.IdeaModel"/>
+    </plugin>
+    <plugin id="checkstyle" description="Checkstyle Plugin">
+        <extends targetClass="org.gradle.api.Project" id="checkstyle" extensionClass="org.gradle.api.plugins.quality.CheckstyleExtension"/>
+    </plugin>
+    <plugin id="codenarc" description="CodeNarc Plugin">
+        <extends targetClass="org.gradle.api.Project" id="codenarc" extensionClass="org.gradle.api.plugins.quality.CodeNarcExtension"/>
+    </plugin>
+    <plugin id="findbugs" description="FindBugs Plugin">
+        <extends targetClass="org.gradle.api.Project" id="findbugs" extensionClass="org.gradle.api.plugins.quality.FindBugsExtension"/>
+    </plugin>
+    <plugin id="pmd" description="PMD Plugin">
+        <extends targetClass="org.gradle.api.Project" id="pmd" extensionClass="org.gradle.api.plugins.quality.PmdExtension"/>
+    </plugin>
+    <plugin id="jdepend" description="JDepend Plugin">
+        <extends targetClass="org.gradle.api.Project" id="jdepend" extensionClass="org.gradle.api.plugins.quality.JDependExtension"/>
     </plugin>
 </plugins>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/content/Lato-bold.woff b/subprojects/docs/src/docs/release/content/Lato-bold.woff
new file mode 100644
index 0000000..68729b7
Binary files /dev/null and b/subprojects/docs/src/docs/release/content/Lato-bold.woff differ
diff --git a/subprojects/docs/src/docs/release/content/Lato-regular.woff b/subprojects/docs/src/docs/release/content/Lato-regular.woff
new file mode 100644
index 0000000..26a056b
Binary files /dev/null and b/subprojects/docs/src/docs/release/content/Lato-regular.woff differ
diff --git a/subprojects/docs/src/docs/release/content/jquery-1.7.2-min.js b/subprojects/docs/src/docs/release/content/jquery-1.7.2-min.js
new file mode 100644
index 0000000..ee02337
--- /dev/null
+++ b/subprojects/docs/src/docs/release/content/jquery-1.7.2-min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement( [...]
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]| [...]
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replac [...]
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/content/logo.gif b/subprojects/docs/src/docs/release/content/logo.gif
new file mode 100644
index 0000000..8e349e0
Binary files /dev/null and b/subprojects/docs/src/docs/release/content/logo.gif differ
diff --git a/subprojects/docs/src/docs/release/content/script.js b/subprojects/docs/src/docs/release/content/script.js
new file mode 100644
index 0000000..9e2fd11
--- /dev/null
+++ b/subprojects/docs/src/docs/release/content/script.js
@@ -0,0 +1,44 @@
+$(function() {
+  function elementInViewport(el) {
+    var rect = el.getBoundingClientRect();
+
+    return (
+      rect.top >= 0 &&
+      rect.left >= 0 &&
+      rect.bottom <= window.innerHeight &&
+      rect.right <= window.innerWidth 
+    );
+  }
+  
+  
+  $("section.major-detail").each(function() {
+    var section = $(this);
+    section.hide();
+    var buttonParagraph = $("<p><button class='display-toggle'>More »</button></p>").insertAfter(section);
+    buttonParagraph.find("button").click(function() {
+      var button = $(this);
+      var hiding = section.is(":visible");
+
+      var toggle = function() {
+        section.slideToggle("slow", function() {
+          button.text(hiding ? "More »" : "« Less");
+        });
+      };
+      
+      var header = section.prevAll("h3:first");
+      
+      if (hiding && !elementInViewport(header.get(0))) {
+        var i = 0;
+        $('html,body').animate({
+          scrollTop: header.offset().top
+        }, "fast", "swing", function() {
+          if (++i == 2) {
+            toggle();
+          }
+        });
+      } else {
+        toggle();
+      }
+    });
+  });
+});
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/content/style.css b/subprojects/docs/src/docs/release/content/style.css
new file mode 100644
index 0000000..25bef77
--- /dev/null
+++ b/subprojects/docs/src/docs/release/content/style.css
@@ -0,0 +1,135 @@
+/* reset.css */
+html {margin:0;padding:0;border:0;}
+body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}
+article, aside, details, figcaption, figure, dialog, footer, header, hgroup, menu, nav, section {display:block;}
+body {line-height:1.5;background:white;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;float:none !important;}
+table, th, td {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:'';}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+:focus {outline:0;}
+
+/* typography.css */
+html {font-size:100.01%;}
+body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
+h1 {font-size:3em;line-height:1;margin-bottom:0.5em;}
+h2 {font-size:2em;margin-bottom:0.75em;}
+h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
+h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
+h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+p {margin:0 0 1.5em;}
+.left {float:left !important;}
+p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
+.right {float:right !important;}
+p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
+a:focus, a:hover {color:#09f;}
+a {color:#06c;text-decoration:underline;}
+blockquote {margin:1.5em;color:#666;font-style:italic;}
+strong, dfn {font-weight:bold;}
+em, dfn {font-style:italic;}
+sup, sub {line-height:0;}
+abbr, acronym {border-bottom:1px dotted #666;}
+address {margin:0 0 1.5em;font-style:italic;}
+del {color:#666;}
+pre {margin:1.5em 0;white-space:pre;}
+pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+li ul, li ol {margin:0;}
+ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
+ul {list-style-type:disc;}
+ol {list-style-type:decimal;}
+dl {margin:0 0 1.5em 0;}
+dl dt {font-weight:bold;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;width:100%;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;}
+tfoot {font-style:italic;}
+caption {background:#eee;}
+.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
+.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
+.hide {display:none;}
+.quiet {color:#666;}
+.loud {color:#000;}
+.highlight {background:#ff0;}
+.added {background:#060;color:#fff;}
+.removed {background:#900;color:#fff;}
+.first {margin-left:0;padding-left:0;}
+.last {margin-right:0;padding-right:0;}
+.top {margin-top:0;padding-top:0;}
+.bottom {margin-bottom:0;padding-bottom:0;}
+
+ at font-face {
+  font-family: 'Lato';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Lato Regular'), local('Lato-Regular'), url('data:font/opentype;base64, at regular-font-base64@') format('woff');
+}
+ at font-face {
+  font-family: 'Lato';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Lato Bold'), local('Lato-Bold'), url('data:font/opentype;base64, at bold-font-base64@') format('woff');
+}
+
+body { 
+  font-size: 16px; 
+  font-family: 'Lato', Arial, serif;
+  margin: 2em 2em 1em;
+  color: #333;
+}
+
+h1,h2,h3,h4,h5,h6 {
+  color: #007042;
+}
+
+a { color: #007042; }
+a:visited { color: #007042; }
+a:hover { color: #007042; }
+a:focus { outline: thin dotted; }
+a:hover, a:active { outline: 0; }
+
+a.anchor {
+  text-decoration: none;
+}
+
+img.logo {
+  margin-bottom: 1em;
+}
+
+section.major-detail {
+  margin: 0 1em 2em;
+  padding: 1em 1em 0;
+  border-left: 2px solid #999;
+  font-size: 14px;
+  border: 1px dotted #999;
+  background-color: #FCFCFC;
+}
+
+section.minor-detail {
+  margin: 0 0.5em;
+  padding: 0 1em;
+  font-size: 14px;
+}
+
+ul.toc-sub {
+  margin-bottom: 0px;
+}
+
+button.display-toggle {
+  cursor: pointer;
+}
+
+section.footer {
+  font-size: 75%;
+  margin: 6em 0 0;
+  padding-top: 1em;
+  border-top: 1px dashed #999;
+  text-align: center;
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
new file mode 100644
index 0000000..bb1b7ed
--- /dev/null
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -0,0 +1,308 @@
+Gradle 1.0 is a major step forward in the evolution of Gradle, and build tools in general.
+
+## New and noteworthy
+
+Here are the new features introduced in Gradle 1.0, including the changes in all of the milestones.
+
+### Powerful dependency management
+Dependency management is at the heart of every build. In Gradle 1.0 we've moved away from using Ivy for dependency resolution. The dependency resolution engine has been rebuilt from the ground up; it is now faster, more accurate and more flexible.
+
+With full DSL support for Maven and Ivy repository formats, offline builds, the ability to refresh dependencies, and better control over version conflict resolution, we feel that Gradle 1.0 raises the bar for dependency management in build automation. As we continue to innovate in this space, we plan to take the dependency management that works so well for Java and adapt it to other languages and technologies.
+
+#### Offline builds
+
+You're not always connected to a network; Gradle 1.0 helps you out when you're offline. When run with the [`--offline`](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:cache_command_line_options) command-line option, Gradle will use whatever cached dependencies are available, failing early if a required dependency is not available in the cache. For Maven snapshots, changing modules and dynamic versions the most recent cached result will be used; no attempt will be mad [...]
+
+#### Validate and refresh dependencies
+
+When run with the [`--refresh-dependencies`](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:cache_command_line_options) command-line option, Gradle will validate all dependencies against the repositories declared for you build, downloading anything that is not up-to-date. As well as ensuring that you have the latest version of all dependencies, this process will check that every required dependency is available in a defined repository; just having the dependency in y [...]
+
+The `--refresh-dependencies` option is particularly helpful when things go wrong with dependency resolution; like when you are setting up a new repository and don't get it exactly right the first time. In such a case, being able to refresh your dependencies without deleting them from your cache directory can be very useful.
+
+Dependency validation utilises the Gradle change detection algorithm for remote repositories, so artifacts that are unchanged from the cached version will not be re-downloaded.
+
+#### Better control over version conflict resolution
+
+When your dependencies are resolved, the transitive dependency set may bring in several different versions of the same module. By default, Gradle's [conflict resolution](http://gradle.org/docs/1.0/userguide/dependency_management.html#sub:version_conflicts) will use the newest version. This is useful in most cases, but sometimes it isn't what you want, so Gradle 1.0 gives you control over the process by:
+
+* Declaring that your build should fail in the case of [version conflicts](http://gradle.org/docs/1.0/userguide/dependency_management.html#sub:version_conflicts).
+* Forcing a particular module version into the dependency set. This can be done [per-dependency](http://gradle.org/docs/1.0/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html) or more commonly [per-configuration](http://gradle.org/docs/1.0/dsl/org.gradle.api.artifacts.ResolutionStrategy.html).
+* [Excluding](http://gradle.org/docs/1.0/userguide/dependency_management.html#exclude-dependencies) a particular module or version from the dependency set.
+
+The options for handling resolution conflicts will continue to improve, giving users of Gradle full customization of the conflict resolution behaviour.
+
+#### Improved repository definition DSL
+
+While it is still possible to supply an Ivy DependencyResolver to locate your dependencies, in Gradle 1.0 we've improved our [repository definition DSL](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:repositories) to make it easier to declare the Ivy and Maven repositories you use for dependency resolution. Using the repository DSL to specify your repository type, location, layout and credentials is now simple and consistent.
+
+#### Publish artifacts other than archives
+
+Gradle has always made it easy to publish the default artifacts generated by your build, but it was not always trivial to publish additional files that weren't generated as a standard archive. Gradle 1.0 introduces the ability to [publish file-based artifacts](http://gradle.org/docs/1.0/userguide/artifact_management.html#N143A2) that are not produced by an archive task, simplifying the process of publishing arbitrary files to your repository.
+
+#### Dependency resolution explained
+
+Sometimes dependency resolution is a black box to the end user. We've attempted to make the whole process more understandable by improving the [documentation](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:dependency_resolution), log messages and error reporting.
+
+We've improved logging of dependency resolution; running Gradle with `--info --refresh-dependencies` will track all remote repository requests, helping to shed light on what's going on behind the scenes.
+
+### Fast, accurate and reliable dependency cache
+
+In Gradle 1.0 we've replaced the ivy artifact cache with our own implementation, targetting performance, consistency and reliability. Our new cache has advanced features that help avoid subtle (and not so subtle) problems permitted by other cache implementations, where a build that runs correctly on one machine fails on another.
+
+#### Origin aware
+The Gradle dependency cache remembers the origin of every dependency it holds, so your build can only access cached dependencies that come from a repository defined for your build. It is not possible for the presence of a cached artifact to mask the fact that a dependency is not available. This helps to ensure that your build will run correctly in any environment.
+
+#### Concurrency-safe cache
+There is no longer any need to declare separate user home directories or cache locations for different projects; the Gradle 1.0 dependency cache is fully thread-safe and multiprocess-safe.
+
+#### Improved change detection and reduced artifact downloads
+
+To reduce the number of downloads required, Gradle 1.0 has an improved algorithm for detecting if a cached artifact is up-to-date. By using published .sha1 files, ETags, and last-modified-date + content-length, Gradle will do everything it can to check if the version on the server is the same as the version you have already downloaded.
+
+#### Reuse previously downloaded artifacts from m2 repository and older Gradle caches
+
+If you're a maven user, in many cases the dependency you require is already available in your local m2 repository. To save downloading the dependency again, Gradle will reuse any locally available artifacts if they match the checksums published by a remote repository.
+
+#### Consistent caching for dynamic dependencies and changing modules.
+
+To improve performance, by default Gradle 1.0 will cache the resolved value of dynamic versions and changing modules for 24 hours. You can easily manage this timeout using the new [cache-control DSL](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:controlling_caching).
+
+The cache-control DSL is a work in progress; in the future Gradle will allow full programmatic control of all aspects of caching.
+
+#### More information
+
+You can find out more about the Gradle dependency cache in the [User Guide chapter](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:dependency_cache) and this series of forum posts: [part 1](http://forums.gradle.org/gradle/topics/welcome_to_our_new_dependency_cache), [part 2](http://forums.gradle.org/gradle/topics/dependency_resolution_improvements), and [part 3](http://forums.gradle.org/gradle/topics/dependency_resolution_in_gradle_1_0_milestone_8).
+
+### Faster builds
+
+A fast build means faster feedback. Gradle 1.0 includes a host of performance improvements.
+
+* Dependency resolution is faster, in some cases many times faster.
+* Gradle daemon enables developers to run builds faster.
+* Java and Groovy compilation run in less time.
+* Up-to-date checks are faster for large source sets.
+
+We've also enhanced the Gradle profile report, which now includes dependency resolution times in an improved layout.
+
+#### Faster dependency resolution
+
+We've done a lot of work to make dependency resolution as fast as possible, while staying reproducible and accurate. 
+
+To do this we:
+
+* Greatly reduced the number of HTTP requests required to download a dependency.
+* Cache the absence of artifacts per repository, so we don't need to check on every resolve.
+* Implemented a new resolve engine which is much faster and more maintainable than the ivy-based one used previously.
+* Improved cache locking and lock escalation so that it is as efficient as possible and yet concurrency safe.
+* Reuse artifacts previously downloaded by Maven or an older version of Gradle.
+
+Faster dependency resolution also means faster up-to-date checks for speedy incremental builds.
+
+#### Improved Gradle daemon
+We've done a lot of work stabilising the [Gradle daemon](http://gradle.org/docs/1.0/userguide/gradle_daemon.html), and it is no longer marked as experimental. The Gradle daemon reduces the startup time of every `gradle` invocation, and we recommend it for all developer builds.
+
+#### Java and Groovy compilation is faster
+By default, Gradle 1.0 no longer uses Ant tasks for Java and Groovy compilation. By removing the task overhead, in-process compilation has been made faster.
+
+When configured to compile in a separate process (`compile.options.fork = true`), Gradle will now use a single daemon compiler process to further speed up the compilation of your source files. This means that the compile process only needs to be forked once per build invocation, dramatically reducing compile times for large multi-project builds.
+
+#### Up-to-date checks are faster for large source sets.
+By reading cached history more efficiently for large set of input and outputs, Gradle 1.0 performs up-to-date checks faster for large sources sets. With faster up-to-date checks your incremental builds will be faster, reducing the time to re-build when little has changed.
+
+#### Build profile report
+When run with the [`--profile`](http://gradle.org/docs/1.0/userguide/tutorial_gradle_command_line.html#sec:profiling_build) command-line option, Gradle will generate a report detailing the breakdown of time spent executing your build, including dependency resolution time. This report can provide hints about the critical things that slow down your build, helping you make your build faster.
+
+### Java code quality plugins
+Gradle has always provided great out-of-the-box support for standard Java applications. With Gradle 1.0 we've included a number of new code quality integrations to make it easy for you to include these important tools in your build process.
+
+Supported tools include:
+
+* Sonar
+* FindBugs
+* PMD
+* Checkstyle
+* CodeNarc
+
+#### Sonar plugin
+Using the [Gradle Sonar plugin](http://gradle.org/docs/1.0/userguide/sonar_plugin.html), you can now easily integrate your build results with Sonar, the web based platform for monitoring code quality.
+
+#### FindBugs plugin 
+FindBugs uses static analysis to look for bugs in Java code. The [Gradle FindBugs plugin](http://gradle.org/docs/1.0/userguide/findbugs_plugin.html) adds a task for every source set to scan the Java bytecode for a list of bug patterns.
+
+#### JDepend plugin 
+JDepend allows you to generate design quality metrics, by analyzing the coupling between Java class files. The [Gradle JDepend plugin](http://gradle.org/docs/1.0/userguide/jdepend_plugin.html) adds Gradle tasks that analyze all inter-package dependencies of a source set, producing a report that can help you to find unexpected couplings, package dependency cycles, and refactoring targets.
+
+#### PMD plugin
+The popular PMD project provides tools to inspect Java source code looking for potential bugs, inefficiencies and duplication. The [Gradle PMD plugin](http://gradle.org/docs/1.0/userguide/pmd_plugin.html) adds a task that will produce a PMD report per defined source set. The plugin provides many configuration options, including the ability to define custom rulesets, and which version of PMD to use.
+
+#### Separate Checkstyle and CodeNarc plugins
+You can now apply the [Gradle Checkstyle plugin](http://gradle.org/docs/1.0/userguide/checkstyle_plugin.html) and the [Gradle CodeNarc plugin](http://gradle.org/docs/1.0/userguide/codenarc_plugin.html) separately to your build. Both of these plugins have been improved to provide more flexibility, and you can now specify exactly which version of each tool to use.
+
+### IDE integration
+
+Whether you develop code in Eclipse STS, Intellij IDEA or NetBeans (experimental), there is a native Gradle integration for your IDE. Using native IDE integrations you can import and run Gradle builds directly into your IDE, and keep your IDE settings in sync with your Gradle build definition.
+While not strictly part of the Gradle distribution, these native integrations continually improve as Gradle provides more features via the Tooling API - the new embeddable API on which these integrations are based. 
+
+If you don't require tight native integration, the Gradle IDE plugins help you by generating standard project files for your environment. We've improved these IDE plugins to provide more configuration options, to have better defaults, to run faster and to smoothly configure your IDE for your Gradle project.
+
+#### Gradle IDE plugins - generating project files for your IDE
+
+The new Gradle IDE plugins generate IDE project files based on the project defined in your Gradle build file.  The [Gradle Eclipse plugin](http://gradle.org/docs/1.0/userguide/eclipse_plugin.html) makes it easy to keep your .classpath and .project files in sync with your Gradle build, while the [Gradle IDEA plugin](http://gradle.org/docs/1.0/userguide/idea_plugin.html) does the same for your .ipr and .iml files.
+
+As usual with Gradle, you have full programmatic control of the generated model. The DSL reference for the [idea](http://gradle.org/docs/1.0/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.html) and [eclipse](http://gradle.org/docs/1.0/dsl/org.gradle.plugins.ide.eclipse.model.EclipseProject.html) plugins contains many code samples, demonstrating how to fine-tune the configuration and ensure your project imports easily into the IDE. 
+
+#### Native Eclipse STS support
+You can now import Gradle projects and run Gradle builds directly inside of Eclipse, using the [Eclipse STS Gradle plugin](http://static.springsource.org/sts/docs/latest/reference/html/gradle). We are very grateful to our friends at SpringSource for making this possible, and we continue to collaborate to make this integration even better.
+
+#### Native Intellij IDEA support
+Using the [Intellij IDEA Gradle plugin](http://www.jetbrains.com/idea/webhelp/gradle-2.html) you can easily import an existing Gradle project directly into Intellij IDEA, with many more features planned in the near future. Many thanks to JetBrains for their continued development of this plugin.
+
+#### Native NetBeans support (experimental)
+The [NetBeans Gradle plugin](http://plugins.netbeans.org/plugin/41776/gradle) provides the ability to import and run Gradle build files directly in NetBeans. Thanks to the NetBeans team for developing this plugin. 
+
+This NetBeans integration is still experimental, but we hope that by leveraging the power of the Gradle Tooling API the plugin will continue to evolve and improve in the future.
+
+### Embedding Gradle via the Tooling API
+
+Gradle 1.0 sees the introduction of the Gradle tooling API, a new way to embed Gradle. This API allows you to execute and monitor builds, and to query for details about the build. The Tooling API takes care of downloading the version of Gradle required to execute a particular build, and utilises the Gradle daemon process for lightning fast command execution.
+
+The Gradle Tooling API operates in a version independent manner, meaning that any given version of the Tooling API is able to execute builds using both newer and older Gradle versions. Your Gradle version isn't tied to the version of the Tooling API being used, allowing tools like the native IDE plugins to remain stable while supporting a wide range of Gradle projects.
+
+#### Easy to embed in your application
+The [Gradle Tooling API](http://gradle.org/docs/1.0/userguide/embedding.html) implementation is lightweight, with only a small number of dependencies: you don't need the Gradle distribution to use the Tooling API. As a well-behaved library, it makes no assumptions about your class loader structure or logging configuration. These reasons ensure that the Gradle Tooling API is easy to bundle inside your application.
+
+#### Compatible with multiple Gradle versions
+Using the Gradle Tooling API allows you to execute builds using many different versions of Gradle: the version of the Tooling API is not tied to the version of Gradle executing. This cross-version compatibility is constantly verified by our extensive test suite, which validates the correct function of Tooling API features across all Gradle versions. What's important is that we don't just test backwards compatibility but also forward compatibility, so you can be sure that you'll be able t [...]
+
+### Gradle Build Daemon
+
+The Gradle build daemon reduces the startup and execution time of builds by running them in a long-lived daemon process. The <code>gradle</code> command becomes a client process that communicates with the daemon process, avoiding various JVM startup costs and giving the JVM an opportunity to optimize hotspots.
+
+Gradle 1.0 brings a host of improvements to the daemon, and we now recommend it for all developer builds.
+
+#### Ready for prime-time
+
+In earlier versions of Gradle, the daemon was considered an experimental feature. Much work has gone into stabilizing the daemon since it was introduced and we are now recommending it for all developer builds. There are still many improvements planned in the upcoming Gradle releases, but we believe that most of the stability issues have been resolved.
+
+While you can explicitly enable the daemon for a particular build execution with the `--daemon` command-line option, the best way to make the daemon your default is by configuring the [org.gradle.daemon](http://gradle.org/docs/1.0/userguide/build_environment.html#sec:gradle_configuration_properties) property.
+            
+#### Compatible with builds requiring user interaction
+
+Some builds consume the standard input, for example, to perform the interaction with the user. The daemon fully supports those kinds of builds, by forwarding any client input to the executing build process.
+
+#### Respects client java version
+
+The java version the daemon uses is configurable. In case you run different builds with different java versions multiple daemons will be spawned to avoid compatibility issues.
+
+#### Future plans
+Longer term, the daemon will offer even more features that improve the performance of your build. Performing up-to-date checks, dependency resolution and project evaluation pre-emptively are just some of many planned features. The Tooling API - an official way to embed Gradle - fully takes advantage of the build daemon.
+
+### Enterprise scale
+
+Working at the enterprise scale means more than just a fast build. Gradle 1.0 adds capabilities to help you push build logic and configuration out to developers and across teams in a controlled way.
+
+Gradle enables zero-configuration on the client side. Any necessary configuration, be it JVM args or repository definitions, can be specified per project, team or enterprise and stored in version control. Bundle these definitions into a corporate plugin and distribute with the Gradle wrapper for a powerful way to manage your enterprise build environment. No more complex wiki pages explaining how to configure the build on a new machine. No more wasted time due to false alarms caused by a  [...]
+
+Making it easy to administer the enterprise build environment was a core goal of Gradle 1.0. We will continue to innovate in this area in future releases.
+
+#### The Gradle wrapper
+
+The [Gradle wrapper](http://gradle.org/docs/1.0/userguide/gradle_wrapper.html) is invaluable in a large team to guarantee that a particular Gradle version is used to execute your build. This provides a consistent build environment, enabling reproducible and maintainable automation.
+
+The Gradle wrapper automatically downloads and installs the required Gradle distribution, just checkout the project from version control and run. No previous install of Gradle is required! Upgrading to a new version of Gradle is trivial with the wrapper. Increment the version number in your wrapper configuration script, and anyone running your build will automatically switch to use the new Gradle version.
+
+#### Define JVM arguments and Java version required to build your project
+
+In many cases your build will only execute on a particular java version, or with certain memory settings. Rather than having developers tweak their local environment to run your build, Gradle allows you to [explicitly configure the JVM args and Java location for your build](http://gradle.org/docs/1.0/userguide/build_environment.html). These settings can then be checked-in to version control and become part of a versioned build environment, improving the maintainability and consistency of [...]
+
+The build environment configuration is honoured by both daemon and non-daemon build executions. However, when configuring build environment settings we strongly recommend the use of the Gradle daemon on development machines, to avoid the performance penalty of starting an additional JVM on each build execution.
+
+#### Use an init script to define common build components
+Using a [Gradle init script](http://gradle.org/docs/1.0/userguide/init_scripts.html), it is possible to configure standard plugins, define standard repositories or apply certain common conventions across a wide range of projects. This feature makes it easy to use a particular standard plugin in a project without the hassle of configuring the build script classpath.
+
+Init scripts are very flexible, and may be applied to a single build execution, to all builds run by a particular user, or to all builds executed by a particular Gradle installation. In Gradle 1.0 it is now possible to have [multiple init scripts](http://gradle.org/docs/1.0/userguide/init_scripts.html#N15350) specified, which will be applied to your build environment in a well-defined order.
+
+#### Assemble a custom Gradle distribution for the enterprise
+Many large organisations desire a consistent set of environments, rules or plugins to be applied to all builds in the enterprise. By constructing Gradle init scripts that specify these corporate standards and bundling these with a regular Gradle distribution, you can construct a build tool that specifically enables and enforces your corporate standards. When combined with the powerful distribution mechanism of the Gradle wrapper, a tailored Gradle install can enable you to provide build  [...]
+
+### C++ support
+
+Gradle 1.0 includes preliminary [support for building C++ based projects](http://gradle.org/docs/1.0/userguide/cpp.html) on both Windows and UNIX like platforms. The `cpp-exe` and `cpp-lib` plugins can be used for building native executables and libraries respectively from C++ source code.
+
+#### Future plans
+These plugins are in the early stages of development, but can already be used to generate native binaries.
+
+Currently, there is no direct support for creating multiple variants of the same binary (e.g. 32 bit vs. 64 bit) and there is no direct support for cross platform source configuration (á la autoconf) at this time. Support for different compiler chains, managing multiple variants and cross platform source configuration will be added over time, making Gradle a fully capable build tool for C++ (and other "native" language) projects.
+
+The Gradle development team would like to encourage all users interested in C++ support to experiment with the existing functionality and provide feedback via the [Gradle Forums](http://forums.gradle.org/).
+
+### More plugins and simpler build scripts
+
+Gradle is committed to making it easier for you to develop software. We continue to add new plugins, conventions and features that make the everyday use of Gradle more convenient. More plugins means more build-by-convention support, reduced configuration and less custom build logic.
+
+Gradle now supports several different types of projects: plugins include support for building:
+
+* C++ libraries and executables
+* Applications that run on the JVM
+* Java EE Web applications
+* Java EE Enterprise applications
+
+Gradle 1.0 also includes numerous improvements to the build DSL and new features to make your build simpler and easier to maintain. 
+
+#### Application plugin
+
+The [Application plugin](http://gradle.org/docs/1.0/userguide/application_plugin.html) simplifies the creation of executable JVM based applications. You can use it to run your application directly from your Gradle build, and it will bundle your application for distribution. The packaged application includes automatically generated start scripts for windows and unix systems, third party dependencies and custom distribution files like licenses or documentation.
+
+#### Ear plugin
+The [Ear Plugin](http://gradle.org/docs/1.0/userguide/ear_plugin.html) adds support for creating Java EE Enterprise Archives (EAR files). It provides EAR specific dependency management and enables customization of the deployment descriptor.
+
+#### Signing plugin
+
+Digital signatures can be used for tracking build artifacts and files. These signatures allow users to verify who built the artifact and when the signature was created. The [Signing Plugin](http://gradle.org/docs/1.0/userguide/signing_plugin.html) enables the user to:
+
+* Define which artifacts to sign.
+* Configure required credentials.
+* Specify certain conditions that require signing, such as only signing for release versions.
+* Publish digital signatures and signed POM files.
+
+#### Announce plugin
+The [Gradle Announce Plugin](http://gradle.org/docs/1.0/userguide/announce_plugin.html) enables the build to announce custom messages to the user at any or every phase of the build. This feature can work well together with `--continue` (see below), by reporting on any failures that occur in a continued build.
+
+The announce plugin integrates nicely with different desktop messaging systems like [Snarl](https://sites.google.com/site/snarlapp/home), [Growl](http://growl.info/) and [Ubuntu Notify](http://www.ubuntu.com/) and also with the internet messaging system [Twitter](http://twitter.com/).
+
+#### Build continuation
+It can be annoying to come back from lunch, only to find that your build failed early on checkstyle and never got around to running your tests. The [`--continue`](http://gradle.org/docs/1.0/userguide/gradle_command_line.html) command line option provides experimental support for continuing a build after a task fails: the end result of the build is the original failure, but the remainder of the build will still execute.
+
+Note that this feature is a work in progress, and is not yet feature complete. In particular, only the first failure is captured and displayed at the end of the build process: you will need to inspect the build output to see what other failures may have occurred in a continued build.
+
+#### Log test output to the console
+
+Gradle strives to provide a clean and concise console output, without unnecessary clutter that hides the important things. So by default Gradle does not show the output of the tests on the console. Gradle 1.0 provides a a simple way to [display test output on the console](http://gradle.org/releases/1.0-milestone-6/docs/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testLogging). You can also hook in a listener to have [full control what test output is shown] [...]
+
+#### Better diagnosis tools
+In Gradle 1.0 we're providing better error messages, more documentation and improved samples to help you streamline any troubleshooting. The new [extra properties](http://gradle.org/docs/1.0/userguide/writing_build_scripts.html#sec:extra_properties) mechanism retains the ability to add ad-hoc properties to your build script, which making it much easier to catch typos.
+
+Naturally there is more work to do, and if you have questions don't hesitate to post them on the [Gradle forums](http://forums.gradle.org).
+
+## Upgrading from an earlier Gradle version
+For details of exactly what was changed in which Gradle 1.0 milestone, please see the release notes of each individual release. You can also consult the migration guide for each milestone release to identify issues that may affect you during an upgrade to Gradle 1.0.
+
+### Previous Release Notes
+
+* [Gradle-1.0-rc-3](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-rc-3+Release+Notes)
+* [Gradle-1.0-rc-2](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-rc-2+Release+Notes)
+* [Gradle-1.0-rc-1](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-rc-1+Release+Notes)
+* [Gradle-1.0-milestone-9](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-9+Release+Notes)
+* [Gradle-1.0-milestone-8a](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-8a+Release+Notes)
+* [Gradle-1.0-milestone-8](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-8+Release+Notes)
+* [Gradle-1.0-milestone-7](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-7+Release+Notes)
+* [Gradle-1.0-milestone-6](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-6+Release+Notes)
+* [Gradle-1.0-milestone-5](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-5+Release+Notes)
+* [Gradle-1.0-milestone-4](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-4+Release+Notes)
+* [Gradle-1.0-milestone-3](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-3+Release+Notes)
+* [Gradle-1.0-milestone-2](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-2+Release+Notes)
+* [Gradle-1.0-milestone-1](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-1+Release+Notes)
+
+## Fixed Issues
+
+The list of issues fixed between 1.0-milestone-9 and 1.0 can be found [here](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.0-rc-1%22%2C+%221.0-rc-2%22%2C+%221.0-rc-3%22%2C+%221.0%22%29+ORDER+BY+priority&tempMax=1000).
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl b/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
index 39f9b83..dbd1b2e 100644
--- a/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
+++ b/subprojects/docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
@@ -19,6 +19,10 @@
     <xsl:import href="highlighting/common.xsl"/>
     <xsl:import href="html/highlight.xsl"/>
 
+    <xsl:output method="html"
+                encoding="UTF-8"
+                indent="no"/>
+
     <xsl:param name="use.extensions">1</xsl:param>
     <xsl:param name="toc.section.depth">1</xsl:param>
     <xsl:param name="section.autolabel">1</xsl:param>
diff --git a/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl b/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl
index 3d542cc..351d486 100644
--- a/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl
+++ b/subprojects/docs/src/docs/stylesheets/userGuidePdf.xsl
@@ -17,6 +17,10 @@
     <xsl:import href="xhtml/docbook.xsl"/>
     <xsl:import href="userGuideHtmlCommon.xsl"/>
 
+    <xsl:output method="xml"
+                encoding="UTF-8"
+                indent="no"/>
+
     <!-- Use custom <head> content, to include stylesheets and bookmarks -->
 
     <xsl:template name="output.html.stylesheets">
diff --git a/subprojects/docs/src/docs/stylesheets/websiteHtml.xsl b/subprojects/docs/src/docs/stylesheets/websiteHtml.xsl
deleted file mode 100644
index 0056c50..0000000
--- a/subprojects/docs/src/docs/stylesheets/websiteHtml.xsl
+++ /dev/null
@@ -1,44 +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.
-  -->
-<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="section.autolabel">0</xsl:param>
-    <xsl:param name="chapter.autolabel">0</xsl:param>
-    <xsl:param name="appendix.autolabel">0</xsl:param>
-    <xsl:param name="root.filename">userguide</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>
-
-    <!-- Override this to remove all the html decorations from each 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>
-
-        <xsl:copy-of select="$content"/>
-    </xsl:template>
-
-    <!-- Override this to remove markup from xref links -->
-    <xsl:template match="chapter|appendix|section" mode="object.xref.markup">
-        <xsl:value-of select="title"/>
-    </xsl:template>
-</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/announcePlugin.xml b/subprojects/docs/src/docs/userguide/announcePlugin.xml
index fee4f01..533bde1 100644
--- a/subprojects/docs/src/docs/userguide/announcePlugin.xml
+++ b/subprojects/docs/src/docs/userguide/announcePlugin.xml
@@ -1,18 +1,3 @@
-<!--
-  ~ 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.
-  -->
 <chapter id='announce_plugin'>
     <title>The Announce Plugin</title>
     <para>The Gradle announce plugin enables you to publish messages on succeeded tasks to your favourite platforms.
diff --git a/subprojects/docs/src/docs/userguide/ant.xml b/subprojects/docs/src/docs/userguide/ant.xml
index 20ca034..6831128 100644
--- a/subprojects/docs/src/docs/userguide/ant.xml
+++ b/subprojects/docs/src/docs/userguide/ant.xml
@@ -99,8 +99,9 @@
 
         <section>
             <title>Using custom Ant tasks in your build</title>
-            <para>To make custom tasks available in your build, you use the <literal>typedef</literal> Ant task, just
-                as you would in a <literal>build.xml</literal> file. You can then refer to the custom Ant task as you
+            <para>To make custom tasks available in your build,
+                you can use the <literal>taskdef</literal> (usually easier) or <literal>typedef</literal> Ant task,
+                just as you would in a <literal>build.xml</literal> file. You can then refer to the custom Ant task as you
                 would a built-in Ant task.
             </para>
             <sample id="useExternalAntTask" dir="userguide/ant/useExternalAntTask" title="Using a custom Ant task">
diff --git a/subprojects/docs/src/docs/userguide/applicationPlugin.xml b/subprojects/docs/src/docs/userguide/applicationPlugin.xml
index 4294b26..29283ca 100644
--- a/subprojects/docs/src/docs/userguide/applicationPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/applicationPlugin.xml
@@ -35,7 +35,8 @@
         </para>
 
         <para>The plugin can also build a distribution for your application. The distribution will package up the runtime dependencies of the application
-            along with some OS specific start scripts. You can run <userinput>gradle install</userinput> to create an image of the application in
+            along with some OS specific start scripts. All files stored in <filename>src/dist</filename> will be added to the root of the
+            distribution. You can run <userinput>gradle installApp</userinput> to create an image of the application in
             <filename>build/install/<replaceable>projectName</replaceable></filename>. You can run <userinput>gradle distZip</userinput> to create a
             ZIP containing the distribution.
         </para>
@@ -76,7 +77,7 @@
             </tr>
             <tr>
                 <td>
-                    <literal>install</literal>
+                    <literal>installApp</literal>
                 </td>
                 <td>
                     <literal>jar</literal>, <literal>startScripts</literal>
@@ -102,4 +103,30 @@
             <para>The application plugin adds some properties to the project, which you can use to configure its behaviour. See <apilink class="org.gradle.api.Project"/>.
             </para>
     </section>
+    
+    <section id="application_distribution_resources">
+            <title>Including other resources in the distribution</title>
+            <para>
+                One of the convention properties added by the plugin is <literal>applicationDistribution</literal> which is a <apilink class="org.gradle.api.file.CopySpec"/>.
+                This specification is used by the <literal>installApp</literal> and <literal>distZip</literal> tasks as the specification of what is to be 
+                include in the distribution. Above copying the starting scripts to the <filename>bin</filename> dir and necessary jars to <filename>lib</filename> 
+                in the distribution, all of the files from the <filename>src/dist</filename> directory are also copied. To include any static files in the 
+                distribution, simply arrange them in the <filename>src/dist</filename> directory.
+            </para>
+            <para>
+                If your project generates files to be included in the distribution, e.g. documentation, you can add these files to the distribution by adding to the 
+                <literal>applicationDistribution</literal> copy spec.
+            </para>
+            <sample id="includeTaskOutputInApplicationDistribution" dir="application" title="Include output from other tasks in the application distribution">
+                <sourcefile file="build.gradle" snippet="distribution-spec"/>
+            </sample>
+            <para>
+                By specifying that the distribution should include the task's output files (see <xref linkend="sec:task_inputs_outputs"/>), Gradle knows 
+                that the task that produces the files must be invoked before the distribution can be assembled and will take care of this for you.
+            </para>
+            <sample id="dependentTaskForApplicationDistributionOutput" dir="application" title="Automatically creating files for distribution">
+                <output args="distZip"/>
+            </sample>
+    </section>
+    
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/artifactDependenciesTutorial.xml b/subprojects/docs/src/docs/userguide/artifactDependenciesTutorial.xml
index 9a1e9a3..a216488 100644
--- a/subprojects/docs/src/docs/userguide/artifactDependenciesTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/artifactDependenciesTutorial.xml
@@ -1,92 +1,214 @@
-<!--
-  ~ 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.
-  -->
-<chapter id="artifact_dependencies_tutorial" xmlns:xi="http://www.w3.org/2001/XInclude">
-    <title>Artifact Basics</title>
+<chapter id="artifact_dependencies_tutorial">
+    <title>Dependency Management Basics</title>
+    <para>
+        This chapter introduces some of the basics of dependency management in Gradle.
+    </para>
 
-    <note>
-        <para>This chapter is currently under construction.</para>
-        <para>For all the details of artifact handling see <xref linkend="artifact_management"/>.</para>
-    </note>
-
-    <para>This chapter introduces some of the basics of artifact handling in Gradle.</para>
+    <section>
+        <title>What is dependency management?</title>
+        <para>
+            Very roughly, dependency management is made up of two pieces. Firstly, Gradle needs to
+            know about the things that your project needs to build or run, in order to find them. We call these incoming
+            files the <firstterm>dependencies</firstterm> of the project.
+            Secondly, Gradle needs to build and upload the things that your project produces. We call these outgoing files
+            the <firstterm>publications</firstterm> of the project. Let's look at these two pieces in more detail:
+        </para>
+        <para>Most projects are not completely self-contained. They need files built by other projects in order to
+            be compiled or tested and so on. For example, in order to use Hibernate in my project, I need to include some
+            Hibernate jars in the classpath when I compile my source. To run my tests, I might also need to include some
+            additional jars in the test classpath, such as a particular JDBC driver or the Ehcache jars.
+        </para>
+        <para>
+            These incoming files form the dependencies of the project.
+            Gradle allows you to tell it what the dependencies of your project are, so that it can take care of
+            finding these dependencies, and making them available in your build.
+            The dependencies might need to be downloaded from a remote Maven or Ivy repository, or located in a local directory, or
+            may need to be built by another project in the same multi-project build.
+            We call this process <firstterm>dependency resolution</firstterm>.
+        </para>
+        <para>
+            Often, the dependencies of a project will themselves have dependencies. For example, Hibernate core requires
+            several other libraries to be present on the classpath with it runs. So, when Gradle runs the tests for your
+            project, it also needs to find these dependencies and make them available. We call these <firstterm>transitive dependencies</firstterm>.
+        </para>
+        <para>
+            The main purpose of most projects is to build some files that are to be used outside the project.
+            For example, if your project produces a java library, you need to build a jar, and maybe a source jar and
+            some documentation, and publish them somewhere.
+        </para>
+        <para>These outgoing files form the publications of the project. Gradle also takes care of this important work for you. You declare the
+            publications of your project, and Gradle take care of building them and publishing them somewhere.
+            Exactly what "publishing" means depends on what you want to do. You might want to copy the files to a local directory,
+            or upload them to a remote Maven or Ivy repository. Or you might use the files in another project in the same
+            multi-project build.
+            We call this process <firstterm>publication</firstterm>.
+        </para>
+    </section>
 
-    <section id="sec:artifact_configurations">
-        <title>Artifact configurations</title>
-        <para>Artifacts are grouped into <firstterm>configurations</firstterm>. A configuration is simply a set of files
-            with a name. You can use them to declare the external dependencies your project has, or to declare the
-            artifacts which your project publishes.</para>
-        <para>To define a configuration:</para>
-        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Definition of a configuration">
-            <sourcefile file="build.gradle" snippet="define-configuration"/>
+    <section>
+        <title>Declaring your dependencies</title>
+        <para>
+            Let's look at some dependency declarations. Here's a basic build script:
+        </para>
+        <sample id="basicDependencyDeclarations" dir="userguide/artifacts/dependencyBasics" title="Declaring dependencies">
+            <sourcefile file="build.gradle"/>
         </sample>
-        <para>To access a configuration:</para>
-        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Accessing a configuration">
-            <sourcefile file="build.gradle" snippet="lookup-configuration"/>
+        <para>What's going on here? This build script says a few things about the project.
+            Firstly, it states that Hibernate core 3.6.7.Final is required
+            to compile the project's production source. By implication, Hibernate core and its dependencies are also required
+            at runtime.
+            The build script also states that any junit >= 4.0 is required to compile the project's tests.
+            It also tells Gradle to look in the Maven central repository for any dependencies that are required.
+            The following sections go into the details.
+        </para>
+    </section>
+
+    <section id="configurations">
+        <title>Dependency configurations</title>
+        <para>In Gradle dependencies are grouped into <firstterm>configurations</firstterm>. A configuration is simply a named set of dependencies.
+            We will refer to them as <firstterm>dependency configurations</firstterm>. You can use them to declare the external dependencies of your project.
+            As we will see later, they are also used to declare the publications of your project.</para>
+        <para>
+            The Java plugin defines a number of standard configurations. These configurations represent the classpaths that the
+            Java plugin uses. Some are listed below, and you can find more details in <xref linkend="tab:configurations"/>.
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>compile</term>
+                <listitem>
+                    <para>The dependencies required to compile the production source of the project.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>runtime</term>
+                <listitem>
+                    <para>
+                        The dependencies required by the production classes at runtime. By default, also includes the
+                        compile time dependencies.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>testCompile</term>
+                <listitem>
+                    <para>
+                        The dependencies required to compile the test source of the project. By default, also includes
+                        the compiled production classes and the compile time dependencies.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>testRuntime</term>
+                <listitem>
+                    <para>
+                        The dependencies required to run the tests. By default, also includes the compile, runtime and test compile
+                        dependencies.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+
+        <para>
+            Various plugins add further standard configurations. You can also define your own custom configurations to use in your
+            build. Please see <xref linkend="sub:configurations"/> for the details of defining and customizing dependency configurations.
+        </para>
+    </section>
+
+    <section>
+        <title>External dependencies</title>
+        <para>There are various types of dependencies that you can declare. One such type is an <firstterm>external dependency</firstterm>.
+            This a dependency on some files built outside the current build, and stored in a repository of some kind, such as Maven central, or
+            a corporate Maven or Ivy repository, or a directory in the local file system.
+        </para>
+        <para>
+            To define an external dependency, you add it to a dependency configuration:
+        </para>
+        <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Definition of an external dependency">
+            <sourcefile file="build.gradle" snippet="define-dependency"/>
         </sample>
-        <para>To configure a configuration:</para>
-        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Configuration of a configuration">
-            <sourcefile file="build.gradle" snippet="configure-configuration"/>
+        <para>
+            An external dependency is identified using <literal>group</literal>, <literal>name</literal> and <literal>version</literal> attributes.
+            Depending on which kind of repository you are using, <literal>group</literal> and <literal>version</literal> may be optional.
+        </para>
+        <para>There is a shortcut form for declaring external dependencies, which uses a string of the form <literal>"<replaceable>group</replaceable>:<replaceable>name</replaceable>:<replaceable>version</replaceable>"</literal>.
+        </para>
+        <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Shortcut definition of an external dependency">
+            <sourcefile file="build.gradle" snippet="define-dependency-shortcut"/>
         </sample>
+        <para>To find out more about defining and working with dependencies, have a look at <xref linkend="sec:how_to_declare_your_dependencies"/>.</para>
     </section>
 
     <section>
         <title>Repositories</title>
-        <para>Artifacts are stored in <firstterm>repositories</firstterm>.</para>
-        <para>To use maven central repository:</para>
-        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Usage of Maven central repository">
+        <para>
+            How does Gradle find the files for external dependencies? Gradle looks for them in a <firstterm>repository</firstterm>.
+            A repository is really just a collection of files, organized by <literal>group</literal>, <literal>name</literal> and
+            <literal>version</literal>. Gradle understands several different repository formats, such as Maven and Ivy, and several
+            different ways of accessing the repository, such as using the local file system or HTTP.
+        </para>
+        <para>By default, Gradle does not define any repositories. You need to define at least one before you can use external dependencies.
+            One option is use the Maven central repository:</para>
+        <sample id="defineMavenCentral" dir="userguide/artifacts/defineRepository" title="Usage of Maven central repository">
             <sourcefile file="build.gradle" snippet="maven-central"/>
         </sample>
-        <para>To use a local directory:</para>
-        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Usage of a local directory">
-            <sourcefile file="build.gradle" snippet="flat-dir"/>
+        <para>Or a remote Maven repository:</para>
+        <sample id="defineRemoteMavenRepo" dir="userguide/artifacts/defineRepository" title="Usage of a remote Maven repository">
+            <sourcefile file="build.gradle" snippet="maven-like-repo"/>
         </sample>
-        <para>You can also use any Ivy resolver. You can have multiple repositories.</para>
-        <para>To access a repository:</para>
-        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Accessing a repository">
-            <sourcefile file="build.gradle" snippet="lookup-resolver"/>
+        <para>Or a remote Ivy repository:</para>
+        <sample id="defineRemoteIvyRepo" dir="userguide/artifacts/defineRepository" title="Usage of a remote Ivy directory">
+            <sourcefile file="build.gradle" snippet="ivy-repo"/>
         </sample>
-        <para>To configure a repository:</para>
-        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Configuration of a repository">
-            <sourcefile file="build.gradle" snippet="configure-resolver"/>
+        <para>You can also have repositories on the local file system. This works for both Maven and Ivy repositories.</para>
+        <sample id="defineRemoteIvyRepo" dir="userguide/artifacts/defineRepository" title="Usage of a local Ivy directory">
+            <sourcefile file="build.gradle" snippet="local-ivy-repo"/>
         </sample>
+        <para>A project can have multiple repositories. Gradle will look for a dependency in each repository in the order they are specified, stopping
+            at the first repository that contains the requested module.
+        </para>
+        <para>To find out more about defining and working with repositories, have a look at <xref linkend="sec:repositories"/>.</para>
     </section>
 
     <section>
-        <title>External dependencies</title>
-        <para>To define an external dependency, you add a dependency to a configuration:</para>
-        <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Definition of an external dependency">
-            <sourcefile file="build.gradle" snippet="define-dependency"/>
+        <title>Publishing artifacts</title>
+        <para>
+            Dependency configurations are also used to publish files.<footnote><para>We think this is confusing, and we are gradually teasing apart the two concepts in the Gradle DSL.</para></footnote>
+            We call these files <firstterm>publication artifacts</firstterm>, or usually just <firstterm>artifacts</firstterm>.
+        </para>
+        <para>
+            The plugins do a pretty good job of defining the artifacts of a project, so you usually don't need to do anything special to tell Gradle
+            what needs to be published. However, you do need to tell Gradle where to publish the artifacts. You do this by attaching repositories
+            to the <literal>uploadArchives</literal> task. Here's an example of publishing to a remote Ivy repository:
+        </para>
+        <sample id="publishIvyRepository" dir="userguide/artifacts/uploading" title="Publishing to an Ivy repository">
+            <sourcefile file="build.gradle" snippet="publish-repository"/>
         </sample>
-        <para><literal>group</literal> and <literal>version</literal> are optional</para>
-        <para>TBD - configuring an external dependency</para>
-        <para>To use the external dependencies of a configuration:</para>
-        <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies"  title="Usage of external dependency of a configuration">
-            <sourcefile file="build.gradle" snippet="use-configuration"/>
-            <output args="-q listJars"/>
+        <para>
+            Now, when you run <userinput>gradle uploadArchives</userinput>, Gradle will build and upload your Jar.
+            Gradle will also generate and upload an <filename>ivy.xml</filename> as well.
+        </para>
+        <para>
+            You can also publish to Maven repositories. The syntax is slightly different.<footnote>
+                <para>We are working to make the syntax consistent for resolving from and publishing to Maven repositories.</para>
+            </footnote>
+            Note that you also need to apply the Maven plugin in order to publish to a Maven repository. In this instance, Gradle
+            will generate and upload a <filename>pom.xml</filename>.
+        </para>
+        <sample id="publishMavenRepository" dir="userguide/artifacts/maven" title="Publishing to a Maven repository">
+            <sourcefile file="build.gradle" snippet="upload-file"/>
         </sample>
+        <para>To find out more about publication, have a look at <xref linkend="artifact_management"/>.</para>
     </section>
 
     <section>
-        <title>Artifact publishing</title>
-        <para>TBD</para>
-    </section>
-
-    <section>
-        <title>API</title>
-        <para>Configurations are contained in a <apilink class="org.gradle.api.artifacts.ConfigurationContainer"/>.
-            Each configuration implements the <apilink class="org.gradle.api.artifacts.Configuration"/>.</para>
+        <title>Where to next?</title>
+        <para>
+            For all the details of dependency resolution, see <xref linkend="dependency_management"/>, and for artifact publication see <xref linkend="artifact_management"/>.
+        </para>
+        <para>
+            If you are interested in the DSL elements mentioned here, have a look at <apilink class="org.gradle.api.Project" method="configurations"/>,
+            <apilink class="org.gradle.api.Project" method="repositories"/> and <apilink class="org.gradle.api.Project" method="dependencies"/>.
+        </para>
+        <para>Otherwise, continue on to some of the other <link linkend="tutorials">tutorials</link>.</para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/artifactMngmt.xml b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
index e81f189..8d5dded 100644
--- a/subprojects/docs/src/docs/userguide/artifactMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
@@ -13,49 +13,70 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id="artifact_management" xmlns:xi="http://www.w3.org/2001/XInclude">
-    <title>Artifact Management</title>
+<chapter id="artifact_management">
+    <title>Publishing artifacts</title>
     <section>
         <title>Introduction</title>
-        <para>This chapter is about how you declare what are the artifacts of your project and how to work with
-            them (e.g. upload them). We define the artifacts of the projects as the files the project want to
-            provide to the outside world. This can be a library or a distribution or any other file. Usually artifacts
-            are archives, but not necessarily. In the Maven world a project can provide only one artifact. With Gradle
-            a project can provide as many artifacts as needed.
+        <para>This chapter is about how you declare the outgoing artifacts of your project, and how to work with
+            them (e.g. upload them). We define the artifacts of the projects as the files the project provides to the
+            outside world. This might be a library or a ZIP distribution or any other file. A project can publish as many artifacts as it wants.
         </para>
     </section>
     <section id="artifacts_and_configurations">
         <title>Artifacts and configurations</title>
         <para>Like dependencies, artifacts are grouped by configurations. In fact, a configuration can contain
-            both, artifacts and dependencies, at the same time. To assign an artifact to a configuration, you can write:
+            both artifacts and dependencies at the same time.
         </para>
-        <sample id="assignArtifact" dir="userguide/artifacts/uploading" title="Assignment of an artifact to a configuration">
-            <sourcefile file="build.gradle" snippet="assign-artifact"/>
-        </sample>
-        <para>What do you gain by assigning an artifact to a configuration? For each configuration (also for the custom
-        ones added by you) Gradle provides the tasks <code>upload[ConfigurationName]</code> and
-        <code>build[ConfigurationName]</code>.
-        <footnote><para>To be exact, the Base plugin provides those tasks. The BasePlugin is automatically applied, if you use
+        <para>For each configuration in your project, Gradle provides the tasks <literal>upload<replaceable>ConfigurationName</replaceable></literal> and
+        <literal>build<replaceable>ConfigurationName</replaceable></literal>.
+        <footnote><para>To be exact, the Base plugin provides those tasks. This plugin is automatically applied if you use
         the Java plugin.</para></footnote>
-        Execution of these tasks will build or upload the artifacts belonging to
-        the respective configuration.
+        Execution of these tasks will build or upload the artifacts belonging to the respective configuration.
         </para>
         <para>Table <xref linkend="tab:configurations"/> shows the configurations added by the Java plugin. Two of the
         configurations are relevant for the usage with artifacts. The <code>archives</code> configuration is the standard
         configuration to assign your artifacts to. The Java plugin automatically assigns the default jar to this
-        configuration. We will talk more about the <code>default</code> configuration in <xref linkend="project_libraries"/>.
-            As with dependencies, you can declare as many custom configurations as you like and assign artifacts to them.
+        configuration. We will talk more about the <code>runtime</code> configuration in <xref linkend="project_libraries"/>.
+        As with dependencies, you can declare as many custom configurations as you like and assign artifacts to them.
         </para>
-        <para>It is important to note that the custom archives you are creating as part of your build are not
-        automatically assigned to any configuration. You have to explicitly do this assignment.</para>
     </section>
+
+    <section>
+        <title>Declaring artifacts</title>
+        <section>
+            <title>Archive task artifacts</title>
+            <para>You can use an archive task to define an artifact:</para>
+            <sample id="archiveTaskArtifact" dir="userguide/artifacts/uploading" title="Defining an artifact using an archive task">
+                <sourcefile file="build.gradle" snippet="archive-artifact"/>
+            </sample>
+            <para>It is important to note that the custom archives you are creating as part of your build are not
+            automatically assigned to any configuration. You have to explicitly do this assignment.</para>
+        </section>
+        <section>
+            <title>File artifacts</title>
+            <para>You can also use a file to define an artifact:</para>
+            <sample id="fileArtifact" dir="userguide/artifacts/uploading" title="Defining an artifact using a file">
+                <sourcefile file="build.gradle" snippet="file-artifact"/>
+            </sample>
+            <para>Gradle will figure out the properties of the artifact based on the name of the file. You can customize these properties:</para>
+            <sample id="fileArtifact" dir="userguide/artifacts/uploading" title="Customizing an artifact">
+                <sourcefile file="build.gradle" snippet="customised-file-artifact"/>
+            </sample>
+            <para>There is a map-based syntax for defining an artifact using a file. The map must include a <literal>file</literal> entry that
+                defines the file. The map may include other artifact properties:
+            </para>
+            <sample id="fileArtifact" dir="userguide/artifacts/uploading" title="Map syntax for defining an artifact using a file">
+                <sourcefile file="build.gradle" snippet="map-file-artifact"/>
+            </sample>
+        </section>
+    </section>
+
     <section>
-        <title>Uploading artifacts</title>
+        <title>Publishing artifacts</title>
         <para>We have said that there is a specific upload task for each configuration. But before you can do an upload,
-            you have to configure the upload task and define where to upload. The repositories you have defined (as described
-            in <xref linkend="sec:repositories"/>) are not automatically used for uploading. In fact, some of those repositories allow only for artifacts downloading.
-            Here is an example how
-            you can configure the upload task of a configuration:
+            you have to configure the upload task and define where to publish the artifacts to. The repositories you have defined (as described
+            in <xref linkend="sec:repositories"/>) are not automatically used for uploading. In fact, some of those repositories allow only for artifact downloading.
+            Here is an example how you can configure the upload task of a configuration:
         </para>
         <sample id="uploading" dir="userguide/artifacts/uploading" title="Configuration of the upload task">
             <sourcefile file="build.gradle" snippet="uploading"/>
@@ -68,11 +89,10 @@
     <section id="project_libraries">
         <title>More about project libraries</title>
         <para>If your project is supposed to be used as a library, you need to define what are the artifacts of this library
-            and what are the dependencies of these artifacts. The Java plugin adds a <code>default</code> configuration for
-            this purpose. This configuration extends both the <code>archives</code> and the <code>runtime</code> configuration,
-            with the implicit assumption that the <code>runtime</code> dependencies are the dependencies of the <code>archives</code>
-            configuration. Of course this is fully customizable. You can add your own custom configuration or let the the
-            existing configurations extends from other configurations. You might have different group of artifacts which have
+            and what are the dependencies of these artifacts. The Java plugin adds a <code>runtime</code> configuration for
+            this purpose, with the implicit assumption that the <code>runtime</code> dependencies are the dependencies of
+            the artifact you want to publish. Of course this is fully customizable. You can add your own custom configuration or let the
+            existing configurations extend from other configurations. You might have different group of artifacts which have
             a different set of dependencies. This mechanism is very powerful and flexible.
             </para>
         <para>If someone wants to use your project as a library, she simply needs to declare on which configuration of
@@ -81,7 +101,7 @@
             is not specified, the <code>default</code> configuration is used (see <xref linkend="sec:dependency_configurations"/>).
             Using your project as a library
             can either happen from within a multi-project build or by retrieving your project from a repository. In
-            the latter case, an ivy.xml descriptor in the repository is supposed to contain all the neccesary information. If you
+            the latter case, an ivy.xml descriptor in the repository is supposed to contain all the necessary information. If you
             work with Maven repositories you don't have the flexibility as described above. For how to publish to a Maven
             repository, see the section <xref linkend="uploading_to_maven_repositories"/>.
         </para>
diff --git a/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml b/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
new file mode 100644
index 0000000..b44b974
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
@@ -0,0 +1,24 @@
+<chapter id='build_announcements_plugin'>
+    <title>The Build Announcements Plugin</title>
+    <note>
+        <para>
+            The build announcements is experimental and should not be considered stable.
+        </para>
+    </note>
+    <para>The build announcements plugin uses the <link linkend="announce_plugin">announce</link> plugin to send local announcements on important events in the build.</para>
+    <section>
+        <title>Usage</title>
+        <para>To use the build announcements plugin, include in your build script:</para>
+        <sample id="useBuildAnnouncementsPlugin" dir="announce" title="Using the build announcements plugin">
+            <sourcefile file="build.gradle" snippet="use-build-announcements-plugin"/>
+        </sample>
+        <para>That's it. If you want to tweak where the announcements go, you can configure the <link linkend="announce_plugin">announce</link> plugin to
+            change the local announcer.
+        </para>
+        <para>You can also apply the plugin from an init script:</para>
+        <sample id="useBuildAnnouncementsPlugin" dir="announce" title="Using the build announcements plugin from an init script">
+            <sourcefile file="init.gradle"/>
+            <test args="-I init.gradle"/>
+        </sample>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/buildEnvironment.xml b/subprojects/docs/src/docs/userguide/buildEnvironment.xml
new file mode 100644
index 0000000..937c201
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/buildEnvironment.xml
@@ -0,0 +1,128 @@
+<!--
+  ~ Copyright 2012 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.
+  -->
+<chapter id='build_environment'>
+    <title>The Build Environment</title>
+
+    <section id="sec:gradle_configuration_properties">
+        <title>Configuring the build environment via gradle.properties</title>
+        <para>Gradle provides several options that make it easy to configure the Java process that will be used to execute your build.
+            While it's possible to configure these in your local environment via GRADLE_OPTS or JAVA_OPTS,
+            certain settings like jvm memory settings, java home, daemon on/off
+            can be more useful if they can versioned with the project in your VCS so that
+            the entire team can work with consistent environment.
+            Setting up a consistent environment for your build is as simple as placing those settings into a <filename>gradle.properties</filename> file.
+            The configuration is applied in following order
+            (in case an option is configured in multiple locations the last one wins):
+            <itemizedlist>
+                <listitem>from <filename>gradle.properties</filename> located in project build dir.</listitem>
+                <listitem>from <filename>gradle.properties</filename> located in <literal>gradle user home</literal>.</listitem>
+                <listitem>from system properties, e.g. when <literal>-Dsome.property</literal> is used in the command line.</listitem>
+            </itemizedlist>
+        </para>
+        <para>
+            The following properties can be used to configure the Gradle build environment:
+            <varlistentry>
+                <term><literal>org.gradle.daemon</literal></term>
+                <listitem><para>When set to <literal>true</literal> the Gradle daemon is to run the build.
+                    For local developer builds this is our favorite property. The developer environment is optimized for speed and feedback
+                    so we nearly always run Gradle jobs with the daemon.
+                    We don't run CI builds with the daemon (i.e. a long running process)
+                    as the CI environment is optimized for consistency and reliability.
+                </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><literal>org.gradle.java.home</literal></term>
+                <listitem><para>Specifies the java home for the Gradle build process.
+                    The value can be set to either <literal>jdk</literal> or <literal>jre</literal> location,
+                    however, depending on what does your build do, <literal>jdk</literal> is safer.
+                    Reasonable default is used if the setting is unspecified.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><literal>org.gradle.jvmargs</literal></term>
+                <listitem><para>Specifies the jvmargs used for the daemon process.
+                    The setting is particularly useful for tweaking memory settings.
+                    At the moment the default settings are pretty generous with regards to memory.</para>
+                </listitem>
+            </varlistentry>
+        </para>
+        <section>
+            <title>Forked java processes</title>
+            <para>Many settings (like the java version and maximum heap size) can only be specified when launching a new JVM for the build process. This means that Gradle
+                must launch a separate JVM process to execute the build after parsing the various <filename>gradle.properties</filename> files.
+                When running with the daemon, a JVM with the correct parameters is started once and reused for each daemon build execution.
+                When Gradle is executed without the daemon, then a new JVM must be launched for every build execution,
+                unless the JVM launched by the Gradle start script happens to have the same parameters.
+            </para>
+            <para>
+            This launching of an extra JVM on every build execution is quite expensive, which is why we highly recommend that you use the Gradle Daemon if you are
+            specifying <literal>org.gradle.java.home</literal> or <literal>org.gradle.jvmargs</literal>. See <xref linkend="gradle_daemon"/> for more details.</para>
+        </section>
+    </section>
+
+    <section id='sec:accessing_the_web_via_a_proxy'>
+        <title>Accessing the web via a proxy</title>
+        <para>Configuring an HTTP proxy (for example for downloading dependencies) is done via standard JVM system properties. These properties can be set directly in the build script; for example <literal>
+                System.setProperty('http.proxyHost', 'www.somehost.org')</literal> for the proxy host. Alternatively, the properties can be specified in a gradle.properties file,
+            either in the build's root directory or in the Gradle home directory.
+        </para>
+        <example>
+            <title>Configuring an HTTP proxy</title>
+            <para><filename>gradle.properties</filename></para>
+            <programlisting><![CDATA[
+systemProp.http.proxyHost=www.somehost.org
+systemProp.http.proxyPort=8080
+systemProp.http.proxyUser=userid
+systemProp.http.proxyPassword=password
+systemProp.http.nonProxyHosts=*.nonproxyrepos.com|localhost	
+]]></programlisting>
+        </example>
+        <para>There are separate settings for HTTPS.</para>
+        <example>
+            <title>Configuring an HTTPS proxy</title>
+            <para><filename>gradle.properties</filename></para>
+            <programlisting><![CDATA[
+systemProp.https.proxyHost=www.somehost.org
+systemProp.https.proxyPort=8080
+systemProp.https.proxyUser=userid
+systemProp.https.proxyPassword=password
+systemProp.https.nonProxyHosts=*.nonproxyrepos.com|localhost
+]]></programlisting>
+        </example>
+        <para>We could not find a good overview for all possible proxy settings. One place to look are the constants
+            in a file from the Ant project. Here a
+            <ulink url='http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/util/ProxySetup.java?view=markup&pathrev=556977'>
+                link
+            </ulink> to the Subversion view. The other is a
+            <ulink url='http://download.oracle.com/javase/6/docs/technotes/guides/net/properties.html'>
+                Networking Properties page
+            </ulink> from the JDK docs. If anyone knows a better overview, please let us know via the mailing list.
+        </para>
+        <section id='sub:ntlm_authentication'>
+            <title>NTLM Authentication</title>
+            <para>If your proxy requires NTLM authentication, you may need to provide the authentication domain as well as the username and password.
+                There are 2 ways that you can provide the domain for authenticating to a NTLM proxy:
+                <itemizedlist>
+                    <listitem>Set the <literal>http.proxyUser</literal> system property to a value like <literal><replaceable>domain</replaceable>/<replaceable>username</replaceable></literal>.
+                    </listitem>
+                    <listitem>Provide the authentication domain via the <literal>http.auth.ntlm.domain</literal> system property.
+                    </listitem>
+                </itemizedlist>
+            </para>
+        </section>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/buildLifecycle.xml b/subprojects/docs/src/docs/userguide/buildLifecycle.xml
index ddfcd30..a66dd09 100644
--- a/subprojects/docs/src/docs/userguide/buildLifecycle.xml
+++ b/subprojects/docs/src/docs/userguide/buildLifecycle.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='build_lifecycle' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='build_lifecycle'>
     <title>The Build Lifecycle</title>
     <para>We said earlier, that the core of Gradle is a language for dependency based programming. In Gradle terms this
         means that you can define tasks and dependencies between tasks. Gradle guarantees that these tasks are executed
@@ -95,9 +95,11 @@
         <section id='sub:project_locations'>
             <title>Project locations</title>
             <para>Multi-project builds are always represented by a tree with a single root. Each element in the tree
-                represent a project. A project has a virtual and a physical path. The virtual path denotes the position
-                of the project in the multi-project build tree. The project tree is created in the
-                <filename>settings.gradle</filename> file. By default it is assumed that the location of the settings
+                represents a project. A project has a path which denotes the position
+                of the project in the multi-project build tree. In majority of cases the project path is consistent with
+                the physical location of the project in the file system. However, this behavior is configurable.
+                The project tree is created in the <filename>settings.gradle</filename> file.
+                By default it is assumed that the location of the settings
                 file is also the location of the root project. But you can redefine the location of the root project
                 in the settings file.
             </para>
@@ -112,11 +114,12 @@
                 <sample id="standardLayouts" dir="userguide/multiproject/standardLayouts" title="Hierarchical layout">
                     <sourcefile file="settings.gradle" snippet="hierarchical-layout"/>
                 </sample>
-                <para>The <literal>include</literal> method takes as an argument a relative virtual path to the root
-                    project. This relative virtual path is assumed to be equal to the relative physical path of the
-                    subproject to the root project. You only need to specify the leafs of the tree. Each parent path of
-                    the leaf project is assumed to be another subproject which obeys to the physical path assumption
-                    described above.
+                <para>The <literal>include</literal> method takes project paths as arguments.
+                    The project path is assumed to be equal to the relative physical file system path.
+                    For example a path 'services:api' by default is mapped to a folder 'services/api'
+                    (relative from the project root). You only need to specify the leafs of the tree.
+                    This means that the inclusion of path 'services:hotels:api' will result in creating 3 projects:
+                    'services', 'services:hotels' and 'services:hotels:api'.
                 </para>
             </section>
             <section>
@@ -126,7 +129,7 @@
                 </sample>
                 <para>The <literal>includeFlat</literal> method takes directory names as an argument. Those directories
                     need to exist at the same level as the root project directory. The location of those directories
-                    are considered as child projects of the root project in the virtual multi-project tree.
+                    are considered as child projects of the root project in the multi-project tree.
                 </para>
             </section>
         </section>
@@ -222,7 +225,7 @@
     <section id="build_lifecycle_events">
         <title>Responding to the lifecycle in the build script</title>
 
-        <para>Your build script can receive notifications as the build progresses through its lifecyle. These
+        <para>Your build script can receive notifications as the build progresses through its lifecycle. These
             notifications generally take 2 forms: You can either implement a particular listener interface, or you can
             provide a closure to execute when the notification is fired. The examples below use closures. For details on
             how to use the listener interfaces, refer to the API documentation.
diff --git a/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml b/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
index 0cdb16f..e1b3352 100644
--- a/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
@@ -158,14 +158,17 @@
         <para>This enables very readable code, especially when using the out of the box tasks provided by the plugins
             (e.g. <literal>compile</literal>).</para>
     </section>
-    <section id='sec:dynamic_properties'>
-        <title>Dynamic task properties</title>
-        <para>You can assign arbitrary <emphasis>new</emphasis> properties to any task.
-        </para>
-        <sample id="dynamicProperties" dir="userguide/tutorial/dynamicProperties" title="Assigning properties to a task">
-            <sourcefile file="build.gradle"/>
+    <section id='sec:extra_task_properties'>
+        <title>Extra task properties</title>
+        <para>You can add your own properties to a task. To add a property named <literal>myProperty</literal>,
+            set <literal>ext.myProperty</literal> to an initial value. From that point on, the property can be read
+            and set like a predefined task property.
+        </para>
+        <sample id="extraTaskProperties" dir="userguide/tutorial/extraProperties" title="Adding extra properties to a task">
+            <sourcefile file="build.gradle" snippet="taskProperty"/>
             <output args="-q showProps"/>
         </sample>
+        Extra properties aren't limited to tasks. You can read more about them in <xref linkend='sec:extra_properties'/>.
     </section>
     <section>
         <title>Using Ant Tasks</title>
@@ -228,10 +231,13 @@
         </para>
     </section>
     <section>
-        <title>Summary</title>
-        <para>This is not the end of the story for tasks. So far we have worked with simple tasks. Tasks will be
-            revisited in <xref linkend='more_about_tasks'/> and when we look at the Java plugin in
-            <xref linkend='java_plugin'/>.
+        <title>Where to next?</title>
+        <para>
+            In this chapter, we have had a first look at tasks. But this is not the end of the story for tasks. If you want to jump into more of the details,
+            have a look at <xref linkend='more_about_tasks'/>.
+        </para>
+        <para>
+            Otherwise, continue on to the tutorials in <xref linkend="tutorial_java_projects"/> and <xref linkend="artifact_dependencies_tutorial"/>.
         </para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/checkstylePlugin.xml b/subprojects/docs/src/docs/userguide/checkstylePlugin.xml
new file mode 100644
index 0000000..7097f21
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/checkstylePlugin.xml
@@ -0,0 +1,113 @@
+<chapter id="checkstyle_plugin">
+    <title>The Checkstyle Plugin</title>
+    <para>The Checkstyle plugin performs quality checks on your project's Java source files using <ulink url="http://checkstyle.sourceforge.net/index.html">Checkstyle</ulink>
+        and generates reports from these checks.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Checkstyle plugin, include in your build script:</para>
+        <sample id="useCheckstylePlugin" dir="codeQuality" title="Using the Checkstyle plugin">
+            <sourcefile file="build.gradle" snippet="use-checkstyle-plugin"/>
+        </sample>
+        <para>The plugin adds a number of tasks to the project that perform the quality checks. You can execute the checks by running <userinput>gradle check</userinput>.</para>
+    </section>
+    <section>
+        <title>Tasks</title>
+        <para>The Checkstyle plugin adds the following tasks to the project:</para>
+        <table>
+            <title>Checkstyle plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>checkstyleMain</literal>
+                </td>
+                <td><literal>classes</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
+                <td>Runs Checkstyle against the production Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyleTest</literal>
+                </td>
+                <td><literal>testClasses</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
+                <td>Runs Checkstyle against the test Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyle<replaceable>SourceSet</replaceable></literal>
+                </td>
+                <td><literal><replaceable>sourceSet</replaceable>Classes</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
+                <td>Runs Checkstyle against the given source set's Java source files.</td>
+            </tr>
+        </table>
+
+        <para>The Checkstyle plugin adds the following dependencies to tasks defined by the Java plugin.</para>
+        <table>
+            <title>Checkstyle plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td><literal>check</literal></td>
+                <td>All Checkstyle tasks, including <literal>checkstyleMain</literal> and <literal>checkstyleTest</literal>.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The Checkstyle plugin expects the following project layout:</para>
+        <table>
+            <title>Checkstyle plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>File</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>config/checkstyle/checkstyle.xml</filename>
+                </td>
+                <td>Checkstyle configuration file</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Checkstyle plugin adds the following dependency configurations:</para>
+        <table>
+            <title>Checkstyle plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>checkstyle</filename>
+                </td>
+                <td>The Checkstyle libraries to use</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Configuration</title>
+        <para>See <apilink class="org.gradle.api.plugins.quality.CheckstyleExtension"/>.</para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/codeNarcPlugin.xml b/subprojects/docs/src/docs/userguide/codeNarcPlugin.xml
new file mode 100644
index 0000000..224d0db
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/codeNarcPlugin.xml
@@ -0,0 +1,114 @@
+<chapter id="codenarc_plugin">
+    <title>The CodeNarc Plugin</title>
+    <para>The CodeNarc plugin performs quality checks on your project's Groovy source files using <ulink url="http://codenarc.sourceforge.net/index.html">CodeNarc</ulink>
+        and generates reports from these checks.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the CodeNarc plugin, include in your build script:</para>
+        <sample id="useCodeNarcPlugin" dir="codeQuality" title="Using the CodeNarc plugin">
+            <sourcefile file="build.gradle" snippet="use-codenarc-plugin"/>
+        </sample>
+        <para>The plugin adds a number of tasks to the project that perform the quality checks. You can execute the checks by running <userinput>gradle check</userinput>.</para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The CodeNarc plugin adds the following tasks to the project:</para>
+        <table>
+            <title>CodeNarc plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>codenarcMain</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
+                <td>Runs CodeNarc against the production Groovy source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>codenarcTest</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
+                <td>Runs CodeNarc against the test Groovy source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>codenarc<replaceable>SourceSet</replaceable></literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
+                <td>Runs CodeNarc against the given source set's Groovy source files.</td>
+            </tr>
+        </table>
+
+        <para>The CodeNarc plugin adds the following dependencies to tasks defined by the Groovy plugin.</para>
+        <table>
+            <title>CodeNarc plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td><literal>check</literal></td>
+                <td>All CodeNarc tasks, including <literal>codenarcMain</literal> and <literal>codenarcTest</literal>.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The CodeNarc plugin expects the following project layout:</para>
+        <table>
+            <title>CodeNarc plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>File</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>config/codenarc/codenarc.xml</filename>
+                </td>
+                <td>CodeNarc configuration file</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The CodeNarc plugin adds the following dependency configurations:</para>
+        <table>
+            <title>CodeNarc plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>codenarc</filename>
+                </td>
+                <td>The CodeNarc libraries to use</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Configuration</title>
+        <para>See <apilink class="org.gradle.api.plugins.quality.CodeNarcExtension"/>.</para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/codeQualityPlugin.xml b/subprojects/docs/src/docs/userguide/codeQualityPlugin.xml
deleted file mode 100644
index 9481247..0000000
--- a/subprojects/docs/src/docs/userguide/codeQualityPlugin.xml
+++ /dev/null
@@ -1,322 +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.
-  -->
-<chapter id="code_quality_plugin">
-    <title>The Code Quality Plugin</title>
-
-    <para>The code quality plugin adds tasks which perform code quality checks and generate reports from these checks.
-        The following tools are supported:
-        <itemizedlist>
-            <listitem><para><ulink url="http://checkstyle.sourceforge.net/index.html">Checkstyle</ulink></para></listitem>
-            <listitem><para><ulink url="http://codenarc.sourceforge.net/index.html">CodeNarc</ulink></para></listitem>
-        </itemizedlist>
-    </para>
-
-    <section>
-        <title>Usage</title>
-        <para>To use the code quality plugin, include in your build script:</para>
-        <sample id="useCodeQualityPlugin" dir="codeQuality" title="Using the code quality plugin">
-            <sourcefile file="build.gradle" snippet="use-plugin"/>
-        </sample>
-    </section>
-
-    <section>
-        <title>Tasks</title>
-        <para>When used with the Java plugin, the code quality plugin adds the following tasks to the project:</para>
-        <table>
-            <title>Code quality plugin - Java tasks</title>
-            <thead>
-                <tr>
-                    <td>Task name</td>
-                    <td>Depends on</td>
-                    <td>Type</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <literal>checkstyleMain</literal>
-                </td>
-                <td>-</td>
-                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
-                <td>Runs Checkstyle against the production Java source files.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>checkstyleTest</literal>
-                </td>
-                <td><literal>compile</literal></td>
-                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
-                <td>Runs Checkstyle against the test Java source files.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>checkstyle<replaceable>SourceSet</replaceable></literal>
-                </td>
-                <td>-</td>
-                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
-                <td>Runs Checkstyle against the given source set's Java source files.</td>
-            </tr>
-        </table>
-
-        <para>When used with the Groovy plugin, the code quality plugin adds the following tasks to the project:</para>
-
-        <table>
-            <title>Code quality plugin - tasks</title>
-            <thead>
-                <tr>
-                    <td>Task name</td>
-                    <td>Depends on</td>
-                    <td>Type</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>codenarcMain</td>
-                <td>-</td>
-                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
-                <td>Runs CodeNarc against the production Groovy source files.</td>
-            </tr>
-            <tr>
-                <td>codenarcTest</td>
-                <td>-</td>
-                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
-                <td>Runs CodeNarc against the test Groovy source files.</td>
-            </tr>
-            <tr>
-                <td>codenarc<replaceable>SourceSet</replaceable></td>
-                <td>-</td>
-                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
-                <td>Runs CodeNarc against the given source set's Groovy source files.</td>
-            </tr>
-        </table>
-        <para>The Code quality plugin adds the following dependencies to tasks added by the Java plugin.</para>
-        <table>
-            <title>Code quality plugin - additional task dependencies</title>
-            <thead>
-                <td>Task name</td>
-                <td>Depends on</td>
-            </thead>
-            <tr>
-                <td>check</td>
-                <td>All Checkstyle and CodeNarc tasks, including <literal>checkstyleMain</literal>,
-                    <literal>checkstyleTest</literal>,
-                    <literal>codenarcMain</literal> and
-                    <literal>codenarcTest</literal>
-                </td>
-            </tr>
-        </table>
-        <figure>
-            <title>Code quality plugin - tasks</title>
-            <imageobject>
-                <imagedata fileref="img/codeQualityPluginTasks.png"/>
-            </imageobject>
-        </figure>
-    </section>
-
-    <section>
-        <title>Project layout</title>
-        <para>The code quality plugin expects the following project layout:</para>
-        <table>
-            <title>Code quality plugin - project layout</title>
-            <thead>
-                <tr>
-                    <td>File</td>
-                    <td>Meaning</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <filename>config/checkstyle/checkstyle.xml</filename>
-                </td>
-                <td>Checkstyle configuration file</td>
-            </tr>
-            <tr>
-                <td>
-                    <filename>config/codenarc/codenarc.xml</filename>
-                </td>
-                <td>CodeNarc configuration file</td>
-            </tr>
-        </table>
-    </section>
-
-    <section>
-        <title>Dependency management</title>
-        <para>The code quality plugin does not add any dependency configurations.</para>
-    </section>
-
-    <section>
-        <title>Convention properties</title>
-        <para>When used with the Java plugin, the code quality plugin adds the following convention properties to the project:</para>
-        <table>
-            <title>Code quality plugin - convention properties</title>
-            <thead>
-                <tr>
-                    <td>Property name</td>
-                    <td>Type</td>
-                    <td>Default value</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <literal>checkstyleConfigFileName</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
-                </td>
-                <td>
-                    <literal>config/checkstyle/checkstyle.xml</literal>
-                </td>
-                <td>
-                    The location of the Checkstyle configuration file, relative to the project directory.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>checkstyleConfigFile</literal>
-                </td>
-                <td>
-                    <classname>File</classname> (read-only)
-                </td>
-                <td>
-                    <literal><replaceable>projectDir</replaceable>/<replaceable>checkstyleConfigFileName</replaceable></literal>
-                </td>
-                <td>
-                    The Checkstyle configuration file.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>checkstyleResultsDirName</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
-                </td>
-                <td>
-                    <literal>checkstyle</literal>
-                </td>
-                <td>
-                    The name of the directory to generate Checkstyle results into, relative to the build directory.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>checkstyleResultsDir</literal>
-                </td>
-                <td>
-                    <classname>File</classname> (read-only)
-                </td>
-                <td>
-                    <literal><replaceable>buildDir</replaceable>/<replaceable>checkstyleResultsDirName</replaceable></literal>
-                </td>
-                <td>
-                    The directory to generate Checkstyle results into.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>checkstyleProperties</literal>
-                </td>
-                <td>
-                    <classname>Map</classname>
-                </td>
-                <td>
-                    <literal>[:]</literal>
-                </td>
-                <td>
-                    The properties to use when loading the Checkstyle configuration.
-                </td>
-            </tr>
-        </table>
-
-        <para>These convention properties are provided by a convention object of type
-            <apilink class="org.gradle.api.plugins.quality.JavaCodeQualityPluginConvention"/>.</para>
-
-        <para>When used with the Groovy plugin, the code quality plugin adds the following convention properties to the project:</para>
-
-        <table>
-            <title>Code quality plugin - convention properties</title>
-            <thead>
-                <tr>
-                    <td>Property name</td>
-                    <td>Type</td>
-                    <td>Default value</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <literal>codeNarcConfigFileName</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
-                </td>
-                <td>
-                    <literal>config/codenarc/codenarc.xml</literal>
-                </td>
-                <td>
-                    The location of the CodeNarc configuration file, relative to the project directory.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>codeNarcConfigFile</literal>
-                </td>
-                <td>
-                    <classname>File</classname> (read-only)
-                </td>
-                <td>
-                    <literal><replaceable>projectDir</replaceable>/<replaceable>codeNarcConfigFileName</replaceable></literal>
-                </td>
-                <td>
-                    The CodeNarc configuration file.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>codeNarcReportsDirName</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
-                </td>
-                <td>
-                    <literal>codenarc</literal>
-                </td>
-                <td>
-                    The name of the directory to generate CodeNarc reports into, relative to the reports directory.
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>codeNarcReportsDir</literal>
-                </td>
-                <td>
-                    <classname>File</classname> (read-only)
-                </td>
-                <td>
-                    <literal><replaceable>reportsDir</replaceable>/<replaceable>codeNarcReportsDirName</replaceable></literal>
-                </td>
-                <td>
-                    The directory to generate CodeNarc reports into.
-                </td>
-            </tr>
-        </table>
-
-        <para>These convention properties are provided by a convention object of type
-            <apilink class="org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention"/>.</para>
-    </section>
-
-</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/commandLine.xml b/subprojects/docs/src/docs/userguide/commandLine.xml
index a4f7a7f..5ebe5b5 100644
--- a/subprojects/docs/src/docs/userguide/commandLine.xml
+++ b/subprojects/docs/src/docs/userguide/commandLine.xml
@@ -1,231 +1,416 @@
-<!--
-  ~ 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.
-  -->
 <appendix id='gradle_command_line'>
     <title>Gradle Command Line</title>
-    <para>The <command>gradle</command> command has the following usage:
+    <para>The
+        <command>gradle</command>
+        command has the following usage:
         <cmdsynopsis>
             <command>gradle</command>
             <arg choice="opt" rep="repeat">option</arg>
             <arg choice="opt" rep="repeat">task</arg>
         </cmdsynopsis>
-        The command-line options available for the <command>gradle</command> command are listed below:
+        The command-line options available for the
+        <command>gradle</command>
+        command are listed below:
     </para>
     <variablelist>
         <varlistentry>
-            <term><option>-?</option>, <option>-h</option>, <option>--help</option></term>
-            <listitem><para>Shows a help message.</para></listitem>
+            <term><option>-?</option>, <option>-h</option>, <option>--help</option>
+            </term>
+            <listitem>
+                <para>Shows a help message.</para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-a</option>, <option>--no-rebuild</option>
+            </term>
+            <listitem>
+                <para>Do not rebuild project dependencies.
+                </para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term>
+                <option>--all</option>
+            </term>
+            <listitem>
+                <para>Shows additional detail in the task listing. See <xref linkend="sec:listing_tasks"/>.
+                </para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-b</option>, <option>--build-file</option>
+            </term>
+            <listitem>
+                <para>Specifies the build file. See <xref linkend="sec:selecting_build"/>.
+                </para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-c</option>, <option>--settings-file</option>
+            </term>
+            <listitem>
+                <para>Specifies the settings file.</para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-C</option>, <option>--cache</option></term>
-            <listitem><para>Specifies how compiled build scripts should be cached. Possible values are:
-                <literal>rebuild</literal> or <literal>on</literal>. Default value is
-                <literal>on</literal>. See <xref linkend="sec:caching"/>.
-            </para></listitem>
+            <term>
+                <option>--continue</option>
+            </term>
+            <listitem>
+                <para>Continues task execution after a task failure.</para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-D</option>, <option>--system-prop</option></term>
-            <listitem><para>Sets a system property of the JVM, for example <literal>-Dmyprop=myvalue</literal>.
-            </para></listitem>
+            <term><option>-D</option>, <option>--system-prop</option>
+            </term>
+            <listitem>
+                <para>Sets a system property of the JVM, for example <literal>-Dmyprop=myvalue</literal>.
+                    See <xref linkend="sec:gradle_properties_and_system_properties"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-I</option>, <option>--init-script</option></term>
-            <listitem><para>Specifies an initialization script. See <xref linkend="init_scripts"/>.</para></listitem>
+            <term><option>-d</option>, <option>--debug</option>
+            </term>
+            <listitem>
+                <para>Log in debug mode (includes normal stacktrace). See <xref linkend="logging"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-P</option>, <option>--project-prop</option></term>
-            <listitem><para>Sets a project property of the root project, for example
-                <literal>-Pmyprop=myvalue</literal>.
-            </para></listitem>
+            <term><option>-g</option>, <option>--gradle-user-home</option>
+            </term>
+            <listitem>
+                <para>Specifies the Gradle user home directory. The default is the
+                    <filename>.gradle</filename>
+                    directory in the user's home directory.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-S</option>, <option>--full-stacktrace</option></term>
-            <listitem><para>Print out the full (very verbose) stacktrace for any exceptions. See <xref linkend="logging"/>.
-            </para></listitem>
+            <term>
+                <option>--gui</option>
+            </term>
+            <listitem>
+                <para>Launches the Gradle GUI. See <xref linkend="tutorial_gradle_gui"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-a</option>, <option>--no-rebuild</option></term>
-            <listitem><para>Do not rebuild project dependencies.
-            </para></listitem>
+            <term><option>-I</option>, <option>--init-script</option>
+            </term>
+            <listitem>
+                <para>Specifies an initialization script. See <xref linkend="init_scripts"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>--all</option></term>
-            <listitem><para>Shows additional detail in the task listing. See <xref linkend="sec:listing_tasks"/>.
-            </para></listitem>
+            <term><option>-i</option>, <option>--info</option>
+            </term>
+            <listitem>
+                <para>Set log level to info. See <xref linkend="logging"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-b</option>, <option>--build-file</option></term>
-            <listitem><para>Specifies the build file. See <xref linkend="sec:selecting_build"/>.
-            </para></listitem>
+            <term><option>-m</option>, <option>--dry-run</option>
+            </term>
+            <listitem>
+                <para>Runs the build with all task actions disabled. See <xref linkend="sec:dry_run"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-c</option>, <option>--settings-file</option></term>
-            <listitem><para>Specifies the settings file.</para></listitem>
+            <term>
+                <option>--no-color</option>
+            </term>
+            <listitem>
+                <para>Do not use color in the console output.</para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-d</option>, <option>--debug</option></term>
-            <listitem><para>Log in debug mode (includes normal stacktrace). See <xref linkend="logging"/>.
-            </para></listitem>
+            <term>
+                <option>--offline</option>
+            </term>
+            <listitem>
+                <para>Specifies that the build should operate without accessing network resources. See <xref linkend="sec:cache_command_line_options"/>.</para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-e</option>, <option>--embedded</option></term>
-            <listitem><para>Specify an embedded build script.
-            </para></listitem>
+            <term><option>-P</option>, <option>--project-prop</option>
+            </term>
+            <listitem>
+                <para>Sets a project property of the root project, for example
+                    <literal>-Pmyprop=myvalue</literal>. See <xref linkend="sec:gradle_properties_and_system_properties"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-g</option>, <option>--gradle-user-home</option></term>
-            <listitem><para>Specifies the Gradle user home directory.
-            </para></listitem>
+            <term><option>-p</option>, <option>--project-dir</option>
+            </term>
+            <listitem>
+                <para>Specifies the start directory for Gradle. Defaults to current directory.
+                    See <xref linkend="sec:selecting_build"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>--gui</option></term>
-            <listitem><para>Launches the Gradle GUI. See <xref linkend="tutorial_gradle_gui"/>.
-            </para></listitem>
+            <term>
+                <option>--profile</option>
+            </term>
+            <listitem>
+                <para>Profiles build execution time and generates a report in the
+                    <filename><replaceable>buildDir</replaceable>/reports/profile</filename>
+                    directory. See <xref linkend="sec:profiling_build"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-i</option>, <option>--info</option></term>
-            <listitem><para>Set log level to info. See <xref linkend="logging"/>.
-            </para></listitem>
+            <term>
+                <option>--project-cache-dir</option>
+            </term>
+            <listitem>
+                <para>Specifies the project-specific cache directory. Default value is
+                    <filename>.gradle</filename>
+                    in the root project directory. See <xref linkend="sec:caching"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-m</option>, <option>--dry-run</option></term>
-            <listitem><para>Runs the build with all task actions disabled.</para> </listitem>
+            <term><option>-q</option>, <option>--quiet</option>
+            </term>
+            <listitem>
+                <para>Log errors only. See <xref linkend="logging"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>--no-color</option></term>
-            <listitem><para>Do not use color in the console output.</para></listitem>
+            <term>
+                <option>--recompile-scripts</option>
+            </term>
+            <listitem>
+                <para>Specifies that cached build scripts are skipped and forced to be recompiled.
+                    See <xref linkend="sec:caching"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-p</option>, <option>--project-dir</option></term>
-            <listitem><para>Specifies the start directory for Gradle. Defaults to current directory.
-                See <xref linkend="sec:selecting_build"/>.
-            </para></listitem>
+            <term>
+                <option>--refresh-dependencies</option>
+            </term>
+            <listitem>
+                <para>Refresh the state of dependencies.
+                    See <xref linkend="sec:cache_command_line_options"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>--profile</option></term>
-            <listitem><para>Profiles build execution time and generates a report in the
-                <build_dir>/reports/profile directory.
-            </para></listitem>
+            <term>
+                <option>--rerun-tasks</option>
+            </term>
+            <listitem>
+                <para>Specifies that any task optimization is ignored.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-q</option>, <option>--quiet</option></term>
-            <listitem><para>Log errors only. See <xref linkend="logging"/>.</para></listitem>
+            <term><option>-S</option>, <option>--full-stacktrace</option>
+            </term>
+            <listitem>
+                <para>Print out the full (very verbose) stacktrace for any exceptions. See <xref linkend="logging"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-s</option>, <option>--stacktrace</option></term>
-            <listitem><para>Print out the stacktrace also for user exceptions (e.g. compile error). See <xref linkend="logging"/>.
-            </para></listitem>
+            <term><option>-s</option>, <option>--stacktrace</option>
+            </term>
+            <listitem>
+                <para>Print out the stacktrace also for user exceptions (e.g. compile error). See <xref linkend="logging"/>.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-u</option>, <option>--no-search-upwards</option></term>
-            <listitem><para>Don't search in parent directories for a <filename>settings.gradle</filename> file.
-            </para></listitem>
+            <term><option>-u</option>, <option>--no-search-upwards</option>
+            </term>
+            <listitem>
+                <para>Don't search in parent directories for a
+                    <filename>settings.gradle</filename>
+                    file.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-v</option>, <option>--version</option></term>
-            <listitem><para>Prints version info.
-            </para></listitem>
+            <term><option>-v</option>, <option>--version</option>
+            </term>
+            <listitem>
+                <para>Prints version info.
+                </para>
+            </listitem>
         </varlistentry>
         <varlistentry>
-            <term><option>-x</option>, <option>--exclude-task</option></term>
-            <listitem><para>Specifies a task to be excluded from execution. See <xref linkend="sec:excluding_tasks_from_the_command_line"/>.
-            </para></listitem>
+            <term><option>-x</option>, <option>--exclude-task</option>
+            </term>
+            <listitem>
+                <para>Specifies a task to be excluded from execution. See <xref linkend="sec:excluding_tasks_from_the_command_line"/>.
+                </para>
+            </listitem>
         </varlistentry>
     </variablelist>
-    <para>The above information is printed to the console when you execute <userinput>gradle -h</userinput>.</para>
+    <para>The above information is printed to the console when you execute <userinput>gradle -h</userinput>.
+    </para>
 
     <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>
+                <term><option>-C</option>, <option>--cache</option>
+                </term>
+                <listitem>
+                    <para>(deprecated) Specifies how compiled build scripts should be cached. Possible values are:
+                        <literal>rebuild</literal>
+                        or <literal>on</literal>. Default value is
+                        <literal>on</literal>. You should use
+                        <option>--recompile-scripts</option>
+                        instead.
+                    </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>
+                <term><option>--no-opt</option>
+                </term>
+                <listitem>
+                    <para>(deprecated) Specifies to ignore all task optimization. You should use
+                        <option>--rerun-tasks</option>
+                        instead.
+                    </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>
+                <term><option>--refresh</option>
+                </term>
+                <listitem>
+                    <para>(deprecated) Refresh the state of resources of the type(s) specified. Currently only <literal>dependencies</literal> is supported.
+                        You should use <option>--refresh-dependencies</option> instead.
+                    </para>
                 </listitem>
             </varlistentry>
         </variablelist>
     </section>
 
-    <section>
-        <title>Experimental command-line options</title>
-        <para>The following options are experimental:</para>
+    <section id='daemon_cli'>
+        <title>Daemon command-line options:</title>
+        <para>The
+            <xref linkend="gradle_daemon"/>
+            contains more information about the daemon.
+            For example it includes information how to turn on the daemon by default
+            so that you can avoid using
+            <literal>--daemon</literal>
+            all the time.
+        </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>
+                <term>
+                    <option>--daemon</option>
+                </term>
+                <listitem>
+                    <para>Uses the Gradle daemon to run the build.
+                        Starts the daemon if not running or existing daemon busy.
+                        <xref linkend="gradle_daemon"/>
+                        contains more detailed
+                        information when new daemon processes are started.
+                    </para>
+                </listitem>
             </varlistentry>
             <varlistentry>
-                <term><option>--foreground</option></term>
-                <listitem><para>Starts the Gradle daemon in the foreground.</para></listitem>
+                <term>
+                    <option>--foreground</option>
+                </term>
+                <listitem>
+                    <para>Starts the Gradle daemon in the foreground. Useful for debugging or troubleshooting
+                        because you can easily monitor the build execution.
+                    </para>
+                </listitem>
             </varlistentry>
             <varlistentry>
-                <term><option>--no-daemon</option></term>
-                <listitem><para>Do not use the Gradle daemon to run the build.</para></listitem>
+                <term>
+                    <option>--no-daemon</option>
+                </term>
+                <listitem>
+                    <para>Do not use the Gradle daemon to run the build.
+                        Useful occasionally if you have configured Gradle to always run with the daemon by default.
+                    </para>
+                </listitem>
             </varlistentry>
             <varlistentry>
-                <term><option>--stop</option></term>
-                <listitem><para>Stops the Gradle daemon if it is running.</para></listitem>
+                <term>
+                    <option>--stop</option>
+                </term>
+                <listitem>
+                    <para>Stops the Gradle daemon if it is running.
+                        You can only stop daemons that were started with
+                        the Gradle version you use when running <literal>--stop</literal>.
+                    </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
+        <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><literal>gradle.user.home</literal></term>
-            <listitem><para>Specifies the Gradle user home directory.</para> </listitem>
-        </varlistentry>
-        <varlistentry>
-            <term><literal>org.gradle.daemon</literal></term>
-            <listitem><para>When set to <literal>true</literal>, use the Gradle daemon to run the build.</para>
+            <term>
+                <literal>gradle.user.home</literal>
+            </term>
+            <listitem>
+                <para>Specifies the Gradle user home directory.</para>
             </listitem>
         </varlistentry>
+        <para>The
+            <xref linkend="sec:gradle_configuration_properties"/>
+            contains specific information about
+            Gradle configuration available via system properties.
+        </para>
     </section>
 
     <section>
         <title>Environment variables</title>
-        <para>The following environment variables are available for the <command>gradle</command> command. Note that
+        <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_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>
+                <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.
+                        <xref linkend="sec:gradle_configuration_properties"/>
+                        contains more information about ways of configuring the daemon
+                        without using environmental variables, e.g. in more maintainable and explicit way.
+                    </para>
+                </listitem>
             </varlistentry>
             <varlistentry>
-                <term><literal>GRADLE_USER_HOME</literal></term>
-                <listitem><para>Specifies the Gradle user home directory.</para></listitem>
+                <term>
+                    <literal>GRADLE_USER_HOME</literal>
+                </term>
+                <listitem>
+                    <para>Specifies the Gradle user home directory.</para>
+                </listitem>
             </varlistentry>
         </variablelist>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
index eef59cb..8b98cd6 100644
--- a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
@@ -179,7 +179,7 @@
         </section>
     </section>
 
-    <section>
+    <section id="sec:dry_run">
         <title>Dry Run</title>
         <para>Sometimes you are interested in which tasks are executed in which order for a given set of tasks specified on the
             command line, but you don't want the tasks to be executed. You can use the <option>-m</option> option for this.
diff --git a/subprojects/docs/src/docs/userguide/cpp.xml b/subprojects/docs/src/docs/userguide/cpp.xml
new file mode 100755
index 0000000..bee4ccc
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/cpp.xml
@@ -0,0 +1,188 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id='cpp'>
+    <title>C++ Support</title>
+
+    <note>
+        <para>
+            The Gradle C++ support is in very early stages of development. Please be aware that the DSL and other configuration may change in later Gradle versions.
+        </para>
+    </note>
+
+    <para>
+        The C++ plugins add support for building software comprised of C++ source code, and managing the process of building “native” software in general.
+        While many excellent build tools exist for this space of software development, Gradle brings the dependency management practices more traditionally
+        found in the JVM development space to C++ developers.
+    </para>
+    <para>
+        The following platforms are supported:
+    </para>
+    <table>
+        <thread>
+            <tr><td>Operating System</td><td>Compiler</td><td>Notes</td></tr>
+        </thread>
+        <tr>
+            <td>Linux</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td><td>Tested with GCC 4.6.1 on Ubuntu 11.10</td>
+        </tr>
+        <tr>
+            <td>Mac OS X</td><td><ulink url="http://gcc.gnu.org/">GCC</ulink></td><td>Tested with XCode 4.2.1 on OS X 10.7</td>
+        </tr>
+        <tr>
+            <td>Windows</td><td><ulink url="http://www.microsoft.com/visualstudio/en-us">Visual C++</ulink></td><td>Tested with Windows 7 and Visual C++ 2010</td>
+        </tr>
+        <tr>
+            <td>Windows</td><td><ulink url="http://www.mingw.org/">MinGW</ulink></td><td>Tested with Windows 7 and MinGW 4.6.2. Note: G++ support is currently broken under cygwin</td>
+        </tr>
+    </table>
+    <para>
+        Currently, there is no direct support for creating multiple variants of the same binary (e.g. 32 bit vs. 64 bit) and there is no direct
+        support for cross platform source configuration (à la <ulink url="http://www.gnu.org/s/autoconf/">autoconf</ulink>) at this time. Support for different
+        compiler chains, managing multiple variants and cross platform source configuration will be added over time, making Gradle a fully capable build tool for C++
+        (and other “native” language) projects.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>
+            The build scripts DSLs, model elements and tasks used to manage C++ projects are added by the <literal>cpp</literal> plugin. However, it is typically
+            more convenient to use either the <literal>cpp-lib</literal> or <literal>cpp-exe</literal> plugins that sit on top of the <literal>cpp</literal>
+            plugin to preconfigure the project to build either a shared library or executable binary respectively.
+        </para>
+        <sample id="useCppExePlugin" dir="cpp/dependencies" title="Using the 'cpp-exe' plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin-exe"/>
+        </sample>
+        <sample id="useCppLibPlugin" dir="cpp/dependencies" title="Using the 'cpp-lib' plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin-lib"/>
+        </sample>
+        <para>
+            The <literal>cpp-exe</literal> plugin configures the project to build a single executable (at <filename><replaceable>$buildDir</replaceable>/binaries/<replaceable>$project.name</replaceable></filename>) and
+            the <literal>cpp-lib</literal> plugin configures the project to build a single shared library (at <filename><replaceable>$buildDir</replaceable>/binaries/lib<replaceable>$project.name</replaceable>.so</filename>).
+        </para>
+    </section>
+
+    <section>
+        <title>Source code locations</title>
+        <para>
+            Both plugins configure the project to look for <filename>.cpp</filename> and <filename>.c</filename> source files in <filename>src/main/cpp</filename> and use the <filename>src/main/headers</filename>
+            directory as a header include root. For a library, the header files in <filename>src/main/headers</filename> are considered the “public” or “exported” headers.
+            Header files that should not be exported (but are used internally) should be placed inside the <filename>src/main/cpp</filename> directory (though be aware that
+            such header files should always be referenced in a manner relative to the file including them).
+        </para>
+        <para>
+            The <literal>cpp</literal> plugin is also very flexible in where it looks for source and header files, aand you can configure the above conventions to look however you
+            like.
+        </para>
+    </section>
+
+    <section>
+        <title>Compiling</title>
+        <para>
+            For both the <literal>cpp-lib</literal> and <literal>cpp-exe</literal> plugins, you can run <userinput>gradle compileMain</userinput> to compile and link the binary.
+        </para>
+        <section>
+            <title>Compiling on UNIX</title>
+            <para>
+                The UNIX C++ support is currently based on the <literal>g++</literal> tool which must be installed and on the <literal>PATH</literal>
+                for the Gradle process.
+            </para>
+        </section>
+        <section>
+            <title>Compiling on Windows</title>
+            <para>
+                The Windows C++ support can use either the MinGW <literal>g++</literal> or the Microsoft Visual C++ <literal>cl</literal> tool, either of which must be installed and on the
+                <literal>PATH</literal> for the Gradle process. Gradle searches first for Microsoft Visual C++, and then MinGW.
+            </para>
+        </section>
+    </section>
+
+    <section>
+        <title>Configuring the compiler</title>
+        <para>Arbitrary arguments can be provided to the compiler by using the following syntax:</para>
+        <sample id="gppArgs" dir="cpp/exe" title="Supplying arbitrary args to the compiler">
+            <sourcefile file="build.gradle" snippet="args"/>
+        </sample>
+        <para>
+            The above example applies to the <literal>cpp-exe</literal> plugin, to supply arguments for the <literal>cpp-lib</literal> plugin replace
+            “<literal>executables</literal>” with “<literal>libraries</literal>”.
+        </para>
+    </section>
+
+    <section>
+        <title>Working with shared libraries</title>
+        <para>
+            The C++ plugin provides an <literal>installMain</literal> task, which creates a development install of the executable, along with the shared libraries it requires.
+            This allows you to run the executable without needing to install the shared libraries in their final locations.
+        </para>
+    </section>
+
+    <section>
+        <title>Dependencies</title>
+        <para>
+            Dependencies for C++ projects are binary libraries that export header files. The header files are used during compilation, with the compiled
+            binary dependency being used during the linking.
+        </para>
+        <section>
+            <title>External Dependencies</title>
+            <para>
+                External dependencies (i.e. from a repository, not a subproject) must be specified using the following syntax:
+            </para>
+            <sample id="gppArgs" dir="cpp/dependencies" title="Declaring dependencies">
+                <sourcefile file="build.gradle" snippet="declaring-dependencies"/>
+            </sample>
+            <para>
+                Each dependency must be specified with the <literal>dependency</literal> method as above and must be declared as part of the source set. The
+                <literal>group</literal>, <literal>name</literal> and <literal>version</literal> arguments <emphasis>must</emphasis> be supplied.
+            </para>
+            <para>
+                For each declared dependency, two actual dependencies are created. One with the classifier “<literal>headers</literal>” and extension
+                “<literal>zip</literal>” which is a zip file of the exported headers, and another with the classifier “<literal>so</literal>” and extension
+                “<literal>so</literal>” which is the compiled library binary to link against (which is supplied as a direct input to the g++ link operation).
+            </para>
+        </section>
+        <section>
+            <title>Project Dependencies</title>
+            <para>
+                The notation for project dependencies is slightly different.
+            </para>
+            <sample id="cppProjectDependencies" dir="cpp/exewithlib" title="Declaring project dependencies">
+                <sourcefile file="build.gradle" snippet="project-dependencies"/>
+            </sample>
+        </section>
+    </section>
+
+    <section>
+        <title>Publishing</title>
+        <para>
+            The <literal>cpp-exe</literal> and <literal>cpp-lib</literal> plugins configure their respective output binaries to be publishable as part of the
+            <literal>archives</literal> configuration. To publish, simply configure the <literal>uploadArchives</literal> task as per usual.
+        </para>
+        <sample id="cppPublish" dir="cpp/dependencies" title="Uploading exe or lib">
+            <sourcefile file="build.gradle" snippet="upload"/>
+        </sample>
+        <para>
+            The <literal>cpp-exe</literal> plugin publishes a single artifact with extension “<literal>exe</literal>”. The <literal>cpp-lib</literal> plugin
+            publishes two artifacts; one with classifier “<literal>headers</literal>” and extension “<literal>zip</literal>”, and one with classifier
+            “<literal>so</literal>” and extension “<literal>so</literal>” (which is the format used when consuming dependencies).
+        </para>
+        <note>
+            <para>
+                Currently, there is no support for publishing the dependencies of artifacts in POM or Ivy files. Future versions will support this.
+            </para>
+        </note>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/customPlugins.xml b/subprojects/docs/src/docs/userguide/customPlugins.xml
index 6c89061..5281c33 100644
--- a/subprojects/docs/src/docs/userguide/customPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/customPlugins.xml
@@ -1,18 +1,3 @@
-<!--
-  ~ 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.
-  -->
 <chapter id="custom_plugins">
     <title>Writing Custom Plugins</title>
 
@@ -94,48 +79,64 @@
 
     <section>
         <title>Getting input from the build</title>
-        <para>Most plugins need to obtain some configuration from the build script. One method for doing this is to use <firstterm>convention objects</firstterm>.
-             The Gradle <apilink class="org.gradle.api.Project"/> has a <apilink class="org.gradle.api.plugins.Convention"/> object 
+        <para>Most plugins need to obtain some configuration from the build script. One method for doing this is to use <firstterm>extension objects</firstterm>.
+             The Gradle <apilink class="org.gradle.api.Project"/> has an associated <apilink class="org.gradle.api.plugins.ExtensionContainer"/> object
              that helps keep track of all the settings and properties being passed to plugins. You can capture user input by telling
-             the Project Convention about your plugin. To capture input, simply add a Java Bean compliant class into the Convention's list of 
-             plugins. Groovy is a good language choice for a plugin because plain old Groovy objects contain all the getter and setter methods
+             the extension container about your plugin. To capture input, simply add a Java Bean compliant class into the extension container's list of
+             extensions. Groovy is a good language choice for a plugin because plain old Groovy objects contain all the getter and setter methods
              that a Java Bean requires. 
         </para>
 
-        <para>Let's add a simple convention object to the project. Here we add a <literal>greeting</literal> property to the
+        <para>Let's add a simple extension object to the project. Here we add a <literal>greeting</literal> extension object to the
             project, which allows you to configure the greeting.
         </para>
 
-        <sample id="customPluginWithConvention" dir="userguide/organizeBuildLogic/customPluginWithConvention" title="A custom plugin convention">
+        <sample id="customPluginWithConvention" dir="userguide/organizeBuildLogic/customPluginWithConvention" title="A custom plugin extension">
             <sourcefile file="build.gradle"/>
             <output args="-q hello"/>
         </sample>
 
-        <para>In this example, <classname>GreetingPluginConvention</classname> is a plain old Groovy object with a field called <literal>greeting</literal>.
-            The convention object is added to the plugin list with the name <literal>greet</literal>. The name of the variable in
-            the build needs to match the name of the field in the convention object.  The name you choose for your plugin 
-            (<literal>greet</literal>) is arbitrary and can be whatever you choose. 
+        <para>In this example, <classname>GreetingPluginExtension</classname> is a plain old Groovy object with a field called <literal>message</literal>.
+            The extension object is added to the plugin list with the name <literal>greeting</literal>. This object then becomes available as a project property
+            with the same name as the extension object.
         </para>
 
-        <para>Oftentimes, you have several related properties you need to specify on a single plugin. With Groovy plugins it is easy
-            to offer a configuration closure block to group settings together. The following example shows you how to do this. 
+        <para>Oftentimes, you have several related properties you need to specify on a single plugin. Gradle adds a configuration closure block for each extension object, so you
+            can group settings together. The following example shows you how this works.
         </para>
 
-        <sample id="customPluginWithAdvancedConvention" dir="userguide/organizeBuildLogic/customPluginWithAdvancedConvention" title="A custom plugin with closure convention">
+        <sample id="customPluginWithAdvancedConvention" dir="userguide/organizeBuildLogic/customPluginWithAdvancedConvention" title="A custom plugin with configuration closure">
             <sourcefile file="build.gradle"/>
             <output args="-q hello"/>
         </sample>
 
-        <para>In this example, several convention settings can be grouped together within the <literal>greet</literal> closure. 
-            The name of the closure block in the build script (<literal>greet</literal>) needs a matching method on 
-            the convention object, and that method must take a closure as an argument. Then, when the closure is executed, 
-            the fields on the convention object will be mapped to the variables within the closure based on the standard
-            Groovy closure delegate feature. This technique is possible in other JVM languages but may not be as convenient 
-            as in Groovy. 
+        <para>In this example, several settings can be grouped together within the <literal>greeting</literal> closure.
+            The name of the closure block in the build script (<literal>greeting</literal>) needs to match the extension object name.
+            Then, when the closure is executed, the fields on the extension object will be mapped to the variables within the closure
+            based on the standard Groovy closure delegate feature.
         </para>
     </section>
 
     <section>
+        <title>Working with files in custom tasks and plugins</title>
+        <para>
+            When developing custom tasks and plugins, it's a good idea to be very flexible when accepting input configuration for file locations.
+            To do this, you can leverage the <apilink class="org.gradle.api.Project" method="file(java.lang.Object)"/> method to resolve values to files as late as possible.
+        </para>
+        <sample id="lazyFileProperties" dir="userguide/tasks/customTaskWithFileProperty" title="Evaluating file properties lazily">
+            <sourcefile file="build.gradle"/>
+            <output args="-q sayGreeting"/>
+        </sample>
+        <para>
+            In this example, we configure the <literal>greet</literal> task <literal>destination</literal> property as a closure, which is evaluated with
+            the <apilink class="org.gradle.api.Project" method="file(java.lang.Object)"/> method to turn the return value of the closure into a file object
+            at the last minute. You will notice that in the above example we specify the <literal>greetingFile</literal> property value after we have 
+            configured to use it for the task. This kind of lazy evaluation is a key benefit of accepting any value when setting a file property, then 
+            resolving that value when reading the property.
+        </para>
+    </section>
+    
+    <section>
         <title>A standalone project</title>
         <para>Now we will move our plugin to a standalone project, so we can publish it and share it with others.
             This project is simply a Groovy project that produces a JAR containing the plugin classes.
@@ -143,7 +144,7 @@
             as a compile-time dependency.
         </para>
 
-        <sample id="customPluginStandalone" dir="customPlugin" title="A build for a custom plugin" includeLocation="true">
+        <sample id="customPluginStandalone" dir="customPlugin/plugin" title="A build for a custom plugin" includeLocation="true">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
 
@@ -152,7 +153,7 @@
 	    <filename>META-INF/gradle-plugins</filename> directory that matches the name of your plugin.
 	</para>
 
-        <sample id="customPluginStandalone" dir="customPlugin" title="Wiring for a custom plugin">
+        <sample id="customPluginStandalone" dir="customPlugin/plugin" title="Wiring for a custom plugin">
             <sourcefile file="src/main/resources/META-INF/gradle-plugins/greeting.properties"/>
         </sample>	
 
@@ -168,8 +169,10 @@
                 The following example shows how you might do this when the JAR containing the plugin has been published
                 to a local repository:
             </para>
-            <sample id="customPluginStandalone" dir="customPlugin" title="Using a custom plugin in another project">
-                <sourcefile file="usesCustomPlugin.gradle" snippet="use-plugin"/>
+            <sample id="customPluginStandalone" dir="customPlugin/consumer" title="Using a custom plugin in another project">
+                <test args="-p../plugin uploadArchives"/>
+                <test args="hello"/>
+                <sourcefile file="build.gradle" snippet="use-plugin"/>
             </sample>
         </section>
         <section>
@@ -177,7 +180,7 @@
             <para>You can use the <apilink class="org.gradle.testfixtures.ProjectBuilder"/> class to create
                 <apilink class="org.gradle.api.Project"/> instances to use when you test your plugin implementation.
             </para>
-            <sample id="customPluginStandalone" dir="customPlugin" title="Testing a custom plugin">
+            <sample id="customPluginStandalone" dir="customPlugin/plugin" title="Testing a custom plugin">
                 <sourcefile file="src/test/groovy/org/gradle/GreetingPluginTest.groovy" snippet="test-plugin"/>
             </sample>
         </section>
@@ -192,5 +195,12 @@
             <sourcefile file="build.gradle"/>
             <output args="-q books"/>
         </sample>
+        <para>
+            The <apilink class="org.gradle.api.Project" method="container(java.lang.Class)"/> methods create instances of <apilink class="org.gradle.api.NamedDomainObjectContainer"/>, that have many useful methods for managing and configuring the objects. In order
+            to use a type with any of the <literal>project.container</literal> methods, it MUST expose a property named “<literal>name</literal>”
+            as the unique, and constant, name for the object. The <literal>project.container(Class)</literal> variant of the container method creates 
+            new instances by attempting to invoke the constructor of the class that takes a single string argument, which is the desired name of the object. 
+            See the above link for <literal>project.container</literal> method variants that allow custom instantiation strategies.
+        </para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/customTasks.xml b/subprojects/docs/src/docs/userguide/customTasks.xml
index 712da01..c8578ae 100644
--- a/subprojects/docs/src/docs/userguide/customTasks.xml
+++ b/subprojects/docs/src/docs/userguide/customTasks.xml
@@ -125,13 +125,13 @@
             as a compile-time dependency.
         </para>
 
-        <sample id="customTaskStandalone" dir="customPlugin" title="A build for a custom task" includeLocation="true">
+        <sample id="customTaskStandalone" dir="customPlugin/plugin" title="A build for a custom task" includeLocation="true">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
 
         <para>We just follow the convention for where the source for the task class should go.</para>
 
-        <sample id="customTaskStandalone" dir="customPlugin" title="A custom task">
+        <sample id="customTaskStandalone" dir="customPlugin/plugin" title="A custom task">
             <sourcefile file="src/main/groovy/org/gradle/GreetingTask.groovy"/>
         </sample>
 
@@ -142,8 +142,10 @@
                 The following example shows how you might do this when the JAR containing the task class has been published
                 to a local repository:
             </para>
-            <sample id="usingCustomTask" dir="customPlugin" title="Using a custom task in another project">
-                <sourcefile file="usesCustomTask.gradle" snippet="use-task"/>
+            <sample id="usingCustomTask" dir="customPlugin/consumer" title="Using a custom task in another project">
+                <test args="-p../plugin uploadArchives"/>
+                <test args="greeting"/>
+                <sourcefile file="build.gradle" snippet="use-task"/>
             </sample>
         </section>
         <section>
@@ -151,7 +153,7 @@
             <para>You can use the <apilink class="org.gradle.testfixtures.ProjectBuilder"/> class to create
                 <apilink class="org.gradle.api.Project"/> instances to use when you test your task class.
             </para>
-            <sample id="customTaskStandalone" dir="customPlugin" title="Testing a custom task">
+            <sample id="customTaskStandalone" dir="customPlugin/plugin" title="Testing a custom task">
                 <sourcefile file="src/test/groovy/org/gradle/GreetingTaskTest.groovy" snippet="test-task"/>
             </sample>
         </section>
diff --git a/subprojects/docs/src/docs/userguide/depMngmt.xml b/subprojects/docs/src/docs/userguide/depMngmt.xml
index 8350081..5ca081f 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='dependency_management' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='dependency_management'>
     <title>Dependency Management</title>
     <section id='sec:Introduction'>
         <title>Introduction</title>
@@ -103,10 +103,56 @@
             <para>In your dependency description you tell Gradle which version of a dependency is needed by another
                 dependency. This frequently leads to conflicts. Different dependencies rely on different versions of
                 another dependency. The JVM unfortunately does not offer yet any easy way, to have different versions of
-                the same jar in the classpath (see <xref linkend='sub:dependency_management_and_java'/>). What Gradle
-                offers you is a resolution strategy, by default the newest version is used. To deal with problems due to
-                version conflicts, reports with dependency graphs are also very helpful. Such reports are another
-                feature of dependency management.
+                the same jar in the classpath (see <xref linkend='sub:dependency_management_and_java'/>).</para>
+            <para>Gradle offers following conflict resolution strategies:
+                <itemizedlist>
+                    <listitem><emphasis>Newest</emphasis> - used by default by Gradle - the newest version of the dependency is used.
+                    This strategy has been in Gradle since early days.
+                    </listitem>
+                    <listitem><emphasis>Fail</emphasis> - fail eagerly on version conflict.
+                        Useful if you need extra control and manage the conflicts manually.
+                        Introduced in <code>1.0-milestone-6</code>. See <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/> for reference on managing the conflict resolution strategies.
+                    </listitem>
+                </itemizedlist>
+                Gradle provides means to resolve version conflicts:
+                <itemizedlist>
+                    <listitem>
+                        Configuring a first level dependency as <emphasis>forced</emphasis>.
+                        The feature has been in Gradle since early days.
+                        This approach is useful if the dependency incurring conflict is already a first level dependency.
+                        See examples in <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
+                    </listitem>
+                    <listitem>
+                        Configuring any dependency (transitive or not) as <emphasis>forced</emphasis>.
+                        The feature was introduced in <code>1.0-milestone-7</code>.
+                        This approach is useful if the dependency incurring conflict is a transitive dependency.
+                        It also can be used to force versions of first level dependencies.
+                        See examples in <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
+                    </listitem>
+                </itemizedlist>
+                To deal with problems due to version conflicts, reports with dependency graphs are also very helpful.
+                Such reports are another feature of dependency management.
+            </para>
+        </section>
+        <section id='sub:dynamic_versions_and_changing_modules'>
+            <title>Dynamic Versions and Changing Modules</title>
+            <para>Sometimes, you always want to use the latest version of a particular dependency, or the latest in a range of versions.
+                You can easily do this using a <emphasis>dynamic version</emphasis>. A dynamic version can be either a version range (eg. <literal>2.+</literal>)
+                or it can be a placeholder for the latest version available (eg. <literal>latest.integration</literal>).
+            </para>
+            <para>Alternatively, sometimes the module you request can change over time, even for the same version.
+                An example of this type of <emphasis>changing module</emphasis> is a maven <literal>SNAPSHOT</literal> module,
+                which always points at the latest artifacts published.
+            </para>
+            <para>
+                The main difference between a <emphasis>dynamic version</emphasis> and a <emphasis>changing module</emphasis> is
+                that when you resolve a <emphasis>dynamic version</emphasis>, you'll get the real, static version as the module name.
+                When you resolve a <emphasis>changing module</emphasis>, the artifacts are named using the version you requested,
+                but the underlying artifacts may change over time.
+            </para>
+            <para>By default, Gradle caches dynamic versions and changing modules for 24 hours.
+                You can override the default cache modes using <link linkend="sec:cache_command_line_options">command line options</link>.
+                You can change the cache expiry times in your build using the <literal>resolution strategy</literal> (see <xref linkend='sec:controlling_caching'/>).
             </para>
         </section>
         <section id='sub:dependency_management_and_java'>
@@ -128,45 +174,96 @@
                 of Ivy which Ivy does not offer itself.
             </para>
             <para>Right now there is a lot of movement in the field of dependency handling. There is OSGi and there is
-                JSR-294.
-                <footnote>
-                    <para>JSR 294: Improved Modularity Support in the JavaTM Programming Language, <ulink url='http://jcp.org/en/jsr/detail?id=294'/>
-                    </para>
-                </footnote>
+                JSR-294, Improved Modularity Support in the JavaTM Programming Language.
                 OSGi is available already, JSR-294 is supposed to be shipped with Java 7. These technologies
                 deal, amongst many other things, also with a painful problem which is neither solved by Maven nor by Ivy. This is enabling different
                 versions of the same jar to be used at runtime.
             </para>
         </section>
     </section>
+    <section id='sub:configurations'>
+        <title>Dependency configurations</title>
+        <para>In Gradle dependencies are grouped into configurations. Configurations have a name, a number of other properties,
+            and they can extend each other.
+            Many Gradle plugin add pre-defined configurations to your project. The Java plugin, for example,
+            adds some configurations to represent the various classpaths it needs. see <xref linkend='sec:java_plugin_and_dependency_management'/>
+            for details. Of course you can add your add custom configurations on top of that. There are many use cases
+            for custom configurations. This is very handy for example for adding dependencies not needed for
+            building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution).
+        </para>
+        <para>
+            A project's configurations are managed by a <literal>configurations</literal> object. The closure you pass to
+            the configurations object is applied against its API. To learn more about this API have a look at
+            <apilink class='org.gradle.api.artifacts.ConfigurationContainer'/>.
+        </para>
+        <para>To define a configuration:</para>
+        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Definition of a configuration">
+            <sourcefile file="build.gradle" snippet="define-configuration"/>
+        </sample>
+        <para>To access a configuration:</para>
+        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Accessing a configuration">
+            <sourcefile file="build.gradle" snippet="lookup-configuration"/>
+        </sample>
+        <para>To configure a configuration:</para>
+        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Configuration of a configuration">
+            <sourcefile file="build.gradle" snippet="configure-configuration"/>
+        </sample>
+    </section>
+
     <section id='sec:how_to_declare_your_dependencies'>
         <title>How to declare your dependencies</title>
-        <para>People who know Ivy have come across most of the concepts we are going to introduce now. But Gradle does not
-            use any XML for declaring the dependencies (e.g. no <literal>ivy.xml</literal> file). It has its own
-            notation which is part of the Gradle build file.
+        <para>There are several different types of dependencies that you can declare:
         </para>
-        <section id='sub:configurations'>
-            <title>Configurations</title>
-            <para>Dependencies are grouped in configurations. Configurations have a name, a number of other properties,
-                and they can extend each other. For examples see: <xref linkend='sec:artifact_configurations'/>.
-                If you use the Java plugin, Gradle adds a number of pre-defined configurations to your build. The
-                plugin also assigns configurations to tasks. See <xref linkend='sec:java_plugin_and_dependency_management'/>
-                for details. Of course you can add your add custom configurations on top of that. There are many use cases
-                for custom configurations. This is very handy for example for adding dependencies not needed for
-                building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution).
-                The configurations are managed by a <literal>configurations</literal> object. The closure you pass to
-                the configurations object is applied against its API. To learn more about this API have a look at
-                <apilink class='org.gradle.api.artifacts.ConfigurationContainer'/>.
-            </para>
-        </section>
+        <table>
+            <title>Dependency types</title>
+            <thead>
+                <tr>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td><link linkend="sub:module_dependencies">External module dependency</link></td>
+                <td>A dependency on an external module in some repository.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:project_dependencies">Project dependency</link></td>
+                <td>A dependency on another project in the same build.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:file_dependencies">File dependency</link></td>
+                <td>A dependency on a set of files on the local filesystem.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:client_module_dependencies">Client module dependency</link></td>
+                <td>A dependency on an external module, where the artifacts are located in some repository but the module meta-data
+                    is specified by the local build. You use this kind of dependency when you want to override the meta-data for the module.
+                </td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:api_dependencies">Gradle API dependency</link></td>
+                <td>A dependency on the API of the current Gradle version.
+                    You use this kind of dependency when you are developing custom Gradle plugins and task types.
+                </td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:groovy_dependencies">Local Groovy dependency</link></td>
+                <td>A dependency on the Groovy version used by the current Gradle version.
+                    You use this kind of dependency when you are developing custom Gradle plugins and task types.
+                </td>
+            </tr>
+        </table>
+
         <section id='sub:module_dependencies'>
-            <title>Module dependencies</title>
-            <para>Module dependencies are the most common dependencies. They correspond to a dependency in an external
-                repository.
+            <title>External module dependencies</title>
+            <para>External module dependencies are the most common dependencies. They refer to a module in an external repository.
             </para>
             <sample id="moduleDependencies" dir="userguide/artifacts/externalDependencies" title="Module dependencies">
                 <sourcefile file="build.gradle" snippet="module-dependencies"/>
             </sample>
+            <para>Please see the <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
+                for more examples and complete reference. Please read on to get thorough understanding of the Gradle's dependency management.
+            </para>
             <para>Gradle provides different notations for module dependencies. There is a string notation and
                 a map notation. A module dependency has an API which allows for further configuration. Have a look at
                 <apilink class='org.gradle.api.artifacts.ExternalModuleDependency'/> to learn all about the API.
@@ -182,6 +279,28 @@
                 a module can only have one and only one artifact. In Gradle and Ivy a module can have multiple artifacts.
                 Each artifact can have a different set of dependencies.
             </para>
+            <section id='ssub:multi_artifact_dependencies'>
+                <title>Depending on modules with multiple artifacts</title>
+                As mentioned earlier, a maven module has only one artifact. So, when your project depends on a maven module
+                it's obvious what artifact is the actual dependency.
+                With Gradle or Ivy the case is different. Ivy model of dependencies (<literal>ivy.xml</literal>) can declare multiple artifacts.
+                For more information, see Ivy reference for <literal>ivy.xml</literal>.
+                In Gradle, when you declare a dependency on an ivy module you actually declare dependency on the <literal>'default'</literal> configuration of that module.
+                So the actual list of artifacts (typically jars) your project depends on, are all artifacts that are attached to the <literal>default</literal> configuration of that module.
+                This is very important in following exemplary use cases:
+                <itemizedlist>
+                    <listitem>The <literal>default</literal> configuration of some module contains some artifacts
+                        you don't want on the classpath. You might need to configure a dependency on specific artifact(s) of given module,
+                        rather than pulling all artifacts of the <literal>default</literal> dependency
+                    </listitem>
+                    <listitem>The artifact you need on the classpath has been published in a different configuration
+                        than the <literal>default</literal> one. This means this artifact will not be pulled in by Gradle.
+                        Unless you explicitly declare what configuration of the module you depend on.
+                    </listitem>
+                </itemizedlist>
+                There are other situations where it is necessary to fine-tune the dependency declaration.
+                Please see the <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/> for examples and complete reference on declaring dependencies.
+            </section>
             <section id='ssub:artifact_dependencies'>
                 <title>Artifact only notation</title>
                 <para>As said above, if no module descriptor file can be found, Gradle by default
@@ -216,7 +335,13 @@
                 <para>As you can see in the example, classifiers can be used together with setting
                 an explicit extension (artifact only notation).</para>
             </section>
+            <para>To use the external dependencies of a configuration:</para>
+            <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies"  title="Usage of external dependency of a configuration">
+                <sourcefile file="build.gradle" snippet="use-configuration"/>
+                <output args="-q listJars"/>
+            </sample>
         </section>
+
         <section id='sub:client_module_dependencies'>
             <title>Client module dependencies</title>
             <para>Client module dependencies enable you to declare <emphasis>transitive</emphasis>
@@ -238,6 +363,7 @@
                 improve this in a future release of Gradle.
             </para>
         </section>
+
         <section id='sub:project_dependencies'>
             <title>Project dependencies</title>
             <para>Gradle distinguishes between external dependencies and dependencies on projects which are part of the
@@ -251,7 +377,8 @@
             <para>Multi-project builds are discussed in <xref linkend='multi_project_builds'/>.
             </para>
         </section>
-        <section>
+
+        <section id="sub:file_dependencies">
             <title>File dependencies</title>
             <para>File dependencies allow you to directly add a set of files to a configuration, without first adding
                 them to a repository. This can be useful if you cannot, or do not want to, place certain files in a
@@ -275,15 +402,27 @@
                 <output args="-q list"/>
             </sample>
         </section>
-        <section>
+
+        <section id="sub:api_dependencies">
             <title>Gradle API Dependency</title>
             <para>You can declare a dependency on the API of the current version of Gradle by using the
                 <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="gradleApi"/> method. This is
                 useful when you are developing custom Gradle tasks or plugins.</para>
-            <sample id="gradle-api-dependencies" dir="java/multiproject/buildSrc" title="Gradle API dependencies">
+            <sample id="gradle-api-dependencies" dir="customPlugin/plugin" title="Gradle API dependencies">
                 <sourcefile file="build.gradle" snippet="gradle-api-dependencies"/>
             </sample>
         </section>
+
+        <section id="sub:groovy_dependencies">
+            <title>Local Groovy Dependency</title>
+            <para>You can declare a dependency on the Groovy that is distributed with Gradle by using the
+                <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="localGroovy"/> method. This is
+                useful when you are developing custom Gradle tasks or plugins in Groovy.</para>
+            <sample id="local-groovy-dependencies" dir="customPlugin/plugin" title="Gradle's Groovy dependencies">
+                <sourcefile file="build.gradle" snippet="local-groovy-dependencies"/>
+            </sample>
+        </section>
+
         <section id='sub:exclude_transitive_dependencies'>
             <title>Excluding transitive dependencies</title>
             <para>You can exclude a <emphasis>transitive</emphasis> dependency either by configuration or by dependency:
@@ -301,6 +440,38 @@
                 Have also a look at the API documentation of <apilink class="org.gradle.api.artifacts.Dependency"/> and
                 <apilink class="org.gradle.api.artifacts.Configuration"/>.
             </para>
+            <para>
+                Not every transitive dependency can be excluded - some transitive dependencies might be essential
+                for correct runtime behavior of the application. Generally, one can exclude transitive
+                dependencies that are either not required by runtime or that are guaranteed to be available
+                on the target environment/platform.
+            </para>
+            <para>
+                Should you exclude per-dependency or per-configuration?
+                It turns out that in majority of cases you want to use the per-configuration exclusion.
+                Here are the some exemplary reasons why one might want to exclude a transitive dependency.
+                Bear in mind that for some of those use cases there are better solutions than exclusions!
+                <itemizedlist>
+                    <listitem>The dependency is undesired due to licensing reasons.</listitem>
+                    <listitem>The dependency is not available in any of remote repositories.</listitem>
+                    <listitem>The dependency is not needed for runtime.</listitem>
+                    <listitem>The dependency has a version that conflicts with a desired version.
+                        For that use case please refer to <xref linkend='sub:version_conflicts'/>
+                        and the documentation on <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
+                        for a potentially better solution to the problem.
+                    </listitem>
+                </itemizedlist>
+                Basically, in most of the cases excluding the transitive dependency should be done per configuration.
+                This way the dependency declaration is more explicit. It is also more accurate because a per-dependency
+                exclude rule does not guarantee the given transitive dependency does not show up in the configuration.
+                For example, some other dependency, which does not have any exclude rules, might pull in
+                that unwanted transitive dependency.
+            </para>
+            <para>
+                Other examples of the dependency exclusions can be found in the reference for
+                <apilink class='org.gradle.api.artifacts.ModuleDependency'/> or
+                <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>.
+            </para>
         </section>
         <section>
             <title>Optional attributes</title>
@@ -341,7 +512,7 @@
             </para>
         </section>
     </section>
-    <section>
+    <section id='sec:working_with_dependencies'>
         <title>Working with dependencies</title>
         <para>For the examples below we have the following dependencies setup:</para>
         <sample id="configurationHandlingSetup" dir="userguide/artifacts/configurationHandling" title="Configuration.copy">
@@ -376,7 +547,7 @@
         <para>The <code>Configuration.files</code> method always retrieves all artifacts of the <emphasis>whole</emphasis> configuration. It
         then filters the retrieved files by specified dependencies. As you can see in the example, transitive dependencies are included.
         </para>
-        <para>You can also copy a configuration. You can optionally specify that only a subset of dependencies from the orginal configuration
+        <para>You can also copy a configuration. You can optionally specify that only a subset of dependencies from the original configuration
             should be copied. The copying methods come in two flavors. The <code>copy</code> method copies only the dependencies belonging
             explicitly to the configuration. The <code>copyRecursive</code> method copies all the dependencies, including the dependencies from extended
             configurations.
@@ -410,58 +581,94 @@
     </section>
     <section id='sec:repositories'>
         <title>Repositories</title>
-        <para>Gradle repository management, based on Apache Ivy, gives you a lot of freedom
-            regarding repository layout and retrieval policies. Additionally Gradle provides various convenience
-            method to add preconfigured repositories.
+        <para>Gradle repository management, based on Apache Ivy, gives you a lot of freedom regarding repository layout and retrieval policies.
+            Additionally Gradle provides various convenience method to add pre-configured repositories.
         </para>
-        <section id='sub:maven_repo'>
-            <title>Maven repositories</title>
-            <para>To add the central Maven2 repository (<ulink url='http://repo1.maven.org/maven2'/>) simply
-                type:
+        <para>You may configure any number of repositories, each of which is treated independently by Gradle. If Gradle finds a module descriptor in a
+        particular repository, it will attempt to download all of the artifacts for that module from <emphasis>the same repository</emphasis>.
+        Although module meta-data and module artifacts must be located in the same repository, it is possible to compose a single repository of multiple
+        URLs, giving multiple locations to search for meta-data files and jar files.</para>
+
+        <para>There are several different types of repositories you can declare:</para>
+        <table>
+            <title>Repository types</title>
+            <thead>
+                <tr>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td><link linkend="sub:maven_central">Maven central repository</link></td>
+                <td>A pre-configured repository that looks for dependencies in Maven Central.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:maven_local">Maven local repository</link></td>
+                <td>A pre-configured repository that looks for dependencies in the local Maven repository.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sub:maven_repo">Maven repository</link></td>
+                <td>A Maven repository. Can be located on the local filesystem or at some remote location.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sec:ivy_repositories">Ivy repository</link></td>
+                <td>An Ivy repository. Can be located on the local filesystem or at some remote location.</td>
+            </tr>
+            <tr>
+                <td><link linkend="sec:flat_dir_resolver">Flat directory repository</link></td>
+                <td>A simple repository on the local filesystem. Does not support any meta-data formats.</td>
+            </tr>
+        </table>
+
+        <section id='sub:maven_central'>
+            <title>Maven central repository</title>
+            <para>To add the central Maven 2 repository (<ulink url='http://repo1.maven.org/maven2'/>) simply add this to your build script:
             </para>
             <sample id="mavenCentral" dir="userguide/artifacts/defineRepository" title="Adding central Maven repository">
                 <sourcefile file="build.gradle" snippet="maven-central"/>
             </sample>
             <para>Now Gradle will look for your dependencies in this repository.
             </para>
-            <para>Quite often certain jars are not in the official Maven repository for licensing reasons (e.g. JTA),
-                but its POMs are.
-            </para>
-            <sample id="mavenCentralJarRepo" dir="userguide/artifacts/defineRepository" title="Adding several Maven repositories">
-                <sourcefile file="build.gradle" snippet="maven-central-jar-repo"/>
+        </section>
+
+        <section id='sub:maven_local'>
+            <title>Local Maven repository</title>
+            <para>To use the local Maven cache as a repository you can do:</para>
+            <sample id="mavenLocalRepo" dir="userguide/artifacts/defineRepository" title="Adding the local Maven cache as a repository">
+                <sourcefile file="build.gradle" snippet="maven-local"/>
             </sample>
-            <para>Gradle will look first in the central Maven repository for the POM and the JAR. If the JAR can't be
-                found there, it will look for it in the other repositories.
-            </para>
-            <para>For adding a custom Maven repository you can say:
+        </section>
+
+        <section id='sub:maven_repo'>
+            <title>Maven repositories</title>
+            <para>For adding a custom Maven repository you can do:
             </para>
             <sample id="mavenLikeRepo" dir="userguide/artifacts/defineRepository" title="Adding custom Maven repository">
                 <sourcefile file="build.gradle" snippet="maven-like-repo"/>
             </sample>
-            <para>To declare additional repositories to look for jars (like above in the example
-                for the central Maven repository), you can say:
+
+            <para>Sometimes a repository will have the POMs published to one location, and the JARs and other artifacts published at another location.
+                To define such a repository, you can do:
             </para>
             <sample id="mavenLikeRepoWithJarRepo" dir="userguide/artifacts/defineRepository" title="Adding additional Maven repositories for JAR files">
                 <sourcefile file="build.gradle" snippet="maven-like-repo-with-jar-repo"/>
             </sample>
-            <para>The first URL is used to look for POMs and JARs. The subsequent URLs are used to look for JARs.
+            <para>Gradle will look at the first URL for the POM and the JAR. If the JAR can't be found there, the artifact URLs are used to look for JARs.
             </para>
-            <para>To use the local Maven cache as a repository you can say:</para>
-            <sample id="mavenLocalRepo" dir="userguide/artifacts/defineRepository" title="Adding the local Maven cache as a repository">
-                <sourcefile file="build.gradle" snippet="maven-local"/>
-            </sample>
             <section>
-            	<title>Accessing password protected Maven repositories</title>
-            	<para>To access a password protected Maven repository (basic authentication) you need to use one of Ivy features:</para>
-    	        <sample id="mavenPasswordProtectedRepo" dir="userguide/artifacts/defineRepository" title="Accessing password protected Maven repository">
-	                <sourcefile file="build.gradle" snippet="maven-password-protected-repo"/>
-        	    </sample>
-        	    <para>Host name should not include <literal>"http://"</literal> prefix. It is advisable to keep your login and password in <filename>gradle.properties</filename> rather than directly in the build file.</para>
+                <title>Accessing password protected Maven repositories</title>
+                <para>To access a Maven repository which uses basic authentication, you specify the username and password to use when you define the repository:
+                </para>
+                <sample id="mavenPasswordProtectedRepo" dir="userguide/artifacts/defineRepository" title="Accessing password protected Maven repository">
+                    <sourcefile file="build.gradle" snippet="authenticated-maven-repo"/>
+                </sample>
+                <para>It is advisable to keep your username and password in <filename>gradle.properties</filename> rather than directly in the build file.</para>
             </section>
         </section>
+
         <section id='sec:flat_dir_resolver'>
-            <title>Flat directory resolver</title>
-            <para>If you want to use a (flat) filesytem directory as a repository, simply type:
+            <title>Flat directory repository</title>
+            <para>If you want to use a (flat) filesystem directory as a repository, simply type:
             </para>
             <sample id="flatDirMulti" dir="userguide/artifacts/defineRepository" title="Flat repository resolver">
                 <sourcefile file="build.gradle" snippet="flat-dir-multi"/>
@@ -471,13 +678,31 @@
                 See <xref linkend='para:dependencies_with_empty_attributes'/>
             </para>
         </section>
-        <section>
+
+        <section id="sec:ivy_repositories">
             <title>Ivy repositories</title>
-            <para>To use an Ivy repository:</para>
+            <para>To use an Ivy repository with a standard layout:</para>
             <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository">
-                <sourcefile file="build.gradle" snippet="ivy-repo"/>
+                <sourcefile file="build.gradle" snippet="ivy-repo-with-maven-layout"/>
             </sample>
-            <para>See <apilink class="org.gradle.api.artifacts.dsl.IvyArtifactRepository"/> for details.</para>
+            <para>See <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository"/> for details.</para>
+            <section>
+                <title>Defining custom patterns for an Ivy repository</title>
+                <para>To define an Ivy repository with a non-standard layout, you can define a pattern layout for the repository:
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with pattern layout">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-pattern-layout"/>
+                </sample>
+            </section>
+            <section>
+                <title>Defining different artifact and ivy file locations for an Ivy repository</title>
+                <para>To define an Ivy repository which fetches ivy files and artifacts from different locations, you can explicitly define complete URL patterns
+                      for artifacts and ivy files:
+                </para>
+                <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with custom patterns">
+                    <sourcefile file="build.gradle" snippet="ivy-repo-with-custom-pattern"/>
+                </sample>
+            </section>
             <section>
                 <title>Accessing password protected Ivy repositories</title>
                 <para>To access an Ivy repository which uses basic authentication, you specify the username and password to use when you define the repository:
@@ -487,24 +712,17 @@
                 </sample>
             </section>
         </section>
+
         <section>
-            <title>More about preconfigured repositories</title>
-            <para>The methods above for creating preconfigured repositories share some common behavior. For all of them, defining
-                a name for the repository is optional. If no name is defined a default name is calculated, depending on the
-                type of the repository. You might want to assign a name, if you want to access the declared repository. For example
-                if you want to use it also for uploading your project artifacts. An explicit name might also be helpful when
-                studying the debug output.
-            </para>
-            <para>The values passed as arguments to the repository methods can be of any type, not just String. The value
-                that is actually used, is the <code>toString</code> result of the argument object.
-            </para>
-        </section>
-        <section id='sub:cache'>
-            <title>Cache</title>
-            <para>When Gradle downloads artifacts from remote repositories it stores them in a local cache located at
-                <literal>USER_HOME/.gradle/cache</literal>. When Gradle downloads artifacts from one of its
-                predefined local resolvers (e.g. flat directory resolver), the cache is not used.
-            </para>
+            <title>Working with repositories</title>
+            <para>To access a repository:</para>
+            <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Accessing a repository">
+                <sourcefile file="build.gradle" snippet="lookup-resolver"/>
+            </sample>
+            <para>To configure a repository:</para>
+            <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Configuration of a repository">
+                <sourcefile file="build.gradle" snippet="configure-resolver"/>
+            </sample>
         </section>
         <section id='sub:more_about_ivy_resolvers'>
             <title>More about Ivy resolvers</title>
@@ -554,14 +772,161 @@ someroot/[artifact]-[revision].[ext]
             </para>
         </section>
     </section>
+    <section id='sec:dependency_resolution'>
+        <title>How dependency resolution works</title>
+        <para>Gradle takes your dependency declarations and repository definitions and attempts to download all of your dependencies by a process called <emphasis>dependency resolution</emphasis>.
+        Below is a brief outline of how this process works.</para>
+        <itemizedlist>
+            <listitem>
+                <para>
+                    Given a required dependency, Gradle first attempts to resolve the <emphasis>module</emphasis> for that dependency. Each repository is inspected in order, searching
+                    first for a <emphasis>module descriptor</emphasis> file (pom or ivy file) that indicates the presence of that module. If no module descriptor is found,
+                    Gradle will search for the presence of the primary <emphasis>module artifact</emphasis> file indicating that the module exists in the repository.
+                </para>
+                <itemizedlist>
+                    <listitem>
+                        <para>If the dependency is declared as a dynamic version (like <literal>1.+</literal>), Gradle will resolve this to the newest available static version (like <literal>1.2</literal>)
+                            in the repository. For maven repositories, this is done using the <literal>maven-metadata.xml</literal> file, while for ivy repositories this is done by directory listing.</para>
+                    </listitem>
+                    <listitem>
+                        <para>If the module descriptor is a <literal>pom</literal> file that has a parent pom declared, Gradle will recursively attempt to resolve each of the parent modules for the pom.</para>
+                    </listitem>
+                </itemizedlist>
+            </listitem>
+            <listitem>
+                <para>Once each repository has been inspected for the module, Gradle will choose the 'best' one to use. This is done using the following criteria:
+                    <itemizedlist>
+                        <listitem>For a dynamic version, a 'higher' static version is preferred over a 'lower' version.</listitem>
+                        <listitem>Modules declared by a module descriptor file (ivy or pom file) are preferred over modules that have an artifact file only.</listitem>
+                        <listitem>Modules from earlier repositories are preferred over modules in later repositories.</listitem>
+                    </itemizedlist>
+                </para>
+                <para>When the dependency is declared by a static version and a module descriptor file is found in a repository, there is no need to continue searching later
+                repositories and the remainder of the process is short-circuited.</para>
+            </listitem>
+            <listitem>
+                <para>All of the artifacts for the module are then requested from the <emphasis>same repository</emphasis> that was chosen in the process above.</para>
+            </listitem>
+        </itemizedlist>
+    </section>
+    <section id='sec:dependency_cache'>
+        <title>The dependency cache</title>
+        <para>Gradle contains a highly sophisticated dependency caching mechanism, which seeks to minimise the number of remote requests made in
+        dependency resolution, while striving to guarantee that the results of dependency resolution are correct and reproducible.
+        </para>
+        <para>
+            The Gradle dependency cache consists of 2 key types of storage:</para>
+        <itemizedlist>
+            <listitem>
+                <para>A file-based store of downloaded artifacts, including binaries like jars as well as raw downloaded meta-data like pom files and ivy files.
+                      The storage path for a downloaded artifact includes the SHA1 checksum, meaning that 2 artifacts with the same name but different content can easily be cached.
+                </para>
+            </listitem>
+            <listitem>
+                <para>A binary store of resolved module meta-data, including the results of resolving dynamic versions, module descriptors, and artifacts.</para>
+            </listitem>
+        </itemizedlist>
+        <para>Separating the storage of downloaded artifacts from the cache metadata permits us to do some very powerful things with our cache that would be difficult with a transparent,
+            file-only cache layout.</para>
+        <para>The Gradle cache does not allow the local cache to hide problems and creating mysterious and difficult to debug behavior
+            that has been a challenge with many build tools. This new behavior is implemented in a bandwidth and storage efficient way.
+            In doing so, Gradle enables reliable and reproducible enterprise builds.</para>
+
+        <section id='sec:cache_features'>
+            <title>Key features of the Gradle dependency cache</title>
+            <section id='sub:cache_metadata'>
+                <title>Separate metadata cache</title>
+                <para>
+                    Gradle keeps a record of various aspects of dependency resolution in binary format in the metadata cache.
+                    The information stored in the metadata cache includes:
+                    <itemizedlist>
+                        <listitem>The result of resolving a dynamic version (eg 1.+) to a concrete version (eg 1.2).</listitem>
+                        <listitem>The resolved module metadata for a particular module, including module artifacts and module dependencies.</listitem>
+                        <listitem>The resolved artifact metadata for a particular artifact, including a pointer to the downloaded artifact file.</listitem>
+                        <listitem>The <emphasis>absence</emphasis> of a particular module or artifact in a particular repository,
+                            eliminating repeated attempts to access a resource that does not exist. </listitem>
+                    </itemizedlist>
+                    Every entry in the metadata cache includes a record of the repository that provided the information as well as a timestamp that can be used for cache expiry.
+                </para>
+            </section>
+            <section id='sub:cache_repository_independence'>
+                <title>Repository caches are independent</title>
+                <para>As described above, for each repository there is a separate metadata cache. A repository is identified by its URL, type and layout.
+                    If a module or artifact has not been previously resolved from <emphasis>this repository</emphasis>, Gradle will attempt to resolve the module
+                    against the repository. This will always involve a remote lookup on the repository, however in many cases no download will be required (see <xref linkend='sub:cache_artifact_reuse'/>, below).</para>
+                <para>Dependency resolution will fail if the required artifacts are not available in any repository specified by the build,
+                    regardless whether the local cache has retrieved this artifact from a different repository.
+                    Repository independence allows builds to be isolated from each other in an advanced way that no build tool has done before.
+                    This is a key feature to create builds that are reliable and reproducible in any environment.</para>
+            </section>
+            <section id='sub:cache_artifact_reuse'>
+                <title>Artifact reuse</title>
+                <para>Before downloading an artifact, Gradle tries to determine the checksum of the required artifact by downloading the sha file associated
+                    with that artifact. If the checksum can be retrieved, an artifact is not downloaded if an artifact already exists with the same id and checksum.
+                    If the checksum cannot be retrieved from the remote server, the artifact will be downloaded (and ignored if it matches an existing artifact).</para>
+                <para>As well as considering artifacts downloaded from a different repository, Gradle will also attempt to reuse artifacts found in the local Maven Repository.
+                    If a candidate artifact has been downloaded by Maven, Gradle will use this artifact if it can be verified to match the checksum declared by the remote server.</para>
+            </section>
+            <section id='sub:cache_checksum_storage'>
+                <title>Checksum based storage</title>
+                <para>
+                    It is possible for different repositories to provide a different binary artifact in response to the same artifact identifier. This is often the case with Maven SNAPSHOT
+                    artifacts, but can also be true for any artifact which is republished without changing it's identifier.
+                    By caching artifacts based on their SHA1 checksum, Gradle is able to maintain multiple versions of the same artifact. This means that when resolving against
+                    one repository Gradle will never overwrite the cached artifact file from a different repository. This is done without requiring a separate artifact file store per repository.
+                </para>
+            </section>
+            <section id='sub:cache_locking'>
+                <title>Cache Locking</title>
+                <para>
+                    The Gradle dependency cache uses file-based locking to ensure that it can safely be used by multiple Gradle processes concurrently. The lock is held whenever the binary
+                    meta-data store is being read or written, but is released for slow operations such as downloading remote artifacts.
+                </para>
+            </section>
+        </section>
+        <section id='sec:cache_command_line_options'>
+            <title>Command line options to override caching</title>
+            <section id='sub:cache_offline'>
+                <title>Offline</title>
+                <para>The <literal>--offline</literal> command line switch tells Gradle to always use dependency modules from the cache, regardless if they are due to be checked again.
+                    When running with offline, Gradle will never attempt to access the network to perform dependency resolution.
+                    If required modules are not present in the dependency cache, build execution will fail.</para>
+            </section>
+            <section id='sub:cache_refresh'>
+                <title>Refresh</title>
+                <para>At times, the Gradle Dependency Cache can be out of sync with the actual state of the configured repositories. Perhaps a repository was initially misconfigured,
+                    or perhaps a "non-changing" module was published incorrectly. To refresh all dependencies in the dependency cache, use the <literal>--refresh-dependencies</literal>
+                    option on the command line.</para>
+                <para>The <literal>--refresh-dependencies</literal> option tells Gradle to ignore all cached entries for resolved modules and artifacts.
+                    A fresh resolve will be performed against all configured repositories, with dynamic versions recalculated, modules refreshed, and artifacts downloaded.
+                    However, where possible Gradle will attempt to if the previously downloaded artifacts are valid before downloading again.
+                    This is done by comparing published SHA1 values in the repository with the SHA1 values for existing downloaded artifacts.</para>
+            </section>
+        </section>
+        <section id='sec:controlling_caching'>
+            <title>Fine-tuned control over dependency caching</title>
+            <para>You can fine-tune certain aspects of caching using the <literal>ResolutionStrategy</literal> for a configuration.</para>
+            <para>By default, Gradle caches dynamic versions for 24 hours. To change how long Gradle will cache the resolved version for a dynamic version, use:
+            </para>
+            <sample id="dynamic-version-cache-control" dir="userguide/artifacts/resolutionStrategy" title="Dynamic version cache control">
+                <sourcefile file="build.gradle" snippet="dynamic-version-cache-control"/>
+            </sample>
+            <para>By default, Gradle caches changing modules for 24 hours. To change how long Gradle will cache the meta-data and artifacts for a changing module, use:
+            </para>
+            <sample id="changing-module-cache-control" dir="userguide/artifacts/resolutionStrategy" title="Changing module cache control">
+                <sourcefile file="build.gradle" snippet="changing-module-cache-control"/>
+            </sample>
+            <para>For more details, take a look at the API documentation for <apilink class="org.gradle.api.artifacts.ResolutionStrategy"/>.</para>
+        </section>
+    </section>
     <section id='sec:strategies_of_transitive_dependency_management'>
         <title>Strategies for transitive dependency management</title>
-        <para>Many projects rely on the <ulink url='http://repo1.maven.org/maven2'>Maven2 repository</ulink>. This is not
+        <para>Many projects rely on the <ulink url='http://repo1.maven.org/maven2'>Maven Central repository</ulink>. This is not
             without problems.
         </para>
         <itemizedlist>
             <listitem>
-                <para>The IBibilio repository can be down or has a very long response time.
+                <para>The Maven Central repository can be down or has a very long response time.
                 </para>
             </listitem>
             <listitem>
@@ -576,27 +941,27 @@ someroot/[artifact]-[revision].[ext]
                 </para>
             </listitem>
         </itemizedlist>
-        <para>If your project relies on the IBibilio repository you are likely to need an additional custom repository,
+        <para>If your project relies on the Maven Central repository you are likely to need an additional custom repository,
             because:
         </para>
         <itemizedlist>
             <listitem>
-                <para>You might need dependencies that are not uploaded to IBibilio yet.
+                <para>You might need dependencies that are not uploaded to Maven Central yet.
                 </para>
             </listitem>
             <listitem>
-                <para>You want to deal properly with wrong metadata in a IBibilio <literal>pom.xml</literal>.
+                <para>You want to deal properly with wrong metadata in a Maven Central <literal>pom.xml</literal>.
                 </para>
             </listitem>
             <listitem>
                 <para>You don't want to expose people who want to build your project, to the
-                    downtimes or sometimes very long response times of IBibilio.
+                    downtimes or sometimes very long response times of Maven Central.
                 </para>
             </listitem>
         </itemizedlist>
         <para>It is not a big deal to set-up a custom repository.
             <footnote>
-                <para>If you want to shield your project from the downtimes of IBibilio things get more complicated. You
+                <para>If you want to shield your project from the downtimes of Maven Central things get more complicated. You
                     probably want to set-up a repository proxy for this. In an enterprise environment this is rather
                     common. For an open source project it looks like overkill.
                 </para>
diff --git a/subprojects/docs/src/docs/userguide/earPlugin.xml b/subprojects/docs/src/docs/userguide/earPlugin.xml
new file mode 100644
index 0000000..c4642f3
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/earPlugin.xml
@@ -0,0 +1,191 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+<chapter id='ear_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Ear Plugin</title>
+    <para>The Ear plugin adds support for assembling web application EAR files. It adds a default EAR archive task.
+        It doesn't require the Java plugin, but for projects that also use the Java plugin it disables the default
+        JAR archive generation.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Ear plugin, include in your build script:</para>
+        <sample id="earWithWar" dir="ear/earWithWar" title="Using the Ear plugin">
+            <sourcefile file="build.gradle" snippet="use-ear-plugin"/>
+            <test args="assemble"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Ear plugin adds the following tasks to the project.</para>
+        <table>
+            <title>Ear plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>ear</literal>
+                </td>
+                <td>
+                    <literal>compile</literal> (only if the Java plugin is also applied)
+                </td>
+                <td><apilink class="org.gradle.plugins.ear.Ear"/></td>
+                <td>Assembles the application EAR file.</td>
+            </tr>
+        </table>
+        <para>The Ear plugin adds the following dependencies to tasks added by the base plugin.</para>
+        <table>
+            <title>Ear plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td>assemble</td>
+                <td>ear</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <table>
+            <title>Ear plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>Directory</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>src/main/application</filename>
+                </td>
+                <td>Ear resources, such as a META-INF directory</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Ear plugin adds two dependency configurations: <literal>deploy</literal> and
+            <literal>earlib</literal>. All dependencies in the <literal>deploy</literal> configuration are
+            placed in the root of the EAR archive, and are <em>not</em> transitive. All dependencies in the
+            <literal>earlib</literal> configuration are placed in the 'lib' directory in the EAR archive and
+            <em>are</em> transitive.
+        </para>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <table>
+            <title>Ear plugin - directory properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>appDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <filename>src/main/application</filename>
+                </td>
+                <td>
+                    The name of the application source directory, relative to the project directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>libDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <filename>lib</filename>
+                </td>
+                <td>
+                    The name of the lib directory inside the generated EAR.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>deploymentDescriptor</literal>
+                </td>
+                <td>
+                    <classname>org.gradle.plugins.ear.descriptor.DeploymentDescriptor</classname>
+                </td>
+                <td>
+                    A deployment descriptor with sensible defaults named <literal>application.xml</literal>
+                </td>
+                <td>
+                    Metadata to generate a deployment descriptor file, e.g. <literal>application.xml</literal>.
+                    If this file already exists in the <literal>appDirName/META-INF</literal> then the existing file contents will be used and
+                    the explicit configuration in the <literal>ear.deploymentDescriptor</literal> will be ignored.
+                </td>
+            </tr>
+        </table>
+
+        <para>These properties are provided by a <apilink class="org.gradle.plugins.ear.EarPluginConvention"/>
+            convention object.
+        </para>
+    </section>
+
+    <section id='sec:default_settings'>
+        <title>Ear</title>
+
+        <para>The default behavior of the Ear task is to copy the content of <literal>src/main/application</literal>
+            to the root of the archive. If your <literal>application</literal> directory doesn't contain a
+            <literal>META-INF/application.xml</literal> deployment descriptor then one will be generated for you.
+		</para>
+        <para>Also have a look at <apilink class="org.gradle.plugins.ear.Ear"/>.</para>
+    </section>
+    <section id='sec:customizing'>
+        <title>Customizing</title>
+        <para>Here is an example with the most important customization options:
+        </para>
+        <sample id="earCustomized" dir="ear/earCustomized/ear" title="Customization of ear plugin">
+            <sourcefile file="build.gradle"/>
+            <test args="assemble"/>
+        </sample>
+        <para>You can also use customization options that the <apilink class="org.gradle.plugins.ear.Ear"/>
+        	task provides, such as <literal>from</literal> and <literal>metaInf</literal>.
+        </para>
+    </section>
+    <section id='sec:using_custom_app_xml'>
+        <title>Using custom descriptor file</title>
+        <para>
+            Let's say you already have the <literal>application.xml</literal> and want to use it instead of configuring the <literal>ear.deploymentDescriptor</literal> section.
+            To accommodate that place the <literal>META-INF/application.xml</literal> in the right place inside your source folders (see the <literal>appDirName</literal> property).
+            The existing file contents will be used and the explicit configuration in the <literal>ear.deploymentDescriptor</literal> will be ignored.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/eclipsePlugin.xml b/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
index 3cb95cd..cc18efb 100644
--- a/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
+++ b/subprojects/docs/src/docs/userguide/eclipsePlugin.xml
@@ -21,6 +21,12 @@
         making it possible to import the project into Eclipse (<guimenuitem>File</guimenuitem> - <guimenuitem>Import...</guimenuitem> - <guimenuitem>Existing Projects into Workspace</guimenuitem>).
         Both external dependencies (including associated source and javadoc files) and project dependencies are considered.</para>
 
+    <para>Since 1.0-milestone-4 WTP-generating code was refactored into a separate plugin called <literal>eclipse-wtp</literal>.
+        So if you are interested in WTP integration then only apply the <literal>eclipse-wtp</literal> plugin. Otherwise applying <literal>eclipse</literal> plugin is enough.
+        This change was requested by Eclipse users who take advantage of <literal>war</literal> or <literal>ear</literal> plugin
+        but they don't use Eclipse WTP. Internally, <literal>eclipse-wtp</literal> also applies the <literal>eclipse</literal> plugin so you don't need to apply both of those plugins.
+    </para>
+
     <para>What exactly the Eclipse plugin generates depends on which other plugins are used:</para>
     <table>
         <title>Eclipse plugin behavior</title>
@@ -42,11 +48,15 @@
         </tr>
         <tr>
             <td><link linkend="war_plugin">War</link></td><td>Adds web application support to <filename>.project</filename> file.
-            Generates WTP settings files.</td>
+            Generates WTP settings files only if <literal>eclipse-wtp</literal> plugin was applied.</td>
+        </tr>
+        <tr>
+            <td><link linkend="ear_plugin">Ear</link></td><td>Adds ear application support to <filename>.project</filename> file.
+            Generates WTP settings files only if <literal>eclipse-wtp</literal> plugin was applied.</td>
         </tr>
     </table>
 
-    <para>One focus of the Eclipse plugin is to be open to customization. Each task provides a standardized set of hooks
+    <para>The Eclipse plugin is open to customization and provides a standardized set of hooks
         for adding and removing content from the generated files.
     </para>
 
@@ -57,7 +67,7 @@
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
         <para>The Eclipse plugin adds a number of tasks to your projects. The main tasks that you will use
-            are the <literal>eclipse</literal> and<literal>cleanEclipse</literal> tasks.
+            are the <literal>eclipse</literal> and <literal>cleanEclipse</literal> tasks.
         </para>
     </section>
     <section>
@@ -103,7 +113,7 @@
                     <literal>-</literal>
                 </td>
                 <td><apilink class="org.gradle.api.tasks.Delete"/></td>
-                <td>Generates the <filename>.project</filename> file.</td>
+                <td>Removes the <filename>.project</filename> file.</td>
             </tr>
             <tr>
                 <td>
@@ -113,7 +123,7 @@
                     <literal>-</literal>
                 </td>
                 <td><apilink class="org.gradle.api.tasks.Delete"/></td>
-                <td>Generates the <filename>.classpath</filename> file.</td>
+                <td>Removes the <filename>.classpath</filename> file.</td>
             </tr>
             <tr>
                 <td>
@@ -186,7 +196,7 @@
                     <literal>-</literal>
                 </td>
                 <td><apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent"/></td>
-                <td>Generates the <filename>.settings/org.eclipse.wst.common.component</filename> file.</td>
+                <td>Generates the <filename>.settings/org.eclipse.wst.common.component</filename> file only if <literal>eclipse-wtp</literal> plugin was applied.</td>
             </tr>
             <tr>
                 <td>
@@ -198,265 +208,75 @@
                 <td>
                     <apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet"/>
                 </td>
-                <td>Generates the <filename>.settings/org.eclipse.wst.common.project.facet.core.xml</filename> file.</td>
-            </tr>
-        </table>
-
-        <table id='eclipse-project'>
-            <title>EclipseProject task</title>
-            <thead>
-                <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <literal>projectName</literal>
-                </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>
-            <tr>
-                <td>
-                    <literal>comment</literal>
-                </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>
-                    <classname>Set<String></classname>
-                </td>
-                <td>empty set</td>
-                <td>The referenced projects of the Eclipse project.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>natures</literal>
-                </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>
-            </tr>
-            <tr>
-                <td>
-                    <literal>buildCommands</literal>
-                </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>
-            </tr>
-            <tr>
-                <td>
-                    <literal>links</literal>
-                </td>
-                <td>
-                    <classname>Set<Link></classname>
-                </td>
-                <td>empty set</td>
-                <td>The links for the Eclipse project.</td>
-            </tr>
-        </table>
-
-        <table id='eclipse-classpath'>
-            <title>EclipseClasspath task</title>
-            <thead>
-                <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <literal>sourceSets</literal>
-                </td>
-                <td><classname>Iterable<SourceSet></classname></td>
-                <td>
-                    <literal>project.sourceSets</literal>
-                </td>
-                <td>The source sets whose source directories are to be added to the Eclipse classpath.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>containers</literal>
-                </td>
-                <td>
-                    <classname>Set<String></classname>
-                </td>
-                <td>empty set</td>
-                <td>The containers to be added to the Eclipse classpath.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>plusConfigurations</literal>
-                </td>
-                <td>
-                    <classname>Set<Configuration></classname>
-                </td>
-                <td><literal>[configurations.testRuntime]</literal></td>
-                <td>The configurations whose files are to be transformed into classpath entries.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>minusConfigurations</literal>
-                </td>
-                <td>
-                    <classname>Set<Configuration></classname>
-                </td>
-                <td>empty set</td>
-                <td>The configurations whose files are to be excluded from the classpath entries.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>downloadSources</literal>
-                </td>
-                <td>
-                    <classname>boolean</classname>
-                </td>
-                <td>
-                    <literal>true</literal>
-                </td>
-                <td>Whether to download sources for external dependencies.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>downloadJavadoc</literal>
-                </td>
-                <td>
-                    <classname>boolean</classname>
-                </td>
-                <td>
-                    <literal>false</literal>
-                </td>
-                <td>Whether to download javadoc for external dependencies.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>variables</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>
+                <td>Generates the <filename>.settings/org.eclipse.wst.common.project.facet.core.xml</filename> file only if <literal>eclipse-wtp</literal> plugin was applied.</td>
             </tr>
         </table>
+    </section>
+    <section>
+        <title>Configuration</title>
 
-        <table id='eclipse-wtp-component'>
-            <title>EclipseWtpComponent task</title>
+        <table id='eclipse-configuration'>
+            <title>Configuration of the Eclipse plugin</title>
             <thead>
                 <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
+                    <td>Model</td>
+                    <td>Reference name</td>
                     <td>Description</td>
                 </tr>
             </thead>
             <tr>
                 <td>
-                    <literal>sourceDirs</literal>
-                </td>
-                <td><classname>Set<File></classname></td>
-                <td>
-                    The source directories of <literal>sourceSets.main</literal>
+                    <apilink class="org.gradle.plugins.ide.eclipse.model.EclipseModel"/>
                 </td>
-                <td>The source sets whose source directories are to be added to the Eclipse classpath.</td>
+                <td><literal>eclipse</literal></td>
+                <td>Top level element that enables configuration of the Eclipse plugin in a DSL-friendly fashion</td>
             </tr>
             <tr>
                 <td>
-                    <literal>deployName</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
+                    <apilink class="org.gradle.plugins.ide.eclipse.model.EclipseProject"/>
                 </td>
-                <td><literal>project.name</literal></td>
-                <td>The deploy name to be used.</td>
+                <td><literal>eclipse.project</literal></td>
+                <td>Allows configuring project information</td>
             </tr>
             <tr>
                 <td>
-                    <literal>plusConfigurations</literal>
-                </td>
-                <td>
-                    <classname>Set<Configuration></classname>
+                    <apilink class="org.gradle.plugins.ide.eclipse.model.EclipseClasspath"/>
                 </td>
-                <td><literal>[configurations.testRuntime]</literal></td>
-                <td>The configurations whose files are to be transformed into classpath entries.</td>
+                <td><literal>eclipse.classpath</literal></td>
+                <td>Allows configuring classpath information</td>
             </tr>
             <tr>
                 <td>
-                    <literal>minusConfigurations</literal>
-                </td>
-                <td>
-                    <classname>Set<Configuration></classname>
+                    <apilink class="org.gradle.plugins.ide.eclipse.model.EclipseJdt"/>
                 </td>
-                <td><literal>[configurations.providedRuntime]</literal></td>
-                <td>The configurations whose files are to be excluded from the classpath entries.</td>
+                <td><literal>eclipse.jdt</literal></td>
+                <td>Allows configuring jdt information (source/target java compatibility)</td>
             </tr>
             <tr>
                 <td>
-                    <literal>variables</literal>
-                </td>
-                <td>
-                    <classname>Map<String,File></classname>
+                    <apilink class="org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent"/>
                 </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>
+                <td><literal>eclipse.wtp.component</literal></td>
+                <td>Allows configuring wtp component information only if <literal>eclipse-wtp</literal> plugin was applied.</td>
             </tr>
-        </table>
-
-        <table id='eclipse-wtp-facet'>
-            <title>EclipseWtpFacet task</title>
-            <thead>
-                <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
             <tr>
                 <td>
-                    <literal>facets</literal>
-                </td>
-                <td>
-                    <classname>List<Facet></classname>
-                </td>
-                <td>
-                    <literal>jst.java</literal>
-                    and
-                    <literal>jst.web</literal>
-                    facet
+                    <apilink class="org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet"/>
                 </td>
-                <td>The facets to be added as installed elements.</td>
+                <td><literal>eclipse.wtp.facet</literal></td>
+                <td>Allows configuring wtp facet information only if <literal>eclipse-wtp</literal> plugin was applied.</td>
             </tr>
         </table>
     </section>
     <section>
         <title>Customizing the generated files</title>
-        <para>All Eclipse tasks provide the same hooks and behavior for customizing the generated content.</para>
-        <para>The tasks recognize existing Eclipse files, and merge them with the generated content.</para>
+        <para>
+            The Eclipse plugin allows you to customise the generated metadata files. The plugin provides a DSL for configuring model objects
+            that model the Eclipse view of the project. These model objects are then merged with the existing Eclipse XML metadata to ultimately
+            generate new metadata. The model objects provide lower level hooks for working with domain objects representing the file content 
+            before and after merging with the model configuration. They also provide a very low level hook for working directly with the raw 
+            XML for adjustment before it is persisted, for fine tuning and configuration that the Eclipse plugin does not model.
+        </para>
         <section>
             <title>Merging</title>
             <para>Sections of existing Eclipse files that are also the target of generated content will be amended or overwritten,
@@ -465,80 +285,77 @@
                 <title>Disabling merging with a complete overwrite</title>
                 <para>To completely overwrite existing Eclipse files, execute a clean task together with its corresponding generation task,
                     for example <userinput>gradle cleanEclipse eclipse</userinput> (in that order). If you want to make this
-                    the default behavior, add <code>eclipse.dependsOn(cleanEclipse)</code> to your build script. This makes it
+                    the default behavior, add <code>tasks.eclipse.dependsOn(cleanEclipse)</code> to your build script. This makes it
                     unnecessary to execute the clean task explicitly.
                 </para>
-                <para>Complete overwrite works equally well for individual files, for example by executing<userinput>gradle cleanEclipseClasspath eclipseClasspath</userinput>.
+                <para>Complete overwrite works equally well for individual files, for example by executing <userinput> gradle cleanEclipseClasspath eclipseClasspath</userinput>.
                 </para>
             </section>
         </section>
         <section>
             <title>Hooking into the generation lifecycle</title>
-            <para>The Eclipse plugin provides domain classes modeling the sections of the Eclipse files
-                that are autogenerated by Gradle. The generation lifecycle is as follows:
+            <para>The Eclipse plugin provides objects modeling the sections of the Eclipse files
+                that are generated by Gradle. The generation lifecycle is as follows:
                 <orderedlist>
-                    <listitem>If there is an existing file, its whole XML content is parsed and stored in memory; otherwise, a default file is used in its place</listitem>
-                    <listitem>The domain objects are populated with the relevant content of the existing file</listitem>
-                    <listitem>The <code>beforeConfigured</code> hook is executed</listitem>
-                    <listitem>The domain objects are populated with content from Gradle's build model, which may require merging with content from the existing file</listitem>
-                    <listitem>The <code>whenConfigured</code> hook is executed</listitem>
-                    <listitem>All sections modeled by the domain objects are removed from the in-memory XML representation</listitem>
-                    <listitem>The domain objects inject their content into the in-memory XML representation</listitem>
-                    <listitem>The <code>withXml</code> hook is executed</listitem>
-                    <listitem>The in-memory XML representation is written to disk</listitem>
+                    <listitem>The file is read; or a default version provided by Gradle is used if it does not exist</listitem>
+                    <listitem>The <code>beforeMerged</code> hook is executed with a domain object representing the existing file</listitem>
+                    <listitem>The existing content is merged with the configuration inferred from the Gradle build or defined explicitly in the eclipse DSL</listitem>
+                    <listitem>The <code>whenMerged</code> hook is executed with a domain object representing contents of the file to be persisted</listitem>
+                    <listitem>The <code>withXml</code> hook is executed with a raw representation of the xml that will be persisted</listitem>
+                    <listitem>The final XML is persisted</listitem>
                 </orderedlist>
-                The following table lists the domain object used for each of the Eclipse task types:
+                The following table lists the domain object used for each of the Eclipse model types:
             </para>
-            <table id='eclipse-task-hooks'>
-                <title>Task Hooks</title>
+            <table id='eclipse-plugin-hooks'>
+                <title>Advanced configuration hooks</title>
                 <thead>
                     <tr>
-                        <td>Task type</td>
-                        <td><literal>beforeConfigured { arg -> }</literal> argument type</td>
-                        <td><literal>whenConfigured { arg -> }</literal> argument type</td>
+                        <td>Model</td>
+                        <td><literal>beforeMerged { arg -> }</literal> argument type</td>
+                        <td><literal>whenMerged { arg -> }</literal> argument type</td>
                         <td><literal>withXml { arg -> }</literal> argument type</td>
                     </tr>
                 </thead>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseProject"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.eclipse.model.EclipseProject"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.Project"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.Project"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseClasspath"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.eclipse.model.EclipseClasspath"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.Classpath"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.Classpath"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseJdt"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.eclipse.model.EclipseJdt"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.Jdt"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.Jdt"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td/>
                 </tr>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseWtpComponent"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.WtpComponent"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.WtpComponent"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.eclipse.GenerateEclipseWtpFacet"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.WtpFacet"/></td>
                     <td><apilink class="org.gradle.plugins.ide.eclipse.model.WtpFacet"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
             </table>
             <section id="sec:partial-overwrite">
                 <title>Partial overwrite of existing content</title>
                 <para>A <link linkend="sec:complete-overwrite">complete overwrite</link> causes all existing content to be discarded,
-                    thereby losing any changes made directly in the IDE. The <code>beforeConfigured</code> hook makes it possible
+                    thereby losing any changes made directly in the IDE. Alternatively, the <code>beforeMerged</code> hook makes it possible
                     to overwrite just certain parts of the existing content. The following example removes all existing dependencies
                     from the <literal>Classpath</literal> domain object:
                     <sample id="partialOverwrites" dir="eclipse"
                             title="Partial Overwrite for Classpath">
-                        <sourcefile file="build.gradle" snippet="module-before-configured"/>
+                        <sourcefile file="build.gradle" snippet="module-before-merged"/>
                     </sample>
                     The resulting <literal>.classpath</literal> file will only contain Gradle-generated dependency entries, but
                     not any other dependency entries that may have been present in the original file. (In the case of dependency entries,
@@ -546,17 +363,17 @@
                     The same could be done for the natures in the <literal>.project</literal> file:
                     <sample id="partialOverwritesProject" dir="eclipse"
                             title="Partial Overwrite for Project">
-                        <sourcefile file="build.gradle" snippet="project-before-configured"/>
+                        <sourcefile file="build.gradle" snippet="project-before-merged"/>
                     </sample>
                 </para>
             </section>
             <section>
                 <title>Modifying the fully populated domain objects</title>
-                <para>The <code>whenConfigured</code> hook allows to manipulate the fully populated domain objects. Often this is the
+                <para>The <code>whenMerged</code> hook allows to manipulate the fully populated domain objects. Often this is the
                     preferred way to customize Eclipse files. Here is how you would export all the dependencies of an Eclipse project:
                     <sample id="exportDependencies" dir="eclipse"
                             title="Export Dependencies">
-                        <sourcefile file="build.gradle" snippet="module-when-configured"/>
+                        <sourcefile file="build.gradle" snippet="module-when-merged"/>
                     </sample>
                 </para>
             </section>
diff --git a/subprojects/docs/src/docs/userguide/embedding.xml b/subprojects/docs/src/docs/userguide/embedding.xml
index c67c1aa..be227e2 100644
--- a/subprojects/docs/src/docs/userguide/embedding.xml
+++ b/subprojects/docs/src/docs/userguide/embedding.xml
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright 2009 the original author or authors.
+  ~ Copyright 2011 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.
@@ -13,10 +13,97 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id="embedding" xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id="embedding">
     <title>Embedding Gradle</title>
 
-    <para>t.b.d.
-    </para>
+    <section id='sec:Introduction'>
+        <title>Introduction to the Tooling API</title>
+        <para>The 1.0 milestone 3 release brought a new API called the tooling API,
+            which you can use for embedding Gradle. This API allows you to execute and monitor builds,
+            and to query Gradle about the details of a build.
+            The main audience for this API is IDE, CI server, other UI authors, or integration testing of your Gradle plugins.
+            However, it is open for anyone who needs to embed Gradle in their application.
+        </para>
+
+        <para>A fundamental characteristic of the tooling API is that it operates in a version independent way.
+            This means that you can use the same API to work with different target versions of Gradle.
+            The tooling API is Gradle wrapper aware and, by default,
+            uses the same target Gradle version as that used by the wrapper-powered project.
+        </para>
+
+        <para>Some features that the tooling API provides today:
+            <itemizedlist>
+                <listitem>You can query Gradle for the details of a build,
+                    including the project hierarchy and the project dependencies,
+                    external dependencies (including source and javadoc jars),
+                    source directories and tasks of each project.
+                </listitem>
+                <listitem>You can execute a build, and listen to stdout and stderr logging and progress
+                    (e.g. the stuff shown in the 'status bar' when you run on the command line).
+                </listitem>
+                <listitem>Tooling API can download and install the appropriate Gradle version, similar to the wrapper.
+                    Bear in mind that the tooling API is wrapper aware so you should not need to configure a Gradle distribution directly.
+                </listitem>
+                <listitem>The implementation is lightweight, with only a small number of dependencies.
+                    It is also a well-behaved library, and makes no assumptions about your class loader structure or logging configuration.
+                    This makes the API easy to bundle in your application.
+                </listitem>
+            </itemizedlist>
+        </para>
+
+        <para>In future we may support other interesting features:
+            <itemizedlist>
+                <listitem>Performance. The API gives us the opportunity to do lots of caching,
+                    static analysis and preemptive work, to make things faster for the user.
+                </listitem>
+                <listitem>Better progress monitoring and build cancellation.
+                    For example, allowing test execution to be monitored.
+                </listitem>
+                <listitem>Notifications when things in the build change, so that UIs and models can be updated.
+                    For example, your Eclipse or IDEA project will update immediately, in the background.
+                </listitem>
+                <listitem>Validating and prompting for user supplied configuration.
+                </listitem>
+                <listitem>Prompting for and managing user credentials.
+                </listitem>
+            </itemizedlist>
+        </para>
+
+        <para>The Tooling API is the official and recommended way to embed Gradle.
+            This means that the existing APIs, namely <apilink class="org.gradle.GradleLauncher"/>
+            and the open API (the UIFactory and friends),
+            are mildly deprecated and will be removed in some future version of Gradle.
+            If you happen to use one of the above APIs, please consider changing your application to use the tooling API instead.
+        </para>
+    </section>
+
+    <section id='sec:Daemon'>
+        <title>Tooling API and the Gradle Build Daemon</title>
+        <para>Please take a look at <xref linkend="gradle_daemon"/>.
+            The Tooling API uses the daemon all the time, e.g. you cannot officially use the Tooling API without the daemon.
+            This means that subsequent calls to the Tooling API, be it model building requests or task executing requests
+            can be executed in the same long-living process. <xref linkend="gradle_daemon"/> contains more details about the daemon,
+            specifically information on situations when new daemons are forked.
+        </para>
+    </section>
+
+    <section id='sec:Quickstart'>
+        <title>Quickstart</title>
+        <para>Since the tooling API is an interface for a programmer most of the documentation lives in Javadoc/Groovydoc.
+            This is exactly our intention - we don't expect this chapter to grow very much.
+            Instead we will add more code samples and improve the Javadoc documentation.
+            The main entry point to the tooling API is the
+            <apilink class="org.gradle.tooling.GradleConnector"/>.
+            You can navigate from there and find code samples and other instructions.
+            Pretty effective way of learning how to use the tooling API is checking out and running
+            the <emphasis>samples</emphasis> that live in <filename>$gradleHome/samples/toolingApi</filename>.
+        </para>
+        <para>
+            If you're embedding Gradle and you're looking for exact set of dependencies the tooling API Jar requires
+            please look at one of the samples in <filename>$gradleHome/samples/toolingApi</filename>.
+            The dependencies are declared in the Gradle build scripts.
+            You can also find the repository declarations where the Jars are obtained from.
+        </para>
+    </section>
 
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/findBugsPlugin.xml b/subprojects/docs/src/docs/userguide/findBugsPlugin.xml
new file mode 100644
index 0000000..eecac59
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/findBugsPlugin.xml
@@ -0,0 +1,95 @@
+<chapter id="findbugs_plugin">
+    <title>The FindBugs Plugin</title>
+    <para>The FindBugs plugin performs quality checks on your project's Java source files using <ulink url="http://findbugs.sourceforge.net">FindBugs</ulink>
+        and generates reports from these checks.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the FindBugs plugin, include in your build script:</para>
+        <sample id="useFindBugsPlugin" dir="codeQuality" title="Using the FindBugs plugin">
+            <sourcefile file="build.gradle" snippet="use-findbugs-plugin"/>
+        </sample>
+        <para>The plugin adds a number of tasks to the project that perform the quality checks. You can execute the checks by running <userinput>gradle check</userinput>.</para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The FindBugs plugin adds the following tasks to the project:</para>
+        <table>
+            <title>FindBugs plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>findbugsMain</literal>
+                </td>
+                <td><literal>classes</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.FindBugs"/></td>
+                <td>Runs FindBugs against the production Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>findbugsTest</literal>
+                </td>
+                <td><literal>testClasses</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.FindBugs"/></td>
+                <td>Runs FindBugs against the test Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>findbugs<replaceable>SourceSet</replaceable></literal>
+                </td>
+                <td>
+                    <literal><replaceable>sourceSet</replaceable>Classes</literal>
+                </td>
+                <td><apilink class="org.gradle.api.plugins.quality.FindBugs"/></td>
+                <td>Runs FindBugs against the given source set's Java source files.</td>
+            </tr>
+        </table>
+
+        <para>The FindBugs plugin adds the following dependencies to tasks defined by the Java plugin.</para>
+        <table>
+            <title>FindBugs plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td><literal>check</literal></td>
+                <td>All FindBugs tasks, including <literal>findbugsMain</literal> and <literal>findbugsTest</literal>.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The FindBugs plugin adds the following dependency configurations:</para>
+        <table>
+            <title>FindBugs plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>findbugs</filename>
+                </td>
+                <td>The FindBugs libraries to use</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Configuration</title>
+        <para>See <apilink class="org.gradle.api.plugins.quality.FindBugsExtension"/>.</para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/glossary.xml b/subprojects/docs/src/docs/userguide/glossary.xml
index 636addf..33be830 100644
--- a/subprojects/docs/src/docs/userguide/glossary.xml
+++ b/subprojects/docs/src/docs/userguide/glossary.xml
@@ -1,11 +1,62 @@
 <glossary>
     <title>Glossary</title>
     <glossdiv>
+        <title>A</title>
+        <glossentry>
+            <glossterm>Artifact</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>B</title>
+        <glossentry>
+            <glossterm>Build Script</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>C</title>
+        <glossentry>
+            <glossterm>Configuration</glossterm>
+            <glosssee otherterm="configuration"/>
+        </glossentry>
+        <glossentry>
+            <glossterm>Configuration Injection</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
         <title>D</title>
         <glossentry>
             <glossterm>DAG</glossterm>
             <glosssee otherterm="dag"/>
         </glossentry>
+        <glossentry>
+            <glossterm>Dependency</glossterm>
+            <glosssee otherterm="externalDependency"/>
+            <glosssee otherterm="projectDependency"/>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry id="configuration">
+            <glossterm>Dependency Configuration</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>Dependency Resolution</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
         <glossentry id="dag">
             <glossterm>Directed Acyclic Graph</glossterm>
             <acronym>DAG</acronym>
@@ -34,4 +85,92 @@
             <glosssee otherterm="dsl"/>
         </glossentry>
     </glossdiv>
+    <glossdiv>
+        <title>E</title>
+        <glossentry id="externalDependency">
+            <glossterm>External Dependency</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>Extension Object</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>I</title>
+        <glossentry id="initScript">
+            <glossterm>Init Script</glossterm>
+            <glossdef>
+                <para>A script that is run before the build itself starts, to allow customization of Gradle and the build.</para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>Initialization Script</glossterm>
+            <glosssee otherterm="initScript"/>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>P</title>
+        <glossentry>
+            <glossterm>Plugin</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>Project</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry id="projectDependency">
+            <glossterm>Project Dependency</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>Publication</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>R</title>
+        <glossentry>
+            <glossterm>Repository</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>S</title>
+        <glossentry>
+            <glossterm>Source Set</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
+    <glossdiv>
+        <title>T</title>
+        <glossentry>
+            <glossterm>Task</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>Transitive Dependency</glossterm>
+            <glossdef>
+                <para>??</para>
+            </glossdef>
+        </glossentry>
+    </glossdiv>
 </glossary>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/gradleDaemon.xml b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
new file mode 100644
index 0000000..f3090f9
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
@@ -0,0 +1,138 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+<chapter id='gradle_daemon'>
+    <title>The Gradle Daemon</title>
+
+    <section id='enter_the_daemon'>
+        <title>Enter the daemon</title>
+        <para><emphasis>The Gradle daemon</emphasis> (sometimes referred as <emphasis>the build daemon</emphasis>)
+            aims to improve the startup and execution time of Gradle.
+        </para>
+
+        <para>
+            We came up with several use cases where the daemon is very useful.
+            For some workflows, the user invokes Gradle many times to execute a small number of relatively quick tasks.
+            For example:
+            <itemizedlist>
+                <listitem>When using test driven development, where the unit tests are executed many times.</listitem>
+                <listitem>When developing a web application, where the application is assembled many times.</listitem>
+                <listitem>When discovering what a build can do, where gradle -t is executed a number of times.</listitem>
+            </itemizedlist>
+            For above sorts of workflows, it is important that the startup cost of invoking Gradle is as small as possible.
+        </para>
+        <para>
+            In addition, user interfaces can provide some interesting features if the Gradle model can be built relatively quickly.
+            For example, the daemon might be useful for following scenarios:
+            <itemizedlist>
+                <listitem>Content assistance in the IDE</listitem>
+                <listitem>Live visualisation of the build in a GUI</listitem>
+                <listitem>Tab completion in a CLI</listitem>
+            </itemizedlist>
+        </para>
+        <para>
+            In general, snappy behavior of the build tool is always handy.
+            If you try using the daemon for your local builds it's going to be hard
+            for you to go back to regular use of Gradle.
+        </para>
+        <para>
+            The Tooling API (see <xref linkend="embedding"/>)
+            uses the daemon all the time, e.g. you cannot officially use the Tooling API without the daemon.
+            This means that if you use the STS Gradle plugin for Eclipse or new Intellij IDEA plugin (IDEA>10)
+            the daemon acts behind the hood.
+        </para>
+        <para>In future the daemon will offer more features:
+            <itemizedlist>
+                <listitem>Snappy up-to-date checks: use native file system change notifications (eg via jdk7 nio.2)
+                    to preemptively perform up-to-date analysis.</listitem>
+                <listitem>Even faster builds: preemptively evaluate projects, so that the model is ready
+                    when the user next invokes Gradle.</listitem>
+                <listitem>Did we mention faster builds? The daemon can potentially preemptively download dependencies
+                    or check for new versions of snapshot dependencies.</listitem>
+                <listitem>Utilize a pool of reusable processes available for compilation and testing.
+                    For example, both the Groovy and Scala compilers have a large startup cost.
+                    The build daemon could maintain a process with Groovy and/or Scala already loaded.</listitem>
+                <listitem>Preemptive execution of certain tasks, for example compilation. Quicker feedback.</listitem>
+                <listitem>Fast and accurate bash tab completion.</listitem>
+                <listitem>Periodically garbage collect the Gradle caches.</listitem>
+            </itemizedlist>
+        </para>
+    </section>
+
+    <section id='reusing_daemons'>
+        <title>Reusing and expiration of daemons</title>
+        <para>The basic idea is that the gradle command forks a daemon process, which performs the actual build.
+            Subsequent invocations of the gradle command will reuse the daemon, avoiding the startup costs.
+            Sometimes we cannot use an existing daemon because it is busy or its java version or jvm arguments are different.
+            For exact details on when exactly new daemon process is forked read the dedicated section below.
+            The daemon process automatically expire after 3 hours of idle time.
+        </para>
+        <para>
+            Here're all situations in which we fork a new daemon process:
+            <itemizedlist>
+                <listitem>If the daemon process is currently busy running some job, a brand new daemon process will be started.</listitem>
+                <listitem>We fork a separate daemon process per java home. So even if there is some idle daemon waiting
+                    for build requests but you happen to run build with a different java home then a brand new daemon will be forked.</listitem>
+                <listitem>We fork a separate daemon process if the jvm arguments for the build are sufficiently different.
+                    For example we will not fork a new daemon if a some system property has changed.
+                    However if -Xmx memory setting change or some fundamental immutable system property changes (e.g. file.encoding)
+                    then new daemon will be forked.
+                </listitem>
+                <listitem>At the moment daemon is coupled with particular version of Gradle.
+                    This means that even if some daemon is idle but you are running the build
+                    with a different version of Gradle, a new daemon will be started.
+                    This also have a consequence for the <literal>--stop</literal> command line instruction:
+                    You can only stop daemons that were started with the Gradle version you use when running <literal>--stop</literal>.
+                </listitem>
+            </itemizedlist>
+            We plan to improve the ways of managing / pooling the daemons in future.
+        </para>
+    </section>
+
+    <section id='daemon_usage_and_troubleshooting'>
+        <title>Usage and troubleshooting</title>
+        <para>For command line usage take a look dedicated section in <xref linkend="gradle_command_line"/>.
+            If you are tired of using the same command line options again and again, take a look at
+            <xref linkend="sec:gradle_configuration_properties"/>.
+            The section contains information on how to configure certain behavior of the daemon
+            (including turning on the daemon by default) in a more 'persistent' way.
+        </para>
+        <para>
+            As mentioned earlier we are actively improving the daemon. At the moment the daemon
+            is marked as 'experimental' in the user interface. We encourage everyone to try the
+            daemon out and get back to us with feedback (or even better: the pull requests).
+            Some ways of troubleshooting the Gradle daemon:
+            <itemizedlist>
+                <listitem>If you have a problem with your build, try temporarily disabling the daemon
+                    (you can pass the command line switch <literal>--no-daemon</literal>).</listitem>
+                <listitem>Occasionally, you may want to stop the daemons either via the <literal>--stop</literal>
+                    command line option or in a more forceful way.</listitem>
+                <listitem>There is a daemon log file, which by default is located in the
+                    Gradle user home directory.</listitem>
+                <listitem>You may want to start the daemon in <literal>--foreground</literal>
+                    mode to observe how the build is executed.</listitem>
+            </itemizedlist>
+        </para>
+    </section>
+
+    <section id="sec:daemon_properties">
+        <title>Daemon properties</title>
+        <para>Some daemon settings can be configured in <filename>gradle.properties</filename>.
+            For example, jvm args - memory settings or the java home.
+            Please find more information in <xref linkend="sec:gradle_properties_and_system_properties"/>
+        </para>
+    </section>
+
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/gradleWrapper.xml b/subprojects/docs/src/docs/userguide/gradleWrapper.xml
index 4212665..0f73982 100644
--- a/subprojects/docs/src/docs/userguide/gradleWrapper.xml
+++ b/subprojects/docs/src/docs/userguide/gradleWrapper.xml
@@ -15,26 +15,28 @@
   -->
 <chapter id='gradle_wrapper'>
     <title>The Gradle Wrapper</title>
-    <para>Gradle is a new tool. You can't expect it to be installed on machines beyond your sphere of influence. An
-        example are continuous integration server where Gradle is not installed and where you have no admin rights for
-        the machine. Or what if you provide an open source project and you want to make it as easy as possible for your
-        users to build it?
+    <para>
+        The Gradle Wrapper (henceforth referred to as the “wrapper) is the preferred way of starting a Gradle build. The wrapper
+        is a batch script on Windows, and a shell script for other operating systems. When you start a Gradle build via the wrapper,
+        Gradle will be automatically downloaded and used to run the build. 
     </para>
-    <para>There is a simple and good <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/> task.
-        <footnote>
-            <para>If you download the Gradle source distribution or check out Gradle from SVN, you can build Gradle via
-                the Gradle wrapper. Gradle itself is continuously built by Bamboo and Teamcity via this wrapper. See
-                <ulink url='website:ci-server.html'/>
-            </para>
-        </footnote>
-        You can create such a task in your build script.
+    <para>
+        The wrapper is something you <emphasis>should</emphasis> check into version control. By distributing the wrapper with your project,
+        anyone can work with it without needing to install Gradle beforehand. Even better, users of the build are guaranteed to use the 
+        version of Gradle that the build was designed to work with. Of course, this is also great for 
+        <ulink url="http://en.wikipedia.org/wiki/Continuous_integration">continuous integration</ulink> servers (i.e. servers that regularly
+        build your project) as it requires no configuration on the server.
+    </para>
+    <para>
+        You install the wrapper into your project by adding and configuring a <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/> 
+        task in your build script, and then executing it.
     </para>
     <sample id="wrapperSimple" dir="userguide/wrapper/simple" title="Wrapper task">
         <sourcefile file="build.gradle"/>
     </sample>
-    <para>The build master usually explicitly executes this task. After such
-        an execution you find the following new or updated files in your project directory (in case the default configuration of the wrapper task is
-        used).
+    <para>
+        After such an execution you find the following new or updated files in your project directory 
+        (in case the default configuration of the wrapper task is used).
     </para>
     <sample id="wrapperSimple" dir="userguide/wrapper/simple" title="Wrapper generated files">
         <layout after="wrapper">
@@ -44,8 +46,10 @@
             gradle/wrapper/gradle-wrapper.properties
         </layout>
     </sample>
-    <para>All these files must be submitted to your version control system. The <command>gradlew</command> command
-        can be used <emphasis>exactly</emphasis> the same way as the <command>gradle</command> command.
+    <para>
+        All of these files <emphasis>should</emphasis> be submitted to your version control system. This only needs to be done once. After
+        these files have been added to the project, the project should then be built with the added <command>gradlew</command> command.
+        The <command>gradlew</command> command can be used <emphasis>exactly</emphasis> the same way as the <command>gradle</command> command.
     </para>
     <para>If you want to switch to a new version of Gradle you don't need to rerun the wrapper task. It is good enough
         to change the respective entry in the <literal>gradle-wrapper.properties</literal> file. But if there is for
@@ -70,7 +74,7 @@
         </sample>
         <para>
             You can specify the download URL of the wrapper distribution. You can also specify where the wrapper distribution
-            should be stored and unpacked (either within the project or within the gradle user home dir). If the wrapper
+            should be stored and unpacked (either within the project or within the Gradle user home dir). If the wrapper
             is run and there is local archive of the wrapper distribution Gradle tries to download it and stores it at
             the specified place. If there is no unpacked wrapper distribution Gradle unpacks the local archive of the
             wrapper distribution at the specified place. All the configuration options have defaults except the version of the wrapper distribution.</para>
@@ -79,6 +83,7 @@
         <para>If you don't
             want any download to happen when your project is build via <command>gradlew</command>, simply add the Gradle
             distribution zip to your version control at the location specified by your wrapper configuration.
+            Relative url is supported - you can specify a distribution file relative to the location of <literal>gradle-wrapper.properties</literal> file.
         </para>
         <para>If you build via the wrapper, any existing Gradle distribution installed on the machine is ignored.
         </para>
@@ -92,7 +97,7 @@
     </section>
     <section id='sec:environment_variable'>
         <title>Environment variable</title>
-        <para>Some rather exotic use cases might occur when working with the Gradle Wrapper. For example the continuos
+        <para>Some rather exotic use cases might occur when working with the Gradle Wrapper. For example the continuous
             integration server goes down during unzipping the Gradle distribution. As the distribution directory exists
             <command>gradlew</command>
             delegates to it but the distribution is corrupt. Or the zip-distribution was not properly downloaded. When
diff --git a/subprojects/docs/src/docs/userguide/groovyPlugin.xml b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
index c1f05b4..f8c25d4 100644
--- a/subprojects/docs/src/docs/userguide/groovyPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
@@ -162,7 +162,7 @@
         <para>Gradle is written in Groovy and allows you to write your build scripts in Groovy. But this is an internal
             aspect of Gradle which is strictly separated from building Groovy projects. You are free to choose the Groovy
             version your project should be build with. This Groovy version is not just used for compiling your code and
-            running your tests. The <literal>groovyc</literal> compiler and the the <literal>groovydoc</literal>
+            running your tests. The <literal>groovyc</literal> compiler and the <literal>groovydoc</literal>
             tool are also taken from the Groovy version you provide. As usual, with freedom comes responsibility ;). You are
             not just free to choose a Groovy version, you have to provide one. Gradle expects that the groovy libraries are
             assigned to the <literal>groovy</literal> dependency configuration. Here is an example using the public Maven
@@ -306,7 +306,7 @@
                     <literal>destinationDir</literal>
                 </td>
                 <td><classname>File</classname>.</td>
-                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+                <td><literal><replaceable>sourceSet</replaceable>.output.classesDir</literal></td>
             </tr>
             <tr>
                 <td>
diff --git a/subprojects/docs/src/docs/userguide/groovyTutorial.xml b/subprojects/docs/src/docs/userguide/groovyTutorial.xml
index bf432f4..61a1690 100644
--- a/subprojects/docs/src/docs/userguide/groovyTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/groovyTutorial.xml
@@ -17,7 +17,7 @@
     <title>Groovy Quickstart</title>
 
     <para>To build a Groovy project, you use the <firstterm>Groovy plugin</firstterm>. This plugin extends the Java
-        plugin to add Groovy compilation capabilties to your project. Your project can contain Groovy source code,
+        plugin to add Groovy compilation capabilities to your project. Your project can contain Groovy source code,
         Java source code, or a mix of the two. In every other respect, a Groovy project is identical to a Java project,
         which we have already seen in <xref linkend="tutorial_java_projects"/>.
     </para>
diff --git a/subprojects/docs/src/docs/userguide/guiTutorial.xml b/subprojects/docs/src/docs/userguide/guiTutorial.xml
index a95bed6..c906d9c 100644
--- a/subprojects/docs/src/docs/userguide/guiTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/guiTutorial.xml
@@ -1,126 +1,126 @@
-<!--
-  ~ 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.
-  -->
-<chapter id="tutorial_gradle_gui">
-    <title>Using the Gradle Graphical User Interface</title>
-    <para>In addition to supporting a traditional command line interface, gradle offers
-    a graphical user interface.  This is a stand alone user interface that can be launched with the
-    <command>--gui</command> option.</para>
-    <example>
-        <title>Launching the GUI</title>
-        <programlisting><![CDATA[
-gradle --gui
-]]></programlisting>
-    </example>
-    <para>Note that this command blocks until the gradle GUI is closed.  Under *nix it is probably preferable to run
-        this as a background task (<command>gradle --gui&</command>)</para>
-    <para>If you run this from your gradle project working directory, you should see a tree of tasks.</para>
-    <figure>
-        <title>GUI Task Tree</title>
-        <imageobject>
-            <imagedata fileref="img/guiTaskTree.png" width="586px" depth="597px"/>
-        </imageobject>
-    </figure>
-    <para>It is preferable to run this command from your gradle project directory so that the settings of the UI will be
-        stored in your project directory. However, you can run it then change the working directory via the Setup tab in
-        the UI.
-    </para>
-    <para>The UI displays 4 tabs along the top and an output window along the bottom.</para>
-    <section>
-        <title>Task Tree</title>
-        <para>
-            The Task Tree shows a hierarchical display of all projects and their tasks.
-            Double clicking a task executes it.
-        </para>
-        <para>
-            There is also a filter so that uncommon tasks can be hidden. You can toggle the filter via the Filter button. 
-            Editing the filter allows you to configure which tasks and projects are shown. Hidden tasks show up in red.
-            Note: newly created tasks will show up by default (versus being hidden by default).
-        </para>
-        <para>The Task Tree context menu provides the following options:</para>
-        <itemizedlist>
-            <listitem><para>Execute ignoring dependencies. This does not require dependent projects to be rebuilt (same as the -a option).</para></listitem>
-            <listitem><para>Add tasks to the favorites (see Favorites tab)</para></listitem>
-            <listitem><para>Hide the selected tasks. This adds them to the filter.</para></listitem>
-            <listitem><para>Edit the build.gradle file.
-                Note: this requires Java 1.6 or higher and requires that you have .gradle files associated in your OS.
-            </para></listitem>
-        </itemizedlist>        
-    </section>
-    <section>
-        <title>Favorites</title>
-        <para>
-            The Favorites tab is place to store commonly-executed commands. These can be complex commands
-            (anything that's legal to gradle) and you can provide them with a display name. This is useful for creating,
-            say, a custom build command that explicitly skips tests, documentation, and samples that you could call
-            "fast build".
-        </para>
-        <para>
-            You can reorder favorites to your liking and even export them to disk so they can imported by others.
-            If you edit them, you are given options to "Always Show Live Output."
-            This only applies if you have 'Only Show Output When Errors Occur'.
-            This override always forces the output to be shown.
-        </para>
-    </section>
-    <section>
-        <title>Command Line</title>
-        <para>
-            The Command Line tab is place to execute a single gradle command directly.
-            Just enter whatever you would normally enter after 'gradle' on the command line.  This also provides
-            a place to try out commands before adding them to favorites.
-        </para>
-    </section>
-    <section>
-        <title>Setup</title>
-        <para>The Setup tab allows configuration of some general settings.</para>
-        <figure>
-            <title>GUI Setup</title>
-            <imageobject>
-                <imagedata fileref="img/guiSetup.png" width="586px" depth="597px"/>
-            </imageobject>
-        </figure>
-
-        <itemizedlist>
-            <listitem>
-                <para>Current Directory</para>
-                <para>Defines the root directory of your gradle project (typically where build.gradle is located).</para>
-            </listitem>
-
-            <listitem>
-                <para>Stack Trace Output</para>
-                <para>
-                    This determines how much information to write out stack traces when errors occur.
-                    Note: if you specify a stack trace level on either  the Command Line or Favorites tab, it will override
-                    this stack trace level.
-                </para>
-            </listitem>
-
-            <listitem>
-                <para>Only Show Output When Errors Occur</para>
-                <para>Enabling this option hides any output when a task is executed unless the build fails.</para>
-            </listitem>
-
-            <listitem>
-                <para>Use Custom Gradle Executor - Advanced feature</para>
-                <para>
-                    This provides you with an alternate way to launch gradle commands.
-                    This is useful if your project requires some extra setup that is done inside another batch file or shell script
-                    (such as specifying an init script).
-                </para>
-            </listitem>
-        </itemizedlist>
-
-    </section>
+<!--
+  ~ 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.
+  -->
+<chapter id="tutorial_gradle_gui">
+    <title>Using the Gradle Graphical User Interface</title>
+    <para>In addition to supporting a traditional command line interface, Gradle offers
+    a graphical user interface.  This is a stand alone user interface that can be launched with the
+    <command>--gui</command> option.</para>
+    <example>
+        <title>Launching the GUI</title>
+        <programlisting><![CDATA[
+gradle --gui
+]]></programlisting>
+    </example>
+    <para>Note that this command blocks until the Gradle GUI is closed.  Under *nix it is probably preferable to run
+        this as a background task (<command>gradle --gui&</command>)</para>
+    <para>If you run this from your Gradle project working directory, you should see a tree of tasks.</para>
+    <figure>
+        <title>GUI Task Tree</title>
+        <imageobject>
+            <imagedata fileref="img/guiTaskTree.png" width="586px" depth="597px"/>
+        </imageobject>
+    </figure>
+    <para>It is preferable to run this command from your Gradle project directory so that the settings of the UI will be
+        stored in your project directory. However, you can run it then change the working directory via the Setup tab in
+        the UI.
+    </para>
+    <para>The UI displays 4 tabs along the top and an output window along the bottom.</para>
+    <section>
+        <title>Task Tree</title>
+        <para>
+            The Task Tree shows a hierarchical display of all projects and their tasks.
+            Double clicking a task executes it.
+        </para>
+        <para>
+            There is also a filter so that uncommon tasks can be hidden. You can toggle the filter via the Filter button. 
+            Editing the filter allows you to configure which tasks and projects are shown. Hidden tasks show up in red.
+            Note: newly created tasks will show up by default (versus being hidden by default).
+        </para>
+        <para>The Task Tree context menu provides the following options:</para>
+        <itemizedlist>
+            <listitem><para>Execute ignoring dependencies. This does not require dependent projects to be rebuilt (same as the -a option).</para></listitem>
+            <listitem><para>Add tasks to the favorites (see Favorites tab)</para></listitem>
+            <listitem><para>Hide the selected tasks. This adds them to the filter.</para></listitem>
+            <listitem><para>Edit the build.gradle file.
+                Note: this requires Java 1.6 or higher and requires that you have .gradle files associated in your OS.
+            </para></listitem>
+        </itemizedlist>        
+    </section>
+    <section>
+        <title>Favorites</title>
+        <para>
+            The Favorites tab is place to store commonly-executed commands. These can be complex commands
+            (anything that's legal to Gradle) and you can provide them with a display name. This is useful for creating,
+            say, a custom build command that explicitly skips tests, documentation, and samples that you could call
+            "fast build".
+        </para>
+        <para>
+            You can reorder favorites to your liking and even export them to disk so they can imported by others.
+            If you edit them, you are given options to "Always Show Live Output."
+            This only applies if you have 'Only Show Output When Errors Occur'.
+            This override always forces the output to be shown.
+        </para>
+    </section>
+    <section>
+        <title>Command Line</title>
+        <para>
+            The Command Line tab is place to execute a single Gradle command directly.
+            Just enter whatever you would normally enter after 'gradle' on the command line.  This also provides
+            a place to try out commands before adding them to favorites.
+        </para>
+    </section>
+    <section>
+        <title>Setup</title>
+        <para>The Setup tab allows configuration of some general settings.</para>
+        <figure>
+            <title>GUI Setup</title>
+            <imageobject>
+                <imagedata fileref="img/guiSetup.png" width="586px" depth="597px"/>
+            </imageobject>
+        </figure>
+
+        <itemizedlist>
+            <listitem>
+                <para>Current Directory</para>
+                <para>Defines the root directory of your Gradle project (typically where build.gradle is located).</para>
+            </listitem>
+
+            <listitem>
+                <para>Stack Trace Output</para>
+                <para>
+                    This determines how much information to write out stack traces when errors occur.
+                    Note: if you specify a stack trace level on either  the Command Line or Favorites tab, it will override
+                    this stack trace level.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>Only Show Output When Errors Occur</para>
+                <para>Enabling this option hides any output when a task is executed unless the build fails.</para>
+            </listitem>
+
+            <listitem>
+                <para>Use Custom Gradle Executor - Advanced feature</para>
+                <para>
+                    This provides you with an alternate way to launch Gradle commands.
+                    This is useful if your project requires some extra setup that is done inside another batch file or shell script
+                    (such as specifying an init script).
+                </para>
+            </listitem>
+        </itemizedlist>
+
+    </section>
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/ideaPlugin.xml b/subprojects/docs/src/docs/userguide/ideaPlugin.xml
index 1153844..bd4326a 100644
--- a/subprojects/docs/src/docs/userguide/ideaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/ideaPlugin.xml
@@ -41,7 +41,7 @@
         </tr>
     </table>
 
-    <para>One focus of the IDEA plugin is to be open to customization. Each task provides a standardized set of hooks
+    <para>One focus of the IDEA plugin is to be open to customization. The plugin provides a standardized set of hooks
         for adding and removing content from the generated files.
     </para>
 
@@ -58,7 +58,10 @@
     <section>
         <title>Tasks</title>
 
-        <para>The IDEA plugin adds the tasks shown below to a project.</para>
+        <para>The IDEA plugin adds the tasks shown below to a project.
+            Notice that <literal>clean</literal> does not depend on <literal>cleanIdeaWorkspace</literal>.
+            It's because workspace contains a lot of user specific temporary data and typically it is not desirable to manipulate it outside IDEA.
+        </para>
 
         <table id='ideatasks'>
             <title>IDEA plugin - Tasks</title>
@@ -83,7 +86,7 @@
                     <literal>cleanIdea</literal>
                 </td>
                 <td>
-                    <literal>cleanIdeaProject</literal>, <literal>cleanIdeaModule</literal>, <literal>cleanIdeaWorkspace</literal>
+                    <literal>cleanIdeaProject</literal>, <literal>cleanIdeaModule</literal>
                 </td>
                 <td><apilink class="org.gradle.api.tasks.Delete"/></td>
                 <td>Removes all IDEA configuration files</td>
@@ -149,276 +152,55 @@
                 <td>Generates the <literal>.iws</literal> file. This task is only added to the root project.</td>
             </tr>
         </table>
+    </section>
 
-        <table id='idea-module'>
-            <title>IdeaModule task</title>
-            <thead>
-                <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
-                    <td>Default Value with Java Plugin</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>
-                    <literal>moduleDir</literal>
-                </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>
-            </tr>
-            <tr>
-                <td>
-                    <literal>outputFile</literal>
-                </td>
-                <td>
-                    <classname>File</classname>
-                </td>
-                <td><literal><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable>.iml</literal></td>
-                <td>-</td>
-                <td>The <filename>.iml</filename> file. Used to look for existing files and as the target for generation. Must not be null.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>inheritOutputDirs</literal>
-                </td>
-                <td>
-                    <classname>Boolean</classname>
-                </td>
-                <td>null</td>
-                <td>null</td>
-                <td>If <literal>null</literal>, the value in the existing or default module file is used (the default file uses
-                    <literal>true</literal>). If <literal>true</literal>, the module output directories will be located below the
-                    project output directories. If <literal>false</literal>, the directories specified with the <literal>sourceDirs</literal>
-                    and <literal>testSourceDirs</literal> properties are used.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>sourceDirs</literal>
-                </td>
-                <td>
-                    <classname>Set<File></classname>
-                </td>
-                <td>empty set</td>
-                <td>The source directories of <literal>sourceSets.main</literal></td>
-                <td>The directories containing the production sources. Must not be null.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>testSourceDirs</literal>
-                </td>
-                <td>
-                    <classname>Set<File></classname>
-                </td>
-                <td>empty set</td>
-                <td>The source directories of <literal>sourceSets.test</literal></td>
-                <td>The directories containing the test sources. Must not be null.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>excludeDirs</literal>
-                </td>
-                <td>
-                    <classname>Set<File></classname>
-                </td>
-                <td>empty set</td>
-                <td>-</td>
-                <td>The directories to be excluded by IDEA. Must not be null.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>outputDir</literal>
-                </td>
-                <td>
-                    <classname>File</classname>
-                </td>
-                <td>
-                    <literal>null</literal>
-                </td>
-                <td><literal>null</literal></td>
-                <td>The IDEA output directory for the production sources. If null, no entry is created.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>testOutputDir</literal>
-                </td>
-                <td>
-                    <classname>File</classname>
-                </td>
-                <td>
-                    <literal>null</literal>
-                </td>
-                <td><literal>null</literal></td>
-                <td>The IDEA output directory for the test sources. If null, no entry is created.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>javaVersion</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
-                </td>
-                <td>
-                    <literal>null</literal>
-                </td>
-                <td>-</td>
-                <td>If null, the value of the existing or default <literal>.ipr</literal> file is used (the default file uses
-                    <literal>inherited</literal>). If set to <literal>inherited</literal>, the project SDK is used. Otherwise,
-                    the SDK for the corresponding value of java version is used.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>downloadSources</literal>
-                </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>
-            <tr>
-                <td>
-                    <literal>downloadJavadoc</literal>
-                </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>
-            <tr>
-                <td>
-                    <literal>scopes</literal>
-                </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 collections of <link linkend="sub:configurations">Configuration</link> objects. The files of the
-                    plus configurations are added minus the files from the minus configurations. </td>
-                </tr>
-            <tr>
-                <td>
-                    <literal>variables</literal>
-                </td>
-                <td>
-                    <classname>Map<String, File></classname>
-                </td>
-                <td>
-                    <literal>[:]</literal>
-                </td>
-                <td>-</td>
-                <td>The variables to be used for replacing absolute paths in the .iml file. For example, you might add
-                    a <literal>GRADLE_USER_HOME</literal> variable to point to the Gradle user home directory.</td>
-            </tr>
-        </table>
-     
-        <table id='idea-project'>
-            <title>IdeaProject task</title>
+    <section>
+        <title>Configuration</title>
+
+        <table id='idea-configuration'>
+            <title>Configuration of the idea plugin</title>
             <thead>
                 <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
-                    <td>Default Value with Java Plugin</td>
+                    <td>Model</td>
+                    <td>Reference name</td>
                     <td>Description</td>
                 </tr>
             </thead>
             <tr>
                 <td>
-                    <literal>subprojects</literal>
-                </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>
-                    file. The subprojects will only be mapped if the IDEA plugin has been
-                    applied to them.</td>
-            </tr>
-            <tr>
-                <td>
-                    <literal>outputFile</literal>
-                </td>
-                <td>
-                    <classname>File</classname>
+                    <apilink class="org.gradle.plugins.ide.idea.model.IdeaModel"/>
                 </td>
-                <td><literal><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable>.ipr</literal></td>
-                <td>-</td>
-                <td>The <literal>.ipr</literal> file. Used to look for existing files and as the target for generation. Must not be null.</td>
+                <td><literal>idea</literal></td>
+                <td>Top level element that enables configuration of the idea plugin in a DSL-friendly fashion</td>
             </tr>
             <tr>
                 <td>
-                    <literal>javaVersion</literal>
-                </td>
-                <td>
-                    <classname>String</classname>
-                </td>
-                <td>
-                    <literal>1.6</literal>
+                    <apilink class="org.gradle.plugins.ide.idea.model.IdeaProject"/>
                 </td>
-                <td>-</td>
-                <td>The Java version used for defining the project SDK.</td>
+                <td><literal>idea.project</literal></td>
+                <td>Allows configuring project information</td>
             </tr>
             <tr>
                 <td>
-                    <literal>wildcards</literal>
-                </td>
-                <td>
-                    <classname>Set<String></classname>
-                </td>
-                <td>
-                    <literal>['!?*.java', '!?*.groovy']</literal>
+                    <apilink class="org.gradle.plugins.ide.idea.model.IdeaModule"/>
                 </td>
-                <td>-</td>
-                <td>The wildcard resource patterns. Must not be null.</td>
+                <td><literal>idea.module</literal></td>
+                <td>Allows configuring module information</td>
             </tr>
-        </table>
-
-        <table id='idea-workspace'>
-            <title>IdeaWorkspace task</title>
-            <thead>
-                <tr>
-                    <td>Property</td>
-                    <td>Type</td>
-                    <td>Default Value</td>
-                    <td>Default Value with Java Plugin</td>
-                    <td>Description</td>
-                </tr>
-            </thead>
             <tr>
                 <td>
-                    <literal>outputFile</literal>
-                </td>
-                <td>
-                    <classname>File</classname>
+                    <apilink class="org.gradle.plugins.ide.idea.model.IdeaWorkspace"/>
                 </td>
-                <td><literal><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable>.iws</literal></td>
-                <td>-</td>
-                <td>The <literal>.iws</literal> file. Used to look for existing files and as the target for generation. Must not be null.</td>
+                <td><literal>idea.workspace</literal></td>
+                <td>Allows configuring the workspace xml</td>
             </tr>
         </table>
     </section>
+
     <section>
         <title>Customizing the generated files</title>
-        <para>All IDEA tasks provide the same hooks and behavior for customizing the generated content.
-            However, the workspace file can effectively only be manipulated via the <code>withXml</code> hook,
+        <para>IDEA plugin provides hooks and behavior for customizing the generated content.
+            The workspace file can effectively only be manipulated via the <code>withXml</code> hook
             because its corresponding domain object is essentially empty.</para>
         <para>The tasks recognize existing IDEA files, and merge them with the generated content.</para>
         <section>
@@ -429,67 +211,64 @@
                 <title>Disabling merging with a complete overwrite</title>
                 <para>To completely overwrite existing IDEA files, execute a clean task together with its corresponding generation task,
                     for example <userinput>gradle cleanIdea idea</userinput> (in that order). If you want to make this
-                    the default behavior, add <code>idea.dependsOn(cleanIdea)</code> to your build script. This makes it
+                    the default behavior, add <code>tasks.idea.dependsOn(cleanIdea)</code> to your build script. This makes it
                     unnecessary to execute the clean task explicitly.
                 </para>
-                <para>Complete overwrite works equally well for individual files, for example by executing<userinput>gradle cleanIdeaModule ideaModule</userinput>.</para>
+                <para>Complete overwrite works equally well for individual files, for example by executing <userinput> gradle cleanIdeaModule ideaModule</userinput>.</para>
             </section>
         </section>
         <section>
             <title>Hooking into the generation lifecycle</title>
-            <para>The IDEA plugin provides domain classes modeling the sections of the IDEA files
-                that are autogenerated by Gradle. The generation lifecycle is as follows:
+            <para>The plugin provides objects modeling the sections of the metadata files
+                that are generated by Gradle. The generation lifecycle is as follows:
                 <orderedlist>
-                    <listitem>If there is an existing file, its whole XML content is parsed and stored in memory; otherwise, a default file is used in its place</listitem>
-                    <listitem>The domain objects are populated with the relevant content of the existing file</listitem>
-                    <listitem>The <code>beforeConfigured</code> hook is executed</listitem>
-                    <listitem>The domain objects are populated with content from Gradle's build model, which may require merging with content from the existing file</listitem>
-                    <listitem>The <code>whenConfigured</code> hook is executed</listitem>
-                    <listitem>All sections modeled by the domain objects are removed from the in-memory XML representation</listitem>
-                    <listitem>The domain objects inject their content into the in-memory XML representation</listitem>
-                    <listitem>The <code>withXml</code> hook is executed</listitem>
-                    <listitem>The in-memory XML representation is written to disk</listitem>
+                    <listitem>The file is read; or a default version provided by Gradle is used if it does not exist</listitem>
+                    <listitem>The <code>beforeMerged</code> hook is executed with a domain object representing the existing file</listitem>
+                    <listitem>The existing content is merged with the configuration inferred from the Gradle build or defined explicitly in the eclipse DSL</listitem>
+                    <listitem>The <code>whenMerged</code> hook is executed with a domain object representing contents of the file to be persisted</listitem>
+                    <listitem>The <code>withXml</code> hook is executed with a raw representation of the xml that will be persisted</listitem>
+                    <listitem>The final XML is persisted</listitem>
                 </orderedlist>
-                The following table lists the domain objects used for each of the IDEA task types:
+                The following table lists the domain object used for each of the model types:
             </para>
-            <table id='idea-task-hooks'>
-                <title>Task Hooks</title>
+            <table id='idea-hooks'>
+                <title>Idea plugin hooks</title>
                 <thead>
                     <tr>
-                        <td>Task type</td>
-                        <td><literal>beforeConfigured { arg -> }</literal> argument type</td>
-                        <td><literal>whenConfigured { arg -> }</literal> argument type</td>
+                        <td>Model</td>
+                        <td><literal>beforeMerged { arg -> }</literal> argument type</td>
+                        <td><literal>whenMerged { arg -> }</literal> argument type</td>
                         <td><literal>withXml { arg -> }</literal> argument type</td>
                     </tr>
                 </thead>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.idea.GenerateIdeaProject"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.idea.model.IdeaProject"/></td>
                     <td><apilink class="org.gradle.plugins.ide.idea.model.Project"/></td>
                     <td><apilink class="org.gradle.plugins.ide.idea.model.Project"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.idea.GenerateIdeaModule"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.idea.model.IdeaModule"/></td>
                     <td><apilink class="org.gradle.plugins.ide.idea.model.Module"/></td>
                     <td><apilink class="org.gradle.plugins.ide.idea.model.Module"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
                 <tr>
-                    <td><apilink class="org.gradle.plugins.ide.idea.GenerateIdeaWorkspace"/></td>
+                    <td><apilink class="org.gradle.plugins.ide.idea.model.IdeaWorkspace"/></td>
                     <td><apilink class="org.gradle.plugins.ide.idea.model.Workspace"/></td>
                     <td><apilink class="org.gradle.plugins.ide.idea.model.Workspace"/></td>
-                    <td><apilink class="org.gradle.api.artifacts.maven.XmlProvider"/></td>
+                    <td><apilink class="org.gradle.api.XmlProvider"/></td>
                 </tr>
             </table>
             <section id="sec:partial-overwrite">
                 <title>Partial overwrite of existing content</title>
                 <para>A <link linkend="sec:complete-overwrite">complete overwrite</link> causes all existing content to be discarded,
-                    thereby losing any changes made directly in the IDE. The <code>beforeConfigured</code> hook makes it possible
+                    thereby losing any changes made directly in the IDE. The <code>beforeMerged</code> hook makes it possible
                     to overwrite just certain parts of the existing content. The following example removes all existing dependencies
                     from the <literal>Module</literal> domain object:
                     <sample id="partialOverwrites" dir="idea"
                             title="Partial Overwrite for Module">
-                        <sourcefile file="build.gradle" snippet="module-before-configured"/>
+                        <sourcefile file="build.gradle" snippet="module-before-merged"/>
                     </sample>
                     The resulting module file will only contain Gradle-generated dependency entries, but
                     not any other dependency entries that may have been present in the original file. (In the case of dependency entries,
@@ -497,17 +276,17 @@
                     The same could be done for the module paths in the project file:
                     <sample id="partialOverwritesProject" dir="idea"
                             title="Partial Overwrite for Project">
-                        <sourcefile file="build.gradle" snippet="project-before-configured"/>
+                        <sourcefile file="build.gradle" snippet="project-before-merged"/>
                     </sample>
                 </para>
             </section>
             <section>
                 <title>Modifying the fully populated domain objects</title>
-                <para>The <code>whenConfigured</code> hook allows to manipulate the fully populated domain objects. Often this is the
+                <para>The <code>whenMerged</code> hook allows to manipulate the fully populated domain objects. Often this is the
                     preferred way to customize IDEA files. Here is how you would export all the dependencies of an IDEA module:
                     <sample id="exportDependencies" dir="idea"
                             title="Export Dependencies">
-                        <sourcefile file="build.gradle" snippet="module-when-configured"/>
+                        <sourcefile file="build.gradle" snippet="module-when-merged"/>
                     </sample>
                     
                 </para>
@@ -530,7 +309,7 @@
         <title>Further things to consider</title>
         <para>The paths of the dependencies in the generated IDEA files are absolute. If you manually define a path variable
             pointing to the Gradle dependency cache, IDEA will automatically replace the absolute dependency paths with
-            this path variable. If you use such a path variable, you need to tell the ideaModule task the name of this variable,
+            this path variable. If you use such a path variable, you need to configure this path variable via <literal>idea.pathVariables</literal>,
             so that it can do a proper merge without creating duplicates.</para>
     </section>
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.graphml b/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.graphml
index 33b0e28..6fcd645 100644
--- a/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.graphml
+++ b/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.graphml
@@ -1,16 +1,21 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
-  <key attr.name="description" attr.type="string" for="node" id="d0"/>
-  <key for="node" id="d1" yfiles.type="nodegraphics"/>
-  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
-  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
-  <key for="graphml" id="d4" yfiles.type="resources"/>
-  <graph edgedefault="directed" id="G" parse.edges="11" parse.nodes="11" parse.order="free">
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yFiles for Java 2.8-->
+  <key for="graphml" id="d0" yfiles.type="resources"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d7"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d8"/>
+  <key for="edge" id="d9" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
     <node id="n0">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="540.255859375" y="143.5"/>
+          <y:Geometry height="30.0" width="140.0" x="540.255859375" y="-145.0"/>
           <y:Fill color="#96E880" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="45.349609375" x="47.3251953125" y="6.296875">compile</y:NodeLabel>
@@ -19,10 +24,9 @@
       </data>
     </node>
     <node id="n1">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="111.0"/>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="-152.5"/>
           <y:Fill color="#96E880" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">runtime</y:NodeLabel>
@@ -31,10 +35,9 @@
       </data>
     </node>
     <node id="n2">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="211.0"/>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="-52.5"/>
           <y:Fill color="#96E880" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="67.357421875" x="36.3212890625" y="6.296875">testCompile</y:NodeLabel>
@@ -43,10 +46,9 @@
       </data>
     </node>
     <node id="n3">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="0.0" y="162.23863636363637"/>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="-102.5"/>
           <y:Fill color="#96E880" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="68.025390625" x="35.9873046875" y="6.296875">testRuntime</y:NodeLabel>
@@ -55,10 +57,9 @@
       </data>
     </node>
     <node id="n4">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="48.761363636363654"/>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="-245.0"/>
           <y:Fill color="#96E880" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="48.68359375" x="45.658203125" y="6.296875">archives</y:NodeLabel>
@@ -67,10 +68,9 @@
       </data>
     </node>
     <node id="n5">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="0.0" y="100.0"/>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="-152.5"/>
           <y:Fill color="#96E880" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="40.029296875" x="49.9853515625" y="6.296875">default</y:NodeLabel>
@@ -79,10 +79,9 @@
       </data>
     </node>
     <node id="n6">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="769.677734375" y="143.5"/>
+          <y:Geometry height="30.0" width="140.0" x="809.677734375" y="-145.0"/>
           <y:Fill color="#C3D9E6" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="96.0390625" x="21.98046875" y="6.296875">compileJava task</y:NodeLabel>
@@ -91,10 +90,9 @@
       </data>
     </node>
     <node id="n7">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="161.0"/>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="-102.5"/>
           <y:Fill color="#C3D9E6" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="48.68359375" x="45.658203125" y="6.296875">test task</y:NodeLabel>
@@ -103,10 +101,9 @@
       </data>
     </node>
     <node id="n8">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="0.0" y="50.0"/>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="-202.5"/>
           <y:Fill color="#C3D9E6" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="42.677734375" x="48.6611328125" y="6.296875">jar task</y:NodeLabel>
@@ -115,10 +112,9 @@
       </data>
     </node>
     <node id="n9">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="540.255859375" y="220.783203125"/>
+          <y:Geometry height="30.0" width="140.0" x="540.255859375" y="-45.0"/>
           <y:Fill color="#C3D9E6" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="119.376953125" x="10.3115234375" y="6.296875">compileTestJava task</y:NodeLabel>
@@ -127,10 +123,9 @@
       </data>
     </node>
     <node id="n10">
-      <data key="d0"/>
-      <data key="d1">
+      <data key="d6">
         <y:ShapeNode>
-          <y:Geometry height="30.0" width="140.0" x="0.0" y="0.0"/>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="-252.5"/>
           <y:Fill color="#C3D9E6" transparent="false"/>
           <y:BorderStyle color="#000000" type="line" width="1.0"/>
           <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="111.390625" x="14.3046875" y="6.296875">uploadArchives task</y:NodeLabel>
@@ -139,12 +134,11 @@
       </data>
     </node>
     <edge id="e0" source="n2" target="n0">
-      <data key="d2"/>
-      <data key="d3">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
           <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="7.5">
-            <y:Point x="450.833984375" y="204.75"/>
-            <y:Point x="500.255859375" y="204.75"/>
+            <y:Point x="420.833984375" y="-45.0"/>
+            <y:Point x="450.833984375" y="-122.5"/>
           </y:Path>
           <y:LineStyle color="#000000" type="line" width="1.0"/>
           <y:Arrows source="none" target="white_delta"/>
@@ -152,23 +146,20 @@
       </data>
     </edge>
     <edge id="e1" source="n1" target="n0">
-      <data key="d2"/>
-      <data key="d3">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5">
-            <y:Point x="500.255859375" y="126.0"/>
-          </y:Path>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5"/>
           <y:LineStyle color="#000000" type="line" width="1.0"/>
           <y:Arrows source="none" target="white_delta"/>
         </y:QuadCurveEdge>
       </data>
     </edge>
     <edge id="e2" source="n3" target="n2">
-      <data key="d2"/>
-      <data key="d3">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
           <y:Path sx="70.0" sy="10.0" tx="-70.0" ty="-0.0">
-            <y:Point x="180.0" y="226.0"/>
+            <y:Point x="150.0" y="-77.5"/>
+            <y:Point x="180.7060546875" y="-37.5"/>
           </y:Path>
           <y:LineStyle color="#000000" type="line" width="1.0"/>
           <y:Arrows source="none" target="white_delta"/>
@@ -176,11 +167,11 @@
       </data>
     </edge>
     <edge id="e3" source="n3" target="n1">
-      <data key="d2"/>
-      <data key="d3">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="-10.0" tx="-70.0" ty="7.5">
-            <y:Point x="180.0" y="132.73863636363637"/>
+          <y:Path sx="70.0" sy="-10.0" tx="-70.0" ty="10.0">
+            <y:Point x="150.0" y="-97.5"/>
+            <y:Point x="180.7060546875" y="-127.5"/>
           </y:Path>
           <y:LineStyle color="#000000" type="line" width="1.0"/>
           <y:Arrows source="none" target="white_delta"/>
@@ -188,88 +179,86 @@
       </data>
     </edge>
     <edge id="e4" source="n5" target="n1">
-      <data key="d2"/>
-      <data key="d3">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-7.5"/>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
           <y:LineStyle color="#000000" type="line" width="1.0"/>
           <y:Arrows source="none" target="white_delta"/>
         </y:QuadCurveEdge>
       </data>
     </edge>
-    <edge id="e5" source="n5" target="n4">
-      <data key="d2"/>
-      <data key="d3">
+    <edge id="e5" source="n0" target="n6">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="10.0">
-            <y:Point x="230.833984375" y="108.26136363636365"/>
-          </y:Path>
-          <y:LineStyle color="#000000" type="line" width="1.0"/>
-          <y:Arrows source="none" target="white_delta"/>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.0" y="-9.06640625">used by</y:EdgeLabel>
         </y:QuadCurveEdge>
       </data>
     </edge>
-    <edge id="e6" source="n0" target="n6">
-      <data key="d2"/>
-      <data key="d3">
+    <edge id="e6" source="n8" target="n4">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="7.5">
+            <y:Point x="150.0" y="-195.0"/>
+            <y:Point x="180.0" y="-204.3670634920635"/>
+            <y:Point x="230.833984375" y="-204.3670634920635"/>
+            <y:Point x="255.833984375" y="-222.5"/>
+          </y:Path>
           <y:LineStyle color="#000000" type="dashed" width="1.0"/>
           <y:Arrows source="none" target="delta"/>
-          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="20.0" y="-9.06640625">used by</y:EdgeLabel>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.833984375" x="40.0" y="-18.433469742063494">adds jar</y:EdgeLabel>
         </y:QuadCurveEdge>
       </data>
     </edge>
-    <edge id="e7" source="n8" target="n4">
-      <data key="d2"/>
-      <data key="d3">
+    <edge id="e7" source="n3" target="n7">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
           <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
           <y:LineStyle color="#000000" type="dashed" width="1.0"/>
           <y:Arrows source="none" target="delta"/>
-          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.833984375" x="40.0" y="-9.06640625">adds jar</y:EdgeLabel>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.7060546875" y="-9.06640625">used by</y:EdgeLabel>
         </y:QuadCurveEdge>
       </data>
     </edge>
-    <edge id="e8" source="n3" target="n7">
-      <data key="d2"/>
-      <data key="d3">
+    <edge id="e8" source="n2" target="n9">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
           <y:LineStyle color="#000000" type="dashed" width="1.0"/>
           <y:Arrows source="none" target="delta"/>
-          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.7060546875" y="-10.305038452148438">used by</y:EdgeLabel>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.0" y="-9.06640625">used by</y:EdgeLabel>
         </y:QuadCurveEdge>
       </data>
     </edge>
-    <edge id="e9" source="n2" target="n9">
-      <data key="d2"/>
-      <data key="d3">
+    <edge id="e9" source="n10" target="n4">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0">
-            <y:Point x="450.833984375" y="235.783203125"/>
-          </y:Path>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5"/>
           <y:LineStyle color="#000000" type="dashed" width="1.0"/>
           <y:Arrows source="none" target="delta"/>
-          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.0" y="-6.783203125">used by</y:EdgeLabel>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.13671875" x="40.3486328125" y="-9.06640625">uploads</y:EdgeLabel>
         </y:QuadCurveEdge>
       </data>
     </edge>
-    <edge id="e10" source="n10" target="n4">
-      <data key="d2"/>
-      <data key="d3">
+    <edge id="e10" source="n8" target="n1">
+      <data key="d9">
         <y:QuadCurveEdge straightness="0.1">
-          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-10.0">
-            <y:Point x="230.4853515625" y="15.0"/>
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-10.0">
+            <y:Point x="150.0" y="-180.0"/>
+            <y:Point x="180.0" y="-171.234126984127"/>
+            <y:Point x="230.833984375" y="-171.234126984127"/>
+            <y:Point x="255.833984375" y="-147.5"/>
           </y:Path>
           <y:LineStyle color="#000000" type="dashed" width="1.0"/>
           <y:Arrows source="none" target="delta"/>
-          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.13671875" x="40.3486328125" y="-9.06640625">uploads</y:EdgeLabel>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.833984375" x="40.0" y="-0.30053323412698774">adds jar</y:EdgeLabel>
         </y:QuadCurveEdge>
       </data>
     </edge>
   </graph>
-  <data key="d4">
+  <data key="d0">
     <y:Resources/>
   </data>
 </graphml>
diff --git a/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.png b/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.png
index f8f4575..ceec2b3 100644
Binary files a/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.png and b/subprojects/docs/src/docs/userguide/img/javaPluginConfigurations.png differ
diff --git a/subprojects/docs/src/docs/userguide/initscripts.xml b/subprojects/docs/src/docs/userguide/initscripts.xml
index 6c775e4..f50a124 100644
--- a/subprojects/docs/src/docs/userguide/initscripts.xml
+++ b/subprojects/docs/src/docs/userguide/initscripts.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='init_scripts' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='init_scripts'>
     <title>Initialization Scripts</title>
     <para>Gradle provides a powerful mechanism to allow customizing the build based on the current environment.  This
         mechanism also supports tools that wish to integrate with Gradle.
@@ -24,12 +24,15 @@
             These scripts, however, are run before the build starts.  Here are several possible uses:
             <itemizedlist>
                 <listitem>
-                    <para>Set up properties based on the current environment (such as a developer's machine vs. a
-                        continuous integration server).
+                    <para>Set up enterprise-wide configuration, such as where to find custom plugins.</para>
+                </listitem>
+                <listitem>
+                    <para>Set up properties based on the current environment, such as a developer's machine vs. a
+                        continuous integration server.
                     </para>
                 </listitem>
                 <listitem>
-                    <para>Supply personal information about the user to the build, such as repository or database
+                    <para>Supply personal information about the user that is required by the build, such as repository or database
                         authentication credentials.
                     </para>
                 </listitem>
@@ -50,24 +53,57 @@
             One main limitation of init scripts is that they cannot access classes in the buildSrc project (see
             <xref linkend='sec:build_sources'/> for details of this feature).
         </para>
-        <para>There are two ways to use init scripts.  Either put a file called <filename>init.gradle</filename> in
-            <filename><replaceable>USER_HOME</replaceable>/.gradle</filename>,
-            or specify the file on the command line.  The command line option is <option>-I</option> or
-            <option>--init-script</option> followed by the path to the script.  The command line option can appear
-            more than once, each time adding another init script. If more than one init script is found they will all be
-            executed. This allows for a tool to specify an init script and the user to put home in their home directory
-            for defining the environment and both scripts will run when gradle is executed.
+    </section>
+    <section>
+        <title>Using an init script</title>
+        <para>There are several ways to use an init script:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>Specify a file on the command line.  The command line option is <option>-I</option> or <option>--init-script</option> followed
+                    by the path to the script.  The command line option can appear more than once, each time adding another init script.</para>
+            </listitem>
+            <listitem>
+                <para>Put a file called <filename>init.gradle</filename> in the <filename><replaceable>USER_HOME</replaceable>/.gradle/</filename> directory.</para>
+            </listitem>
+            <listitem>
+                <para>Put a file that ends with <filename>.gradle</filename> in the <filename><replaceable>USER_HOME</replaceable>/.gradle/init.d/</filename> directory.</para>
+            </listitem>
+            <listitem>
+                <para>Put a file that ends with <filename>.gradle</filename> in the <filename><replaceable>GRADLE_HOME</replaceable>/init.d/</filename>
+                    directory, in the Gradle distribution. This allows you to package up a custom Gradle distribution containing some custom build logic and plugins. You can
+                    combine this with the <link linkend="gradle_wrapper">Gradle wrapper</link> as a way to make custom logic available to all builds
+                    in your enterprise.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>
+            If more than one init script is found they will all be executed, in the order specified above. Scripts in a given directory are executed
+            in alphabetical order.
+            This allows, for example, a tool to specify an init script on the command line and the user to put one in their home directory for defining
+            the environment and both scripts will run when Gradle is executed.
         </para>
     </section>
     <section>
         <title>Writing an init script</title>
-        <para>
-            <para>Similar to a Gradle build script, an init script is a groovy script. Each init script has a
-                <apilink class="org.gradle.api.invocation.Gradle"/> instance associated with it. Any property reference
-                and method call in the init script will delegate to this <classname>Gradle</classname> instance.
-            </para>
-            <para>Each init script also implements the <apilink class="org.gradle.api.Script"/> interface.</para>
+        <para>Similar to a Gradle build script, an init script is a groovy script. Each init script has a
+            <apilink class="org.gradle.api.invocation.Gradle"/> instance associated with it. Any property reference
+            and method call in the init script will delegate to this <classname>Gradle</classname> instance.
         </para>
+        <para>Each init script also implements the <apilink class="org.gradle.api.Script"/> interface.</para>
+        <section>
+            <title>Configuring projects from an init script</title>
+            <para>You can use an init script to configure the projects in the build. This works in a similar way to configuring projects in a
+                multi-project build.
+                The following sample shows how to perform extra configuration from an init script <emphasis>before</emphasis> the projects are evaluated.
+                This sample uses this feature to configure an extra repository to be used only for certain environments.
+            </para>
+            <sample id="initScriptConfiguration" dir="userguide/initScripts/configurationInjection" title="Using init script to perform extra configuration before projects are evaluated">
+                <sourcefile file="build.gradle"/>
+                <sourcefile file="init.gradle"/>
+                <output args="--init-script init.gradle -q showRepos"/>
+            </sample>
+        </section>
     </section>
     <section id='sec:custom_classpath'>
         <title>External dependencies for the init script</title>
diff --git a/subprojects/docs/src/docs/userguide/installation.xml b/subprojects/docs/src/docs/userguide/installation.xml
index b3345b5..2521327 100644
--- a/subprojects/docs/src/docs/userguide/installation.xml
+++ b/subprojects/docs/src/docs/userguide/installation.xml
@@ -32,7 +32,7 @@
 
 <section>
     <title>Download</title>
-    <para>You can download one of the Gradle distributions from the <ulink url="website:downloads.html">Gradle web site</ulink>.</para>
+    <para>You can download one of the Gradle distributions from the <ulink url="website:downloads">Gradle web site</ulink>.</para>
 </section>
 
 <section>
@@ -52,7 +52,7 @@
     </listitem>
     <listitem>
         <para>The binary sources. This is for reference only. If you want to build Gradle you need to download the source distribution
-            or checkout the sources from the source repository. See the <ulink url="website:build.html">Gradle web site</ulink> for details.
+            or checkout the sources from the source repository. See the <ulink url="website:development">Gradle web site</ulink> for details.
         </para>
     </listitem>
 </itemizedlist>
@@ -78,22 +78,10 @@ some zip front ends for Mac OS X don't restore the file permissions properly.
 <title>Running and testing your installation</title>
 
 <para>You run Gradle via the <command>gradle</command> command. To check if Gradle is properly installed just type
-<command>gradle -v</command> and you should get an output like:
+<command>gradle -v</command>. The output shows Gradle version and also local environment configuration (groovy and jvm version, etc.).
+    The displayed gradle version should match the distribution you have downloaded.
 </para>
 
-<screen>
-------------------------------------------------------------
-Gradle 1.0-milestone-3
-------------------------------------------------------------
-
-Gradle build time: Monday, 25 April 2011 5:24:40 PM EST
-Groovy: 1.7.10
-Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
-Ivy: 2.2.0
-JVM: 1.6.0_22 (Sun Microsystems Inc. 17.1-b03)
-OS: Linux 2.6.35-28-generic amd64
-</screen>
-
 </section>
 
 <section>
diff --git a/subprojects/docs/src/docs/userguide/introduction.xml b/subprojects/docs/src/docs/userguide/introduction.xml
index 162f0a8..3a07ccc 100644
--- a/subprojects/docs/src/docs/userguide/introduction.xml
+++ b/subprojects/docs/src/docs/userguide/introduction.xml
@@ -42,8 +42,7 @@
         </listitem>
         <listitem>
             <para>Support for transitive dependency management without the need for remote repositories or
-                <literal>pom.xml</literal> and <literal>ivy.xml</literal>
-                files.
+                <filename>pom.xml</filename> and <filename>ivy.xml</filename> files.
             </para>
         </listitem>
         <listitem>
@@ -70,11 +69,7 @@
             documented as completely as they need to be. Some of the content presented won't be entirely clear or
             will assume that you know more about Gradle than you do. We need your help to improve this user
             guide. You can find out more about contributing to the documentation at the
-            <ulink url="website:contributing.html">Gradle web site</ulink>.
-        </para>
-        <para>You can find more examples, and some additions to this user guide, on the
-            <ulink url="http://docs.codehaus.org/display/GRADLE/User+guide">wiki</ulink>.
-            You can also contribute your own examples and extra content there.
+            <ulink url="website:contribute">Gradle web site</ulink>.
         </para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/javaPlugin.xml b/subprojects/docs/src/docs/userguide/javaPlugin.xml
index 32f507e..ccec467 100644
--- a/subprojects/docs/src/docs/userguide/javaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaPlugin.xml
@@ -31,14 +31,15 @@
 
     <section>
         <title>Source sets</title>
-        <para>The Java plugin introduces the concept of a <firstterm>source set</firstterm>. A source set is a group of
+        <para>The Java plugin introduces the concept of a <firstterm>source set</firstterm>. A source set is simply a group of
             source files which are compiled and executed together. These source files may include Java source files and
             resource files. Other plugins add the ability to include Groovy and Scala source files in a source set.
             A source set has an associated compile classpath, and runtime classpath.
         </para>
         <para>
-            You might use a source set to define an integration test suite, or for the API classes of your project, or
-            to separate source which needs to be compiled against different Java versions.
+            One use for source sets is to group source files into logical groups which describe their purpose. For example,
+            you might use a source set to define an integration test suite, or you might use separate source sets to define
+            the API and implementation classes of your project.
         </para>
         <para>The Java plugin defines two standard source sets, called <literal>main</literal> and <literal>test</literal>.
             The <literal>main</literal> source set contains your production source code, which is compiled and assembled
@@ -297,7 +298,9 @@
                     The tasks which produce the artifacts in configuration <replaceable>ConfigurationName</replaceable>.
                 </td>
                 <td><apilink class="org.gradle.api.Task"/></td>
-                <td>Assembles the artifacts in the specified configuration.</td>
+                <td>Assembles the artifacts in the specified configuration.
+                    The task is added by the Base plugin which is implicitly applied by the Java plugin.
+                </td>
             </tr>
             <tr>
                 <td>
@@ -307,7 +310,9 @@
                     The tasks which uploads the artifacts in configuration <replaceable>ConfigurationName</replaceable>.
                 </td>
                 <td><apilink class="org.gradle.api.tasks.Upload"/></td>
-                <td>Assembles and uploads the artifacts in the specified configuration.</td>
+                <td>Assembles and uploads the artifacts in the specified configuration.
+                    The task is added by the Base plugin which is implicitly applied by the Java plugin.
+                </td>
             </tr>
         </table>
 
@@ -356,8 +361,6 @@
         <title>Dependency management</title>
         <para>The Java plugin adds a number of dependency configurations to your project, as shown below. It assigns
             those configurations to tasks such as <literal>compileJava</literal> and <literal>test</literal>.
-            To learn more about configurations see <xref linkend="sub:configurations"/> and
-            <xref linkend="artifacts_and_configurations"/>.
         </para>
         <table id='tab:configurations'>
             <title>Java plugin - dependency configurations</title>
@@ -401,9 +404,10 @@
             </tr>
             <tr>
                 <td>default</td>
-                <td>runtime, archives</td>
+                <td>runtime</td>
                 <td>-</td>
-                <td>Artifacts produced <literal>and</literal> dependencies required by this project.
+                <td>The default configuration used by a project dependency on this project. Contains the artifacts and
+                    dependencies required by this project at runtime.
                 </td>
             </tr>
         </table>
@@ -413,6 +417,30 @@
                 <imagedata fileref="img/javaPluginConfigurations.png"/>
             </imageobject>
         </figure>
+        <para>For each source set you add to the project, the Java plugins adds the following dependency configurations:</para>
+        <table id="java_source_set_configurations">
+            <title>Java plugin - source set dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Extends</td>
+                    <td>Used by tasks</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td><replaceable>sourceSet</replaceable>Compile</td>
+                <td>-</td>
+                <td>compile<replaceable>SourceSet</replaceable>Java</td>
+                <td>Compile time dependencies for the given source set</td>
+            </tr>
+            <tr>
+                <td><replaceable>sourceSet</replaceable>Runtime</td>
+                <td><replaceable>sourceSet</replaceable>Compile</td>
+                <td>-</td>
+                <td>Runtime time dependencies for the given source set</td>
+            </tr>
+        </table>
     </section>
 
     <section>
@@ -629,9 +657,7 @@
                 <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or a Number, eg
                     <literal>'1.5'</literal> or <literal>1.5</literal>.
                 </td>
-                <td>
-                    <literal>1.5</literal>
-                </td>
+                <td>Value of the current used JVM</td>
                 <td>Java version compatibility to use when compiling Java source.</td>
             </tr>
             <tr>
@@ -664,17 +690,6 @@
                 <td>an empty manifest</td>
                 <td>The manifest to include in all JAR files.</td>
             </tr>
-            <tr>
-                <td>
-                    <literal>metaInf</literal>
-                </td>
-                <td>
-                    <classname>List</classname>
-                </td>
-                <td><literal>[]</literal></td>
-                <td>A set of <link linkend="sec:file_collections">file collections</link> which specify the files to
-                    include in the <filename>META-INF</filename> directory or all JAR files.</td>
-            </tr>
         </table>
 
         <para>
@@ -688,7 +703,7 @@
         <title>Working with source sets</title>
         <para>You can access the source sets of a project using the <literal>sourceSets</literal> property. This
             is a container for the project's source sets, of type <apilink class="org.gradle.api.tasks.SourceSetContainer"/>.
-            There is also a <literal>sourceSets()</literal> method, which you can pass a closure to which configures the
+            There is also a <literal>sourceSets { }</literal> script block, which you can pass a closure to configure the
             source set container. The source set container works pretty much the same way as other containers, such
             as <literal>tasks</literal>.
         </para>
@@ -701,15 +716,6 @@
         <sample id="configureSourceSet" dir="java/customizedLayout" title="Configuring the source directories of a source set">
             <sourcefile file="build.gradle" snippet="define-main"/>
         </sample>
-        <para>To define a new source set, you simply reference it in the <literal>sourceSets { }</literal> block.
-            When you define a source set, the Java plugin adds a number of tasks which assemble the classes for the
-            source set, as shown in <xref linkend="java_source_set_tasks"/>. For example, if you add a source set called
-            <literal>intTest</literal>, the Java plugin adds <literal>compileIntTestJava</literal>, <literal>processIntTestResources</literal>
-            and <literal>intTestClasses</literal> tasks.
-        </para>
-        <sample id="defineSourceSet" dir="userguide/java/sourceSets" title="Defining a source set">
-            <sourcefile file="build.gradle" snippet="define-source-set"/>
-        </sample>
 
         <section>
             <title>Source set properties</title>
@@ -742,7 +748,21 @@
                 </tr>
                 <tr>
                     <td>
-                        <literal>classesDir</literal>
+                        <literal>output</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.tasks.SourceSetOutput"/> (read-only)
+                    </td>
+                    <td>
+                        Not null
+                    </td>
+                    <td>
+                        The output files of the source set, containing its compiled classes and resources.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>output.classesDir</literal>
                     </td>
                     <td>
                         <classname>File</classname>
@@ -756,13 +776,27 @@
                 </tr>
                 <tr>
                     <td>
+                        <literal>output.resourcesDir</literal>
+                    </td>
+                    <td>
+                        <classname>File</classname>
+                    </td>
+                    <td>
+                        <literal><replaceable>buildDir</replaceable>/resources/<replaceable>name</replaceable></literal>
+                    </td>
+                    <td>
+                        The directory to generate the resources of this source set into.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
                         <literal>compileClasspath</literal>
                     </td>
                     <td>
                         <apilink class="org.gradle.api.file.FileCollection"/>
                     </td>
                     <td>
-                        <literal>compile</literal> Configuration.
+                        <literal>compile<replaceable>SourceSet</replaceable></literal> configuration.
                     </td>
                     <td>
                         The classpath to use when compiling the source files of this source set.
@@ -776,7 +810,7 @@
                         <apilink class="org.gradle.api.file.FileCollection"/>
                     </td>
                     <td>
-                        <literal>classesDir</literal> + <literal>runtime</literal> Configuration.
+                        <literal>output</literal> + <literal>runtime<replaceable>SourceSet</replaceable></literal> configuration.
                     </td>
                     <td>
                         The classpath to use when executing the classes of this source set.
@@ -846,7 +880,7 @@
                         <literal>allJava</literal>
                     </td>
                     <td>
-                        <apilink class="org.gradle.api.file.FileTree"/> (read-only)
+                        <apilink class="org.gradle.api.file.SourceDirectorySet"/> (read-only)
                     </td>
                     <td>
                         <literal>java</literal>
@@ -861,7 +895,7 @@
                         <literal>allSource</literal>
                     </td>
                     <td>
-                        <apilink class="org.gradle.api.file.FileTree"/> (read-only)
+                        <apilink class="org.gradle.api.file.SourceDirectorySet"/> (read-only)
                     </td>
                     <td>
                         <literal>resources + java</literal>
@@ -875,11 +909,33 @@
         </section>
 
         <section>
-            <title>Some source set examples</title>
-            <para>Using dependency configurations to define the source set classpath:</para>
-            <sample id="configureSourceSet" dir="userguide/java/sourceSets" title="Defining the classpath of a source set">
-                <sourcefile file="build.gradle" snippet="classpath-using-configurations"/>
+            <title>Defining new source sets</title>
+            <para>To define a new source set, you simply reference it in the <literal>sourceSets { }</literal> block. Here's an example:
+            </para>
+            <sample id="defineSourceSet" dir="userguide/java/sourceSets" title="Defining a source set">
+                <sourcefile file="build.gradle" snippet="define-source-set"/>
             </sample>
+            <para>
+                When you define a new source set, the Java plugin adds some dependency configurations for the
+                source set, as shown in <xref linkend="java_source_set_configurations"/>. You can use these configurations to
+                define the compile and runtime dependencies of the source set.
+            </para>
+            <sample id="sourceSetDependencies" dir="userguide/java/sourceSets" title="Defining source set dependencies">
+                <sourcefile file="build.gradle" snippet="source-set-dependencies"/>
+            </sample>
+            <para>
+                The Java plugin also adds a number of tasks which assemble the classes for the
+                source set, as shown in <xref linkend="java_source_set_tasks"/>. For example, for a source set called
+                <literal>intTest</literal>, you can run <userinput>gradle intTestClasses</userinput> to compile the
+                int test classes.
+            </para>
+            <sample id="compileSourceSet" dir="userguide/java/sourceSets" title="Compiling a source set">
+                <output args="intTestClasses"/>
+            </sample>
+        </section>
+
+        <section>
+            <title>Some source set examples</title>
             <para>Adding a JAR containing the classes of a source set:</para>
             <sample id="configureSourceSet" dir="userguide/java/sourceSets" title="Assembling a JAR for a source set">
                 <sourcefile file="build.gradle" snippet="jar"/>
@@ -918,7 +974,7 @@
                     <literal>classpath</literal>
                 </td>
                 <td><apilink class="org.gradle.api.file.FileCollection"/></td>
-                <td><literal>sourceSets.main.classes + sourceSets.main.compileClasspath</literal></td>
+                <td><literal>sourceSets.main.output + sourceSets.main.compileClasspath</literal></td>
             </tr>
             <tr>
                 <td><literal>source</literal></td>
@@ -994,7 +1050,7 @@
                     <literal>destinationDir</literal>
                 </td>
                 <td><classname>File</classname>. Can set using anything described in <xref linkend="sec:locating_files"/>.</td>
-                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+                <td><literal><replaceable>sourceSet</replaceable>.output.resourcesDir</literal></td>
             </tr>
         </table>
     </section>
@@ -1002,8 +1058,7 @@
     <section id='sec:compile'>
         <title>CompileJava</title>
         <para>The Java plugin adds a <apilink class="org.gradle.api.tasks.compile.Compile"/> instance for each
-            source set in the project. The compile task delegates to Ant's javac task to do the compile. You can set most
-            of the properties of the Ant javac task.
+            source set in the project. Some of the most common configuration options are shown below.
         </para>
         <table>
             <title>Java plugin - Compile properties</title>
@@ -1033,9 +1088,20 @@
                     <literal>destinationDir</literal>
                 </td>
                 <td><classname>File</classname>.</td>
-                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+                <td><literal><replaceable>sourceSet</replaceable>.output.classesDir</literal></td>
             </tr>
         </table>
+        
+        <para>The compile task delegates to Ant's javac task. Setting <literal>options.useAnt</literal> to <literal>false</literal>
+            activates Gradle's direct compiler integration, bypassing the Ant task. In a future Gradle release, this will become the default.
+        </para>
+        
+        <para>By default, the Java compiler runs in the Gradle process. Setting <literal>options.fork</literal> to <literal>true</literal>
+            causes compilation to occur in a separate process. In the case of the Ant javac task, this means that a new process will be
+            forked for each compile task, which can slow down compilation. Conversely, Gradle's direct compiler integration (see above) will
+            reuse the same compiler process as much as possible. In both cases, all fork options specified
+            with <literal>options.forkOptions</literal> will be honored.
+        </para>
     </section>
 
     <section id='sec:java_test'>
@@ -1172,7 +1238,7 @@
                 <tr>
                     <td><literal>testClassesDir</literal></td>
                     <td><classname>File</classname></td>
-                    <td><literal>sourceSets.test.classesDir</literal></td>
+                    <td><literal>sourceSets.test.output.classesDir</literal></td>
                 </tr>
                 <tr>
                     <td><literal>classpath</literal></td>
@@ -1233,7 +1299,7 @@
             </sample>
             <para>Manifest are merged in the order they are declared by the <literal>from</literal> statement. If
             the based manifest and the merged manifest both define values for the same key, the merged manifest wins by default.
-            You can fully customize the merge behavior behavior by adding <literal>eachEntry</literal> actions in which
+            You can fully customize the merge behavior by adding <literal>eachEntry</literal> actions in which
                 you have access to a <apilink class="org.gradle.api.java.archives.ManifestMergeDetails"/> instance for each entry
                 of the resulting manifest. The merge is not immediately triggered by the from statement. It is done lazily,
                 either when generating the jar, or by calling <literal>writeTo</literal> or <literal>effectiveManifest</literal></para>
@@ -1243,16 +1309,6 @@
                 <sourcefile file="build.gradle" snippet="write"/>
             </sample>
         </section>
-        <section id='sub:metainf'>
-            <title>MetaInf</title>
-            <para>The convention object of the Java plugin has a <literal>metaInf</literal> property pointing to a list of
-                <classname>FileSet</classname> objects. With these file sets you can define which files should be in the
-                <filename>META-INF</filename> directory of a JAR or a WAR archive.
-            </para>
-            <programlisting><![CDATA[
-metaInf << new FileSet(someDir)
-]]></programlisting>
-        </section>
     </section>
 
     <section id='sec:upload'>
diff --git a/subprojects/docs/src/docs/userguide/javaTutorial.xml b/subprojects/docs/src/docs/userguide/javaTutorial.xml
index 8c6d383..edef18d 100644
--- a/subprojects/docs/src/docs/userguide/javaTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/javaTutorial.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='tutorial_java_projects' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='tutorial_java_projects'>
     <title>Java Quickstart</title>
 
     <section>
@@ -126,7 +126,7 @@
             <sample id="javaQuickstart" dir="java/quickstart" title="Adding dependencies">
                 <sourcefile file="build.gradle" snippet="dependencies"/>
             </sample>
-            <para>You can find out more in <xref linkend="dependency_management"/>.</para>
+            <para>You can find out more in <xref linkend="artifact_dependencies_tutorial"/>.</para>
         </section>
 
         <section>
@@ -261,16 +261,18 @@
     </section>
 
     <section>
-        <title>Summary</title>
-        <para>In this chapter, you have seen how to do some of the things you commonly need to build a Java based
-            project. This chapter is not exhaustive, and there are many other things you can do with Java projects in
-            Gradle. These are dealt with in later chapters. Also, a lot of the behaviour you have seen in this chapter
-            is configurable. For example, you can change where Gradle looks Java source files, or add extra tasks, or
-            you can change what any task actually does. Again, you will see how this works in later chapters.
-        </para>
+        <title>Where to next?</title>
         <para>
+            In this chapter, you have seen how to do some of the things you commonly need to build a Java based
+            project. This chapter is not exhaustive, and there are many other things you can do with Java projects in
+            Gradle.
             You can find out more about the Java plugin in <xref linkend="java_plugin"/>, and you can find more sample
             Java projects in the <filename>samples/java</filename> directory in the Gradle distribution.
         </para>
+        <para>
+            Otherwise, continue on to <xref linkend="artifact_dependencies_tutorial"/>.
+        </para>
+        <para>
+        </para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/jdependPlugin.xml b/subprojects/docs/src/docs/userguide/jdependPlugin.xml
new file mode 100644
index 0000000..9918c37
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/jdependPlugin.xml
@@ -0,0 +1,95 @@
+<chapter id="jdepend_plugin">
+    <title>The JDepend Plugin</title>
+    <para>The JDepend plugin performs quality checks on your project's source files using <ulink url="http://clarkware.com/software/JDepend.html">JDepend</ulink>
+        and generates reports from these checks.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the JDepend plugin, include in your build script:</para>
+        <sample id="useJDependPlugin" dir="codeQuality" title="Using the JDepend plugin">
+            <sourcefile file="build.gradle" snippet="use-jdepend-plugin"/>
+        </sample>
+        <para>The plugin adds a number of tasks to the project that perform the quality checks. You can execute the checks by running <userinput>gradle check</userinput>.</para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The JDepend plugin adds the following tasks to the project:</para>
+        <table>
+            <title>JDepend plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>jdependMain</literal>
+                </td>
+                <td><literal>classes</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.JDepend"/></td>
+                <td>Runs JDepend against the production Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>jdependTest</literal>
+                </td>
+                <td><literal>testClasses</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.JDepend"/></td>
+                <td>Runs JDepend against the test Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>jdepend<replaceable>SourceSet</replaceable></literal>
+                </td>
+                <td>
+                    <literal><replaceable>sourceSet</replaceable>Classes</literal>
+                </td>
+                <td><apilink class="org.gradle.api.plugins.quality.JDepend"/></td>
+                <td>Runs JDepend against the given source set's Java source files.</td>
+            </tr>
+        </table>
+
+        <para>The JDepend plugin adds the following dependencies to tasks defined by the Java plugin.</para>
+        <table>
+            <title>JDepend plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td><literal>check</literal></td>
+                <td>All JDepend tasks, including <literal>jdependMain</literal> and <literal>jdependTest</literal>.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The JDepend plugin adds the following dependency configurations:</para>
+        <table>
+            <title>JDepend plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>jdepend</filename>
+                </td>
+                <td>The JDepend libraries to use</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Configuration</title>
+        <para>See <apilink class="org.gradle.api.plugins.quality.JDependExtension"/>.</para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/multiproject.xml b/subprojects/docs/src/docs/userguide/multiproject.xml
index 4661460..0d09e09 100644
--- a/subprojects/docs/src/docs/userguide/multiproject.xml
+++ b/subprojects/docs/src/docs/userguide/multiproject.xml
@@ -150,7 +150,8 @@
             </section>
             <section id='ssub:filtering_by_properties'>
                 <title>Filtering by properties</title>
-                <para>Using the project name for filtering is one option. Using dynamic project properties is another.
+                <para>Using the project name for filtering is one option. Using extra project properties is another.
+                    (See <xref linkend='sec:extra_properties'/> for more information on extra properties.)
                 </para>
                 <sample id="multiprojectTropicalWithProperties" dir="userguide/multiproject/tropicalWithProperties/water" includeLocation="true" title="Adding custom behaviour to some projects (filtered by project properties)">
                     <layout>
@@ -323,37 +324,18 @@
                     <output args="-q action"/>
                 </sample>
                 <para>We have now declared that the
-                    <literal>consumer</literal>
+                    <literal>action</literal> task in the <literal>consumer</literal>
                     project has an
                     <emphasis>execution dependency</emphasis>
-                    on the
+                    on the <literal>action</literal> task on the
                     <literal>producer</literal>
-                    project. For Gradle declaring
-                    <emphasis>execution dependencies</emphasis>
-                    between
-                    <emphasis>projects</emphasis>
-                    is syntactic sugar. Under the hood Gradle creates task dependencies out of them. You can also create
-                    cross project tasks dependencies manually by using the absolute path of the tasks.
+                    project.
                 </para>
             </section>
-            <section id='ssub:the_nature_of_project_dependencies'>
-                <title>The nature of project dependencies</title>
-                <para>Let's change the naming of our tasks and execute the build.
-                </para>
-                <sample id="multiprojectMessagesDifferentTaskNames" dir="userguide/multiproject/dependencies/messagesDifferentTaskNames/messages" title="Project execution dependencies">
-                    <sourcefile file="consumer/build.gradle"/>
-                    <sourcefile file="producer/build.gradle"/>
-                    <output args="-q consume"/>
-                </sample>
-                <para>Oops. Why does this not work? The
-                    <literal>dependsOn</literal>
-                    command is created for projects with a common lifecycle. Provided you have two Java projects where
-                    one depends on the other. If you trigger a compile for the dependent project you don't want that
-                    <emphasis>all</emphasis>
-                    tasks of the other project get executed. Therefore a
-                    <literal>dependsOn</literal>
-                    creates dependencies between tasks with equal names. To deal with the scenario above you would do
-                    the following:
+            <section id='ssub:the_nature_of_cross_project_task_dependencies'>
+                <title>The nature of cross project task dependencies</title>
+                <para>Of course, task dependencies across different projects are not limited to tasks with the same name.
+                    Let's change the naming of our tasks and execute the build.
                 </para>
                 <sample id="multiprojectMessagesTaskDependencies" dir="userguide/multiproject/dependencies/messagesTaskDependencies/messages" title="Cross project task dependencies">
                     <sourcefile file="consumer/build.gradle"/>
@@ -413,6 +395,10 @@
                 difference is that always all projects are configured, even when you start the build from a subproject.
                 The default configuration order is top down, which is usually what is needed.
             </para>
+            <para>
+                To change the the default configuration order to be bottom up, That means that a project configuration
+                depends on the configuration of its child projects, the <literal>evaluationDependsOnChildren()</literal> method can be used.
+            </para>
             <para>On the same nesting level the configuration order depends on the alphanumeric position. The most
                 common use case is to have multi-project builds that share a common lifecycle (e.g. all projects use the
                 Java plugin). If you declare with
@@ -481,26 +467,6 @@
                 build system does not support such patterns, you either can't solve your problem or you need to do ugly
                 hacks which are hard to maintain and massively afflict your productivity as a build master.
             </para>
-            <para>There is one more thing to note from the current example. We have used the command <literal>
-                dependsOnChildren()</literal>. It is a convenience method and calls the
-                <literal>dependsOn</literal>
-                method of the parent project for every child project (not every sub project). It declares a
-                <literal>execution</literal>
-                dependency of
-                <literal>webDist</literal>
-                on
-                <literal>date</literal>
-                and <literal>hello</literal>.
-            </para>
-            <para>Another use case would be a situation where the subprojects have a configuration
-                <emphasis>and</emphasis>
-                execution dependency on the parent project. This is the case when the parent project does configuration
-                injection into its subprojects, and additionally produces something at execution time that is needed by
-                its child projects (e.g. code generation). In this case the parent project would call the
-                <literal>childrenDependOnMe</literal>
-                method to create an execution dependency for the child projects. We might add an example for this in a
-                future version of the user guide.
-            </para>
         </section>
     </section>
     <section id='sec:project_jar_dependencies'>
@@ -580,7 +546,7 @@
         <section id="disable_dependency_projects">
             <title>Disabling the build of dependency projects</title>
             <para>Sometimes you don't want depended on projects to be built when doing a partial build.
-                To disable the build of the depended on projects you can run gradle with the <code>-a</code> option.
+                To disable the build of the depended on projects you can run Gradle with the <code>-a</code> option.
             </para>
         </section>
     </section>
@@ -607,7 +573,7 @@
         <para>While you are working in a typical development cycle repeatedly building and testing changes to
             the :api project (knowing that you are only changing files in this one project), you may not want to
             even suffer the expense of :shared:compile checking to see what has changed in the :shared project.
-            Adding the <literal>-a</literal> option will cause gradle to use cached jars to resolve any project lib
+            Adding the <literal>-a</literal> option will cause Gradle to use cached jars to resolve any project lib
             dependencies and not try to re-build the depended on projects.
         </para>
         <sample id="multitestingBuildDashA" dir="userguide/multiproject/dependencies/java" title="Partial Build and Test Single Project">
@@ -633,9 +599,8 @@
         <sample id="multitestingBuildDependents" dir="userguide/multiproject/dependencies/java" title="Build and Test Dependent Projects">
             <output args=":api:buildDependents"/>
         </sample>
-        <para>Finally, you may want to build and test everything in all projects.  If the root project has declared
-        <literal>dependsOnChildren()</literal> (as this one does), then any task you run against the root project
-        will cause that same named task to be run on all the children.  So you can just run
+        <para>Finally, you may want to build and test everything in all projects. Any task you run in the root project folder
+        will cause that same named task to be run on all the children. So you can just run
         <literal>gradle build</literal> to build and test all projects.
         </para>
     </section>
@@ -659,9 +624,8 @@
         <para>Writing this chapter was pretty exhausting and reading it might have a similar effect. Our final message
             for this chapter is that multi-project builds with Gradle are usually
             <emphasis>not</emphasis>
-            difficult. There are six elements you need to remember: <literal>allprojects</literal>, <literal>
-            subprojects</literal>, <literal>dependsOn</literal>, <literal>childrenDependOnMe</literal>,
-            <literal>dependsOnChildren</literal>
+            difficult. There are five elements you need to remember: <literal>allprojects</literal>, <literal>
+            subprojects</literal>, <literal>evaluationDependsOn</literal>, <literal>evaluationDependsOnChildren</literal>
             and project lib dependencies.
             <footnote>
                 <para>So we are well in the range of the
diff --git a/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml b/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
index 1171a6d..ecaae3c 100644
--- a/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
+++ b/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
@@ -60,7 +60,7 @@
                 reuse that task in multiple places.</para>
         </listitem>
         <listitem>
-            <para><link linkend="custom_plugins">Custom plugins</link>. Put your build build logic into a custom plugin,
+            <para><link linkend="custom_plugins">Custom plugins</link>. Put your build logic into a custom plugin,
                 and apply that plugin to multiple projects. The plugin must be in the classpath of your build script.
                 You can achieve this either by using <link linkend="sec:build_sources"><filename>build sources</filename></link> or
                 by adding an <link linkend="sec:external_dependencies">external library</link> that contains the plugin. 
@@ -108,19 +108,38 @@
     
     <section id='sec:build_sources'>
         <title>Build sources in the <filename>buildSrc</filename> project</title>
-        <para>If you run Gradle, it checks for the existence of a directory called <filename>buildSrc</filename>. Just put
-            your build source code in this directory and stick to the layout convention for a Java/Groovy project (see
-            <xref linkend='javalayout'/>). Gradle then automatically compiles and tests this code and puts it in
-            the classpath of your build script. You don't need to provide any further instruction. This can be a good
-            place to add your custom tasks and plugins.
+        <para>When you run Gradle, it checks for the existence of a directory called <filename>buildSrc</filename>. 
+            Gradle then automatically compiles and tests this code and puts it in the classpath of your build script. 
+            You don't need to provide any further instruction. This can be a good place to add your custom tasks and plugins.
         </para>
         <para>For multi-project builds there can be only one <filename>buildSrc</filename> directory, which has to be
-            in the root project.
+            in the root project directory.
         </para>
-        <para>This is probably good enough for most of the cases. If you need more flexibility, you can provide a
-            <filename>build.gradle</filename> and a <filename>settings.gradle</filename> file in the
-            <filename>buildSrc</filename> directory. If you like, you can even have a multi-project build in there.
+        <para>Listed below is the default build script that Gradle applies to the <filename>buildSrc</filename> project:</para>
+        <figure>
+            <title>Default buildSrc build script</title>
+            <programlisting><xi:include href='../../../../../subprojects/core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt' parse='text'/></programlisting>
+        </figure>
+        <para>
+            This means that you can just put you build source code in this directory and stick to the layout convention for a 
+            Java/Groovy project (see <xref linkend='javalayout'/>).
+        </para>
+        <para>
+            If you need more flexibility, you can provide your own <filename>build.gradle</filename>. Gradle applies the default build script
+            regardless of whether there is one specified. This means you only need to declare the extra things you need. Below is an example.
+            Notice that this example does not need to declare a dependency on the Gradle API, as this is done by the default build script:
         </para>
+        <sample id="customBuildSrcBuild" dir="java/multiproject" title="Custom buildSrc build script">
+            <sourcefile file="buildSrc/build.gradle"/>
+        </sample>
+        <para>
+            The <filename>buildSrc</filename> project can be a multi-project build. This works like any other regular Gradle multi-project build. However,
+            you need to make all of the projects that you wish be on the classpath of the actual build <literal>runtime</literal> dependencies of the root project in 
+            <filename>buildSrc</filename>. You can do this by adding this to the configuration of each project you wish to export:
+        </para>
+        <sample id="multiProjectBuildSrc" dir="multiProjectBuildSrc" includeLocation="true" title="Adding subprojects to the root buildSrc project">
+            <sourcefile file="buildSrc/build.gradle" snippet="addToRootProject"/>
+        </sample>
     </section>
 
     <section id="sec:external_build">
diff --git a/subprojects/docs/src/docs/userguide/overview.xml b/subprojects/docs/src/docs/userguide/overview.xml
index 32d2cd1..066a346 100644
--- a/subprojects/docs/src/docs/userguide/overview.xml
+++ b/subprojects/docs/src/docs/userguide/overview.xml
@@ -143,7 +143,7 @@
             <varlistentry>
                 <term>Free and open source</term>
                 <listitem>
-                    <para>Gradle is an open source project, and is licensed under the <ulink url="website:license.html">ASL</ulink>.
+                    <para>Gradle is an open source project, and is licensed under the <ulink url="website:license">ASL</ulink>.
                     </para>
                 </listitem>
             </varlistentry>
diff --git a/subprojects/docs/src/docs/userguide/plugins.xml b/subprojects/docs/src/docs/userguide/plugins.xml
index c775d80..5a7a4f4 100644
--- a/subprojects/docs/src/docs/userguide/plugins.xml
+++ b/subprojects/docs/src/docs/userguide/plugins.xml
@@ -13,135 +13,145 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='plugins' xmlns:xi="http://www.w3.org/2001/XInclude">
-    <title>Using Plugins</title>
-    <para>Now we look at <emphasis>how</emphasis> Gradle provides build-by-convention and out of the box functionality.
-        These features are decoupled from the core of Gradle, and are provided via plugins. Although the plugins are
-        decoupled, we would like to point out that the Gradle core plugins are NEVER updated or changed for a
-        particular Gradle distribution. If there is a bug in the compile functionality of Gradle, we will release a new
-        version of Gradle. There is no change of behavior for the lifetime of a given distribution of Gradle.
+<chapter id='plugins'>
+    <title>Gradle Plugins</title>
+    <para>
+        Gradle at its core intentionally provides little useful functionality for real world automation. All of the useful
+        features, such as the ability to compile Java code for example, are added by <emphasis>plugins</emphasis>.
+        Plugins add new tasks (e.g. <apilink class='org.gradle.api.tasks.compile.Compile'/>), domain objects (e.g.
+        <apilink class="org.gradle.api.tasks.SourceSet"/>), conventions (e.g. main Java source is located at
+        <literal>src/main/java</literal>) as well as extending core objects and objects from other plugins.
+    </para>
+    <para>
+        In this chapter we will discuss how to use plugins and the terminology and concepts surrounding plugins.
     </para>
     <section id='sec:using_plugins'>
-        <title>Declaring plugins</title>
-        <para>If you want to use the plugin for building a Java project, simply include
+        <title>Applying plugins</title>
+        <para>
+            Plugins are said to be <emphasis>applied</emphasis>, which is done via the <apilink class="org.gradle.api.Project" method="apply(java.util.Map)" /> method.
         </para>
-        <sample id="useJavaPlugin" dir="java/quickstart" title="Using a plugin">
+        <sample id="useJavaPlugin" dir="java/quickstart" title="Applying a plugin">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
-        <para>in your script. That's all. From a technological point of view plugins use just the same
-            operations as you can use from your build scripts. That is, they use the Project and Task API.
-            The Gradle plugins generally use this API to:
-        </para>
-        <itemizedlist>
-            <listitem>
-                <para>Add tasks to the project (e.g. compile, test)
-                </para>
-            </listitem>
-            <listitem>
-                <para>Create dependencies between those tasks to let them execute in the appropriate order.
-                </para>
-            </listitem>
-            <listitem>
-                <para>Add dependency configurations to the project.</para>
-            </listitem>
-            <listitem>
-                <para>Add a so called <firstterm>convention object</firstterm> to the project.
-                </para>
-            </listitem>
-        </itemizedlist>
-        <para>Let's check this out:
-        </para>
-        <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Applying a plugin by id">
-            <sourcefile file="build.gradle" snippet="apply-by-id"/>
-            <output args="-q show"/>
-        </sample>
-        <para>The Java plugin adds a <literal>compileJava</literal> task and a <literal>processResources</literal> task
-            to the project object which can be accessed by a build script. It has configured the <literal>destinationDir</literal>
-            property of both of these tasks.
+        <para>
+            Plugins advertise a short name for themselves. In the above case, we are using the short name ‘<literal>java</literal>’ to apply
+            the <apilink class="org.gradle.api.plugins.JavaPlugin" />.
         </para>
-        <para>The <literal>apply()</literal> method either takes a string or a class as an argument. You can write
+        <para>
+            We could also have used the following syntax:
         </para>
         <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Applying a plugin by type">
             <sourcefile file="build.gradle" snippet="apply-by-type"/>
         </sample>
-        <para>Thanks to Gradle's default imports (see <xref linkend='ide_support'/>) you can also write in this case.
+        <para>
+            Thanks to Gradle's default imports (see <xref linkend='ide_support'/>) you could also write:
         </para>
         <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Applying a plugin by type">
             <sourcefile file="build.gradle" snippet="apply-by-type-with-import"/>
         </sample>
-        <para>Any class, which implements the <apilink class="org.gradle.api.Plugin"/> interface, can be used as a
-            plugin. Just pass the class as an argument. You don't need to configure anything else for this.</para>
-        <para>If you want to use your own plugins, you must make sure that they are accessible via the build script
-            classpath (see <xref linkend='organizing_build_logic'/> for more information). To learn more about how
-            to write custom plugins, see <xref linkend='custom_plugins'/>.
+        <para>
+            The application of plugins is <emphasis>idempotent</emphasis>. That is, a plugin can be applied multiple times. If the plugin
+            has previously been applied, any further applications will have no effect.
+        </para>
+        <para>
+            A plugin is simply any class that implements the <apilink class="org.gradle.api.Plugin"/> interface. Gradle provides
+            the core plugins as part of its distribution so simply applying the plugin as above is all you need to do.
+            For 3rd party plugins however, you need to make the plugins available to the build classpath. For more information
+            on how to do this, see <xref linkend="sec:external_dependencies" />.
+        </para>
+        <para>
+            For more on writing your own plugins, see <xref linkend="custom_plugins" />.
         </para>
     </section>
-    <section id='sub:more_about_convention_objects'>
-        <title>Using the convention object</title>
-        <para>If you use the Java plugin
-            for example, there are a <literal>compileJava</literal> and a <literal>processResources</literal> task for
-            your production code (the same is true for your test
-            code). The default location for the output of those tasks is the directory <literal>build/classes/main</literal>.
-            What if you want to change this? Let's try:
-        </para>
-        <sample id="pluginConfig" dir="userguide/tutorial/pluginConfig" title="Configuring a plugin task">
-            <sourcefile file="build.gradle"/>
+    <section>
+        <title>What plugins do</title>
+        <para>
+            Applying a plugin to the project allows the plugin to extend the project's capabilities. It can do things
+            such as:
+        </para>
+        <itemizedlist>
+            <listitem>Add tasks to the project (e.g. compile, test)</listitem>
+            <listitem>Pre-configure added tasks with useful defaults.</listitem>
+            <listitem>Add dependency configurations to the project (see <xref linkend="configurations"/>).</listitem>
+            <listitem>Add new properties and methods to existing type via extensions.</listitem>
+        </itemizedlist>
+        <para>
+            Let's check this out:
+        </para>
+        <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Tasks added by a plugin">
+            <sourcefile file="build.gradle" snippet="apply-by-id"/>
             <output args="-q show"/>
         </sample>
-        <para>Setting the <literal>destinationDir</literal>
-            of the <literal>processResources</literal> task had only an effect on the <literal>processResources</literal>
-            task. Maybe this was what you wanted. But what if
-            you want to change the output directory for all tasks? It would be unfortunate if you had to do this for
-            each task separately.
+        <para>
+            The Java plugin has added a <literal>compileJava</literal> task and a <literal>processResources</literal> task
+            to the project and configured the <literal>destinationDir</literal> property of both of these tasks.
         </para>
-        <para>Gradle's tasks are usually <firstterm>convention aware</firstterm>. A plugin can add a convention object to
-            your project, and map certain values of this convention object to task properties.
+    </section>
+    <section id='sub:more_about_convention_objects'>
+        <title>Conventions</title>
+        <para>
+            Plugins can pre-configure the project in smart ways to support convention-over-configuration. Gradle
+            provides mechanisms and sophisticated support and it's a key ingredient in powerful-yet-concise build scripts.
         </para>
-        <sample id="pluginConvention" dir="userguide/tutorial/pluginConvention" title="Plugin convention object">
+        <para>
+            We saw in the example above that the Java plugins adds a task named <literal>compileJava</literal> that has
+            a property named <literal>destinationDir</literal> (that configures where the compiled Java source should be placed).
+            The Java plugin defaults this property to point to <literal>build/classes/main</literal> in the project directory.
+            This is an example of convention-over-configuration via a <emphasis>reasonable default</emphasis>.
+        </para>
+        <para>
+            We can change this property simply by giving it a new value.
+        </para>
+        <sample id="pluginConfig" dir="userguide/tutorial/pluginConfig" title="Changing plugin defaults">
             <sourcefile file="build.gradle"/>
             <output args="-q show"/>
         </sample>
-        <para>The Java plugin has added a convention object with a <literal>sourceSets</literal>
-            property, which we use to set the classes directory. Notice that setting this has changed the <literal>destinationDir</literal>
-            property of both the <literal>processResources</literal> and
-            <literal>compileJava</literal> tasks.</para>
-        <para>By setting a task attribute explicitly (as we have done in the first example) you overwrite the convention
-            value for this particular task.
+        <para>
+            However, the <literal>compileJava</literal> task is likely to not be the only task that needs to know where
+            the class files are. 
         </para>
-        <para>Not all of the tasks attributes are mapped to convention object values. It is the decision of the plugin
-            to decide what are the shared properties and then bundle them in a convention object and map them to the
-            tasks.
+        <para>
+            The Java plugin adds the concept of <emphasis>source sets</emphasis> (see <apilink class="org.gradle.api.tasks.SourceSet" />)
+            to describe the aspects of a set of source, one aspect being where the class files should be written to when it is compiled.
+            The Java plugin maps the <literal>destinationDir</literal> property of the <literal>compileJava</literal> task to this aspect of the source set.
         </para>
         <para>
-            The properties of a convention object can be accessed as project properties. As shown in the following
-            example, you can also access the convention object explicitly.
+            We can change where the class files are written via the source set.
         </para>
-        <sample id="pluginAccessConvention" dir="userguide/tutorial/pluginAccessConvention" title="Using the plugin convention object">
+        <sample id="pluginConvention" dir="userguide/tutorial/pluginConvention" title="Plugin convention object">
             <sourcefile file="build.gradle"/>
             <output args="-q show"/>
         </sample>
-        <para>Every project object has a <apilink class="org.gradle.api.plugins.Convention"/> object which is a
-            container for convention objects contributed
-            by the plugins declared for your project. If you simply access or set a property or access a method in
-            your build script, the project object first looks if this is a property of itself. If not, it delegates
-            the request to its convention object. The convention object checks if any of the plugin convention
-            objects can fulfill the request (first wins and the order is not defined). The plugin convention objects
-            also introduce a namespace.
-        </para>
-        <section id='sub:declaring_plugins_multiple_times'>
-            <title>Declaring plugins multiple times</title>
-            <para>A plugin is only called once for a given project, even if you have multiple
-                <literal>apply()</literal>
-                statements. An additional call after the first call has no effect but doesn't hurt either. This can be
-                important if you use plugins which extend other plugins. For example
-                the Groovy plugin automatically applies the Java plugin. We say the Groovy plugin extends the Java plugin. But you might as well
-                write:
-            </para>
-            <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Explicit application of an implied plugin">
-                <sourcefile file="build.gradle" snippet="explicit-apply"/>
-            </sample>
-            <para>If you use cross-project configuration in multi-project builds this is a useful feature.
-            </para>
-        </section>
+        <para>
+            In the above example, we applied the Java plugin which, among other things, did the following:
+        </para>
+        <itemizedlist>
+            <listitem>Added a new domain object type: <apilink class="org.gradle.api.tasks.SourceSet" /></listitem>
+            <listitem>Configured a <literal>main</literal> source set with default (i.e. conventional) values for properties</listitem>
+            <listitem>Configured supporting tasks to use these properties to perform work</listitem>
+        </itemizedlist>
+        <para>
+            All of this happened during the <literal>apply plugin: "java"</literal> step. In the example above we <emphasis>changed</emphasis>
+            the desired location of the class files after this conventional configuration had been performed. Notice by the output with the example
+            that the value for <literal>compileJava.destinationDir</literal> also changed to reflect the configuration change.
+        </para>
+        <para>
+            Consider the case where another task is to consume the classes files. If this task is configured to use the value from
+            <literal>sourceSets.main.output.classesDir</literal>, then changing it in this location will update both the
+            <literal>compileJava</literal> task and this other consumer task whenever it is changed.
+        </para>
+        <para>
+            This ability to configure properties of objects to reflect the value of another object's task at all times (i.e. even when it changes) is
+            known as “<emphasis>convention mapping</emphasis>”. It allows Gradle to provide conciseness through convention-over-configuration and
+            sensible defaults yet not require complete reconfiguration if a conventional default needs to be changed. Without this, in the above example
+            we would have had to reconfigure every object that needs to work with the class files.
+        </para>
+    </section>
+    <section>
+        <title>More on plugins</title>
+        <para>
+            This chapter aims to serve as an introduction to plugins and Gradle and the role they play. For more information on the inner workings
+            of plugins, see <xref linkend="custom_plugins" />.
+        </para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/pmdPlugin.xml b/subprojects/docs/src/docs/userguide/pmdPlugin.xml
new file mode 100644
index 0000000..b7e743a
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/pmdPlugin.xml
@@ -0,0 +1,94 @@
+<chapter id="pmd_plugin">
+    <title>The PMD Plugin</title>
+    <para>The PMD plugin performs quality checks on your project's Java source files using <ulink url="http://pmd.sourceforge.net">PMD</ulink>
+        and generates reports from these checks.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the PMD plugin, include in your build script:</para>
+        <sample id="usePmdPlugin" dir="codeQuality" title="Using the PMD plugin">
+            <sourcefile file="build.gradle" snippet="use-pmd-plugin"/>
+        </sample>
+        <para>The plugin adds a number of tasks to the project that perform the quality checks. You can execute the checks by running <userinput>gradle check</userinput>.</para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The PMD plugin adds the following tasks to the project:</para>
+        <table>
+            <title>PMD plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>pmdMain</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.Pmd"/></td>
+                <td>Runs PMD against the production Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>pmdTest</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.Pmd"/></td>
+                <td>Runs PMD against the test Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>pmd<replaceable>SourceSet</replaceable></literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.Pmd"/></td>
+                <td>Runs PMD against the given source set's Java source files.</td>
+            </tr>
+        </table>
+
+        <para>The PMD plugin adds the following dependencies to tasks defined by the Java plugin.</para>
+        <table>
+            <title>PMD plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td><literal>check</literal></td>
+                <td>All PMD tasks, including <literal>pmdMain</literal> and <literal>pmdTest</literal>.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The PMD plugin adds the following dependency configurations:</para>
+        <table>
+            <title>PMD plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>pmd</filename>
+                </td>
+                <td>The PMD libraries to use</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Configuration</title>
+        <para>See <apilink class="org.gradle.api.plugins.quality.PmdExtension"/>.</para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/potentialTraps.xml b/subprojects/docs/src/docs/userguide/potentialTraps.xml
index a21bea1..8f1d7d3 100644
--- a/subprojects/docs/src/docs/userguide/potentialTraps.xml
+++ b/subprojects/docs/src/docs/userguide/potentialTraps.xml
@@ -28,7 +28,7 @@
             methods. This is a heavily discussed behavior in the Groovy community.
             <footnote>
                 <para>One of those discussions can be found here:
-                    <ulink url='http://www.nabble.com/script-scoping-question-td16034724.html'/>
+                    <ulink url='http://groovy.329449.n5.nabble.com/script-scoping-question-td355887.html'/>
                 </para>
             </footnote>
         </para>
diff --git a/subprojects/docs/src/docs/userguide/signingPlugin.xml b/subprojects/docs/src/docs/userguide/signingPlugin.xml
new file mode 100644
index 0000000..4f56ddd
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/signingPlugin.xml
@@ -0,0 +1,181 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id='signing_plugin'>
+    <title>The Signing Plugin</title>
+
+    <para>
+        The signing plugin adds the ability to digitally sign built files and artifacts.
+        These digital signatures can then be used to prove who built the artifact the signature is attached to
+        as well as other information such as when the signature was generated.
+    </para>
+    <para>
+        The signing plugin currently only provides support for generating <ulink url='http://www.pgpi.org/'>PGP signatures</ulink> 
+        (which is the signature format <ulink url="https://docs.sonatype.org/display/Repository/Central+Sync+Requirements">required for 
+        publication to the Maven Central Repository</ulink>).
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Signing plugin, include in your build script:</para>
+        <sample id="useSigningPlugin" dir="signing/maven" title="Using the Signing plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+    
+    <section>
+        <title>Signatory credentials</title>
+        <para>
+            In order to create PGP signatures, you will need a key pair (instructions on creating a key pair using the <ulink url="http://www.gnupg.org/">GnuPG tools</ulink> 
+            can be found in the <ulink url="http://www.gnupg.org/documentation/howtos.html">GnuPG HOWTOs</ulink>). You need to provide the signing plugin
+            with your key information, which means three things: 
+        </para>
+        <itemizedlist>
+            <listitem><para>The public key ID (an 8 character hexadecimal string).</para></listitem>
+            <listitem><para>The absolute path to the secret key ring file containing your private key.</para></listitem>
+            <listitem><para>The passphrase used to protect your private key.</para></listitem>
+        </itemizedlist>
+        <para>
+            These items must be supplied as the property projects <literal>signing.keyId</literal>, <literal>signing.password</literal> 
+            and <literal>signing.secretKeyRingFile</literal> respectively. Given the personal and private nature of these values, a good practice
+            is to store them in the user <literal>gradle.properties</literal> file (described in <xref linkend="sec:gradle_properties_and_system_properties"/>).
+        </para>
+        <programlisting>
+            signing.keyId=24875D73
+signing.password=secret
+signing.secretKeyRingFile=/Users/me/.gnupg/secring.gpg
+        </programlisting>
+        <para>
+            If specifying this information in the user <literal>gradle.properties</literal> file is not feasible for your environment, you can source the information
+            however you need to and set the project properties manually.
+        </para>
+        <!--
+            Unsure how to go about automatically testing this little snippet.
+        -->
+        <programlisting>
+import org.gradle.plugins.signing.Sign
+
+gradle.taskGraph.whenReady { taskGraph ->
+    if (taskGraph.allTasks.any { it instanceof Sign }) {
+        // Use Java 6's console to read from the console (no good for a CI environment)
+        Console console = System.console()
+        console.printf "\n\nWe have to sign some things in this build.\n\nPlease enter your signing details.\n\n"
+
+        def id = console.readLine("PGP Key Id: ")
+        def file = console.readLine("PGP Secret Key Ring File (absolute path): ")
+        def password = console.readPassword("PGP Private Key Password: ")
+
+        allprojects { ext."signing.keyId" = id }
+        allprojects { ext."signing.secretKeyRingFile" = file }
+        allprojects { ext."signing.password" = password }
+
+        console.printf "\nThanks.\n\n"
+    }
+}
+        </programlisting>
+    </section>
+
+    <section>
+        <title>Specifying what to sign</title>
+        <para>
+            As well as configuring how things are to be signed (i.e. the signatory configuration), you must also specify what is to be signed. 
+            The Signing plugin provides a DSL that allows you to specify the tasks and/or configurations that should be signed.
+        </para>
+        <section>
+            <title>Signing Configurations</title>
+            <para>
+                It is common to want to sign the artifacts of a configuration. For example, the <link linkend="java_plugin">Java plugin</link> 
+                configures a jar to built and this jar artifact is added to the <literal>archives</literal> configuration. 
+                Using the Signing DSL, you can specify that all of the artifacts of this configuration should be signed.
+            </para>
+            <sample id="signingArchives" dir="signing/maven" title="Signing a configuration">
+                <sourcefile file="build.gradle" snippet="sign-archives"/>
+            </sample>
+            <para>
+                This will create a task (of type <apilink class="org.gradle.plugins.signing.Sign"/>) in your project named “<literal>signArchives</literal>”, 
+                that will build any <literal>archives</literal> artifacts (if needed) and then generate signatures for them. The signature files will be placed
+                alongside the artifacts being signed.
+            </para>
+            <sample id="signingArchivesOutput" dir="signing/maven" title="Signing a configuration output">
+                <output args="signArchives"/>
+            </sample>
+        </section>
+        <section>
+            <title>Signing Tasks</title>
+            <para>
+                In some cases the artifact that you need to sign may not be part of a configuration. In this case you can directly sign the task that
+                produces the artifact to sign.
+            </para>
+            <sample id="signingTasks" dir="signing/tasks" title="Signing a task">
+                <sourcefile file="build.gradle" snippet="sign-task"/>
+            </sample>
+            <para>
+                This will create a task (of type <apilink class="org.gradle.plugins.signing.Sign"/>) in your project named “<literal>signStuffZip</literal>”, 
+                that will build the input task's archive (if needed) and then sign it. The signature file will be placed
+                alongside the artifact being signed.
+            </para>
+            <sample id="signingTaskOutput" dir="signing/tasks" title="Signing a task output">
+                <output args="signStuffZip"/>
+            </sample>
+            <para>
+                For a task to be “signable”, it must produce an archive of some type. Tasks that do this are the <apilink class="org.gradle.api.tasks.bundling.Tar"/>,
+                <apilink class="org.gradle.api.tasks.bundling.Zip"/>, <apilink class="org.gradle.api.tasks.bundling.Jar"/>, 
+                <apilink class="org.gradle.api.tasks.bundling.War"/> and <apilink class="org.gradle.plugins.ear.Ear"/> tasks.
+            </para>
+        </section>
+        <section>
+            <title>Conditional Signing</title>
+            <para>
+                A common usage pattern is to only sign build artifacts under certain conditions. For example, you may not wish to sign artifacts for non release versions.
+                To achieve this, you can specify that signing is only required under certain conditions.
+            </para>
+            <sample id="conditionalSigning" dir="signing/conditional" title="Conditional signing">
+                <sourcefile file="build.gradle" snippet="conditional-signing" />
+            </sample>
+            <para>
+                In this example, we only want to require signing if we are building a release version and we are going to publish it. Because we are inspecting the task
+                graph to determine if we are going to be publishing, we must set the <literal>signing.required</literal> property to a closure to defer the evaluation. See
+                <apilink class="org.gradle.plugins.signing.SigningExtension" method="setRequired" /> for more information.
+            </para>
+        </section>
+    </section>
+    
+    <section>
+        <title>Publishing the signatures</title>
+        <para>
+            When specifying what is to be signed via the Signing DSL, the resultant signature artifacts are automatically added to the <literal>signatures</literal> and
+            <literal>archives</literal> dependency configurations. This means that if you want to upload your signatures to your distribution repository along
+            with the artifacts you simply execute the <literal>uploadArchives</literal> task as normal.
+        </para>
+    </section>
+    
+    <section>
+        <title>Signing POM files</title>
+        <para>
+            When deploying signatures for your artifacts to a Maven repository, you will also want to sign the published POM file. The signing plugin adds a
+            <literal>signing.signPom()</literal> (see: <apilink class="org.gradle.plugins.signing.SigningExtension" method="signPom" />) method that can be used in the
+            <literal>beforeDeployment()</literal> block in your upload task configuration.
+        </para>
+        <sample id="signingMavenPom" dir="signing/maven" title="Signing a POM for deployment">
+            <sourcefile file="build.gradle" snippet="sign-pom"/>
+        </sample>
+        <para>
+            When signing is not required and the pom cannot be signed due to insufficient configuration (i.e. no credentials for signing) then the
+            <literal>signPom()</literal> method will silently do nothing.
+        </para>
+    </section>
+    
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/sonarPlugin.xml b/subprojects/docs/src/docs/userguide/sonarPlugin.xml
index 460704e..ce430bc 100644
--- a/subprojects/docs/src/docs/userguide/sonarPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/sonarPlugin.xml
@@ -17,34 +17,117 @@
     <title>The Sonar Plugin</title>
 
     <para>The Sonar plugin provides integration with <ulink url="http://www.sonarsource.org">Sonar</ulink>,
-        a web-based platform for monitoring code quality. The plugin adds a <literal>sonar</literal> task
-        to the project, which analyzes the project's source code and stores the results in Sonar's database.
+        a web-based platform for monitoring code quality. The plugin adds a <literal>sonarAnalyze</literal> task
+        that analyzes the project to which the plugin is applied and its subprojects. The results are stored in
+        the Sonar database. The plugin requires Sonar 2.9 or higher.
     </para>
 
     <para>
-        The <literal>sonar</literal> task is a standalone task that needs to be executed explicitly. By default,
-        it gets configured to analyze the Java code in the main source set. In a typical setup, the task would
-        be run once per day on a build server.
-    </para>
-
-    <para>
-        Only projects which have the Java plugin applied (possibly by way of another plugin) are affected by the
-        Sonar plugin. Other projects can still declare a task of type <apilink class="org.gradle.api.plugins.sonar.Sonar"/>
-        and configure it manually.
+        The <literal>sonarAnalyze</literal> task is a standalone task that needs to be executed explicitly
+        and doesn't depend on any other tasks. Apart from source code, the task also analyzes class files
+        and test result files (if available). For best results, it is therefore recommended to run a full
+        build before the analysis. In a typical setup, analysis would be performed once per day on a build server.
     </para>
 
     <section>
         <title>Usage</title>
         <para>At a minimum, the Sonar plugin has to be applied to the project.</para>
-        <sample id="useSonarPlugin" dir="sonar" title="Using the Sonar plugin">
-            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        <sample id="quickstart" dir="sonar/quickstart" title="Applying the Sonar plugin">
+            <sourcefile file="build.gradle" snippet="apply-plugin"/>
         </sample>
-        <para>Typically, it is also necessary to configure connection settings for the Sonar server and database.</para>
-        <sample id="useSonarPlugin" dir="sonar" title="Configuring connection settings">
+        <para>Unless Sonar is run locally and with default settings, it is also necessary to configure
+            connection settings for the Sonar server and database.</para>
+        <sample id="quickstart" dir="sonar/quickstart" title="Configuring Sonar connection settings">
             <sourcefile file="build.gradle" snippet="connection-settings"/>
         </sample>
-        <para>For a complete documentation of all Sonar-specific configuration properties, see the
-            <ulink url= "http://docs.codehaus.org/display/SONAR/Advanced+parameters">Sonar documentation</ulink>.
+        <para>Project settings determine how the project is going to be analyzed. The default configuration
+            works well for analyzing standard Java projects and can be customized in many ways.</para>
+        <sample id="quickstart" dir="sonar/quickstart" title="Configuring Sonar project settings">
+            <sourcefile file="build.gradle" snippet="project-settings"/>
+        </sample>
+        <para>
+            The <literal>sonar</literal>, <literal>server</literal>, <literal>database</literal>, and <literal>project</literal>
+            blocks in the examples above configure objects of type <apilink class="org.gradle.api.plugins.sonar.model.SonarRootModel"/>,
+            <apilink class="org.gradle.api.plugins.sonar.model.SonarServer"/>, <apilink class="org.gradle.api.plugins.sonar.model.SonarDatabase"/>,
+            and <apilink class="org.gradle.api.plugins.sonar.model.SonarProject"/>, respectively. See their API documentation for further information.
+        </para>
+    </section>
+
+    <section>
+        <title>Analyzing Multi-Project Builds</title>
+        <para>The Sonar plugin is capable of analyzing a whole project hierarchy at once. This yields a hierarchical view in the
+            Sonar web interface with aggregated metrics and the ability to drill down into subprojects. It is also faster and
+            less likely to run into out-of-memory problems than analyzing each project separately.
+        </para>
+        <para>
+            To analyze a project hierarchy, the Sonar plugin needs to be applied to the top-most project of the hierarchy.
+            Typically (but not necessarily) this will be the root project. The <literal>sonar</literal> block
+            in that project configures an object of type <apilink class="org.gradle.api.plugins.sonar.model.SonarRootModel"/>.
+            It holds all global configuration, most importantly server and database connection settings.
+        </para>
+        <sample id="multiProject" dir="sonar/multiProject" title="Global configuration in a multi-project build">
+            <sourcefile file="build.gradle" snippet="global-configuration"/>
+        </sample>
+        <para>Each project in the hierarchy has its own project configuration. Common values can be set from a parent build script.</para>
+        <sample id="multiProject" dir="sonar/multiProject" title="Common project configuration in a multi-project build">
+            <sourcefile file="build.gradle" snippet="common-project-configuration"/>
+        </sample>
+        <para>The <literal>sonar</literal> block in a subproject configures an object of type <apilink class="org.gradle.api.plugins.sonar.model.SonarProjectModel"/>.</para>
+        <para>
+            Projects can also be configured individually. For example, setting the <literal>skip</literal> property to <literal>true</literal>
+            prevents a project (and its subprojects) from being analyzed. Skipped projects will not be displayed in the Sonar web interface.
+        </para>
+        <sample id="multiProject" dir="sonar/multiProject" title="Individual project configuration in a multi-project build">
+            <sourcefile file="build.gradle" snippet="individual-project-configuration"/>
+        </sample>
+        <para>
+            Another typical per-project configuration is the programming language to be analyzed. Note that Sonar can only analyze one language per project.
+        </para>
+        <sample id="multiProject" dir="sonar/multiProject" title="Configuring the language to be analyzed">
+            <sourcefile file="build.gradle" snippet="language-configuration"/>
+        </sample>
+        <para>
+            When setting only a single property at a time, the equivalent property syntax is more succinct:
+        </para>
+        <sample id="multiProject" dir="sonar/multiProject" title="Using property syntax">
+            <sourcefile file="build.gradle" snippet="property-syntax"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Analyzing Custom Source Sets</title>
+        <para>By default, the Sonar plugin will analyze the production sources in the <literal>main</literal> source set and
+            the test sources in the <literal>test</literal> source set. This works independent of the project's source directory layout.
+            Additional source sets can be added as needed.
+        </para>
+        <sample id="advanced" dir="sonar/advanced" title="Analyzing custom source sets">
+            <sourcefile file="build.gradle" snippet="source-sets"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Setting Custom Sonar Properties</title>
+        <para>
+            Eventually, most configuration is passed to the Sonar code analyzer in the form of key-value pairs known as Sonar properties.
+            The <apilink class="org.gradle.api.plugins.sonar.model.SonarProperty"/> annotations in the API documentation show how properties
+            of the plugin's object model get mapped to the corresponding Sonar properties. The Sonar plugin offers hooks to post-process Sonar
+            properties before they get passed to the code analyzer. The same hooks can be used to add additional properties which aren't covered
+            by the plugin's object model.
+        </para>
+
+        <para>For global Sonar properties, use the <code>withGlobalProperties</code> hook on <apilink class="org.gradle.api.plugins.sonar.model.SonarRootModel"/>:</para>
+        <sample id="advanced" dir="sonar/advanced" title="Setting custom global properties">
+            <sourcefile file="build.gradle" snippet="global-properties"/>
+        </sample>
+
+        <para>For per-project Sonar properties, use the <code>withProjectProperties</code> hook on <apilink class="org.gradle.api.plugins.sonar.model.SonarProject"/>:</para>
+        <sample id="advanced" dir="sonar/advanced" title="Setting custom project properties">
+            <sourcefile file="build.gradle" snippet="project-properties"/>
+        </sample>
+
+        <para>
+            The Sonar documentation provides a complete list of Sonar properties. (Note that most of these properties are already covered
+            by the plugin's object model.) For configuring third-party Sonar plugins, consult their documentation.
         </para>
     </section>
 
@@ -62,22 +145,11 @@
                 </tr>
             </thead>
             <tr>
-                <td><literal>sonar</literal></td>
+                <td><literal>sonarAnalyze</literal></td>
                 <td>-</td>
-                <td><apilink class="org.gradle.api.plugins.sonar.Sonar"/></td>
-                <td>Analyzes the project's source code and stores results in Sonar's database.</td>
+                <td><apilink class="org.gradle.api.plugins.sonar.SonarAnalyze"/></td>
+                <td>Analyzes a project hierarchy and stores the results in the Sonar database.</td>
             </tr>
         </table>
     </section>
-
-    <section>
-        <title>Limitations</title>
-        <itemizedlist>
-            <listitem>
-                <para>The projects of a multi-project build are currently analyzed independently. This means that no
-                    aggregated view will be available.
-                </para>
-            </listitem>
-        </itemizedlist>
-    </section>
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/standardPlugins.xml b/subprojects/docs/src/docs/userguide/standardPlugins.xml
index 67bc013..6c73a10 100644
--- a/subprojects/docs/src/docs/userguide/standardPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/standardPlugins.xml
@@ -75,6 +75,51 @@
         </table>
     </section>
     <section>
+        <title>Experimental language plugins</title>
+        <para>These experimental plugins add support for various languages:</para>
+        <table>
+            <title>Language plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend='cpp'><literal>cpp</literal></link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Adds C++ source compilation capabilities to a project.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='cpp'><literal>cpp-exe</literal></link>
+                </td>
+                <td><literal>cpp</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds C++ executable compilation and linking capabilities to a project.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='cpp'><literal>cpp-lib</literal></link>
+                </td>
+                <td><literal>cpp</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds C++ library compilation and linking capabilities to a project.</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
         <title>Integration plugins</title>
         <para>These plugins provide some integration with various build and runtime technologies.</para>
         <table>
@@ -101,8 +146,8 @@
                 <td>
                     <link linkend='application_plugin'><literal>application</literal></link>
                 </td>
-                <td><literal>-</literal></td>
-                <td><literal>java</literal>, <literal>groovy</literal></td>
+                <td><literal>java</literal></td>
+                <td>-</td>
                 <td>
                     <para>Adds tasks for running and bundling a project as application.
                     </para>
@@ -110,6 +155,26 @@
             </tr>
             <tr>
                 <td>
+                    <link linkend='build_announcements_plugin'><literal>build-announcements</literal></link>
+                </td>
+                <td>announce</td>
+                <td>-</td>
+                <td>
+                    <para>Sends local announcements to your desktop about interesting events in the build lifecycle.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='ear_plugin'><literal>ear</literal></link>
+                </td>
+                <td>-</td>
+                <td><literal>java</literal></td>
+                <td>
+                    <para>Adds support for building J2EE applications.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend='jetty_plugin'><literal>jetty</literal></link>
                 </td>
                 <td><literal>war</literal></td>
@@ -165,12 +230,25 @@
             </thead>
             <tr>
                 <td>
-                    <link linkend='code_quality_plugin'><literal>code-quality</literal></link>
+                    <link linkend='checkstyle_plugin'><literal>checkstyle</literal></link>
                 </td>
-                <td><literal>reporting-base</literal></td>
-                <td><literal>java</literal>, <literal>groovy</literal></td>
+                <td><literal>java-base</literal></td>
+                <td>-</td>
                 <td>
-                    <para>Performs code quality checks and generate reports from these checks.
+                    <para>Performs quality checks on your project's Java source files using <ulink url="http://checkstyle.sourceforge.net/index.html">Checkstyle</ulink>
+                        and generates reports from these checks.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='codenarc_plugin'><literal>codenarc</literal></link>
+                </td>
+                <td><literal>groovy-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Performs quality checks on your project's Groovy source files using <ulink url="http://codenarc.sourceforge.net/index.html">CodeNarc</ulink>
+                        and generates reports from these checks.
                     </para>
                 </td>
             </tr>
@@ -179,7 +257,7 @@
                     <link linkend='eclipse_plugin'><literal>eclipse</literal></link>
                 </td>
                 <td>-</td>
-                <td><literal>java</literal>, <literal>groovy</literal>, <literal>scala</literal>, <literal>war</literal></td>
+                <td><literal>java</literal>, <literal>groovy</literal>, <literal>scala</literal></td>
                 <td>
                     <para>Generates files that are used by <ulink url="http://eclipse.org">Eclipse IDE</ulink>, thus making
                         it possible to import the project into Eclipse. See also <xref linkend="tutorial_java_projects"/>.
@@ -188,6 +266,31 @@
             </tr>
             <tr>
                 <td>
+                    <link linkend='eclipse_plugin'><literal>eclipse-wtp</literal></link>
+                </td>
+                <td>-</td>
+                <td><literal>ear</literal>, <literal>war</literal></td>
+                <td>
+                    <para>Does the same as the eclipse plugin plus generates eclipse WTP (Web Tools Platform) configuration files.
+                        After importing to eclipse your war/ear projects should be configured to work with WTP.
+                        See also <xref linkend="tutorial_java_projects"/>.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='findbugs_plugin'><literal>findbugs</literal></link>
+                </td>
+                <td><literal>java-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Performs quality checks on your project's Java source files using <ulink url="http://findbugs.sourceforge.net">FindBugs</ulink>
+                        and generates reports from these checks.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend='idea_plugin'><literal>idea</literal></link>
                 </td>
                 <td>-</td>
@@ -200,6 +303,30 @@
             </tr>
             <tr>
                 <td>
+                    <link linkend='jdepend_plugin'><literal>jdepend</literal></link>
+                </td>
+                <td><literal>java-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Performs quality checks on your project's source files using <ulink url="http://clarkware.com/software/JDepend.html">JDepend</ulink>
+                        and generates reports from these checks.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='pmd_plugin'><literal>pmd</literal></link>
+                </td>
+                <td><literal>java-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Performs quality checks on your project's Java source files using <ulink url="http://pmd.sourceforge.net">PMD</ulink>
+                        and generates reports from these checks.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend='project_reports_plugin'><literal>project-report</literal></link>
                 </td>
                 <td><literal>reporting-base</literal></td>
@@ -211,6 +338,17 @@
             </tr>
             <tr>
                 <td>
+                    <link linkend='signing_plugin'> <literal>signing</literal> </link>
+                </td>
+                <td>base</td>
+                <td>-</td>
+                <td>
+                    <para>Adds the ability to digitally sign built files and artifacts.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
                     <link linkend='sonar_plugin'> <literal>sonar</literal> </link>
                 </td>
                 <td>-</td>
@@ -239,7 +377,26 @@
             </thead>
             <tr>
                 <td>base</td>
-                <td><para>Adds the standard lifecycle tasks to the project, plus some shared convention properties.</para></td>
+                <td><para>Adds the standard lifecycle tasks and configures reasonable defaults for the archive tasks:
+                    <itemizedlist>
+                        <listitem>adds build<replaceable>ConfigurationName</replaceable> tasks.
+                            Those tasks assemble the artifacts belonging to the specified configuration.
+                        </listitem>
+                        <listitem>adds upload<replaceable>ConfigurationName</replaceable> tasks.
+                            Those tasks assemble and upload the artifacts belonging to the specified configuration.
+                        </listitem>
+                        <listitem>configures reasonable default values for all archive tasks (e.g. tasks that inherit from
+                            <apilink class="org.gradle.api.tasks.bundling.AbstractArchiveTask"/>).
+                            For example, the archive tasks are tasks of types: <apilink class="org.gradle.api.tasks.bundling.Jar"/>,
+                            <apilink class="org.gradle.api.tasks.bundling.Tar"/>, <apilink class="org.gradle.api.tasks.bundling.Zip"/>.
+                            Specifically, <literal>destinationDir</literal>, <literal>baseName</literal> and <literal>version</literal>
+                            properties of the archive tasks are preconfigured with defaults.
+                            This is extremely useful because it drives consistency across projects;
+                            the consistency regarding naming conventions of archives and their location after the build completed.
+                        </listitem>
+                    </itemizedlist>
+                    </para>
+                </td>
             </tr>
             <tr>
                 <td>java-base</td>
@@ -262,7 +419,7 @@
     <section>
         <title>Third party plugins</title>
         <para>You can find a list of external plugins on the
-            <ulink url="http://gradle.codehaus.org/Plugins">wiki</ulink>.
+            <ulink url="http://wiki.gradle.org/display/GRADLE/Plugins">wiki</ulink>.
         </para>
     </section>
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/tasks.xml b/subprojects/docs/src/docs/userguide/tasks.xml
index 07c499f..b98232a 100644
--- a/subprojects/docs/src/docs/userguide/tasks.xml
+++ b/subprojects/docs/src/docs/userguide/tasks.xml
@@ -91,7 +91,7 @@
         </sample>
         <para>You might know this approach from the Hibernates Criteria Query API or JMock. Of course the API of a task
             has to support this. The <literal>from</literal>, <literal>to</literal> and <literal>include</literal>
-            methods all return an object that may be used to chain to additional configuration methods. Gradle's build-in tasks usually
+            methods all return an object that may be used to chain to additional configuration methods. Gradle's built-in tasks usually
             support this configuration style.
         </para>
         <para>But there is yet another way of configuring a task. It also preserves the context and it is arguably the
@@ -234,7 +234,7 @@
             you might have noticed that Gradle will skip tasks that are up-to-date. This behaviour is also available
             for your tasks, not just for built-in tasks.
         </para>
-        <section>
+        <section id="sec:task_inputs_outputs">
             <title>Declaring a task's inputs and outputs</title>
             <para>
                 Let's have a look at an example. Here our task generates several output files from a source XML file. Let's
diff --git a/subprojects/docs/src/docs/userguide/thisAndThat.xml b/subprojects/docs/src/docs/userguide/thisAndThat.xml
index 56d57ab..e15074d 100644
--- a/subprojects/docs/src/docs/userguide/thisAndThat.xml
+++ b/subprojects/docs/src/docs/userguide/thisAndThat.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<chapter id='tutorial_this_and_that' xmlns:xi="http://www.w3.org/2001/XInclude">
+<chapter id='tutorial_this_and_that'>
     <title>Tutorial - 'This and That'</title>
     <section id='sec:directory_creation'>
         <title>Directory creation</title>
@@ -29,22 +29,6 @@
             <sourcefile file="build.gradle"/>
             <output args="-q compile"/>
         </sample>
-        <para>But Gradle offers you also <emphasis>Directory Tasks</emphasis> to deal with this.</para>
-        <sample id="directoryTask" dir="userguide/tutorial/directoryTask" title="Directory creation with Directory tasks">
-            <sourcefile file="build.gradle"/>
-            <output args="-q otherResources"/>
-        </sample>
-        <para>A <emphasis>Directory Task</emphasis>
-            is a simple task whose name is a relative path to the project dir
-            <footnote>
-                <para>The notation <literal>dir('/somepath')</literal> is a convenience method for
-                    <literal>tasks.add('somepath', type: Directory)</literal>
-                </para>
-            </footnote>
-            . During the execution phase the directory corresponding to this path gets created if it does not exist yet.
-            Another interesting thing to note in this example, is that you can also pass tasks objects to the dependsOn
-            declaration of a task.
-        </para>
     </section>
     <section id='sec:gradle_properties_and_system_properties'>
         <title>Gradle properties and system properties</title>
@@ -102,36 +86,7 @@
             </para>
         </section>
     </section>
-    <section id='sec:accessing_the_web_via_a_proxy'>
-        <title>Accessing the web via a proxy</title>
-        <para>Setting a proxy for web access (for example for downloading dependencies) is easy. Gradle does not need to
-            provide special functionality for this. The JVM can be instructed to go via proxy by setting certain system
-            properties. You could set these system properties directly in your build script with <literal>
-                System.properties['proxy.proxyUser'] = 'userid'</literal>. An arguably nicer way is shown in
-            <xref linkend='sec:gradle_properties_and_system_properties'/>. Your gradle.properties file could look like
-            this:
-        </para>
-        <example>
-            <title>Accessing the web via a proxy</title>
-            <para><filename>gradle.properties</filename></para>
-            <programlisting><![CDATA[
-systemProp.http.proxyHost=www.somehost.org
-systemProp.http.proxyPort=8080
-systemProp.http.proxyUser=userid
-systemProp.http.proxyPassword=password
-systemProp.http.nonProxyHosts=*.nonproxyrepos.com|localhost	
-]]></programlisting>
-        </example>
-        <para>We could not find a good overview for all possible proxy settings. One place to look are the constants
-            in a file from the ant project. Here a
-            <ulink url='http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/util/ProxySetup.java?view=markup&pathrev=556977'>
-                link
-            </ulink> to the svn view. The other is a
-            <ulink url='http://java.sun.com/javase/6/docs/technotes/guides/net/properties.html'>
-                Networking Properties page
-            </ulink> from the JDK docs. If anyone knows a better overview please let us know via the mailing list.
-        </para>
-    </section>
+
     <section id="sec:configuring_using_external_script">
         <title>Configuring the project using an external build script</title>
         <para>You can configure the current project using an external build script. All of the Gradle build language
@@ -169,7 +124,7 @@ systemProp.http.nonProxyHosts=*.nonproxyrepos.com|localhost
             <filename>.gradle</filename> directory in which it puts the compiled script. The next time you run this
             build, Gradle uses the compiled script, if the script has not changed since it was compiled.  Otherwise the
             script gets compiled and the new version is stored in the cache. If you run Gradle with the
-            <option>-C rebuild</option> option, the cached script is discarded and the script is compiled and stored
+            <option>--recompile-scripts</option> option, the cached script is discarded and the script is compiled and stored
             in the cache. This way you can force Gradle to rebuild the cache.
         </para>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/troubleshooting.xml b/subprojects/docs/src/docs/userguide/troubleshooting.xml
new file mode 100644
index 0000000..dc44756
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/troubleshooting.xml
@@ -0,0 +1,58 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='troubleshooting'>
+    <title>Troubleshooting</title>
+    <note>
+        <para>
+            This chapter is currently a work in progress.
+        </para>
+    </note>
+    <para>
+        When using Gradle (or any software package), you can run into problems. You may not understand
+        how to use a particular feature, or you may encounter a defect. Or, you may have a general question
+        about Gradle.
+    </para>
+    <para>
+        This chapter gives some advice for troubleshooting problems and explains how to get help with your problems.
+    </para>
+    
+    <section>
+        <title>Working through problems</title>
+        <para>
+            If you are encountering problems, one of the first things to try is using the very latest release of Gradle. New versions of Gradle are
+            released frequently with bug fixes and new features. The problem you are having may have been fixed in a new release.
+        </para>
+        <para>
+            If you are using the Gradle Daemon, try temporarily disabling the daemon (you can pass the command line switch <literal>--no-daemon</literal>).
+            The Gradle Daemon is currently an experimental feature and may introduce build failures.
+            More information about troubleshooting daemon is located in <xref linkend="gradle_daemon"/>.
+        </para>
+    </section>
+    <section>
+        <title>Getting help</title>
+        <para>
+            The place to go for help with Gradle is <ulink url="http://forums.gradle.org">http://forums.gradle.org</ulink>.
+            The Gradle Forums is the place where you can report problems and ask questions to the Gradle 
+            developers and other community members.
+        </para>
+        <para>
+            If something's not working for you, posting a question or problem report to the forums is the fastest way 
+            to get help. It's also the place to post improvement suggestions or new ideas. The development team 
+            frequently posts news items and announces releases via the forum, making it a great way to stay up to 
+            date with the latest Gradle developments.
+        </para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/tutorials.xml b/subprojects/docs/src/docs/userguide/tutorials.xml
index 93ecb71..e0cd5e9 100644
--- a/subprojects/docs/src/docs/userguide/tutorials.xml
+++ b/subprojects/docs/src/docs/userguide/tutorials.xml
@@ -49,6 +49,14 @@
             </varlistentry>
             <varlistentry>
                 <term>
+                    <xref linkend="artifact_dependencies_tutorial"/>
+                </term>
+                <listitem>
+                    <para>Shows how to start using Gradle's dependency management.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>
                     <xref linkend="tutorial_groovy_projects"/>
                 </term>
                 <listitem>
@@ -67,6 +75,6 @@
     </section>
     <section condition="website">
         <title>Where to next?</title>
-        <para>There are more tutorials and plenty of reference material in the <ulink url="website:documentation.html">user guide</ulink>.</para>
+        <para>There are more tutorials and plenty of reference material in the <ulink url="website:documentation">user guide</ulink>.</para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
index 7efc0cc..b382017 100644
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -15,7 +15,7 @@
   -->
 <book xmlns:xi="http://www.w3.org/2001/XInclude">
     <bookinfo>
-        <title>Gradle</title>
+        <title>Gradle User Guide</title>
         <subtitle>A better way to build</subtitle>
         <copyright>
             <year>2007-2010</year>
@@ -40,14 +40,17 @@
     <xi:include href='overview.xml'/>
     <xi:include href='tutorials.xml'/>
     <xi:include href='installation.xml'/>
+    <xi:include href='troubleshooting.xml'/>
     <xi:include href='buildScriptsTutorial.xml'/>
     <xi:include href='javaTutorial.xml'/>
+    <xi:include href='artifactDependenciesTutorial.xml'/>
     <xi:include href='groovyTutorial.xml'/>
     <xi:include href='webTutorial.xml'/>
-    <xi:include href='artifactDependenciesTutorial.xml'/>
     <xi:include href='commandLineTutorial.xml'/>
     <xi:include href='guiTutorial.xml'/>
+    <xi:include href='gradleDaemon.xml'/>
     <xi:include href='thisAndThat.xml'/>
+    <xi:include href='buildEnvironment.xml'/>
     <xi:include href='writingBuildScripts.xml'/>
     <xi:include href='tasks.xml'/>
     <xi:include href='workingWithFiles.xml'/>
@@ -59,8 +62,13 @@
     <xi:include href='groovyPlugin.xml'/>
     <xi:include href='scalaPlugin.xml'/>
     <xi:include href='warPlugin.xml'/>
+    <xi:include href='earPlugin.xml'/>
     <xi:include href='jettyPlugin.xml'/>
-    <xi:include href='codeQualityPlugin.xml'/>
+    <xi:include href='checkstylePlugin.xml'/>
+    <xi:include href='codeNarcPlugin.xml'/>
+    <xi:include href='findBugsPlugin.xml'/>
+    <xi:include href='jdependPlugin.xml'/>
+    <xi:include href='pmdPlugin.xml'/>
     <xi:include href='sonarPlugin.xml'/>
     <xi:include href='osgi.xml'/>
     <xi:include href='eclipsePlugin.xml'/>
@@ -68,10 +76,13 @@
     <xi:include href='antlrPlugin.xml'/>
     <xi:include href='projectReports.xml'/>
     <xi:include href='announcePlugin.xml'/>
+    <xi:include href='buildAnnouncementsPlugin.xml'/>
     <xi:include href='applicationPlugin.xml'/>
 	<xi:include href='depMngmt.xml'/>
     <xi:include href='artifactMngmt.xml'/>
     <xi:include href='mavenPlugin.xml'/>
+    <xi:include href='signingPlugin.xml'/>
+    <xi:include href='cpp.xml'/>
     <xi:include href='buildLifecycle.xml'/>
     <xi:include href='multiproject.xml'/>
     <xi:include href='customTasks.xml'/>
diff --git a/subprojects/docs/src/docs/userguide/warPlugin.xml b/subprojects/docs/src/docs/userguide/warPlugin.xml
index 8a5cd89..15a0be8 100644
--- a/subprojects/docs/src/docs/userguide/warPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/warPlugin.xml
@@ -176,14 +176,9 @@
         <para>Here is an example with the most important customization options:
         </para>
         <sample id="webproject" dir="webApplication/customised" title="Customization of war plugin">
-            <sourcefile file="build.gradle"/>
+            <sourcefile file="build.gradle" snippet="customization"/>
         </sample>
         <para>Of course one can configure the different file-sets with a closure to define excludes and includes.
         </para>
-        <para>If you want to enable the generation of the default jar archive additional to the war archive just type:
-        </para>
-        <sample id="customisedWebApplication" dir="webApplication/customised" title="Generation of JAR archive in addition to WAR archive">
-            <sourcefile file="build.gradle" snippet="enable-jar"/>
-        </sample>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/webTutorial.xml b/subprojects/docs/src/docs/userguide/webTutorial.xml
index 6a8a9fc..e4c016c 100644
--- a/subprojects/docs/src/docs/userguide/webTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/webTutorial.xml
@@ -21,7 +21,7 @@
     </note>
 
     <para>This chapter introduces some of the Gradle's support for web applications. Gradle provides two plugins for web
-        application developement: the War plugin and the Jetty plugin. The War plugin extends the Java plugin to build a
+        application development: the War plugin and the Jetty plugin. The War plugin extends the Java plugin to build a
         WAR file for your project. The Jetty plugin extends the War plugin to allow you to deploy your web application
         to an embedded Jetty web container.
     </para>
diff --git a/subprojects/docs/src/docs/userguide/workingWithFiles.xml b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
index 2e27bf9..1d70c42 100644
--- a/subprojects/docs/src/docs/userguide/workingWithFiles.xml
+++ b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
@@ -87,14 +87,35 @@
             <output args="-q list"/>
         </sample>
 
+        <para>
+            Some other types of things you can pass to <literal>files()</literal>:
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term><classname>FileCollection</classname></term>
+                <listitem>
+                    <para>These are flattened and the contents included in the file collection.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><classname>Task</classname></term>
+                <listitem>
+                    <para>The output files of the task are included in the file collection.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><classname>TaskOutputs</classname></term>
+                <listitem>
+                    <para>The output files of the TaskOutputs are included in the file collection.</para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+
         <para>It is important to note that the content of a file collection is evaluated lazily, when it is needed.
             This means you can, for example, create a <literal>FileCollection</literal> that represents files which
             will be created in the future by, say, some task.
         </para>
 
-        <para>The <literal>files()</literal> method also accepts <classname>FileCollection</classname> instances.
-            These are flattened and the contents included in the file collection.
-        </para>
     </section>
 
     <section id="sec:file_trees">
@@ -133,7 +154,7 @@
 
         <para>You can use the contents of an archive, such as a ZIP or TAR file, as a file tree. You do this using
             the <apilink class="org.gradle.api.Project" method="zipTree"/> and
-            <apilink class="org.gradle.api.Project" method="tarTree"/> methods. These methods return a <literal>FileTree</literal>
+            <apilink class="org.gradle.api.Project" method="tarTree(java.lang.Object)"/> methods. These methods return a <literal>FileTree</literal>
             instance which you can use like any other file tree or file collection. For example, you can use it to expand
             the archive by copying the contents, or to merge some archives into another.
         </para>
@@ -376,5 +397,5 @@
         </para>
 
     </section>
-    
+
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/writingBuildScripts.xml b/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
index f968633..9f8c0a3 100644
--- a/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
+++ b/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
@@ -135,6 +135,44 @@
         </para>
     </section>
     <section>
+        <title>Declaring variables</title>
+        <para>There are two kinds of variables that can be declared in a build script: local variables and extra properties.
+        </para>
+        <section>
+            <title>Local variables</title>
+            <para>Local variables are declared with the <literal>def</literal> keyword. They are only visible in the scope where they have been declared.
+                Local variables are a feature of the underlying Groovy language.
+            </para>
+            <sample id="localVariables" dir="userguide/tutorial/localVariables" title="Using local variables">
+                <sourcefile file="build.gradle"/>
+            </sample>
+        </section>
+        <section id='sec:extra_properties'>
+            <title>Extra properties</title>
+            <para>All enhanced objects in Gradle's domain model can hold extra user-defined properties. This includes, but is not limited to, projects, tasks,
+                and source sets. Extra properties can be added, read and set via the owning object's <literal>ext</literal> property.
+            </para>
+            <sample id="extraProperties" dir="userguide/tutorial/extraProperties" title="Using extra properties">
+                <sourcefile file="build.gradle" snippet="sourceSetProperty"/>
+            </sample>
+            <para>In this example, a property named <literal>purpose</literal> is added to all source sets by setting <literal>ext.purpose</literal> to
+                <literal>null</literal> (<literal>null</literal> is a permissible value). Once the property has been added, it can be read and set like
+                a predefined property. Alternatively, <literal>ext.</literal> can be used as well.
+            </para>
+            <para>By requiring special syntax for adding a property, Gradle can fail fast when an attempt is made to set a (predefined or extra) property
+                but the property is misspelled or does not exist.
+                <footnote>
+                    <para>As of Gradle 1.0 milestone 9, using <literal>ext</literal> to add extra properties is strongly encouraged but not yet enforced.
+                        Therefore, Gradle will not fail when an unknown property is set. However, it will print a warning.
+                    </para>
+                </footnote>
+                Extra properties can be accessed from anywhere their owning object can be accessed, giving them a wider scope than local variables.
+                Extra properties on a parent project are visible from subprojects.
+            </para>
+            <para>For further details on extra properties and their API, see <apilink class="org.gradle.api.plugins.ExtraPropertiesExtension"/>.</para>
+        </section>
+    </section>
+    <section>
         <title>Some Groovy basics</title>
         <para>Groovy provides plenty of features for creating DSLs, and the Gradle build language takes advantage of these.
             Understanding how the build language works will help you when you write your build script, and in particular,
diff --git a/subprojects/docs/src/samples/announce/build.gradle b/subprojects/docs/src/samples/announce/build.gradle
index 6b7de53..7b47911 100644
--- a/subprojects/docs/src/samples/announce/build.gradle
+++ b/subprojects/docs/src/samples/announce/build.gradle
@@ -1,6 +1,9 @@
 // START SNIPPET use-plugin
 apply plugin: 'announce'
 // END SNIPPET use-plugin
+// START SNIPPET use-build-announcements-plugin
+apply plugin: 'build-announcements'
+// END SNIPPET use-build-announcements-plugin
 
 //START SNIPPET announce-plugin-conf
 announce {  
diff --git a/subprojects/docs/src/samples/announce/init.gradle b/subprojects/docs/src/samples/announce/init.gradle
new file mode 100644
index 0000000..fd0d3c5
--- /dev/null
+++ b/subprojects/docs/src/samples/announce/init.gradle
@@ -0,0 +1,3 @@
+rootProject {
+    apply plugin: 'build-announcements'
+}
diff --git a/subprojects/docs/src/samples/application/build.gradle b/subprojects/docs/src/samples/application/build.gradle
index 6d5f37e..e0dbfa6 100644
--- a/subprojects/docs/src/samples/application/build.gradle
+++ b/subprojects/docs/src/samples/application/build.gradle
@@ -1,18 +1,33 @@
-apply plugin:'java'
-// START SNIPPET use-plugin
-apply plugin:'application'
-// END SNIPPET use-plugin
-
-version = '1.0.2'
-
-// START SNIPPET mainClassName-conf
-mainClassName = "org.gradle.sample.Main"
-// END SNIPPET mainClassName-conf
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    compile 'commons-collections:commons-collections:3.2.1'
+apply plugin:'java'
+// START SNIPPET use-plugin
+apply plugin:'application'
+// END SNIPPET use-plugin
+
+version = '1.0.2'
+
+// START SNIPPET mainClassName-conf
+mainClassName = "org.gradle.sample.Main"
+// END SNIPPET mainClassName-conf
+
+// START SNIPPET distribution-spec
+task createDocs {
+    def docs = file("$buildDir/docs")
+    outputs.dir docs
+    doLast {
+        docs.mkdirs()
+        new File(docs, "readme.txt").write("Read me!")
+    }
+}
+
+applicationDistribution.from(createDocs) {
+    into "docs"
+}
+// END SNIPPET distribution-spec
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile 'commons-collections:commons-collections:3.2.1'
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/application/src/dist/LICENSE b/subprojects/docs/src/samples/application/src/dist/LICENSE
new file mode 100644
index 0000000..3933db3
--- /dev/null
+++ b/subprojects/docs/src/samples/application/src/dist/LICENSE
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2011 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.
+ */
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/codeQuality/build.gradle b/subprojects/docs/src/samples/codeQuality/build.gradle
old mode 100644
new mode 100755
index 700266a..fc2cc3f
--- a/subprojects/docs/src/samples/codeQuality/build.gradle
+++ b/subprojects/docs/src/samples/codeQuality/build.gradle
@@ -1,6 +1,19 @@
-// START SNIPPET use-plugin
-apply plugin: 'code-quality'
-// END SNIPPET use-plugin
+// START SNIPPET use-checkstyle-plugin
+apply plugin: 'checkstyle'
+// END SNIPPET use-checkstyle-plugin
+// START SNIPPET use-codenarc-plugin
+apply plugin: 'codenarc'
+// END SNIPPET use-codenarc-plugin
+// START SNIPPET use-findbugs-plugin
+apply plugin: 'findbugs'
+// END SNIPPET use-findbugs-plugin
+// START SNIPPET use-pmd-plugin
+apply plugin: 'pmd'
+// END SNIPPET use-pmd-plugin
+// START SNIPPET use-jdepend-plugin
+apply plugin: 'jdepend'
+// END SNIPPET use-jdepend-plugin
+apply plugin: 'java'
 apply plugin: 'groovy'
 
 repositories {
@@ -8,6 +21,6 @@ repositories {
 }
 
 dependencies {
-    groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.10'
-    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+    groovy localGroovy()
+    testCompile 'junit:junit:4.8.2'
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/codeQuality/config/checkstyle/checkstyle.xml b/subprojects/docs/src/samples/codeQuality/config/checkstyle/checkstyle.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/codeQuality/config/codenarc/codenarc.xml b/subprojects/docs/src/samples/codeQuality/config/codenarc/codenarc.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/codeQuality/readme.xml b/subprojects/docs/src/samples/codeQuality/readme.xml
old mode 100644
new mode 100755
index 8e51f35..cd13354
--- a/subprojects/docs/src/samples/codeQuality/readme.xml
+++ b/subprojects/docs/src/samples/codeQuality/readme.xml
@@ -1,18 +1,18 @@
-<!--
-  ~ 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.
-  -->
-<sample>
-    <para>A project which uses the code quality plugin.</para>
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>A project which uses the various code quality plugins.</para>
 </sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/codeQuality/src/main/groovy/org/gradle/sample/GroovyPerson.groovy b/subprojects/docs/src/samples/codeQuality/src/main/groovy/org/gradle/sample/GroovyPerson.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/codeQuality/src/main/java/org/gradle/sample/Person.java b/subprojects/docs/src/samples/codeQuality/src/main/java/org/gradle/sample/Person.java
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/codeQuality/src/test/groovy/org/gradle/sample/PersonTest.groovy b/subprojects/docs/src/samples/codeQuality/src/test/groovy/org/gradle/sample/PersonTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/cpp/dependencies/build.gradle b/subprojects/docs/src/samples/cpp/dependencies/build.gradle
new file mode 100644
index 0000000..b96b63f
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/dependencies/build.gradle
@@ -0,0 +1,63 @@
+apply plugin: "base"
+
+project(":lib") {
+    archivesBaseName = "some-lib"
+
+    // START SNIPPET use-plugin-lib
+    apply plugin: "cpp-lib"
+    // END SNIPPET use-plugin-lib
+    apply plugin: "eclipse-cdt"
+    apply plugin: 'maven'
+
+
+// START SNIPPET upload
+group = "some-org"
+archivesBaseName = "some-lib"
+version = 1.0
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("${buildDir}/repo"))
+        }
+    }
+}
+// END SNIPPET upload
+
+}
+
+project(":exe") {
+    // START SNIPPET use-plugin-exe
+    apply plugin: "cpp-exe"
+    // END SNIPPET use-plugin-exe
+    apply plugin: "eclipse-cdt"
+    apply plugin: "maven"
+
+    version = 1.0
+    
+    repositories {
+        maven {
+            url new File(project(":lib").buildDir, "repo")
+        }
+    }
+
+// START SNIPPET declaring-dependencies
+cpp {
+    sourceSets {
+        main {
+            dependency group: "some-org", name: "some-lib", version: "1.0"
+        }
+    }
+}
+// END SNIPPET declaring-dependencies
+    
+    uploadArchives {
+        repositories {
+            mavenDeployer {
+                repository(url: uri("${buildDir}/repo"))
+            }
+        }
+    }
+}
+
+task build(dependsOn: project(":exe").compileMain)
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
new file mode 100644
index 0000000..09f8ace
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/dependencies/exe/src/main/cpp/main.cpp
@@ -0,0 +1,6 @@
+#include "hello.h"
+
+int main () {
+  hello();
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/cpp/hello.cpp
new file mode 100644
index 0000000..1ac707f
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/cpp/hello.cpp
@@ -0,0 +1,5 @@
+#include <iostream>
+
+void hello () {
+  std::cout << "Hello, World!\n";
+}
diff --git a/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/headers/hello.h b/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/headers/hello.h
new file mode 100644
index 0000000..14e73eb
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/dependencies/lib/src/main/headers/hello.h
@@ -0,0 +1 @@
+void hello();
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/dependencies/settings.gradle b/subprojects/docs/src/samples/cpp/dependencies/settings.gradle
new file mode 100644
index 0000000..462618d
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/dependencies/settings.gradle
@@ -0,0 +1 @@
+include "exe", "lib"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exe/build.gradle b/subprojects/docs/src/samples/cpp/exe/build.gradle
new file mode 100644
index 0000000..bcbc79e
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exe/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: "cpp-exe"
+
+// START SNIPPET args
+executables {
+    main {
+        spec {
+            args "-fno-access-control", "-fconserve-space"
+        }
+    }
+}
+// END SNIPPET args
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exe/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/cpp/exe/src/main/cpp/hello.cpp
new file mode 100644
index 0000000..1adbff2
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exe/src/main/cpp/hello.cpp
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main () {
+  std::cout << "Hello, World!\n";
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/build.gradle b/subprojects/docs/src/samples/cpp/exewithlib/build.gradle
new file mode 100644
index 0000000..1bce29e
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exewithlib/build.gradle
@@ -0,0 +1,18 @@
+// START SNIPPET project-dependencies
+project(":lib") {
+    apply plugin: "cpp-lib"
+}
+
+project(":exe") {
+    apply plugin: "cpp-exe"
+    cpp {
+        sourceSets {
+            main {
+                libs << project(":lib").libraries.main
+            }
+        }
+    }
+}
+// END SNIPPET project-dependencies
+
+task build(dependsOn: project(":exe").compileMain)
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/exe/src/main/cpp/main.cpp b/subprojects/docs/src/samples/cpp/exewithlib/exe/src/main/cpp/main.cpp
new file mode 100644
index 0000000..09f8ace
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exewithlib/exe/src/main/cpp/main.cpp
@@ -0,0 +1,6 @@
+#include "hello.h"
+
+int main () {
+  hello();
+  return 0;
+}
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/cpp/hello.cpp b/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/cpp/hello.cpp
new file mode 100755
index 0000000..5d79bce
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/cpp/hello.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+#ifdef _WIN32
+#define LIB_FUNC __declspec(dllexport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello () {
+  std::cout << "Hello, World!\n";
+}
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h b/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h
new file mode 100755
index 0000000..1d3f35d
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exewithlib/lib/src/main/headers/hello.h
@@ -0,0 +1,7 @@
+#ifdef _WIN32
+#define LIB_FUNC __declspec(dllimport)
+#else
+#define LIB_FUNC
+#endif
+
+void LIB_FUNC hello();
diff --git a/subprojects/docs/src/samples/cpp/exewithlib/settings.gradle b/subprojects/docs/src/samples/cpp/exewithlib/settings.gradle
new file mode 100644
index 0000000..462618d
--- /dev/null
+++ b/subprojects/docs/src/samples/cpp/exewithlib/settings.gradle
@@ -0,0 +1 @@
+include "exe", "lib"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/customBuildLanguage/billing/build.gradle b/subprojects/docs/src/samples/customBuildLanguage/billing/build.gradle
index e8f242d..c739bd3 100644
--- a/subprojects/docs/src/samples/customBuildLanguage/billing/build.gradle
+++ b/subprojects/docs/src/samples/customBuildLanguage/billing/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: org.gradle.samples.ProductModulePlugin
+apply plugin: 'product-module'
 
 dependencies {
     compile project(':identityManagement')
diff --git a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy
index bde5d2a..738e944 100644
--- a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy
+++ b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy
@@ -5,6 +5,7 @@ import org.gradle.api.Project
 class ProductDefinition {
     String displayName
     List<Project> modules = []
+    List<File> distSrcDirs = []
 
     def displayName(String name) {
         displayName = name
diff --git a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
index af7607e..d47ed71 100644
--- a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
+++ b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
@@ -30,18 +30,19 @@ class ProductPlugin implements Plugin<Project> {
                 mavenCentral()
             }
 
-            def pluginConvention = new ProductPluginConvention()
-            convention.plugins.product = pluginConvention
-            pluginConvention.distSrcDirs << rootProject.file('src/dist')
-            pluginConvention.distSrcDirs << project.file('src/dist')
+            def product = extensions.create("product", ProductDefinition)
+            product.distSrcDirs << rootProject.file('src/dist')
+            product.distSrcDirs << project.file('src/dist')
 
             configurations {
                 runtime
             }
             tasks.add(name: 'dist', type: Zip)
+            artifacts {
+                archives dist
+            }
 
             afterEvaluate {
-                ProductDefinition product = pluginConvention.product
                 product.modules.each {p ->
                     dependencies { runtime project.project(p.path) }
                 }
@@ -50,7 +51,7 @@ class ProductPlugin implements Plugin<Project> {
                     into('lib') {
                         from configurations.runtime
                     }
-                    from(pluginConvention.distSrcDirs) {
+                    from(product.distSrcDirs) {
                         filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
                                 productName: product.displayName,
                                 version: version
diff --git a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPluginConvention.groovy b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPluginConvention.groovy
deleted file mode 100644
index f413d92..0000000
--- a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPluginConvention.groovy
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.gradle.samples
-
-class ProductPluginConvention {
-    ProductDefinition product = new ProductDefinition()
-    List<File> distSrcDirs = []
-
-    def product(Closure cl) {
-        cl.delegate = product
-        cl.resolveStrategy = Closure.DELEGATE_FIRST
-        cl.call()
-    }
-}
diff --git a/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/resources/META-INF/gradle-plugins/product-module.properties b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/resources/META-INF/gradle-plugins/product-module.properties
new file mode 100644
index 0000000..31e26d0
--- /dev/null
+++ b/subprojects/docs/src/samples/customBuildLanguage/buildSrc/src/main/resources/META-INF/gradle-plugins/product-module.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.samples.ProductModulePlugin
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/customBuildLanguage/identityManagement/build.gradle b/subprojects/docs/src/samples/customBuildLanguage/identityManagement/build.gradle
index d35b213..c12ee34 100644
--- a/subprojects/docs/src/samples/customBuildLanguage/identityManagement/build.gradle
+++ b/subprojects/docs/src/samples/customBuildLanguage/identityManagement/build.gradle
@@ -1 +1 @@
-apply plugin: org.gradle.samples.ProductModulePlugin
+apply plugin: 'product-module'
diff --git a/subprojects/docs/src/samples/customBuildLanguage/reporting/build.gradle b/subprojects/docs/src/samples/customBuildLanguage/reporting/build.gradle
index de86f33..3a52aea 100644
--- a/subprojects/docs/src/samples/customBuildLanguage/reporting/build.gradle
+++ b/subprojects/docs/src/samples/customBuildLanguage/reporting/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: org.gradle.samples.ProductModulePlugin
+apply plugin: 'product-module'
 
 dependencies {
     compile project(':identityManagement')
diff --git a/subprojects/docs/src/samples/customDistribution/consumer/build.gradle b/subprojects/docs/src/samples/customDistribution/consumer/build.gradle
new file mode 100644
index 0000000..bfc4fed
--- /dev/null
+++ b/subprojects/docs/src/samples/customDistribution/consumer/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'greeting'
+
+task wrapper(type: Wrapper) {
+    // Need to configure the wrapper to point to the custom distribution
+    distributionUrl = uri("../plugin/build/distributions/custom-gradle-${gradle.gradleVersion}.zip")
+}
diff --git a/subprojects/docs/src/samples/customDistribution/plugin/build.gradle b/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
new file mode 100644
index 0000000..6739273
--- /dev/null
+++ b/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'groovy'
+
+repositories {
+    mavenCentral()
+}
+
+configurations {
+    gradleApi
+    gradleApi.extendsFrom groovy
+    compile.extendsFrom gradleApi
+}
+
+dependencies {
+    gradleApi gradleApi()
+    groovy localGroovy()
+    compile 'com.google.guava:guava:11.0.1'
+}
+
+task dist(type: Zip) {
+    description = 'Creates a custom Gradle distribution containing the custom plugins'
+    // Make sure you give this distribution a name that indicates that it is not the standard distribution
+    archiveName = "custom-gradle-${gradle.gradleVersion}.zip"
+    into("custom-gradle-${gradle.gradleVersion}") {
+        into('init.d') { from 'src/initScripts' }
+        into('init.d/libs') {
+            from jar
+            from configurations.runtime - configurations.gradleApi
+        }
+        from gradle.gradleHomeDir
+    }
+}
diff --git a/subprojects/docs/src/samples/customDistribution/plugin/settings.gradle b/subprojects/docs/src/samples/customDistribution/plugin/settings.gradle
new file mode 100644
index 0000000..48be017
--- /dev/null
+++ b/subprojects/docs/src/samples/customDistribution/plugin/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'customDistribution'
diff --git a/subprojects/docs/src/samples/customDistribution/plugin/src/initScripts/custom-plugins.gradle b/subprojects/docs/src/samples/customDistribution/plugin/src/initScripts/custom-plugins.gradle
new file mode 100644
index 0000000..e1f5159
--- /dev/null
+++ b/subprojects/docs/src/samples/customDistribution/plugin/src/initScripts/custom-plugins.gradle
@@ -0,0 +1,10 @@
+println "hi from custom plugin init script."
+
+// Make the plugin visible to all projects, by including it in the build script classpath.
+rootProject {
+    buildscript {
+        dependencies {
+            classpath fileTree(dir: "${initscript.sourceFile.parentFile}/libs", include: '*.jar')
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy b/subprojects/docs/src/samples/customDistribution/plugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
similarity index 100%
copy from subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
copy to subprojects/docs/src/samples/customDistribution/plugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
diff --git a/subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy b/subprojects/docs/src/samples/customDistribution/plugin/src/main/groovy/org/gradle/GreetingTask.groovy
similarity index 100%
copy from subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy
copy to subprojects/docs/src/samples/customDistribution/plugin/src/main/groovy/org/gradle/GreetingTask.groovy
diff --git a/subprojects/docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties b/subprojects/docs/src/samples/customDistribution/plugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
similarity index 100%
copy from subprojects/docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
copy to subprojects/docs/src/samples/customDistribution/plugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
diff --git a/subprojects/docs/src/samples/customDistribution/readme.xml b/subprojects/docs/src/samples/customDistribution/readme.xml
new file mode 100644
index 0000000..8b7b8be
--- /dev/null
+++ b/subprojects/docs/src/samples/customDistribution/readme.xml
@@ -0,0 +1,13 @@
+<sample>
+    <para>This sample demonstrates how to create a custom Gradle distribution and use it with the Gradle wrapper.</para>
+    <para>This sample contains the following projects:</para>
+    <itemizedlist>
+        <listitem>
+            <para>The <filename>plugin</filename> directory contains the project that implements a custom plugin, and bundles the plugin into a custom
+                Gradle distribution.</para>
+        </listitem>
+        <listitem>
+            <para>The <filename>consumer</filename> directory contains the project that uses the custom distribution.</para>
+        </listitem>
+    </itemizedlist>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/customPlugin/build.gradle b/subprojects/docs/src/samples/customPlugin/build.gradle
deleted file mode 100644
index 14a47f3..0000000
--- a/subprojects/docs/src/samples/customPlugin/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-// START SNIPPET use-plugin
-apply plugin: 'groovy'
-
-dependencies {
-    compile gradleApi()
-}
-// END SNIPPET use-plugin
-
-apply plugin: 'maven'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    groovy localGroovy()
-    testCompile 'junit:junit:4.8.2'
-}
-
-group = 'org.gradle'
-version = '1.0-SNAPSHOT'
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri('repo'))
-        }
-    }
-}
diff --git a/subprojects/docs/src/samples/customPlugin/consumer/build.gradle b/subprojects/docs/src/samples/customPlugin/consumer/build.gradle
new file mode 100644
index 0000000..fe39333
--- /dev/null
+++ b/subprojects/docs/src/samples/customPlugin/consumer/build.gradle
@@ -0,0 +1,24 @@
+// START SNIPPET use-plugin
+// START SNIPPET use-task
+buildscript {
+    repositories {
+        maven {
+            url uri('../repo')
+        }
+    }
+    dependencies {
+        classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT'
+    }
+}
+// END SNIPPET use-task
+buildscript {
+   configurations.classpath.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+apply plugin: 'greeting'
+// END SNIPPET use-plugin
+// START SNIPPET use-task
+
+task greeting(type: org.gradle.GreetingTask) {
+    greeting = 'howdy!'
+}
+// END SNIPPET use-task
diff --git a/subprojects/docs/src/samples/customPlugin/plugin/build.gradle b/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
new file mode 100644
index 0000000..71676d1
--- /dev/null
+++ b/subprojects/docs/src/samples/customPlugin/plugin/build.gradle
@@ -0,0 +1,37 @@
+// START SNIPPET use-plugin
+apply plugin: 'groovy'
+
+// START SNIPPET gradle-api-dependencies
+// START SNIPPET local-groovy-dependencies
+dependencies {
+// END SNIPPET local-groovy-dependencies
+    compile gradleApi()
+// END SNIPPET gradle-api-dependencies
+// START SNIPPET local-groovy-dependencies
+    groovy localGroovy()
+// START SNIPPET gradle-api-dependencies
+}
+// END SNIPPET gradle-api-dependencies
+// END SNIPPET local-groovy-dependencies
+// END SNIPPET use-plugin
+
+apply plugin: 'maven'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.8.2'
+}
+
+group = 'org.gradle'
+version = '1.0-SNAPSHOT'
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri('../repo'))
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/customPlugin/plugin/settings.gradle b/subprojects/docs/src/samples/customPlugin/plugin/settings.gradle
new file mode 100644
index 0000000..7ae4aa9
--- /dev/null
+++ b/subprojects/docs/src/samples/customPlugin/plugin/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'customPlugin'
diff --git a/subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy b/subprojects/docs/src/samples/customPlugin/plugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
similarity index 100%
rename from subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
rename to subprojects/docs/src/samples/customPlugin/plugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
diff --git a/subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy b/subprojects/docs/src/samples/customPlugin/plugin/src/main/groovy/org/gradle/GreetingTask.groovy
similarity index 100%
rename from subprojects/docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy
rename to subprojects/docs/src/samples/customPlugin/plugin/src/main/groovy/org/gradle/GreetingTask.groovy
diff --git a/subprojects/docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties b/subprojects/docs/src/samples/customPlugin/plugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
similarity index 100%
rename from subprojects/docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
rename to subprojects/docs/src/samples/customPlugin/plugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
diff --git a/subprojects/docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy b/subprojects/docs/src/samples/customPlugin/plugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy
similarity index 100%
rename from subprojects/docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy
rename to subprojects/docs/src/samples/customPlugin/plugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy
diff --git a/subprojects/docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy b/subprojects/docs/src/samples/customPlugin/plugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy
similarity index 100%
rename from subprojects/docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy
rename to subprojects/docs/src/samples/customPlugin/plugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy
diff --git a/subprojects/docs/src/samples/customPlugin/readme.xml b/subprojects/docs/src/samples/customPlugin/readme.xml
index 7ac351d..9f03006 100644
--- a/subprojects/docs/src/samples/customPlugin/readme.xml
+++ b/subprojects/docs/src/samples/customPlugin/readme.xml
@@ -1,3 +1,12 @@
 <sample>
-    <para>A project which implements a custom plugin and task.</para>
+    <para>A set of projects that show how to implement, test, publish and use a custom plugin and task.</para>
+    <para>This sample contains the following projects:</para>
+    <itemizedlist>
+        <listitem>
+            <para>The <filename>plugin</filename> directory contains the project that implements and publishes the plugin.</para>
+        </listitem>
+        <listitem>
+            <para>The <filename>consumer</filename> directory contains the project that uses the plugin.</para>
+        </listitem>
+    </itemizedlist>
 </sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/customPlugin/usesCustomPlugin.gradle b/subprojects/docs/src/samples/customPlugin/usesCustomPlugin.gradle
deleted file mode 100644
index d600e09..0000000
--- a/subprojects/docs/src/samples/customPlugin/usesCustomPlugin.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-// START SNIPPET use-plugin
-buildscript {
-    repositories {
-        mavenRepo urls: uri('repo')
-    }
-    dependencies {
-        classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT'
-    }
-}
-
-apply plugin: 'greeting'
-// END SNIPPET use-plugin
diff --git a/subprojects/docs/src/samples/customPlugin/usesCustomTask.gradle b/subprojects/docs/src/samples/customPlugin/usesCustomTask.gradle
deleted file mode 100644
index c062c12..0000000
--- a/subprojects/docs/src/samples/customPlugin/usesCustomTask.gradle
+++ /dev/null
@@ -1,14 +0,0 @@
-// START SNIPPET use-task
-buildscript {
-    repositories {
-        mavenRepo urls: uri('repo')
-    }
-    dependencies {
-        classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT'
-    }
-}
-
-task greeting(type: org.gradle.GreetingTask) {
-    greeting = 'howdy!'
-}
-// END SNIPPET use-task
diff --git a/subprojects/docs/src/samples/dependencies/build.gradle b/subprojects/docs/src/samples/dependencies/build.gradle
index eafbe60..ee23c00 100644
--- a/subprojects/docs/src/samples/dependencies/build.gradle
+++ b/subprojects/docs/src/samples/dependencies/build.gradle
@@ -25,8 +25,8 @@ project(':northSea') {
 repositories {
     add(new FileSystemResolver()) {
         name = "repo"
-        addArtifactPattern("$rootDir/repo/[organization]/[module]-[revision].[ext]")
-        addIvyPattern("$rootDir/repo/[organization]/ivy-[module]-[revision].xml")
+        addArtifactPattern("$rootDir/repo/[organization]/[module](-[classifier])-[revision].[ext]")
+        addIvyPattern("$rootDir/repo/[organization]/ivy-[module](-[classifier])-[revision].xml")
         checkmodified = true
     }
 }
@@ -106,7 +106,7 @@ task test(dependsOn: [configurations.subprojectAtlantic, configurations.subproje
 }
 
 def assertCorrectFilesForCompleteConfigurations() {
-    expectedResults = [
+    ext.expectedResults = [
             oneDepWithNoTransitives: ['herring-1.0.jar'] as Set,
             oneDepWithTransitives: ['tuna-1.0.jar', 'herring-1.0.jar'] as Set,
             twoDepsWithNoTransitives: ['seal-1.0.jar', 'herring-1.0.jar'] as Set,
@@ -121,8 +121,8 @@ def assertCorrectFilesForCompleteConfigurations() {
             mixed: ['tuna-1.0.jar', 'orca-1.0.jar', 'seal-1.0.jar', 'herring-1.0.jar', 'selfResolving1.jar', 'someDir'] as Set,
             subprojectAtlantic: ['selfResolving1.jar', 'herring-1.0.jar'] as Set,
             subprojectNorthSea: ['selfResolving1.jar', 'herring-1.0.jar', 'selfResolving2.jar', 'squid-1.0.jar'] as Set,
-            classifier: ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar', "herring-1.0.jar"] as Set,
-            extendingClassifier: ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar', "herring-1.0.jar"] as Set
+            classifier: ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar', "herring-1.0.jar"] as Set,
+            extendingClassifier: ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar', "herring-1.0.jar"] as Set
     ]
     expectedResults.each { configurationName, expectedFileNames ->
         Set resolvedFileNames = configurations[configurationName].files.collect { it.name }
@@ -142,14 +142,15 @@ def assertFilesForDependencySubsets() {
     assertSubsetFiles(configurations.twoDepsWithDifferentDependencyConfigurations, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
     assertSubsetFiles(configurations.twoDepsWithDifferentDependencyConfigurations, { dep -> dep.name == 'billfish' }, ['tuna-1.0.jar', 'billfish-1.0.jar', 'squid-1.0.jar'])
     assertSubsetFiles(configurations.depWithMultipleConfigurations, { dep -> dep.configuration == 'default' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.depWithMultipleConfigurations, { dep -> dep.configuration == 'specialWaters' }, ['tuna-1.0.jar', 'squid-1.0.jar'])
     assertSubsetFiles(configurations.extendingWithDifferentConfiguration, { dep -> dep.name != 'seal' }, ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'])
     assertSubsetFiles(configurations.extendingWithDifferentConfiguration, { dep -> dep.name == 'tuna' && dep.configuration == 'default' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
     assertSubsetFiles(configurations.extending, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
     assertSubsetFiles(configurations.selfResolving, { dep -> dep.source.singleFile.name == 'someDir' }, ['someDir'])
     assertSubsetFiles(configurations.mixed, { dep ->
         dep instanceof org.gradle.api.artifacts.SelfResolvingDependency || dep.name == 'tuna' }, ['someDir', 'selfResolving1.jar', 'tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.classifier, { dep -> dep.name == 'dolphin' }, ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar'])
-    assertSubsetFiles(configurations.extendingClassifier, { dep -> dep.name == 'dolphin' }, ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar'])
+    assertSubsetFiles(configurations.classifier, { dep -> dep.name == 'dolphin' }, ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar'])
+    assertSubsetFiles(configurations.extendingClassifier, { dep -> dep.name == 'dolphin' }, ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar'])
 }
 
 def assertSubsetFiles(configuration, spec, expectedFileNames) {
diff --git a/subprojects/docs/src/samples/ear/earCustomized/ear/build.gradle b/subprojects/docs/src/samples/ear/earCustomized/ear/build.gradle
new file mode 100644
index 0000000..eaa0c77
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/ear/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'ear'
+apply plugin: 'java'
+
+repositories { mavenCentral() }
+
+dependencies {
+    //following dependencies will become the ear modules and placed in the ear root
+    deploy project(':war')
+
+    //following dependencies will become ear libs and placed in a dir configured via libDirName property
+    earlib group: 'log4j', name: 'log4j', version: '1.2.15', ext: 'jar'
+}
+
+ear {
+    appDirName 'src/main/app'  // use application metadata found in this folder
+    libDirName 'APP-INF/lib'  // put dependency libraries into APP-INF/lib inside the generated EAR;
+                                // also modify the generated deployment descriptor accordingly
+    deploymentDescriptor {  // custom entries for application.xml:
+//      fileName = "application.xml"  // same as the default value
+//      version = "6"  // same as the default value
+        applicationName = "customear"
+        initializeInOrder = true
+        displayName = "Custom Ear"  // defaults to project.name
+        description = "My customized EAR for the Gradle documentation"  // defaults to project.description
+//      libraryDirectory = "APP-INF/lib"  // not needed, because setting libDirName above did this for us
+//      module("my.jar", "java")  // wouldn't deploy since my.jar isn't a deploy dependency
+//      webModule("my.war", "/")  // wouldn't deploy since my.war isn't a deploy dependency
+        securityRole "admin"
+        securityRole "superadmin"
+        withXml { provider -> // add a custom node to the XML
+            provider.asNode().appendNode("data-source", "my/data/source")
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/ear/earCustomized/ear/readme.xml b/subprojects/docs/src/samples/ear/earCustomized/ear/readme.xml
new file mode 100755
index 0000000..721117a
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/ear/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Web application ear project with customized contents</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earCustomized/ear/src/main/app/META-INF/weblogic-application.xml b/subprojects/docs/src/samples/ear/earCustomized/ear/src/main/app/META-INF/weblogic-application.xml
new file mode 100644
index 0000000..5cb3375
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/ear/src/main/app/META-INF/weblogic-application.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wls:weblogic-application xmlns:wls="http://www.bea.com/ns/weblogic/weblogic-application" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/javaee_6.xsd http://www.bea.com/ns/weblogic/weblogic-application http://www.bea.com/ns/weblogic/weblogic-application/1.0/weblogic-application.xsd">
+    <wls:application-param>
+        <wls:param-name>webapp.encoding.default</wls:param-name>
+        <wls:param-value>UTF-8</wls:param-value>
+    </wls:application-param>
+</wls:weblogic-application>
+
diff --git a/subprojects/docs/src/samples/ear/earCustomized/settings.gradle b/subprojects/docs/src/samples/ear/earCustomized/settings.gradle
new file mode 100644
index 0000000..28e44fa
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/settings.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2011 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.
+ */
+include 'war', 'ear'
diff --git a/subprojects/docs/src/samples/ear/earCustomized/war/build.gradle b/subprojects/docs/src/samples/ear/earCustomized/war/build.gradle
new file mode 100644
index 0000000..ec41505
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/war/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.
+ */
+
+apply plugin: 'war'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'log4j', name: 'log4j', version: '1.2.15', ext: 'jar'
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earCustomized/war/src/main/java/org/gradle/sample/SimpleGreeter.java b/subprojects/docs/src/samples/ear/earCustomized/war/src/main/java/org/gradle/sample/SimpleGreeter.java
new file mode 100644
index 0000000..b79bcdf
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/war/src/main/java/org/gradle/sample/SimpleGreeter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.sample;
+
+import org.apache.log4j.LogManager;
+
+public class SimpleGreeter {
+    public String getGreeting() throws Exception {
+        LogManager.getRootLogger().info("generating greeting.");
+        return "Hello world!";
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earCustomized/war/src/main/webapp/index.jsp b/subprojects/docs/src/samples/ear/earCustomized/war/src/main/webapp/index.jsp
new file mode 100644
index 0000000..118b435
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earCustomized/war/src/main/webapp/index.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="greeter" class="org.gradle.sample.SimpleGreeter"/>
+<html>
+<p>${greeter.greeting}</p>
+</html>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earWithWar/build.gradle b/subprojects/docs/src/samples/ear/earWithWar/build.gradle
new file mode 100644
index 0000000..2137539
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earWithWar/build.gradle
@@ -0,0 +1,13 @@
+// START SNIPPET use-ear-plugin
+apply plugin: 'ear'
+// END SNIPPET use-ear-plugin
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    deploy project(':war')
+
+    earlib group: 'log4j', name: 'log4j', version: '1.2.15', ext: 'jar'
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earWithWar/readme.xml b/subprojects/docs/src/samples/ear/earWithWar/readme.xml
new file mode 100755
index 0000000..80f2c09
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earWithWar/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Web application ear project</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earWithWar/settings.gradle b/subprojects/docs/src/samples/ear/earWithWar/settings.gradle
new file mode 100644
index 0000000..ae7c57e
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earWithWar/settings.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2011 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.
+ */
+include 'war'
diff --git a/subprojects/docs/src/samples/ear/earWithWar/war/build.gradle b/subprojects/docs/src/samples/ear/earWithWar/war/build.gradle
new file mode 100644
index 0000000..ec41505
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earWithWar/war/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.
+ */
+
+apply plugin: 'war'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'log4j', name: 'log4j', version: '1.2.15', ext: 'jar'
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earWithWar/war/src/main/java/org/gradle/sample/SimpleGreeter.java b/subprojects/docs/src/samples/ear/earWithWar/war/src/main/java/org/gradle/sample/SimpleGreeter.java
new file mode 100644
index 0000000..b79bcdf
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earWithWar/war/src/main/java/org/gradle/sample/SimpleGreeter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.sample;
+
+import org.apache.log4j.LogManager;
+
+public class SimpleGreeter {
+    public String getGreeting() throws Exception {
+        LogManager.getRootLogger().info("generating greeting.");
+        return "Hello world!";
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ear/earWithWar/war/src/main/webapp/index.jsp b/subprojects/docs/src/samples/ear/earWithWar/war/src/main/webapp/index.jsp
new file mode 100644
index 0000000..118b435
--- /dev/null
+++ b/subprojects/docs/src/samples/ear/earWithWar/war/src/main/webapp/index.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="greeter" class="org.gradle.sample.SimpleGreeter"/>
+<html>
+<p>${greeter.greeting}</p>
+</html>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/eclipse/build.gradle b/subprojects/docs/src/samples/eclipse/build.gradle
index 0abdb06..317c680 100644
--- a/subprojects/docs/src/samples/eclipse/build.gradle
+++ b/subprojects/docs/src/samples/eclipse/build.gradle
@@ -2,35 +2,33 @@ apply plugin: 'war'
 // START SNIPPET use-plugin
 apply plugin: 'eclipse'
 // END SNIPPET use-plugin
-// START SNIPPET module-before-configured
-// START SNIPPET module-when-configured
-eclipseClasspath {
-// END SNIPPET module-when-configured
-    beforeConfigured { classpath ->
+// START SNIPPET module-before-merged
+// START SNIPPET module-when-merged
+eclipse.classpath.file {
+// END SNIPPET module-when-merged
+    beforeMerged { classpath ->
         classpath.entries.removeAll { entry -> entry.kind == 'lib' || entry.kind == 'var' }
     }
-// END SNIPPET module-before-configured
-// START SNIPPET module-when-configured
-    whenConfigured { classpath ->
+// END SNIPPET module-before-merged
+// START SNIPPET module-when-merged
+    whenMerged { classpath ->
         classpath.entries.findAll { entry -> entry.kind == 'lib' }*.exported = false
     }
-// START SNIPPET module-before-configured
+// START SNIPPET module-before-merged
 }
-// END SNIPPET module-before-configured
-// END SNIPPET module-when-configured
+// END SNIPPET module-before-merged
+// END SNIPPET module-when-merged
 
-// START SNIPPET project-before-configured
-eclipseProject {
-    beforeConfigured { project ->
-        project.natures.clear()
-    }
+// START SNIPPET project-before-merged
+eclipse.project.file.beforeMerged { project ->
+    project.natures.clear()
 }
-// END SNIPPET project-before-configured
+// END SNIPPET project-before-merged
 
 // START SNIPPET wtp-with-xml
-eclipseWtpFacet {
-    withXml { provider ->
-        provider.asNode().fixed.find { it. at facet == 'jst.java' }. at facet = 'jst2.java'
-    }
+apply plugin: 'eclipse-wtp'
+
+eclipse.wtp.facet.file.withXml { provider ->
+    provider.asNode().fixed.find { it. at facet == 'jst.java' }. at facet = 'jst2.java'
 }
-// END SNIPPET wtp-with-xml
+// END SNIPPET wtp-with-xml
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.5.6/build.gradle b/subprojects/docs/src/samples/groovy/groovy-1.5.6/build.gradle
deleted file mode 100644
index de4094d..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.5.6/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'groovy'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    groovy 'org.codehaus.groovy:groovy-all:1.5.6'
-    testCompile 'junit:junit:4.8.2'
-}
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.5.6/readme.xml b/subprojects/docs/src/samples/groovy/groovy-1.5.6/readme.xml
deleted file mode 100644
index 6373cd6..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.5.6/readme.xml
+++ /dev/null
@@ -1,18 +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.
-  -->
-<sample>
-    <para>Groovy project using Groovy 1.5.6</para>
-</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.5.6/src/main/groovy/org/gradle/Person.groovy b/subprojects/docs/src/samples/groovy/groovy-1.5.6/src/main/groovy/org/gradle/Person.groovy
deleted file mode 100644
index 98b99e4..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.5.6/src/main/groovy/org/gradle/Person.groovy
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.gradle
-
-class Person {
-    String name
-}
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.5.6/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/docs/src/samples/groovy/groovy-1.5.6/src/test/groovy/org/gradle/PersonTest.groovy
deleted file mode 100644
index 8504ae5..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.5.6/src/test/groovy/org/gradle/PersonTest.groovy
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.gradle
-
-import org.codehaus.groovy.runtime.InvokerHelper
-import org.junit.Test
-import static org.junit.Assert.*
-
-class PersonTest {
-    @Test public void canConstructAPerson() {
-        Person p = new Person(name: 'name')
-        assertEquals('name', p.name)
-    }
-
-    @Test public void usingCorrectVersionOfGroovy() {
-        assertEquals('1.5.6', InvokerHelper.version)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.6.7/build.gradle b/subprojects/docs/src/samples/groovy/groovy-1.6.7/build.gradle
deleted file mode 100644
index fe926bf..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.6.7/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-apply plugin: 'groovy'
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    groovy 'org.codehaus.groovy:groovy-all:1.6.7'
-    testCompile 'junit:junit:4.8.2'
-}
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.6.7/readme.xml b/subprojects/docs/src/samples/groovy/groovy-1.6.7/readme.xml
deleted file mode 100644
index b00773d..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.6.7/readme.xml
+++ /dev/null
@@ -1,18 +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.
-  -->
-<sample>
-    <para>Groovy project using Groovy 1.6.7</para>
-</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.6.7/src/main/groovy/org/gradle/Person.groovy b/subprojects/docs/src/samples/groovy/groovy-1.6.7/src/main/groovy/org/gradle/Person.groovy
deleted file mode 100644
index 98b99e4..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.6.7/src/main/groovy/org/gradle/Person.groovy
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.gradle
-
-class Person {
-    String name
-}
diff --git a/subprojects/docs/src/samples/groovy/groovy-1.6.7/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/docs/src/samples/groovy/groovy-1.6.7/src/test/groovy/org/gradle/PersonTest.groovy
deleted file mode 100644
index 008adf9..0000000
--- a/subprojects/docs/src/samples/groovy/groovy-1.6.7/src/test/groovy/org/gradle/PersonTest.groovy
+++ /dev/null
@@ -1,31 +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
-
-import org.codehaus.groovy.runtime.InvokerHelper
-import org.junit.Test
-import static org.junit.Assert.*
-
-class PersonTest {
-    @Test public void canConstructAPerson() {
-        Person p = new Person(name: 'name')
-        assertEquals('name', p.name)
-    }
-
-    @Test public void usingCorrectVersionOfGroovy() {
-        assertEquals('1.6.7', InvokerHelper.version)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle
new file mode 100644
index 0000000..da3d5c5
--- /dev/null
+++ b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/build.gradle
@@ -0,0 +1,7 @@
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile group: 'junit', name: 'junit', version: '4.8.2'
+}
diff --git a/subprojects/docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.groovy b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.groovy
new file mode 100644
index 0000000..1f81ebb
--- /dev/null
+++ b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.groovy
@@ -0,0 +1,7 @@
+package org.gradle.buildsrc
+
+import groovy.util.AntBuilder
+
+class BuildSrcClass {
+
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.java b/subprojects/docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.java
deleted file mode 100644
index 1f61639..0000000
--- a/subprojects/docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.gradle.buildsrc;
-
-import groovy.util.AntBuilder;
-
-public class BuildSrcClass {
-
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
index 5e62afb..eb9ff22 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
+++ b/subprojects/docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
@@ -3,5 +3,5 @@ apply plugin: 'java'
 version = 'SNAPSHOT'
 
 dependencies {
-    compile 'org.codehaus.groovy:groovy-all:1.6.7'
+    compile 'org.codehaus.groovy:groovy-all:1.7.10'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle b/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
index 1534fc4..7d7e909 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/build.gradle
@@ -4,7 +4,7 @@ group = 'org.gradle'
 version = '1.0'
 
 dependencies {
-    groovy 'org.codehaus.groovy:groovy-all:1.7.10'
+    groovy 'org.codehaus.groovy:groovy-all:1.8.4'
     compile project(':groovycDetector')
     testCompile 'junit:junit:4.8.2'
 }
diff --git a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
index 99247e9..e1a64a8 100644
--- a/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
+++ b/subprojects/docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
@@ -7,7 +7,7 @@ class GroovycVersionTest {
   def groovycVersion
 
   @Test
-  void versionShouldBe1_7_10() {
-    assertEquals("1.7.10", groovycVersion)
+  void versionShouldBe1_8_4() {
+    assertEquals("1.8.4", groovycVersion)
   }
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/idea/build.gradle b/subprojects/docs/src/samples/idea/build.gradle
index 90e1e53..ed7269f 100644
--- a/subprojects/docs/src/samples/idea/build.gradle
+++ b/subprojects/docs/src/samples/idea/build.gradle
@@ -1,36 +1,36 @@
 // START SNIPPET use-plugin
 apply plugin: 'idea'
 // END SNIPPET use-plugin
-// START SNIPPET module-before-configured
-// START SNIPPET module-when-configured
-ideaModule {
-// END SNIPPET module-when-configured
-    beforeConfigured { module ->
+// START SNIPPET module-before-merged
+// START SNIPPET module-when-merged
+idea.module.iml {
+// END SNIPPET module-when-merged
+    beforeMerged { module ->
         module.dependencies.clear()
     }
-// END SNIPPET module-before-configured
-// START SNIPPET module-when-configured
-    whenConfigured { module ->
+// END SNIPPET module-before-merged
+// START SNIPPET module-when-merged
+    whenMerged { module ->
         module.dependencies*.exported = true
     }
-// START SNIPPET module-before-configured
+// START SNIPPET module-before-merged
 }
-// END SNIPPET module-before-configured
-// END SNIPPET module-when-configured
+// END SNIPPET module-before-merged
+// END SNIPPET module-when-merged
 
-// START SNIPPET project-before-configured
+// START SNIPPET project-before-merged
 // START SNIPPET project-with-xml
-ideaProject {
+idea.project.ipr {
 // END SNIPPET project-with-xml
-    beforeConfigured { project ->
+    beforeMerged { project ->
         project.modulePaths.clear()
     }
-// END SNIPPET project-before-configured
+// END SNIPPET project-before-merged
 // START SNIPPET project-with-xml
     withXml { provider ->
         provider.node.component.find { it. at name == 'VcsDirectoryMappings' }.mapping. at vcs = 'Git'
     }
-// START SNIPPET project-before-configured
+// START SNIPPET project-before-merged
 }
-// END SNIPPET project-before-configured
+// END SNIPPET project-before-merged
 // END SNIPPET project-with-xml
diff --git a/subprojects/docs/src/samples/ivypublish/build.gradle b/subprojects/docs/src/samples/ivypublish/build.gradle
index c376953..79a97a1 100644
--- a/subprojects/docs/src/samples/ivypublish/build.gradle
+++ b/subprojects/docs/src/samples/ivypublish/build.gradle
@@ -15,17 +15,9 @@ ant {
     buildDir = owner.buildDir.toString()
 }
 
-String ivyPattern = '/[module]/[revision]/ivy.xml'
-String artifactPattern = '/[module]/[revision]/[artifact](.[ext])'
 File localPublicationsDir = file("$buildDir/repo")
 
 repositories {
-    add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-        name = 'repo'
-        validate = false
-        addIvyPattern(localPublicationsDir.absolutePath + ivyPattern)
-        addArtifactPattern(localPublicationsDir.absolutePath + artifactPattern)
-    }
     mavenCentral()
 }
 
@@ -42,7 +34,10 @@ artifacts {
 uploadArchives {
     uploadDescriptor = true
     repositories {
-        add project.repositories.repo
+        ivy {
+            ivyPattern localPublicationsDir.absolutePath + '/[module]/[revision]/ivy.xml'
+            artifactPattern localPublicationsDir.absolutePath + '/[module]/[revision]/[artifact](.[ext])'
+        }
     }
     doLast {
         File repoDir = new File("$buildDir/repo/ivypublish/1.0/")
@@ -53,7 +48,7 @@ uploadArchives {
         def ns = new groovy.xml.Namespace("http://ant.apache.org/ivy/maven", 'm')
         def root = new XmlParser().parse(new File(repoDir, 'ivy.xml'))
         assert root.publications.artifact.find { it. at name == 'ivypublishSource' }.attribute(ns.classifier) == 'src'
-        assert (root.configurations.conf.collect { it. at name } as Set) == ['archives', 'compile', 'default', 'runtime','testCompile', 'testRuntime']  as Set
+        assert (root.configurations.conf.collect { it. at name } as Set) == ['archives', 'compile', 'default', 'runtime', 'testCompile', 'testRuntime'] as Set
         assert root.dependencies.dependency.find { it. at org == 'junit' }.attributes() == [org: 'junit', name: 'junit', rev: '4.8.2', conf: 'compile->default']
         assert root.dependencies.dependency.find { it. at org == 'ivypublish' }.attributes() == [org: 'ivypublish', name: 'subproject', rev: 'unspecified',
                 conf: 'compile->default']
diff --git a/subprojects/docs/src/samples/java/apiAndImpl/build.gradle b/subprojects/docs/src/samples/java/apiAndImpl/build.gradle
new file mode 100644
index 0000000..c3958ec
--- /dev/null
+++ b/subprojects/docs/src/samples/java/apiAndImpl/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: "java"
+apply plugin: "maven"
+
+group = "myorg"
+version = 1.0
+
+repositories {
+    mavenCentral()
+}
+
+sourceSets.all { set ->
+    def jarTask = task("${set.name}Jar", type: Jar) {
+        baseName = baseName + "-$set.name"
+        from set.output
+    }
+
+    artifacts {
+        archives jarTask
+    }
+}
+
+sourceSets {
+    api
+    impl
+}
+
+dependencies {
+    apiCompile 'commons-codec:commons-codec:1.5'
+
+    implCompile sourceSets.api.output
+    implCompile 'commons-lang:commons-lang:2.6'
+
+    testCompile 'junit:junit:4.9'
+    testCompile sourceSets.api.output
+    testCompile sourceSets.impl.output
+    runtime configurations.apiRuntime
+    runtime configurations.implRuntime
+}
+
+jar {
+    from sourceSets.api.output
+    from sourceSets.impl.output
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("${buildDir}/repo"))
+
+            addFilter("main") { artifact, file -> artifact.name == project.name }
+            ["api", "impl"].each { type ->
+                addFilter(type) { artifact, file -> artifact.name.endsWith("-$type") }
+                
+                // We now have to map our configurations to the correct maven scope for each pom
+                ["compile", "runtime"].each { scope ->
+                    configuration = configurations[type + scope.capitalize()]
+                    ["main", type].each { pomName ->
+                        pom(pomName).scopeMappings.addMapping 1, configuration, scope
+                    }
+                }
+            }
+
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/java/apiAndImpl/src/api/java/doubler/Doubler.java b/subprojects/docs/src/samples/java/apiAndImpl/src/api/java/doubler/Doubler.java
new file mode 100644
index 0000000..93913e9
--- /dev/null
+++ b/subprojects/docs/src/samples/java/apiAndImpl/src/api/java/doubler/Doubler.java
@@ -0,0 +1,5 @@
+package doubler;
+
+public interface Doubler {
+    int doubleIt(int toDouble);
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/java/apiAndImpl/src/impl/java/doubler/impl/DoublerImpl.java b/subprojects/docs/src/samples/java/apiAndImpl/src/impl/java/doubler/impl/DoublerImpl.java
new file mode 100644
index 0000000..28f4f6e
--- /dev/null
+++ b/subprojects/docs/src/samples/java/apiAndImpl/src/impl/java/doubler/impl/DoublerImpl.java
@@ -0,0 +1,9 @@
+package doubler.impl;
+
+import doubler.Doubler;
+
+public class DoublerImpl implements Doubler {
+    public int doubleIt(int toDouble) {
+        return toDouble * 2;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/java/apiAndImpl/src/test/java/doubler/impl/DoublerImplTest.java b/subprojects/docs/src/samples/java/apiAndImpl/src/test/java/doubler/impl/DoublerImplTest.java
new file mode 100644
index 0000000..7007567
--- /dev/null
+++ b/subprojects/docs/src/samples/java/apiAndImpl/src/test/java/doubler/impl/DoublerImplTest.java
@@ -0,0 +1,13 @@
+package doubler.impl;
+
+import doubler.Doubler;
+import org.junit.Test;
+
+public class DoublerImplTest {
+    
+    @Test
+    public void testIt() {
+        Doubler doubler = new DoublerImpl();
+        assert doubler.doubleIt(2) == 4;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/java/base/build.gradle b/subprojects/docs/src/samples/java/base/build.gradle
index 762aa87..38988bc 100644
--- a/subprojects/docs/src/samples/java/base/build.gradle
+++ b/subprojects/docs/src/samples/java/base/build.gradle
@@ -8,16 +8,10 @@ subprojects {
         mavenCentral()
     }
 
-    configurations {
-        compile
-    }
-
     sourceSets {
         main {
             java.srcDir "$projectDir/java"
             resources.srcDir "$projectDir/java"
-            compileClasspath = configurations.compile
-            runtimeClasspath = compileClasspath + classes
         }
     }
 }
diff --git a/subprojects/docs/src/samples/java/base/prod/build.gradle b/subprojects/docs/src/samples/java/base/prod/build.gradle
index 4c6444e..c3d4929 100644
--- a/subprojects/docs/src/samples/java/base/prod/build.gradle
+++ b/subprojects/docs/src/samples/java/base/prod/build.gradle
@@ -1,17 +1,12 @@
-
-configurations {
-    getByName('default').extendsFrom(compile)
-}
-
 dependencies {
     compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    "default" configurations.runtime
 }
 
 task jar(type: Jar) {
-    from sourceSets.main.classes
+    from sourceSets.main.output
 }
 
 artifacts {
-    archives jar
+    runtime jar
 }
-
diff --git a/subprojects/docs/src/samples/java/base/test/build.gradle b/subprojects/docs/src/samples/java/base/test/build.gradle
index 11e970f..fc4b45f 100644
--- a/subprojects/docs/src/samples/java/base/test/build.gradle
+++ b/subprojects/docs/src/samples/java/base/test/build.gradle
@@ -3,7 +3,7 @@ dependencies {
 }
 
 task test(type: Test) {
-    testClassesDir = sourceSets.main.classesDir
+    testClassesDir = sourceSets.main.output.classesDir
     classpath = sourceSets.main.runtimeClasspath
 }
 
diff --git a/subprojects/docs/src/samples/java/customizedLayout/readme.xml b/subprojects/docs/src/samples/java/customizedLayout/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/java/multiproject/api/build.gradle b/subprojects/docs/src/samples/java/multiproject/api/build.gradle
index 07a79ec..43d1bdc 100644
--- a/subprojects/docs/src/samples/java/multiproject/api/build.gradle
+++ b/subprojects/docs/src/samples/java/multiproject/api/build.gradle
@@ -31,7 +31,7 @@ compileJava.options.compilerArgs = ['-Xlint:unchecked']
 
 task spiJar(type: Jar) {
     appendix = 'spi'
-    from sourceSets.main.classes
+    from sourceSets.main.output
     include 'org/gradle/api/'
 }
 
@@ -48,6 +48,10 @@ task dist(type: Zip) {
         from configurations.runtime
     }
 }
+
+artifacts {
+   archives dist
+}
 // END SNIPPET dists
 
 // We want to test if commons-math was properly added to the build script classpath
diff --git a/subprojects/docs/src/samples/java/multiproject/build.gradle b/subprojects/docs/src/samples/java/multiproject/build.gradle
index 51a8299..1573a9b 100644
--- a/subprojects/docs/src/samples/java/multiproject/build.gradle
+++ b/subprojects/docs/src/samples/java/multiproject/build.gradle
@@ -1,7 +1,7 @@
 // START SNIPPET configuration-injection
 subprojects {
     apply plugin: 'java'
-    apply plugin: 'eclipse'
+    apply plugin: 'eclipse-wtp'
 
     repositories {
        mavenCentral()
diff --git a/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle b/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle
index ad3506c..da3d5c5 100644
--- a/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle
+++ b/subprojects/docs/src/samples/java/multiproject/buildSrc/build.gradle
@@ -1,14 +1,7 @@
-apply plugin: 'java'
-
 repositories {
     mavenCentral()
 }
 
-// START SNIPPET gradle-api-dependencies
 dependencies {
-    compile gradleApi()
-// END SNIPPET gradle-api-dependencies
     testCompile group: 'junit', name: 'junit', version: '4.8.2'
-// START SNIPPET gradle-api-dependencies
 }
-// END SNIPPET gradle-api-dependencies
diff --git a/subprojects/docs/src/samples/java/multiproject/readme.xml b/subprojects/docs/src/samples/java/multiproject/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/java/quickstart/build.gradle b/subprojects/docs/src/samples/java/quickstart/build.gradle
index 8e274b9..f1b1d1d 100644
--- a/subprojects/docs/src/samples/java/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/java/quickstart/build.gradle
@@ -37,7 +37,9 @@ test {
 // START SNIPPET upload
 uploadArchives {
     repositories {
-       flatDir(dirs: file('repos'))
+       flatDir {
+           dirs 'repos'
+       }
     }
 }
 // END SNIPPET upload
diff --git a/subprojects/docs/src/samples/java/quickstart/readme.xml b/subprojects/docs/src/samples/java/quickstart/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle b/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle
index a6d81a8..2c397e2 100644
--- a/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle
+++ b/subprojects/docs/src/samples/java/withIntegrationTests/build.gradle
@@ -4,33 +4,26 @@ repositories {
     mavenCentral()
 }
 
-configurations {
-    integrationTestCompile {
-        extendsFrom testCompile
-    }
-    integrationTestRuntime {
-        extendsFrom integrationTestCompile, testRuntime
+sourceSets {
+    integrationTest {
+        java.srcDir file('src/integration-test/java')
+        resources.srcDir file('src/integration-test/resources')
     }
 }
 
 dependencies {
     testCompile group: 'junit', name: 'junit', version: '4.8.2'
     integrationTestCompile group: 'commons-collections', name: 'commons-collections', version: '3.2'
-}
-
-sourceSets {
-    integrationTest {
-        java.srcDir file('src/integration-test/java')
-        resources.srcDir file('src/integration-test/resources')
-        compileClasspath = sourceSets.main.classes + sourceSets.test.classes + configurations.integrationTestCompile
-        runtimeClasspath = classes + compileClasspath + configurations.integrationTestRuntime
-    }
+    integrationTestCompile sourceSets.main.output
+    integrationTestCompile configurations.testCompile
+    integrationTestCompile sourceSets.test.output
+    integrationTestRuntime configurations.testRuntime
 }
 
 task integrationTest(type: Test, dependsOn: jar) {
-    testClassesDir = sourceSets.integrationTest.classesDir
+    testClassesDir = sourceSets.integrationTest.output.classesDir
     classpath = sourceSets.integrationTest.runtimeClasspath
     systemProperties['jar.path'] = jar.archivePath
 }
 
-build.dependsOn integrationTest
+check.dependsOn integrationTest
diff --git a/subprojects/docs/src/samples/java/withIntegrationTests/readme.xml b/subprojects/docs/src/samples/java/withIntegrationTests/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/build.gradle b/subprojects/docs/src/samples/maven/pomGeneration/build.gradle
index 3328ca2..5675d65 100644
--- a/subprojects/docs/src/samples/maven/pomGeneration/build.gradle
+++ b/subprojects/docs/src/samples/maven/pomGeneration/build.gradle
@@ -9,7 +9,9 @@ archivesBaseName = 'mywar'
 buildDir = 'target'
 
 repositories {
-    flatDir(dirs: "$projectDir/lib")
+    flatDir {
+        dirs "lib"
+    }
 }
 
 configurations {
@@ -45,10 +47,9 @@ artifacts {
 
 // Configure the release and snapshot repositories
 
-def deployer = null
 uploadArchives {
     repositories {
-        deployer = mavenDeployer {
+        mavenDeployer {
             repository(url: uri("pomRepo"))
             snapshotRepository(url: uri("snapshotRepo"))
         }
@@ -57,15 +58,14 @@ uploadArchives {
 
 // Customize the contents of the pom
 
-installer = install.repositories.mavenInstaller
+ext.installer = install.repositories.mavenInstaller
+ext.deployer = uploadArchives.repositories.mavenDeployer
 
-if (hasProperty('customVersion')) {
-    [installer, deployer]*.pom*.version = customVersion
-    installer.pom.project {
-        groupId 'installGroup'
-    }
-    deployer.pom.groupId = 'deployGroup'
+[installer, deployer]*.pom*.version = '1.0MVN'
+installer.pom.project {
+    groupId 'installGroup'
 }
+deployer.pom.groupId = 'deployGroup'
 
 // START SNIPPET new-pom
 task writeNewPom << {
@@ -93,15 +93,3 @@ task writeNewPom << {
 task writeDeployerPom(dependsOn: uploadArchives) << {
     deployer.pom.writeTo("$buildDir/deployerpom.xml")
 }
-
-// For our integration tests
-
-install.doLast {
-    def settings = installer.settings
-    new File(buildDir, "localRepoPath.txt").write(settings.localRepository)
-}
-
-clean {
-    delete 'pomRepo'
-    delete 'snapshotRepo'
-}
diff --git a/subprojects/docs/src/samples/maven/pomGeneration/readme.xml b/subprojects/docs/src/samples/maven/pomGeneration/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/maven/quickstart/build.gradle b/subprojects/docs/src/samples/maven/quickstart/build.gradle
old mode 100644
new mode 100755
index afe7d51..c7cff5e
--- a/subprojects/docs/src/samples/maven/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/maven/quickstart/build.gradle
@@ -16,14 +16,3 @@ uploadArchives {
         }
     }
 }
-
-// For our integration tests
-
-install.doLast {
-    def settings = repositories.mavenInstaller.settings
-    new File(buildDir, "localRepoPath.txt").write(settings.localRepository)
-}
-
-clean {
-    delete 'pomRepo'
-}
diff --git a/subprojects/docs/src/samples/maven/quickstart/readme.xml b/subprojects/docs/src/samples/maven/quickstart/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/maven/quickstart/src/main/java/org/MyClass.java b/subprojects/docs/src/samples/maven/quickstart/src/main/java/org/MyClass.java
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/mavenRepo/build.gradle b/subprojects/docs/src/samples/mavenRepo/build.gradle
deleted file mode 100644
index 0bc7130..0000000
--- a/subprojects/docs/src/samples/mavenRepo/build.gradle
+++ /dev/null
@@ -1,103 +0,0 @@
-
-sillyexceptions = 'sillyexceptions'
-repotest = 'repotest'
-
-/*
- * 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.
- */
-
-/*
- * gradle_sourceforge:
- * - repotest
- * -- repotest
- * --- 1.0
- * ---- repotest-1.0.pom (-> testdep-1.0)
- *
- * - repotest
- * -- classifier
- * --- 1.0
- * ---- classifier-1.0.pom (-> classifier-dep-1.0)
- * ---- classifier-1.0-jdk14.jar
- * ---- classifier-1.0-jdk15.jar
- *
- * - repotest
- * -- classifier-dep
- * --- 1.0
- * ---- classifier-dep-1.0.pom
- * ---- classifier-dep-1.0.jar
- *
- * gradle_sourceforge2
- * - repotest
- * -- repotest
- * --- 1.0
- * ---- repotest-1.0.jar
- *
- * - testdep
- * -- testdep
- * --- 1.0
- * ---- testdep-1.0.pom
- * ---- testdep-1.0.jar
- *
- * - testdep2
- * -- testdep2
- * --- 1.0
- * ---- testdep2-1.0.jar
- * ---- testdep2-1.0.pom
- *
- * - jaronly
- * -- jaronly
- * --- 1.0
- * ---- jaronly-1.0.jar
- *
- * Maven Repo:
- *
- * - sillyexceptions
- * -- sillyexceptions
- * --- 1.0.1
- * ---- sillyexceptions-1.0.1.jar
- * ---- sillyexceptions-1.0.1.pom
- *
- * Transitive Dependencies
- *
- * repotest -> testdep
- * testdep -> testdep2
- */
-
-configurations {
-    test
-}
-
-repositories {
-    mavenRepo(urls: ['http://gradle.sourceforge.net/repository/', 'http://gradle.sourceforge.net/otherrepo/']).allownomd = false
-    mavenRepo(urls: 'http://gradle.sourceforge.net/otherrepo/')
-    mavenCentral()
-}
-
-dependencies {
-    test "$sillyexceptions:$sillyexceptions:1.0.1 at jar", "$repotest:$repotest:1.0", "$repotest:classifier:1.0:jdk15", "jaronly:jaronly:1.0"
-}
-
-task retrieve << {
-    delete buildDir
-    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/$sillyexceptions")
-    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/$repotest")
-    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/testdep")
-    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/testdep2")
-    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/jaronly")
-    copy {
-        from configurations.test
-        into buildDir
-    }
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/build.gradle b/subprojects/docs/src/samples/multiProjectBuildSrc/build.gradle
new file mode 100644
index 0000000..d3f3ec8
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: "plugina"
+apply plugin: "pluginb"
+
+task showPlugins << {
+  project.plugins.each {
+    println it.getClass().name
+  }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle
new file mode 100644
index 0000000..c2bafa7
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/build.gradle
@@ -0,0 +1,14 @@
+subprojects {
+  apply plugin: "groovy"
+
+  dependencies {
+    groovy localGroovy()
+    compile gradleApi()
+  }
+
+// START SNIPPET addToRootProject
+rootProject.dependencies {
+  runtime project(path)
+}
+// END SNIPPET addToRootProject
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/plugina/src/main/groovy/plugina/PluginA.groovy b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/plugina/src/main/groovy/plugina/PluginA.groovy
new file mode 100644
index 0000000..fc79cff
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/plugina/src/main/groovy/plugina/PluginA.groovy
@@ -0,0 +1,10 @@
+package plugina
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+public class PluginA implements Plugin<Project> {
+
+    void apply(Project project) {}
+
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/plugina/src/main/resources/META-INF/gradle-plugins/plugina.properties b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/plugina/src/main/resources/META-INF/gradle-plugins/plugina.properties
new file mode 100644
index 0000000..ea11cec
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/plugina/src/main/resources/META-INF/gradle-plugins/plugina.properties
@@ -0,0 +1 @@
+implementation-class=plugina.PluginA
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/pluginb/src/main/groovy/pluginb/PluginB.groovy b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/pluginb/src/main/groovy/pluginb/PluginB.groovy
new file mode 100644
index 0000000..36fbb7b
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/pluginb/src/main/groovy/pluginb/PluginB.groovy
@@ -0,0 +1,10 @@
+package pluginb
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+class PluginB implements Plugin<Project> {
+
+    void apply(Project project) {}
+
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/pluginb/src/main/resources/META-INF/gradle-plugins/pluginb.properties b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/pluginb/src/main/resources/META-INF/gradle-plugins/pluginb.properties
new file mode 100644
index 0000000..2b6a9c4
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/pluginb/src/main/resources/META-INF/gradle-plugins/pluginb.properties
@@ -0,0 +1 @@
+implementation-class=pluginb.PluginB
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/settings.gradle b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/settings.gradle
new file mode 100644
index 0000000..6fbcc24
--- /dev/null
+++ b/subprojects/docs/src/samples/multiProjectBuildSrc/buildSrc/settings.gradle
@@ -0,0 +1 @@
+include "plugina", "pluginb"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/osgi/build.gradle b/subprojects/docs/src/samples/osgi/build.gradle
index 57a65f8..6144f06 100644
--- a/subprojects/docs/src/samples/osgi/build.gradle
+++ b/subprojects/docs/src/samples/osgi/build.gradle
@@ -8,12 +8,14 @@ apply plugin: 'osgi'
 
 repositories {
     mavenCentral()
-    mavenRepo(urls: 'http://repository.jboss.org/maven2/')
+    maven {
+        url 'http://repository.jboss.org/maven2/'
+    }
 }
 
 dependencies {
     groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.10'
-    compile group: 'org.eclipse', name: 'osgi', version: '3.4.3.R34x_v20081215-1030'
+    compile group: 'org.eclipse', name: 'osgi', version: '3.5.0.v20090520'
 }
 
 jar {
diff --git a/subprojects/docs/src/samples/osgi/readme.xml b/subprojects/docs/src/samples/osgi/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/scala/customizedLayout/readme.xml b/subprojects/docs/src/samples/scala/customizedLayout/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/scala/fsc/readme.xml b/subprojects/docs/src/samples/scala/fsc/readme.xml
index 509c320..2713ac0 100644
--- a/subprojects/docs/src/samples/scala/fsc/readme.xml
+++ b/subprojects/docs/src/samples/scala/fsc/readme.xml
@@ -1,3 +1,3 @@
 <sample>
-    <para>Sala project using the Fast Scala Compiler (fsc).</para>
+    <para>Scala project using the Fast Scala Compiler (fsc).</para>
 </sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/scala/mixedJavaAndScala/readme.xml b/subprojects/docs/src/samples/scala/mixedJavaAndScala/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/scala/quickstart/readme.xml b/subprojects/docs/src/samples/scala/quickstart/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/signing/conditional/build.gradle b/subprojects/docs/src/samples/signing/conditional/build.gradle
new file mode 100644
index 0000000..0a9b7f8
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/conditional/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+group = 'gradle'
+
+// START SNIPPET conditional-signing
+version = '1.0-SNAPSHOT'
+ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
+
+signing {
+    required { isReleaseVersion && gradle.taskGraph.hasTask("uploadArchives") }
+    sign configurations.archives
+}
+// END SNIPPET conditional-signing
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("${buildDir}/repo"))
+            if (isReleaseVersion) {
+                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+            }
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/signing/conditional/src/main/java/Sample.java b/subprojects/docs/src/samples/signing/conditional/src/main/java/Sample.java
new file mode 100644
index 0000000..c29d257
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/conditional/src/main/java/Sample.java
@@ -0,0 +1,2 @@
+class Sample {
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/conditional/src/main/resources/sample.txt b/subprojects/docs/src/samples/signing/conditional/src/main/resources/sample.txt
new file mode 100644
index 0000000..2a865d1
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/conditional/src/main/resources/sample.txt
@@ -0,0 +1 @@
+some resource.
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/maven/build.gradle b/subprojects/docs/src/samples/signing/maven/build.gradle
new file mode 100644
index 0000000..f47d9dd
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/maven/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+
+// START SNIPPET use-plugin
+apply plugin: 'signing'
+// END SNIPPET use-plugin
+
+group = 'gradle'
+version = '1.0'
+
+// Typically set in ~/.gradle/gradle.properties
+project['signing.keyId'] = "24875D73"
+project['signing.password'] = "gradle"
+project['signing.secretKeyRingFile'] = file("secKeyRingFile.gpg").absolutePath
+
+// START SNIPPET sign-archives
+signing {
+    sign configurations.archives
+}
+// END SNIPPET sign-archives
+
+// START SNIPPET sign-pom
+uploadArchives {
+    repositories {
+        mavenDeployer {
+// END SNIPPET sign-pom
+            repository(url: uri("${buildDir}/repo"))
+// START SNIPPET sign-pom
+            beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+        }
+    }
+}
+// END SNIPPET sign-pom
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/maven/gradle.properties b/subprojects/docs/src/samples/signing/maven/gradle.properties
new file mode 100644
index 0000000..17498d3
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/maven/gradle.properties
@@ -0,0 +1,7 @@
+/*
+// START SNIPPET user-properties
+signing.keyId=24875D73
+signing.password=secret
+signing.secretKeyRingFile=/Users/me/.gnupg/secring.gpg
+// END SNIPPET user-properties
+*/
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/maven/secKeyRingFile.gpg b/subprojects/docs/src/samples/signing/maven/secKeyRingFile.gpg
new file mode 100644
index 0000000..47a24fe
Binary files /dev/null and b/subprojects/docs/src/samples/signing/maven/secKeyRingFile.gpg differ
diff --git a/subprojects/docs/src/samples/signing/maven/src/main/java/Sample.java b/subprojects/docs/src/samples/signing/maven/src/main/java/Sample.java
new file mode 100644
index 0000000..c29d257
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/maven/src/main/java/Sample.java
@@ -0,0 +1,2 @@
+class Sample {
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/maven/src/main/resources/sample.txt b/subprojects/docs/src/samples/signing/maven/src/main/resources/sample.txt
new file mode 100644
index 0000000..2a865d1
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/maven/src/main/resources/sample.txt
@@ -0,0 +1 @@
+some resource.
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/tasks/build.gradle b/subprojects/docs/src/samples/signing/tasks/build.gradle
new file mode 100644
index 0000000..3e4a4b0
--- /dev/null
+++ b/subprojects/docs/src/samples/signing/tasks/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: "signing"
+
+// Typically set in ~/.gradle/gradle.properties
+project.ext['signing.keyId'] = "24875D73"
+project.ext['signing.password'] = "gradle"
+project.ext['signing.secretKeyRingFile'] = file("secKeyRingFile.gpg").absolutePath
+
+// START SNIPPET sign-task
+task stuffZip (type: Zip) {
+    baseName = "stuff"
+    from "src/stuff"
+}
+
+signing {
+    sign stuffZip
+}
+// END SNIPPET sign-task
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/signing/tasks/secKeyRingFile.gpg b/subprojects/docs/src/samples/signing/tasks/secKeyRingFile.gpg
new file mode 100644
index 0000000..47a24fe
Binary files /dev/null and b/subprojects/docs/src/samples/signing/tasks/secKeyRingFile.gpg differ
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/docs/src/samples/signing/tasks/src/stuff/hello.txt
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/docs/src/samples/signing/tasks/src/stuff/hello.txt
diff --git a/subprojects/docs/src/samples/sonar/advanced/build.gradle b/subprojects/docs/src/samples/sonar/advanced/build.gradle
new file mode 100644
index 0000000..898fd2a
--- /dev/null
+++ b/subprojects/docs/src/samples/sonar/advanced/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: "java"
+apply plugin: "sonar"
+
+sourceSets {
+    custom
+    integTest
+}
+
+// START SNIPPET source-sets
+sonar.project {
+    sourceDirs += sourceSets.custom.allSource.srcDirs
+    testDirs += sourceSets.integTest.allSource.srcDirs
+}
+// END SNIPPET source-sets
+
+// START SNIPPET global-properties
+sonar.withGlobalProperties { props ->
+    props["some.global.property"] = "some value"
+}
+// END SNIPPET global-properties
+
+// START SNIPPET project-properties
+sonar.project.withProjectProperties { props ->
+    props["some.project.property"] = "some value"
+}
+// END SNIPPET project-properties
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/sonar/build.gradle b/subprojects/docs/src/samples/sonar/build.gradle
deleted file mode 100644
index ab14832..0000000
--- a/subprojects/docs/src/samples/sonar/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-apply plugin: "java"
-
-// START SNIPPET use-plugin
-apply plugin: "sonar"
-// END SNIPPET use-plugin
-
-// START SNIPPET connection-settings
-sonar {
-    serverUrl = "http://my.server.com"
-
-    globalProperty "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
-    globalProperty "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
-    globalProperty "sonar.jdbc.username", "myusername"
-    globalProperty "sonar.jdbc.password", "mypassword"
-}
-// END SNIPPET connection-settings
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
-    testCompile group: 'junit', name: 'junit', version: '4.+'
-}
diff --git a/subprojects/docs/src/samples/sonar/multiProject/build.gradle b/subprojects/docs/src/samples/sonar/multiProject/build.gradle
new file mode 100644
index 0000000..035ad78
--- /dev/null
+++ b/subprojects/docs/src/samples/sonar/multiProject/build.gradle
@@ -0,0 +1,49 @@
+// START SNIPPET global-configuration
+apply plugin: "sonar"
+
+sonar {
+    server {
+        url = "http://my.server.com"
+    }
+    database {
+        url = "jdbc:mysql://my.server.com/sonar"
+        driverClassName = "com.mysql.jdbc.Driver"
+        username = "Fred Flintstone"
+        password = "very clever"
+    }
+}
+// END SNIPPET global-configuration
+
+// START SNIPPET common-project-configuration
+subprojects {
+    sonar {
+        project {
+            sourceEncoding = "UTF-8"
+        }
+    }
+}
+// END SNIPPET common-project-configuration
+
+// START SNIPPET individual-project-configuration
+project(":project1") {
+    sonar {
+        project {
+            skip = true
+        }
+    }
+}
+// END SNIPPET individual-project-configuration
+
+// START SNIPPET language-configuration
+project(":project2") {
+    sonar {
+        project {
+            language = "groovy"
+        }
+    }
+}
+// END SNIPPET language-configuration
+
+// START SNIPPET property-syntax
+project(":project2").sonar.project.language = "groovy"
+// END SNIPPET property-syntax
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/sonar/multiProject/settings.gradle b/subprojects/docs/src/samples/sonar/multiProject/settings.gradle
new file mode 100644
index 0000000..e07a6ab
--- /dev/null
+++ b/subprojects/docs/src/samples/sonar/multiProject/settings.gradle
@@ -0,0 +1 @@
+include "project1", "project2"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/sonar/quickstart/build.gradle b/subprojects/docs/src/samples/sonar/quickstart/build.gradle
new file mode 100644
index 0000000..04e5b5b
--- /dev/null
+++ b/subprojects/docs/src/samples/sonar/quickstart/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: "java"
+
+// START SNIPPET apply-plugin
+apply plugin: "sonar"
+// END SNIPPET apply-plugin
+
+// START SNIPPET connection-settings
+sonar {
+    server {
+        url = "http://my.server.com"
+    }
+    database {
+        url = "jdbc:mysql://my.server.com/sonar"
+        driverClassName = "com.mysql.jdbc.Driver"
+        username = "Fred Flintstone"
+        password = "very clever"
+    }
+}
+// END SNIPPET connection-settings
+
+// START SNIPPET project-settings
+sonar {
+    project {
+        coberturaReportPath = file("$buildDir/cobertura.xml")
+    }
+}
+// END SNIPPET project-settings
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    testCompile group: 'junit', name: 'junit', version: '4.+'
+}
diff --git a/subprojects/docs/src/samples/sonar/src/main/java/org/gradle/Person.java b/subprojects/docs/src/samples/sonar/quickstart/src/main/java/org/gradle/Person.java
similarity index 100%
rename from subprojects/docs/src/samples/sonar/src/main/java/org/gradle/Person.java
rename to subprojects/docs/src/samples/sonar/quickstart/src/main/java/org/gradle/Person.java
diff --git a/subprojects/docs/src/samples/sonar/src/test/java/org/gradle/PersonTest.java b/subprojects/docs/src/samples/sonar/quickstart/src/test/java/org/gradle/PersonTest.java
similarity index 100%
rename from subprojects/docs/src/samples/sonar/src/test/java/org/gradle/PersonTest.java
rename to subprojects/docs/src/samples/sonar/quickstart/src/test/java/org/gradle/PersonTest.java
diff --git a/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle b/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle
index e589ea9..9839ede 100644
--- a/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle
+++ b/subprojects/docs/src/samples/testng/java-jdk15-passing/build.gradle
@@ -7,7 +7,7 @@ repositories {
 }
 
 dependencies {
-    testCompile 'org.testng:testng:5.14.10'
+    testCompile 'org.testng:testng:6.3.1'
 }
 
 test {
diff --git a/subprojects/docs/src/samples/testng/suitexmlbuilder/build.gradle b/subprojects/docs/src/samples/testng/suitexmlbuilder/build.gradle
index 0f8e7b4..cbf6178 100644
--- a/subprojects/docs/src/samples/testng/suitexmlbuilder/build.gradle
+++ b/subprojects/docs/src/samples/testng/suitexmlbuilder/build.gradle
@@ -5,7 +5,7 @@ repositories {
 }
 
 dependencies {
-    testCompile 'org.testng:testng:5.14.10'
+    testCompile 'org.testng:testng:6.3.1'
 }
 
 test {
diff --git a/subprojects/docs/src/samples/toolingApi/build/build.gradle b/subprojects/docs/src/samples/toolingApi/build/build.gradle
index 51571ad..2a6a867 100644
--- a/subprojects/docs/src/samples/toolingApi/build/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/build/build.gradle
@@ -1,22 +1,27 @@
+//This build script contains extra logic that we use for automated tests
+//Not all of that is needed to use the Tooling Api!
+
 apply plugin: 'java'
 apply plugin: 'application'
 
 if (!hasProperty('toolingApiVersion')) {
-    toolingApiVersion = gradle.gradleVersion
+    ext.toolingApiVersion = gradle.gradleVersion
 }
 if (!hasProperty('toolingApiRepo')) {
-    toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
+    ext.toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
 }
 
 repositories {
-    mavenRepo urls: toolingApiRepo
+    maven {
+        url toolingApiRepo
+    }
     mavenCentral()
 }
 
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.1'
+    runtime 'org.slf4j:slf4j-simple:1.6.4'
 }
 
 mainClassName = 'org.gradle.sample.Main'
@@ -25,4 +30,9 @@ run {
     if (project.hasProperty('gradleDistribution')) {
         args = [gradleDistribution]
     }
+
+    if (project.hasProperty('automationSystemProperty')) {
+        def entry = project.getProperty('automationSystemProperty').split('=')
+        systemProperties[entry[0]] = entry[1]
+    }
 }
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
new file mode 100644
index 0000000..2a6a867
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
@@ -0,0 +1,38 @@
+//This build script contains extra logic that we use for automated tests
+//Not all of that is needed to use the Tooling Api!
+
+apply plugin: 'java'
+apply plugin: 'application'
+
+if (!hasProperty('toolingApiVersion')) {
+    ext.toolingApiVersion = gradle.gradleVersion
+}
+if (!hasProperty('toolingApiRepo')) {
+    ext.toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
+}
+
+repositories {
+    maven {
+        url toolingApiRepo
+    }
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
+    // Need an SLF4J implementation at runtime
+    runtime 'org.slf4j:slf4j-simple:1.6.4'
+}
+
+mainClassName = 'org.gradle.sample.Main'
+
+run {
+    if (project.hasProperty('gradleDistribution')) {
+        args = [gradleDistribution]
+    }
+
+    if (project.hasProperty('automationSystemProperty')) {
+        def entry = project.getProperty('automationSystemProperty').split('=')
+        systemProperties[entry[0]] = entry[1]
+    }
+}
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml b/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml
new file mode 100644
index 0000000..afe569d
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>An application which uses the tooling API to build the Eclipse model for a project.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/eclipse/src/main/java/org/gradle/sample/Main.java
new file mode 100644
index 0000000..f9ccaa5
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/src/main/java/org/gradle/sample/Main.java
@@ -0,0 +1,39 @@
+package org.gradle.sample;
+
+import org.gradle.tooling.*;
+import org.gradle.tooling.model.*;
+import org.gradle.tooling.model.eclipse.*;
+
+import java.io.File;
+
+public class Main {
+    public static void main(String[] args) {
+        // Configure the connector and create the connection
+        GradleConnector connector = GradleConnector.newConnector();
+        connector.forProjectDirectory(new File("."));
+        if (args.length > 0) {
+            connector.useInstallation(new File(args[0]));
+        }
+
+        ProjectConnection connection = connector.connect();
+        try {
+            // Load the Eclipse model for the project
+            EclipseProject project = connection.getModel(EclipseProject.class);
+            System.out.println("Project: " + project.getName());
+            System.out.println("Project directory: " + project.getProjectDirectory());
+            System.out.println("Source directories:");
+            for (EclipseSourceDirectory srcDir : project.getSourceDirectories()) {
+                System.out.println(srcDir.getPath());
+            }
+            System.out.println("Project classpath:");
+            for (ExternalDependency externalDependency : project.getClasspath()) {
+                System.out.println(externalDependency.getFile().getName());
+            }
+            System.out.println("Associated gradle project:");
+            System.out.println(project.getGradleProject());
+        } finally {
+            // Clean up
+            connection.close();
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/toolingApi/idea/build.gradle b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
new file mode 100644
index 0000000..c41c9ba
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
@@ -0,0 +1,45 @@
+//This build script contains extra logic that we use for automated tests
+//Not all of that is needed to use the Tooling Api!
+
+apply plugin: 'java'
+apply plugin: 'application'
+
+if (!hasProperty('toolingApiVersion')) {
+    ext.toolingApiVersion = gradle.gradleVersion
+}
+if (!hasProperty('toolingApiRepo')) {
+    ext.toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
+}
+
+repositories {
+    maven {
+        url toolingApiRepo
+    }
+    mavenCentral()
+}
+
+mainClassName = 'org.gradle.sample.Main'
+
+dependencies {
+    compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
+    // Need an SLF4J implementation at runtime
+    runtime 'org.slf4j:slf4j-simple:1.6.4'
+}
+
+mainClassName = 'org.gradle.sample.Main'
+
+run {
+    if (project.hasProperty('gradleDistribution')) {
+        args = [gradleDistribution]
+    }
+
+    if (project.hasProperty('automationSystemProperty')) {
+        def entry = project.getProperty('automationSystemProperty').split('=')
+        systemProperties[entry[0]] = entry[1]
+    }
+    
+    //jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']
+}
+
+//Example for local debugging:
+//gradle clean run -u -PtoolingApiRepo=/Users/szczepan/gradle/gradle.src/build/repo -PgradleDistribution=/Users/szczepan/programs/gradle-current -PtoolingApiVersion=1.0-milestone-4-20110725164259+0200
diff --git a/subprojects/docs/src/samples/toolingApi/idea/readme.xml b/subprojects/docs/src/samples/toolingApi/idea/readme.xml
new file mode 100644
index 0000000..8a94816
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/idea/readme.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright 2011 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.
+  -->
+
+<sample>
+    <para>An application which uses the tooling API to extract information needed by IntelliJ IDEA.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/idea/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/idea/src/main/java/org/gradle/sample/Main.java
new file mode 100644
index 0000000..ef182dd
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/idea/src/main/java/org/gradle/sample/Main.java
@@ -0,0 +1,64 @@
+package org.gradle.sample;
+
+import org.gradle.tooling.*;
+import org.gradle.tooling.model.*;
+import org.gradle.tooling.model.idea.*;
+
+import java.io.File;
+
+public class Main {
+    public static void main(String[] args) {
+        // Configure the connector and create the connection
+        GradleConnector connector = GradleConnector.newConnector();
+        connector.forProjectDirectory(new File("."));
+        if (args.length > 0) {
+            connector.useInstallation(new File(args[0]));
+        }
+
+        ProjectConnection connection = connector.connect();
+        try {
+            IdeaProject project = connection.getModel(IdeaProject.class);
+            System.out.println("***");
+            System.out.println("Project details: ");
+            System.out.println(project);
+
+            System.out.println("***");
+            System.out.println("Project modules: ");
+            for(IdeaModule module: project.getModules()) {
+                System.out.println("  " + module);
+                System.out.println("  module details:");
+
+                System.out.println("    tasks from associated gradle project:");
+                for (GradleTask task: module.getGradleProject().getTasks()) {
+                    System.out.println("      " + task.getName());
+                }
+
+                for (IdeaContentRoot root: module.getContentRoots()) {
+                    System.out.println("    Content root: " + root.getRootDirectory());
+                    System.out.println("    source dirs:");
+                    for (IdeaSourceDirectory dir: root.getSourceDirectories()) {
+                        System.out.println("      " + dir);
+                    }
+
+                    System.out.println("    test dirs:");
+                    for (IdeaSourceDirectory dir: root.getTestDirectories()) {
+                        System.out.println("      " + dir);
+                    }
+
+                    System.out.println("    exclude dirs:");
+                    for (File dir: root.getExcludeDirectories()) {
+                        System.out.println("      " + dir);
+                    }
+                }
+
+                System.out.println("    dependencies:");
+                for (IdeaDependency dependency: module.getDependencies()) {
+                    System.out.println("      * " + dependency);
+                }
+            }
+        } finally {
+            // Clean up
+            connection.close();
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/toolingApi/model/build.gradle b/subprojects/docs/src/samples/toolingApi/model/build.gradle
deleted file mode 100644
index 51571ad..0000000
--- a/subprojects/docs/src/samples/toolingApi/model/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'application'
-
-if (!hasProperty('toolingApiVersion')) {
-    toolingApiVersion = gradle.gradleVersion
-}
-if (!hasProperty('toolingApiRepo')) {
-    toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
-}
-
-repositories {
-    mavenRepo urls: toolingApiRepo
-    mavenCentral()
-}
-
-dependencies {
-    compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
-    // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.1'
-}
-
-mainClassName = 'org.gradle.sample.Main'
-
-run {
-    if (project.hasProperty('gradleDistribution')) {
-        args = [gradleDistribution]
-    }
-}
diff --git a/subprojects/docs/src/samples/toolingApi/model/readme.xml b/subprojects/docs/src/samples/toolingApi/model/readme.xml
deleted file mode 100644
index e297f11..0000000
--- a/subprojects/docs/src/samples/toolingApi/model/readme.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<sample>
-    <para>An application which uses the tooling API to build the model for a project.</para>
-</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/model/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/model/src/main/java/org/gradle/sample/Main.java
deleted file mode 100644
index 07bb189..0000000
--- a/subprojects/docs/src/samples/toolingApi/model/src/main/java/org/gradle/sample/Main.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.gradle.sample;
-
-import org.gradle.tooling.GradleConnector;
-import org.gradle.tooling.ProjectConnection;
-import org.gradle.tooling.model.ExternalDependency;
-import org.gradle.tooling.model.eclipse.EclipseProject;
-import org.gradle.tooling.model.eclipse.EclipseSourceDirectory;
-
-import java.io.File;
-
-public class Main {
-    public static void main(String[] args) {
-        // Configure the connector and create the connection
-        GradleConnector connector = GradleConnector.newConnector();
-        connector.forProjectDirectory(new File("."));
-        if (args.length > 0) {
-            connector.useInstallation(new File(args[0]));
-        }
-
-        ProjectConnection connection = connector.connect();
-        try {
-            // Load the Eclipse model for the project
-            EclipseProject project = connection.getModel(EclipseProject.class);
-            System.out.println("Project: " + project.getName());
-            System.out.println("Project directory: " + project.getProjectDirectory());
-            System.out.println("Source directories:");
-            for (EclipseSourceDirectory srcDir : project.getSourceDirectories()) {
-                System.out.println(srcDir.getPath());
-            }
-            System.out.println("Project classpath:");
-            for (ExternalDependency externalDependency : project.getClasspath()) {
-                System.out.println(externalDependency.getFile().getName());
-            }
-        } finally {
-            // Clean up
-            connection.close();
-        }
-    }
-}
diff --git a/subprojects/docs/src/samples/userguide/ant/useExternalAntTask/build.gradle b/subprojects/docs/src/samples/userguide/ant/useExternalAntTask/build.gradle
index 1d09c14..c3bf546 100644
--- a/subprojects/docs/src/samples/userguide/ant/useExternalAntTask/build.gradle
+++ b/subprojects/docs/src/samples/userguide/ant/useExternalAntTask/build.gradle
@@ -1,7 +1,7 @@
 task check << {
     ant.taskdef(resource: 'checkstyletask.properties') {
         classpath {
-            fileset(dir: 'libs', include: '*.jar')
+            fileset(dir: 'libs', includes: '*.jar')
         }
     }
     ant.checkstyle(config: 'checkstyle.xml') {
diff --git a/subprojects/docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/pmd-rules.xml b/subprojects/docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/pmd-rules.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/build.gradle
index 525520b..2c34d15 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/configurationHandling/build.gradle
@@ -1,22 +1,19 @@
-import org.apache.ivy.plugins.resolver.FileSystemResolver
-
 repositories {
-    add(new FileSystemResolver()) {
-        name = "repo"
-        addArtifactPattern("$projectDir/repo/[organization]/[module]-[revision].[ext]")
-        addIvyPattern("$projectDir/repo/[organization]/ivy-[module]-[revision].xml")
-        checkmodified = true
+    ivy {
+        artifactPattern "$projectDir/repo/[organization]/[module]-[revision].[ext]"
+        ivyPattern "$projectDir/repo/[organization]/ivy-[module]-[revision].xml"
     }
 }
 
 //START SNIPPET setup
 configurations {
     sealife
-    alllife.extendsFrom sealife
+    alllife
 }
 
 dependencies {
     sealife "sea.mammals:orca:1.0", "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
+    alllife configurations.sealife
     alllife "air.birds:albatros:1.0"
 }
 //END SNIPPET setup
diff --git a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
index 5734af1..ece7988 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
@@ -4,14 +4,10 @@ repositories {
 }
 //END SNIPPET maven-central
 
-//START SNIPPET maven-password-protected-repo
-org.apache.ivy.util.url.CredentialsStore.INSTANCE.addCredentials("REALM", "HOST", "USER", "PASSWORD"); 
-//END SNIPPET maven-password-protected-repo
-
 //START SNIPPET maven-central-jar-repo
 repositories {
-    mavenCentral name: 'single-jar-repo', urls: "http://repo.mycompany.com/jars"
-    mavenCentral name: 'multi-jar-repos', urls: ["http://repo.mycompany.com/jars1", "http://repo.mycompany.com/jars2"]
+    mavenCentral name: 'single-jar-repo', artifactUrls: ["http://repo.mycompany.com/jars"]
+    mavenCentral name: 'multi-jar-repos', artifactUrls: ["http://repo.mycompany.com/jars1", "http://repo.mycompany.com/jars2"]
 }
 //END SNIPPET maven-central-jar-repo
 
@@ -23,22 +19,46 @@ repositories {
 
 //START SNIPPET maven-like-repo
 repositories {
-    mavenRepo urls: "http://repo.mycompany.com/maven2"
+    maven {
+        url "http://repo.mycompany.com/maven2"
+    }
 }
 //END SNIPPET maven-like-repo
 
 //START SNIPPET maven-like-repo-with-jar-repo
 repositories {
-    mavenRepo urls: ["http://repo2.mycompany.com/maven2", "http://repo.mycompany.com/jars"]
+    maven {
+        // Look for POMs and artifacts, such as JARs, here
+        url "http://repo2.mycompany.com/maven2"
+        // Look for artifacts here if not found at the above location
+        artifactUrls "http://repo.mycompany.com/jars"
+        artifactUrls "http://repo.mycompany.com/jars2"
+    }
 }
 //END SNIPPET maven-like-repo-with-jar-repo
 
+//START SNIPPET authenticated-maven-repo
+repositories {
+    maven {
+        credentials {
+            username 'user'
+            password 'password'
+        }
+        url "http://repo.mycompany.com/maven2"
+    }
+}
+//END SNIPPET authenticated-maven-repo
+
 //START SNIPPET flat-dir
 //START SNIPPET flat-dir-multi
 repositories {
-    flatDir name: 'localRepository', dirs: 'lib'
+    flatDir {
+        dirs 'lib'
+    }
 //END SNIPPET flat-dir
-    flatDir dirs: ['lib1', 'lib2']
+    flatDir {
+        dirs 'lib1', 'lib2'
+    }
 //START SNIPPET flat-dir
 }
 //END SNIPPET flat-dir
@@ -47,18 +67,58 @@ repositories {
 //START SNIPPET ivy-repo
 repositories {
     ivy {
-        name = 'ivyRepo'
-        artifactPattern "http://repo.mycompany.com/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+        url "http://repo.mycompany.com/repo"
     }
 }
 //END SNIPPET ivy-repo
 
+//START SNIPPET local-ivy-repo
+repositories {
+    ivy {
+        // URL can refer to a local directory
+        url "../local-repo"
+    }
+}
+//END SNIPPET local-ivy-repo
+
+//START SNIPPET ivy-repo-with-maven-layout
+repositories {
+    ivy {
+        url "http://repo.mycompany.com/repo"
+        layout "maven"
+    }
+}
+//END SNIPPET ivy-repo-with-maven-layout
+
+//START SNIPPET ivy-repo-with-pattern-layout
+repositories {
+    ivy {
+        url "http://repo.mycompany.com/repo"
+        layout 'pattern', {
+            artifact "[module]/[revision]/[artifact].[ext]"
+            ivy "[module]/[revision]/ivy.xml"
+        }
+    }
+}
+//END SNIPPET ivy-repo-with-pattern-layout
+
+//START SNIPPET ivy-repo-with-custom-pattern
+repositories {
+    ivy {
+        artifactPattern "http://repo.mycompany.com/3rd-party-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+        artifactPattern "http://repo.mycompany.com/company-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+        ivyPattern "http://repo.mycompany.com/ivy-files/[organisation]/[module]/[revision]/ivy.xml"
+    }
+}
+//END SNIPPET ivy-repo-with-custom-pattern
+
 //START SNIPPET authenticated-ivy-repo
 repositories {
     ivy {
-        name = 'privateIvyRepo'
-        userName = 'user'
-        password = 'password'
+        credentials {
+            username 'user'
+            password 'password'
+        }
         artifactPattern "http://repo.mycompany.com/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
     }
 }
@@ -73,11 +133,16 @@ task lookup << {
 
 //START SNIPPET configure-resolver
 repositories {
+    flatDir {
+        name 'localRepository'
+    }
+}
+repositories {
     localRepository {
-        addArtifactPattern(file('lib').absolutePath + '/[name]/[revision]/[name]-[revision].[ext]')
+        dirs 'lib'
     }
 }
 repositories.localRepository {
-    addArtifactPattern(file('lib').absolutePath + '/[name]/[revision]/[name]-[revision].[ext]')
+    dirs 'lib'
 }
 //END SNIPPET configure-resolver
diff --git a/subprojects/docs/src/samples/userguide/artifacts/dependencyBasics/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/dependencyBasics/build.gradle
new file mode 100644
index 0000000..95422be
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/dependencyBasics/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
+    testCompile group: 'junit', name: 'junit', version: '4.+'
+}
diff --git a/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle
index bbf2d90..fb47d5b 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle
@@ -2,12 +2,9 @@ apply plugin: 'java'
 
 // START SNIPPET file-system-resolver
 repositories {
-    add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-        name = 'repo'
-        addIvyPattern "$projectDir/repo/[organisation]/[module]-ivy-[revision].xml"
-        addArtifactPattern "$projectDir/repo/[organisation]/[module]-[revision](-[classifier]).[ext]"
-        descriptor = 'optional'
-        checkmodified = true
+    ivy {
+        ivyPattern "$projectDir/repo/[organisation]/[module]-ivy-[revision].xml"
+        artifactPattern "$projectDir/repo/[organisation]/[module]-[revision](-[classifier]).[ext]"
     }
 }
 // END SNIPPET file-system-resolver
diff --git a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
index 33d0103..2cba162 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
@@ -2,26 +2,29 @@ repositories {
     mavenCentral()
 }
 
-//START SNIPPET define-dependency
 configurations {
     compile
+    runtime
 }
 
+//START SNIPPET define-dependency
 dependencies {
-    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
 }
 //END SNIPPET define-dependency
 
+//START SNIPPET define-dependency-shortcut
+dependencies {
+    compile 'org.hibernate:hibernate-core:3.6.7.Final'
+}
+//END SNIPPET define-dependency-shortcut
+
 //START SNIPPET use-configuration
 task listJars << {
     configurations.compile.each { File file -> println file.name }
 }
 //END SNIPPET use-configuration
 
-configurations {
-    runtime
-}
-
 //START SNIPPET module-dependencies
 dependencies {
     runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
@@ -63,7 +66,7 @@ dependencies {
 
 //START SNIPPET client-modules
 dependencies {
-    runtime module("org.codehaus.groovy:groovy-all:1.7.10") {
+    runtime module("org.codehaus.groovy:groovy-all:1.8.4") {
         dependency("commons-cli:commons-cli:1.0") {
             transitive = false
         }
@@ -82,7 +85,7 @@ dependencies {
 //END SNIPPET file-dependencies
 
 //START SNIPPET list-grouping
-List groovy = ["org.codehaus.groovy:groovy-all:1.7.10 at jar",
+List groovy = ["org.codehaus.groovy:groovy-all:1.8.4 at jar",
                "commons-cli:commons-cli:1.0 at jar",
                "org.apache.ant:ant:1.8.2 at jar"]
 List hibernate = ['org.hibernate:hibernate:3.0.5 at jar', 'somegroup:someorg:1.0 at jar']
diff --git a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
index f7206ee..643cc40 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
@@ -6,36 +6,38 @@ apply plugin: 'maven'
 //START SNIPPET customize-pom
 //START SNIPPET multiple-poms
 uploadArchives {
-    repositories.mavenDeployer {
-        repository(url: "file://localhost/tmp/myRepo/")
+    repositories {
+        mavenDeployer {
+            repository(url: "file://localhost/tmp/myRepo/")
 //END SNIPPET upload-file
 //END SNIPPET multiple-poms
 //END SNIPPET builder
-        pom.version = '1.0Maven'
-        pom.artifactId = 'myMavenName'
+            pom.version = '1.0Maven'
+            pom.artifactId = 'myMavenName'
 //END SNIPPET customize-pom
 //START SNIPPET builder
-        pom.project {
-            licenses {
-                license {
-                    name 'The Apache Software License, Version 2.0'
-                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
-                    distribution 'repo'
+            pom.project {
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
                 }
             }
-        }
 //END SNIPPET builder
 //START SNIPPET multiple-poms
-        addFilter('api') {artifact, file ->
-            artifact.name == 'api'
-        }
-        addFilter('service') {artifact, file ->
-            artifact.name == 'service'
-        }
-        pom('api').version = 'mySpecialMavenVersion'
+            addFilter('api') {artifact, file ->
+                artifact.name == 'api'
+            }
+            addFilter('service') {artifact, file ->
+                artifact.name == 'service'
+            }
+            pom('api').version = 'mySpecialMavenVersion'
 //START SNIPPET customize-pom
 //START SNIPPET upload-file
 //START SNIPPET builder
+        }
     }
 }
 //END SNIPPET customize-pom
diff --git a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
new file mode 100644
index 0000000..266f993
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
@@ -0,0 +1,11 @@
+//START SNIPPET dynamic-version-cache-control
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
+}
+//END SNIPPET dynamic-version-cache-control
+
+//START SNIPPET changing-module-cache-control
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 30, 'days'
+}
+//END SNIPPET changing-module-cache-control
diff --git a/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle
index de34000..55b4b94 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/uploading/build.gradle
@@ -1,28 +1,81 @@
 apply plugin: 'java'
 
-//START SNIPPET assign-artifact
+//START SNIPPET archive-artifact
 task myJar(type: Jar)
 
 artifacts {
     archives myJar
 }
-//END SNIPPET assign-artifact
+//END SNIPPET archive-artifact
+
+//START SNIPPET file-artifact
+def someFile = file('build/somefile.txt')
+
+artifacts {
+    archives someFile
+}
+//END SNIPPET file-artifact
+
+//START SNIPPET customised-file-artifact
+task myTask(type:  MyTaskType) {
+    destFile = file('build/somefile.txt')
+}
+
+artifacts {
+    archives(myTask.destFile) {
+        name 'my-artifact'
+        type 'text'
+        builtBy myTask
+    }
+}
+//END SNIPPET customised-file-artifact
+
+//START SNIPPET map-file-artifact
+task generate(type:  MyTaskType) {
+    destFile = file('build/somefile.txt')
+}
+
+artifacts {
+    archives file: generate.destFile, name: 'my-artifact', type: 'text', builtBy: generate
+}
+//END SNIPPET map-file-artifact
+
+class MyTaskType extends DefaultTask {
+    File destFile
+}
 
 //START SNIPPET uploading
 repositories {
-    flatDir(name: 'fileRepo', dirs: "$projectDir/repo")
+    flatDir {
+        name "fileRepo"
+        dirs "repo"
+    }
 }
 
 uploadArchives {
-    uploadDescriptor = false
     repositories {
         add project.repositories.fileRepo
-        add(new org.apache.ivy.plugins.resolver.SshResolver()) {
-            name = 'sshRepo'
-            user = 'username'
-            userPassword = 'pw'
-            host = "http://repo.mycompany.com"
+        ivy {
+            credentials {
+                username "username"
+                password "pw"
+            }
+            url "http://repo.mycompany.com"
+        }
+    }
+}
+//END SNIPPET uploading
+
+//START SNIPPET publish-repository
+uploadArchives {
+    repositories {
+        ivy {
+            credentials {
+                username "username"
+                password "pw"
+            }
+            url "http://repo.mycompany.com"
         }
     }
 }
-//END SNIPPET uploading
\ No newline at end of file
+//END SNIPPET publish-repository
diff --git a/subprojects/docs/src/samples/userguide/files/copy/build.gradle b/subprojects/docs/src/samples/userguide/files/copy/build.gradle
index 85c0815..3f60c29 100644
--- a/subprojects/docs/src/samples/userguide/files/copy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/files/copy/build.gradle
@@ -21,6 +21,10 @@ task anotherCopyTask(type: Copy) {
     from 'src/main/webapp'
     // Copy a single file
     from 'src/staging/index.html'
+    // Copy the output of a task
+    from copyTask
+    // Copy the output of a task using Task outputs explicitly.
+    from copyTaskWithPatterns.outputs
     // Copy the contents of a Zip file
     from zipTree('src/main/assets.zip')
     // Determine the destination directory later
@@ -93,5 +97,5 @@ task filter(type: Copy) {
 // END SNIPPET filter-files
 
 task test {
-    dependsOn tasks.findAll { it.name != 'test' }
+    dependsOn tasks.matching { it.name != 'test' }
 }
diff --git a/subprojects/docs/src/samples/userguide/files/fileTrees/build.gradle b/subprojects/docs/src/samples/userguide/files/fileTrees/build.gradle
index 6bea9af..8e86ef6 100644
--- a/subprojects/docs/src/samples/userguide/files/fileTrees/build.gradle
+++ b/subprojects/docs/src/samples/userguide/files/fileTrees/build.gradle
@@ -10,8 +10,7 @@ tree.exclude '**/Abstract*'
 tree = fileTree('src').include('**/*.java')
 
 // Create a tree using closure
-tree = fileTree {
-    from 'src'
+tree = fileTree('src') {
     include '**/*.java'
 }
 
@@ -47,4 +46,9 @@ FileTree zip = zipTree('someFile.zip')
 
 // Create a TAR file tree using path
 FileTree tar = tarTree('someFile.tar')
+
+//tar tree attempts to guess the compression based on the file extension
+//however if you must specify the compression explicitly you can:
+FileTree someTar = tarTree(resources.gzip('someTar.ext'))
+
 // END SNIPPET archive-trees
diff --git a/subprojects/docs/src/samples/userguide/initScripts/configurationInjection/build.gradle b/subprojects/docs/src/samples/userguide/initScripts/configurationInjection/build.gradle
new file mode 100644
index 0000000..f06e2f9
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/initScripts/configurationInjection/build.gradle
@@ -0,0 +1,8 @@
+repositories {
+    mavenCentral()
+}
+
+task showRepos << {
+    println "All repos:"
+    println repositories.collect { it.name }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/initScripts/configurationInjection/init.gradle b/subprojects/docs/src/samples/userguide/initScripts/configurationInjection/init.gradle
new file mode 100644
index 0000000..9dd137e
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/initScripts/configurationInjection/init.gradle
@@ -0,0 +1,5 @@
+allprojects {
+    repositories {
+        mavenLocal()
+    }
+}
diff --git a/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle b/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
index 572c800..e44a293 100644
--- a/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
+++ b/subprojects/docs/src/samples/userguide/java/sourceSets/build.gradle
@@ -1,24 +1,28 @@
 apply plugin: 'java'
 
+if (hasProperty('showOutput')) {
+
 // START SNIPPET access-source-set
 // Various ways to access the main source set
-println sourceSets.main.classesDir
-println sourceSets['main'].classesDir
+println sourceSets.main.output.classesDir
+println sourceSets['main'].output.classesDir
 sourceSets {
-    println main.classesDir
+    println main.output.classesDir
 }
 sourceSets {
     main {
-        println classesDir
+        println output.classesDir
     }
 }
 
 // Iterate over the source sets
-sourceSets.each {SourceSet set ->
-    println set.name
+sourceSets.all {
+    println name
 }
 // END SNIPPET access-source-set
 
+}
+
 // START SNIPPET define-source-set
 sourceSets {
     intTest
@@ -29,23 +33,20 @@ repositories {
     mavenCentral()
 }
 
-// START SNIPPET classpath-using-configurations
-configurations {
-    intTestCompile { extendsFrom compile }
-    intTestRuntime { extendsFrom intTestCompile, runtime }
+// START SNIPPET source-set-dependencies
+sourceSets {
+    intTest
 }
 
-sourceSets {
-    intTest {
-        compileClasspath = sourceSets.main.classes + configurations.intTestCompile
-        runtimeClasspath = classes + sourceSets.main.classes + configurations.intTestRuntime
-    }
+dependencies {
+    intTestCompile 'junit:junit:4.8.2'
+    intTestRuntime 'asm:asm-all:3.3.1'
 }
-// END SNIPPET classpath-using-configurations
+// END SNIPPET source-set-dependencies
 
 // START SNIPPET jar
 task intTestJar(type: Jar) {
-    from sourceSets.intTest.classes
+    from sourceSets.intTest.output
 }
 // END SNIPPET jar
 
@@ -57,7 +58,7 @@ task intTestJavadoc(type: Javadoc) {
 
 // START SNIPPET test
 task intTest(type: Test) {
-    testClassesDir = sourceSets.intTest.classesDir
+    testClassesDir = sourceSets.intTest.output.classesDir
     classpath = sourceSets.intTest.runtimeClasspath
 }
 // END SNIPPET test
diff --git a/subprojects/docs/src/samples/userguide/java/sourceSets/src/intTest/java/SomeTest.java b/subprojects/docs/src/samples/userguide/java/sourceSets/src/intTest/java/SomeTest.java
new file mode 100644
index 0000000..674359b
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/java/sourceSets/src/intTest/java/SomeTest.java
@@ -0,0 +1,7 @@
+import org.junit.Test;
+
+public class SomeTest {
+    @Test
+    public void ok() {
+    }
+}
diff --git a/subprojects/docs/src/samples/userguide/java/sourceSets/src/intTest/resources/resource.txt b/subprojects/docs/src/samples/userguide/java/sourceSets/src/intTest/resources/resource.txt
new file mode 100644
index 0000000..b1e1c2e
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/java/sourceSets/src/intTest/resources/resource.txt
@@ -0,0 +1 @@
+some resource
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
index 834ee63..0318cc3 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
@@ -22,6 +22,3 @@ project(':services:personService') {
     }
 }
 
-dependsOnChildren()
-
-
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
index 5be51d7..2d8c71d 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
@@ -14,7 +14,7 @@ project(':api') {
     task spiJar(type: Jar) {
         baseName = 'api-spi'
         dependsOn classes
-        from sourceSets.main.classes
+        from sourceSets.main.output
         include('org/gradle/sample/api/**')
     }
     artifacts {
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/consumer/build.gradle
deleted file mode 100644
index 5505c5f..0000000
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/consumer/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-dependsOn(':producer')
-
-task consume << {
-    println("Consuming message: " +
-            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/producer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/producer/build.gradle
deleted file mode 100644
index 49a8f5a..0000000
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/producer/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-task produce << {
-    println "Producing message:"
-    rootProject.producerMessage = 'Watch the order of execution.'
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/settings.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/settings.gradle
deleted file mode 100644
index 055860f..0000000
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
index 85ebec4..2e70b12 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
@@ -1,6 +1,4 @@
-dependsOn(':producer')
-
-task action << {
+task action(dependsOn: ":producer:action") << {
     println("Consuming message: " +
             (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle
index d2bc499..5f5ff41 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle
@@ -1,5 +1,3 @@
-dependsOnChildren()
-
 allprojects {
     apply plugin: 'java'
     group = 'org.gradle.sample'
@@ -17,7 +15,7 @@ subprojects {
 }
 
 task explodedDist(dependsOn: assemble) << {
-    File explodedDist = mkdir(buildDir, 'explodedDist')
+    File explodedDist = mkdir("$buildDir/explodedDist")
     subprojects.each {project ->
         project.tasks.withType(Jar).each {archiveTask ->
             copy {
diff --git a/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle
index 2b3696c..2c20c99 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle
@@ -1,4 +1,4 @@
-arctic = true
+ext.arctic = true
 hello << { println "- I'm the largest animal that has ever lived on this planet." }
 
 task distanceToIceberg << {
diff --git a/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle
index 05de0ba..453c824 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle
@@ -1,4 +1,4 @@
-arctic = true
+ext.arctic = true
 hello << { println "- The weight of my species in summer is twice as heavy as all human beings." }
 
 task distanceToIceberg << {
diff --git a/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle
index 45f5a66..98ea82c 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle
@@ -1 +1 @@
-arctic = false
\ No newline at end of file
+ext.arctic = false
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle
index d922c8f..4d02663 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle
@@ -1,2 +1,2 @@
-arctic = true
+ext.arctic = true
 hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle
index 9d4fbfb..4c18ab8 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle
@@ -1,4 +1,4 @@
-arctic = true
+ext.arctic = true
 hello.doLast {
     println "- The weight of my species in summer is twice as heavy as all human beings."
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle b/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle
index 45f5a66..98ea82c 100644
--- a/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle
+++ b/subprojects/docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle
@@ -1 +1 @@
-arctic = false
\ No newline at end of file
+ext.arctic = false
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle
index f290fca..191a08c 100644
--- a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle
+++ b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: GreetingPlugin
 
 class GreetingPlugin implements Plugin<Project> {
-    def void apply(Project project) {
+    void apply(Project project) {
         project.task('hello') << {
             println "Hello from the GreetingPlugin"
         }
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle
index 7abec1e..342be2f 100644
--- a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle
+++ b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle
@@ -1,25 +1,20 @@
 apply plugin: GreetingPlugin
 
-greet {
-    message = 'Hi from Gradle' 
+greeting {
+    message = 'Hi'
+    greeter = 'Gradle'
 }
 
 class GreetingPlugin implements Plugin<Project> {
-    def void apply(Project project) {
-
-        project.convention.plugins.greet = new GreetingPluginConvention()
+    void apply(Project project) {
+        project.extensions.create("greeting", GreetingPluginExtension)
         project.task('hello') << {
-            println project.convention.plugins.greet.message
+            println "${project.greeting.message} from ${project.greeting.greeter}"
         }
     }
 }
 
-class GreetingPluginConvention {
+class GreetingPluginExtension {
     String message
-
-    def greet(Closure closure) {
-        closure.delegate = this
-        closure() 
-    }
+    String greeter
 }
-
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle
index db9e9a2..190eb3f 100644
--- a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle
+++ b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle
@@ -1,16 +1,18 @@
 apply plugin: GreetingPlugin
 
-greeting = 'Hi from Gradle'
+greeting.message = 'Hi from Gradle'
 
 class GreetingPlugin implements Plugin<Project> {
-    def void apply(Project project) {
-        project.convention.plugins.greet = new GreetingPluginConvention()
+    void apply(Project project) {
+        // Add the 'greeting' extension object
+        project.extensions.create("greeting", GreetingPluginExtension)
+        // Add a task that uses the configuration
         project.task('hello') << {
-            println project.convention.plugins.greet.greeting
+            println project.greeting.message
         }
     }
 }
 
-class GreetingPluginConvention {
-    def String greeting = 'Hello from GreetingPlugin'
+class GreetingPluginExtension {
+    def String message = 'Hello from GreetingPlugin'
 }
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/build.gradle
index 8bb5185..3393d97 100644
--- a/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/build.gradle
+++ b/subprojects/docs/src/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/build.gradle
@@ -19,12 +19,12 @@ task books << {
 }
 
 class DocumentationPlugin implements Plugin<Project> {
-    def void apply(Project project) {
-        def books = project.container(Book) { name -> new Book(name) }
+    void apply(Project project) {
+        def books = project.container(Book)
         books.all {
             sourceFile = project.file("src/docs/$name")
         }
-        project.convention.plugins.documentation = new DocumentationPluginConvention(books)
+        project.extensions.books = books
     }
 }
 
@@ -35,16 +35,4 @@ class Book {
     Book(String name) {
         this.name = name
     }
-}
-
-class DocumentationPluginConvention {
-    final NamedDomainObjectContainer<Book> books
-
-    DocumentationPluginConvention(NamedDomainObjectContainer<Book> books) {
-        this.books = books
-    }
-
-    def books(Closure cl) {
-        books.configure(cl)
-    }
-}
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/inherited/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/inherited/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/inherited/child/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/inherited/child/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/inherited/settings.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/inherited/settings.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child1/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child1/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/settings.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/settings.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/nestedBuild/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/nestedBuild/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/nestedBuild/other.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/nestedBuild/other.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/tasks/customTaskUsingConvention/build.gradle b/subprojects/docs/src/samples/userguide/tasks/customTaskUsingConvention/build.gradle
new file mode 100644
index 0000000..d1b1a36
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/customTaskUsingConvention/build.gradle
@@ -0,0 +1,34 @@
+// START SNIPPET tasks
+apply plugin: GreetingPlugin
+
+// our default greeting
+greeting = "Hello!"
+
+task hello(type: GreetingTask)
+
+task bonjour(type: GreetingTask) {
+    greeting = "Bonjour!"
+}
+// END SNIPPET tasks
+
+// START SNIPPET plugin
+class GreetingPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.tasks.withType(GreetingTask) { task ->
+            task.conventionMapping.greeting = { project.greeting }
+        }
+    }
+}
+// END SNIPPET plugin
+
+// START SNIPPET task
+class GreetingTask extends DefaultTask {
+
+    String greeting
+
+    @TaskAction
+    def greet() {
+        println getGreeting()
+    }
+}
+// END SNIPPET task
diff --git a/subprojects/docs/src/samples/userguide/tasks/customTaskWithFileProperty/build.gradle b/subprojects/docs/src/samples/userguide/tasks/customTaskWithFileProperty/build.gradle
new file mode 100644
index 0000000..4923cd5
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/customTaskWithFileProperty/build.gradle
@@ -0,0 +1,29 @@
+// START SNIPPET task
+class GreetingToFileTask extends DefaultTask {
+
+    def destination
+
+    File getDestination() {
+        project.file(destination)
+    }
+
+    @TaskAction
+    def greet() {
+        def file = getDestination()
+        file.parentFile.mkdirs()
+        file.write "Hello!"
+    }
+}
+// END SNIPPET task
+
+// START SNIPPET config
+task greet(type: GreetingToFileTask) {
+    destination = { project.greetingFile }
+}
+
+task sayGreeting(dependsOn: greet) << {
+    println file(greetingFile).text
+}
+
+greetingFile = "$buildDir/hello.txt"
+// END SNIPPET config
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tasks/customTaskWithFileProperty/build/hello.txt b/subprojects/docs/src/samples/userguide/tasks/customTaskWithFileProperty/build/hello.txt
new file mode 100644
index 0000000..05a682b
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tasks/customTaskWithFileProperty/build/hello.txt
@@ -0,0 +1 @@
+Hello!
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/inputsAndOutputs/build.gradle b/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/inputsAndOutputs/build.gradle
index 3af6cb0..32a52e3 100644
--- a/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/inputsAndOutputs/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/inputsAndOutputs/build.gradle
@@ -1,7 +1,7 @@
 // START SNIPPET declare-inputs-and-outputs
 task transform {
-    srcFile = file('mountains.xml')
-    destDir = new File(buildDir, 'generated')
+    ext.srcFile = file('mountains.xml')
+    ext.destDir = new File(buildDir, 'generated')
     inputs.file srcFile
     outputs.dir destDir
 // END SNIPPET declare-inputs-and-outputs
diff --git a/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/noInputsAndOutputs/build.gradle b/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/noInputsAndOutputs/build.gradle
index c7f980d..63bd63a 100644
--- a/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/noInputsAndOutputs/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tasks/incrementalBuild/noInputsAndOutputs/build.gradle
@@ -1,6 +1,6 @@
 task transform {
-    srcFile = file('mountains.xml')
-    destDir = new File(buildDir, 'generated')
+    ext.srcFile = file('mountains.xml')
+    ext.destDir = new File(buildDir, 'generated')
     doLast {
         println "Transforming source file."
         destDir.mkdirs()
diff --git a/subprojects/docs/src/samples/userguide/tutorial/configByDag/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/configByDag/build.gradle
index a20ce99..1688ec0 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/configByDag/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/configByDag/build.gradle
@@ -1,14 +1,15 @@
-gradle.taskGraph.whenReady {taskGraph ->
-    if (taskGraph.hasTask(':release')) {
-        version = '1.0'
-    } else {
-        version = '1.0-SNAPSHOT'
-    }
-}
-
 task distribution << {
     println "We build the zip with version=$version"
 }
+
 task release(dependsOn: 'distribution') << {
     println 'We release now'
 }
+
+gradle.taskGraph.whenReady {taskGraph ->
+    if (taskGraph.hasTask(release)) {
+        version = '1.0'
+    } else {
+        version = '1.0-SNAPSHOT'
+    }
+}
diff --git a/subprojects/docs/src/samples/userguide/tutorial/configureObjectUsingScript/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/configureObjectUsingScript/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/tutorial/configureObjectUsingScript/other.gradle b/subprojects/docs/src/samples/userguide/tutorial/configureObjectUsingScript/other.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/tutorial/configureProjectUsingScript/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/configureProjectUsingScript/build.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/tutorial/configureProjectUsingScript/other.gradle b/subprojects/docs/src/samples/userguide/tutorial/configureProjectUsingScript/other.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguide/tutorial/directoryTask/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/directoryTask/build.gradle
deleted file mode 100644
index 0cd8c19..0000000
--- a/subprojects/docs/src/samples/userguide/tutorial/directoryTask/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-classes = dir('build/classes')
-task resources(dependsOn: classes) << {
-    // do something
-}
-task otherResources(dependsOn: classes) << {
-    if (classes.dir.isDirectory()) {
-        println 'The class directory exists. I can operate'
-    }
-    // do something
-}
diff --git a/subprojects/docs/src/samples/userguide/tutorial/dynamicProperties/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/dynamicProperties/build.gradle
deleted file mode 100644
index b536f07..0000000
--- a/subprojects/docs/src/samples/userguide/tutorial/dynamicProperties/build.gradle
+++ /dev/null
@@ -1,6 +0,0 @@
-task myTask
-myTask.myProperty = 'myCustomPropValue'
-
-task showProps << {
-    println myTask.myProperty
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle
new file mode 100644
index 0000000..f9e3a94
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle
@@ -0,0 +1,30 @@
+// START SNIPPET taskProperty
+task myTask
+myTask.ext.myProperty = 'myCustomPropValue'
+
+task showProps << {
+    println myTask.myProperty
+}
+// END SNIPPET taskProperty
+
+// START SNIPPET sourceSetProperty
+apply plugin: "java"
+
+sourceSets.all { ext.purpose = null }
+
+sourceSets {
+    main {
+        purpose = "production"
+    }
+    test {
+        purpose = "test"
+    }
+    plugin {
+        ext.purpose = "production"
+    }
+}
+
+task printProductionSourceDirs << {
+    sourceSets.matching { it.purpose == "production" }.each { println it.java.srcDirs }
+}
+// END SNIPPET sourceSetProperty
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
index e24e9fc..63fccdf 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
@@ -2,7 +2,7 @@ apply plugin: 'groovy'
 
 // START SNIPPET groovy-dependency
 repositories {
-    flatDir(dirs: file('lib'))
+    flatDir { dirs 'lib' }
 }
 
 dependencies {
diff --git a/subprojects/docs/src/samples/userguide/tutorial/localVariables/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/localVariables/build.gradle
new file mode 100644
index 0000000..3e174c0
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/tutorial/localVariables/build.gradle
@@ -0,0 +1,6 @@
+def dest = "dest"
+
+task copy(type: Copy) {
+    from "source"
+    into dest
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/manifest/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/manifest/build.gradle
index d72eb0f..244222d 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/manifest/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/manifest/build.gradle
@@ -10,7 +10,7 @@ jar {
 // END SNIPPET add-to-manifest
 
 // START SNIPPET custom-manifest
-sharedManifest = manifest {
+ext.sharedManifest = manifest {
     attributes("Implementation-Title": "Gradle", "Implementation-Version": version)
 }
 task fooJar(type: Jar) {
diff --git a/subprojects/docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle
index 083f92c..31c805c 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle
@@ -2,10 +2,9 @@ apply plugin: 'java'
 
 task show << {
     // Access the convention property as a project property
-    println relativePath(sourceSets.main.classesDir)
-    println relativePath(project.sourceSets.main.classesDir)
+    println relativePath(sourceSets.main.output.classesDir)
+    println relativePath(project.sourceSets.main.output.classesDir)
 
     // Access the convention property via the convention object
-    println relativePath(project.convention.sourceSets.main.classesDir)
-    println relativePath(project.convention.plugins.java.sourceSets.main.classesDir)
+    println relativePath(project.convention.plugins.java.sourceSets.main.output.classesDir)
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/pluginConfig/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/pluginConfig/build.gradle
index 40df7b1..a4a21e9 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/pluginConfig/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/pluginConfig/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'java'
 
+compileJava.destinationDir = file("$buildDir/output/classes")
+
 task show << {
-    processResources.destinationDir = new File(buildDir, 'output')
-    println relativePath(processResources.destinationDir)
     println relativePath(compileJava.destinationDir)
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/tutorial/pluginConvention/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/pluginConvention/build.gradle
index e6aa5b9..a732ac5 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/pluginConvention/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/pluginConvention/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'java'
 
+sourceSets.main.output.classesDir = file("$buildDir/output/classes")
+
 task show << {
-    sourceSets.main.classesDir = new File(buildDir, 'output')
-    println relativePath(processResources.destinationDir)
     println relativePath(compileJava.destinationDir)
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/projectReports/api/build.gradle
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/docs/src/samples/userguide/tutorial/projectReports/api/build.gradle
diff --git a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
index d7caa4a..8615aed 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/projectReports/build.gradle
@@ -63,7 +63,7 @@ project(':api') {
 description = 'The shared API for the application'
 // END SNIPPET project-description
     dependencies {
-        compile "org.codehaus.groovy:groovy-all:1.7.10"
+        compile "org.codehaus.groovy:groovy-all:1.8.4"
     }
 }
 
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/projectReports/webapp/build.gradle
similarity index 100%
copy from subprojects/docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
copy to subprojects/docs/src/samples/userguide/tutorial/projectReports/webapp/build.gradle
diff --git a/subprojects/docs/src/samples/userguideOutput/compileSourceSet.out b/subprojects/docs/src/samples/userguideOutput/compileSourceSet.out
new file mode 100644
index 0000000..d1d7a66
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/compileSourceSet.out
@@ -0,0 +1,7 @@
+:compileIntTestJava
+:processIntTestResources
+:intTestClasses
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out b/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
index 2096352..45cee23 100644
--- a/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
+++ b/subprojects/docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
@@ -1,5 +1,5 @@
 orca-1.0.jar
-seal-2.0.jar
 shark-1.0.jar
 tuna-1.0.jar
+seal-2.0.jar
 herring-1.0.jar
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/configureObjectUsingScript.out b/subprojects/docs/src/samples/userguideOutput/configureObjectUsingScript.out
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguideOutput/configureProjectUsingScript.out b/subprojects/docs/src/samples/userguideOutput/configureProjectUsingScript.out
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguideOutput/customTaskWithConventionOutput.out b/subprojects/docs/src/samples/userguideOutput/customTaskWithConventionOutput.out
new file mode 100644
index 0000000..25ae813
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/customTaskWithConventionOutput.out
@@ -0,0 +1,2 @@
+Hello!
+Bonjour!
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out b/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
index 343e30b..ac7bdc2 100644
--- a/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/dependencyListReport.out
@@ -1,6 +1,6 @@
 
 ------------------------------------------------------------
-Root Project
+Root project
 ------------------------------------------------------------
 
 No configurations
@@ -10,7 +10,7 @@ Project :api - The shared API for the application
 ------------------------------------------------------------
 
 compile
-\--- org.codehaus.groovy:groovy-all:1.7.10 [default]
+\--- org.codehaus.groovy:groovy-all:1.8.4 [default]
 
 ------------------------------------------------------------
 Project :webapp - The Web application implementation
@@ -18,5 +18,5 @@ Project :webapp - The Web application implementation
 
 compile
 +--- projectReports:api:1.0-SNAPSHOT [compile]
-|    \--- org.codehaus.groovy:groovy-all:1.7.10 [default]
+|    \--- org.codehaus.groovy:groovy-all:1.8.4 [default]
 \--- commons-io:commons-io:1.2 [default]
diff --git a/subprojects/docs/src/samples/userguideOutput/dependentTaskForApplicationDistributionOutput.out b/subprojects/docs/src/samples/userguideOutput/dependentTaskForApplicationDistributionOutput.out
new file mode 100644
index 0000000..6ca8520
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/dependentTaskForApplicationDistributionOutput.out
@@ -0,0 +1,11 @@
+:createDocs
+:compileJava
+:processResources UP-TO-DATE
+:classes
+:jar
+:startScripts
+:distZip
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/externalDependencies.out b/subprojects/docs/src/samples/userguideOutput/externalDependencies.out
index 443a8a4..65061e7 100644
--- a/subprojects/docs/src/samples/userguideOutput/externalDependencies.out
+++ b/subprojects/docs/src/samples/userguideOutput/externalDependencies.out
@@ -1 +1,8 @@
-commons-collections-3.2.jar
+hibernate-core-3.6.7.Final.jar
+antlr-2.7.6.jar
+commons-collections-3.1.jar
+dom4j-1.6.1.jar
+slf4j-api-1.6.1.jar
+hibernate-commons-annotations-3.2.0.Final.jar
+hibernate-jpa-2.0-api-1.0.1.Final.jar
+jta-1.1.jar
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/dynamicProperties.out b/subprojects/docs/src/samples/userguideOutput/extraTaskProperties.out
similarity index 100%
rename from subprojects/docs/src/samples/userguideOutput/dynamicProperties.out
rename to subprojects/docs/src/samples/userguideOutput/extraTaskProperties.out
diff --git a/subprojects/docs/src/samples/userguideOutput/inheritedBuildLogic.out b/subprojects/docs/src/samples/userguideOutput/inheritedBuildLogic.out
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguideOutput/initScriptConfiguration.out b/subprojects/docs/src/samples/userguideOutput/initScriptConfiguration.out
new file mode 100644
index 0000000..c1665b6
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/initScriptConfiguration.out
@@ -0,0 +1,2 @@
+All repos:
+[MavenLocal, MavenRepo]
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/injectedBuildLogic.out b/subprojects/docs/src/samples/userguideOutput/injectedBuildLogic.out
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguideOutput/lazyFileProperties.out b/subprojects/docs/src/samples/userguideOutput/lazyFileProperties.out
new file mode 100644
index 0000000..10ddd6d
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/lazyFileProperties.out
@@ -0,0 +1 @@
+Hello!
diff --git a/subprojects/docs/src/samples/userguideOutput/multitestingBuildDependents.out b/subprojects/docs/src/samples/userguideOutput/multitestingBuildDependents.out
index 5d47300..989b941 100644
--- a/subprojects/docs/src/samples/userguideOutput/multitestingBuildDependents.out
+++ b/subprojects/docs/src/samples/userguideOutput/multitestingBuildDependents.out
@@ -1,31 +1,32 @@
-:shared:compileJava
-:shared:processResources
-:shared:classes
-:shared:jar
-:api:compileJava
-:api:processResources
-:api:classes
-:api:jar
-:api:assemble
-:api:compileTestJava
-:api:processTestResources
-:api:testClasses
-:api:test
-:api:check
-:api:build
-:services:personService:compileJava
-:services:personService:processResources
-:services:personService:classes
-:services:personService:jar
-:services:personService:assemble
-:services:personService:compileTestJava
-:services:personService:processTestResources
-:services:personService:testClasses
-:services:personService:test
-:services:personService:check
-:services:personService:build
-:api:buildDependents
-
-BUILD SUCCESSFUL
-
-Total time: 1 secs
+:shared:compileJava
+:shared:processResources
+:shared:classes
+:shared:jar
+:api:compileJava
+:api:processResources
+:api:classes
+:api:jar
+:api:assemble
+:api:compileTestJava
+:api:processTestResources
+:api:testClasses
+:api:test
+:api:check
+:api:build
+:services:personService:compileJava
+:services:personService:processResources
+:services:personService:classes
+:services:personService:jar
+:services:personService:assemble
+:services:personService:compileTestJava
+:services:personService:processTestResources
+:services:personService:testClasses
+:services:personService:test
+:services:personService:check
+:services:personService:build
+:services:personService:buildDependents
+:api:buildDependents
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/multitestingBuildNeeded.out b/subprojects/docs/src/samples/userguideOutput/multitestingBuildNeeded.out
index e4347d3..134ce2e 100644
--- a/subprojects/docs/src/samples/userguideOutput/multitestingBuildNeeded.out
+++ b/subprojects/docs/src/samples/userguideOutput/multitestingBuildNeeded.out
@@ -1,27 +1,28 @@
-:shared:compileJava
-:shared:processResources
-:shared:classes
-:shared:jar
-:api:compileJava
-:api:processResources
-:api:classes
-:api:jar
-:api:assemble
-:api:compileTestJava
-:api:processTestResources
-:api:testClasses
-:api:test
-:api:check
-:api:build
-:shared:assemble
-:shared:compileTestJava
-:shared:processTestResources
-:shared:testClasses
-:shared:test
-:shared:check
-:shared:build
-:api:buildNeeded
-
-BUILD SUCCESSFUL
-
-Total time: 1 secs
+:shared:compileJava
+:shared:processResources
+:shared:classes
+:shared:jar
+:api:compileJava
+:api:processResources
+:api:classes
+:api:jar
+:api:assemble
+:api:compileTestJava
+:api:processTestResources
+:api:testClasses
+:api:test
+:api:check
+:api:build
+:shared:assemble
+:shared:compileTestJava
+:shared:processTestResources
+:shared:testClasses
+:shared:test
+:shared:check
+:shared:build
+:shared:buildNeeded
+:api:buildNeeded
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/nestedBuild.out b/subprojects/docs/src/samples/userguideOutput/nestedBuild.out
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/userguideOutput/pluginAccessConvention.out b/subprojects/docs/src/samples/userguideOutput/pluginAccessConvention.out
index f14f2a5..357cd85 100644
--- a/subprojects/docs/src/samples/userguideOutput/pluginAccessConvention.out
+++ b/subprojects/docs/src/samples/userguideOutput/pluginAccessConvention.out
@@ -1,4 +1,3 @@
 build/classes/main
 build/classes/main
 build/classes/main
-build/classes/main
diff --git a/subprojects/docs/src/samples/userguideOutput/pluginConfig.out b/subprojects/docs/src/samples/userguideOutput/pluginConfig.out
index 9d22cbc..82b16cf 100644
--- a/subprojects/docs/src/samples/userguideOutput/pluginConfig.out
+++ b/subprojects/docs/src/samples/userguideOutput/pluginConfig.out
@@ -1,2 +1 @@
-build/output
-build/classes/main
+build/output/classes
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/pluginConvention.out b/subprojects/docs/src/samples/userguideOutput/pluginConvention.out
index 6daa1e0..bd191d2 100644
--- a/subprojects/docs/src/samples/userguideOutput/pluginConvention.out
+++ b/subprojects/docs/src/samples/userguideOutput/pluginConvention.out
@@ -1,2 +1 @@
-build/output
-build/output
+build/output/classes
diff --git a/subprojects/docs/src/samples/userguideOutput/pluginIntro.out b/subprojects/docs/src/samples/userguideOutput/pluginIntro.out
index ce14304..48d282e 100644
--- a/subprojects/docs/src/samples/userguideOutput/pluginIntro.out
+++ b/subprojects/docs/src/samples/userguideOutput/pluginIntro.out
@@ -1,2 +1,2 @@
 build/classes/main
-build/classes/main
+build/resources/main
diff --git a/subprojects/docs/src/samples/userguideOutput/projectListReport.out b/subprojects/docs/src/samples/userguideOutput/projectListReport.out
index a29ff66..61b93ec 100644
--- a/subprojects/docs/src/samples/userguideOutput/projectListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/projectListReport.out
@@ -1,6 +1,6 @@
 
 ------------------------------------------------------------
-Root Project
+Root project
 ------------------------------------------------------------
 
 Root project 'projectReports'
diff --git a/subprojects/docs/src/samples/userguideOutput/propertyListReport.out b/subprojects/docs/src/samples/userguideOutput/propertyListReport.out
index 1fb0c2e..2049c5b 100644
--- a/subprojects/docs/src/samples/userguideOutput/propertyListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/propertyListReport.out
@@ -3,14 +3,10 @@
 Project :api - The shared API for the application
 ------------------------------------------------------------
 
-additionalProperties: {}
-all: [task ':api:clean', task ':api:compile', task ':api:libs']
 allprojects: [project ':api']
 ant: org.gradle.api.internal.project.DefaultAntBuilder at 12345
 antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory at 12345
 artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler at 12345
-asDynamicObject: org.gradle.api.internal.DynamicObjectHelper at 12345
-asMap: {clean=task ':api:clean', compile=task ':api:compile', libs=task ':api:libs'}
+asDynamicObject: org.gradle.api.internal.ExtensibleDynamicObject at 12345
 buildDir: /home/user/gradle/samples/userguide/tutorial/projectReports/api/build
-buildDirName: build
 buildFile: /home/user/gradle/samples/userguide/tutorial/projectReports/api/build.gradle
diff --git a/subprojects/docs/src/samples/userguideOutput/signingArchivesOutput.out b/subprojects/docs/src/samples/userguideOutput/signingArchivesOutput.out
new file mode 100644
index 0000000..caa7f72
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/signingArchivesOutput.out
@@ -0,0 +1,9 @@
+:compileJava
+:processResources
+:classes
+:jar
+:signArchives
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/signingTaskOutput.out b/subprojects/docs/src/samples/userguideOutput/signingTaskOutput.out
new file mode 100644
index 0000000..9c6bb62
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/signingTaskOutput.out
@@ -0,0 +1,6 @@
+:stuffZip
+:signStuffZip
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out b/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
index 3e29c92..7912ac5 100644
--- a/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/taskListAllReport.out
@@ -1,6 +1,6 @@
 
 ------------------------------------------------------------
-Root Project
+All tasks runnable from root project
 ------------------------------------------------------------
 
 Default tasks: dists
@@ -23,4 +23,4 @@ dependencies - Displays the dependencies of root project 'projectReports'.
 help - Displays a help message
 projects - Displays the sub-projects of root project 'projectReports'.
 properties - Displays the properties of root project 'projectReports'.
-tasks - Displays the tasks in root project 'projectReports'.
+tasks - Displays the tasks runnable from root project 'projectReports' (some of the displayed tasks may belong to subprojects).
diff --git a/subprojects/docs/src/samples/userguideOutput/taskListReport.out b/subprojects/docs/src/samples/userguideOutput/taskListReport.out
index b5a2aa5..61abbea 100644
--- a/subprojects/docs/src/samples/userguideOutput/taskListReport.out
+++ b/subprojects/docs/src/samples/userguideOutput/taskListReport.out
@@ -1,6 +1,6 @@
 
 ------------------------------------------------------------
-Root Project
+All tasks runnable from root project
 ------------------------------------------------------------
 
 Default tasks: dists
@@ -17,6 +17,6 @@ dependencies - Displays the dependencies of root project 'projectReports'.
 help - Displays a help message
 projects - Displays the sub-projects of root project 'projectReports'.
 properties - Displays the properties of root project 'projectReports'.
-tasks - Displays the tasks in root project 'projectReports'.
+tasks - Displays the tasks runnable from root project 'projectReports' (some of the displayed tasks may belong to subprojects).
 
 To see all tasks and more detail, run with --all.
diff --git a/subprojects/docs/src/samples/webApplication/customised/build.gradle b/subprojects/docs/src/samples/webApplication/customised/build.gradle
index 9607ab1..675c11d 100644
--- a/subprojects/docs/src/samples/webApplication/customised/build.gradle
+++ b/subprojects/docs/src/samples/webApplication/customised/build.gradle
@@ -1,17 +1,17 @@
-import org.apache.commons.httpclient.HttpClient
-import org.apache.commons.httpclient.methods.GetMethod
-
+apply plugin: 'jetty'
+// START SNIPPET use-plugin
+apply plugin: 'war'
+// END SNIPPET use-plugin
 group = 'gradle'
 version = '1.0'
-apply plugin: 'war'
-apply plugin: 'jetty'
 
+// START SNIPPET customization
 configurations {
    moreLibs
 }
 
 repositories {
-   flatDir(dirs: "$projectDir/lib")
+   flatDir { dirs "lib" }
    mavenCentral()
 }
 
@@ -37,29 +37,7 @@ war {
     classpath configurations.moreLibs // adds a configuration to the WEB-INF/lib dir.
     webXml = file('src/someWeb.xml') // copies a file to WEB-INF/web.xml
 }
-
-// START SNIPPET enable-jar
-jar.enabled = true
-// END SNIPPET enable-jar
-
-[jettyRun, jettyRunWar]*.daemon = true
+// END SNIPPET customization
 stopKey = 'foo'
 stopPort = 9451
-httpPort = 8163
-
-task runTest(dependsOn: jettyRun) << {
-    callServlet()
-}
-
-task runWarTest(dependsOn: jettyRunWar) << {
-    callServlet()
-}
-
-private void callServlet() {
-    HttpClient client = new HttpClient()
-    GetMethod method = new GetMethod("http://localhost:$httpPort/customised/hello")
-    client.executeMethod(method)
-    new File(buildDir, "servlet-out.txt").write(method.getResponseBodyAsString())
-    jettyStop.execute()
-}
-
+httpPort = 8080
diff --git a/subprojects/docs/src/samples/webApplication/customised/readme.xml b/subprojects/docs/src/samples/webApplication/customised/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/docs/src/samples/webApplication/quickstart/build.gradle b/subprojects/docs/src/samples/webApplication/quickstart/build.gradle
index 51cb498..9e708f4 100644
--- a/subprojects/docs/src/samples/webApplication/quickstart/build.gradle
+++ b/subprojects/docs/src/samples/webApplication/quickstart/build.gradle
@@ -14,25 +14,6 @@ dependencies {
     compile group: 'log4j', name: 'log4j', version: '1.2.15', ext: 'jar'
 }
 
-gradle.taskGraph.whenReady {graph ->
-    if (graph.hasTask(runTest) || graph.hasTask(runWarTest)) {
-        [jettyRun, jettyRunWar]*.daemon = true
-    }
-}
-stopKey = 'foo'
+httpPort = 8080
 stopPort = 9451
-httpPort = 8163
-
-task runTest(dependsOn: jettyRun) << {
-    callServlet()
-}
-
-task runWarTest(dependsOn: jettyRunWar) << {
-    callServlet()
-}
-
-private void callServlet() {
-    URL url = new URL("http://localhost:$httpPort/quickstart")
-    println url.text
-    jettyStop.execute()
-}
+stopKey = 'foo'
diff --git a/subprojects/docs/src/samples/webApplication/quickstart/readme.xml b/subprojects/docs/src/samples/webApplication/quickstart/readme.xml
old mode 100644
new mode 100755
diff --git a/subprojects/ear/ear.gradle b/subprojects/ear/ear.gradle
new file mode 100644
index 0000000..d06cfc5
--- /dev/null
+++ b/subprojects/ear/ear.gradle
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+    compile project(':core')
+    compile project(":plugins")
+}
+
+useTestFixtures()
diff --git a/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginGoodBehaviourTest.groovy b/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..10c9230
--- /dev/null
+++ b/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginGoodBehaviourTest.groovy
@@ -0,0 +1,22 @@
+/*
+ * 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.ear
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class EarPluginGoodBehaviourTest extends WellBehavedPluginTest {
+}
diff --git a/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy b/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy
new file mode 100644
index 0000000..dd74fe3
--- /dev/null
+++ b/subprojects/ear/src/integTest/groovy/org/gradle/plugins/ear/EarPluginIntegrationTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2011 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.ear
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * @author: Szczepan Faber, created at: 6/3/11
+ */
+class EarPluginIntegrationTest extends AbstractIntegrationTest {
+
+    @Before
+    void "boring setup"() {
+        file("rootLib.jar").createNewFile()
+        file("earLib.jar").createNewFile()
+
+        file("settings.gradle").write("rootProject.name='root'")
+    }
+
+    @Test
+    void "creates ear archive"() {
+        file("build.gradle").write("""
+apply plugin: 'ear'
+
+dependencies {
+    deploy files('rootLib.jar')
+    earlib files('earLib.jar')
+}
+
+""")
+        //when
+        executer.withTasks('assemble').run()
+        file("build/libs/root.ear").unzipTo(file("unzipped"))
+
+        //then
+        file("unzipped/rootLib.jar").assertExists()
+        file("unzipped/META-INF/MANIFEST.MF").assertExists()
+        file("unzipped/META-INF/application.xml").assertExists()
+        file("unzipped/lib/earLib.jar").assertExists()
+    }
+
+    @Test
+    void "customizes ear archive"() {
+        file("build.gradle").write("""
+apply plugin: 'ear'
+
+dependencies {
+    earlib files('earLib.jar')
+}
+
+ear {
+    libDirName 'CUSTOM/lib'
+
+    deploymentDescriptor {
+        applicationName = "cool ear"
+    }
+}
+
+""")
+        //when
+        executer.withTasks('assemble').run()
+        file("build/libs/root.ear").unzipTo(file("unzipped"))
+
+        //then
+        file("unzipped/CUSTOM/lib/earLib.jar").assertExists()
+        assert file("unzipped/META-INF/application.xml").text.contains('cool ear')
+    }
+
+    @Test
+    void "uses content found in specified app folder"() {
+        def applicationXml = """<?xml version="1.0"?>
+<application xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_6.xsd" version="6">
+  <application-name>customear</application-name>
+</application>
+"""
+
+        file('app/META-INF/application.xml').createFile().write(applicationXml)
+        file('app/someOtherFile.txt').createFile()
+        file('app/META-INF/stuff/yetAnotherFile.txt').createFile()
+        file("build.gradle").write("""
+apply plugin: 'ear'
+
+ear {
+  appDirName 'app'
+}
+""")
+
+        //when
+        executer.withTasks('assemble').run()
+        file("build/libs/root.ear").unzipTo(file("unzipped"))
+
+        //then
+        assert file("unzipped/someOtherFile.txt").assertExists()
+        assert file("unzipped/META-INF/stuff/yetAnotherFile.txt").assertExists()
+        assert file("unzipped/META-INF/application.xml").text == applicationXml
+    }
+
+    @Test
+    void "uses content found in default app folder"() {
+        def applicationXml = """<?xml version="1.0"?>
+<application xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_6.xsd" version="6">
+  <application-name>customear</application-name>
+</application>
+"""
+
+        file('src/main/application/META-INF/application.xml').createFile().write(applicationXml)
+        file('src/main/application/someOtherFile.txt').createFile()
+        file('src/main/application/META-INF/stuff/yetAnotherFile.txt').createFile()
+        file("build.gradle").write("""
+apply plugin: 'ear'
+ear {
+    deploymentDescriptor {
+        applicationName = 'descriptor modification will not have any affect when application.xml already exists in source'
+    }
+}
+""")
+
+        //when
+        executer.withTasks('assemble').run()
+        file("build/libs/root.ear").unzipTo(file("unzipped"))
+
+        //then
+        assert file("unzipped/someOtherFile.txt").assertExists()
+        assert file("unzipped/META-INF/stuff/yetAnotherFile.txt").assertExists()
+        assert file("unzipped/META-INF/application.xml").text == applicationXml
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy
new file mode 100644
index 0000000..a5bac93
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/Ear.groovy
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 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.ear
+
+import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
+import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
+import org.gradle.plugins.ear.descriptor.EarModule
+import org.gradle.plugins.ear.descriptor.internal.DefaultEarModule
+import org.gradle.plugins.ear.descriptor.internal.DefaultEarWebModule
+
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.file.CopySpec
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.internal.file.collections.FileTreeAdapter
+import org.gradle.api.internal.file.collections.MapFileTree
+import org.gradle.util.ConfigureUtil
+
+/**
+ * Assembles an EAR archive.
+ *
+ * @author David Gileadi
+ */
+class Ear extends Jar {
+    public static final String EAR_EXTENSION = 'ear'
+
+    /**
+     * The name of the library directory in the EAR file. Default is "lib".
+     */
+    String libDirName
+
+    /**
+     * The deployment descriptor configuration.
+     */
+    DeploymentDescriptor deploymentDescriptor
+
+    private CopySpec lib
+
+    Ear() {
+        extension = EAR_EXTENSION
+        lib = copyAction.rootSpec.addChild().into {
+            getLibDirName()
+        }
+        copyAction.mainSpec.eachFile { FileCopyDetails details ->
+            if (deploymentDescriptor && details.path.equalsIgnoreCase('META-INF/' + deploymentDescriptor.fileName)) {
+                // the deployment descriptor already exists; no need to generate it
+                deploymentDescriptor = null
+            }
+            // since we might generate the deployment descriptor, record each top-level module
+            if (deploymentDescriptor && details.path.lastIndexOf('/') <= 0) {
+                EarModule module
+                if (details.path.toLowerCase().endsWith(".war")) {
+                    module = new DefaultEarWebModule(details.path, details.path.substring(0, details.path.lastIndexOf('.')))
+                } else {
+                    module = new DefaultEarModule(details.path)
+                }
+                if (!deploymentDescriptor.modules.contains(module)) {
+                    deploymentDescriptor.modules.add module
+                }
+            }
+        }
+        // create our own metaInf which runs after mainSpec's files
+        // this allows us to generate the deployment descriptor after recording all modules it contains
+        def metaInf = copyAction.mainSpec.addChild().into('META-INF')
+        metaInf.addChild().from {
+            MapFileTree descriptorSource = new MapFileTree(temporaryDirFactory)
+            final DeploymentDescriptor descriptor = deploymentDescriptor
+            if (descriptor) {
+                if (!descriptor.libraryDirectory) {
+                    descriptor.libraryDirectory = libDirName
+                }
+                descriptorSource.add(descriptor.fileName) {OutputStream outstr ->
+                    descriptor.writeTo(new OutputStreamWriter(outstr))
+                }
+                return new FileTreeAdapter(descriptorSource)
+            }
+            return null
+        }
+    }
+
+    /**
+     * Configures the deployment descriptor for this EAR archive.
+     *
+     * <p>The given closure is executed to configure the deployment descriptor. The {@link DeploymentDescriptor}
+     * is passed to the closure as its delegate.</p>
+     *
+     * @param configureClosure The closure.
+     * @return This.
+     */
+    Ear deploymentDescriptor(Closure configureClosure) {
+        if (!deploymentDescriptor) {
+            deploymentDescriptor = new DefaultDeploymentDescriptor(project.fileResolver) // implied use of ProjectInternal
+        }
+        ConfigureUtil.configure(configureClosure, deploymentDescriptor)
+        this
+    }
+
+    /**
+     * A location for dependency libraries to include in the 'lib' directory of the EAR archive.
+     */
+    public CopySpec getLib() {
+        return lib.addChild()
+    }
+
+    /**
+     * Adds dependency libraries to include in the 'lib' directory of the EAR archive.
+     *
+     * <p>The given closure is executed to configure a {@code CopySpec}. The {@link CopySpec} is passed to the closure
+     * as its delegate.</p>
+     *
+     * @param configureClosure The closure.
+     * @return The created {@code CopySpec}
+     */
+    public CopySpec lib(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, getLib())
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
new file mode 100644
index 0000000..cd27c69
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2011 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.ear;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.plugins.ear.descriptor.DeploymentDescriptor;
+
+import java.util.concurrent.Callable;
+
+/**
+ * <p>
+ * A {@link Plugin} with tasks which assemble a web application into a EAR file.
+ * </p>
+ *
+ * @author David Gileadi, Hans Dockter
+ */
+public class EarPlugin implements Plugin<ProjectInternal> {
+
+    public static final String EAR_TASK_NAME = "ear";
+
+    public static final String DEPLOY_CONFIGURATION_NAME = "deploy";
+    public static final String EARLIB_CONFIGURATION_NAME = "earlib";
+
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(BasePlugin.class);
+
+        final EarPluginConvention earPluginConvention = project.getServices().get(Instantiator.class).newInstance(EarPluginConvention.class, project.getFileResolver());
+        project.getConvention().getPlugins().put("ear", earPluginConvention);
+        earPluginConvention.setLibDirName("lib");
+        earPluginConvention.setAppDirName("src/main/application");
+
+        wireEarTaskConventions(project, earPluginConvention);
+        configureConfigurations(project);
+
+        PluginContainer plugins = project.getPlugins();
+
+        setupEarTask(project, earPluginConvention);
+
+        configureWithJavaPluginApplied(project, earPluginConvention, plugins);
+        configureWithNoJavaPluginApplied(project, earPluginConvention);
+    }
+
+    private void configureWithNoJavaPluginApplied(final ProjectInternal project, final EarPluginConvention earPluginConvention) {
+        project.getTasks().withType(Ear.class, new Action<Ear>() {
+            public void execute(final Ear task) {
+                task.from(new Callable<FileCollection>() {
+                    public FileCollection call() throws Exception {
+                        if (project.getPlugins().hasPlugin(JavaPlugin.class)) {
+                            return null;
+                        } else {
+                            return project.fileTree(earPluginConvention.getAppDirName());
+                        }
+                    }
+                });
+            }
+        });
+    }
+
+    private void configureWithJavaPluginApplied(final ProjectInternal project, final EarPluginConvention earPluginConvention, PluginContainer plugins) {
+        plugins.withType(JavaPlugin.class, new Action<JavaPlugin>() {
+            public void execute(JavaPlugin javaPlugin) {
+                final JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin(JavaPluginConvention.class);
+
+                SourceSet sourceSet = javaPluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+                sourceSet.getResources().srcDir(new Callable() {
+                    public Object call() throws Exception {
+                        return earPluginConvention.getAppDirName();
+                    }
+                });
+                project.getTasks().withType(Ear.class, new Action<Ear>() {
+                    public void execute(final Ear task) {
+                        task.dependsOn(new Callable<FileCollection>() {
+                            public FileCollection call() throws Exception {
+                                return javaPluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME)
+                                        .getRuntimeClasspath();
+                            }
+                        });
+                        task.from(new Callable<FileCollection>() {
+                            public FileCollection call() throws Exception {
+                                return javaPluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput();
+                            }
+                        });
+                    }
+                });
+            }
+        });
+    }
+
+    private void setupEarTask(final Project project, EarPluginConvention convention) {
+        Ear ear = project.getTasks().add(EAR_TASK_NAME, Ear.class);
+        ear.setDescription("Generates a ear archive with all the modules, the application descriptor and the libraries.");
+        DeploymentDescriptor deploymentDescriptor = convention.getDeploymentDescriptor();
+        if (deploymentDescriptor != null) {
+            if (deploymentDescriptor.getDisplayName() == null) {
+                deploymentDescriptor.setDisplayName(project.getName());
+            }
+            if (deploymentDescriptor.getDescription() == null) {
+                deploymentDescriptor.setDescription(project.getDescription());
+            }
+        }
+        ear.setGroup(BasePlugin.BUILD_GROUP);
+        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(new ArchivePublishArtifact(ear));
+
+        project.getTasks().withType(Ear.class, new Action<Ear>() {
+            public void execute(Ear task) {
+                task.getLib().from(new Callable<FileCollection>() {
+                    public FileCollection call() throws Exception {
+                        return project.getConfigurations().getByName(EARLIB_CONFIGURATION_NAME);
+                    }
+                });
+                task.from(new Callable<FileCollection>() {
+                    public FileCollection call() throws Exception {
+                        // add the module configuration's files
+                        return project.getConfigurations().getByName(DEPLOY_CONFIGURATION_NAME);
+                    }
+                });
+            }
+        });
+    }
+
+    private void wireEarTaskConventions(Project project, final EarPluginConvention earConvention) {
+        project.getTasks().withType(Ear.class, new Action<Ear>() {
+            public void execute(Ear task) {
+                task.getConventionMapping().map("libDirName", new Callable<String>() {
+                    public String call() throws Exception { return earConvention.getLibDirName(); }
+                });
+                task.getConventionMapping().map("deploymentDescriptor", new Callable<DeploymentDescriptor>() {
+                    public DeploymentDescriptor call() throws Exception { return earConvention.getDeploymentDescriptor(); }
+                });
+            }
+        });
+    }
+
+    private void configureConfigurations(final Project project) {
+
+        ConfigurationContainer configurations = project.getConfigurations();
+        Configuration moduleConfiguration = configurations.add(DEPLOY_CONFIGURATION_NAME).setVisible(false)
+                .setTransitive(false).setDescription("Classpath for deployable modules, not transitive.");
+        Configuration earlibConfiguration = configurations.add(EARLIB_CONFIGURATION_NAME).setVisible(false)
+                .setDescription("Classpath for module dependencies.");
+
+        configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
+                .extendsFrom(moduleConfiguration, earlibConfiguration);
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPluginConvention.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPluginConvention.groovy
new file mode 100644
index 0000000..3a74f5a
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPluginConvention.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 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.ear
+
+import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
+import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.ConfigureUtil
+
+public class EarPluginConvention {
+
+    /**
+     * The name of the application directory, relative to the project directory. Default is "src/main/application".
+     */
+    String appDirName
+
+    public void setAppDirName(String appDirName) {
+        this.appDirName = appDirName
+        if (deploymentDescriptor) {
+            deploymentDescriptor.readFrom new File(appDirName, "META-INF/" + deploymentDescriptor.fileName)
+        }
+    }
+
+    /**
+     * Allows changing the application directory. Default is "src/main/application".
+     *
+     * @param appDirName
+     */
+    void appDirName(String appDirName) {
+        this.setAppDirName(appDirName);
+    }
+
+    /**
+     * The name of the library directory in the EAR file. Default is "lib".
+     */
+    String libDirName
+
+    /**
+     * Allows changing the library directory in the EAR file. Default is "lib".
+     */
+    void libDirName(String libDirName) {
+        this.libDirName = libDirName
+    }
+
+    /**
+     * A custom deployment descriptor configuration. Default is an "application.xml" with sensible defaults.
+     */
+    DeploymentDescriptor deploymentDescriptor
+
+    private final FileResolver fileResolver
+
+    def EarPluginConvention(FileResolver fileResolver) {
+        this.fileResolver = fileResolver
+        deploymentDescriptor = new DefaultDeploymentDescriptor(fileResolver)
+        deploymentDescriptor.readFrom "$appDirName/META-INF/$deploymentDescriptor.fileName"
+    }
+
+    /**
+     * Configures the deployment descriptor for this EAR archive.
+     *
+     * <p>The given closure is executed to configure the deployment descriptor. The {@link DeploymentDescriptor}
+     * is passed to the closure as its delegate.</p>
+     *
+     * @param configureClosure The closure.
+     * @return This.
+     */
+    public EarPluginConvention deploymentDescriptor(Closure configureClosure) {
+        if (!deploymentDescriptor) {
+            deploymentDescriptor = new DefaultDeploymentDescriptor(fileResolver)
+        }
+        ConfigureUtil.configure(configureClosure, deploymentDescriptor)
+        return this
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
new file mode 100644
index 0000000..26a9013
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2011 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.ear.descriptor;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A deployment descriptor such as application.xml.
+ * 
+ * @author David Gileadi
+ */
+public interface DeploymentDescriptor {
+
+    /**
+     * The name of the descriptor file, typically "application.xml"
+     */
+    public String getFileName();
+
+    public void setFileName(String fileName);
+
+    /**
+     * The version of application.xml. Required. Valid versions are "1.3", "1.4", "5" and "6". Defaults to "6".
+     */
+    public String getVersion();
+
+    public void setVersion(String version);
+
+    /**
+     * The application name. Optional. Only valid with version 6.
+     */
+    public String getApplicationName();
+
+    public void setApplicationName(String applicationName);
+
+    /**
+     * Whether to initialize modules in the order they appear in the descriptor, with the exception of client modules.
+     * Optional. Only valid with version 6.
+     */
+    public Boolean getInitializeInOrder();
+
+    public void setInitializeInOrder(Boolean initializeInOrder);
+
+    /**
+     * The application description. Optional.
+     */
+    public String getDescription();
+
+    public void setDescription(String description);
+
+    /**
+     * The application display name. Optional.
+     */
+    public String getDisplayName();
+
+    public void setDisplayName(String displayName);
+
+    /**
+     * The name of the directory to look for libraries in. Optional. If not specified then "lib" is assumed. Typically
+     * this should be set via {@link org.gradle.plugins.ear.EarPluginConvention#setLibDirName(String)} instead of this property.
+     */
+    public String getLibraryDirectory();
+
+    public void setLibraryDirectory(String libraryDirectory);
+
+    /**
+     * List of module descriptors. Must not be empty. Non-null and order-maintaining by default. Must maintain order if
+     * initializeInOrder is <code>true</code>.
+     */
+    public Set<? extends EarModule> getModules();
+
+    public void setModules(Set<? extends EarModule> modules);
+
+    /**
+     * Add a module to the deployment descriptor.
+     * 
+     * @param module
+     *            The module to add.
+     * @param type
+     *            The type of the module, such as "ejb", "java", etc.
+     * @return this.
+     */
+    public DeploymentDescriptor module(EarModule module, String type);
+
+    /**
+     * Add a module to the deployment descriptor.
+     * 
+     * @param path
+     *            The path of the module to add.
+     * @param type
+     *            The type of the module, such as "ejb", "java", etc.
+     * @return this.
+     */
+    public DeploymentDescriptor module(String path, String type);
+
+    /**
+     * Add a web module to the deployment descriptor.
+     * 
+     * @param path
+     *            The path of the module to add.
+     * @param contextRoot
+     *            The context root type of the web module.
+     * @return this.
+     */
+    public DeploymentDescriptor webModule(String path, String contextRoot);
+
+    /**
+     * List of security roles. Optional. Non-null and order-maintaining by default.
+     */
+    public Set<? extends EarSecurityRole> getSecurityRoles();
+
+    public void setSecurityRoles(Set<? extends EarSecurityRole> securityRoles);
+
+    /**
+     * Add a security role to the deployment descriptor.
+     * 
+     * @param role
+     *            The security role to add.
+     * @return this.
+     */
+    public DeploymentDescriptor securityRole(EarSecurityRole role);
+
+    /**
+     * Add a security role to the deployment descriptor.
+     * 
+     * @param role
+     *            The name of the security role to add.
+     * @return this.
+     */
+    public DeploymentDescriptor securityRole(String role);
+
+    /**
+     * Mapping of module paths to module types. Non-null by default. For example, to specify that a module is a java
+     * module, set <code>moduleTypeMappings["myJavaModule.jar"] = "java"</code>.
+     */
+    public Map<String, String> getModuleTypeMappings();
+
+    public void setModuleTypeMappings(Map<String, String> moduleTypeMappings);
+
+    /**
+     * 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 groovy.util.Node}. The closure can modify the XML before it is written to the
+     * output file. This allows additional JavaEE version 6 elements like "data-source" or "resource-ref" to be
+     * included.
+     * 
+     * @param closure
+     *            The closure to execute when the XML has been created
+     * @return this
+     */
+    public DeploymentDescriptor withXml(Closure 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 groovy.util.Node}. The action can modify the XML before it is written to the output
+     * file. This allows additional JavaEE version 6 elements like "data-source" or "resource-ref" to be included.
+     * 
+     * @param action
+     *            The action to execute when the XML has been created
+     * @return this
+     */
+    public DeploymentDescriptor withXml(Action<? super XmlProvider> action);
+
+    /**
+     * Reads the deployment descriptor from a reader.
+     * 
+     * @param reader
+     *            The reader to read the deployment descriptor from
+     * @return this
+     */
+    DeploymentDescriptor readFrom(Reader reader);
+
+    /**
+     * Reads the deployment descriptor from a file. The paths are resolved as defined by
+     * {@link org.gradle.api.Project#files(Object...)}
+     * 
+     * @param path
+     *            The path of the file to read the deployment descriptor from
+     * @return whether the descriptor could be read from the given path
+     */
+    boolean readFrom(Object path);
+
+    /**
+     * Writes the deployment descriptor into a writer.
+     * 
+     * @param writer
+     *            The writer to write the deployment descriptor to
+     * @return this
+     */
+    DeploymentDescriptor writeTo(Writer writer);
+
+    /**
+     * Writes the deployment descriptor into a file. The paths are resolved as defined by
+     * {@link org.gradle.api.Project#files(Object...)}
+     * 
+     * @param path
+     *            The path of the file to write the deployment descriptor into.
+     * @return this
+     */
+    DeploymentDescriptor writeTo(Object path);
+}
\ No newline at end of file
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java
new file mode 100644
index 0000000..42a22fc
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarModule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.ear.descriptor;
+
+import groovy.util.Node;
+
+/**
+ * A module element in a deployment descriptor like application.xml.
+ * 
+ * @author David Gileadi
+ */
+public interface EarModule {
+
+    /**
+     * The connector element specifies the URI of an archive file, relative to the top level of the application package.
+     */
+    public String getPath();
+
+    public void setPath(String path);
+
+    /**
+     * The alt-dd element specifies an optional URI to the post-assembly version of the deployment descriptor file for a
+     * particular Java EE module. The URI must specify the full pathname of the deployment descriptor file relative to
+     * the application's root directory. If alt-dd is not specified, the deployer must read the deployment descriptor
+     * from the default location and file name required by the respective component specification.
+     */
+    public String getAltDeployDescriptor();
+
+    public void setAltDeployDescriptor(String altDeployDescriptor);
+
+    /**
+     * Convert this object to an XML Node (or two nodes if altDeployDescriptor is not null).
+     * 
+     * @param parentModule
+     *            The parent <module> node to add this node to.
+     * @param name
+     *            The name of this node.
+     * @return The new node. If an <alt-dd> node is created it is not returned.
+     */
+    public Node toXmlNode(Node parentModule, Object name);
+}
\ No newline at end of file
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java
new file mode 100644
index 0000000..1ec1873
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarSecurityRole.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.ear.descriptor;
+
+/**
+ * A security-role element in a deployment descriptor like application.xml.
+ * 
+ * @author David Gileadi
+ */
+public interface EarSecurityRole {
+
+    /**
+     * A description of the security role. Optional.
+     */
+    public String getDescription();
+
+    public void setDescription(String description);
+
+    /**
+     * The name of the security role. Required.
+     */
+    public String getRoleName();
+
+    public void setRoleName(String roleName);
+
+}
\ No newline at end of file
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java
new file mode 100644
index 0000000..f34425d
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/EarWebModule.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.ear.descriptor;
+
+/**
+ * A module element in a deployment descriptor like application.xml that has a web child element.
+ * 
+ * @author David Gileadi
+ */
+public interface EarWebModule extends EarModule {
+
+    /**
+     * The context-root element specifies the context root of a web application.
+     */
+    public String getContextRoot();
+
+    public void setContextRoot(String contextRoot);
+}
\ No newline at end of file
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
new file mode 100644
index 0000000..36a499c
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2011 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.ear.descriptor.internal
+
+import groovy.xml.QName
+import org.gradle.api.Action
+import org.gradle.api.UncheckedIOException
+import org.gradle.api.XmlProvider
+import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
+import org.gradle.plugins.ear.descriptor.EarModule
+import org.gradle.plugins.ear.descriptor.EarSecurityRole
+import org.gradle.plugins.ear.descriptor.EarWebModule
+import org.gradle.api.internal.XmlTransformer
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.DomNode
+
+/**
+ * @author David Gileadi
+ */
+class DefaultDeploymentDescriptor implements DeploymentDescriptor {
+
+    private String fileName = "application.xml"
+    String version = "6"
+    String applicationName
+    Boolean initializeInOrder
+    String description
+    String displayName
+    String libraryDirectory
+    Set<? extends EarModule> modules = new LinkedHashSet<EarModule>()
+    Set<? extends EarSecurityRole> securityRoles = new LinkedHashSet<EarSecurityRole>()
+    Map<String, String> moduleTypeMappings = new HashMap<String, String>()
+    private FileResolver fileResolver
+    final XmlTransformer transformer = new XmlTransformer()
+
+    public DefaultDeploymentDescriptor(FileResolver fileResolver) {
+        this(new File("META-INF", "application.xml"), fileResolver)
+    }
+
+    public DefaultDeploymentDescriptor(Object descriptorPath, FileResolver fileResolver) {
+        this.fileResolver = fileResolver
+        if (fileResolver) {
+            File descriptorFile = fileResolver.resolve(descriptorPath)
+            if (descriptorFile) {
+                fileName = descriptorFile.name
+                readFrom descriptorFile
+            }
+        }
+    }
+
+    public String getFileName() {
+        return fileName
+    }
+    public void setFileName(String fileName) {
+        this.fileName = fileName
+        readFrom new File("META-INF", fileName)
+	}
+
+    public DefaultDeploymentDescriptor module(EarModule module, String type) {
+        modules.add module
+        moduleTypeMappings[module.path] = type
+        return this
+    }
+
+    public DefaultDeploymentDescriptor module(String path, String type) {
+        return module(new DefaultEarModule(path), type)
+    }
+
+    public DefaultDeploymentDescriptor webModule(String path, String contextRoot) {
+        modules.add(new DefaultEarWebModule(path, contextRoot))
+        moduleTypeMappings[path] = "web"
+        return this
+    }
+
+    public DefaultDeploymentDescriptor securityRole(EarSecurityRole role) {
+        securityRoles.add role
+        return this
+    }
+
+    public DeploymentDescriptor securityRole(String role) {
+        securityRoles.add(new DefaultEarSecurityRole(role))
+        return this
+    }
+
+    public DeploymentDescriptor withXml(Closure closure) {
+        transformer.addAction(closure)
+        return this
+    }
+
+    public DeploymentDescriptor withXml(Action<? super XmlProvider> action) {
+        transformer.addAction(action)
+        return this
+    }
+
+    boolean readFrom(Object path) {
+        if (!fileResolver) {
+            return false
+        }
+        File descriptorFile = fileResolver.resolve(path)
+        if (!descriptorFile || !descriptorFile.exists()) {
+            return false
+        }
+
+        try {
+            FileReader reader = new FileReader(descriptorFile)
+            readFrom reader
+            return true
+        } catch (IOException e) {
+            throw new UncheckedIOException(e)
+        }
+    }
+
+    DeploymentDescriptor readFrom(Reader reader) {
+        try {
+            def appNode = new XmlParser().parse(reader)
+            version = appNode. at version
+
+            appNode.children().each { child ->
+                switch (localNameOf(child)) {
+                    case "application-name":
+                        applicationName = child.text()
+                        break
+                    case "initialize-in-order":
+                        initializeInOrder = child.text() as Boolean
+                        break
+                    case "description":
+                        description = child.text()
+                        break
+                    case "display-name":
+                        displayName = child.text()
+                        break
+                    case "library-directory":
+                        libraryDirectory = child.text()
+                        break
+                    case "module":
+                        def module
+                        child.children().each { moduleNode ->
+                            switch (localNameOf(moduleNode)) {
+                                case "web":
+                                    module = new DefaultEarWebModule(moduleNode."web-uri".text(), moduleNode."context-root".text())
+                                    modules.add(module)
+                                    moduleTypeMappings[module.path] = "web"
+                                    break
+                                case "alt-dd":
+                                    module.altDeployDescriptor = moduleNode.text()
+                                    break
+                                default:
+                                    module = new DefaultEarModule(moduleNode.text())
+                                    modules.add(module)
+                                    moduleTypeMappings[module.path] = localNameOf(moduleNode)
+                                    break
+                            }
+                        }
+                        break
+                    case "security-role":
+                        securityRoles.add(new DefaultEarSecurityRole(child."role-name".text(), child.description.text()))
+                        break
+                    default:
+                        withXml { it.asNode().append child}
+                        break
+                }
+            }
+        } finally {
+            reader.close();
+        }
+        return this
+    }
+
+    private String localNameOf(Node node) {
+        node.name() instanceof QName ? node.name().localPart : node.name() as String
+    }
+
+    public DefaultDeploymentDescriptor writeTo(Object path) {
+
+        try {
+            File file = fileResolver.resolve(path)
+            if (file.getParentFile() != null) {
+                file.getParentFile().mkdirs()
+            }
+            FileWriter writer = new FileWriter(file)
+            try {
+                return writeTo(writer)
+            } finally {
+                writer.close()
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e)
+        }
+    }
+
+    public DefaultDeploymentDescriptor writeTo(Writer writer) {
+        transformer.transform(toXmlNode(), writer)
+        return this;
+    }
+
+    protected Node toXmlNode() {
+        DomNode root = new DomNode(nodeNameFor("application"))
+        root. at version = version
+        if (version != "1.3") {
+            root.@"xmlns:xsi" = "http://www.w3.org/2001/XMLSchema-instance"
+        }
+        switch (version) {
+            case "1.3":
+                root.publicId = "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
+                root.systemId = "http://java.sun.com/dtd/application_1_3.dtd"
+                break
+            case "1.4":
+                root.@"xsi:schemaLocation" = "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"
+                break
+            case "5":
+            case "6":
+                root.@"xsi:schemaLocation" = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_${version}.xsd"
+                break
+        }
+
+        if (applicationName) {
+            new Node(root, nodeNameFor("application-name"), applicationName)
+        }
+        if (description) {
+            new Node(root, nodeNameFor("description"), description)
+        }
+        if (displayName) {
+            new Node(root, nodeNameFor("display-name"), displayName)
+        }
+        if (initializeInOrder) {
+            new Node(root, nodeNameFor("initialize-in-order"), initializeInOrder)
+        }
+        modules.each { module ->
+            def moduleNode = new Node(root, nodeNameFor("module"))
+            module.toXmlNode(moduleNode, moduleNameFor(module))
+        }
+        if (securityRoles) {
+            securityRoles.each { role ->
+                def roleNode = new Node(root, nodeNameFor("security-role"))
+                if (role.description) {
+                    new Node(roleNode, nodeNameFor("description"), role.description)
+                }
+                new Node(roleNode, nodeNameFor("role-name"), role.roleName)
+            }
+        }
+        if (libraryDirectory) {
+            new Node(root, nodeNameFor("library-directory"), libraryDirectory)
+        }
+        return root
+    }
+
+    protected Object moduleNameFor(EarModule module) {
+
+        def name = moduleTypeMappings[module.path]
+        if (!name) {
+            if (module instanceof EarWebModule) {
+                name = "web"
+            } else {
+                // assume EJB is the most common kind of EAR deployment
+                name = "ejb"
+            }
+        }
+        return nodeNameFor(name)
+    }
+
+    protected Object nodeNameFor(name) {
+
+        switch (version) {
+            case "1.3":
+                return name
+            case "1.4":
+                return new QName("http://java.sun.com/xml/ns/j2ee", name)
+            default:
+                return new QName("http://java.sun.com/xml/ns/javaee", name)
+        }
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy
new file mode 100644
index 0000000..14e1a19
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarModule.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.ear.descriptor.internal
+
+import groovy.xml.QName
+import org.gradle.plugins.ear.descriptor.EarModule
+
+/**
+ * @author David Gileadi
+ */
+class DefaultEarModule implements EarModule {
+
+    String path;
+    String altDeployDescriptor;
+
+    public DefaultEarModule() {
+    }
+
+    public DefaultEarModule(String path) {
+
+        this.path = path;
+    }
+
+    public Node toXmlNode(Node parentModule, Object name) {
+
+        def node = new Node(parentModule, name, path)
+        if (altDeployDescriptor) {
+            new Node(parentModule, nodeNameFor("alt-dd", name), altDeployDescriptor)
+        }
+        return node
+    }
+
+    protected Object nodeNameFor(name, sampleName) {
+
+        if (sampleName instanceof QName) {
+            return new QName(sampleName.namespaceURI, name)
+        }
+        return name
+    }
+
+    int hashCode() {
+        int result;
+        result = (path != null ? path.hashCode() : 0);
+        result = 31 * result + (altDeployDescriptor != null ? altDeployDescriptor.hashCode() : 0);
+        return result;
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true; }
+        if (!(o instanceof DefaultEarModule)) { return false; }
+
+        DefaultEarModule that = (DefaultEarModule) o;
+
+        if (altDeployDescriptor != that.altDeployDescriptor) { return false; }
+        if (path != that.path) { return false; }
+
+        return true;
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy
new file mode 100644
index 0000000..0bc4965
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarSecurityRole.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.ear.descriptor.internal
+
+import org.gradle.plugins.ear.descriptor.EarSecurityRole
+
+/**
+ * @author David Gileadi
+ */
+class DefaultEarSecurityRole implements EarSecurityRole {
+
+    String description
+    String roleName
+
+    public DefaultEarSecurityRole() {
+    }
+
+    public DefaultEarSecurityRole(String roleName) {
+
+        this.roleName = roleName;
+    }
+
+    public DefaultEarSecurityRole(String roleName, String description) {
+
+        this.roleName = roleName;
+        this.description = description;
+    }
+
+    int hashCode() {
+        int result;
+        result = (description != null ? description.hashCode() : 0);
+        result = 31 * result + (roleName != null ? roleName.hashCode() : 0);
+        return result;
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true; }
+        if (!(o instanceof DefaultEarSecurityRole)) { return false; }
+
+        DefaultEarSecurityRole that = (DefaultEarSecurityRole) o;
+
+        if (description != that.description) { return false; }
+        if (roleName != that.roleName) { return false; }
+
+        return true;
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy
new file mode 100644
index 0000000..76640c7
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultEarWebModule.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.ear.descriptor.internal
+
+import org.gradle.plugins.ear.descriptor.EarWebModule;
+
+/**
+ * @author David Gileadi
+ */
+class DefaultEarWebModule extends DefaultEarModule implements EarWebModule {
+
+    String contextRoot
+
+    public DefaultEarWebModule() {
+    }
+
+    public DefaultEarWebModule(String path, String contextRoot) {
+
+        super(path);
+        this.contextRoot = contextRoot;
+    }
+
+    public Node toXmlNode(Node parentModule, Object name) {
+
+        def web = new Node(parentModule, name)
+        new Node(web, nodeNameFor("web-uri", name), path)
+        new Node(web, nodeNameFor("context-root", name), contextRoot)
+        if (altDeployDescriptor) {
+            new Node(parentModule, nodeNameFor("alt-dd", name), altDeployDescriptor)
+        }
+    }
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/package-info.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/package-info.java
new file mode 100644
index 0000000..777bd21
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Classes for working with EAR deployment descriptors.
+ */
+package org.gradle.plugins.ear.descriptor;
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/package-info.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/package-info.java
new file mode 100644
index 0000000..c2fa211
--- /dev/null
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Support for generating EAR archives in a Gradle build
+ */
+package org.gradle.plugins.ear;
diff --git a/subprojects/ear/src/main/resources/META-INF/gradle-plugins/ear.properties b/subprojects/ear/src/main/resources/META-INF/gradle-plugins/ear.properties
new file mode 100644
index 0000000..19f1fed
--- /dev/null
+++ b/subprojects/ear/src/main/resources/META-INF/gradle-plugins/ear.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.ear.EarPlugin
diff --git a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
new file mode 100644
index 0000000..898c6e7
--- /dev/null
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarPluginTest.groovy
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2011 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.ear
+
+import org.gradle.api.Action
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.WarPlugin
+import org.gradle.util.HelperUtil
+import org.junit.Before
+import org.junit.Test
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.plugins.JavaBasePlugin
+
+/**
+ * @author David Gileadi
+ */
+class EarPluginTest {
+    private ProjectInternal project
+    private EarPlugin earPlugin
+    private static final String TEST_APP_XML = toPlatformLineSeparators('<?xml version="1.0" encoding="UTF-8"?>\n' +
+        '<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" version="5">\n' +
+        '  <display-name>Application</display-name>\n' +
+        '  <module>\n' +
+        '    <web>\n' +
+        '      <web-uri>Web.war</web-uri>\n' +
+        '      <context-root>/</context-root>\n' +
+        '    </web>\n' +
+        '  </module>\n' +
+        '  <module>\n' +
+        '    <ejb>jrules-bres-session-wl100-6.7.3.jar</ejb>\n' +
+        '  </module>\n' +
+        '</application>')
+    
+
+    @Before
+    public void setUp() {
+        project = HelperUtil.createRootProject()
+        earPlugin = new EarPlugin()
+    }
+
+    @Test public void appliesBasePluginAndAddsConvention() {
+        earPlugin.apply(project)
+        
+        assertTrue(project.getPlugins().hasPlugin(BasePlugin));
+        assertThat(project.convention.plugins.ear, instanceOf(EarPluginConvention))
+    }
+    
+    @Test public void createsConfigurations() {
+        earPlugin.apply(project)
+
+        def configuration = project.configurations.getByName(EarPlugin.DEPLOY_CONFIGURATION_NAME)
+        assertFalse(configuration.visible)
+        assertFalse(configuration.transitive)
+
+        configuration = project.configurations.getByName(EarPlugin.EARLIB_CONFIGURATION_NAME)
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+    }
+
+    @Test public void addsTasks() {
+        earPlugin.apply(project)
+
+        def task = project.tasks[EarPlugin.EAR_TASK_NAME]
+        assertThat(task, instanceOf(Ear))
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+
+        task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assertThat(task, dependsOn(EarPlugin.EAR_TASK_NAME))
+    }
+
+    @Test public void addsTasksToJavaProject() {
+        project.plugins.apply(JavaPlugin.class)
+        earPlugin.apply(project)
+
+        def task = project.tasks[EarPlugin.EAR_TASK_NAME]
+        assertThat(task, instanceOf(Ear))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+
+        task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assertThat(task, dependsOn(EarPlugin.EAR_TASK_NAME))
+    }
+
+    @Test public void dependsOnEarlibConfig() {
+        earPlugin.apply(project)
+
+        Project childProject = HelperUtil.createChildProject(project, 'child')
+        JavaPlugin javaPlugin = new JavaPlugin()
+        javaPlugin.apply(childProject)
+
+        project.dependencies {
+            earlib project(path: childProject.path, configuration: 'archives')
+        }
+
+        def task = project.tasks[EarPlugin.EAR_TASK_NAME]
+        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, hasItem(':child:jar'))
+    }
+
+    @Test public void appliesMappingsToArchiveTasks() {
+        earPlugin.apply(project)
+
+        def task = project.task(type: Ear, 'customEar')
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+    }
+
+    @Test public void worksWithJavaBasePluginAppliedBeforeEarPlugin() {
+        project.plugins.apply(JavaBasePlugin.class)
+        earPlugin.apply(project)
+        def task = project.task(type: Ear, 'customEar')
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+    }
+
+    @Test public void appliesMappingsToArchiveTasksForJavaProject() {
+        earPlugin.apply(project)
+        project.plugins.apply(JavaPlugin.class)
+
+        def task = project.task(type: Ear, 'customEar') {
+            earModel = new EarPluginConvention(null)
+        }
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+
+        assertThat(task, dependsOn(hasItems(JavaPlugin.CLASSES_TASK_NAME)))
+    }
+
+    @Test public void addsEarAsPublication() {
+        earPlugin.apply(project)
+
+        Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
+        assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1));
+        assertThat(archiveConfiguration.getAllArtifacts().iterator().next().getType(), equalTo("ear"));
+    }
+
+    @Test public void replacesWarAsPublication() {
+        earPlugin.apply(project)
+        new WarPlugin().apply(project)
+
+        Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
+        assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1));
+        assertThat(archiveConfiguration.getAllArtifacts().iterator().next().getType(), equalTo("ear"));
+    }
+
+    @Test public void replacesJarAsPublication() {
+        earPlugin.apply(project)
+        new JavaPlugin().apply(project)
+
+        Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
+        assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1));
+        assertThat(archiveConfiguration.getAllArtifacts().iterator().next().getType(), equalTo("ear"));
+    }
+
+    @Test public void supportsAppDir() {
+        project.file("src/main/application/META-INF").mkdirs()
+        project.file("src/main/application/META-INF/test.txt").createNewFile()
+        project.file("src/main/application/test2.txt").createNewFile()
+
+        earPlugin.apply(project)
+
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+
+        inEar "test2.txt"
+        inEar "META-INF/test.txt"
+    }
+
+    @Test public void supportsRenamedAppDir() {
+        project.file("src/main/myapp").mkdirs()
+        project.file("src/main/myapp/test.txt").createNewFile()
+
+        earPlugin.apply(project)
+        project.convention.plugins.ear.appDirName = "src/main/myapp"
+
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+        inEar "test.txt"
+    }
+
+    @Test public void supportsRenamingLibDir() {
+        Project childProject = HelperUtil.createChildProject(project, 'child')
+        childProject.file("src/main/resources").mkdirs()
+        childProject.file("src/main/resources/test.txt").createNewFile()
+        JavaPlugin javaPlugin = new JavaPlugin()
+        javaPlugin.apply(childProject)
+
+        execute childProject.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+
+        earPlugin.apply(project)
+        project.convention.plugins.ear.libDirName = "APP-INF/lib"
+        project.dependencies {
+            earlib project(path: childProject.path, configuration: 'archives')
+        }
+
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+        
+        inEar "APP-INF/lib/child.jar"
+    }
+
+    @Test public void supportsGeneratingDeploymentDescriptor() {
+        earPlugin.apply(project)
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+
+        inEar "META-INF/application.xml"
+    }
+
+    @Test public void avoidsOverwritingDeploymentDescriptor() {
+        project.file("src/main/application/META-INF").mkdirs()
+        project.file("src/main/application/META-INF/application.xml").text = TEST_APP_XML
+
+        earPlugin.apply(project)
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+
+        assert inEar("META-INF/application.xml").text == TEST_APP_XML
+    }
+
+    @Test public void supportsRenamingDeploymentDescriptor() {
+        earPlugin.apply(project)
+        project.convention.plugins.ear.deploymentDescriptor {
+            fileName = "myapp.xml"
+        }
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+
+        inEar "META-INF/myapp.xml"
+    }
+
+    @Test public void avoidsOverwritingRenamedDeploymentDescriptor() {
+        project.file("src/main/application/META-INF").mkdirs()
+        project.file("src/main/application/META-INF/myapp.xml").text = TEST_APP_XML
+
+        earPlugin.apply(project)
+        project.convention.plugins.ear.deploymentDescriptor {
+            fileName = "myapp.xml"
+        }
+        execute project.tasks[EarPlugin.EAR_TASK_NAME]
+        assert inEar("META-INF/myapp.xml").text == TEST_APP_XML
+    }
+
+    private void execute(Task task) {
+        for (Task dep : task.taskDependencies.getDependencies(task)) {
+            for (Action action : dep.actions) {
+                action.execute(dep)
+            }
+        }
+        for (Action action : task.actions) {
+            action.execute(task)
+        }
+    }
+    
+    File inEar(path) {
+        def ear = project.zipTree("build/libs/${project.name}.ear")
+        assert !ear.empty
+        ear.matching { include path }.singleFile
+    }
+}
diff --git a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarTest.groovy b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarTest.groovy
new file mode 100644
index 0000000..fc2f7d5
--- /dev/null
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/EarTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2011 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.ear
+
+import org.gradle.plugins.ear.descriptor.internal.DefaultDeploymentDescriptor
+import org.gradle.api.tasks.bundling.AbstractArchiveTaskTest
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author David Gileadi
+ */
+class EarTest extends AbstractArchiveTaskTest {
+
+    Ear ear
+
+    Map filesFromDepencencyManager
+
+    @Before public void setUp() {
+        super.setUp()
+        ear = createTask(Ear)
+        configure(ear)
+    }
+
+    AbstractArchiveTask getArchiveTask() {
+        ear
+    }
+
+    @Test public void testEar() {
+        assertEquals(Ear.EAR_EXTENSION, ear.extension)
+    }
+
+    @Test public void testLibDirName() {
+        ear.libDirName = "APP-INF/lib"
+        assertEquals(ear.libDirName, ear.lib.destPath as String)
+    }
+
+    @Test public void testDeploymentDescriptor() {
+        ear.deploymentDescriptor = new DefaultDeploymentDescriptor(null)
+        checkDeploymentDescriptor()
+    }
+
+    @Test public void testDeploymentDescriptorWithNullManifest() {
+        ear.deploymentDescriptor = null
+        checkDeploymentDescriptor()
+    }
+
+    public void checkDeploymentDescriptor() {
+        ear.deploymentDescriptor {
+            fileName = "myApp.xml"
+            version = "5"
+            applicationName = "myapp"
+            initializeInOrder = true
+            displayName = "My App"
+            description = "My Application"
+            libraryDirectory = "APP-INF/lib"
+            module("my.jar", "java")
+            webModule("my.war", "/")
+            securityRole "admin"
+            securityRole "superadmin"
+            withXml { provider ->
+                //just adds an action
+            }
+        }
+        def d = ear.deploymentDescriptor
+        assertEquals("myApp.xml", d.fileName)
+        assertEquals("5", d.version)
+        assertEquals("myapp", d.applicationName)
+        assertEquals(true, d.initializeInOrder)
+        assertEquals("My App", d.displayName)
+        assertEquals("My Application", d.description)
+        assertEquals("APP-INF/lib", d.libraryDirectory)
+        assertEquals(2, d.modules.size())
+        assertEquals("my.jar", (d.modules as List)[0].path)
+        assertEquals("my.war", (d.modules as List)[1].path)
+        assertEquals("/", (d.modules as List)[1].contextRoot)
+        assertEquals("java", d.moduleTypeMappings["my.jar"])
+        assertEquals("web", d.moduleTypeMappings["my.war"])
+        assertEquals(2, d.securityRoles.size())
+        assertEquals("admin", (d.securityRoles as List)[0].roleName)
+        assertEquals("superadmin", (d.securityRoles as List)[1].roleName)
+        assertEquals(1, d.transformer.actions.size())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
new file mode 100644
index 0000000..59ed353
--- /dev/null
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.ear.descriptor.internal
+
+import javax.xml.parsers.DocumentBuilderFactory
+import spock.lang.Specification
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+/**
+ * @author: Szczepan Faber, created at: 6/3/11
+ */
+class DefaultDeploymentDescriptorTest extends Specification {
+
+    def out = new StringWriter()
+    def descriptor = new DefaultDeploymentDescriptor(null)
+
+    def "writes default descriptor"() {
+        when:
+        descriptor.writeTo(out)
+
+        then:
+        def root = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(out.toString().getBytes("utf-8"))).documentElement
+        root.nodeName == 'application'
+        root.getAttribute("xmlns") == "http://java.sun.com/xml/ns/javaee"
+        root.getAttribute("xmlns:xsi") == "http://www.w3.org/2001/XMLSchema-instance"
+        root.getAttribute("xsi:schemaLocation") == "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_6.xsd"
+        root.getAttribute("version") == "6"
+        root.childNodes.length == 0
+    }
+
+    def "writes version 1.3 default descriptor"() {
+        descriptor.version = '1.3'
+
+        when:
+        descriptor.writeTo(out)
+
+        then:
+        out.toString() == toPlatformLineSeparators("""<?xml version="1.0"?>
+<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN" "http://java.sun.com/dtd/application_1_3.dtd">
+<application version="1.3"/>
+""")
+    }
+
+    def "writes customized descriptor"() {
+        descriptor.fileName = "myApp.xml"
+        descriptor.version = "1.3"
+        descriptor.applicationName = "myapp"
+        descriptor.initializeInOrder = true
+        descriptor.displayName = "My App"
+        descriptor.description = "My Application"
+        descriptor.libraryDirectory = "APP-INF/lib"
+        descriptor.module("my.jar", "java")
+        descriptor.webModule("my.war", "/")
+        descriptor.securityRole "admin"
+        descriptor.securityRole "superadmin"
+        descriptor.withXml { it.asNode().appendNode("data-source", "my/data/source") }
+
+        when:
+        descriptor.writeTo(out)
+
+        then:
+        out.toString() == toPlatformLineSeparators("""<?xml version="1.0"?>
+<!DOCTYPE application PUBLIC "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN" "http://java.sun.com/dtd/application_1_3.dtd">
+<application version="1.3">
+  <application-name>myapp</application-name>
+  <description>My Application</description>
+  <display-name>My App</display-name>
+  <initialize-in-order>true</initialize-in-order>
+  <module>
+    <java>my.jar</java>
+  </module>
+  <module>
+    <web>
+      <web-uri>my.war</web-uri>
+      <context-root>/</context-root>
+    </web>
+  </module>
+  <security-role>
+    <role-name>admin</role-name>
+  </security-role>
+  <security-role>
+    <role-name>superadmin</role-name>
+  </security-role>
+  <library-directory>APP-INF/lib</library-directory>
+  <data-source>my/data/source</data-source>
+</application>
+""")
+    }
+}
diff --git a/subprojects/ide/ide.gradle b/subprojects/ide/ide.gradle
index d8c3403..4fc3cef 100644
--- a/subprojects/ide/ide.gradle
+++ b/subprojects/ide/ide.gradle
@@ -13,26 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-apply plugin: 'groovy' //needed so that resources are excluded from compilation (see how ideaProject task is configured in root project)
-apply from: "$rootDir/gradle/integTest.gradle"
-
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':scala')
     compile project(':core')
     compile project(':plugins')
+    compile project(':ear')
     compile project(':toolingApi')
-    compile libraries.commons_io
-    compile libraries.slf4j_api, libraries.jaxen
-    compile 'dom4j:dom4j:1.6.1 at jar'
+    compile libraries.slf4j_api
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
     testCompile libraries.xmlunit
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
-
-    //needed?
-    integTestCompile libraries.slf4j_api
-    integTestCompile project(path: ':core', configuration: 'integTestFixtures')
-    integTestRuntime project(path: ':core', configuration: 'integTestFixturesRuntime')
+    testCompile project(':coreImpl')
 }
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy
index d073477..1c956bd 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AbstractIdeIntegrationTest.groovy
@@ -17,19 +17,20 @@
 
 package org.gradle.plugins.ide
 
+import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.integtests.fixtures.MavenRepository
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.util.TestFile
 
 abstract class AbstractIdeIntegrationTest extends AbstractIntegrationTest {
-    protected void runTask(taskName, settingsScript = "rootProject.name = 'root'", buildScript) {
+    protected ExecutionResult runTask(taskName, settingsScript = "rootProject.name = 'root'", buildScript) {
         def settingsFile = file("settings.gradle")
         settingsFile << settingsScript
 
         def buildFile = file("build.gradle")
         buildFile << buildScript
 
-        executer.usingSettingsFile(settingsFile).usingBuildScript(buildFile).withTasks(taskName).run()
+        return executer.usingSettingsFile(settingsFile).usingBuildScript(buildFile).withTasks(taskName).run()
     }
 
     protected File getFile(Map options, String filename) {
@@ -48,16 +49,12 @@ abstract class AbstractIdeIntegrationTest extends AbstractIntegrationTest {
         buildFile.parentFile.file("src/main/resources").createDir()
     }
 
-    protected File publishArtifact(dir, group, artifact, dependency = null) {
-        def module = new MavenRepository(new TestFile(dir)).module(group, artifact, 1.0)
-        if (dependency) {
-            module.dependsOn(dependency)
-        }
-        return module.publishArtifact()
+    protected MavenRepository getMavenRepo() {
+        return new MavenRepository(getFile([:], 'repo'))
     }
 
-    protected runIdeaTask(buildScript) {
-        runTask("idea", buildScript)
+    protected ExecutionResult runIdeaTask(buildScript) {
+        return runTask("idea", buildScript)
     }
 
     protected parseImlFile(Map options = [:], String projectName) {
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..80da222
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesIntegrationTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.ide
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+/**
+ * @author Szczepan Faber, created at: 3/27/11
+ */
+class AutoTestedSamplesIntegrationTest extends AbstractAutoTestedSamplesTest {
+
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/ide/src/main")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesTest.groovy
deleted file mode 100644
index 71bf480..0000000
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/AutoTestedSamplesTest.groovy
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 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.ide
-
-import org.gradle.integtests.fixtures.internal.AbstractAutoTestedSamplesTest
-import org.junit.Test
-
-/**
- * Author: Szczepan Faber, created at: 3/27/11
- */
-class AutoTestedSamplesTest extends AbstractAutoTestedSamplesTest {
-
-    @Test
-    void runSamples() {
-        runSamplesFrom("subprojects/ide/src/main")
-    }
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
index 357a529..5d4de05 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/AbstractEclipseIntegrationTest.groovy
@@ -1,73 +1,75 @@
-/*
- * Copyright 2011 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.ide.eclipse
-
-import org.gradle.plugins.ide.AbstractIdeIntegrationTest
-
-class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
-    protected runEclipseTask(settingsScript = "rootProject.name = 'root'", buildScript) {
-        runTask("eclipse", settingsScript, buildScript)
-    }
-
-    protected File getProjectFile(Map options) {
-        getFile(options, ".project")
-    }
-
-    protected File getClasspathFile(Map options) {
-        getFile(options, ".classpath")
-    }
-
-    protected File getComponentFile(Map options) {
-        getFile(options, ".settings/org.eclipse.wst.common.component")
-    }
-
-    protected File getFacetFile(Map options) {
-        getFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
-    }
-
-    protected parseProjectFile(Map options) {
-        parseFile(options, ".project")
-    }
-
-    protected parseClasspathFile(Map options) {
-        parseFile(options, ".classpath")
-    }
-
-    protected parseComponentFile(Map options) {
-        parseFile(options, ".settings/org.eclipse.wst.common.component")
-    }
-
-    protected parseFacetFile(Map options) {
-        parseFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
-    }
-
-    protected String parseJdtFile() {
-        return getFile([:], '.settings/org.eclipse.jdt.core.prefs').text
-    }
-
-    protected findEntries(classpath, kind) {
-        classpath.classpathentry.findAll { it. at kind == kind }
-    }
-
-    protected libEntriesInClasspathFileHaveFilenames(String... filenames) {
-        def classpath = parseClasspathFile()
-        def libs = findEntries(classpath, "lib")
-        assert libs*. at path*.text().collect { new File(it).name } as Set == filenames as Set
-    }
-}
+/*
+ * Copyright 2011 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.ide.eclipse
+
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.plugins.ide.AbstractIdeIntegrationTest
+
+class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
+    protected ExecutionResult runEclipseTask(settingsScript = "rootProject.name = 'root'", buildScript) {
+        runTask("eclipse", settingsScript, buildScript)
+    }
+
+    protected File getProjectFile(Map options) {
+        getFile(options, ".project")
+    }
+
+    protected File getClasspathFile(Map options) {
+        getFile(options, ".classpath")
+    }
+
+    protected File getComponentFile(Map options) {
+        getFile(options, ".settings/org.eclipse.wst.common.component")
+    }
+
+    protected File getFacetFile(Map options) {
+        getFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
+    }
+
+    protected parseProjectFile(Map options) {
+        parseFile(options, ".project")
+    }
+
+    protected parseClasspathFile(Map options) {
+        parseFile(options, ".classpath")
+    }
+
+    protected parseComponentFile(Map options) {
+        parseFile(options, ".settings/org.eclipse.wst.common.component")
+    }
+
+    protected parseFacetFile(Map options) {
+        parseFile(options, ".settings/org.eclipse.wst.common.project.facet.core.xml")
+    }
+
+    protected String parseJdtFile() {
+        return getFile([:], '.settings/org.eclipse.jdt.core.prefs').text
+    }
+
+    protected findEntries(classpath, kind) {
+        classpath.classpathentry.findAll { it. at kind == kind }
+    }
+
+    protected libEntriesInClasspathFileHaveFilenames(String... filenames) {
+        def classpath = parseClasspathFile()
+        def libs = findEntries(classpath, "lib")
+        assert libs*. at path*.text().collect { new File(it).name } as Set == filenames as Set
+    }
+
+    protected EclipseClasspathFixture getClasspath() {
+        return new EclipseClasspathFixture(distribution.testDir, distribution.userHomeDir)
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
new file mode 100644
index 0000000..e1c771c
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathFixture.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2011 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.ide.eclipse
+
+import java.util.regex.Pattern
+import org.gradle.util.TestFile
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
+
+class EclipseClasspathFixture {
+    final TestFile projectDir
+    final TestFile userHomeDir
+    private Node classpath
+
+    EclipseClasspathFixture(TestFile projectDir, TestFile userHomeDir) {
+        this.projectDir = projectDir
+        this.userHomeDir = userHomeDir
+    }
+
+    Node getClasspath() {
+        if (classpath == null) {
+            TestFile file = projectDir.file('.classpath')
+            println "Using .classpath:"
+            println file.text
+            classpath = new XmlParser().parse(file)
+        }
+        return classpath
+    }
+
+    List<Node> getEntries() {
+        return getClasspath().classpathentry as List
+    }
+
+    String getOutput() {
+        return getClasspath().classpathentry.find { it. at kind == 'output' }. at path
+    }
+
+    List<String> getContainers() {
+        return getClasspath().classpathentry.findAll { it. at kind == 'con' }.collect { it. at path }
+    }
+
+    List<String> getSources() {
+        return getClasspath().classpathentry.findAll{ it. at kind == 'src' && !it. at path.startsWith('/') }.collect { it. at path }
+    }
+
+    List<String> getProjects() {
+        return getClasspath().classpathentry.findAll { it. at kind == 'src' && it. at path.startsWith('/') }.collect { it. at path }
+    }
+
+    List<EclipseLibrary> getLibs() {
+        return getClasspath().classpathentry.findAll { it. at kind == 'lib' }.collect { new EclipseLibrary(it) }
+    }
+
+    List<EclipseLibrary> getVars() {
+        return getClasspath().classpathentry.findAll { it. at kind == 'var' }.collect { new EclipseLibrary(it) }
+    }
+
+    class EclipseLibrary {
+        final Node entry
+
+        EclipseLibrary(Node entry) {
+            this.entry = entry
+        }
+
+        void assertHasJar(File jar) {
+            assert entry. at path == jar.absolutePath.replace(File.separator, '/')
+        }
+
+        void assertHasJar(String jar) {
+            assert entry. at path == jar
+        }
+
+        void assertHasCachedJar(String group, String module, String version) {
+            assert entry. at path ==~ cachePath(group, module, version, "jar") + Pattern.quote("${module}-${version}.jar")
+        }
+
+        void assertHasSource(File jar) {
+            assert entry. at sourcepath == jar.absolutePath.replace(File.separator, '/')
+        }
+
+        void assertHasSource(String jar) {
+            assert entry. at sourcepath == jar
+        }
+
+        void assertHasCachedSource(String group, String module, String version) {
+            assert entry. at sourcepath ==~ cachePath(group, module, version, "source") + Pattern.quote("${module}-${version}-sources.jar")
+        }
+
+        private String cachePath(String group, String module, String version, String type) {
+            return Pattern.quote("${userHomeDir.absolutePath.replace(File.separator, '/')}") + "/caches/artifacts-${artifactCacheVersion}/filestore/" + Pattern.quote("${group}/${module}/${version}/${type}/") + "\\w+/"
+        }
+
+        private def getArtifactCacheVersion() {
+            return DefaultCacheLockingManager.CACHE_LAYOUT_VERSION;
+        }
+
+        void assertHasNoSource() {
+            assert !entry. at sourcepath
+        }
+
+        void assertHasJavadoc(File file) {
+            assert entry.attributes
+            assert entry.attributes[0].attribute[0]. at name == 'javadoc_location'
+            assert entry.attributes[0].attribute[0]. at value == jarUrl(file)
+        }
+
+        private String jarUrl(File filePath){
+            "jar:${filePath.toURI().toURL()}!/"
+        }
+
+        void assertHasNoJavadoc() {
+            assert entry.attributes.size() == 0
+        }
+
+        void assertExported() {
+            assert entry. at exported == 'true'
+        }
+
+        void assertNotExported() {
+            assert !entry. at exported
+        }
+    }
+}
+
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
index 4fb5d49..aec98a9 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
@@ -18,9 +18,10 @@ package org.gradle.plugins.ide.eclipse
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 import org.junit.Test
+import spock.lang.Issue
 
 /**
- * Author: Szczepan Faber
+ * @author Szczepan Faber
  */
 class EclipseClasspathIntegrationTest extends AbstractEclipseIntegrationTest {
 
@@ -30,7 +31,127 @@ class EclipseClasspathIntegrationTest extends AbstractEclipseIntegrationTest {
     String content
 
     @Test
-    void allowsConfiguringEclipseClasspath() {
+    void classpathContainsLibraryEntriesForExternalAndFileDependencies() {
+        //given
+        def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
+        module.artifact(classifier: 'sources')
+        module.artifact(classifier: 'javadoc')
+        module.publish()
+        def jar = module.artifactFile
+        def srcJar = module.artifactFile(classifier: 'sources')
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+    mavenCentral()
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+    compile 'commons-lang:commons-lang:2.6'
+    compile files('lib/dep.jar')
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 3
+        libraries[0].assertHasJar(jar)
+        libraries[0].assertHasSource(srcJar)
+        libraries[0].assertHasNoJavadoc()
+        libraries[1].assertHasCachedJar('commons-lang', 'commons-lang', '2.6')
+        libraries[1].assertHasCachedSource('commons-lang', 'commons-lang', '2.6')
+        libraries[1].assertHasNoJavadoc()
+        libraries[2].assertHasJar(file('lib/dep.jar'))
+        libraries[2].assertHasNoSource()
+        libraries[2].assertHasNoJavadoc()
+    }
+
+    @Test
+    @Issue("GRADLE-1622")
+    void classpathContainsEntriesForDependenciesThatOnlyDifferByClassifier() {
+        given:
+        def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
+        module.artifact(classifier: 'extra')
+        module.artifact(classifier: 'tests')
+        module.publish()
+        def baseJar = module.artifactFile
+        def extraJar = module.artifactFile(classifier: 'extra')
+        def testsJar = module.artifactFile(classifier: 'tests')
+        def anotherJar = mavenRepo.module('coolGroup', 'another', '1.0').publish().artifactFile
+
+        when:
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+    compile 'coolGroup:niceArtifact:1.0:extra'
+    testCompile 'coolGroup:another:1.0'
+    testCompile 'coolGroup:niceArtifact:1.0:tests'
+}
+"""
+
+        then:
+        def libraries = classpath.libs
+        assert libraries.size() == 4
+        libraries[0].assertHasJar(baseJar)
+        libraries[1].assertHasJar(extraJar)
+        libraries[2].assertHasJar(testsJar)
+        libraries[3].assertHasJar(anotherJar)
+    }
+
+    @Test
+    void substituesPathVariablesIntoLibraryPathsExceptForJavadoc() {
+        //given
+        def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
+        module.artifact(classifier: 'sources')
+        module.artifact(classifier: 'javadoc')
+        module.publish()
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+    compile files('lib/dep.jar')
+}
+
+eclipse {
+    pathVariables REPO_DIR: file('${mavenRepo.uri}')
+    pathVariables LIB_DIR: file('lib')
+    classpath.downloadJavadoc = true
+}
+"""
+
+        //then
+        def libraries = classpath.vars
+        assert libraries.size() == 2
+        libraries[0].assertHasJar('REPO_DIR/coolGroup/niceArtifact/1.0/niceArtifact-1.0.jar')
+        libraries[0].assertHasSource('REPO_DIR/coolGroup/niceArtifact/1.0/niceArtifact-1.0-sources.jar')
+        libraries[1].assertHasJar('LIB_DIR/dep.jar')
+
+        //javadoc is not substituted
+        libraries[0].assertHasJavadoc(file("repo/coolGroup/niceArtifact/1.0/niceArtifact-1.0-javadoc.jar"))
+    }
+
+    @Test
+    void canCustomizeTheClasspathModel() {
         //when
         runEclipseTask """
 apply plugin: 'java'
@@ -41,48 +162,473 @@ sourceSets.main.resources.srcDirs.each { it.mkdirs() }
 
 configurations {
   someConfig
-  someOtherConfig
 }
 
 dependencies {
-  someConfig files('foo.txt', 'bar.txt', 'baz.txt')
-  someOtherConfig files('baz.txt')
+  someConfig files('foo.txt')
 }
 
 eclipse {
 
-  pathVariables fooPathVariable: new File('.')
+  pathVariables fooPathVariable: projectDir
 
   classpath {
     sourceSets = []
 
     plusConfigurations += configurations.someConfig
-    minusConfigurations += configurations.someOtherConfig
 
     containers 'someFriendlyContainer', 'andYetAnotherContainer'
 
-    classesOutputDir = file('build-eclipse')
+    defaultOutputDir = file('build-eclipse')
 
     downloadSources = false
     downloadJavadoc = true
+
+    file {
+      withXml { it.asNode().appendNode('message', 'be cool') }
+    }
   }
 }
 """
 
-        content = getFile([:], '.classpath').text
-        println content
+        //then
+        def vars = classpath.vars
+        assert vars.size() == 1
+        vars[0].assertHasJar("fooPathVariable/foo.txt")
+
+        def containers = classpath.containers
+        assert containers.size() == 3
+        assert containers[1] == 'someFriendlyContainer'
+        assert containers[2] == 'andYetAnotherContainer'
+
+        assert classpath.output == 'build-eclipse'
+        assert classpath.classpath.message[0].text() == 'be cool'
+    }
+
+    @Test
+    @Issue("GRADLE-1487")
+    void handlesPlusMinusConfigurationsForSelfResolvingDeps() {
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+configurations {
+  someConfig
+  someOtherConfig
+}
+
+dependencies {
+  someConfig files('foo.txt', 'bar.txt', 'unwanted.txt')
+  someOtherConfig files('unwanted.txt')
+}
+
+eclipse.classpath {
+    plusConfigurations += configurations.someConfig
+    minusConfigurations += configurations.someOtherConfig
+}
+"""
 
         //then
-        contains('foo.txt')
-        contains('bar.txt')
+        def libraries = classpath.libs
+        assert libraries.size() == 2
+        libraries[0].assertHasJar(file("foo.txt"))
+        libraries[1].assertHasJar(file("bar.txt"))
+    }
+
+    @Test
+    void handlesPlusMinusConfigurationsForProjectDeps() {
+        //when
+        runEclipseTask "include 'foo', 'bar', 'unwanted'",
+                """
+allprojects {
+  apply plugin: 'java'
+  apply plugin: 'eclipse'
+}
+
+configurations {
+  someConfig
+  someOtherConfig
+}
+
+dependencies {
+  someConfig project(':foo')
+  someConfig project(':bar')
+  someConfig project(':unwanted')
+  someOtherConfig project(':unwanted')
+}
 
-        contains('fooPathVariable')
-        contains('someFriendlyContainer', 'andYetAnotherContainer')
+eclipse.classpath {
+    plusConfigurations += configurations.someConfig
+    minusConfigurations += configurations.someOtherConfig
+}
+"""
 
-        contains('build-eclipse')
+        //then
+        assert classpath.projects == ['/foo', '/bar']
     }
 
-    protected def contains(String ... contents) {
-        contents.each { assert content.contains(it)}
+    @Test
+    void handlesPlusMinusConfigurationsForExternalDeps() {
+        //given
+        def jar = mavenRepo.module('coolGroup', 'coolArtifact', '1.0').dependsOn('coolGroup', 'unwantedArtifact', '1.0').publish().artifactFile
+        mavenRepo.module('coolGroup', 'unwantedArtifact', '1.0').publish()
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+configurations {
+  someConfig
+  someOtherConfig
+}
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+  someConfig 'coolGroup:coolArtifact:1.0'
+  someOtherConfig 'coolGroup:unwantedArtifact:1.0'
+}
+
+eclipse.classpath {
+    plusConfigurations += configurations.someConfig
+    minusConfigurations += configurations.someOtherConfig
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 1
+        libraries[0].assertHasJar(jar)
+    }
+
+    @Test
+    void canToggleJavadocAndSourcesOn() {
+        //given
+        def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
+        module.artifact(classifier: 'sources')
+        module.artifact(classifier: 'javadoc')
+        module.publish()
+        def jar = module.artifactFile
+        def srcJar = module.artifactFile(classifier: 'sources')
+        def javadocJar = module.artifactFile(classifier: 'javadoc')
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+}
+
+eclipse.classpath {
+    downloadSources = true
+    downloadJavadoc = true
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 1
+        libraries[0].assertHasJar(jar)
+        libraries[0].assertHasSource(srcJar)
+        libraries[0].assertHasJavadoc(javadocJar)
+    }
+
+    @Test
+    void canToggleJavadocAndSourcesOff() {
+        //given
+        def module = mavenRepo.module('coolGroup', 'niceArtifact', '1.0')
+        module.artifact(classifier: 'sources')
+        module.artifact(classifier: 'javadoc')
+        module.publish()
+        def jar = module.artifactFile
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+}
+
+eclipse.classpath {
+    downloadSources = false
+    downloadJavadoc = false
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 1
+        libraries[0].assertHasJar(jar)
+        libraries[0].assertHasNoSource()
+        libraries[0].assertHasNoJavadoc()
+    }
+
+    @Test
+    void removeDependenciesFromExistingClasspathFileWhenMerging() {
+        //given
+        getClasspathFile() << '''<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="output" path="bin"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+	<classpathentry kind="lib" path="/some/path/someDependency.jar" exported="true"/>
+	<classpathentry kind="var" path="SOME_VAR/someVarDependency.jar" exported="true"/>
+	<classpathentry kind="src" path="/someProject" exported="true"/>
+</classpath>
+'''
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+dependencies {
+  compile files('newDependency.jar')
+}
+"""
+
+        //then
+        assert classpath.entries.size() == 3
+        def libraries = classpath.libs
+        assert libraries.size() == 1
+        libraries[0].assertHasJar(file('newDependency.jar'))
+    }
+
+    @Issue('GRADLE-1953')
+    @Test
+    void canConstructAndReconstructClasspathFromJavaSourceSets() {
+        given:
+        def buildFile = file("build.gradle") << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+"""
+        createJavaSourceDirs(buildFile)
+
+        when:
+        executer.usingBuildScript(buildFile).withTasks('eclipseClasspath').run()
+
+        then:
+        assert classpath.entries.size() == 4
+        assert classpath.sources.size() == 2
+
+        when:
+        executer.usingBuildScript(buildFile).withTasks('eclipseClasspath').run()
+
+        then:
+        assert classpath.entries.size() == 4
+        assert classpath.sources.size() == 2
+    }
+
+    @Test
+    void canAccessXmlModelBeforeAndAfterGeneration() {
+        //given
+        def classpath = getClasspathFile([:])
+        classpath << '''<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="output" path="bin"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+	<classpathentry kind="lib" path="/some/path/someDependency.jar" exported="true"/>
+	<classpathentry kind="var" path="SOME_VAR/someVarDependency.jar" exported="true"/>
+</classpath>
+'''
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+def hooks = []
+
+dependencies {
+  compile files('newDependency.jar')
+}
+
+eclipse {
+  classpath {
+    file {
+      beforeMerged {
+        hooks << 'beforeMerged'
+        assert it.entries.size() == 4
+        assert it.entries.any { it.path.contains('someDependency.jar') }
+        assert it.entries.any { it.path.contains('someVarDependency.jar') }
+        assert !it.entries.any { it.path.contains('newDependency.jar') }
+      }
+      whenMerged {
+        hooks << 'whenMerged'
+        assert it.entries.size() == 3
+        assert it.entries.any { it.path.contains('newDependency.jar') }
+        assert !it.entries.any { it.path.contains('someDependency.jar') }
+        assert !it.entries.any { it.path.contains('someVarDependency.jar') }
+      }
+    }
+  }
+}
+
+eclipseClasspath.doLast() {
+  assert hooks == ['beforeMerged', 'whenMerged']
+}
+"""
+
+        //then no exception is thrown
+    }
+
+    @Issue("GRADLE-1502")
+    @Test
+    void createsLinkedResourcesForSourceDirectoriesWhichAreNotUnderTheProjectDirectory() {
+        file('someGroovySrc').mkdirs()
+
+        def settingsFile = file('settings.gradle')
+        settingsFile << "include 'api'"
+
+        def buildFile = file('build.gradle')
+        buildFile << """
+allprojects {
+  apply plugin: 'java'
+  apply plugin: 'eclipse'
+  apply plugin: 'groovy'
+}
+
+project(':api') {
+    sourceSets {
+        main {
+            groovy.srcDirs = ['../someGroovySrc']
+        }
+    }
+}
+"""
+        //when
+        executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withTasks('eclipse').run()
+
+        //then
+        def classpath = getClasspathFile(project: 'api').text
+
+        assert !classpath.contains('path="../someGroovySrc"'): "external src folders are not supported by eclipse"
+        assert classpath.contains('path="someGroovySrc"'): "external folder should be mapped to linked resource"
+
+        def project = parseProjectFile(project: 'api')
+
+        assert project.linkedResources.link.location.text().contains('someGroovySrc'): 'should contain linked resource for folder that is not beneath the project dir'
+    }
+
+    @Issue("GRADLE-1402")
+    @Test
+    void shouldNotPutSourceSetsOutputDirOnClasspath() {
+        testFile('build/generated/main/prod.resource').createFile()
+        testFile('build/generated/test/test.resource').createFile()
+
+        //when
+        runEclipseTask '''
+apply plugin: "java"
+apply plugin: "eclipse"
+
+sourceSets.main.output.dir "$buildDir/generated/main"
+sourceSets.test.output.dir "$buildDir/generated/test"
+'''
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 2
+        libraries[0].assertHasJar(file('build/generated/main'))
+        libraries[1].assertHasJar(file('build/generated/test'))
+    }
+
+    @Test
+    void theBuiltByTaskBeExecuted() {
+        //when
+        def result = runEclipseTask('''
+apply plugin: "java"
+apply plugin: "eclipse"
+
+sourceSets.main.output.dir "$buildDir/generated/main", builtBy: 'generateForMain'
+sourceSets.test.output.dir "$buildDir/generated/test", builtBy: 'generateForTest'
+
+task generateForMain << {}
+task generateForTest << {}
+''')
+        //then
+        result.assertTasksExecuted(':generateForMain', ':generateForTest', ':eclipseClasspath', ':eclipseJdt', ':eclipseProject', ':eclipse')
+    }
+
+    @Test
+    @Issue("GRADLE-1613")
+    void shouldAllowSettingNonExportedConfigurations() {
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+configurations {
+  provided
+}
+
+dependencies {
+  compile  files('compileDependency.jar')
+  provided files('providedDependency.jar')
+}
+
+eclipse {
+  classpath {
+    plusConfigurations += configurations.provided
+    noExportConfigurations += configurations.provided
+  }
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 2
+        libraries[0].assertHasJar(file('compileDependency.jar'))
+        libraries[0].assertExported()
+        libraries[1].assertHasJar(file('providedDependency.jar'))
+        libraries[1].assertNotExported()
+    }
+
+    @Test
+    void doesNotBreakWhenSomeDependenciesCannotBeResolved() {
+        //given
+        def repoJar = mavenRepo.module('coolGroup', 'niceArtifact', '1.0').publish().artifactFile
+        def localJar = file('someDependency.jar').createFile()
+
+        file("settings.gradle") << "include 'someApiProject'\n"
+
+        //when
+        runEclipseTask """
+allprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+}
+
+repositories {
+    maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+    compile project(':someApiProject')
+    compile 'i.dont:Exist:1.0'
+    compile files('someDependency.jar')
+}
+"""
+
+        //then
+        def libraries = classpath.libs
+        assert libraries.size() == 3
+        libraries[0].assertHasJar(repoJar)
+        libraries[1].assertHasJar(file('unresolved dependency - i.dont#Exist;1.0'))
+        libraries[2].assertHasJar(localJar)
     }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..a343841
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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.ide.eclipse
+
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class EclipseClasspathRemoteResolutionIntegrationTest extends AbstractEclipseIntegrationTest {
+
+    @Rule public final HttpServer server = new HttpServer()
+    @Rule public final TestResources testResources = new TestResources()
+
+    String content
+
+    @Before
+    void "setup"() {
+        distribution.requireOwnUserHomeDir()
+    }
+
+    @Test
+    void "does not break when source or javadoc artifacts are missing or broken"() {
+//        given:
+        def projectA = mavenRepo.module('group', 'projectA', '1.0').publish()
+        def projectB = mavenRepo.module('group', 'projectB', '1.0').publish()
+        server.start()
+
+//        when:
+        server.resetExpectations()
+        server.expectGet('/repo/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo/group/projectA/1.0/projectA-1.0-sources.jar')
+        server.expectGetMissing('/repo/group/projectA/1.0/projectA-1.0-javadoc.jar')
+        server.expectGet('/repo/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/repo/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+        server.addBroken('/repo/group/projectB/1.0/projectB-1.0-sources.jar')
+        server.addBroken('/repo/group/projectB/1.0/projectB-1.0-javadoc.jar')
+
+
+//        and:
+        def result = runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+eclipse {
+    classpath.downloadJavadoc = true
+}
+"""
+//        then:
+        result.error == ''
+    }
+
+    def cleanup() {
+        server.resetExpectations()
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy
new file mode 100644
index 0000000..5e44ba1
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseEarIntegrationTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.ide.eclipse
+
+import org.junit.Test
+
+/**
+ * @author Szczepan Faber
+ */
+class EclipseEarIntegrationTest extends AbstractEclipseIntegrationTest {
+
+    @Test
+    void configuresEarWithoutJavaPlugin() {
+        //when
+        runEclipseTask """
+apply plugin: 'ear'
+apply plugin: 'eclipse'
+"""
+
+        //then
+        assert file('.project').exists()
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
index 94216d2..6083bee 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest.groovy
@@ -16,8 +16,10 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TextUtil
 import org.junit.Rule
 import org.junit.Test
+import spock.lang.Issue
 
 class EclipseIntegrationTest extends AbstractEclipseIntegrationTest {
     private static String nonAscii = "\\u7777\\u8888\\u9999"
@@ -82,15 +84,15 @@ sourceSets {
     @Test
     void canHandleCircularModuleDependencies() {
         def repoDir = file("repo")
-        def artifact1 = publishArtifact(repoDir, "myGroup", "myArtifact1", "myArtifact2")
-        def artifact2 = publishArtifact(repoDir, "myGroup", "myArtifact2", "myArtifact1")
+        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
+        def artifact2 = maven(repoDir).module("myGroup", "myArtifact2").dependsOn("myArtifact1").publish().artifactFile
 
         runEclipseTask """
 apply plugin: "java"
 apply plugin: "eclipse"
 
 repositories {
-    mavenRepo urls: "${repoDir.toURI()}"
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -105,22 +107,22 @@ dependencies {
     void eclipseFilesAreWrittenWithUtf8Encoding() {
         runEclipseTask """
 apply plugin: "war"
-apply plugin: "eclipse"
-
-eclipseProject {
-  projectName = "$nonAscii"
-}
-
-eclipseClasspath {
-  containers("$nonAscii")
-}
+apply plugin: "eclipse-wtp"
 
-eclipseWtpComponent {
-  deployName = "$nonAscii"
-}
+eclipse {
+    project.name = "$nonAscii"
+    classpath {
+        containers "$nonAscii"
+    }
 
-eclipseWtpFacet {
-  facet([name: "$nonAscii"])
+    wtp {
+        component {
+            deployName = "$nonAscii"
+        }
+        facet {
+            facet name: "$nonAscii"
+        }
+    }
 }
         """
 
@@ -137,33 +139,49 @@ eclipseWtpFacet {
         runEclipseTask('''
 apply plugin: 'java'
 apply plugin: 'war'
-apply plugin: 'eclipse'
+apply plugin: 'eclipse-wtp'
 
 def beforeConfiguredObjects = 0
 def whenConfiguredObjects = 0
 
-eclipseProject {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-eclipseClasspath {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-eclipseWtpFacet {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-eclipseWtpComponent {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-eclipseJdt {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
+eclipse {
+    project {
+        file {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+
+    classpath {
+        file {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+
+    wtp.component {
+        file {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+
+    wtp.facet {
+        file {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+
+    jdt {
+        file {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
 }
 
-eclipse << {
+tasks.eclipse << {
     assert beforeConfiguredObjects == 5 : "beforeConfigured() hooks shoold be fired for domain model objects"
     assert whenConfiguredObjects == 5 : "whenConfigured() hooks shoold be fired for domain model objects"
 }
@@ -174,15 +192,15 @@ eclipse << {
     @Test
     void respectsPerConfigurationExcludes() {
         def repoDir = file("repo")
-        def artifact1 = publishArtifact(repoDir, "myGroup", "myArtifact1", "myArtifact2")
-        def artifact2 = publishArtifact(repoDir, "myGroup", "myArtifact2")
+        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
+        maven(repoDir).module("myGroup", "myArtifact2").publish()
 
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
 repositories {
-    mavenRepo urls: "${repoDir.toURI()}"
+    maven { url "${repoDir.toURI()}" }
 }
 
 configurations {
@@ -200,15 +218,15 @@ dependencies {
     @Test
     void respectsPerDependencyExcludes() {
         def repoDir = file("repo")
-        def artifact1 = publishArtifact(repoDir, "myGroup", "myArtifact1", "myArtifact2")
-        def artifact2 = publishArtifact(repoDir, "myGroup", "myArtifact2")
+        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
+        maven(repoDir).module("myGroup", "myArtifact2").publish()
 
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
 repositories {
-    mavenRepo urls: "${repoDir.toURI()}"
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -229,18 +247,17 @@ dependencies {
     }
 
     @Test
-    void addsLinkToTheOutputFile() {
+    void addsLinkToTheProjectFile() {
         runEclipseTask '''
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
-eclipseProject {
-    link name: 'one', type: '2', location: '/xyz'
-    link name: 'two', type: '3', locationUri: 'file://xyz'
+eclipse.project {
+    linkedResource name: 'one', type: '2', location: '/xyz'
+    linkedResource name: 'two', type: '3', locationUri: 'file://xyz'
 }
 '''
 
-        println getProjectFile().text
         def xml = parseProjectFile()
         assert xml.linkedResources.link[0].name.text() == 'one'
         assert xml.linkedResources.link[0].type.text() == '2'
@@ -257,7 +274,7 @@ eclipseProject {
 apply plugin: 'java'
 apply plugin: 'eclipse'
 
-eclipseJdt {
+eclipse.jdt {
     sourceCompatibility = '1.4'
     targetCompatibility = 1.3
 }
@@ -267,4 +284,85 @@ eclipseJdt {
         assert jdt.contains('source=1.4')
         assert jdt.contains('targetPlatform=1.3')
     }
+
+    @Test
+    void dslAllowsShortFormsForProject() {
+        runEclipseTask '''
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+eclipse.project.name = 'x'
+assert eclipse.project.name == 'x'
+
+eclipse {
+    project.name += 'x'
+    assert project.name == 'xx'
+}
+
+eclipse.project {
+    name += 'x'
+    assert name == 'xxx'
+}
+
+'''
+    }
+
+    @Test
+    void dslAllowsShortForms() {
+        runEclipseTask '''
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+eclipse.classpath.downloadSources = false
+assert eclipse.classpath.downloadSources == false
+
+eclipse.classpath.file.withXml {}
+eclipse.classpath {
+    file.withXml {}
+}
+eclipse {
+    classpath.file.withXml {}
+}
+'''
+    }
+
+    @Test
+    @Issue("GRADLE-1157")
+    void canHandleDependencyWithoutSourceJarInFlatDirRepo() {
+        def repoDir = testDir.createDir("repo")
+        repoDir.createFile("lib-1.0.jar")
+
+        runEclipseTask """
+apply plugin: "java"
+apply plugin: "eclipse"
+
+repositories {
+	flatDir { dirs "${TextUtil.escapeString(repoDir)}" }
+}
+
+dependencies {
+	compile "some:lib:1.0"
+}
+        """
+    }
+
+    @Test
+    @Issue("GRADLE-1706") // doesn't prove that the issue is fixed because the test also passes with 1.0-milestone-4
+    void canHandleDependencyWithoutSourceJarInMavenRepo() {
+        def repoDir = testDir.createDir("repo")
+        maven(repoDir).module("some", "lib").publish()
+
+        runEclipseTask """
+apply plugin: "java"
+apply plugin: "eclipse"
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+dependencies {
+	compile "some:lib:1.0"
+}
+        """
+    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
index 33951ed..bb31da6 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
@@ -67,8 +67,8 @@ project(':api') {
 }
 
 project(':shared:model') {
-    eclipseProject {
-        projectName = 'very-cool-model'
+    eclipse {
+        project.name = 'very-cool-model'
     }
 }
 
@@ -76,15 +76,14 @@ project(':services:utilities') {
     dependencies {
         compile project(':util'), project(':contrib:services:util'), project(':shared:api'), project(':shared:model')
     }
-    eclipseProject {
-        projectName = 'util'
+    eclipse {
+        project.name = 'util'
     }
 }
 """
 
         //when
         executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withTasks("eclipse").run()
-//        println(getTestDir())
 
         //then
         assertApiProjectContainsCorrectDependencies()
@@ -123,8 +122,10 @@ allprojects {
 }
 
 subprojects {
-    eclipseProject {
-        projectName = rootProject.name + project.path.replace(':', '-')
+    eclipse {
+        project {
+            name = rootProject.name + path.replace(':', '-')
+        }
     }
 }
 
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginGoodBehaviourTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..576d96e
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.ide.eclipse
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class EclipsePluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "eclipse"
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
index b4727d5..341d2e7 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
@@ -20,7 +20,7 @@ import org.junit.Rule
 import org.junit.Test
 
 /**
- * Author: Szczepan Faber
+ * @author Szczepan Faber
  */
 class EclipseProjectIntegrationTest extends AbstractEclipseIntegrationTest {
 
@@ -52,6 +52,10 @@ eclipse {
 
     linkedResource name: 'linkToFolderFoo', type: 'aFolderFoo', location: '/test/folders/foo'
     linkedResource name: 'linkToUriFoo', type: 'aFooUri', locationUri: 'http://test/uri/foo'
+
+    file {
+      withXml { it.asNode().appendNode('motto', 'Stay happy!') }
+    }
   }
 
   jdt {
@@ -63,7 +67,6 @@ eclipse {
 
         //then
         content = getFile([:], '.project').text
-        println content
 
         def dotProject = parseProjectFile()
         assert dotProject.name.text() == 'someBetterName'
@@ -76,11 +79,111 @@ eclipse {
         contains('linkToFolderFoo', 'aFolderFoo', '/test/folders/foo')
         contains('linkToUriFoo', 'aFooUri', 'http://test/uri/foo')
 
+        contains('<motto>Stay happy!</motto>')
+
         def jdt = parseJdtFile()
         assert jdt.contains('targetPlatform=1.3')
         assert jdt.contains('source=1.4')
     }
 
+    @Test
+    void enablesBeforeAndWhenHooksForProject() {
+        //given
+        def project = file('.project')
+        project << '''<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>root</name>
+	<comment/>
+	<projects/>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>some.nature.one</nature>
+	</natures>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments/>
+		</buildCommand>
+	</buildSpec>
+	<linkedResources/>
+</projectDescription>'''
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+eclipse {
+  project {
+    file {
+      beforeMerged {
+        assert it.natures.contains('some.nature.one')
+        it.natures << 'some.nature.two'
+      }
+      whenMerged {
+        assert it.natures.contains('some.nature.one')
+        assert it.natures.contains('some.nature.two')
+
+        it.natures << 'some.nature.three'
+      }
+    }
+  }
+}
+        """
+
+        content = getFile([:], '.project').text
+        //then
+
+        contains('some.nature.one', 'some.nature.two', 'some.nature.three')
+    }
+
+    @Test
+    void enablesBeforeAndWhenAndWithPropertiesHooksForJdt() {
+        //given
+        def jdtFile = file('.settings/org.eclipse.jdt.core.prefs')
+        jdtFile << '''
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.3
+'''
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+ext.hooks = []
+
+eclipse {
+
+  jdt {
+    file {
+      beforeMerged {
+        hooks << 'beforeMerged'
+      }
+      whenMerged {
+        hooks << 'whenMerged'
+        assert '1.1' != it.targetCompatibility.toString()
+        it.targetCompatibility = JavaVersion.toVersion('1.1')
+      }
+      withProperties {
+        hooks << 'withProperties'
+        it.dummy = 'testValue'
+      }
+    }
+  }
+}
+
+eclipseJdt.doLast() {
+  assert hooks == ['beforeMerged', 'whenMerged', 'withProperties']
+}
+        """
+
+        def jdt = parseJdtFile()
+
+        //then
+        assert jdt.contains('targetPlatform=1.1')
+        assert jdt.contains('dummy=testValue')
+    }
+
     protected def contains(String ... contents) {
         contents.each { assert content.contains(it)}
     }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy
index bafc970..b5803c3 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpIntegrationTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.junit.Test
+import spock.lang.Issue
 
 // TODO: run prepareWebProject() only once per class for performance reasons (not as simply as it seems)
 class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
@@ -73,17 +74,40 @@ class EclipseWtpIntegrationTest extends AbstractEclipseIntegrationTest {
     void allProjectDependenciesOfWebProjectAreAddedAsRuntimeDependencies() {
         prepareWebProject()
 
-        def projectModules = parseComponentFile(project: "web")
+        def projectModules = parseComponentFile(project: "web", print: true)
 
 		assert getDeployName(projectModules) == "web"
 		assert getHandleFilenames(projectModules) == ["java1", "java2", "groovy", "myartifact-1.0.jar", "myartifactdep-1.0.jar"] as Set
 		assert getDependencyTypes(projectModules) == ["uses"] * 5 as Set
     }
 
+    @Test
+    @Issue("GRADLE-1415")
+    void canUseSelfResolvingFiles() {
+        def buildFile = """
+apply plugin: "war"
+apply plugin: "eclipse"
+
+dependencies {
+    compile fileTree(dir: "libs", includes: ["*.jar"])
+}
+        """
+
+        def libsDir = file("libs")
+        libsDir.mkdir()
+        libsDir.createFile("foo.jar")
+
+        // when
+        runEclipseTask(buildFile)
+
+        // then
+        libEntriesInClasspathFileHaveFilenames("foo.jar")
+    }
+
     private prepareWebProject() {
         def repoDir = file("repo")
-        publishArtifact(repoDir, "mygroup", "myartifact", "myartifactdep")
-        publishArtifact(repoDir, "mygroup", "myartifactdep")
+        maven(repoDir).module("mygroup", "myartifact").dependsOn("myartifactdep").publish()
+        maven(repoDir).module("mygroup", "myartifactdep").publish()
 
         def settingsFile = file("settings.gradle")
         settingsFile << """
@@ -98,11 +122,11 @@ include("groovy")
         webBuildFile.parentFile.file("src/main/webapp").createDir()
 
         webBuildFile << """
-apply plugin: "eclipse"
+apply plugin: "eclipse-wtp"
 apply plugin: "war"
 
 repositories {
-    mavenRepo(name: "repo", urls: "${repoDir.toURI()}")
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -116,11 +140,11 @@ dependencies {
         createJavaSourceDirs(java1BuildFile)
 
         java1BuildFile << """
-apply plugin: "eclipse"
+apply plugin: "eclipse-wtp"
 apply plugin: "java"
 
 repositories {
-    mavenRepo(name: "repo", urls: "${repoDir.toURI()}")
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -133,11 +157,11 @@ dependencies {
         createJavaSourceDirs(java2BuildFile)
 
         java2BuildFile << """
-apply plugin: "eclipse"
+apply plugin: "eclipse-wtp"
 apply plugin: "java"
 
 repositories {
-    mavenRepo(name: "repo", urls: "${repoDir.toURI()}")
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -150,7 +174,7 @@ dependencies {
         groovyBuildFile.parentFile.file("src/main/groovy").createDir()
 
         groovyBuildFile << """
-apply plugin: "eclipse"
+apply plugin: "eclipse-wtp"
 apply plugin: "groovy"
         """
 
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
index d96512d..737693b 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
@@ -17,12 +17,14 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.plugins.ide.eclipse.model.AbstractClasspathEntry
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import spock.lang.Issue
 
 /**
- * Author: Szczepan Faber, created at: 4/19/11
+ * @author Szczepan Faber, created at: 4/19/11
  */
 class EclipseWtpModelIntegrationTest extends AbstractEclipseIntegrationTest {
 
@@ -32,21 +34,21 @@ class EclipseWtpModelIntegrationTest extends AbstractEclipseIntegrationTest {
     String component
 
     @Test
-    void allowsConfiguringEclipseProject() {
+    void allowsConfiguringEclipseWtp() {
         //given
         file('someExtraSourceDir').mkdirs()
+        file('src/foo/bar').mkdirs()
 
         def repoDir = file("repo")
-        publishArtifact(repoDir, "gradle", "foo")
-        publishArtifact(repoDir, "gradle", "bar")
-        publishArtifact(repoDir, "gradle", "baz")
-
+        maven(repoDir).module("gradle", "foo").publish()
+        maven(repoDir).module("gradle", "bar").publish()
+        maven(repoDir).module("gradle", "baz").publish()
 
         //when
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'war'
-apply plugin: 'eclipse'
+apply plugin: 'eclipse-wtp'
 
 configurations {
   configOne
@@ -54,7 +56,7 @@ configurations {
 }
 
 repositories {
-  mavenRepo urls: "${repoDir.toURI()}"
+  maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -90,7 +92,6 @@ eclipse {
 
         component = getFile([:], '.settings/org.eclipse.wst.common.component').text
         def facet = getFile([:], '.settings/org.eclipse.wst.common.project.facet.core.xml').text
-        println facet //TODO SF after completing the refactoring, get rid of the printlns
 
         //then component:
         contains('someExtraSourceDir')
@@ -100,7 +101,7 @@ eclipse {
 
         contains('someBetterDeployName')
 
-        //contains('userHomeVariable') //TODO SF don't know how to test it at the moment
+        //contains('userHomeVariable') //TODO don't know how to test it at the moment
 
         contains('./src/foo/bar', './deploy/foo/bar')
         contains('wbPropertyOne', 'New York!')
@@ -111,6 +112,111 @@ eclipse {
         assert facet.contains('1.333')
     }
 
+    @Test
+    void allowsConfiguringHooksForComponent() {
+        //given
+        def componentFile = file('.settings/org.eclipse.wst.common.component')
+        componentFile << '''<?xml version="1.0" encoding="UTF-8"?>
+<project-modules id="moduleCoreId" project-version="2.0">
+	<wb-module deploy-name="coolDeployName">
+		<property name="context-root" value="root"/>
+		<wb-resource deploy-path="/" source-path="src/main/webapp"/>
+	</wb-module>
+</project-modules>
+'''
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'eclipse-wtp'
+
+def hooks = []
+
+eclipse {
+  wtp {
+    component {
+      file {
+        beforeMerged {
+          hooks << 'beforeMerged'
+          assert it.deployName == 'coolDeployName'
+        }
+        whenMerged {
+          hooks << 'whenMerged'
+          it.deployName = 'betterDeployName'
+        }
+        withXml { it.asNode().appendNode('be', 'cool') }
+      }
+    }
+  }
+}
+
+eclipseWtpComponent.doLast() {
+  assert hooks == ['beforeMerged', 'whenMerged']
+}
+
+        """
+
+        //when
+        component = getFile([:], '.settings/org.eclipse.wst.common.component').text
+
+        //then
+        assert component.contains('betterDeployName')
+        assert !component.contains('coolDeployName')
+        assert component.contains('<be>cool</be>')
+    }
+
+    @Test
+    void allowsConfiguringHooksForFacet() {
+        //given
+        def componentFile = file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+        componentFile << '''<?xml version="1.0" encoding="UTF-8"?>
+<faceted-project>
+	<fixed facet="jst.java"/>
+	<fixed facet="jst.web"/>
+	<installed facet="jst.web" version="2.4"/>
+	<installed facet="jst.java" version="5.0"/>
+	<installed facet="facet.one" version="1.0"/>
+</faceted-project>
+'''
+
+        //when
+        runEclipseTask """
+import org.gradle.plugins.ide.eclipse.model.Facet
+
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'eclipse-wtp'
+
+eclipse {
+  wtp {
+    facet {
+      file {
+        beforeMerged {
+          assert it.facets.contains(new Facet('facet.one', '1.0'))
+          it.facets.add(new Facet('facet.two', '2.0'))
+        }
+        whenMerged {
+          assert it.facets.contains(new Facet('facet.one', '1.0'))
+          assert it.facets.contains(new Facet('facet.two', '2.0'))
+          it.facets.add(new Facet('facet.three', '3.0'))
+        }
+        withXml { it.asNode().appendNode('be', 'cool') }
+      }
+    }
+  }
+}
+        """
+
+        def facet = getFile([:], '.settings/org.eclipse.wst.common.project.facet.core.xml').text
+
+        assert facet.contains('facet.one')
+        assert facet.contains('facet.two')
+        assert facet.contains('facet.three')
+
+        assert facet.contains('<be>cool</be>')
+    }
+
     @Ignore("GRADLE-1487")
     @Test
     void allowsFileDependencies() {
@@ -118,7 +224,7 @@ eclipse {
         runEclipseTask """
 apply plugin: 'java'
 apply plugin: 'war'
-apply plugin: 'eclipse'
+apply plugin: 'eclipse-wtp'
 
 configurations {
   configOne
@@ -132,20 +238,55 @@ dependencies {
 
 eclipse {
   wtp {
-    plusConfigurations += configurations.configOne
-    minusConfigurations += configurations.configTwo
+    component {
+        plusConfigurations += configurations.configOne
+        minusConfigurations += configurations.configTwo
+    }
   }
 }
         """
 
         def component = getFile([:], '.settings/org.eclipse.wst.common.component').text
-        println component
+        assert component.contains('foo.txt')
+        assert component.contains('bar.txt')
+        assert !component.contains('baz.txt')
+    }
+
+    @Test
+    void createsTasksOnDependantUponProjectsEvenIfTheyDontHaveWarPlugin() {
+        //given
+        def settings = file('settings.gradle')
+        settings << "include 'impl', 'contrib'"
+
+        def build = file('build.gradle')
+        build << """
+project(':impl') {
+  apply plugin: 'java'
+  apply plugin: 'war'
+  apply plugin: 'eclipse-wtp'
+
+  dependencies { compile project(':contrib') }
+}
+
+project(':contrib') {
+  apply plugin: 'java'
+  apply plugin: 'eclipse-wtp'
+}
+"""
+        //when
+        executer.usingSettingsFile(settings).usingBuildScript(build).withTasks('eclipse').run()
 
         //then
+        assert getComponentFile(project: 'impl').exists()
+        assert getFacetFile(project: 'impl').exists()
+
+        assert getComponentFile(project: 'contrib').exists()
+        assert getFacetFile(project: 'contrib').exists()
     }
 
     @Test
-    void createsTasksOnDependantUponProjectsEvenIfTheyDontHaveWarPlugin() {
+    @Issue("GRADLE-1881")
+    void "uses eclipse project name for wtp module dependencies"() {
         //given
         def settings = file('settings.gradle')
         settings << "include 'impl', 'contrib'"
@@ -155,25 +296,312 @@ eclipse {
 project(':impl') {
   apply plugin: 'java'
   apply plugin: 'war'
-  apply plugin: 'eclipse'
+  apply plugin: 'eclipse-wtp'
 
   dependencies { compile project(':contrib') }
+
+  eclipse.project.name = 'cool-impl'
 }
 
 project(':contrib') {
   apply plugin: 'java'
-  apply plugin: 'eclipse'
+  apply plugin: 'eclipse-wtp'
+  //should not have war nor ear applied
+
+  eclipse.project.name = 'cool-contrib'
 }
 """
         //when
         executer.usingSettingsFile(settings).usingBuildScript(build).withTasks('eclipse').run()
 
         //then
-        getComponentFile(project: 'impl')
-        getFacetFile(project: 'impl')
+        //the deploy name is correct:
+        assert getComponentFile(project: 'impl').text.contains('deploy-name="cool-impl"')
+        //the dependent-module name is correct:
+        assert getComponentFile(project: 'impl').text.contains('handle="module:/resource/cool-contrib/cool-contrib"')
+        //the submodule name is correct:
+        assert getComponentFile(project: 'contrib').text.contains('deploy-name="cool-contrib"')
+    }
+
+    @Test
+    @Issue("GRADLE-1881")
+    void "does not explode if dependent project does not have eclipse plugin"() {
+        //given
+        def settings = file('settings.gradle')
+        settings << "include 'impl', 'contrib'"
+
+        def build = file('build.gradle')
+        build << """
+project(':impl') {
+  apply plugin: 'java'
+  apply plugin: 'war'
+  apply plugin: 'eclipse-wtp'
+
+  dependencies { compile project(':contrib') }
+
+  eclipse.project.name = 'cool-impl'
+}
+
+project(':contrib') {
+  apply plugin: 'java'
+}
+"""
+        //when
+        executer.usingSettingsFile(settings).usingBuildScript(build).withTasks('eclipse').run()
+
+        //then no exception thrown
+    }
+
+    @Test
+    @Issue("GRADLE-2030")
+    void "component for war plugin does not contain non-existing source and resource dirs"() {
+        //given
+        file('xxxSource').createDir()
+        file('xxxResource').createDir()
+
+        //when
+        runEclipseTask """
+          apply plugin: 'java'
+          apply plugin: 'war'
+          apply plugin: 'eclipse-wtp'
+          
+          sourceSets.main.java.srcDirs 'yyySource', 'xxxSource'
+
+          eclipse.wtp.component {
+            resource sourcePath: 'xxxResource', deployPath: 'deploy-xxx'
+            resource sourcePath: 'yyyResource', deployPath: 'deploy-yyy'
+          }
+"""
+        //then
+        def component = getComponentFile().text
+
+        assert component.contains('xxxSource')
+        assert !component.contains('yyySource')
+
+        assert component.contains('xxxResource')
+        assert !component.contains('yyyResource')
+    }
+
+    @Test
+    @Issue("GRADLE-2030")
+    void "component for ear plugin does not contain non-existing source and resource dirs"() {
+        //given
+        file('xxxSource').createDir()
+        file('xxxResource').createDir()
+
+        //when
+        runEclipseTask """
+          apply plugin: 'java'
+          apply plugin: 'ear'
+          apply plugin: 'eclipse-wtp'
+
+          sourceSets.main.java.srcDirs 'yyySource', 'xxxSource'
+
+          appDirName = 'nonExistingAppDir'
+
+          eclipse.wtp.component {
+            resource sourcePath: 'xxxResource', deployPath: 'deploy-xxx'
+            resource sourcePath: 'yyyResource', deployPath: 'deploy-yyy'
+          }
+"""
+        //then
+        def component = getComponentFile().text
+
+        assert component.contains('xxxSource')
+        assert !component.contains('yyySource')
+
+        assert component.contains('xxxResource')
+        assert !component.contains('yyyResource')
+        
+        assert !component.contains('nonExistingAppDir')
+    }
+
+    @Test
+    void "component for ear plugin contains the app dir"() {
+        //given
+        file('coolAppDir').createDir()
+
+        //when
+        runEclipseTask """
+          apply plugin: 'java'
+          apply plugin: 'ear'
+          apply plugin: 'eclipse-wtp'
+
+          appDirName = 'coolAppDir'
+"""
+        //then
+        def component = getComponentFile().text
+
+        assert component.contains('coolAppDir')
+    }
+
+    @Test
+    @Issue("GRADLE-1974")
+    void "may use web libraries container"() {
+        //given
+        //adding a little bit more stress with a subproject and some web resources:
+        file("src/main/webapp/index.jsp") << "<html>Hey!</html>"
+        file("settings.gradle") << "include 'someCoolLib'"
+
+        file("build.gradle") << """
+            apply plugin: 'war'
+            apply plugin: 'eclipse-wtp'
+
+            project(':someCoolLib') {
+              apply plugin: 'java'
+              apply plugin: 'eclipse-wtp'
+            }
+
+            repositories { mavenCentral() }
+
+            dependencies {
+              compile 'commons-io:commons-io:1.4'
+              compile project(':someCoolLib')
+            }
+        """
+
+        //when
+        executer.withTasks("eclipse").run()
+
+        //then the container is configured
+        assert getClasspathFile().text.contains(EclipseWtpPlugin.WEB_LIBS_CONTAINER)
+    }
+
+    @Test
+    @Issue("GRADLE-1974")
+    void "the web container is not present without war+wtp combo"() {
+        //given
+        file("build.gradle") << """
+            apply plugin: 'java' //anything but not war
+            apply plugin: 'eclipse-wtp'
+        """
+
+        //when
+        executer.withTasks("eclipse").run()
+
+        //then container is added only once:
+        assert !getClasspathFile().text.contains(EclipseWtpPlugin.WEB_LIBS_CONTAINER)
+    }
+
+    @Test
+    @Issue("GRADLE-1707")
+    void "the library and variable classpath entries are marked as component non-dependency"() {
+        //given
+        file('libs/myFoo.jar').touch()
+
+        file("build.gradle") << """
+            apply plugin: 'war'
+            apply plugin: 'eclipse-wtp'
+
+            repositories { mavenCentral() }
+
+            dependencies {
+              compile 'commons-io:commons-io:1.4'
+              compile files('libs/myFoo.jar')
+            }
+
+            eclipse.pathVariables MY_LIBS: file('libs')
+        """
+
+        //when
+        executer.withTasks("eclipse").run()
+
+        //then
+        def classpath = getClasspathFile(print: true).text
+        def component = getComponentFile().text
+
+        //the jar dependency is configured in the WTP component file and in the classpath
+        assert classpath.contains('commons-io')
+        assert component.contains('commons-io')
+
+        assert classpath.contains('kind="var" path="MY_LIBS/myFoo.jar"')
+        assert component.contains('myFoo.jar')
+
+        //the jar dependencies are configured as non-dependency in the .classpath
+        classpath.count(AbstractClasspathEntry.COMPONENT_NON_DEPENDENCY_ATTRIBUTE) == 2
+        classpath.count(AbstractClasspathEntry.COMPONENT_DEPENDENCY_ATTRIBUTE) == 0
+    }
+
+    @Test
+    @Issue("GRADLE-1707")
+    void "classpath entries are protected from conflicting component dependency attributes"() {
+        //given
+        file("build.gradle") << """
+            apply plugin: 'war'
+            apply plugin: 'eclipse-wtp'
+
+            repositories { mavenCentral() }
+
+            dependencies {
+              compile 'commons-io:commons-io:1.4'
+            }
+
+            import org.gradle.plugins.ide.eclipse.model.AbstractClasspathEntry
+
+            eclipse.classpath.file.whenMerged { cp ->
+              cp.entries.each {
+                if(it instanceof AbstractClasspathEntry) {
+                  //some people have workarounds in their builds and configure the component dependency,
+                  //just like here:
+                  it.entryAttributes[AbstractClasspathEntry.COMPONENT_DEPENDENCY_ATTRIBUTE] = 'WEB-INF/lib'
+                }
+              }
+            }
+        """
+
+        //when
+        executer.withTasks("eclipse").run()
+
+        //then
+        def classpath = getClasspathFile(print: true).text
+        //component dependency wins:
+        assert classpath.contains(AbstractClasspathEntry.COMPONENT_DEPENDENCY_ATTRIBUTE)
+        //non-dependency (our default) loses:
+        assert !classpath.contains(AbstractClasspathEntry.COMPONENT_NON_DEPENDENCY_ATTRIBUTE)
+    }
+
+    @Test
+    @Issue("GRADLE-1412")
+    void "dependent project's library and variable classpath entries contain necessary dependency attribute"() {
+        //given
+        file('libs/myFoo.jar').touch()
+        file('settings.gradle') << "include 'someLib'"
+
+        file("build.gradle") << """
+            apply plugin: 'war'
+            apply plugin: 'eclipse-wtp'
+
+            dependencies {
+                compile project(':someLib')
+            }
+
+            project(':someLib') {
+                apply plugin: 'java'
+                apply plugin: 'eclipse-wtp'
+                
+                repositories { mavenCentral() }
+
+                dependencies {
+                  compile 'commons-io:commons-io:1.4'
+                  compile files('libs/myFoo.jar')
+                }
+
+                eclipse.pathVariables MY_LIBS: file('libs')
+            }
+        """
+
+        //when
+        executer.withTasks("eclipse").run()
+
+        //then
+        def classpath = getClasspathFile(project: 'someLib', print: true).text
+
+        //contains both entries
+        assert classpath.contains('kind="var" path="MY_LIBS/myFoo.jar"')
+        assert classpath.contains('commons-io')
 
-        getComponentFile(project: 'contrib')
-        getFacetFile(project: 'contrib')
+        //both var and lib entries have the attribute
+        classpath.count('<attribute name="org.eclipse.jst.component.dependency" value="../"/>') == 2
     }
 
     protected def contains(String ... contents) {
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy
new file mode 100644
index 0000000..0ea5b7d
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2011 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.ide.idea
+
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.plugins.ide.AbstractIdeIntegrationTest
+import org.junit.Rule
+import org.junit.Test
+
+class ConfigurationHooksIntegrationTest extends AbstractIdeIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Test
+    void triggersBeforeAndWhenConfigurationHooks() {
+
+        //this test is a bit peculiar as it has assertions inside the gradle script
+        //couldn't find a better way of asserting on before/when configured hooks
+        runIdeaTask '''
+apply plugin: 'java'
+apply plugin: 'idea'
+
+def beforeConfiguredObjects = 0
+def whenConfiguredObjects = 0
+
+idea {
+    project {
+        ipr {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+    workspace {
+        iws {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+    module {
+        iml {
+            beforeMerged {beforeConfiguredObjects++ }
+            whenMerged {whenConfiguredObjects++ }
+        }
+    }
+}
+
+tasks.idea << {
+    assert beforeConfiguredObjects == 3 : "beforeConfigured() hooks shoold be fired for domain model objects"
+    assert whenConfiguredObjects == 3 : "whenConfigured() hooks shoold be fired for domain model objects"
+}
+'''
+    }
+
+    @Test
+    void whenHooksApplyChangesToGeneratedFile() {
+        //when
+        runIdeaTask '''
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea {
+    module {
+        iml {
+            whenMerged { it.jdkName = '1.44' }
+        }
+    }
+
+    project {
+        ipr {
+            whenMerged { it.wildcards += '!?*.ruby' }
+        }
+    }
+}
+'''
+        //then
+        def iml = getFile([:], 'root.iml').text
+        assert iml.contains('1.44')
+
+        def ipr = getFile([:], 'root.ipr').text
+        assert ipr.contains('!?*.ruby')
+    }
+
+
+    private containsDir(path, urls) {
+        urls.any { it.endsWith(path) }
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksTest.groovy
deleted file mode 100644
index 493683f..0000000
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksTest.groovy
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2011 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.ide.idea
-
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.plugins.ide.AbstractIdeIntegrationTest
-import org.junit.Rule
-import org.junit.Test
-
-class ConfigurationHooksTest extends AbstractIdeIntegrationTest {
-    @Rule
-    public final TestResources testResources = new TestResources()
-
-    @Test
-    void triggersBeforeAndWhenConfigurationHooks() {
-        //this test is a bit peculiar as it has assertions inside the gradle script
-        //couldn't find a better way of asserting on before/when configured hooks
-        runIdeaTask '''
-apply plugin: 'java'
-apply plugin: 'idea'
-
-def beforeConfiguredObjects = 0
-def whenConfiguredObjects = 0
-
-ideaModule {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-ideaProject {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-ideaWorkspace {
-    beforeConfigured { beforeConfiguredObjects++ }
-    whenConfigured { whenConfiguredObjects++ }
-}
-
-idea << {
-    assert beforeConfiguredObjects == 3 : "beforeConfigured() hooks shoold be fired for domain model objects"
-    assert whenConfiguredObjects == 3 : "whenConfigured() hooks shoold be fired for domain model objects"
-}
-'''
-    }
-
-    @Test
-    void whenHooksApplyChangesToGeneratedFile() {
-        //when
-        runIdeaTask '''
-apply plugin: 'java'
-apply plugin: 'idea'
-
-ideaModule {
-    whenConfigured { it.javaVersion = '1.44' }
-}
-ideaProject {
-    whenConfigured { it.wildcards += '!?*.ruby' }
-}
-'''
-        //then
-        def iml = getFile([:], 'root.iml').text
-        assert iml.contains('1.44')
-
-        def ipr = getFile([:], 'root.ipr').text
-        assert ipr.contains('!?*.ruby')
-    }
-
-
-    private containsDir(path, urls) {
-        urls.any { it.endsWith(path) }
-    }
-}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
index f5b46c1..357c546 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
@@ -16,11 +16,13 @@
 
 package org.gradle.plugins.ide.idea
 
+import java.util.regex.Pattern
 import junit.framework.AssertionFailedError
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
 import org.custommonkey.xmlunit.XMLAssert
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.internal.os.OperatingSystem
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.gradle.util.TestFile
 import org.junit.Rule
@@ -104,15 +106,15 @@ apply plugin: 'idea'
     @Test
     void canHandleCircularModuleDependencies() {
         def repoDir = file("repo")
-        def artifact1 = publishArtifact(repoDir, "myGroup", "myArtifact1", "myArtifact2")
-        def artifact2 = publishArtifact(repoDir, "myGroup", "myArtifact2", "myArtifact1")
+        def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish().artifactFile
+        def artifact2 = maven(repoDir).module("myGroup", "myArtifact2").dependsOn("myArtifact1").publish().artifactFile
 
         runIdeaTask """
 apply plugin: "java"
 apply plugin: "idea"
 
 repositories {
-    mavenRepo urls: "${repoDir.toURI()}"
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -127,6 +129,36 @@ dependencies {
     }
 
     @Test
+        void libraryReferenceSubstitutesPathVariable() {
+            def repoDir = file("repo")
+            def artifact1 = maven(repoDir).module("myGroup", "myArtifact1").publish().artifactFile
+
+            runIdeaTask """
+    apply plugin: "java"
+    apply plugin: "idea"
+
+    repositories {
+        maven { url "${repoDir.toURI()}" }
+    }
+
+    idea {
+       pathVariables("GRADLE_REPO": file("repo"))
+    }
+
+    dependencies {
+        compile "myGroup:myArtifact1:1.0"
+    }
+            """
+
+            def module = parseImlFile("root")
+            def libs = module.component.orderEntry.library
+            assert libs.size() == 1
+            assert libs.CLASSES.root*. at url*.text().collect { new File(it).name } as Set == [artifact1.name + "!"] as Set
+            assert libs.CLASSES.root*. at url*.text().findAll(){ it.contains("\$GRADLE_REPO\$") }.size() == 1
+            assert libs.CLASSES.root*. at url*.text().collect { it.replace("\$GRADLE_REPO\$", relPath(repoDir))} as Set == ["jar://${relPath(artifact1)}!/"] as Set
+        }
+
+    @Test
     void onlyAddsSourceDirsThatExistOnFileSystem() {
         runIdeaTask """
 apply plugin: "java"
@@ -159,11 +191,11 @@ apply plugin: 'idea'
 
 def hookActivated = 0
 
-ideaModule {
+idea.module.iml {
     withXml { hookActivated++ }
 }
 
-idea << {
+tasks.idea << {
     assert hookActivated == 1 : "withXml() hook shoold be fired"
 }
 '''
@@ -172,15 +204,15 @@ idea << {
     @Test
     void respectsPerConfigurationExcludes() {
         def repoDir = file("repo")
-        def artifact1 = publishArtifact(repoDir, "myGroup", "myArtifact1", "myArtifact2")
-        def artifact2 = publishArtifact(repoDir, "myGroup", "myArtifact2")
+        maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish()
+        maven(repoDir).module("myGroup", "myArtifact2").publish()
 
         runIdeaTask """
 apply plugin: 'java'
 apply plugin: 'idea'
 
 repositories {
-    mavenRepo urls: "${repoDir.toURI()}"
+    maven { url "${repoDir.toURI()}" }
 }
 
 configurations {
@@ -200,15 +232,15 @@ dependencies {
     @Test
     void respectsPerDependencyExcludes() {
         def repoDir = file("repo")
-        def artifact1 = publishArtifact(repoDir, "myGroup", "myArtifact1", "myArtifact2")
-        def artifact2 = publishArtifact(repoDir, "myGroup", "myArtifact2")
+        maven(repoDir).module("myGroup", "myArtifact1").dependsOn("myArtifact2").publish()
+        maven(repoDir).module("myGroup", "myArtifact2").publish()
 
         runIdeaTask """
 apply plugin: 'java'
 apply plugin: 'idea'
 
 repositories {
-    mavenRepo urls: "${repoDir.toURI()}"
+    maven { url "${repoDir.toURI()}" }
 }
 
 dependencies {
@@ -223,24 +255,115 @@ dependencies {
         assert libs.size() == 1
     }
 
+    @Test
+    void allowsCustomOutputFolders() {
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea.module {
+    inheritOutputDirs = false
+    outputDir = file('foo-out')
+    testOutputDir = file('foo-out-test')
+}
+"""
+
+        //then
+        def iml = getFile([:], 'root.iml').text
+        assert iml.contains('inherit-compiler-output="false"')
+        assert iml.contains('foo-out')
+        assert iml.contains('foo-out-test')
+    }
+
+    @Test
+    void dslSupportsShortFormsForModule() {
+        runTask('idea', """
+apply plugin: 'idea'
+
+idea.module.name = 'X'
+assert idea.module.name == 'X'
+
+idea {
+    module.name += 'X'
+    assert module.name == 'XX'
+}
+
+idea.module {
+    name += 'X'
+    assert name == 'XXX'
+}
+
+""")
+    }
+
+    @Test
+    void dslSupportsShortFormsForProject() {
+        runTask('idea', """
+apply plugin: 'idea'
+
+idea.project.wildcards = ['1'] as Set
+assert idea.project.wildcards == ['1'] as Set
+
+idea {
+    project.wildcards += '2'
+    assert project.wildcards == ['1', '2'] as Set
+}
+
+idea.project {
+    wildcards += '3'
+    assert wildcards == ['1', '2', '3'] as Set
+}
+
+""")
+    }
+
+    @Test
+    void showDecentMessageWhenInputFileWasTinkeredWith() {
+        //given
+        file('root.iml') << 'messed up iml file'
+
+        file('build.gradle') << '''
+apply plugin: "java"
+apply plugin: "idea"
+'''
+        file('settings.gradle') << 'rootProject.name = "root"'
+
+        //when
+        def failure = executer.withTasks('idea').runWithFailure()
+
+        //then
+        failure.output.contains("Perhaps this file was tinkered with?")
+    }
+
     private void assertHasExpectedContents(String path) {
         TestFile file = testDir.file(path).assertIsFile()
         TestFile expectedFile = testDir.file("expectedFiles/${path}.xml").assertIsFile()
 
-        def cache = distribution.userHomeDir.file("cache")
-        def cachePath = cache.absolutePath.replace(File.separator, '/')
-        def expectedXml = expectedFile.text.replace('@CACHE_DIR@', cachePath)
+        def expectedXml = expectedFile.text
 
-        Diff diff = new Diff(expectedXml, file.text)
+        def homeDir = distribution.userHomeDir.absolutePath.replace(File.separator, '/')
+        def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
+        def actualXml = file.text.replaceAll(pattern, '@CACHE_DIR@/$1/@SHA1@/')
+
+        Diff diff = new Diff(expectedXml, actualXml)
         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
         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)
+            if (OperatingSystem.current().unix) {
+                def process = ["diff", expectedFile.absolutePath, file.absolutePath].execute()
+                process.consumeProcessOutput(System.out, System.err)
+                process.waitFor()
+            }
+            throw new AssertionFailedError("generated file '$path' does not contain the expected contents: ${e.message}.\nExpected:\n${expectedXml}\nActual:\n${actualXml}").initCause(e)
         }
     }
 
     private containsDir(path, urls) {
         urls.any { it.endsWith(path) }
     }
+
+    private String relPath(File file){
+        return file.absolutePath.replace(File.separator, "/")
+    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
index a975411..f4ef078 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.integtests.fixtures.TestResources
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
 import org.junit.Test
+import spock.lang.Issue
 
 class IdeaModuleIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
@@ -50,6 +51,8 @@ configurations {
 }
 
 idea {
+    pathVariables CUSTOM_VARIABLE: file('customModuleContentRoot').parentFile
+
     module {
         name = 'foo'
         contentRoot = file('customModuleContentRoot')
@@ -66,8 +69,7 @@ idea {
         outputDir = file('muchBetterOutputDir')
         testOutputDir = file('muchBetterTestOutputDir')
 
-        javaVersion = '1.6'
-        variables = [CUSTOM_VARIABLE: file('customModuleContentRoot').parentFile]
+        jdkName = '1.6'
 
         iml {
             generateTo = file('customImlFolder')
@@ -99,8 +101,7 @@ idea {
     }
 
     @Test
-    void plusMinusConfigurationsAreCorrectlyApplied() {
-        file('foo.jar', 'bar.jar')
+    void plusMinusConfigurationsWorkFineForSelfResolvingFileDependencies() {
         //when
         runTask 'idea', '''
 apply plugin: "java"
@@ -110,17 +111,19 @@ configurations {
   bar
   foo
   foo.extendsFrom(bar)
+  baz
 }
 
 dependencies {
   bar files('bar.jar')
-  foo files('foo.jar')
+  foo files('foo.jar', 'foo2.jar', 'foo3.jar')
+  baz files('foo3.jar')
 }
 
 idea {
     module {
         scopes.COMPILE.plus += configurations.foo
-        scopes.COMPILE.minus += configurations.bar
+        scopes.COMPILE.minus += [configurations.bar, configurations.baz]
     }
 }
 '''
@@ -128,7 +131,10 @@ idea {
 
         //then
         assert content.contains('foo.jar')
+        assert content.contains('foo2.jar')
+
         assert !content.contains('bar.jar')
+        assert !content.contains('foo3.jar')
     }
 
     @Test
@@ -158,7 +164,7 @@ idea {
         excludeDirs = [project.file('folderThatIsExcludedNow')] as Set
         iml {
             beforeMerged { it.excludeFolders.clear() }
-            whenMerged   { it.javaVersion = '1.33'   }
+            whenMerged   { it.jdkName = '1.33'   }
         }
     }
 }
@@ -169,4 +175,117 @@ idea {
         assert !iml.contains('folderThatWasExcludedEarlier')
         assert iml.contains('1.33')
     }
+
+    @Issue("GRADLE-1504")
+    @Test
+    void shouldNotPutSourceSetsOutputDirOnClasspath() {
+        testFile('build/generated/main/foo.resource').createFile()
+        testFile('build/ws/test/service.xml').createFile()
+
+        //when
+        runTask 'idea', '''
+apply plugin: "java"
+apply plugin: "idea"
+
+sourceSets.main.output.dir "$buildDir/generated/main"
+sourceSets.test.output.dir "$buildDir/ws/test"
+'''
+        def iml = parseFile(print: true, 'root.iml')
+
+        //then
+        assert iml.component.orderEntry. at scope.collect { it.text() == ['RUNTIME', 'TEST'] }
+
+        def classesDirs = iml.component.orderEntry.library.CLASSES.root. at url.collect { it.text() }
+        assert classesDirs.any { it.contains ('generated/main') }
+        assert classesDirs.any { it.contains ('ws/test') }
+    }
+
+    @Test
+    void theBuiltByTaskBeExecuted() {
+        //when
+        def result = runIdeaTask('''
+apply plugin: "java"
+apply plugin: "idea"
+
+sourceSets.main.output.dir "$buildDir/generated/main", builtBy: 'generateForMain'
+sourceSets.test.output.dir "$buildDir/generated/test", builtBy: 'generateForTest'
+
+task generateForMain << {}
+task generateForTest << {}
+''')
+        //then
+        result.assertTasksExecuted(':generateForMain', ':generateForTest', ':ideaModule', ':ideaProject', ':ideaWorkspace', ':idea')
+    }
+
+    @Test
+    void enablesTogglingJavadocAndSourcesOff() {
+        //given
+        def repoDir = file("repo")
+        def module = maven(repoDir).module("coolGroup", "niceArtifact")
+        module.artifact(classifier: 'sources')
+        module.artifact(classifier: 'javadoc')
+        module.publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+dependencies {
+    compile 'coolGroup:niceArtifact:1.0'
+}
+
+idea.module {
+    downloadSources = false
+    downloadJavadoc = false
+}
+"""
+        def content = getFile([:], 'root.iml').text
+
+        //then
+        assert !content.contains('niceArtifact-1.0-sources.jar')
+        assert !content.contains('niceArtifact-1.0-javadoc.jar')
+    }
+
+    @Test
+    void doesNotBreakWhenSomeDependenciesCannotBeResolved() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("groupOne", "artifactTwo").publish()
+
+        file("settings.gradle") << "include 'someApiProject', 'impl'\n"
+        file('someDependency.jar').createFile()
+
+        //when
+        runIdeaTask """
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'idea'
+}
+
+project(':impl') {
+    repositories {
+        maven { url "${repoDir.toURI()}" }
+    }
+
+    dependencies {
+        compile 'groupOne:artifactTwo:1.0'
+        compile project(':someApiProject')
+        compile 'i.dont:Exist:1.0'
+        compile files('someDependency.jar')
+    }
+}
+"""
+        def content = getFile([print : true], 'impl/impl.iml').text
+
+        //then
+        assert content.count("someDependency.jar") == 1
+        assert content.count("artifactTwo-1.0.jar") == 1
+        assert content.count("someApiProject") == 1
+        assert content.count("unresolved dependency - i.dont#Exist;1.0") == 1
+    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy
index 50776d8..ef544a9 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaMultiModuleIntegrationTest.groovy
@@ -16,9 +16,9 @@
 package org.gradle.plugins.ide.idea
 
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 
 /**
  * @author Szczepan Faber, @date 03.03.11
@@ -57,8 +57,10 @@ project(':shared:model') {
     dependencies {
         utilities project(':util')
     }
-    ideaModule {
-        scopes.TEST.plus.add(configurations.utilities)
+    idea {
+        module {
+            scopes.TEST.plus.add(configurations.utilities)
+        }
     }
 }
 """
@@ -113,8 +115,10 @@ project(':api') {
 }
 
 project(':shared:model') {
-    ideaModule {
-        moduleName = 'very-cool-model'
+    idea {
+        module {
+            name = 'very-cool-model'
+        }
     }
 }
 
@@ -122,15 +126,16 @@ project(':services:utilities') {
     dependencies {
         compile project(':util'), project(':contrib:services:util'), project(':shared:api'), project(':shared:model')
     }
-    ideaModule {
-        moduleName = 'util'
+    idea {
+        module {
+            name = 'util'
+        }
     }
 }
 """
 
         //when
         executer.usingBuildScript(buildFile).usingSettingsFile(settingsFile).withTasks("idea").run()
-//        println(getTestDir())
 
         //then
         assertIprContainsCorrectModules()
@@ -232,8 +237,8 @@ allprojects {
 }
 
 project(':contrib') {
-    ideaModule {
-        moduleName = 'cool-contrib'
+    idea.module {
+        name = 'cool-contrib'
     }
 }
 """
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaPluginGoodBehaviourTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..8f25c0d
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.ide.idea
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class IdeaPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "idea"
+    }
+}
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
index 61e7b57..6cb8d1d 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
@@ -20,11 +20,50 @@ import org.gradle.integtests.fixtures.TestResources
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
 import org.junit.Test
+import spock.lang.Issue
 
 class IdeaProjectIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
     public final TestResources testResources = new TestResources()
 
+    @Issue("GRADLE-1011")
+    @Test
+    void "uses java plugin compatibility settings"() {
+        //when
+        runTask('idea', '''
+apply plugin: "java"
+apply plugin: "idea"
+
+sourceCompatibility = 1.4
+''')
+
+        //then
+        def ipr = getFile([:], 'root.ipr').text
+
+        assert ipr.contains('project-jdk-name="1.4"')
+        assert ipr.contains('languageLevel="JDK_1_4"')
+    }
+
+    @Test
+    void "allows configuring the language level"() {
+        //when
+        runTask('idea', '''
+apply plugin: "java"
+apply plugin: "idea"
+
+idea.project {
+    jdkName   = 1.6
+    languageLevel = 1.5
+}
+''')
+
+        //then
+        def ipr = getFile([:], 'root.ipr').text
+
+        assert ipr.contains('project-jdk-name="1.6"')
+        assert ipr.contains('languageLevel="JDK_1_5"')
+    }
+
     @Test
     void enablesCustomizationsOnNewModel() {
         //when
@@ -36,11 +75,11 @@ allprojects {
 
 idea {
     project {
-        javaVersion = '1.44'
+        jdkName = '1.3'
         wildcards += '!?*.ruby'
 
         //let's remove one of the subprojects from generation:
-        subprojects -= project(':someProjectThatWillBeExcluded')
+        modules -= project(':someProjectThatWillBeExcluded').idea.module
 
         outputFile = new File(outputFile.parentFile, 'someBetterName.ipr')
 
@@ -56,9 +95,64 @@ idea {
 
         //then
         def ipr = getFile([:], 'someBetterName.ipr').text
-        assert ipr.contains('1.44')
+        assert ipr.contains('project-jdk-name="1.3"')
         assert ipr.contains('!?*.ruby')
         assert !ipr.contains('someProjectThatWillBeExcluded')
         assert ipr.contains('hey buddy!')
     }
+
+    @Test
+    void configuresHooks() {
+        def ipr = file('root.ipr')
+        ipr.text = '''<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac"/>
+    <annotationProcessing enabled="false" useClasspath="true"/>
+    <wildcardResourcePatterns>
+      <entry name="!?*.groovy"/>
+      <entry name="!?*.java"/>
+      <entry name="!?*.fooBar"/>
+    </wildcardResourcePatterns>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/root.iml" filepath="$PROJECT_DIR$/root.iml"/>
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.5">
+    <output url="file://$PROJECT_DIR$/out"/>
+  </component>
+</project>
+'''
+
+        //when
+        runTask 'idea', '''
+apply plugin: "java"
+apply plugin: "idea"
+
+def hooks = []
+
+idea {
+    project {
+        ipr {
+            beforeMerged {
+                assert it.wildcards.contains('!?*.fooBar')
+                it.wildcards << '!?*.fooBarTwo'
+                hooks << 'before'
+            }
+            whenMerged {
+                assert it.wildcards.contains('!?*.fooBarTwo')
+                hooks << 'when'
+            }
+        }
+    }
+}
+
+ideaProject.doLast {
+    assert hooks == ['before', 'when']
+}
+'''
+        //then no exception thrown
+    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy
new file mode 100644
index 0000000..5fb85ba
--- /dev/null
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaWorkspaceIntegrationTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.ide.idea
+
+import org.gradle.plugins.ide.AbstractIdeIntegrationTest
+import org.junit.Test
+
+/**
+ * @author: Szczepan Faber, created at: 6/9/11
+ */
+class IdeaWorkspaceIntegrationTest extends AbstractIdeIntegrationTest {
+
+    @Test
+    void enablesCustomizationOfIws() {
+        //when
+        runIdeaTask '''
+apply plugin: "java"
+apply plugin: "idea"
+
+idea.workspace.iws.withXml { it.asNode().appendNode('foo', 'bar') }
+
+idea {
+    workspace {
+        iws {
+            withXml {
+                it.asNode().appendNode('stay', 'happy')
+            }
+        }
+    }
+}
+'''
+
+        //then
+        String content = getFile([:], "root.iws").text
+        assert content.contains("<foo>bar")
+        assert content.contains("<stay>happy")
+    }
+}
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
index 67610d0..2122871 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
@@ -6,13 +6,13 @@
 	<classpathentry kind="src" path="src/test/resources"/>
 	<classpathentry kind="src" path="src/test/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" 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@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar" kind="lib"
+					path="@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar" exported="true">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
 	</classpathentry>
-	<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@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true">
 		<attributes>
 			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
 		</attributes>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
index d2ba916..b643e50 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
@@ -6,7 +6,7 @@
 	<classpathentry kind="src" path="src/test/resources"/>
 	<classpathentry kind="src" path="src/test/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" 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"/>
+	<classpathentry sourcepath="@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar" kind="lib"
+					path="@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar" exported="true"/>
+	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
index 23469ff..99535c7 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppJava6Classpath.xml
@@ -2,4 +2,5 @@
 	<classpathentry kind="output" path="bin"/>
 	<classpathentry kind="src" path="src/main/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
index 5fb6ab5..a28206f 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsClasspath.xml
@@ -2,11 +2,12 @@
 	<classpathentry kind="output" path="bin"/>
 	<classpathentry 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">
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/source/@SHA1@/commons-lang-2.5-sources.jar" kind="var"
+					path="GRADLE_USER_HOME/@CACHE@/commons-lang/commons-lang/2.5/jar/@SHA1@/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"/>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/javadoc/@SHA1@/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"/>
+	<classpathentry sourcepath="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="var" path="GRADLE_USER_HOME/@CACHE@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
index 5705cec..4fd25c0 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webAppWithVarsWtpComponent.xml
@@ -2,8 +2,7 @@
 	<wb-module deploy-name="webAppWithVars">
 		<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:/classpath/var/GRADLE_USER_HOME//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/2.5/jar/@SHA1@/commons-lang-2.5.jar">
 			<dependency-type>uses</dependency-type>
 		</dependent-module>
 	</wb-module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
index 0a572fe..e075b38 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
@@ -2,18 +2,19 @@
 	<classpathentry kind="output" path="bin"/>
 	<classpathentry kind="src" path="src/main/java"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container" exported="true"/>
 	<classpathentry kind="src" path="/api" 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"
+	<classpathentry sourcepath="@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/source/@SHA1@/slf4j-api-1.5.8-sources.jar" kind="lib" path="@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/jar/@SHA1@/slf4j-api-1.5.8.jar" exported="true"/>
+	<classpathentry sourcepath="@CACHE_DIR@/commons-lang/commons-lang/2.5/source/@SHA1@/commons-lang-2.5-sources.jar" kind="lib" path="@CACHE_DIR@/commons-lang/commons-lang/2.5/jar/@SHA1@/commons-lang-2.5.jar"
 					exported="true">
 		<attributes>
-			<attribute name="javadoc_location" value="@CACHE_DIR@/commons-lang/commons-lang/javadocs/commons-lang-2.5-javadoc.jar"/>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-lang/commons-lang/2.5/javadoc/@SHA1@/commons-lang-2.5-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<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">
+	<classpathentry sourcepath="@CACHE_DIR@/commons-io/commons-io/1.2/source/@SHA1@/commons-io-1.2-sources.jar" kind="lib" path="@CACHE_DIR@/commons-io/commons-io/1.2/jar/@SHA1@/commons-io-1.2.jar" exported="true">
 		<attributes>
-			<attribute name="javadoc_location" value="@CACHE_DIR@/commons-io/commons-io/javadocs/commons-io-1.2-javadoc.jar"/>
+			<attribute name="javadoc_location" value="jar:file:@CACHE_DIR@/commons-io/commons-io/1.2/javadoc/@SHA1@/commons-io-1.2-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<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"/>
+	<classpathentry sourcepath="@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar" kind="lib" path="@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar" exported="true"/>
 </classpath>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
index ba922f1..3974e56 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
@@ -2,11 +2,10 @@
 	<wb-module deploy-name="webservice">
 		<property name="context-root" value="webservice"/>
 		<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/lib/@CACHE_DIR@/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/2.5/jar/@SHA1@/commons-lang-2.5.jar">
 			<dependency-type>uses</dependency-type>
 		</dependent-module>
 	</wb-module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java
index 02cee2c..1605126 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java
@@ -2,9 +2,6 @@ package org.gradle.shared;
 
 import junit.framework.TestCase;
 
-import java.io.IOException;
-import java.util.Properties;
-
 public class PersonTest extends TestCase {
     public void testTest() {
         assertTrue(true);
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
index a541c43..32cad06 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
@@ -1,7 +1,9 @@
+import java.util.regex.Pattern
+import junit.framework.AssertionFailedError
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
 import org.custommonkey.xmlunit.XMLAssert
-import junit.framework.AssertionFailedError
+import org.gradle.plugins.ide.eclipse.model.AbstractClasspathEntry
 import org.junit.ComparisonFailure
 
 buildscript {
@@ -9,14 +11,20 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'xmlunit:xmlunit:1.3'
+        classpath 'xmlunit:xmlunit:1.3', 'junit:junit:4.8.1'
     }
 }
 
 defaultTasks 'eclipse', 'cleanEclipse'
 
 allprojects {
-    apply plugin: 'eclipse'
+    apply plugin: 'eclipse-wtp'
+
+    group = 'org.gradle'
+
+    plugins.withType(JavaBasePlugin) {
+        sourceCompatibility = 1.5
+    }
 }
 
 subprojects {
@@ -32,16 +40,29 @@ allprojects {
     afterEvaluate { p ->
         configure(p) {
             eclipseProject.doLast {
-                compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Project.xml")),
-                        file(".project").text)
+                compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Project.xml"),
+                        file(".project"))
             }
 
             if (p.hasProperty('eclipseClasspath')) {
+                eclipse {
+                    classpath {
+                        downloadJavadoc = true
+                        //this hack is it avoid getting mad with xmlunit failures for whitespace
+                        //and with futile attempts to resolve them properly.
+                        file.whenMerged { classpath ->
+                            classpath.entries.each { entry ->
+                                if (entry instanceof AbstractClasspathEntry) {
+                                    entry.entryAttributes.remove(AbstractClasspathEntry.COMPONENT_NON_DEPENDENCY_ATTRIBUTE)
+                                }
+                            }
+                        }
+                    }
+                }
                 eclipseClasspath {
-                    downloadJavadoc = true
                     doLast {
-                        compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Classpath.xml")),
-                                file(".classpath").text)
+                        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Classpath.xml"),
+                                file(".classpath"))
                     }
                 }
             }
@@ -50,7 +71,7 @@ allprojects {
                 eclipseJdt {
                     doLast {
                         compareProperties(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}Jdt.properties")),
-                                file(".settings/org.eclipse.jdt.core.prefs").text)
+                                getActualXml(file(".settings/org.eclipse.jdt.core.prefs")))
                     }
                 }
             }
@@ -58,16 +79,16 @@ allprojects {
             if (p.hasProperty('eclipseWtpComponent')) {
                 eclipseWtpComponent {
                     doLast {
-                        compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}WtpComponent.xml")),
-                                file(".settings/org.eclipse.wst.common.component").text)
+                        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}WtpComponent.xml"),
+                                file(".settings/org.eclipse.wst.common.component"))
                     }
                 }
             }
             if (p.hasProperty('eclipseWtpFacet')) {
                 eclipseWtpFacet {
                     doLast {
-                        compareXmlWithIgnoringOrder(getExpectedXml(file("$rootDir/../expectedFiles/${project.name}WtpFacet.xml")),
-                                file(".settings/org.eclipse.wst.common.project.facet.core.xml").text)
+                        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}WtpFacet.xml"),
+                                file(".settings/org.eclipse.wst.common.project.facet.core.xml"))
                     }
                 }
             }
@@ -88,7 +109,9 @@ void compareProperties(String expectedProperties, String actualProperties) {
     assert expected == actual
 }
 
-void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+void compareXmlWithIgnoringOrder(File expectedFile, File actualFile) {
+    String expectedXml = getExpectedXml(expectedFile)
+    String actualXml = getActualXml(actualFile)
     Diff diff = new Diff(expectedXml, actualXml)
     diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
     try {
@@ -96,12 +119,24 @@ void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
     } catch (AssertionFailedError error) {
         println "EXPECTED:\n${expectedXml}"
         println "ACTUAL:\n${actualXml}"
-        throw new ComparisonFailure("Unexpected content for generated file: ${error.message}", expectedXml, actualXml).initCause(error)
+        throw new ComparisonFailure("Comparison filure: expected: $expectedFile, actual: $actualFile"
+            + "\nUnexpected content for generated file: ${error.message}", expectedXml, actualXml).initCause(error)
     }
 }
 
 String getExpectedXml(File file) {
-    def cache = new File(gradle.gradleUserHomeDir, "/cache").absolutePath.replace(File.separator, '/')
-    return file.text.replace('@CACHE_DIR@', cache)
+    return file.text
+}
+
+String getActualXml(File file) {
+    def homeDir = gradle.gradleUserHomeDir.absolutePath.replace(File.separator, '/')
+    def pattern = Pattern.compile(Pattern.quote(homeDir) + "/caches/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
+    def text = file.text.replaceAll(pattern, '@CACHE_DIR@/$1/@SHA1@/')
+    pattern = Pattern.compile("GRADLE_USER_HOME/artifacts-\\d+/filestore/([^/]+/[^/]+/[^/]+/[^/]+)/[a-z0-9]+/")
+    text = text.replaceAll(pattern, 'GRADLE_USER_HOME/@CACHE@/$1/@SHA1@/')
+
+    //remove trailing slashes for windows paths
+    text = text.replaceAll("jar:file:/", 'jar:file:')
+    return text
 }
 
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle
index 9cd7844..923d6c4 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/webAppWithVars/build.gradle
@@ -5,5 +5,6 @@ dependencies {
     testCompile 'junit:junit:4.7'
 }
 
-eclipseClasspath.variables = ['GRADLE_USER_HOME': gradle.gradleUserHomeDir]
-eclipseWtpComponent.variables = ['GRADLE_USER_HOME': gradle.gradleUserHomeDir]
+eclipse {
+    pathVariables GRADLE_USER_HOME: file("${gradle.gradleUserHomeDir}/caches")
+}
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
index 74c082e..b85fec0 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
@@ -7,8 +7,10 @@ allprojects {
 subprojects {
     apply plugin: 'java'
 
-    ideaModule {
-        downloadJavadoc = true
+    idea {
+        module {
+            downloadJavadoc = true
+        }
     }
 
     repositories {
@@ -26,5 +28,5 @@ subprojects {
 cleanIdea.doLast {
     assert !file("${project.name}.iml").isFile()
     assert !file("${project.name}.ipr").isFile()
-    assert !file("${project.name}.iws").isFile()
+    assert file("${project.name}.iws").isFile() //we don't rid the iws file
 }
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml
index 6ea0ae7..2ad650e 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/api/api.iml.xml
@@ -12,22 +12,22 @@
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/jars/commons-collections-3.2.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" scope="TEST">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/junit/junit/jars/junit-4.7.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml
index 647bac0..771a48d 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webservice/webservice.iml.xml
@@ -11,11 +11,11 @@
     <orderEntry type="module-library" exported="">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/jars/slf4j-api-1.5.8.jar!/"/>
+          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/jar/@SHA1@/slf4j-api-1.5.8.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/sources/slf4j-api-1.5.8-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/1.5.8/source/@SHA1@/slf4j-api-1.5.8-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
@@ -32,37 +32,37 @@
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/jars/commons-lang-2.4.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/jar/@SHA1@/commons-lang-2.4.jar!/"/>
         </CLASSES>
         <JAVADOC>
-          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/javadocs/commons-lang-2.4-javadoc.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/javadoc/@SHA1@/commons-lang-2.4-javadoc.jar!/"/>
         </JAVADOC>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/sources/commons-lang-2.4-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/2.4/source/@SHA1@/commons-lang-2.4-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-io/commons-io/jars/commons-io-1.2.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/jar/@SHA1@/commons-io-1.2.jar!/"/>
         </CLASSES>
         <JAVADOC>
-          <root url="jar://@CACHE_DIR@/commons-io/commons-io/javadocs/commons-io-1.2-javadoc.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/javadoc/@SHA1@/commons-io-1.2-javadoc.jar!/"/>
         </JAVADOC>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-io/commons-io/sources/commons-io-1.2-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/1.2/source/@SHA1@/commons-io-1.2-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" scope="TEST">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/junit/junit/jars/junit-4.7.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/build.gradle
index bcfd7ff..6230e96 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/build.gradle
@@ -10,6 +10,6 @@ dependencies {
     runtime 'junit:junit:4.7 at jar'
 }
 
-ideaModule {
-    variables['CUSTOM_DIR'] = new File(gradle.gradleUserHomeDir, 'custom')
-}
+idea {
+    pathVariables CUSTOM_DIR: new File(gradle.gradleUserHomeDir, 'custom')
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml
index 2382905..39d78c7 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/overwritesExistingDependencies/expectedFiles/root.iml.xml
@@ -10,22 +10,22 @@
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/jars/commons-collections-3.2.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/jar/@SHA1@/commons-collections-3.2.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/3.2/source/@SHA1@/commons-collections-3.2-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" exported="" scope="RUNTIME">
       <library>
         <CLASSES>
-          <root url="jar://@CACHE_DIR@/junit/junit/jars/junit-4.7.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/jar/@SHA1@/junit-4.7.jar!/"/>
         </CLASSES>
         <JAVADOC/>
         <SOURCES>
-          <root url="jar://@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar!/"/>
+          <root url="jar://@CACHE_DIR@/junit/junit/4.7/source/@SHA1@/junit-4.7-sources.jar!/"/>
         </SOURCES>
       </library>
     </orderEntry>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle
index 8b39aa1..400052e 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle
@@ -1,4 +1,6 @@
 allprojects {
     apply plugin: 'java'
     apply plugin: 'idea'
+
+    sourceCompatibility = 1.5
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
new file mode 100644
index 0000000..3c1ec00
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/FileContentMerger.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.ide.api
+
+import org.gradle.listener.ActionBroadcast
+
+/**
+ * Models the generation/parsing/merging capabilities.
+ * <p>
+ * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
+ *
+ * @author: Szczepan Faber, created at: 4/21/11
+ */
+class FileContentMerger {
+
+    ActionBroadcast whenMerged = new ActionBroadcast()
+    ActionBroadcast beforeMerged = new ActionBroadcast()
+
+    /**
+     * Adds a closure to be called after content is loaded from existing file
+     * but before gradle build information is merged
+     * <p>
+     * This is advanced api that gives access to internal implementation.
+     * It might be useful if you want to alter the way gradle build information is merged into existing file content
+     * <p>
+     * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
+     *
+     * @param closure The closure to execute.
+     */
+    public void beforeMerged(Closure closure) {
+        beforeMerged.add(closure)
+    }
+
+    /**
+     * Adds a closure to be called after content is loaded from existing file
+     * and after gradle build information is merged
+     * <p>
+     * This is advanced api that gives access to internal implementation of idea plugin.
+     * Use it only to tackle some tricky edge cases.
+     * <p>
+     * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
+     *
+     * @param closure The closure to execute.
+     */
+    public void whenMerged(Closure closure) {
+        whenMerged.add(closure)
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java
index b642ead..8654da6 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/GeneratorTask.java
@@ -15,8 +15,7 @@
  */
 package org.gradle.plugins.ide.api;
 
-import groovy.lang.Closure;
-import org.gradle.api.Action;
+import org.gradle.api.GradleException;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.OutputFile;
@@ -58,10 +57,17 @@ public class GeneratorTask<T> extends ConventionTask {
         getOutputs().upToDateWhen(Specs.satisfyNone());
     }
 
+    @SuppressWarnings("UnusedDeclaration")
     @TaskAction
     void generate() {
         if (getInputFile().exists()) {
-            domainObject = generator.read(getInputFile());
+            try {
+                domainObject = generator.read(getInputFile());
+            } catch (RuntimeException e) {
+                throw new GradleException(String.format("Cannot parse file '%s'.\n"
+                        + "       Perhaps this file was tinkered with? In that case try delete this file and then retry.",
+                        getInputFile()), e);
+            }
         } else {
             domainObject = generator.defaultInstance();
         }
@@ -110,56 +116,4 @@ public class GeneratorTask<T> extends ConventionTask {
         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/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy
new file mode 100644
index 0000000..46dcea1
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesFileContentMerger.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.ide.api
+
+import org.gradle.api.internal.PropertiesTransformer
+
+/**
+* Models the generation/parsing/merging capabilities. Adds properties-related hooks.
+* <p>
+* For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseJdt} and others.
+*/
+class PropertiesFileContentMerger extends FileContentMerger {
+    PropertiesTransformer transformer
+    
+    PropertiesFileContentMerger(PropertiesTransformer transformer) {
+        this.transformer = transformer
+    }
+    
+    /**
+    * Adds a closure to be called when the file has been created. The {@link java.util.Properties} are passed to the closure as a
+    * parameter. The closure can modify the Properties before they are written to the output file.
+    * <p>
+    * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseJdt} and others.
+    *
+    * @param closure The closure to execute when the Properties have been created.
+    */
+    void withProperties(Closure closure) {
+        transformer.addAction(closure)
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesGeneratorTask.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesGeneratorTask.java
new file mode 100644
index 0000000..c34b07c
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/PropertiesGeneratorTask.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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.ide.api;
+
+import org.gradle.api.internal.PropertiesTransformer;
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject;
+import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator;
+
+/**
+ * A convenience superclass for those tasks which generate Properties configuration files from a domain object of type T.
+ *
+ * @param <T> The domain object type.
+ */
+public abstract class PropertiesGeneratorTask<T extends PersistableConfigurationObject> extends GeneratorTask<T> {
+    private final PropertiesTransformer transformer = new PropertiesTransformer();
+    
+    public PropertiesGeneratorTask() {
+        generator = new PersistableConfigurationObjectGenerator<T>() {
+            public T create() {
+                return PropertiesGeneratorTask.this.create();
+            }
+
+            public void configure(T object) {
+                PropertiesGeneratorTask.this.configure(object);
+            }
+        };
+    }
+
+    protected PropertiesTransformer getTransformer() {
+        return transformer;
+    }
+
+    protected abstract void configure(T object);
+
+    protected abstract T create();
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
new file mode 100644
index 0000000..e59326e
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlFileContentMerger.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.ide.api
+
+import org.gradle.api.internal.XmlTransformer
+
+/**
+ * Models the generation/parsing/merging capabilities. Adds xml-related hooks.
+ * <p>
+ * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
+ *
+ * @author: Szczepan Faber, created at: 4/21/11
+ */
+class XmlFileContentMerger extends FileContentMerger {
+
+    XmlTransformer xmlTransformer
+
+    XmlFileContentMerger(XmlTransformer xmlTransformer) {
+        this.xmlTransformer = xmlTransformer
+    }
+
+    /**
+     * Adds a closure to be called when the file has been created. The XML is passed to the closure as a
+     * parameter in form of a {@link org.gradle.api.XmlProvider}. The closure can modify the XML before
+     * it is written to the output file.
+     * <p>
+     * For examples see docs for {@link org.gradle.plugins.ide.eclipse.model.EclipseProject} or {@link org.gradle.plugins.ide.idea.model.IdeaProject} and others.
+     *
+     * @param closure The closure to execute when the XML has been created.
+     */
+    public void withXml(Closure closure) {
+        xmlTransformer.addAction(closure)
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java
index f50fd88..b0b3952 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/api/XmlGeneratorTask.java
@@ -15,9 +15,6 @@
  */
 package org.gradle.plugins.ide.api;
 
-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.plugins.ide.internal.generator.generator.PersistableConfigurationObject;
 import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator;
@@ -50,25 +47,4 @@ public abstract class XmlGeneratorTask<T extends PersistableConfigurationObject>
 
     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/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy
index e02bb04..76a212a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -15,18 +15,20 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.api.JavaVersion
 import org.gradle.api.Project
-import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.Instantiator
 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.ear.EarPlugin
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.eclipse.internal.EclipseNameDeduper
+import org.gradle.plugins.ide.eclipse.internal.LinkedResourcesCreator
+import org.gradle.plugins.ide.eclipse.model.BuildCommand
+import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
+import org.gradle.plugins.ide.eclipse.model.EclipseModel
 import org.gradle.plugins.ide.internal.IdePlugin
-import org.gradle.plugins.ide.eclipse.model.*
 
 /**
  * <p>A plugin which generates Eclipse files.</p>
@@ -35,14 +37,11 @@ import org.gradle.plugins.ide.eclipse.model.*
  */
 class EclipsePlugin extends IdePlugin {
     static final String ECLIPSE_TASK_NAME = "eclipse"
-    static final String CLEAN_ECLIPSE_TASK_NAME = "cleanEclipse"
     static final String ECLIPSE_PROJECT_TASK_NAME = "eclipseProject"
-    static final String ECLIPSE_WTP_COMPONENT_TASK_NAME = "eclipseWtpComponent"
-    static final String ECLIPSE_WTP_FACET_TASK_NAME = "eclipseWtpFacet"
     static final String ECLIPSE_CP_TASK_NAME = "eclipseClasspath"
     static final String ECLIPSE_JDT_TASK_NAME = "eclipseJdt"
 
-    EclipseModel model = new EclipseModel()
+    EclipseModel model
 
     @Override protected String getLifecycleTaskName() {
         return 'eclipse'
@@ -52,19 +51,27 @@ class EclipsePlugin extends IdePlugin {
         lifecycleTask.description = 'Generates all Eclipse files.'
         cleanTask.description = 'Cleans all Eclipse files.'
 
-        project.convention.plugins.eclipse = model
+        model = project.extensions.create("eclipse", EclipseModel)
 
         configureEclipseProject(project)
         configureEclipseClasspath(project)
         configureEclipseJdt(project)
-        configureEclipseWtpComponent(project)
-        configureEclipseWtpFacet(project)
 
-        project.gradle.projectsEvaluated {
-            new EclipseNameDeduper().configure(project)
+        hookDeduplicationToTheRoot(project)
+    }
+
+    void hookDeduplicationToTheRoot(Project project) {
+        if (project.parent == null) {
+            project.gradle.projectsEvaluated {
+                makeSureProjectNamesAreUnique()
+            }
         }
     }
 
+    public void makeSureProjectNamesAreUnique() {
+        new EclipseNameDeduper().configureRoot(project.rootProject);
+    }
+
     private void configureEclipseProject(Project project) {
         maybeAddTask(project, this, ECLIPSE_PROJECT_TASK_NAME, GenerateEclipseProject) {
             //task properties:
@@ -73,53 +80,39 @@ class EclipsePlugin extends IdePlugin {
             outputFile = project.file('.project')
 
             //model:
-            model.project = services.get(ClassGenerator).newInstance(EclipseProject)
-            projectModel = model.project
+            model.project = projectModel
 
             projectModel.name = project.name
             projectModel.conventionMapping.comment = { project.description }
 
             project.plugins.withType(JavaBasePlugin) {
-                projectModel.buildCommand "org.eclipse.jdt.core.javabuilder"
+                if (!project.plugins.hasPlugin(EarPlugin)) {
+                    projectModel.buildCommand "org.eclipse.jdt.core.javabuilder"
+                }
                 projectModel.natures "org.eclipse.jdt.core.javanature"
+                projectModel.conventionMapping.linkedResources = {
+                    new LinkedResourcesCreator().links(project)
+                }
             }
 
             project.plugins.withType(GroovyBasePlugin) {
-                projectModel.natures.add(natures.indexOf("org.eclipse.jdt.core.javanature"), "org.eclipse.jdt.groovy.core.groovyNature")
+                projectModel.natures.add(projectModel.natures.indexOf("org.eclipse.jdt.core.javanature"), "org.eclipse.jdt.groovy.core.groovyNature")
             }
 
             project.plugins.withType(ScalaBasePlugin) {
-                projectModel.buildCommands.set(buildCommands.findIndexOf { it.name == "org.eclipse.jdt.core.javabuilder" },
-                        new BuildCommand("ch.epfl.lamp.sdt.core.scalabuilder"))
-                projectModel.natures.add(natures.indexOf("org.eclipse.jdt.core.javanature"), "ch.epfl.lamp.sdt.core.scalanature")
-            }
-
-            project.plugins.withType(WarPlugin) {
-                projectModel.buildCommand 'org.eclipse.wst.common.project.facet.core.builder'
-                projectModel.buildCommand 'org.eclipse.wst.validation.validationbuilder'
-                projectModel.natures 'org.eclipse.wst.common.project.facet.core.nature'
-                projectModel.natures 'org.eclipse.wst.common.modulecore.ModuleCoreNature'
-                projectModel.natures 'org.eclipse.jem.workbench.JavaEMFNature'
-
-                doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
-                    configureTask(otherProject, ECLIPSE_PROJECT_TASK_NAME) {
-                        projectModel.buildCommand 'org.eclipse.wst.common.project.facet.core.builder'
-                        projectModel.buildCommand 'org.eclipse.wst.validation.validationbuilder'
-                        projectModel.natures 'org.eclipse.wst.common.project.facet.core.nature'
-                        projectModel.natures 'org.eclipse.wst.common.modulecore.ModuleCoreNature'
-                        projectModel.natures 'org.eclipse.jem.workbench.JavaEMFNature'
-                    }
-                }
+                projectModel.buildCommands.set(projectModel.buildCommands.findIndexOf { it.name == "org.eclipse.jdt.core.javabuilder" },
+                        new BuildCommand("org.scala-ide.sdt.core.scalabuilder"))
+                projectModel.natures.add(projectModel.natures.indexOf("org.eclipse.jdt.core.javanature"), "org.scala-ide.sdt.core.scalanature")
             }
         }
     }
 
     private void configureEclipseClasspath(Project project) {
-        model.classpath = project.services.get(ClassGenerator).newInstance(EclipseClasspath, [project: project])
-        model.classpath.conventionMapping.classesOutputDir = { new File(project.projectDir, 'bin') }
+        model.classpath = project.services.get(Instantiator).newInstance(EclipseClasspath, project)
+        model.classpath.conventionMapping.defaultOutputDir = { new File(project.projectDir, 'bin') }
 
         project.plugins.withType(JavaBasePlugin) {
-            maybeAddTask(project, this, ECLIPSE_CP_TASK_NAME, GenerateEclipseClasspath) {
+            maybeAddTask(project, this, ECLIPSE_CP_TASK_NAME, GenerateEclipseClasspath) { task ->
                 //task properties:
                 description = "Generates the Eclipse classpath file."
                 inputFile = project.file('.classpath')
@@ -127,26 +120,19 @@ class EclipsePlugin extends IdePlugin {
 
                 //model properties:
                 classpath = model.classpath
+                classpath.file = new XmlFileContentMerger(xmlTransformer)
+
+                classpath.sourceSets = project.sourceSets
 
-                classpath.sourceSets = project.sourceSets //TODO SF - should be a convenience property?
                 classpath.containers 'org.eclipse.jdt.launching.JRE_CONTAINER'
 
                 project.plugins.withType(JavaPlugin) {
                     classpath.plusConfigurations = [project.configurations.testRuntime]
-                }
-
-                project.plugins.withType(WarPlugin) {
-                    doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
-                        configureTask(otherProject, ECLIPSE_CP_TASK_NAME) {
-                            whenConfigured { Classpath classpath ->
-                                for (entry in classpath.entries) {
-                                    if (entry instanceof Library) {
-                                        // '../' and '/WEB-INF/lib' both seem to be correct (and equivalent) values here
-                                        entry.entryAttributes['org.eclipse.jst.component.dependency'] = '../'
-                                    }
-                                }
-                            }
-                        }
+                    classpath.conventionMapping.classFolders = {
+                        return (project.sourceSets.main.output.dirs + project.sourceSets.test.output.dirs) as List
+                    }
+                    task.dependsOn {
+                        project.sourceSets.main.output.dirs + project.sourceSets.test.output.dirs
                     }
                 }
             }
@@ -161,144 +147,17 @@ class EclipsePlugin extends IdePlugin {
                 outputFile = project.file('.settings/org.eclipse.jdt.core.prefs')
                 inputFile = project.file('.settings/org.eclipse.jdt.core.prefs')
                 //model properties:
-                model.jdt = services.get(ClassGenerator).newInstance(EclipseJdt)
-                jdt = model.jdt
+                model.jdt = jdt
                 jdt.conventionMapping.sourceCompatibility = { project.sourceCompatibility }
                 jdt.conventionMapping.targetCompatibility = { project.targetCompatibility }
             }
         }
     }
 
-    private void configureEclipseWtpComponent(Project project) {
-        project.plugins.withType(WarPlugin) {
-            maybeAddTask(project, this, ECLIPSE_WTP_COMPONENT_TASK_NAME, GenerateEclipseWtpComponent) {
-                //task properties:
-                description = 'Generates the Eclipse WTP component settings file.'
-                inputFile = project.file('.settings/org.eclipse.wst.common.component')
-                outputFile = project.file('.settings/org.eclipse.wst.common.component')
-
-                //model properties:
-                model.wtp.component = services.get(ClassGenerator).newInstance(EclipseWtpComponent, [project: project])
-                component = model.wtp.component
-
-                component.conventionMapping.sourceDirs = { getMainSourceDirs(project) }
-                component.plusConfigurations = [project.configurations.runtime]
-                component.minusConfigurations = [project.configurations.providedRuntime]
-                component.deployName = project.name
-                component.resource deployPath: '/', sourcePath: project.convention.plugins.war.webAppDirName // TODO: not lazy
-                component.conventionMapping.contextPath = { project.war.baseName }
-            }
-
-            doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
-                def eclipsePlugin = otherProject.plugins.getPlugin(EclipsePlugin)
-                // require Java plugin because we need source set 'main'
-                // (in the absence of 'main', it probably makes no sense to write the file)
-                otherProject.plugins.withType(JavaPlugin) {
-                    maybeAddTask(otherProject, eclipsePlugin, ECLIPSE_WTP_COMPONENT_TASK_NAME, GenerateEclipseWtpComponent) {
-                        //task properties:
-                        description = 'Generates the Eclipse WTP component settings file.'
-                        inputFile = otherProject.file('.settings/org.eclipse.wst.common.component')
-                        outputFile = otherProject.file('.settings/org.eclipse.wst.common.component')
-
-                        //model properties:
-                        eclipsePlugin.model.wtp.component = services.get(ClassGenerator).newInstance(EclipseWtpComponent, [project: otherProject])
-                        component = eclipsePlugin.model.wtp.component
-
-                        component.deployName = otherProject.name
-                        component.conventionMapping.resources = {
-                            getMainSourceDirs(otherProject).collect { new WbResource("/", otherProject.relativePath(it)) }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private void configureEclipseWtpFacet(Project project) {
-        project.plugins.withType(WarPlugin) {
-            maybeAddTask(project, this, ECLIPSE_WTP_FACET_TASK_NAME, GenerateEclipseWtpFacet) {
-                //task properties:
-                description = 'Generates the Eclipse WTP facet settings file.'
-                inputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-                outputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-
-                //model properties:
-                model.wtp.facet = services.get(ClassGenerator).newInstance(EclipseWtpFacet)
-                facet = model.wtp.facet
-                facet.conventionMapping.facets = { [new Facet("jst.web", "2.4"), new Facet("jst.java", toJavaFacetVersion(project.sourceCompatibility))] }
-            }
-
-            doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
-                def eclipsePlugin = otherProject.plugins.getPlugin(EclipsePlugin)
-                maybeAddTask(otherProject, eclipsePlugin, ECLIPSE_WTP_FACET_TASK_NAME, GenerateEclipseWtpFacet) {
-                    //task properties:
-                    description = 'Generates the Eclipse WTP facet settings file.'
-                    inputFile = otherProject.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-                    outputFile = otherProject.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-
-                    //model properties:
-                    eclipsePlugin.model.wtp.facet = services.get(ClassGenerator).newInstance(EclipseWtpFacet)
-                    facet = eclipsePlugin.model.wtp.facet
-
-                    facet.conventionMapping.facets = { [new Facet("jst.utility", "1.0")] }
-                    otherProject.plugins.withType(JavaPlugin) {
-                        facet.conventionMapping.facets = {
-                            [new Facet("jst.utility", "1.0"), new Facet("jst.java",
-                                    toJavaFacetVersion(otherProject.sourceCompatibility))]
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    // TODO: might have to search all class paths of all source sets for project dependendencies, not just runtime configuration
-    private void doLaterWithEachDependedUponEclipseProject(Project project, Closure action) {
-        project.gradle.projectsEvaluated {
-            eachDependedUponEclipseProject(project, action)
-        }
-    }
-
-    private void eachDependedUponEclipseProject(Project project, Closure action) {
-        def runtimeConfig = project.configurations.findByName("runtime")
-        if (runtimeConfig) {
-            def projectDeps = runtimeConfig.getAllDependencies(ProjectDependency)
-            def dependedUponProjects = projectDeps*.dependencyProject
-            for (dependedUponProject in dependedUponProjects) {
-                dependedUponProject.plugins.withType(EclipsePlugin) { action(dependedUponProject) }
-                eachDependedUponEclipseProject(dependedUponProject, action)
-            }
-        }
-    }
-
-    private void withTask(Project project, String taskName, Closure action) {
-        project.tasks.matching { it.name == taskName }.all(action)
-    }
-
-    private void configureTask(Project project, String taskName, Closure action) {
-        withTask(project, taskName) { task ->
-            project.configure(task, action)
-        }
-    }
-
-    private void maybeAddTask(Project project, EclipsePlugin plugin, String taskName, Class taskType, Closure action) {
+    private void maybeAddTask(Project project, IdePlugin plugin, String taskName, Class taskType, Closure action) {
         if (project.tasks.findByName(taskName)) { return }
         def task = project.tasks.add(taskName, taskType)
         project.configure(task, action)
         plugin.addWorker(task)
     }
-
-    private String toJavaFacetVersion(JavaVersion version) {
-        if (version == JavaVersion.VERSION_1_5) {
-            return '5.0'
-        }
-        if (version == JavaVersion.VERSION_1_6) {
-            return '6.0'
-        }
-        return version.toString()
-    }
-
-    private Set<File> getMainSourceDirs(Project project) {
-        project.sourceSets.main.allSource.srcDirs as LinkedHashSet
-    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy
new file mode 100644
index 0000000..323b899
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2011 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.ide.eclipse
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.Project
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.WarPlugin
+import org.gradle.plugins.ear.EarPlugin
+import org.gradle.plugins.ide.eclipse.model.Facet.FacetType
+import org.gradle.plugins.ide.internal.IdePlugin
+import org.gradle.plugins.ide.eclipse.model.*
+
+/**
+ * @author: Szczepan Faber, created at: 6/28/11
+ */
+class EclipseWtpPlugin extends IdePlugin {
+
+    static final String ECLIPSE_WTP_COMPONENT_TASK_NAME = "eclipseWtpComponent"
+    static final String ECLIPSE_WTP_FACET_TASK_NAME = "eclipseWtpFacet"
+    static final String WEB_LIBS_CONTAINER = 'org.eclipse.jst.j2ee.internal.web.container'
+
+    @Override protected String getLifecycleTaskName() {
+        return "eclipseWtp"
+    }
+
+    EclipseWtp eclipseWtpModel
+
+    @Override protected void onApply(Project project) {
+        EclipsePlugin delegatePlugin = project.getPlugins().apply(EclipsePlugin.class);
+        delegatePlugin.model.wtp = project.services.get(Instantiator).newInstance(EclipseWtp, delegatePlugin.model.classpath)
+        eclipseWtpModel = delegatePlugin.model.wtp
+
+        lifecycleTask.description = 'Generates Eclipse wtp configuration files.'
+        cleanTask.description = 'Cleans Eclipse wtp configuration files.'
+
+        delegatePlugin.getLifecycleTask().dependsOn(getLifecycleTask())
+        delegatePlugin.getCleanTask().dependsOn(getCleanTask())
+
+        configureEclipseProjectForPlugin(project, WarPlugin)
+        configureEclipseProjectForPlugin(project, EarPlugin)
+        configureEclipseClasspathForWarPlugin(project)
+
+        configureEclipseWtpComponent(project)
+        configureEclipseWtpFacet(project)
+    }
+
+    private void configureEclipseClasspathForWarPlugin(Project project) {
+        project.plugins.withType(WarPlugin) {
+            project.eclipse.classpath.containers WEB_LIBS_CONTAINER
+
+            project.eclipse.classpath.file.whenMerged { Classpath classpath ->
+                for (entry in classpath.entries) {
+                    if (entry instanceof AbstractLibrary) {
+                        //this is necessary to avoid annoying warnings upon import to Eclipse
+                        //the .classpath entries can be marked all as non-deployable dependencies
+                        //because the wtp component file declares the deployable dependencies
+                        entry.entryAttributes[AbstractClasspathEntry.COMPONENT_NON_DEPENDENCY_ATTRIBUTE] = ''
+                    }
+                }
+            }
+
+            doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
+                otherProject.eclipse.classpath.file.whenMerged { Classpath classpath ->
+                    for (entry in classpath.entries) {
+                        if (entry instanceof AbstractLibrary) {
+                            // '../' and '/WEB-INF/lib' both seem to be correct (and equivalent) values here
+                            //this is necessary so that the depended upon projects will have their dependencies
+                            // deployed to WEB-INF/lib of the main project.
+                            entry.entryAttributes[AbstractClasspathEntry.COMPONENT_DEPENDENCY_ATTRIBUTE] = '../'
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void configureEclipseWtpComponent(Project project) {
+        configureEclipseWtpComponentWithType(project, WarPlugin)
+        configureEclipseWtpComponentWithType(project, EarPlugin)
+    }
+
+    private void configureEclipseWtpComponentWithType(Project project, Class<?> type) {
+        project.plugins.withType(type) {
+            maybeAddTask(project, this, ECLIPSE_WTP_COMPONENT_TASK_NAME, GenerateEclipseWtpComponent) {
+                //task properties:
+                description = 'Generates the Eclipse WTP component settings file.'
+                inputFile = project.file('.settings/org.eclipse.wst.common.component')
+                outputFile = project.file('.settings/org.eclipse.wst.common.component')
+
+                //model properties:
+                eclipseWtpModel.component = component
+
+                component.conventionMapping.deployName = { project.eclipse.project.name }
+
+                if (WarPlugin.class.isAssignableFrom(type)) {
+                    component.libConfigurations = [project.configurations.runtime]
+                    component.minusConfigurations = [project.configurations.providedRuntime]
+                    component.conventionMapping.contextPath = { project.war.baseName }
+                    component.conventionMapping.resources = { [new WbResource('/', project.convention.plugins.war.webAppDirName)] }
+                    component.conventionMapping.sourceDirs = { getMainSourceDirs(project) }
+                } else if (EarPlugin.class.isAssignableFrom(type)) {
+                    component.rootConfigurations = [project.configurations.deploy]
+                    component.libConfigurations = [project.configurations.earlib]
+                    component.minusConfigurations = []
+                    component.classesDeployPath = "/"
+                    component.libDeployPath = "/lib"
+                    component.conventionMapping.sourceDirs = { [project.file { project.appDirName }] as Set }
+                    project.plugins.withType(JavaPlugin) {
+                        component.conventionMapping.sourceDirs = { getMainSourceDirs(project) }
+                    }
+                }
+            }
+
+            doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
+                def eclipseWtpPlugin = otherProject.plugins.getPlugin(EclipseWtpPlugin)
+                // require Java plugin because we need source set 'main'
+                // (in the absence of 'main', it probably makes no sense to write the file)
+                otherProject.plugins.withType(JavaPlugin) {
+                    maybeAddTask(otherProject, eclipseWtpPlugin, ECLIPSE_WTP_COMPONENT_TASK_NAME, GenerateEclipseWtpComponent) {
+                        //task properties:
+                        description = 'Generates the Eclipse WTP component settings file.'
+                        inputFile = otherProject.file('.settings/org.eclipse.wst.common.component')
+                        outputFile = otherProject.file('.settings/org.eclipse.wst.common.component')
+
+                        //model properties:
+                        eclipseWtpPlugin.eclipseWtpModel.component = component
+
+                        component.conventionMapping.deployName = { otherProject.eclipse.project.name }
+                        component.conventionMapping.resources = {
+                            getMainSourceDirs(otherProject).collect { new WbResource("/", otherProject.relativePath(it)) }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void configureEclipseWtpFacet(Project project) {
+        configureEclipseWtpFacetWithType(project, WarPlugin)
+        configureEclipseWtpFacetWithType(project, EarPlugin)
+    }
+
+    private void configureEclipseWtpFacetWithType(Project project, Class<?> type) {
+        project.plugins.withType(type) {
+            maybeAddTask(project, this, ECLIPSE_WTP_FACET_TASK_NAME, GenerateEclipseWtpFacet) {
+                //task properties:
+                description = 'Generates the Eclipse WTP facet settings file.'
+                inputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+                outputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+
+                //model properties:
+                eclipseWtpModel.facet = facet
+                if (WarPlugin.isAssignableFrom(type)) {
+                    facet.conventionMapping.facets = { [new Facet(FacetType.fixed, "jst.java", null), new Facet(FacetType.fixed, "jst.web", null),
+                        new Facet(FacetType.installed, "jst.web", "2.4"), new Facet(FacetType.installed, "jst.java", toJavaFacetVersion(project.sourceCompatibility))] }
+                } else if (EarPlugin.isAssignableFrom(type)) {
+                    facet.conventionMapping.facets = { [new Facet(FacetType.fixed, "jst.ear", null), new Facet(FacetType.installed, "jst.ear", "5.0")] }
+                }
+            }
+
+            doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
+                def eclipseWtpPlugin = otherProject.plugins.getPlugin(EclipseWtpPlugin)
+                maybeAddTask(otherProject, eclipseWtpPlugin, ECLIPSE_WTP_FACET_TASK_NAME, GenerateEclipseWtpFacet) {
+                    //task properties:
+                    description = 'Generates the Eclipse WTP facet settings file.'
+                    inputFile = otherProject.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+                    outputFile = otherProject.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+
+                    //model properties:
+                    eclipseWtpPlugin.eclipseWtpModel.facet = facet
+
+                    facet.conventionMapping.facets = { [new Facet(FacetType.fixed, "jst.java", null), new Facet(FacetType.fixed, "jst.web", null),
+                        new Facet(FacetType.installed, "jst.utility", "1.0")] }
+                    otherProject.plugins.withType(JavaPlugin) {
+                        facet.conventionMapping.facets = {
+                            [new Facet(FacetType.fixed, "jst.java", null), new Facet(FacetType.fixed, "jst.web", null),
+                                new Facet(FacetType.installed, "jst.utility", "1.0"), new Facet(FacetType.installed, "jst.java", toJavaFacetVersion(otherProject.sourceCompatibility))]
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void maybeAddTask(Project project, IdePlugin plugin, String taskName, Class taskType, Closure action) {
+        if (project.tasks.findByName(taskName)) { return }
+        def task = project.tasks.add(taskName, taskType)
+        project.configure(task, action)
+        plugin.addWorker(task)
+    }
+
+    private void doLaterWithEachDependedUponEclipseProject(Project project, Closure action) {
+        project.gradle.projectsEvaluated {
+            eachDependedUponEclipseProject(project, action)
+        }
+    }
+
+    private void eachDependedUponEclipseProject(Project project, Closure action) {
+        def runtimeConfig = project.configurations.findByName("runtime")
+        if (runtimeConfig) {
+            def projectDeps = runtimeConfig.allDependencies.withType(ProjectDependency)
+            def dependedUponProjects = projectDeps*.dependencyProject
+            for (dependedUponProject in dependedUponProjects) {
+                dependedUponProject.plugins.withType(EclipseWtpPlugin) { action(dependedUponProject) }
+                eachDependedUponEclipseProject(dependedUponProject, action)
+            }
+        }
+    }
+
+    private void configureEclipseProjectForPlugin(Project project, Class<?> type) {
+        project.plugins.withType(type) {
+            project.tasks.withType(GenerateEclipseProject) {
+                projectModel.buildCommand 'org.eclipse.wst.common.project.facet.core.builder'
+                projectModel.buildCommand 'org.eclipse.wst.validation.validationbuilder'
+                projectModel.natures 'org.eclipse.wst.common.project.facet.core.nature'
+                projectModel.natures 'org.eclipse.wst.common.modulecore.ModuleCoreNature'
+                projectModel.natures 'org.eclipse.jem.workbench.JavaEMFNature'
+
+                doLaterWithEachDependedUponEclipseProject(project) { Project otherProject ->
+                    otherProject.tasks.withType(GenerateEclipseProject) {
+                        projectModel.buildCommand 'org.eclipse.wst.common.project.facet.core.builder'
+                        projectModel.buildCommand 'org.eclipse.wst.validation.validationbuilder'
+                        projectModel.natures 'org.eclipse.wst.common.project.facet.core.nature'
+                        projectModel.natures 'org.eclipse.wst.common.modulecore.ModuleCoreNature'
+                        projectModel.natures 'org.eclipse.jem.workbench.JavaEMFNature'
+                    }
+                }
+            }
+        }
+    }
+
+    private Set<File> getMainSourceDirs(Project project) {
+        project.sourceSets.main.allSource.srcDirs as LinkedHashSet
+    }
+
+    private String 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/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy
index 63af317..020c7b2 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseClasspath.groovy
@@ -20,9 +20,12 @@ import org.gradle.api.tasks.SourceSet
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.Classpath
 import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
+import org.gradle.util.DeprecationLogger
 
 /**
- * Generates an Eclipse <code>.classpath</code> file.
+ * Generates an Eclipse <code>.classpath</code> file. If you want to fine tune the eclipse configuration
+ * <p>
+ * At this moment nearly all configuration is done via {@link EclipseClasspath}.
  *
  * @author Hans Dockter
  */
@@ -35,7 +38,7 @@ class GenerateEclipseClasspath extends XmlGeneratorTask<Classpath> {
     }
 
     @Override protected Classpath create() {
-        return new Classpath(xmlTransformer)
+        return new Classpath(xmlTransformer, classpath.fileReferenceFactory)
     }
 
     @Override protected void configure(Classpath xmlClasspath) {
@@ -43,113 +46,153 @@ class GenerateEclipseClasspath extends XmlGeneratorTask<Classpath> {
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.sourceSets. See examples in {@link EclipseClasspath}.
+     * <p>
      * The source sets to be added to the classpath.
      */
     Iterable<SourceSet> getSourceSets() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.sourceSets", "eclipse.classpath.sourceSets")
         classpath.sourceSets
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.sourceSets. See examples in {@link EclipseClasspath}.
+     * <p>
      * The source sets to be added to the classpath.
      */
     void setSourceSets(Iterable<SourceSet> sourceSets) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.sourceSets", "eclipse.classpath.sourceSets")
         classpath.sourceSets = sourceSets
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.plusConfigurations. See examples in {@link EclipseClasspath}.
+     * <p>
      * The configurations which files are to be transformed into classpath entries.
      */
     Collection<Configuration> getPlusConfigurations() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.plusConfigurations", "eclipse.classpath.plusConfigurations")
         classpath.plusConfigurations
     }
 
     void setPlusConfigurations(Collection<Configuration> plusConfigurations) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.plusConfigurations", "eclipse.classpath.plusConfigurations")
         classpath.plusConfigurations = plusConfigurations
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.minusConfigurations. See examples in {@link EclipseClasspath}.
+     * <p>
      * The configurations which files are to be excluded from the classpath entries.
      */
     Collection<Configuration> getMinusConfigurations() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.minusConfigurations", "eclipse.classpath.minusConfigurations")
         classpath.minusConfigurations
     }
 
     void setMinusConfigurations(Collection<Configuration> minusConfigurations) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.minusConfigurations", "eclipse.classpath.minusConfigurations")
         classpath.minusConfigurations = minusConfigurations
     }
 
     /**
+     * Deprecated. Please use #eclipse.pathVariables. See examples in {@link EclipseClasspath}.
+     * <p>
      * Adds path variables to be used for replacing absolute paths in classpath entries.
      *
      * @param pathVariables A map with String->File pairs.
      */
     Map<String, File> getVariables() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.variables", "eclipse.pathVariables")
         classpath.pathVariables
     }
 
     void setVariables(Map<String, File> variables) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.variables", "eclipse.pathVariables")
         classpath.pathVariables = variables
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.containers. See examples in {@link EclipseClasspath}.
+     * <p>
      * Containers to be added to the classpath
      */
     Set<String> getContainers() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.containers", "eclipse.classpath.containers")
         classpath.containers
     }
 
     void setContainers(Set<String> containers) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.containers", "eclipse.classpath.containers")
         classpath.containers = containers
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.defaultOutputDir. See examples in {@link EclipseClasspath}.
+     * <p>
      * The default output directory for eclipse generated files, eg classes.
      */
     File getDefaultOutputDir() {
-        classpath.classesOutputDir
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.defaultOutputDir", "eclipse.classpath.defaultOutputDir")
+        classpath.defaultOutputDir
     }
 
     void setDefaultOutputDir(File defaultOutputDir) {
-        classpath.classesOutputDir = defaultOutputDir
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.defaultOutputDir", "eclipse.classpath.defaultOutputDir")
+        classpath.defaultOutputDir = defaultOutputDir
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.downloadSources. See examples in {@link EclipseClasspath}.
+     * <p>
      * Whether to download and add sources associated with the dependency jars. Defaults to true.
      */
     boolean getDownloadSources() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.downloadSources", "eclipse.classpath.downloadSources")
         classpath.downloadSources
     }
 
     void setDownloadSources(boolean downloadSources) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.downloadSources", "eclipse.classpath.downloadSources")
         classpath.downloadSources = downloadSources
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.downloadJavadoc. See examples in {@link EclipseClasspath}.
+     * <p>
      * Whether to download and add javadocs associated with the dependency jars. Defaults to false.
      */
     boolean getDownloadJavadoc() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.downloadJavadoc", "eclipse.classpath.downloadJavadoc")
         classpath.downloadJavadoc
     }
 
     void setDownloadJavadoc(boolean downloadJavadoc) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.downloadJavadoc", "eclipse.classpath.downloadJavadoc")
         classpath.downloadJavadoc = downloadJavadoc
     }
 
     /**
+     * Deprecated. Please use #eclipse.classpath.containers. See examples in {@link EclipseClasspath}.
+     * <p>
      * Adds containers to the .classpath.
      *
      * @param containers the container names to be added to the .classpath.
      */
     void containers(String... containers) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.containers", "eclipse.classpath.containers")
         classpath.containers(containers)
     }
 
     /**
+     * Deprecated. Please use #eclipse.pathVariables. See examples in {@link EclipseClasspath}.
+     * <p>
      * Adds variables to be used for replacing absolute paths in classpath entries.
      *
      * @param variables A map where the keys are the variable names and the values are the variable values.
      */
     void variables(Map<String, File> variables) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseClasspath.variables", "eclipse.pathVariables")
         assert variables != null
         classpath.pathVariables.putAll variables
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy
index 20d0a69..aed3fb4 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy
@@ -15,54 +15,37 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.api.JavaVersion
-import org.gradle.plugins.ide.api.GeneratorTask
+import org.gradle.api.internal.Instantiator
+import org.gradle.plugins.ide.api.PropertiesFileContentMerger
+import org.gradle.plugins.ide.api.PropertiesGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseJdt
 import org.gradle.plugins.ide.eclipse.model.Jdt
-import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObjectGenerator
 
 /**
- * Generates the Eclipse JDT configuration file.
+ * Generates the Eclipse JDT configuration file. If you want to fine tune the eclipse configuration
+ * <p>
+ * At this moment nearly all configuration is done via {@link EclipseJdt}.
  */
-class GenerateEclipseJdt extends GeneratorTask<Jdt> {
+class GenerateEclipseJdt extends PropertiesGeneratorTask<Jdt> {
 
     /**
      * Eclipse project model that contains information needed for this task
      */
     EclipseJdt jdt
 
-    /**
-     * The source Java language level.
-     */
-    JavaVersion getSourceCompatibility() {
-        jdt.sourceCompatibility
-    }
-
-    void setSourceCompatibility(Object sourceCompatibility) {
-        jdt.sourceCompatibility = sourceCompatibility
-    }
-
-    /**
-     * The target JVM to generate {@code .class} files for.
-     */
-    JavaVersion getTargetCompatibility() {
-        jdt.targetCompatibility
+    GenerateEclipseJdt() {
+        jdt = services.get(Instantiator).newInstance(EclipseJdt, new PropertiesFileContentMerger(getTransformer()))
     }
-
-    void setTargetCompatibility(Object targetCompatibility) {
-        jdt.targetCompatibility = targetCompatibility
+    
+    protected Jdt create() {
+        return new Jdt(getTransformer())
     }
-
-    GenerateEclipseJdt() {
-        generator = new PersistableConfigurationObjectGenerator<Jdt>() {
-            Jdt create() {
-                return new Jdt()
-            }
-
-            void configure(Jdt jdt) {
-                jdt.sourceCompatibility = getJdt().sourceCompatibility
-                jdt.targetCompatibility = getJdt().targetCompatibility
-            }
-        }
+    
+    protected void configure(Jdt jdtContent) {
+        def jdtModel = getJdt()
+        jdtModel.file.beforeMerged.execute(jdtContent)
+        jdtContent.sourceCompatibility = jdtModel.sourceCompatibility
+        jdtContent.targetCompatibility = jdtModel.targetCompatibility
+        jdtModel.file.whenMerged.execute(jdtContent)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
index 1c02064..aecdd3e 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2011 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.
@@ -15,24 +15,17 @@
  */
 package org.gradle.plugins.ide.eclipse
 
+import org.gradle.api.internal.Instantiator
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.api.XmlGeneratorTask
-import org.gradle.plugins.ide.eclipse.model.BuildCommand
 import org.gradle.plugins.ide.eclipse.model.EclipseProject
-import org.gradle.plugins.ide.eclipse.model.Link
 import org.gradle.plugins.ide.eclipse.model.Project
 
 /**
- * Generates an Eclipse <code>.project</code> file.
+ * Generates an Eclipse <code>.project</code> file. If you want to fine tune the eclipse configuration
  * <p>
- * Example how to configure eclipse project generation:
- * <pre autoTested=''>
- * apply plugin: 'java'
- * apply plugin: 'eclipse'
+ * At this moment nearly all configuration is done via {@link EclipseProject}.
  *
- * eclipseProject {
- *   //...
- * }
- * </pre>
  * @author Hans Dockter
  */
 class GenerateEclipseProject extends XmlGeneratorTask<Project> {
@@ -44,6 +37,7 @@ class GenerateEclipseProject extends XmlGeneratorTask<Project> {
 
     GenerateEclipseProject() {
         xmlTransformer.indentation = "\t"
+        projectModel = services.get(Instantiator).newInstance(EclipseProject, new XmlFileContentMerger(xmlTransformer))
     }
 
     @Override protected Project create() {
@@ -54,131 +48,4 @@ class GenerateEclipseProject extends XmlGeneratorTask<Project> {
         projectModel.mergeXmlProject(project);
     }
 
-    /**
-     * Configures eclipse project name. It is <b>optional</b> because the task should configure it correctly for you.
-     * By default it will try to use the <b>project.name</b> or prefix it with a part of a <b>project.path</b>
-     * to make sure the moduleName is unique in the scope of a multi-module build.
-     * The 'uniqeness' of a module name is required for correct import
-     * into Eclipse and the task will make sure the name is unique.
-     * <p>
-     * The logic that makes sure project names are uniqe is available <b>since</b> 1.0-milestone-2
-     * <p>
-     * In case you need to override the default projectName this is the way to go:
-     * <pre autoTested=''>
-     * apply plugin: 'eclipse'
-     *
-     * eclipseProject {
-     *   projectName = 'some-important-project'
-     * }
-     * </pre>
-     */
-    String getProjectName() {
-        projectModel.name
-    }
-
-    void setProjectName(String projectName) {
-        projectModel.name = projectName
-    }
-
-    /**
-     * A comment used for the eclipse project
-     */
-    String getComment() {
-        projectModel.comment
-    }
-
-    void setComment(String comment) {
-        projectModel.comment = comment
-    }
-
-    /**
-     * The referenced projects of this Eclipse project.
-     */
-    Set<String> getReferencedProjects() {
-        projectModel.referencedProjects
-    }
-
-    void setReferencedProjects(Set<String> referencedProjects) {
-        projectModel.referencedProjects = referencedProjects
-    }
-
-    /**
-     * The natures to be added to this Eclipse project.
-     */
-    List<String> getNatures() {
-        projectModel.natures
-    }
-
-    void setNatures(List<String> natures) {
-        projectModel.natures = natures
-    }
-
-    /**
-     * The build commands to be added to this Eclipse project.
-     */
-    List<BuildCommand> getBuildCommands() {
-        projectModel.buildCommands
-    }
-
-    void setBuildCommands(List<BuildCommand> buildCommands) {
-        projectModel.buildCommands = buildCommands
-    }
-
-    /**
-     * The linked resources to be added to this Eclipse project.
-     */
-    Set<Link> getLinks() {
-        projectModel.linkedResources
-    }
-
-    void setLinks(Set<Link> links) {
-        projectModel.linkedResources = links
-    }
-
-    /**
-     * Adds natures entries to the eclipse project.
-     * @param natures the nature names
-     */
-    void natures(String... natures) {
-        projectModel.natures(natures)
-    }
-
-    /**
-     * Adds project references to the eclipse project.
-     *
-     * @param referencedProjects The name of the project references.
-     */
-    void referencedProjects(String... referencedProjects) {
-        projectModel.referencedProjects(referencedProjects)
-    }
-
-    /**
-     * Adds a build command with arguments to the eclipse project.
-     *
-     * @param args A map with arguments, where the key is the name of the argument and the value the value.
-     * @param buildCommand The name of the build command.
-     * @see #buildCommand(String)
-     */
-    void buildCommand(Map args, String buildCommand) {
-        projectModel.buildCommand(args, buildCommand)
-    }
-
-    /**
-     * Adds a build command to the eclipse project.
-     *
-     * @param buildCommand The name of the build command
-     * @see #buildCommand(Map, String)
-     */
-    void buildCommand(String buildCommand) {
-        projectModel.buildCommand(buildCommand)
-    }
-
-    /**
-     * Adds a link to the eclipse project.
-     *
-     * @param args A maps with the args for the link. Legal keys for the map are name, type, location and locationUri.
-     */
-    void link(Map<String, String> args) {
-        projectModel.linkedResource(args)
-    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
index ccbeb7d..f74795c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2008 the original author or authors.
+ * Copyright 2011 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.
@@ -16,14 +16,20 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.internal.Instantiator
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent
 import org.gradle.plugins.ide.eclipse.model.WbProperty
 import org.gradle.plugins.ide.eclipse.model.WbResource
 import org.gradle.plugins.ide.eclipse.model.WtpComponent
+import org.gradle.util.DeprecationLogger
 
 /**
  * Generates the org.eclipse.wst.common.component settings file for Eclipse WTP.
+ * If you want to fine tune the eclipse configuration
+ * <p>
+ * At this moment nearly all configuration is done via {@link EclipseWtpComponent}.
  *
  * @author Hans Dockter
  */
@@ -33,6 +39,7 @@ class GenerateEclipseWtpComponent extends XmlGeneratorTask<WtpComponent> {
 
     GenerateEclipseWtpComponent() {
         xmlTransformer.indentation = "\t"
+        component = services.get(Instantiator).newInstance(EclipseWtpComponent, project, new XmlFileContentMerger(xmlTransformer))
     }
 
     @Override protected WtpComponent create() {
@@ -44,118 +51,157 @@ class GenerateEclipseWtpComponent extends XmlGeneratorTask<WtpComponent> {
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.sourceDirs. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * The source directories to be transformed into wb-resource elements.
      */
     Set<File> getSourceDirs() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.sourceDirs", "eclipse.wtp.component.sourceDirs")
         component.sourceDirs
     }
 
     void setSourceDirs(Set<File> sourceDirs) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.sourceDirs", "eclipse.wtp.component.sourceDirs")
         component.sourceDirs = sourceDirs
     }
 
     /**
-     * The configurations whose files are to be transformed into dependent-module elements.
+     * Deprecated. Please use #eclipse.wtp.component.plusConfigurations. See examples in {@link EclipseWtpComponent}.
      */
     Set<Configuration> getPlusConfigurations() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.plusConfigurations", "eclipse.wtp.component.plusConfigurations")
         component.plusConfigurations
     }
 
     void setPlusConfigurations(Set<Configuration> plusConfigurations) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.plusConfigurations", "eclipse.wtp.component.plusConfigurations")
         component.plusConfigurations = plusConfigurations
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.minusConfigurations. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * The configurations whose files are to be excluded from dependent-module elements.
      */
     Set<Configuration> getMinusConfigurations() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.minusConfigurations", "eclipse.wtp.component.minusConfigurations")
         component.minusConfigurations
     }
 
     void setMinusConfigurations(Set<Configuration> minusConfigurations) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.minusConfigurations", "eclipse.wtp.component.minusConfigurations")
         component.minusConfigurations = minusConfigurations
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.deployName. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * The deploy name to be used.
      */
     String getDeployName() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.deployName", "eclipse.wtp.component.deployName")
         component.deployName
     }
 
     void setDeployName(String deployName) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.deployName", "eclipse.wtp.component.deployName")
         component.deployName = deployName
     }
 
     /**
+     * Deprecated. Please use #eclipse.pathVariables. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * The variables to be used for replacing absolute path in dependent-module elements.
      */
     Map<String, File> getVariables() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.variables", "eclipse.pathVariables")
         component.pathVariables
     }
 
     void setVariables(Map<String, File> variables) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.variables", "eclipse.pathVariables")
         component.pathVariables = variables
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.resources. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * Additional wb-resource elements.
      */
     List<WbResource> getResources() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.resources", "eclipse.wtp.component.resources")
         component.resources
     }
 
     void setResources(List<WbResource> resources) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.resources", "eclipse.wtp.component.resources")
         component.resources = resources
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.properties. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * Additional property elements.
      */
     List<WbProperty> getProperties() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.properties", "eclipse.wtp.component.properties")
         component.properties
     }
 
     void setProperties(List<WbProperty> properties) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.properties", "eclipse.wtp.component.properties")
         component.properties = properties
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.contextPath. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * The context path for the web application
      */
     String getContextPath() {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.contextPath", "eclipse.wtp.component.contextPath")
         component.contextPath
     }
 
     void setContextPath(String contextPath) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.contextPath", "eclipse.wtp.component.contextPath")
         component.contextPath = contextPath
     }
 
     /**
+     * Deprecated. Please use #eclipse.pathVariables. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * Adds variables to be used for replacing absolute path in dependent-module elements.
      *
      * @param variables A map where the keys are the variable names and the values are the variable values.
      */
     void variables(Map<String, File> variables) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.variables", "eclipse.pathVariables")
         assert variables != null
         component.pathVariables.putAll variables
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.property. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * Adds a property.
      *
      * @param args A map that must contain a name and value key with corresponding values.
      */
     void property(Map<String, String> args) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.property", "eclipse.wtp.component.property")
         component.property(args)
     }
 
     /**
+     * Deprecated. Please use #eclipse.wtp.component.resource. See examples in {@link EclipseWtpComponent}.
+     * <p>
      * Adds a wb-resource.
      *
      * @param args A map that must contain a deployPath and sourcePath key with corresponding values.
      */
     void resource(Map<String, String> args) {
+        DeprecationLogger.nagUserOfReplacedMethod("eclipseWtpComponent.resource", "eclipse.wtp.component.resource")
         component.resource(args)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
index c153008..6487e01 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2008 the original author or authors.
+ * Copyright 2011 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.
@@ -15,13 +15,17 @@
  */
 package org.gradle.plugins.ide.eclipse
 
+import org.gradle.api.internal.Instantiator
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet
-import org.gradle.plugins.ide.eclipse.model.Facet
 import org.gradle.plugins.ide.eclipse.model.WtpFacet
 
 /**
  * Generates the org.eclipse.wst.common.project.facet.core settings file for Eclipse WTP.
+ * If you want to fine tune the eclipse configuration
+ * <p>
+ * At this moment nearly all configuration is done via {@link EclipseWtpFacet}.
  *
  * @author Hans Dockter
  */
@@ -31,6 +35,7 @@ class GenerateEclipseWtpFacet extends XmlGeneratorTask<WtpFacet> {
 
     GenerateEclipseWtpFacet() {
         xmlTransformer.indentation = "\t"
+        facet = services.get(Instantiator).newInstance(EclipseWtpFacet, new XmlFileContentMerger(xmlTransformer))
     }
 
     @Override protected WtpFacet create() {
@@ -41,23 +46,4 @@ class GenerateEclipseWtpFacet extends XmlGeneratorTask<WtpFacet> {
         facet.mergeXmlFacet(xmlFacet)
     }
 
-    /**
-     * The facets to be added as elements.
-     */
-    List<Facet> getFacets() {
-        facet.facets
-    }
-
-    void setFacets(List<Facet> facets) {
-        facet.facets = facets
-    }
-
-    /**
-     * Adds a facet.
-     *
-     * @param args A map that must contain a 'name' and 'version' key with corresponding values.
-     */
-    void facet(Map<String, ?> args) {
-        facet.facet(args)
-    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy
index a619e2a..6907897 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/EclipseNameDeduper.groovy
@@ -25,8 +25,8 @@ import org.gradle.plugins.ide.internal.configurer.ProjectDeduper
  */
 class EclipseNameDeduper {
 
-    void configure(Project aProject) {
-        def eclipseProjects = aProject.rootProject.allprojects.findAll { it.plugins.hasPlugin(EclipsePlugin) }
+    void configureRoot(Project rootProject) {
+        def eclipseProjects = rootProject.allprojects.findAll { it.plugins.hasPlugin(EclipsePlugin) }
         new ProjectDeduper().dedupe(eclipseProjects, { project ->
             new DeduplicationTarget(project: project,
                     moduleName: project.eclipseProject.projectModel.name,
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy
new file mode 100644
index 0000000..017c677
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/internal/LinkedResourcesCreator.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.ide.eclipse.internal
+
+import org.gradle.plugins.ide.eclipse.model.Link
+import org.gradle.plugins.ide.eclipse.model.internal.SourceFoldersCreator
+import org.gradle.api.Project
+
+/**
+ * @author: Szczepan Faber, created at: 4/22/11
+ */
+class LinkedResourcesCreator {
+
+    Set<Link> links(Project project) {
+        def folders = new SourceFoldersCreator().getExternalSourceFolders(project.sourceSets, {project.relativePath(it)} )
+        folders.collect {
+            new Link(it.name, '2', it.absolutePath, null)
+        } as Set
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy
index a15cce3..1c12689 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractClasspathEntry.groovy
@@ -23,11 +23,13 @@ import org.gradle.plugins.ide.eclipse.model.internal.PathUtil
 // TODO: consider entryAttributes in equals, hashCode, and toString
 abstract class AbstractClasspathEntry implements ClasspathEntry {
     private static final String NATIVE_LIBRARY_ATTRIBUTE = 'org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY'
+    public static final String COMPONENT_NON_DEPENDENCY_ATTRIBUTE = 'org.eclipse.jst.component.nondependency'
+    public static final String COMPONENT_DEPENDENCY_ATTRIBUTE = 'org.eclipse.jst.component.dependency'
 
     String path
     boolean exported
-    final Set<AccessRule> accessRules
-    final Map entryAttributes
+    Set<AccessRule> accessRules
+    final Map<String, Object> entryAttributes
 
     AbstractClasspathEntry(Node node) {
         path = normalizePath(node. at path)
@@ -37,13 +39,12 @@ abstract class AbstractClasspathEntry implements ClasspathEntry {
         assert path != null && accessRules != null
     }
 
-    AbstractClasspathEntry(String path, boolean exported, String nativeLibraryLocation, Set accessRules) {
-        assert path != null && accessRules != null
+    AbstractClasspathEntry(String path) {
+        assert path != null
         this.path = normalizePath(path);
-        this.exported = exported
-        this.accessRules = accessRules
+        this.exported = false
+        this.accessRules = [] as Set
         entryAttributes = [:]
-        this.nativeLibraryLocation = nativeLibraryLocation
     }
 
     String getNativeLibraryLocation() {
@@ -58,7 +59,7 @@ abstract class AbstractClasspathEntry implements ClasspathEntry {
         addClasspathEntry(node, [:])
     }
 
-    protected Node addClasspathEntry(Node node, Map attributes) {
+    protected Node addClasspathEntry(Node node, Map<String, ?> attributes) {
         def allAttributes = attributes.findAll { it.value } + [kind: getKind(), path: path]
         if (exported && !(this instanceof SourceFolder)) {
             allAttributes.exported = true
@@ -103,9 +104,16 @@ abstract class AbstractClasspathEntry implements ClasspathEntry {
     }
 
     void writeEntryAttributes(Node node) {
-        def effectiveEntryAttrs = entryAttributes.findAll { it.value }
+        def effectiveEntryAttrs = entryAttributes.findAll { it.value || it.key == COMPONENT_NON_DEPENDENCY_ATTRIBUTE }
         if (!effectiveEntryAttrs) { return }
 
+        if (effectiveEntryAttrs.containsKey(COMPONENT_DEPENDENCY_ATTRIBUTE)
+                && effectiveEntryAttrs.containsKey(COMPONENT_NON_DEPENDENCY_ATTRIBUTE)) {
+            //For conflicting component dependency entries, the non-dependency loses
+            //because it is our default and it means the user has configured something else.
+            effectiveEntryAttrs.remove(COMPONENT_NON_DEPENDENCY_ATTRIBUTE)
+        }
+
         Node attributesNode = node.children().find { it.name()  == 'attributes' } ?: node.appendNode('attributes')
         effectiveEntryAttrs.each { key, value ->
             attributesNode.appendNode('attribute', [name: key, value: value])
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
index c7d8949..4cdbf14 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
@@ -15,40 +15,45 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+
 /**
  * @author Hans Dockter
  */
 abstract class AbstractLibrary extends AbstractClasspathEntry {
-    String sourcePath
+    FileReference sourcePath
+    FileReference javadocPath
+    FileReference library
+    String declaredConfigurationName
 
-    AbstractLibrary(Node node) {
+    AbstractLibrary(Node node, FileReferenceFactory fileReferenceFactory) {
         super(node)
-        sourcePath = normalizePath(node. at sourcepath)
+        javadocPath = fileReferenceFactory.fromJarURI(entryAttributes.javadoc_location)
     }
 
-    String getJavadocPath() {
-        normalizePath(entryAttributes.javadoc_location)
+    AbstractLibrary(FileReference library) {
+        super(library.path)
+        this.library = library
     }
 
-    void setJavadocPath(String path) {
-        entryAttributes.javadoc_location = path
+    void setLibrary(FileReference library) {
+        this.library = library
+        path = library.path
     }
 
-    AbstractLibrary(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String sourcePath,
-                        String javadocPath) {
-        super(path, exported, nativeLibraryLocation, accessRules)
-        this.sourcePath = normalizePath(sourcePath);
-        this.javadocPath = normalizePath(javadocPath);
+    void setJavadocPath(FileReference path) {
+        this.javadocPath = path
+        entryAttributes.javadoc_location = path ? path.jarURL : null
     }
 
     void appendNode(Node node) {
-        addClasspathEntry(node, [sourcepath: sourcePath])
+        addClasspathEntry(node, [sourcepath: sourcePath?.path])
     }
 
     boolean equals(o) {
         if (this.is(o)) { return true }
 
-        if (getClass() != o.class) { return false }
+        if (o == null || getClass() != o.class) { return false }
 
         AbstractLibrary that = (AbstractLibrary) o;
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
index 9c6892f..8d0fa40 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Classpath.groovy
@@ -17,6 +17,7 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.internal.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
 /**
  * Represents the customizable elements of an eclipse classpath file. (via XML hooks everything is customizable).
@@ -24,10 +25,12 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
  * @author Hans Dockter
  */
 class Classpath extends XmlPersistableConfigurationObject {
+    private final FileReferenceFactory fileReferenceFactory
     List<ClasspathEntry> entries = []
 
-    Classpath(XmlTransformer xmlTransformer) {
+    Classpath(XmlTransformer xmlTransformer, FileReferenceFactory fileReferenceFactory) {
         super(xmlTransformer)
+        this.fileReferenceFactory = fileReferenceFactory
     }
 
     Classpath() {
@@ -46,11 +49,11 @@ class Classpath extends XmlPersistableConfigurationObject {
                     def path = entryNode. at path
                     entry = path.startsWith('/') ? new ProjectDependency(entryNode) : new SourceFolder(entryNode)
                     break
-                case 'var': entry = new Variable(entryNode)
+                case 'var': entry = new Variable(entryNode, fileReferenceFactory)
                     break
                 case 'con': entry = new Container(entryNode)
                     break
-                case 'lib': entry = new Library(entryNode)
+                case 'lib': entry = new Library(entryNode, fileReferenceFactory)
                     break
                 case 'output': entry = new Output(entryNode)
                     break
@@ -99,6 +102,6 @@ class Classpath extends XmlPersistableConfigurationObject {
     }
 
     private boolean isDependency(ClasspathEntry entry) {
-        entry instanceof ProjectDependency || entry instanceof Library
+        entry instanceof ProjectDependency || entry instanceof AbstractLibrary
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy
index a05a243..2bd8c7e 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Container.groovy
@@ -19,12 +19,12 @@ package org.gradle.plugins.ide.eclipse.model
  * @author Hans Dockter
  */
 class Container extends AbstractClasspathEntry {
-    Container(node) {
-        super(node);
+    Container(Node node) {
+        super(node)
     }
 
-    Container(String path, boolean exported, String nativeLibraryLocation, Set accessRules) {
-        super(path, exported, nativeLibraryLocation, accessRules)
+    Container(String path) {
+        super(path)
     }
 
     String getKind() {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseClasspath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseClasspath.groovy
index 86aafbc..85c3df9 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseClasspath.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseClasspath.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -17,10 +17,14 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.tasks.SourceSet
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.eclipse.model.internal.ClasspathFactory
+import org.gradle.plugins.ide.eclipse.model.internal.ExportedEntriesUpdater
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+import org.gradle.util.ConfigureUtil
 
 /**
- * DSL-friendly model of the eclipse classpath needed for .classpath generation
+ * Enables fine-tuning classpath details (.classpath file) of the Eclipse plugin
  * <p>
  * Example of use with a blend of all possible properties.
  * Bear in mind that usually you don't have configure eclipse classpath directly because Gradle configures it for free!
@@ -39,23 +43,21 @@ import org.gradle.plugins.ide.eclipse.model.internal.ClasspathFactory
  *   //if you want parts of paths in resulting file to be replaced by variables (files):
  *   pathVariables 'GRADLE_HOME': file('/best/software/gradle'), 'TOMCAT_HOME': file('../tomcat')
  *
- *
  *   classpath {
- *     //you can configure the sourceSets however Gradle simply uses current sourceSets
- *     //so it's probably best not to change it.
- *     //sourceSets =
- *
  *     //you can tweak the classpath of the eclipse project by adding extra configurations:
  *     plusConfigurations += configurations.provided
  *
  *     //you can also remove configurations from the classpath:
  *     minusConfigurations += configurations.someBoringConfig
  *
+ *     //if you don't want some classpath entries 'exported' in eclipse
+ *     noExportConfigurations += configurations.provided
+ *
  *     //if you want to append extra containers:
  *     containers 'someFriendlyContainer', 'andYetAnotherContainer'
  *
  *     //customizing the classes output directory:
- *     classesOutputDir = file('build-eclipse')
+ *     defaultOutputDir = file('build-eclipse')
  *
  *     //default settings for dependencies sources/javadoc download:
  *     downloadSources = true
@@ -64,7 +66,44 @@ import org.gradle.plugins.ide.eclipse.model.internal.ClasspathFactory
  * }
  * </pre>
  *
- * Author: Szczepan Faber, created at: 4/16/11
+ * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * It is also possible to affect the way eclipse plugin merges the existing configuration
+ * via beforeMerged and whenMerged closures.
+ * <p>
+ * beforeMerged and whenMerged closures receive {@link Classpath} object
+ * <p>
+ * Examples of advanced configuration:
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'eclipse'
+ *
+ * eclipse {
+ *   classpath {
+ *     file {
+ *       //if you want to mess with the resulting xml in whatever way you fancy
+ *       withXml {
+ *         def node = it.asNode()
+ *         node.appendNode('xml', 'is what I love')
+ *       }
+ *
+ *       //closure executed after .classpath content is loaded from existing file
+ *       //but before gradle build information is merged
+ *       beforeMerged { classpath ->
+ *         //you can tinker with the {@link Classpath} here
+ *       }
+ *
+ *       //closure executed after .classpath content is loaded from existing file
+ *       //and after gradle build information is merged
+ *       whenMerged { classpath ->
+ *         //you can tinker with the {@link Classpath} here
+ *       }
+ *     }
+ *   }
+ * }
+ * </pre>
+ *
+ * @author Szczepan Faber, created at: 4/16/11
  */
 class EclipseClasspath {
 
@@ -89,14 +128,22 @@ class EclipseClasspath {
      */
     Collection<Configuration> minusConfigurations = []
 
-   /**
+    /**
+     * The included configurations (plusConfigurations) which files will not be exported.
+     * Only make sense if those configurations are also a part of {@link #plusConfigurations}
+     * <p>
+     * For example see docs for {@link EclipseClasspath}
+     */
+    Collection<Configuration> noExportConfigurations = []
+
+    /**
      * Containers to be added to the classpath
      * <p>
      * For example see docs for {@link EclipseClasspath}
      */
     Set<String> containers = new LinkedHashSet<String>()
 
-   /**
+    /**
      * Adds containers to the .classpath.
      * <p>
      * For example see docs for {@link EclipseClasspath}
@@ -109,11 +156,11 @@ class EclipseClasspath {
     }
 
     /**
-     * The default output directory for eclipse generated files, eg classes.
+     * The default output directory where eclipse puts compiled classes
      * <p>
      * For example see docs for {@link EclipseClasspath}
      */
-    File classesOutputDir
+    File defaultOutputDir
 
     /**
      * Whether to download and add sources associated with the dependency jars. Defaults to true.
@@ -130,38 +177,54 @@ class EclipseClasspath {
     boolean downloadJavadoc = false
 
     /**
-     * Calculates, resolves & returns dependency entries of this classpath
+     * Enables advanced configuration like tinkering with the output xml
+     * or affecting the way existing .classpath content is merged with gradle build information
+     * <p>
+     * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link Classpath}
+     * <p>
+     *
+     * For example see docs for {@link EclipseProject}
      */
-    public List<ClasspathEntry> resolveDependencies() {
-        return new ClasspathFactory().createEntries(this)
+    void file(Closure closure) {
+        ConfigureUtil.configure(closure, file)
     }
 
-    /******/
-
-    org.gradle.api.Project project
-
     /**
-     * The path variables to be used for replacing absolute paths in classpath entries.
-     * <p>
-     * For example see docs for {@link EclipseModel}
+     * See {@link #file(Closure)}
      */
+    XmlFileContentMerger file
+
+    /** ****/
+
+    final org.gradle.api.Project project
     Map<String, File> pathVariables = [:]
+    boolean projectDependenciesOnly = false
+
+    List<File> classFolders
+
+    EclipseClasspath(org.gradle.api.Project project) {
+        this.project = project
+    }
 
     /**
-     * Modifies the content of plusConfigurations and minusConfigurations by filtering dependencies
-     *
-     * @param projectDependenciesOnly true - only project dependencies, false - no filter
+     * Calculates, resolves & returns dependency entries of this classpath
      */
-    public void setProjectDependenciesOnly(boolean projectDependenciesOnly) {
-        if (projectDependenciesOnly) {
-            onlyProject = { it instanceof org.gradle.api.artifacts.ProjectDependency }
-            plusConfigurations = plusConfigurations.collect { it.copyRecursive { dependency -> onlyProject(dependency) }}
-            minusConfigurations = minusConfigurations.collect { it.copyRecursive { dependency -> onlyProject(dependency) }}
-        }
+    public List<ClasspathEntry> resolveDependencies() {
+        def entries = new ClasspathFactory().createEntries(this)
+        new ExportedEntriesUpdater().updateExported(entries, this.noExportConfigurations*.name)
+        return entries
     }
 
     void mergeXmlClasspath(Classpath xmlClasspath) {
+        file.beforeMerged.execute(xmlClasspath)
         def entries = resolveDependencies()
         xmlClasspath.configure(entries)
+        file.whenMerged.execute(xmlClasspath)
+    }
+
+    FileReferenceFactory getFileReferenceFactory() {
+        def referenceFactory = new FileReferenceFactory()
+        pathVariables.each { name, dir -> referenceFactory.addPathVariable(name, dir) }
+        return referenceFactory
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy
index f050772..b6f81f1 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseJdt.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -17,11 +17,42 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.JavaVersion
+import org.gradle.plugins.ide.api.PropertiesFileContentMerger
+import org.gradle.util.ConfigureUtil
 
 /**
- * Models the java compatibility information
- * <p>
- * For example see docs for {@link EclipseProject}
+ * Enables fine-tuning jdt details of the Eclipse plugin
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'eclipse'
+ *
+ * eclipse {
+ *   jdt {
+ *     //if you want to alter the java versions (by default they are configured with gradle java plugin settings):
+ *     sourceCompatibility = 1.6
+ *     targetCompatibility = 1.5
+ *
+ *     file {
+ *       //whenMerged closure is the highest voodoo
+ *       //and probably should be used only to solve tricky edge cases.
+ *       //the type passed to the closure is {@link Jdt}
+ *
+ *       //closure executed after jdt file content is loaded from existing file
+ *       //and after gradle build information is merged
+ *       whenMerged { jdt
+ *         //you can tinker with the {@link Jdt} here
+ *       }
+ *       
+ *       //withProperties allows addition of properties not currently
+ *       //modeled by Gradle
+ *       withProperties { properties ->
+ *           //you can tinker with the {@link java.util.Properties} here
+ *       }
+ *     }
+ *   }
+ * }
+ * </pre>
  *
  * @author: Szczepan Faber, created at: 4/20/11
  */
@@ -30,7 +61,7 @@ class EclipseJdt {
     /**
      * The source Java language level.
      * <p>
-     * For example see docs for {@link EclipseProject}
+     * For example see docs for {@link EclipseJdt}
      */
     JavaVersion sourceCompatibility
 
@@ -41,11 +72,34 @@ class EclipseJdt {
     /**
      * The target JVM to generate {@code .class} files for.
      * <p>
-     * For example see docs for {@link EclipseProject}
+     * For example see docs for {@link EclipseJdt}
      */
     JavaVersion targetCompatibility
 
     void setTargetCompatibility(Object targetCompatibility) {
         this.targetCompatibility = JavaVersion.toVersion(targetCompatibility)
     }
+
+    /**
+     * Enables advanced configuration like affecting the way existing jdt file content
+     * is merged with gradle build information
+     * <p>
+     * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link Jdt}
+     * <p>
+     * The object passed to withProperties{} closures is of type {@link java.util.Properties}
+     * <p>
+     * For example see docs for {@link EclipseJdt}
+     */
+    void file(Closure closure) {
+        ConfigureUtil.configure(closure, file)
+    }
+
+    /**
+     * See {@link #file(Closure) }
+     */
+    final PropertiesFileContentMerger file
+    
+    EclipseJdt(PropertiesFileContentMerger file) {
+        this.file = file
+    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModel.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModel.groovy
index 25ab494..e7333bb 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModel.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModel.groovy
@@ -49,14 +49,36 @@ import org.gradle.util.ConfigureUtil
  */
 class EclipseModel {
 
+    /**
+     * Configures eclipse project information
+     * <p>
+     * For examples see docs for {@link EclipseProject}
+     */
     EclipseProject project
+
+    /**
+     * Configures eclipse classpath information
+     * <p>
+     * For examples see docs for {@link EclipseClasspath}
+     */
     EclipseClasspath classpath
+
+    /**
+     * Configures eclipse java compatibility information (jdt)
+     * <p>
+     * For examples see docs for {@link EclipseProject}
+     */
     EclipseJdt jdt
 
-    EclipseWtp wtp = new EclipseWtp()
+    /**
+     * Configures eclipse wtp information
+     * <p>
+     * For examples see docs for {@link EclipseWtp}
+     */
+    EclipseWtp wtp
 
     /**
-     * Configures eclipse project
+     * Configures eclipse project information
      * <p>
      * For examples see docs for {@link EclipseProject}
      *
@@ -102,6 +124,9 @@ class EclipseModel {
     /**
      * Adds path variables to be used for replacing absolute paths in classpath entries.
      * <p>
+     * If the beginning of the absolute path of a library or other path-related element matches a value of a variable,
+     * a variable entry is used. The matching part of the library path is replaced with the variable name.
+     * <p>
      * For example see docs for {@link EclipseModel}
      *
      * @param pathVariables A map with String->File pairs.
@@ -109,7 +134,7 @@ class EclipseModel {
     void pathVariables(Map<String, File> pathVariables) {
         assert pathVariables != null
         classpath.pathVariables.putAll pathVariables
-        if (wtp.component) {
+        if (wtp && wtp.component) {
             wtp.component.pathVariables.putAll pathVariables
         }
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy
index 0809f70..7e5d2f3 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProject.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -16,9 +16,11 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.InvalidUserDataException
+import org.gradle.plugins.ide.api.XmlFileContentMerger
+import org.gradle.util.ConfigureUtil
 
 /**
- * DSL-friendly model of the eclipse project needed for .project generation
+ * Enables fine-tuning project details (.project file) of the Eclipse plugin
  * <p>
  * Example of use with a blend of all possible properties.
  * Bear in mind that usually you don't have configure eclipse project directly because Gradle configures it for free!
@@ -48,7 +50,7 @@ import org.gradle.api.InvalidUserDataException
  *     //if you want to append some extra build command:
  *     buildCommand 'buildThisLovelyProject'
  *     //if you want to append a build command with parameters:
- *     buildCommand argumentOne: "I'm first", argumentTwo: "I'm second", 'buildItWithTheArguments'
+ *     buildCommand 'buildItWithTheArguments', argumentOne: "I'm first", argumentTwo: "I'm second"
  *
  *     //if you want to create an extra link in the eclipse project,
  *     //by location uri:
@@ -56,16 +58,49 @@ import org.gradle.api.InvalidUserDataException
  *     //by location:
  *     linkedResource name: 'someLinkByLocation', type: 'someLinkType', location: '/some/location'
  *   }
+ * }
+ * </pre>
+ *
+ * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * It is also possible to affect the way eclipse plugin merges the existing configuration
+ * via beforeMerged and whenMerged closures.
+ * <p>
+ * beforeMerged and whenMerged closures receive {@link Project} object
+ * <p>
+ * Examples of advanced configuration:
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'eclipse'
+ *
+ * eclipse {
+ *   project {
  *
- *   jdt {
- *     //if you want to alter the java versions (by default they are configured with gradle java plugin settings):
- *     sourceCompatibility = 1.6
- *     targetCompatibility = 1.5
+ *     file {
+ *       //if you want to mess with the resulting xml in whatever way you fancy
+ *       withXml {
+ *         def node = it.asNode()
+ *         node.appendNode('xml', 'is what I love')
+ *       }
+ *
+ *       //closure executed after .project content is loaded from existing file
+ *       //but before gradle build information is merged
+ *       beforeMerged { project ->
+ *         //if you want skip merging natures... (a very abstract example)
+ *         project.natures.clear()
+ *       }
+ *
+ *       //closure executed after .project content is loaded from existing file
+ *       //and after gradle build information is merged
+ *       whenMerged { project ->
+ *         //you can tinker with the {@link Project} here
+ *       }
+ *     }
  *   }
  * }
  * </pre>
  *
- * Author: Szczepan Faber, created at: 4/13/11
+ * @author Szczepan Faber, created at: 4/13/11
  */
 class EclipseProject {
 
@@ -73,10 +108,19 @@ class EclipseProject {
      * Configures eclipse project name. It is <b>optional</b> because the task should configure it correctly for you.
      * By default it will try to use the <b>project.name</b> or prefix it with a part of a <b>project.path</b>
      * to make sure the moduleName is unique in the scope of a multi-module build.
-     * The 'uniqeness' of a module name is required for correct import
+     * The 'uniqueness' of a module name is required for correct import
      * into Eclipse and the task will make sure the name is unique.
      * <p>
-     * The logic that makes sure project names are uniqe is available <b>since</b> 1.0-milestone-2
+     * The logic that makes sure project names are unique is available <b>since</b> 1.0-milestone-2
+     * <p>
+     * If your project has problems with unique names it is recommended to always run gradle eclipse from the root, e.g. for all subprojects, including generation of .classpath.
+     * If you run the generation of the eclipse project only for a single subproject then you may have different results
+     * because the unique names are calculated based on eclipse projects that are involved in the specific build run.
+     * <p>
+     * If you update the project names then make sure you run gradle eclipse from the root, e.g. for all subprojects.
+     * The reason is that there may be subprojects that depend on the subproject with amended eclipse project name.
+     * So you want them to be generated as well because the project dependencies in .classpath need to refer to the amended project name.
+     * Basically, for non-trivial projects it is recommended to always run gradle eclipse from the root.
      * <p>
      * For example see docs for {@link EclipseProject}
      */
@@ -90,14 +134,22 @@ class EclipseProject {
     String comment
 
     /**
-     * The referenced projects of this Eclipse project.
+     * The referenced projects of this Eclipse project (*not*: java build path project references).
+     * <p>
+     * Referencing projects does not mean adding a build path dependencies between them!
+     * If you need to configure a build path dependency use Gradle's dependencies section or
+     * eclipse.classpath.whenMerged { classpath -> ... to manipulate the classpath entries
      * <p>
      * For example see docs for {@link EclipseProject}
      */
     Set<String> referencedProjects = new LinkedHashSet<String>()
 
     /**
-     * Adds project references to the eclipse project.
+     * The referenced projects of this Eclipse project (*not*: java build path project references).
+     * <p>
+     * Referencing projects does not mean adding a build path dependencies between them!
+     * If you need to configure a build path dependency use Gradle's dependencies section or
+     * eclipse.classpath.whenMerged { classpath -> ... to manipulate the classpath entries
      *
      * @param referencedProjects The name of the project references.
      */
@@ -179,13 +231,36 @@ class EclipseProject {
         if (illegalArgs) {
             throw new InvalidUserDataException("You provided illegal argument for a link: $illegalArgs. Valid link args are: $validKeys")
         }
-        //TODO SF: move validation here, update tests
         linkedResources << new Link(args.name, args.type, args.location, args.locationUri)
     }
 
+    /**
+     * Enables advanced configuration like tinkering with the output xml
+     * or affecting the way existing .project content is merged with gradle build information
+     * <p>
+     * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link Project}
+     * <p>
+     *
+     * For example see docs for {@link EclipseProject}
+     */
+    void file(Closure closure) {
+        ConfigureUtil.configure(closure, file)
+    }
+
+    /**
+     * See {@link #file(Closure) }
+     */
+    final XmlFileContentMerger file
+
     /*****/
 
+    EclipseProject(XmlFileContentMerger file) {
+        this.file = file
+    }
+
     void mergeXmlProject(Project xmlProject) {
+        file.beforeMerged.execute(xmlProject)
         xmlProject.configure(this)
+        file.whenMerged.execute(xmlProject)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy
index 7432497..8d65c5c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtp.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -19,20 +19,14 @@ package org.gradle.plugins.ide.eclipse.model
 import org.gradle.util.ConfigureUtil
 
 /**
- * Dsl-friendly model of the eclipse wtp information
+ * Enables fine-tuning wtp/wst details of the Eclipse plugin
  * <p>
- * Example of use with a blend of all possible properties.
- * Bear in mind that usually you don't have configure them directly because Gradle configures it for free!
+ * More interesting examples you will find in docs for {@link EclipseWtpComponent} and {@link EclipseWtpFacet}
  *
  * <pre autoTested=''>
  * apply plugin: 'java'
  * apply plugin: 'war'
- * apply plugin: 'eclipse'
- *
- * configurations {
- *   someInterestingConfiguration
- *   anotherConfiguration
- * }
+ * apply plugin: 'eclipse-wtp'
  *
  * eclipse {
  *
@@ -41,31 +35,11 @@ import org.gradle.util.ConfigureUtil
  *
  *   wtp {
  *     component {
- *       //you can configure the context path:
- *       contextPath = 'someContextPath'
- *
- *       //you can configure the deployName:
- *       deployName = 'killerApp'
- *
- *       //you can alter the wb-resource elements. sourceDirs is a ConvenienceProperty.
- *       sourceDirs += file('someExtraFolder')
- *
- *       //you can alter the files are to be transformed into dependent-module elements:
- *       plusConfigurations += configurations.someInterestingConfiguration
- *
- *       //or whose files are to be excluded from dependent-module elements:
- *       minusConfigurations += configurations.anotherConfiguration
- *
- *       //you can add a wb-resource elements; mandatory keys: 'sourcePath', 'deployPath':
- *       resource sourcePath: 'extra/resource', deployPath: 'deployment/resource'
- *
- *       //you can add a wb-property elements; mandatory keys: 'name', 'value':
- *       property name: 'moodOfTheDay', value: ':-D'
+ *       //for examples see docs for {@link EclipseWtpComponent}
  *     }
  *
  *     facet {
- *       //you can add some extra wtp facets; mandatory keys: 'name', 'version':
- *       facet name: 'someCoolFacet', version: '1.3'
+ *       //for examples see docs for {@link EclipseWtpFacet}
  *     }
  *   }
  * }
@@ -76,13 +50,24 @@ import org.gradle.util.ConfigureUtil
  */
 class EclipseWtp {
 
+    /**
+     * Configures wtp component.
+     * <p>
+     * For examples see docs for {@link EclipseWtpComponent}
+     */
     EclipseWtpComponent component
+
+    /**
+     * Configures wtp facet.
+     * <p>
+     * For examples see docs for {@link EclipseWtpFacet}
+     */
     EclipseWtpFacet facet
 
     /**
      * Configures wtp component.
      * <p>
-     * For examples see docs for {@link EclipseWtp}
+     * For examples see docs for {@link EclipseWtpComponent}
      *
      * @param action
      */
@@ -93,11 +78,26 @@ class EclipseWtp {
     /**
      * Configures wtp facet.
      * <p>
-     * For examples see docs for {@link EclipseWtp}
+     * For examples see docs for {@link EclipseWtpFacet}
      *
      * @param action
      */
     void facet(Closure action) {
         ConfigureUtil.configure(action, facet)
     }
+
+    /**
+     * Deprecated. EclipseWtp needs access to EclipseClasspath. Please use the other constructor.
+     */
+    @Deprecated
+    public EclipseWtp() {}
+
+    /**
+     * @param eclipseClasspath - wtp needs access to classpath
+     */
+    public EclipseWtp(EclipseClasspath eclipseClasspath) {
+        this.eclipseClasspath = eclipseClasspath
+    }
+
+    private EclipseClasspath eclipseClasspath
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy
index 60fc191..6879b25 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpComponent.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -17,32 +17,140 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.dsl.ConventionProperty
+import org.gradle.plugins.ide.api.XmlFileContentMerger
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 import org.gradle.plugins.ide.eclipse.model.internal.WtpComponentFactory
+import org.gradle.util.ConfigureUtil
 
 /**
- * Models the information need for wtp component
+ * Enables fine-tuning wtp component details of the Eclipse plugin
  * <p>
- * For examples see docs for {@link EclipseWtp}
+ * Example of use with a blend of all possible properties.
+ * Bear in mind that usually you don't have configure them directly because Gradle configures it for free!
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'war'
+ * apply plugin: 'eclipse-wtp'
+ *
+ * configurations {
+ *   someInterestingConfiguration
+ *   anotherConfiguration
+ * }
+ *
+ * eclipse {
+ *
+ *   //if you want parts of paths in resulting file(s) to be replaced by variables (files):
+ *   pathVariables 'GRADLE_HOME': file('/best/software/gradle'), 'TOMCAT_HOME': file('../tomcat')
+ *
+ *   wtp {
+ *     component {
+ *       //you can configure the context path:
+ *       contextPath = 'someContextPath'
+ *
+ *       //you can configure the deployName:
+ *       deployName = 'killerApp'
+ *
+ *       //you can alter the wb-resource elements. sourceDirs is a ConventionProperty.
+ *       //non-existing source dirs won't be added to the component file.
+ *       sourceDirs += file('someExtraFolder')
+ *
+ *       //you can alter the files are to be transformed into dependent-module elements:
+ *       plusConfigurations += configurations.someInterestingConfiguration
+ *
+ *       //or whose files are to be excluded from dependent-module elements:
+ *       minusConfigurations += configurations.anotherConfiguration
+ *
+ *       //you can add a wb-resource elements; mandatory keys: 'sourcePath', 'deployPath':
+ *       //if sourcePath points to non-existing folder it will *not* be added.
+ *       resource sourcePath: 'extra/resource', deployPath: 'deployment/resource'
+ *
+ *       //you can add a wb-property elements; mandatory keys: 'name', 'value':
+ *       property name: 'moodOfTheDay', value: ':-D'
+ *     }
+ *   }
+ * }
+ * </pre>
+ *
+ * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * It is also possible to affect the way eclipse plugin merges the existing configuration
+ * via beforeMerged and whenMerged closures.
+ * <p>
+ * beforeMerged and whenMerged closures receive {@link WtpComponent} object
+ * <p>
+ * Examples of advanced configuration:
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'war'
+ * apply plugin: 'eclipse-wtp'
+ *
+ * eclipse {
+ *
+ *   wtp {
+ *     component {
+ *       file {
+ *         //if you want to mess with the resulting xml in whatever way you fancy
+ *         withXml {
+ *           def node = it.asNode()
+ *           node.appendNode('xml', 'is what I love')
+ *         }
+ *
+ *         //closure executed after wtp component file content is loaded from existing file
+ *         //but before gradle build information is merged
+ *         beforeMerged { wtpComponent ->
+ *           //tinker with {@link WtpComponent} here
+ *         }
+ *
+ *         //closure executed after wtp component file content is loaded from existing file
+ *         //and after gradle build information is merged
+ *         whenMerged { wtpComponent ->
+ *           //you can tinker with the {@link WtpComponent} here
+ *         }
+ *       }
+ *     }
+ *   }
+ * }
+ * </pre>
  *
  * @author: Szczepan Faber, created at: 4/20/11
  */
 class EclipseWtpComponent {
 
     /**
-     * The source directories to be transformed into wb-resource elements.
-     * <p>
-     * Warning, this property is a {@link org.gradle.api.dsl.ConvenienceProperty}
+     * {@link ConventionProperty} for the source directories to be transformed into wb-resource elements.
      * <p>
      * For examples see docs for {@link EclipseWtp}
+     * <p>
+     * Only source dirs that exist will be added to the wtp component file.
+     * Non-existing resource directory declarations lead to errors when project is imported into Eclipse.
      */
     Set<File> sourceDirs
 
     /**
-     * The configurations whose files are to be transformed into dependent-module elements.
+     * The configurations whose files are to be transformed into dependent-module elements with a deploy path of '/'.
      * <p>
      * For examples see docs for {@link EclipseWtp}
      */
-    Set<Configuration> plusConfigurations
+    Set<Configuration> rootConfigurations = []
+
+    /**
+     * The configurations whose files are to be transformed into dependent-module elements with a deploy path of #libDeployPath.
+     * <p>
+     * For examples see docs for {@link EclipseWtp}
+     */
+    Set<Configuration> libConfigurations
+
+    /**
+     * Synonym for {@link #libConfigurations}.
+     */
+    Set<Configuration> getPlusConfigurations() {
+        getLibConfigurations()
+    }
+    void setPlusConfigurations(Set<Configuration> plusConfigurations) {
+        setLibConfigurations(plusConfigurations)
+    }
 
     /**
      * The configurations whose files are to be excluded from dependent-module elements.
@@ -59,11 +167,14 @@ class EclipseWtpComponent {
     String deployName
 
     /**
-     * Additional wb-resource elements.
-     * <p>
-     * Warning, this property is a {@link org.gradle.api.dsl.ConvenienceProperty}
+     * {@link ConventionProperty} for additional wb-resource elements.
      * <p>
      * For examples see docs for {@link EclipseWtp}
+     * <p>
+     * Only resources that link to an existing directory ({@code WbResource#sourcePath})
+     * will be added to the wtp component file.
+     * The reason is that non-existing resource directory declarations
+     * lead to errors when project is imported into Eclipse.
      */
     List<WbResource> resources = []
 
@@ -75,7 +186,6 @@ class EclipseWtpComponent {
      * @param args A map that must contain a deployPath and sourcePath key with corresponding values.
      */
     void resource(Map<String, String> args) {
-        //TODO SF validation
         resources.add(new WbResource(args.deployPath, args.sourcePath))
     }
 
@@ -94,7 +204,6 @@ class EclipseWtpComponent {
      * @param args A map that must contain a 'name' and 'value' key with corresponding values.
      */
     void property(Map<String, String> args) {
-        //TODO SF validation
         properties.add(new WbProperty(args.name, args.value))
     }
 
@@ -105,9 +214,40 @@ class EclipseWtpComponent {
      */
     String contextPath
 
+    /**
+     * The deploy path for classes.
+     * <p>
+     * For examples see docs for {@link EclipseWtp}
+     */
+    String classesDeployPath = "/WEB-INF/classes"
+
+    /**
+     * The deploy path for libraries.
+     * <p>
+     * For examples see docs for {@link EclipseWtp}
+     */
+    String libDeployPath = "/WEB-INF/lib"
+
+    /**
+     * Enables advanced configuration like tinkering with the output xml
+     * or affecting the way existing wtp component file content is merged with gradle build information
+     * <p>
+     * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link WtpComponent}
+     * <p>
+     * For example see docs for {@link EclipseWtpComponent}
+     */
+    void file(Closure closure) {
+        ConfigureUtil.configure(closure, file)
+    }
+
+    /**
+     * See {@link #file(Closure) }
+     */
+    final XmlFileContentMerger file
+
     //********
 
-    org.gradle.api.Project project
+    final org.gradle.api.Project project
 
     /**
      * The variables to be used for replacing absolute path in dependent-module elements.
@@ -116,7 +256,20 @@ class EclipseWtpComponent {
      */
     Map<String, File> pathVariables = [:]
 
+    EclipseWtpComponent(org.gradle.api.Project project, XmlFileContentMerger file) {
+        this.project = project
+        this.file = file
+    }
+
     void mergeXmlComponent(WtpComponent xmlComponent) {
+        file.beforeMerged.execute(xmlComponent)
         new WtpComponentFactory().configure(this, xmlComponent)
+        file.whenMerged.execute(xmlComponent)
+    }
+
+    FileReferenceFactory getFileReferenceFactory() {
+        def referenceFactory = new FileReferenceFactory()
+        pathVariables.each { name, dir -> referenceFactory.addPathVariable(name, dir) }
+        return referenceFactory
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy
index 9aa1686..3f9f327 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/EclipseWtpFacet.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -16,12 +16,52 @@
 
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.util.ConfigureUtil
 
 /**
- * Models the information need for wtp component
+ * Enables fine-tuning wtp facet details of the Eclipse plugin
  * <p>
- * For examples see docs for {@link EclipseWtp}
+ * Advanced configuration closures beforeMerged and whenMerged receive {@link WtpFacet} object as parameter.
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'war'
+ * apply plugin: 'eclipse-wtp'
+ *
+ * eclipse {
+ *   wtp {
+ *     facet {
+ *       //you can add some extra wtp facets; mandatory keys: 'name', 'version':
+ *       facet name: 'someCoolFacet', version: '1.3'
+ *
+ *       file {
+ *         //if you want to mess with the resulting xml in whatever way you fancy
+ *         withXml {
+ *           def node = it.asNode()
+ *           node.appendNode('xml', 'is what I love')
+ *         }
+ *
+ *         //beforeMerged and whenMerged closures are the highest voodoo for the tricky edge cases.
+ *         //the type passed to the closures is {@link WtpFacet}
+ *
+ *         //closure executed after wtp facet file content is loaded from existing file
+ *         //but before gradle build information is merged
+ *         beforeMerged { wtpFacet ->
+ *           //tinker with {@link WtpFacet} here
+ *         }
+ *
+ *         //closure executed after wtp facet file content is loaded from existing file
+ *         //and after gradle build information is merged
+ *         whenMerged { wtpFacet ->
+ *           //you can tinker with the {@link WtpFacet} here
+ *         }
+ *       }
+ *     }
+ *   }
+ * }
+ *
+ * </pre>
  *
  * @author: Szczepan Faber, created at: 4/20/11
  */
@@ -30,15 +70,15 @@ class EclipseWtpFacet {
     /**
      * The facets to be added as elements.
      * <p>
-     * For examples see docs for {@link EclipseWtp}
+     * For examples see docs for {@link EclipseWtpFacet}
      */
-    // TODO: What's the difference between fixed and installed facets? Why do we only model the latter?
     List<Facet> facets = []
+    // TODO: What's the difference between fixed and installed facets? Why do we only model the latter?
 
     /**
      * Adds a facet.
      * <p>
-     * For examples see docs for {@link EclipseWtp}
+     * For examples see docs for {@link EclipseWtpFacet}
      *
      * @param args A map that must contain a 'name' and 'version' key with corresponding values.
      */
@@ -46,9 +86,33 @@ class EclipseWtpFacet {
         facets << ConfigureUtil.configureByMap(args, new Facet())
     }
 
-    /*****/
+    /**
+     * Enables advanced configuration like tinkering with the output xml
+     * or affecting the way existing wtp facet file content is merged with gradle build information
+     * <p>
+     * The object passed to whenMerged{} and beforeMerged{} closures is of type {@link WtpFacet}
+     * <p>
+     *
+     * For example see docs for {@link EclipseWtpFacet}
+     */
+    void file(Closure closure) {
+        ConfigureUtil.configure(closure, file)
+    }
+
+    /**
+     * See {@link #file(Closure) }
+     */
+    final XmlFileContentMerger file
+
+    //********
+
+    EclipseWtpFacet(XmlFileContentMerger file) {
+        this.file = file
+    }
 
     void mergeXmlFacet(WtpFacet xmlFacet) {
+        file.beforeMerged.execute(xmlFacet)
         xmlFacet.configure(getFacets())
+        file.whenMerged.execute(xmlFacet)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy
index e5f9542..facfc09 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Facet.groovy
@@ -20,24 +20,46 @@ package org.gradle.plugins.ide.eclipse.model
  */
 
 class Facet {
+
+    enum FacetType { installed, fixed }
+
+    FacetType type
     String name
     String version
 
     def Facet() {
+        type = FacetType.installed
     }
 
     def Facet(Node node) {
-        this(node. at facet, node. at version)
+        this(FacetType.valueOf(node.name()), node. at facet, node. at version)
     }
 
     def Facet(String name, String version) {
-        assert name != null && version != null
+        this(FacetType.installed, name, version)
+    }
+
+    def Facet(FacetType type, String name, String version) {
+        assert type != null && name != null
+        if (!type) {
+            type = FacetType.installed
+        }
+        if (type == FacetType.installed) {
+            assert version != null
+        } else {
+            assert version == null
+        }
+        this.type = type
         this.name = name
         this.version = version
     }
 
     void appendNode(Node node) {
-        node.appendNode("installed", [facet: name, version: version])
+        if (type == FacetType.installed) {
+            node.appendNode(type as String, [facet: name, version: version])
+        } else {
+            node.appendNode(type as String, [facet: name])
+        }
     }
 
     boolean equals(o) {
@@ -47,6 +69,7 @@ class Facet {
 
         Facet facet = (Facet) o;
 
+        if (type != facet.type) { return false }
         if (name != facet.name) { return false }
         if (version != facet.version) { return false }
 
@@ -56,13 +79,15 @@ class Facet {
     int hashCode() {
         int result;
 
-        result = name.hashCode();
-        result = 31 * result + version.hashCode();
+        result = type.hashCode();
+        result = 31 * result + name.hashCode();
+        result = 31 * result + ((version == null) ? 0 : version.hashCode());
         return result;
     }
 
     public String toString() {
         return "Facet{" +
+                "type='" + type + '\'' +
                 "name='" + name + '\'' +
                 ", version='" + version + '\'' +
                 '}';
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/FileReference.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/FileReference.java
new file mode 100644
index 0000000..a727784
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/FileReference.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.ide.eclipse.model;
+
+import java.io.File;
+
+/**
+ * A reference to a file in eclipse.
+ */
+public interface FileReference {
+    /**
+     * Returns the logical path for the file.
+     */
+    String getPath();
+
+    /**
+     * Returns the target file.
+     */
+    File getFile();
+
+    /**
+     * Returns the jar URL of the file
+     */
+    String getJarURL();
+    /**
+     * Returns true if this reference is relative to a path variable.
+     */
+    boolean isRelativeToPathVariable();
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Jdt.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Jdt.java
index ac00764..92c3eee 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Jdt.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Jdt.java
@@ -15,11 +15,12 @@
  */
 package org.gradle.plugins.ide.eclipse.model;
 
+import java.util.Properties;
+
 import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.PropertiesTransformer;
 import org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject;
 
-import java.util.Properties;
-
 /**
  * Represents the Eclipse JDT settings.
  */
@@ -27,6 +28,10 @@ public class Jdt extends PropertiesPersistableConfigurationObject {
     private JavaVersion sourceCompatibility;
     private JavaVersion targetCompatibility;
 
+    public Jdt(PropertiesTransformer transformer) {
+        super(transformer);
+    }
+    
     /**
      * Sets the source compatibility for the compiler.
      */
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy
index 2d2ffbf..55791f7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Library.groovy
@@ -15,16 +15,19 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+
 /**
  * @author Hans Dockter
  */
 class Library extends AbstractLibrary {
-    Library(Node node) {
-        super(node);
+    Library(Node node, FileReferenceFactory fileReferenceFactory) {
+        super(node, fileReferenceFactory)
+        sourcePath = fileReferenceFactory.fromPath(node. at sourcepath)
     }
 
-    Library(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String sourcePath, String javadocPath) {
-        super(path, exported, nativeLibraryLocation, accessRules, sourcePath, javadocPath)
+    Library(FileReference library) {
+        super(library)
     }
 
     String getKind() {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
index 8fd1980..bff57a5 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Project.groovy
@@ -56,24 +56,6 @@ class Project extends XmlPersistableConfigurationObject {
      */
     Set<Link> linkedResources = new LinkedHashSet<Link>()
 
-    /**
-     * The links to be added to this Eclipse project.
-     * <p>
-     * @deprecated Please use linkedResources
-     */
-    @Deprecated
-    Set<Link> getLinks() {
-        this.linkedResources
-    }
-
-    /**
-     * @deprecated Please use linkedResources
-     */
-    @Deprecated
-    void setLinks(Set<Link> links) {
-        this.linkedResources(links)
-    }
-
     def Project(XmlTransformer xmlTransformer) {
         super(xmlTransformer)
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy
index 0b60866..8837062 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependency.groovy
@@ -21,14 +21,15 @@ package org.gradle.plugins.ide.eclipse.model
 class ProjectDependency extends AbstractClasspathEntry {
 
     String gradlePath
+    String declaredConfigurationName
 
     ProjectDependency(Node node) {
         super(node)
         assertPathIsValid()
     }
 
-    ProjectDependency(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String gradlePath) {
-        super(path, exported, nativeLibraryLocation, accessRules)
+    ProjectDependency(String path, String gradlePath) {
+        super(path)
         assertPathIsValid()
         this.gradlePath = gradlePath
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy
index 0664b9a..02cafa7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolder.groovy
@@ -16,12 +16,16 @@
 package org.gradle.plugins.ide.eclipse.model
 
 /**
+ * SourceFolder.path contains only project relative path.
+ *
  * @author Hans Dockter
  */
 class SourceFolder extends AbstractClasspathEntry {
     String output
-    List includes
-    List excludes
+    List<String> includes
+    List<String> excludes
+    //optional
+    File dir
 
     SourceFolder(Node node) {
         super(node)
@@ -30,18 +34,29 @@ class SourceFolder extends AbstractClasspathEntry {
         this.excludes = node. at excluding?.split('\\|') ?: []
     }
 
-    SourceFolder(String path, String nativeLibraryLocation, Set accessRules, String output,
-                     List includes, List excludes) {
-        super(path, false, nativeLibraryLocation, accessRules)
+    SourceFolder(String projectRelativePath, String output) {
+        super(projectRelativePath)
         this.output = normalizePath(output);
-        this.includes = includes ?: [];
-        this.excludes = excludes ?: [];
+        this.includes = []
+        this.excludes = []
     }
 
     String getKind() {
         'src'
     }
 
+    String getName() {
+        dir.name
+    }
+
+    String getAbsolutePath() {
+        dir.absolutePath
+    }
+
+    void trimPath() {
+        path = name
+    }
+
     void appendNode(Node node) {
         addClasspathEntry(node, [including: includes.join("|"), excluding: excludes.join("|"), output: output])
     }
@@ -80,6 +95,7 @@ class SourceFolder extends AbstractClasspathEntry {
     public String toString() {
         return "SourceFolder{" +
                 "path='" + path + '\'' +
+                ", dir='" + dir + '\'' +
                 ", nativeLibraryLocation='" + nativeLibraryLocation + '\'' +
                 ", exported=" + exported +
                 ", accessRules=" + accessRules +
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy
index 6128bb2..f2d2965 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/Variable.groovy
@@ -15,16 +15,19 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+
 /**
  * @author Hans Dockter
  */
 class Variable extends AbstractLibrary {
-    Variable(Node node) {
-        super(node)
+    Variable(Node node, FileReferenceFactory fileReferenceFactory) {
+        super(node, fileReferenceFactory)
+        sourcePath = fileReferenceFactory.fromVariablePath(node. at sourcepath)
     }
 
-    Variable(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String sourcePath, String javadocPath) {
-        super(path, exported, nativeLibraryLocation, accessRules, sourcePath, javadocPath)
+    Variable(FileReference library) {
+        super(library)
     }
 
     String getKind() {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
index e202cb9..56e0e96 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacet.groovy
@@ -20,7 +20,6 @@ import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObje
 
 /**
  * Creates the .settings/org.eclipse.wst.common.project.facet.core.xml file for WTP projects.
- * Only handles installed facets. Fixed facets will be left untouched.
  *
  * @author Hans Dockter
  */
@@ -32,6 +31,7 @@ class WtpFacet extends XmlPersistableConfigurationObject {
     }
 
     @Override protected void load(Node xml) {
+        xml.fixed.each { facets.add(new Facet(it)) }
         xml.installed.each { facets.add(new Facet(it)) }
     }
 
@@ -51,6 +51,7 @@ class WtpFacet extends XmlPersistableConfigurationObject {
     }
 
     private void removeConfigurableDataFromXml() {
+        xml.fixed.each { xml.remove(it) }
         xml.installed.each { xml.remove(it) }
     }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy
new file mode 100644
index 0000000..6df3a5b
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClassFoldersCreator.groovy
@@ -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.plugins.ide.eclipse.model.internal
+
+import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
+import org.gradle.plugins.ide.eclipse.model.Library
+
+/**
+ * Eclipse calls them 'class folders' on java build path->libraries tab
+ *
+ * @author: Szczepan Faber, created at: 5/6/11
+ */
+class ClassFoldersCreator {
+
+    List<Library> create(EclipseClasspath classpath) {
+        List<Library> out = new LinkedList<Library>()
+        FileReferenceFactory fileReferenceFactory = classpath.fileReferenceFactory
+
+        classpath.classFolders.each { File folder ->
+            Library library = new Library(fileReferenceFactory.fromFile(folder))
+            library.exported = true
+            out << library
+        }
+
+        return out;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
index e8e9a15..fb1f35c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
@@ -15,203 +15,95 @@
  */
 package org.gradle.plugins.ide.eclipse.model.internal
 
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.api.specs.Spec
-import org.gradle.api.tasks.SourceSet
-import org.gradle.api.artifacts.*
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeLocalFileDependency
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeProjectDependency
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeRepoFileDependency
 import org.gradle.plugins.ide.eclipse.model.*
-import org.gradle.api.file.DirectoryTree
 
 /**
  * @author Hans Dockter
  */
 class ClasspathFactory {
 
-    List<ClasspathEntry> createEntries(EclipseClasspath classpath) {
-        def entries = []
-        entries.add(new Output(classpath.project.relativePath(classpath.classesOutputDir)))
-        entries.addAll(getEntriesFromSourceSets(classpath.sourceSets, classpath.project))
-        entries.addAll(getEntriesFromContainers(classpath.getContainers()))
-        entries.addAll(getEntriesFromConfigurations(classpath))
-        return entries
-    }
-
-    private List<SourceFolder> getEntriesFromSourceSets(Iterable<SourceSet> sourceSets, org.gradle.api.Project project) {
-        def entries = []
-        def sortedSourceSets = sortSourceSetsAsPerUsualConvention(sourceSets.collect{it})
-
-        sortedSourceSets.each { SourceSet sourceSet ->
-            def sortedSourceDirs = sortSourceDirsAsPerUsualConvention(sourceSet.allSource.srcDirTrees)
-
-            sortedSourceDirs.each { tree ->
-                def dir = tree.dir
-                if (dir.isDirectory()) {
-                    entries.add(new SourceFolder(
-                            project.relativePath(dir),
-                            null,
-                            [] as Set,
-                            null,
-                            tree.patterns.includes as List,
-                            tree.patterns.excludes as List))
-                }
-            }
-        }
-        entries
-    }
-
-    private List getEntriesFromContainers(Set containers) {
-        containers.collect { container ->
-            new Container(container, true, null, [] as Set)
-        }
-    }
-
-    private List getEntriesFromConfigurations(EclipseClasspath classpath) {
-        getModules(classpath) + getLibraries(classpath)
-    }
-
-    protected List getModules(EclipseClasspath classpath) {
-        return getDependencies(classpath.plusConfigurations, classpath.minusConfigurations, { it instanceof org.gradle.api.artifacts.ProjectDependency })
-                .collect { projectDependency -> new ProjectDependencyBuilder().build(projectDependency.dependencyProject) }
-    }
-
-    protected Set getLibraries(EclipseClasspath classpath) {
-        def allResolvedDependencies = resolveDependencies(classpath.plusConfigurations, classpath.minusConfigurations)
-
-        Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-            addSourceArtifact(dependency)
+    private final ClasspathEntryBuilder outputCreator = new ClasspathEntryBuilder() {
+        void update(List<ClasspathEntry> entries, EclipseClasspath eclipseClasspath) {
+            entries.add(new Output(eclipseClasspath.project.relativePath(eclipseClasspath.defaultOutputDir)))
         }
-        Map sourceFiles = classpath.downloadSources ? getFiles(classpath.project, sourceDependencies, "sources") : [:]
-
-        Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-            addJavadocArtifact(dependency)
-        }
-        Map javadocFiles = classpath.downloadJavadoc ? getFiles(classpath.project, javadocDependencies, "javadoc") : [:]
-
-        List moduleLibraries = resolveFiles(classpath.plusConfigurations, classpath.minusConfigurations).collect { File binaryFile ->
-            File sourceFile = sourceFiles[binaryFile.name]
-            File javadocFile = javadocFiles[binaryFile.name]
-            createLibraryEntry(binaryFile, sourceFile, javadocFile, classpath.pathVariables)
-        }
-        moduleLibraries.addAll(getSelfResolvingFiles(getDependencies(classpath.plusConfigurations, classpath.minusConfigurations,
-                { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}), classpath.pathVariables))
-        moduleLibraries
     }
 
-    private getSelfResolvingFiles(Collection dependencies, Map<String, File> pathVariables) {
-        dependencies.collect { SelfResolvingDependency dependency ->
-            dependency.resolve().collect { File file ->
-                createLibraryEntry(file, null, null, pathVariables)
+    private final ClasspathEntryBuilder containersCreator = new ClasspathEntryBuilder() {
+        void update(List<ClasspathEntry> entries, EclipseClasspath eclipseClasspath) {
+            eclipseClasspath.containers.each { container ->
+                Container entry = new Container(container)
+                entry.exported = true
+                entries << entry
             }
-        }.flatten()
-    }
-
-    AbstractLibrary createLibraryEntry(File binary, File source, File javadoc, Map<String, File> pathVariables) {
-        def usedVariableEntry = pathVariables.find { String name, File value -> binary.canonicalPath.startsWith(value.canonicalPath) }
-        if (usedVariableEntry) {
-            String name = usedVariableEntry.key
-            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
-            return new Variable(binaryPath, true, null, [] as Set, sourcePath, javadocPath)
         }
-        new Library(binary.canonicalPath, true, null, [] as Set, source ? source.canonicalPath : null, javadoc ? javadoc.canonicalPath : null)
     }
 
-    private Set<Dependency> getDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations, Closure filter) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(plusConfiguration.allDependencies.findAll(filter))
+    private final ClasspathEntryBuilder projectDependenciesCreator = new ClasspathEntryBuilder() {
+        void update(List<ClasspathEntry> entries, EclipseClasspath eclipseClasspath) {
+            entries.addAll(dependenciesExtractor.extractProjectDependencies(eclipseClasspath.plusConfigurations, eclipseClasspath.minusConfigurations)
+                .collect { IdeProjectDependency it -> new ProjectDependencyBuilder().build(it.project, it.declaredConfiguration.name) })
         }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(minusConfiguration.allDependencies.findAll(filter))
-        }
-        result
     }
 
-    private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(getAllDeps(plusConfiguration.resolvedConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
-        }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(getAllDeps(minusConfiguration.resolvedConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
-        }
-        result
-    }
-
-    private Set<File> resolveFiles(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(plusConfiguration.files { it instanceof ExternalDependency })
-        }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(minusConfiguration.files { it instanceof ExternalDependency })
-        }
-        result
-    }
-
-    private getFiles(org.gradle.api.Project project, Set dependencies, String classifier) {
-        return project.configurations.detachedConfiguration((dependencies as Dependency[])).files.inject([:]) { result, sourceFile ->
-            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
-            result[key] = sourceFile
-            result
-        }
-    }
-
-    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
-        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
-            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
-                    resolvedDependency.configuration)
-            dependency.transitive = false
-            configureClosure.call(dependency)
-            dependency
-        }
-    }
+    private final ClasspathEntryBuilder librariesCreator = new ClasspathEntryBuilder() {
+        void update(List<ClasspathEntry> entries, EclipseClasspath classpath) {
+            dependenciesExtractor.extractRepoFileDependencies(
+                    classpath.project.configurations, classpath.plusConfigurations, classpath.minusConfigurations, classpath.downloadSources, classpath.downloadJavadoc)
+            .each { IdeRepoFileDependency it ->
+                entries << createLibraryEntry(it.file, it.sourceFile, it.javadocFile, it.declaredConfiguration.name, classpath)
+            }
 
-    private List<SourceSet> sortSourceSetsAsPerUsualConvention(Collection<SourceSet> sourceSets) {
-        return sourceSets.sort { sourceSet ->
-            switch(sourceSet.name) {
-                case SourceSet.MAIN_SOURCE_SET_NAME: return 0
-                case SourceSet.TEST_SOURCE_SET_NAME: return 1
-                default: return 2
+            dependenciesExtractor.extractLocalFileDependencies(classpath.plusConfigurations, classpath.minusConfigurations)
+            .each { IdeLocalFileDependency it ->
+                entries << createLibraryEntry(it.file, null, null, it.declaredConfiguration.name, classpath)
             }
         }
     }
 
-    private List<DirectoryTree> sortSourceDirsAsPerUsualConvention(Collection<DirectoryTree> sourceDirs) {
-        return sourceDirs.sort { sourceDir ->
-            if (sourceDir.dir.path.endsWith("java")) { 0 }
-            else if (sourceDir.dir.path.endsWith("resources")) { 2 }
-            else { 1 }
-        }
-    }
+    private final sourceFoldersCreator = new SourceFoldersCreator()
+    private final IdeDependenciesExtractor dependenciesExtractor = new IdeDependenciesExtractor()
+    private final classFoldersCreator = new ClassFoldersCreator()
 
-    protected Set getAllDeps(Collection deps, Set allDeps = []) {
-        deps.each { ResolvedDependency resolvedDependency ->
-            def notSeenBefore = allDeps.add(resolvedDependency)
-            if (notSeenBefore) { // defend against circular dependencies
-                getAllDeps(resolvedDependency.children, allDeps)
-            }
-        }
-        allDeps
-    }
+    List<ClasspathEntry> createEntries(EclipseClasspath classpath) {
+        def entries = []
+        outputCreator.update(entries, classpath)
+        sourceFoldersCreator.populateForClasspath(entries, classpath)
+        containersCreator.update(entries, classpath)
 
-    protected void addSourceArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'source'
-            artifact.extension = 'jar'
-            artifact.classifier = 'sources'
+        if (classpath.projectDependenciesOnly) {
+            projectDependenciesCreator.update(entries, classpath)
+        } else {
+            projectDependenciesCreator.update(entries, classpath)
+            librariesCreator.update(entries, classpath)
+            entries.addAll(classFoldersCreator.create(classpath))
         }
+        return entries
     }
 
-    protected void addJavadocArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'javadoc'
-            artifact.extension = 'jar'
-            artifact.classifier = 'javadoc'
-        }
+    private AbstractLibrary createLibraryEntry(File binary, File source, File javadoc, String declaredConfigurationName, EclipseClasspath classpath) {
+        def referenceFactory = classpath.fileReferenceFactory
+        
+        def binaryRef = referenceFactory.fromFile(binary)
+        def sourceRef = referenceFactory.fromFile(source)
+        def javadocRef = referenceFactory.fromFile(javadoc);
+        def out
+        if (binaryRef.relativeToPathVariable) {
+            out = new Variable(binaryRef)
+        } else {
+            out = new Library(binaryRef)
+        }
+        out.javadocPath = javadocRef
+        out.sourcePath = sourceRef
+        out.exported = true
+        out.declaredConfigurationName = declaredConfigurationName
+        out
     }
 }
+
+interface ClasspathEntryBuilder {
+	void update(List<ClasspathEntry> entries, EclipseClasspath eclipseClasspath)
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy
new file mode 100644
index 0000000..c626e02
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ExportedEntriesUpdater.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.ide.eclipse.model.internal
+
+import org.gradle.plugins.ide.eclipse.model.AbstractLibrary
+import org.gradle.plugins.ide.eclipse.model.ClasspathEntry
+import org.gradle.plugins.ide.eclipse.model.ProjectDependency
+
+/**
+ * @author: Szczepan Faber, created at: 7/4/11
+ */
+class ExportedEntriesUpdater {
+    void updateExported(List<ClasspathEntry> classpathEntries, List<String> noExportConfigNames) {
+        classpathEntries.each {
+            if (it instanceof AbstractLibrary || it instanceof ProjectDependency) {
+                if (noExportConfigNames.contains(it.declaredConfigurationName)) {
+                    it.exported = false
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactory.groovy
new file mode 100644
index 0000000..fb6068d
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactory.groovy
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2011 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.ide.eclipse.model.internal
+
+import org.gradle.plugins.ide.eclipse.model.FileReference
+
+class FileReferenceFactory {
+    private final Map<String, File> variables = [:]
+
+    /**
+     * Adds a path variable
+     */
+    void addPathVariable(String name, File dir) {
+        variables[name] = dir
+    }
+
+    /**
+     * Creates a reference to the given file. Returns null for a null file.
+     */
+    FileReference fromFile(File file) {
+        if (!file) {
+            return null
+        }
+        def path = null
+        def usedVar = false
+        for (entry in variables.entrySet()) {
+            def rootDirPath = entry.value.absolutePath
+            def filePath = file.absolutePath
+            if (filePath == rootDirPath) {
+                path = entry.key
+                usedVar = true
+                break
+            }
+            if (filePath.startsWith(rootDirPath + File.separator)) {
+                int len = rootDirPath.length()
+                path = entry.key + filePath.substring(len)
+                usedVar = true
+                break
+            }
+        }
+        path = PathUtil.normalizePath(path ?: file.absolutePath)
+        return new FileReferenceImpl(file, path, usedVar)
+    }
+
+    /**
+     * Creates a reference to the given path. Returns null for for null path
+     */
+    FileReference fromPath(String path) {
+        if (path == null) {
+            return null
+        }
+        new FileReferenceImpl(new File(path), path, false)
+    }
+
+    /**
+     * Creates a reference to the given path. Returns null for for null path
+     */
+    FileReference fromJarURI(String jarURI) {
+        if (jarURI== null) {
+            return null
+        }
+        //cut the pre and postfix of this url
+        URI fileURI = new URI(jarURI - "jar:" - "!/");
+        File file = new File(fileURI);
+        String path = PathUtil.normalizePath(file.absolutePath)
+        new FileReferenceImpl(file, path, false);
+    }
+    /**
+     * Creates a reference to the given path containing a variable reference. Returns null for null variable path
+     */
+    FileReference fromVariablePath(String path) {
+        if (path == null) {
+            return null
+        }
+        for (entry in variables.entrySet()) {
+            def prefix = "$entry.key/"
+            if (path.startsWith(prefix)) {
+                def file = new File(entry.value, path.substring(prefix.length()))
+                return new FileReferenceImpl(file, path, true)
+            }
+        }
+        return fromPath(path)
+    }
+
+    private static class FileReferenceImpl implements FileReference {
+        final File file
+        final String path
+        final boolean relativeToPathVariable
+
+        FileReferenceImpl(File file, String path, boolean relativeToPathVariable) {
+            this.file = file
+            this.path = path
+            this.relativeToPathVariable = relativeToPathVariable
+        }
+
+        @Override
+        boolean equals(Object obj) {
+            if (obj.is(this)) {
+                return true
+            }
+            if (obj == null || obj.getClass() != getClass()) {
+                return false
+            }
+
+            return file.equals(obj.file)
+        }
+
+        public String getJarURL(){
+            //windows needs an additional backslash in jar urls
+            return "jar:${file.toURI()}!/"
+        }
+
+        public String toString() {
+            return "{" +
+                    "file='" + file + '\'' +
+                    "path='" + path + '\'' +
+                    ", jarUrl='" + getJarURL() + '\'' +
+                    '}';
+        }
+
+        @Override
+        int hashCode() {
+            return file.hashCode()
+        }
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
index 2a634a1..b242a39 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilder.groovy
@@ -15,19 +15,24 @@
  */
 package org.gradle.plugins.ide.eclipse.model.internal
 
+import org.gradle.api.Project
+import org.gradle.plugins.ide.eclipse.EclipsePlugin
 import org.gradle.plugins.ide.eclipse.model.ProjectDependency
 
 /**
  * @author Szczepan Faber, @date: 11.03.11
  */
 class ProjectDependencyBuilder {
-    ProjectDependency build(gradleProject) {
+    ProjectDependency build(Project project, String declaredConfigurationName) {
         def name
-        if (gradleProject.hasProperty('eclipseProject') && gradleProject.eclipseProject) {
-            name = gradleProject.eclipseProject.projectName
+        if (project.plugins.hasPlugin(EclipsePlugin)) {
+            name = project.eclipse.project.name
         } else {
-            name = gradleProject.name
+            name = project.name
         }
-        new ProjectDependency('/' + name, true, null, [] as Set, gradleProject.path)
+        def out = new ProjectDependency('/' + name, project.path)
+        out.exported = true
+        out.declaredConfigurationName = declaredConfigurationName
+        out
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy
new file mode 100644
index 0000000..83d5f28
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/SourceFoldersCreator.groovy
@@ -0,0 +1,102 @@
+/*
+ * 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.ide.eclipse.model.internal
+
+import org.gradle.api.file.DirectoryTree
+import org.gradle.api.tasks.SourceSet
+import org.gradle.plugins.ide.eclipse.model.ClasspathEntry
+import org.gradle.plugins.ide.eclipse.model.EclipseClasspath
+import org.gradle.plugins.ide.eclipse.model.SourceFolder
+
+/**
+ * @author: Szczepan Faber, created at: 4/22/11
+ */
+class SourceFoldersCreator {
+    void populateForClasspath(List<ClasspathEntry> entries, EclipseClasspath classpath) {
+        def provideRelativePath = { classpath.project.relativePath(it) }
+        List regulars = getRegularSourceFolders(classpath.sourceSets, provideRelativePath)
+
+        //externals are mapped to linked resources so we just need a name of the resource, without full path
+        List trimmedExternals = getExternalSourceFolders(classpath.sourceSets, provideRelativePath).collect { SourceFolder folder ->
+            folder.trimPath()
+            folder
+        }
+
+        entries.addAll(regulars)
+        entries.addAll(trimmedExternals)
+    }
+
+    /**
+     * paths that navigate higher than project dir are not allowed in eclipse .classpath
+     * regardless if they are absolute or relative
+     *
+     * @return source folders that live inside the project
+     */
+    List<SourceFolder> getRegularSourceFolders(Iterable<SourceSet> sourceSets, Closure provideRelativePath) {
+        def sourceFolders = projectRelativeFolders(sourceSets, provideRelativePath)
+        return sourceFolders.findAll { !it.path.contains('..') }
+    }
+
+    /**
+     * see {@link #getRegularSourceFolders}
+     *
+     * @return source folders that live outside of the project
+     */
+    List<SourceFolder> getExternalSourceFolders(Iterable<SourceSet> sourceSets, Closure provideRelativePath) {
+        def sourceFolders = projectRelativeFolders(sourceSets, provideRelativePath)
+        return sourceFolders.findAll { it.path.contains('..') }
+    }
+
+    private List<SourceFolder> projectRelativeFolders(Iterable<SourceSet> sourceSets, Closure provideRelativePath) {
+        def entries = []
+        def sortedSourceSets = sortSourceSetsAsPerUsualConvention(sourceSets.collect{it})
+
+        sortedSourceSets.each { SourceSet sourceSet ->
+            def sortedSourceDirs = sortSourceDirsAsPerUsualConvention(sourceSet.allSource.srcDirTrees)
+
+            sortedSourceDirs.each { tree ->
+                def dir = tree.dir
+                if (dir.isDirectory()) {
+                    def folder = new SourceFolder(provideRelativePath(dir), null)
+                    folder.dir = dir
+                    folder.includes = tree.patterns.includes as List
+                    folder.excludes = tree.patterns.excludes as List
+                    entries.add(folder)
+                }
+            }
+        }
+        entries
+    }
+
+    private List<SourceSet> sortSourceSetsAsPerUsualConvention(Collection<SourceSet> sourceSets) {
+        return sourceSets.sort { sourceSet ->
+            switch(sourceSet.name) {
+                case SourceSet.MAIN_SOURCE_SET_NAME: return 0
+                case SourceSet.TEST_SOURCE_SET_NAME: return 1
+                default: return 2
+            }
+        }
+    }
+
+    private List<DirectoryTree> sortSourceDirsAsPerUsualConvention(Collection<DirectoryTree> sourceDirs) {
+        return sourceDirs.sort { sourceDir ->
+            if (sourceDir.dir.path.endsWith("java")) { 0 }
+            else if (sourceDir.dir.path.endsWith("resources")) { 2 }
+            else { 1 }
+        }
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
index 93676d3..d1ff2e8 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
@@ -15,10 +15,11 @@
  */
 package org.gradle.plugins.ide.eclipse.model.internal
 
-import org.apache.commons.io.FilenameUtils
+import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Dependency
 import org.gradle.api.artifacts.ExternalDependency
 import org.gradle.api.artifacts.SelfResolvingDependency
+import org.gradle.plugins.ide.eclipse.EclipsePlugin
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent
 import org.gradle.plugins.ide.eclipse.model.WbDependentModule
 import org.gradle.plugins.ide.eclipse.model.WbResource
@@ -30,44 +31,51 @@ import org.gradle.plugins.ide.eclipse.model.WtpComponent
 class WtpComponentFactory {
     void configure(EclipseWtpComponent wtp, WtpComponent component) {
         def entries = getEntriesFromSourceDirs(wtp)
-        entries.addAll(wtp.resources)
+        entries.addAll(wtp.resources.findAll { wtp.project.file(it.sourcePath).isDirectory() } )
         entries.addAll(wtp.properties)
-        entries.addAll(getEntriesFromConfigurations(wtp))
+        // for ear files root deps are NOT transitive; wars don't use root deps so this doesn't hurt them
+        // TODO: maybe do this in a more explicit way, via config or something
+        entries.addAll(getEntriesFromConfigurations(wtp.rootConfigurations, wtp.minusConfigurations, wtp, '/', false))
+        entries.addAll(getEntriesFromConfigurations(wtp.libConfigurations, wtp.minusConfigurations, wtp, wtp.libDeployPath, true))
 
         component.configure(wtp.deployName, wtp.contextPath, entries)
     }
 
     private List getEntriesFromSourceDirs(EclipseWtpComponent wtp) {
         wtp.sourceDirs.findAll { it.isDirectory() }.collect { dir ->
-            new WbResource("/WEB-INF/classes", wtp.project.relativePath(dir))
+            new WbResource(wtp.classesDeployPath, wtp.project.relativePath(dir))
         }
     }
 
-    private List getEntriesFromConfigurations(EclipseWtpComponent wtp) {
-        (getEntriesFromProjectDependencies(wtp) as List) + (getEntriesFromLibraries(wtp) as List)
+    private List getEntriesFromConfigurations(Set plusConfigurations, Set minusConfigurations, EclipseWtpComponent wtp, String deployPath, boolean transitive) {
+        (getEntriesFromProjectDependencies(plusConfigurations, minusConfigurations, deployPath, transitive) as List) +
+                (getEntriesFromLibraries(plusConfigurations, minusConfigurations, wtp, deployPath) as List)
     }
 
     // must include transitive project dependencies
-    private Set getEntriesFromProjectDependencies(EclipseWtpComponent wtp) {
-        def dependencies = getDependencies(wtp.plusConfigurations, wtp.minusConfigurations,
+    private Set getEntriesFromProjectDependencies(Set plusConfigurations, Set minusConfigurations, String deployPath, boolean transitive) {
+        def dependencies = getDependencies(plusConfigurations, minusConfigurations,
                 { it instanceof org.gradle.api.artifacts.ProjectDependency })
 
         def projects = dependencies*.dependencyProject
 
         def allProjects = [] as LinkedHashSet
         allProjects.addAll(projects)
-        projects.each { collectDependedUponProjects(it, allProjects) }
+        if (transitive) {
+            projects.each { collectDependedUponProjects(it, allProjects) }
+        }
 
         allProjects.collect { project ->
-            new WbDependentModule("/WEB-INF/lib", "module:/resource/" + project.name + "/" + project.name)
+            def moduleName = project.plugins.hasPlugin(EclipsePlugin) ? project.eclipse.project.name : project.name
+            new WbDependentModule(deployPath, "module:/resource/" + moduleName + "/" + moduleName)
         }
     }
 
-    // TODO: might have to search all class paths of all source sets for project dependendencies, not just runtime configuration
+    // TODO: might have to search all class paths of all source sets for project dependencies, not just runtime configuration
     private void collectDependedUponProjects(org.gradle.api.Project project, LinkedHashSet result) {
         def runtimeConfig = project.configurations.findByName("runtime")
         if (runtimeConfig) {
-            def projectDeps = runtimeConfig.getAllDependencies(org.gradle.api.artifacts.ProjectDependency)
+            def projectDeps = runtimeConfig.allDependencies.withType(org.gradle.api.artifacts.ProjectDependency)
             def dependedUponProjects = projectDeps*.dependencyProject
             result.addAll(dependedUponProjects)
             for (dependedUponProject in dependedUponProjects) {
@@ -77,42 +85,41 @@ class WtpComponentFactory {
     }
 
     // must NOT include transitive library dependencies
-    private Set getEntriesFromLibraries(EclipseWtpComponent wtp) {
-        Set declaredDependencies = getDependencies(wtp.plusConfigurations, wtp.minusConfigurations,
+    private Set getEntriesFromLibraries(Set plusConfigurations, Set minusConfigurations, EclipseWtpComponent wtp, String deployPath) {
+        Set declaredDependencies = getDependencies(plusConfigurations, minusConfigurations,
                 { it instanceof ExternalDependency})
 
         Set libFiles = wtp.project.configurations.detachedConfiguration((declaredDependencies as Dependency[])).files +
-                getSelfResolvingFiles(getDependencies(wtp.plusConfigurations, wtp.minusConfigurations,
+                getSelfResolvingFiles(getDependencies(plusConfigurations, minusConfigurations,
                         { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}))
 
         libFiles.collect { file ->
-            createWbDependentModuleEntry(file, wtp.pathVariables)
+            createWbDependentModuleEntry(file, wtp.fileReferenceFactory, deployPath)
         }
     }
 
     private LinkedHashSet getSelfResolvingFiles(LinkedHashSet<SelfResolvingDependency> dependencies) {
-        dependencies.collect { it.resolve() }.flatten()
+        dependencies.collect { it.resolve() }.flatten() as LinkedHashSet
     }
 
-    private WbDependentModule createWbDependentModuleEntry(File file, Map<String, File> variables) {
-        def usedVariableEntry = variables.find { name, value -> file.canonicalPath.startsWith(value.canonicalPath) }
+    private WbDependentModule createWbDependentModuleEntry(File file, FileReferenceFactory fileReferenceFactory, String deployPath) {
+        def ref = fileReferenceFactory.fromFile(file)
         def handleSnippet
-        if (usedVariableEntry) {
-            handleSnippet = "var/$usedVariableEntry.key/${file.canonicalPath.substring(usedVariableEntry.value.canonicalPath.length())}"
+        if (ref.relativeToPathVariable) {
+            handleSnippet = "var/$ref.path"
         } else {
-            handleSnippet = "lib/${file.canonicalPath}"
+            handleSnippet = "lib/${ref.path}"
         }
-        handleSnippet = FilenameUtils.separatorsToUnix(handleSnippet)
-        return new WbDependentModule('/WEB-INF/lib', "module:/classpath/$handleSnippet")
+        return new WbDependentModule(deployPath, "module:/classpath/$handleSnippet")
     }
 
     private LinkedHashSet getDependencies(Set plusConfigurations, Set minusConfigurations, Closure filter) {
         def declaredDependencies = new LinkedHashSet()
-        plusConfigurations.each { configuration ->
-            declaredDependencies.addAll(configuration.allDependencies.findAll(filter))
+        plusConfigurations.each { Configuration configuration ->
+            declaredDependencies.addAll(configuration.allDependencies.matching(filter))
         }
-        minusConfigurations.each { configuration ->
-            declaredDependencies.removeAll(configuration.allDependencies.findAll(filter))
+        minusConfigurations.each { Configuration configuration ->
+            declaredDependencies.removeAll(configuration.allDependencies.matching(filter))
         }
         return declaredDependencies
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy
index 2e05677..e6e8efa 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaModule.groovy
@@ -15,32 +15,16 @@
  */
 package org.gradle.plugins.ide.idea
 
-import org.gradle.api.artifacts.Configuration
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.idea.model.IdeaModule
 import org.gradle.plugins.ide.idea.model.Module
 
 /**
- * Generates an IDEA module file.
+ * Generates an IDEA module file. If you want to fine tune the idea configuration
  * <p>
- * Example how to use scopes property to enable 'provided' dependencies in the output *.iml file:
- * <pre autoTested=''>
- * apply plugin: 'java'
- * apply plugin: 'idea'
- *
- * configurations {
- *   provided
- *   provided.extendsFrom(compile)
- * }
- *
- * dependencies {
- *   //provided "some.interesting:dependency:1.0"
- * }
- *
- * ideaModule {
- *   scopes.PROVIDED.plus += configurations.provided
- * }
- * </pre>
+ * Please refer to interesting examples on idea configuration in {@link IdeaModule}.
+ * <p>
+ * At this moment nearly all configuration is done via {@link IdeaModule}.
  *
  * @author Hans Dockter
  */
@@ -51,10 +35,6 @@ public class GenerateIdeaModule extends XmlGeneratorTask<Module> {
      */
     IdeaModule module
 
-    //TODO SF: IMPORTANT
-    //Once we decide to break backwards compatibility below hacky delegating getters/setters will be gone
-    //and the implementation of this task will dwindle into few lines of code or disappear completely
-
     @Override protected Module create() {
         new Module(xmlTransformer, module.pathFactory)
     }
@@ -64,168 +44,11 @@ public class GenerateIdeaModule extends XmlGeneratorTask<Module> {
     }
 
     /**
-     * The content root directory of the module.
-     */
-    File getModuleDir() {
-        module.contentRoot
-    }
-
-    void setModuleDir(File contentRoot) {
-        module.contentRoot = contentRoot
-    }
-
-    /**
-     * The directories containing the production sources.
-     */
-    Set<File> getSourceDirs() {
-       module.sourceDirs
-    }
-
-    void setSourceDirs(Set<File> sourceDirs) {
-       module.sourceDirs = sourceDirs
-    }
-
-    /**
-     * The directories containing the test sources.
-     */
-    Set<File> getTestSourceDirs() {
-        module.testSourceDirs
-    }
-
-    void setTestSourceDirs(Set<File> testSourceDirs) {
-        module.testSourceDirs = testSourceDirs
-    }
-
-    /**
-     * The directories to be excluded.
-     */
-    Set<File> getExcludeDirs() {
-        module.excludeDirs
-    }
-
-    void setExcludeDirs(Set<File> excludeDirs) {
-        module.excludeDirs = excludeDirs
-    }
-
-    /**
-     * If true, output directories for this module will be located below the output directory for the project;
-     * otherwise, they will be set to the directories specified by #outputDir and #testOutputDir.
-     */
-    Boolean getInheritOutputDirs() {
-        module.inheritOutputDirs
-    }
-
-    void setInheritOutputDirs(Boolean inheritOutputDirs) {
-        module.inheritOutputDirs
-    }
-
-    /**
-     * The output directory for production classes. If {@code null}, no entry will be created.
-     */
-    File getOutputDir() {
-        module.outputDir
-    }
-
-    void setOutputDir(File outputDir) {
-        module.outputDir
-    }
-
-    /**
-     * The output directory for test classes. If {@code null}, no entry will be created.
-     */
-    File getTestOutputDir() {
-        module.testOutputDir
-    }
-
-    void setTestOutputDir(File testOutputDir) {
-        module.testOutputDir
-    }
-
-    /**
-     * The JDK to use for this module. If {@code null}, the value of the existing or default ipr XML (inherited)
-     * is used. If it is set to <code>inherited</code>, the project SDK is used. Otherwise the SDK for the corresponding
-     * value of java version is used for this module
-     */
-    String getJavaVersion() {
-        module.javaVersion
-    }
-
-    void setJavaVersion(String javaVersion) {
-        module.javaVersion = javaVersion
-    }
-
-    /**
-     * Whether to download and add sources associated with the dependency jars.
-     */
-    boolean getDownloadSources() {
-        module.downloadSources
-    }
-
-    void setDownloadSources(boolean downloadSources) {
-        module.downloadSources = downloadSources
-    }
-
-    /**
-     * Whether to download and add javadoc associated with the dependency jars.
-     */
-    boolean getDownloadJavadoc() {
-        module.downloadJavadoc
-    }
-
-    void setDownloadJavadoc(boolean downloadJavadoc) {
-        module.downloadJavadoc = downloadJavadoc
-    }
-
-    /**
-     * The variables to be used for replacing absolute paths in the iml entries. For example, you might add a
-     * {@code GRADLE_USER_HOME} variable to point to the Gradle user home dir.
-     */
-    Map<String, File> getVariables() {
-        module.variables
-    }
-
-    void setVariables(Map<String, File> variables) {
-        module.variables = variables
-    }
-
-    /**
-     * 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 collections of {@link org.gradle.api.artifacts.Configuration} objects. The files of the
-     * plus configurations are added minus the files from the minus configurations. See example below...
-     * <p>
-     * Example how to use scopes property to enable 'provided' dependencies in the output *.iml file:
-     * <pre autoTested=''>
-     * apply plugin: 'java'
-     * apply plugin: 'idea'
-     *
-     * configurations {
-     *   provided
-     *   provided.extendsFrom(compile)
-     * }
-     *
-     * dependencies {
-     *   //provided "some.interesting:dependency:1.0"
-     * }
-     *
-     * ideaModule {
-     *   scopes.PROVIDED.plus += configurations.provided
-     * }
-     * </pre>
-     */
-    Map<String, Map<String, Collection<Configuration>>> getScopes() {
-        module.scopes
-    }
-
-    void setScopes(Map<String, Map<String, Collection<Configuration>>> scopes) {
-        module.scopes = scopes
-    }
-
-    /**
      * Configures output *.iml file. It's <b>optional</b> because the task should configure it correctly for you
      * (including making sure it is unique in the multi-module build).
-     * If you really need to change the output file name it is much easier to do it via the <b>moduleName</b> property.
+     * If you really need to change the output file name it is much easier to do it via the <b>idea.module.name</b> property.
      * <p>
-     * Please refer to documentation on <b>moduleName</b> property. In IntelliJ IDEA the module name is the same as the name of the *.iml file.
+     * Please refer to documentation in {@link IdeaModule} <b>name</b> property. In IntelliJ IDEA the module name is the same as the name of the *.iml file.
      */
     File getOutputFile() {
         return module.outputFile
@@ -235,32 +58,4 @@ public class GenerateIdeaModule extends XmlGeneratorTask<Module> {
         module.outputFile = newOutputFile
     }
 
-    /**
-     * Configures module name. It's <b>optional</b> because the task should configure it correctly for you.
-     * By default it will try to use the <b>project.name</b> or prefix it with a part of a <b>project.path</b>
-     * to make sure the moduleName is unique in the scope of a multi-module build.
-     * The 'uniqeness' of a module name is required for correct import
-     * into IntelliJ IDEA and the task will make sure the name is unique. See example below...
-     * <p>
-     * <b>moduleName</b> is a synthethic property that actually modifies the <b>outputFile</b> property value.
-     * This means that you should not configure both moduleName and outputFile at the same time. moduleName is recommended.
-     * <p>
-     * However, in case you really need to override the default moduleName this is the way to go:
-     * <pre autoTested=''>
-     * apply plugin: 'idea'
-     *
-     * ideaModule {
-     *   moduleName = 'some-important-project'
-     * }
-     * </pre>
-     * <p>
-     * <b>since</b> 1.0-milestone-2
-     */
-    String getModuleName() {
-        return module.name
-    }
-
-    void setModuleName(String moduleName) {
-        module.name = moduleName
-    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy
index 47d9e66..754e039 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaProject.groovy
@@ -20,7 +20,9 @@ import org.gradle.plugins.ide.idea.model.IdeaProject
 import org.gradle.plugins.ide.idea.model.Project
 
 /**
- * Generates an IDEA project file.
+ * Generates an IDEA project file for root project *only*. If you want to fine tune the idea configuration
+ * <p>
+ * At this moment nearly all configuration is done via {@link IdeaProject}.
  *
  * @author Hans Dockter
  */
@@ -41,40 +43,6 @@ public class GenerateIdeaProject 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.
-     */
-    Set<org.gradle.api.Project> getSubprojects() {
-        ideaProject.subprojects
-    }
-
-    void setSubprojects(Set<org.gradle.api.Project> subprojects) {
-        ideaProject.subprojects = subprojects
-    }
-
-    /**
-     * The java version used for defining the project sdk.
-     */
-    String getJavaVersion() {
-        ideaProject.javaVersion
-    }
-
-    void setJavaVersion(String javaVersion) {
-        ideaProject.javaVersion = javaVersion
-    }
-
-    /**
-     * The wildcard resource patterns.
-     */
-    Set getWildcards() {
-        ideaProject.wildcards
-    }
-
-    void setWildcards(Set wildcards) {
-        ideaProject.wildcards = wildcards
-    }
-
-    /**
      * output *.ipr file
      */
     File getOutputFile() {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy
index b6ba704..0a146ec 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/GenerateIdeaWorkspace.groovy
@@ -16,19 +16,24 @@
 package org.gradle.plugins.ide.idea
 
 import org.gradle.plugins.ide.api.XmlGeneratorTask
+import org.gradle.plugins.ide.idea.model.IdeaWorkspace
 import org.gradle.plugins.ide.idea.model.Workspace
 
 /**
- * Generates an IDEA workspace file.
+ * Generates an IDEA workspace file *only* for root project.
+ * There's little you can configure about workspace generation at the moment.
  *
  * @author Hans Dockter
  */
 public class GenerateIdeaWorkspace extends XmlGeneratorTask<Workspace> {
 
+    IdeaWorkspace workspace
+
     @Override protected Workspace create() {
         return new Workspace(xmlTransformer)
     }
 
-    @Override protected void configure(Workspace object) {
+    @Override protected void configure(Workspace xmlWorkspace) {
+        getWorkspace().mergeXmlWorkspace(xmlWorkspace)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
index 40dd06a..8bc19e1 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2011 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.
@@ -18,17 +18,18 @@ package org.gradle.plugins.ide.idea;
 
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
-import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.Instantiator
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.idea.internal.IdeaNameDeduper
 import org.gradle.plugins.ide.internal.IdePlugin
 import org.gradle.plugins.ide.idea.model.*
 
 /**
- * @author Hans Dockter
- *
- * Adds an IdeaModule task. When applied to a root project, also adds an IdeaProject task.
+ * Adds a GenerateIdeaModule task. When applied to a root project, also adds a GenerateIdeaProject task.
  * For projects that have the Java plugin applied, the tasks receive additional Java-specific configuration.
+ *
+ *  @author Hans Dockter
  */
 class IdeaPlugin extends IdePlugin {
 
@@ -42,33 +43,43 @@ class IdeaPlugin extends IdePlugin {
         lifecycleTask.description = 'Generates IDEA project files (IML, IPR, IWS)'
         cleanTask.description = 'Cleans IDEA project files (IML, IPR)'
 
-        model = new IdeaModel()
-        project.convention.plugins.idea = model
+        model= project.extensions.create("idea", IdeaModel)
 
         configureIdeaWorkspace(project)
         configureIdeaProject(project)
         configureIdeaModule(project)
         configureForJavaPlugin(project)
 
-        project.gradle.projectsEvaluated {
-            //TODO SF: is it possible to do deduplication on the fly? - same applies for eclipse
-            new IdeaNameDeduper().configure(project)
+        hookDeduplicationToTheRoot(project)
+    }
+
+    void hookDeduplicationToTheRoot(Project project) {
+        if (isRoot(project)) {
+            project.gradle.projectsEvaluated {
+                makeSureModuleNamesAreUnique()
+            }
         }
     }
 
+    public void makeSureModuleNamesAreUnique() {
+        new IdeaNameDeduper().configureRoot(project.rootProject)
+    }
+
     private configureIdeaWorkspace(Project project) {
         if (isRoot(project)) {
             def task = project.task('ideaWorkspace', description: 'Generates an IDEA workspace file (IWS)', type: GenerateIdeaWorkspace) {
+                workspace = new IdeaWorkspace(iws: new XmlFileContentMerger(xmlTransformer))
+                model.workspace = workspace
                 outputFile = new File(project.projectDir, project.name + ".iws")
             }
-            addWorker(task)
+            addWorker(task, false)
         }
     }
 
     private configureIdeaModule(Project project) {
         def task = project.task('ideaModule', description: 'Generates IDEA module files (IML)', type: GenerateIdeaModule) {
-            def iml = new IdeaModuleIml(xmlTransformer: xmlTransformer, generateTo: project.projectDir)
-            module = services.get(ClassGenerator).newInstance(IdeaModule, [project: project, iml: iml])
+            def iml = new IdeaModuleIml(xmlTransformer, project.projectDir)
+            module = services.get(Instantiator).newInstance(IdeaModule, project, iml)
 
             model.module = module
 
@@ -81,7 +92,7 @@ class IdeaPlugin extends IdePlugin {
             module.conventionMapping.pathFactory = {
                 PathFactory factory = new PathFactory()
                 factory.addPathVariable('MODULE_DIR', outputFile.parentFile)
-                module.variables.each { key, value ->
+                module.pathVariables.each { key, value ->
                     factory.addPathVariable(key, value)
                 }
                 factory
@@ -94,15 +105,19 @@ class IdeaPlugin extends IdePlugin {
     private configureIdeaProject(Project project) {
         if (isRoot(project)) {
             def task = project.task('ideaProject', description: 'Generates IDEA project file (IPR)', type: GenerateIdeaProject) {
-                def ipr = new IdeaProjectIpr(xmlTransformer: xmlTransformer)
-                ideaProject = services.get(ClassGenerator).newInstance(IdeaProject, [ipr: ipr])
+                def ipr = new XmlFileContentMerger(xmlTransformer)
+                ideaProject = services.get(Instantiator).newInstance(IdeaProject, ipr)
 
                 model.project = ideaProject
 
                 ideaProject.outputFile = new File(project.projectDir, project.name + ".ipr")
-                ideaProject.javaVersion = JavaVersion.VERSION_1_6.toString()
+                ideaProject.conventionMapping.jdkName = { JavaVersion.VERSION_1_6.toString() }
+                ideaProject.conventionMapping.languageLevel = { new IdeaLanguageLevel(JavaVersion.VERSION_1_6) }
                 ideaProject.wildcards = ['!?*.java', '!?*.groovy'] as Set
-                ideaProject.subprojects = project.rootProject.allprojects
+                ideaProject.conventionMapping.modules = {
+                    project.rootProject.allprojects.findAll { it.plugins.hasPlugin(IdeaPlugin) }.collect { it.idea.module }
+                }
+
                 ideaProject.conventionMapping.pathFactory = {
                     new PathFactory().addPathVariable('PROJECT_DIR', outputFile.parentFile)
                 }
@@ -120,8 +135,9 @@ class IdeaPlugin extends IdePlugin {
 
     private configureIdeaProjectForJava(Project project) {
         if (isRoot(project)) {
-            project.ideaProject {
-                javaVersion = project.sourceCompatibility
+            project.idea.project.conventionMapping.jdkName   = { project.sourceCompatibility.toString() }
+            project.idea.project.conventionMapping.languageLevel = {
+                new IdeaLanguageLevel(project.sourceCompatibility)
             }
         }
     }
@@ -137,6 +153,13 @@ class IdeaPlugin extends IdePlugin {
                     RUNTIME: [plus: [configurations.runtime], minus: [configurations.compile]],
                     TEST: [plus: [configurations.testRuntime], minus: [configurations.runtime]]
             ]
+            module.conventionMapping.singleEntryLibraries = { [
+                    RUNTIME: project.sourceSets.main.output.dirs,
+                    TEST: project.sourceSets.test.output.dirs
+            ] }
+            dependsOn {
+                project.sourceSets.main.output.dirs + project.sourceSets.test.output.dirs
+            }
         }
     }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy
index 14abafd..c7d3a1b 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaNameDeduper.groovy
@@ -25,8 +25,8 @@ import org.gradle.plugins.ide.internal.configurer.ProjectDeduper
  */
 class IdeaNameDeduper {
 
-    void configure(Project theProject) {
-        def ideaProjects = theProject.rootProject.allprojects.findAll { it.plugins.hasPlugin(IdeaPlugin) }
+    void configureRoot(Project rootProject) {
+        def ideaProjects = rootProject.allprojects.findAll { it.plugins.hasPlugin(IdeaPlugin) }
         new ProjectDeduper().dedupe(ideaProjects, { project ->
             new DeduplicationTarget(project: project,
                     moduleName: project.ideaModule.module.name,
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/FilePath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/FilePath.groovy
new file mode 100644
index 0000000..12af3af
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/FilePath.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.ide.idea.model
+
+/**
+ * A Path that keeps the reference to the File
+ */
+class FilePath extends Path {
+
+    /**
+     * file
+     */
+    final File file
+
+    FilePath(File file, String url, String canonicalUrl, String relPath) {
+        super(url, canonicalUrl, relPath)
+        this.file = file
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy
new file mode 100644
index 0000000..9cb1b9f
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevel.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.ide.idea.model
+
+import org.gradle.api.JavaVersion
+
+/**
+ * Java language level used by IDEA projects.
+ *
+ * @author: Szczepan Faber, created at: 7/14/11
+ */
+class IdeaLanguageLevel {
+
+    String level
+
+    IdeaLanguageLevel(Object version) {
+        if (version instanceof String && version =~ /^JDK_/) {
+            level = version
+            return
+        }
+        level = JavaVersion.toVersion(version).name().replaceFirst("VERSION", "JDK")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy
index 5e46817..87eb0dc 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModel.groovy
@@ -20,21 +20,77 @@ import org.gradle.util.ConfigureUtil
 
 /**
  * DSL-friendly model of the IDEA project information.
- * First point of entry when it comes to customizing the idea generation
+ * First point of entry when it comes to customizing the IDEA generation.
  * <p>
- * See the examples in docs for {@link IdeaModule} or {@link IdeaProject}
+ * See the examples in docs for {@link IdeaModule} or {@link IdeaProject}.
  * <p>
- * Author: Szczepan Faber, created at: 3/31/11
+ * @author Szczepan Faber, created at: 3/31/11
  */
 class IdeaModel {
+
+    /**
+     * Configures IDEA module information.
+     * <p>
+     * For examples see docs for {@link IdeaModule}.
+     */
     IdeaModule module
+
+    /**
+     * Configures IDEA project information.
+     * <p>
+     * For examples see docs for {@link IdeaProject}.
+     */
     IdeaProject project
 
+    /**
+     * Configures IDEA workspace information.
+     * <p>
+     * For examples see docs for {@link IdeaWorkspace}.
+     */
+    IdeaWorkspace workspace = new IdeaWorkspace()
+
+    /**
+     * Configures IDEA module information.
+     * <p>
+     * For examples see docs for {@link IdeaModule}.
+     *
+     * @param closure
+     */
     void module(Closure closure) {
         ConfigureUtil.configure(closure, getModule())
     }
 
+    /**
+     * Configures IDEA project information.
+     * <p>
+     * For examples see docs for {@link IdeaProject}.
+     *
+     * @param closure
+     */
     void project(Closure closure) {
         ConfigureUtil.configure(closure, getProject())
     }
-}
\ No newline at end of file
+
+    /**
+     * Configures IDEA workspace information.
+     * <p>
+     * For examples see docs for {@link IdeaWorkspace}.
+     *
+     * @param closure
+     */
+    void workspace(Closure closure) {
+        ConfigureUtil.configure(closure, getWorkspace())
+    }
+
+    /**
+     * Adds path variables to be used for replacing absolute paths in resulting files (*.iml, etc.).
+     * <p>
+     * For example see docs for {@link IdeaModule}.
+     *
+     * @param pathVariables A map with String->File pairs.
+     */
+    void pathVariables(Map<String, File> pathVariables) {
+        assert pathVariables != null
+        module.pathVariables.putAll pathVariables
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
index 9f51134..4e878cc 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
@@ -17,20 +17,21 @@
 package org.gradle.plugins.ide.idea.model
 
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.dsl.ConventionProperty
 import org.gradle.plugins.ide.idea.model.internal.IdeaDependenciesProvider
 import org.gradle.util.ConfigureUtil
 
 /**
- * Model for an IDEA module.
+ * Enables fine-tuning module details (*.iml file) of the IDEA plugin .
  * <p>
  * Example of use with a blend of all possible properties.
- * Bear in mind that usually you don't have configure this model directly because Gradle configures it for free!
+ * Typically you don't have configure this model directly because Gradle configures it for you.
  *
  * <pre autoTested=''>
  * apply plugin: 'java'
  * apply plugin: 'idea'
  *
- * //for the sake of the example lets have a 'provided' dependency configuration
+ * //for the sake of this example, let's introduce a 'provided' configuration
  * configurations {
  *   provided
  *   provided.extendsFrom(compile)
@@ -41,6 +42,10 @@ import org.gradle.util.ConfigureUtil
  * }
  *
  * idea {
+ *
+ *   //if you want parts of paths in resulting files (*.iml, etc.) to be replaced by variables (Files)
+ *   pathVariables GRADLE_HOME: file('~/cool-software/gradle')
+ *
  *   module {
  *     //if for some reason you want to add an extra sourceDirs
  *     sourceDirs += file('some-extra-source-folder')
@@ -51,7 +56,7 @@ import org.gradle.util.ConfigureUtil
  *     //and some extra dirs that should be excluded by IDEA
  *     excludeDirs += file('some-extra-exclude-dir')
  *
- *     //if you don't like the name Gradle have chosen
+ *     //if you don't like the name Gradle has chosen
  *     name = 'some-better-name'
  *
  *     //if you prefer different output folders
@@ -59,26 +64,38 @@ import org.gradle.util.ConfigureUtil
  *     outputDir = file('muchBetterOutputDir')
  *     testOutputDir = file('muchBetterTestOutputDir')
  *
- *     //if you prefer different java version than inherited from IDEA project
- *     javaVersion = '1.6'
+ *     //if you prefer different SDK than that inherited from IDEA project
+ *     jdkName = '1.6'
  *
- *     //if you need to put provided dependencies on the classpath
+ *     //if you need to put 'provided' dependencies on the classpath
  *     scopes.PROVIDED.plus += configurations.provided
  *
  *     //if 'content root' (as IDEA calls it) of the module is different
  *     contentRoot = file('my-module-content-root')
  *
- *     //if you love browsing javadocs
+ *     //if you love browsing Javadoc
  *     downloadJavadoc = true
  *
  *     //and hate reading sources :)
  *     downloadSources = false
+ *   }
+ * }
+ * </pre>
+ *
+ * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * It is also possible to affect the way the IDEA plugin merges the existing configuration
+ * via beforeMerged and whenMerged closures.
+ * <p>
+ * beforeMerged and whenMerged closures receive {@link Module} object
+ * <p>
+ * Examples of advanced configuration:
  *
- *     //if you want parts of paths in resulting *.iml to be replaced by variables (files)
- *     variables = [GRADLE_HOME: file('~/cool-software/gradle')]
- *     //pathVariables
- *     //TODO SF: think about moving the pathVariables to the upper level
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'idea'
  *
+ * idea {
+ *   module {
  *     iml {
  *       //if you like to keep *.iml in a secret folder
  *       generateTo = file('secret-modules-folder')
@@ -90,9 +107,6 @@ import org.gradle.util.ConfigureUtil
  *         node.appendNode('butAlso', 'I find increasing pleasure tinkering with output *.iml contents. Yeah!!!')
  *       }
  *
- *       //beforeMerged and whenMerged closures are the highest voodoo
- *       //and probably should be used only to solve tricky edge cases:
- *
  *       //closure executed after *.iml content is loaded from existing file
  *       //but before gradle build information is merged
  *       beforeMerged { module ->
@@ -103,10 +117,7 @@ import org.gradle.util.ConfigureUtil
  *       //closure executed after *.iml content is loaded from existing file
  *       //and after gradle build information is merged
  *       whenMerged { module ->
- *         //If you really want to update the javaVersion
- *         module.javaVersion = '1.6'
- *         //but you don't want to do it here...
- *         //because you can do it much easier in idea.module configuration!
+ *         //you can tinker with {@link Module}
  *       }
  *     }
  *   }
@@ -114,12 +125,29 @@ import org.gradle.util.ConfigureUtil
  *
  * </pre>
  *
- * Author: Szczepan Faber, created at: 3/31/11
+ * @author Szczepan Faber, created at: 3/31/11
  */
 class IdeaModule {
 
    /**
-     * IDEA module name; controls the name of the *.iml file
+     * Configures module name, that is the name of the *.iml file.
+     * <p>
+     * It's <b>optional</b> because the task should configure it correctly for you.
+     * By default it will try to use the <b>project.name</b> or prefix it with a part of a <b>project.path</b>
+     * to make sure the module name is unique in the scope of a multi-module build.
+     * The 'uniqueness' of a module name is required for correct import
+     * into IDEA and the task will make sure the name is unique.
+     * <p>
+     * <b>since</b> 1.0-milestone-2
+     * <p>
+     * If your project has problems with unique names it is recommended to always run <tt>gradle idea</tt> from the root, i.e. for all subprojects.
+     * If you run the generation of the IDEA module only for a single subproject then you may have different results
+     * because the unique names are calculated based on IDEA modules that are involved in the specific build run.
+     * <p>
+     * If you update the module names then make sure you run <tt>gradle idea</tt> from the root, e.g. for all subprojects, including generation of IDEA project.
+     * The reason is that there may be subprojects that depend on the subproject with amended module name.
+     * So you want them to be generated as well because the module dependencies need to refer to the amended project name.
+     * Basically, for non-trivial projects it is recommended to always run <tt>gradle idea</tt> from the root.
      * <p>
      * For example see docs for {@link IdeaModule}
      */
@@ -189,9 +217,7 @@ class IdeaModule {
     Set<File> testSourceDirs
 
     /**
-     * The directories to be excluded.
-     * <p>
-     * Warning - it is a {@link org.gradle.api.dsl.ConvenienceProperty}
+     * {@link ConventionProperty} for the directories to be excluded.
      * <p>
      * For example see docs for {@link IdeaModule}
      */
@@ -199,7 +225,7 @@ class IdeaModule {
 
     /**
      * If true, output directories for this module will be located below the output directory for the project;
-     * otherwise, they will be set to the directories specified by {@link #outputDir} and {@link #testOutputDir}.
+     * otherwise, they will be set to the directories specified by #outputDir and #testOutputDir.
      * <p>
      * For example see docs for {@link IdeaModule}
      */
@@ -225,7 +251,7 @@ class IdeaModule {
      * <p>
      * For example see docs for {@link IdeaModule}
      */
-    Map<String, File> variables = [:]
+    Map<String, File> pathVariables = [:]
 
     /**
      * The JDK to use for this module. If {@code null}, the value of the existing or default ipr XML (inherited)
@@ -234,7 +260,12 @@ class IdeaModule {
      * <p>
      * For example see docs for {@link IdeaModule}
      */
-    String javaVersion = Module.INHERITED
+    String jdkName
+
+    /**
+     * See {@link #iml(Closure) }
+     */
+    final IdeaModuleIml iml
 
     /**
      * Enables advanced configuration like tinkering with the output xml
@@ -246,65 +277,68 @@ class IdeaModule {
         ConfigureUtil.configure(closure, getIml())
     }
 
-    //TODO SF: most likely what's above should be a part of an interface and what's below should not be exposed.
-    //For now, below methods are protected - same applies to new model
-
-    org.gradle.api.Project project
-    Module xmlModule
-    PathFactory pathFactory
-    IdeaModuleIml iml
-
+    /**
+     * Configures output *.iml file. It's <b>optional</b> because the task should configure it correctly for you
+     * (including making sure it is unique in the multi-module build).
+     * If you really need to change the output file name (or the module name) it is much easier to do it via the <b>moduleName</b> property!
+     * <p>
+     * Please refer to documentation on <b>moduleName</b> property. In IntelliJ IDEA the module name is the same as the name of the *.iml file.
+     */
     File getOutputFile() {
         new File((File) iml.getGenerateTo(), getName() + ".iml")
     }
 
     void setOutputFile(File newOutputFile) {
-        name = newOutputFile.name.replaceFirst(/\.iml$/,"");
+        setName(newOutputFile.name.replaceFirst(/\.iml$/,""))
         iml.generateTo = newOutputFile.parentFile
     }
 
-    Set<Path> getSourcePaths() {
-        getSourceDirs().findAll { it.exists() }.collect { path(it) }
-    }
-
-    Set<Dependency> getDependencies() {
-        new IdeaDependenciesProvider().provide(this, getPathFactory());
+    /**
+     * Resolves and returns the module's dependencies.
+     *
+     * @return dependencies
+     */
+    Set<Dependency> resolveDependencies() {
+        return new IdeaDependenciesProvider().provide(this)
     }
 
-    Set<Path> getTestSourcePaths() {
-        getTestSourceDirs().findAll { it.exists() }.collect { getPathFactory().path(it) }
-    }
+    /**
+     * An owner of this IDEA module.
+     * <p>
+     * If IdeaModule requires some information from gradle this field should not be used for this purpose.
+     * IdeaModule instances should be configured with all necessary information by the plugin or user.
+     */
+    final org.gradle.api.Project project
+    PathFactory pathFactory
 
-    Set<Path> getExcludePaths() {
-        getExcludeDirs().collect { path(it) }
-    }
+    /**
+     * If true then external artifacts (e.g. those found in repositories) will not be included in the resulting classpath
+     * (only project and local file dependencies will be included).
+     */
+    boolean offline
 
-    Path getOutputPath() {
-        getOutputDir() ? path(getOutputDir()) : null
-    }
+    Map<String, Collection<File>> singleEntryLibraries
 
-    Path getTestOutputPath() {
-        getTestOutputDir() ? path(getTestOutputDir()) : null
+    IdeaModule(org.gradle.api.Project project, IdeaModuleIml iml) {
+        this.project = project
+        this.iml = iml
     }
 
     void mergeXmlModule(Module xmlModule) {
         iml.beforeMerged.execute(xmlModule)
-        xmlModule.configure(getContentPath(), getSourcePaths(), getTestSourcePaths(), getExcludePaths(),
-                getInheritOutputDirs(), getOutputPath(), getTestOutputPath(), getDependencies(), getJavaVersion())
-        iml.whenMerged.execute(xmlModule)
-
-        this.xmlModule = xmlModule
-    }
 
-    void generate() {
-        xmlModule.store(getOutputFile())
-    }
+        def path = { getPathFactory().path(it) }
+        def contentRoot = path(getContentRoot())
+        Set sourceFolders = getSourceDirs().findAll { it.exists() }.collect { path(it) }
+        Set testSourceFolders = getTestSourceDirs().findAll { it.exists() }.collect { path(it) }
+        Set excludeFolders = getExcludeDirs().collect { path(it) }
+        def outputDir = getOutputDir() ? path(getOutputDir()) : null
+        def testOutputDir = getTestOutputDir() ? path(getTestOutputDir()) : null
+        Set dependencies = resolveDependencies()
 
-    Path getContentPath() {
-        path(getContentRoot())
-    }
+        xmlModule.configure(contentRoot, sourceFolders, testSourceFolders, excludeFolders,
+                getInheritOutputDirs(), outputDir, testOutputDir, dependencies, getJdkName())
 
-    Path path(File dir) {
-        getPathFactory().path(dir)
+        iml.whenMerged.execute(xmlModule)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
index fa5ec29..66770ab 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModuleIml.groovy
@@ -17,66 +17,20 @@
 package org.gradle.plugins.ide.idea.model
 
 import org.gradle.api.internal.XmlTransformer
-import org.gradle.listener.ActionBroadcast
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 
 /**
- * Models the generation/parsing/merging capabilities of idea module
+ * Models the generation/parsing/merging capabilities of an IDEA module.
  * <p>
- * For example see docs for {@link IdeaModule}
- * <p>
- * Author: Szczepan Faber, created at: 4/5/11
+ * For examples, see docs for {@link IdeaModule}.
+ *
+ * @author: Szczepan Faber, created at: 4/5/11
  */
-class IdeaModuleIml {
-
-    ActionBroadcast whenMerged = new ActionBroadcast()
-    ActionBroadcast beforeMerged = new ActionBroadcast()
-    XmlTransformer xmlTransformer
-
-    /**
-     * Adds a closure to be called after *.iml content is loaded from existing file
-     * but before gradle build information is merged
-     * <p>
-     * This is advanced api that gives access to internal implementation of idea plugin.
-     * It might be useful if you want to alter the way gradle build information is merged into existing *.iml content
-     * <p>
-     * The {@link Module} object is passed as a parameter to the closure
-     * <p>
-     * For example see docs for {@link IdeaModule}
-     *
-     * @param closure The closure to execute.
-     */
-    public void beforeMerged(Closure closure) {
-        beforeMerged.add(closure)
-    }
+class IdeaModuleIml extends XmlFileContentMerger {
 
-    /**
-     * Adds a closure to be called after *.iml content is loaded from existing file
-     * and after gradle build information is merged
-     * <p>
-     * This is advanced api that gives access to internal implementation of idea plugin.
-     * Use it only to tackle some tricky edge cases.
-     * <p>
-     * The {@link Module} object is passed as a parameter to the closure
-     * <p>
-     * For example see docs for {@link IdeaModule}
-     *
-     * @param closure The closure to execute.
-     */
-    public void whenMerged(Closure closure) {
-        whenMerged.add(closure)
-    }
-
-    /**
-     * Adds a closure to be called when the *.iml file 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.
-     * <p>
-     * For example see docs for {@link IdeaModule}
-     *
-     * @param closure The closure to execute when the XML has been created.
-     */
-    public void withXml(Closure closure) {
-        xmlTransformer.addAction(closure)
+    IdeaModuleIml(XmlTransformer xmlTransformer, File generateTo) {
+        super(xmlTransformer)
+        this.generateTo = generateTo
     }
 
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
index 6777556..6660143 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
@@ -16,14 +16,14 @@
 
 package org.gradle.plugins.ide.idea.model
 
-import org.gradle.plugins.ide.idea.IdeaPlugin
+import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.util.ConfigureUtil
 
 /**
- * Model for idea project.
+ * Enables fine-tuning project details (*.ipr file) of the IDEA plugin.
  * <p>
  * Example of use with a blend of all possible properties.
- * Bear in mind that usually you don't have configure idea module directly because Gradle configures it for free!
+ * Typically you don't have configure IDEA module directly because Gradle configures it for you.
  *
  * <pre autoTested=''>
  * apply plugin: 'java'
@@ -31,67 +31,114 @@ import org.gradle.util.ConfigureUtil
  *
  * idea {
  *   project {
- *     //if you want to set specific java version for the idea project
- *     javaVersion = '1.5'
+ *     //if you want to set specific jdk and language level
+ *     jdkName = '1.6'
+ *     languageLevel = '1.5'
  *
  *     //you can update the source wildcards
  *     wildcards += '!?*.ruby'
  *
- *     //you can update the project list that will make the modules list in the *.ipr
- *     //subprojects -= project(':someProjectThatWillBeExcluded')
+ *     //you can change the modules of the the *.ipr
+ *     //modules = project(':someProject').idea.module
  *
  *     //you can change the output file
  *     outputFile = new File(outputFile.parentFile, 'someBetterName.ipr')
+ *   }
+ * }
+ * </pre>
  *
- *     //you can apply advanced logic to the xml generation/merging
- *     ipr {
+ * For tackling edge cases users can perform advanced configuration on resulting xml file.
+ * It is also possible to affect the way IDEA plugin merges the existing configuration
+ * via beforeMerged and whenMerged closures.
+ * <p>
+ * beforeMerged and whenMerged closures receive {@link Project} object
+ * <p>
+ * Examples of advanced configuration:
  *
- *       //you can tinker with the output *.ipr file before it's written to file
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'idea'
+ *
+ * idea {
+ *   project {
+ *     ipr {
+ *       //you can tinker with the output *.ipr file before it's written out
  *       withXml {
  *         def node = it.asNode()
  *         node.appendNode('iLove', 'tinkering with the output *.ipr file!')
  *       }
+ *
+ *       //closure executed after *.ipr content is loaded from existing file
+ *       //but before gradle build information is merged
+ *       beforeMerged { project ->
+ *         //you can tinker with {@link Project}
+ *       }
+ *
+ *       //closure executed after *.ipr content is loaded from existing file
+ *       //and after gradle build information is merged
+ *       whenMerged { project ->
+*         //you can tinker with {@link Project}
+ *       }
  *     }
  *   }
  * }
  * </pre>
  *
- * Author: Szczepan Faber, created at: 4/4/11
+ * @author Szczepan Faber, created at: 4/4/11
  */
 class IdeaProject {
 
     /**
-     * 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.
+     * A {@link org.gradle.api.dsl.ConventionProperty} that holds modules for the ipr file.
      * <p>
      * See the examples in the docs for {@link IdeaProject}
      */
-    Set<org.gradle.api.Project> subprojects
+    List<IdeaModule> modules
 
     /**
      * The java version used for defining the project sdk.
      * <p>
      * See the examples in the docs for {@link IdeaProject}
      */
-    String javaVersion
+    String jdkName
+
+    /**
+     * The java language level of the project.
+     * Pass a valid Java version number (e.g. '1.5') or IDEA language level (e.g. 'JDK_1_5').
+     * <p>
+     * See the examples in the docs for {@link IdeaProject}.
+     */
+    IdeaLanguageLevel languageLevel
+
+    void setLanguageLevel(Object languageLevel) {
+        this.languageLevel = new IdeaLanguageLevel(languageLevel)
+    }
 
     /**
      * The wildcard resource patterns.
      * <p>
-     * See the examples in the docs for {@link IdeaProject}
+     * See the examples in the docs for {@link IdeaProject}.
      */
-    Set wildcards
+    Set<String> wildcards
 
     /**
      * Output *.ipr
      * <p>
-     * See the examples in the docs for {@link IdeaProject}
+     * See the examples in the docs for {@link IdeaProject}.
      */
     File outputFile
 
     /**
+     * The name of the IDEA project. It is a convenience property that returns the name of the output file (without the file extension).
+     * In IDEA, the project name is driven by the name of the 'ipr' file.
+     */
+    String getName() {
+       getOutputFile().name.replaceFirst(/\.ipr$/, '')
+    }
+
+    /**
      * Enables advanced configuration like tinkering with the output xml
-     * or affecting the way existing *.ipr content is merged with gradle build information
+     * or affecting the way existing *.ipr content is merged with Gradle build information.
      * <p>
      * See the examples in the docs for {@link IdeaProject}
      */
@@ -99,21 +146,23 @@ class IdeaProject {
         ConfigureUtil.configure(closure, getIpr())
     }
 
-    //******
+    /**
+     * See {@link #ipr(Closure) }
+     */
+    final XmlFileContentMerger ipr
 
-    Project xmlProject
     PathFactory pathFactory
-    IdeaProjectIpr ipr
+
+    IdeaProject(XmlFileContentMerger ipr) {
+        this.ipr = ipr
+    }
 
     void mergeXmlProject(Project xmlProject) {
-        this.xmlProject = xmlProject
-        def modulePaths = getSubprojects().inject(new LinkedHashSet()) { result, subproject ->
-            if (subproject.plugins.hasPlugin(IdeaPlugin)) {
-                File imlFile = subproject.ideaModule.outputFile
-                result << new ModulePath(getPathFactory().relativePath('PROJECT_DIR', imlFile))
-            }
-            result
+        ipr.beforeMerged.execute(xmlProject)
+        def modulePaths = getModules().collect {
+            getPathFactory().relativePath('PROJECT_DIR', it.outputFile)
         }
-        xmlProject.configure(modulePaths, getJavaVersion(), getWildcards())
+        xmlProject.configure(modulePaths, getJdkName(), getLanguageLevel(), getWildcards())
+        ipr.whenMerged.execute(xmlProject)
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProjectIpr.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProjectIpr.groovy
deleted file mode 100644
index 2ff9614..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProjectIpr.groovy
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2011 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.ide.idea.model
-
-import org.gradle.api.internal.XmlTransformer
-
-/**
- * Models the generation/parsing/merging capabilities of idea project
- * <p>
- * For example see docs for {@link IdeaProject}
- * <p>
- * Author: Szczepan Faber, created at: 4/5/11
- */
-class IdeaProjectIpr {
-    XmlTransformer xmlTransformer
-
-    /**
-     * Adds a closure to be called when the *.ipr 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.
-     * <p>
-     * For example see docs for {@link IdeaProject}
-     *
-     * @param closure The closure to execute when the XML has been created.
-     */
-    public void withXml(Closure closure) {
-        xmlTransformer.addAction(closure);
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy
new file mode 100644
index 0000000..98541e8
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaWorkspace.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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.ide.idea.model
+
+import org.gradle.plugins.ide.api.XmlFileContentMerger
+import org.gradle.util.ConfigureUtil
+
+/**
+ * Enables fine-tuning workspace details (*.iws file) of the IDEA plugin.
+ * <p>
+ * At the moment, the only practical way of manipulating the resulting content is via the withXml hook:
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ * apply plugin: 'idea'
+ *
+ * idea.workspace.iws.withXml { provider ->
+ *     provider.asNode().appendNode('gradleRocks', 'true')
+ * }
+ * </pre>
+ *
+ * @author Szczepan Faber, created at: 06/09/11
+ */
+class IdeaWorkspace {
+
+    /**
+     * Enables advanced manipulation of the output xml.
+     * <p>
+     * For example see docs for {@link IdeaWorkspace}
+     *
+     * @param closure
+     */
+    void iws(Closure closure) {
+        ConfigureUtil.configure(closure, getIws())
+    }
+
+    /**
+     * Enables advanced manipulation of the output xml.
+     * <p>
+     * For example see docs for {@link IdeaWorkspace}
+     */
+    XmlFileContentMerger iws
+
+    void mergeXmlWorkspace(Workspace xmlWorkspace) {
+        iws.beforeMerged.execute(xmlWorkspace)
+
+        //we don't merge anything in the iws, yet.
+        //I kept the logic for the sake of consistency
+        // and compatibility with pre M4 ways of configuring IDEA information.
+
+        iws.whenMerged.execute(xmlWorkspace)
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy
index 88a3c75..b681913 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Jdk.groovy
@@ -16,7 +16,7 @@
 package org.gradle.plugins.ide.idea.model
 
 /**
- * Represents a the information for the project JavaSDK. This information are attributes of the ProjectRootManager
+ * Represents information for the project Java SDK. This translates to attributes of the ProjectRootManager
  * element in the ipr.
  * 
  * @author Hans Dockter
@@ -27,26 +27,20 @@ class Jdk {
     String languageLevel
     String projectJdkName
 
-    def Jdk(String javaVersion) {
-        if (javaVersion.startsWith("1.4")) {
+    def Jdk(String jdkName, IdeaLanguageLevel ideaLanguageLevel) {
+        if (jdkName.startsWith("1.4")) {
             assertKeyword = true
             jdk15 = false
-            languageLevel = 'JDK_1_4'
         }
-        else if (javaVersion.startsWith("1.5")) {
+        else if (jdkName >= '1.5') {
             assertKeyword = true
             jdk15 = true
-            languageLevel = 'JDK_1_5'
-        }
-        else if (javaVersion >= '1.6') {
-            assertKeyword = true
-            jdk15 = true
-            languageLevel = 'JDK_1_6'
         }
         else {
             assertKeyword = false
         }
-        projectJdkName = javaVersion
+        languageLevel = ideaLanguageLevel.level
+        projectJdkName = jdkName
     }
 
     def Jdk(assertKeyword, jdk15, languageLevel, projectJdkName) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
index 8369a72..f835879 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
@@ -17,6 +17,7 @@ package org.gradle.plugins.ide.idea.model
 
 import org.gradle.api.internal.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
+import org.gradle.util.DeprecationLogger
 
 /**
  * Represents the customizable elements of an iml (via XML hooks everything of the iml is customizable).
@@ -68,7 +69,17 @@ class Module extends XmlPersistableConfigurationObject {
      */
     Set<Dependency> dependencies = [] as LinkedHashSet
 
-    String javaVersion
+    String jdkName
+
+    String getJavaVersion() {
+        DeprecationLogger.nagUserOfReplacedMethod("javaVersion", "jdkName")
+        jdkName
+    }
+
+    void setJavaVersion(String jdkName) {
+        DeprecationLogger.nagUserOfReplacedMethod("javaVersion", "jdkName")
+        this.jdkName = jdkName
+    }
 
     private final PathFactory pathFactory
 
@@ -91,7 +102,7 @@ class Module extends XmlPersistableConfigurationObject {
 
     private readJdkFromXml() {
         def jdk = findOrderEntries().find { it. at type == 'jdk' }
-        javaVersion = jdk ? jdk. at jdkName : INHERITED
+        jdkName = jdk ? jdk. at jdkName : INHERITED
     }
 
     private readSourceAndExcludeFolderFromXml() {
@@ -141,7 +152,8 @@ class Module extends XmlPersistableConfigurationObject {
         }
     }
 
-    protected def configure(Path contentPath, Set sourceFolders, Set testSourceFolders, Set excludeFolders, Boolean inheritOutputDirs, Path outputDir, Path testOutputDir, Set dependencies, String javaVersion) {
+    protected def configure(Path contentPath, Set sourceFolders, Set testSourceFolders, Set excludeFolders,
+                            Boolean inheritOutputDirs, Path outputDir, Path testOutputDir, Set dependencies, String jdkName) {
         this.contentPath = contentPath
         this.sourceFolders.addAll(sourceFolders)
         this.testSourceFolders.addAll(testSourceFolders)
@@ -156,8 +168,10 @@ class Module extends XmlPersistableConfigurationObject {
             this.testOutputDir = testOutputDir
         }
         this.dependencies = dependencies; // overwrite rather than append dependencies
-        if (javaVersion) {
-            this.javaVersion = javaVersion
+        if (jdkName) {
+            this.jdkName = jdkName
+        } else {
+            this.jdkName = Module.INHERITED
         }
     }
 
@@ -174,9 +188,9 @@ class Module extends XmlPersistableConfigurationObject {
     }
 
     private addJdkToXml() {
-        assert javaVersion != null
+        assert jdkName != null
         Node moduleJdk = findOrderEntries().find { it. at type == 'jdk' }
-        if (javaVersion != INHERITED) {
+        if (jdkName != INHERITED) {
             Node inheritedJdk = findOrderEntries().find { it. at type == "inheritedJdk" }
             if (inheritedJdk) {
                 inheritedJdk.parent().remove(inheritedJdk)
@@ -184,7 +198,7 @@ class Module extends XmlPersistableConfigurationObject {
             if (moduleJdk) {
                 findNewModuleRootManager().remove(moduleJdk)
             }
-            findNewModuleRootManager().appendNode("orderEntry", [type: "jdk", jdkName: javaVersion, jdkType: "JavaSDK"])
+            findNewModuleRootManager().appendNode("orderEntry", [type: "jdk", jdkName: jdkName, jdkType: "JavaSDK"])
         } else if (!(findOrderEntries().find { it. at type == "inheritedJdk" })) {
             if (moduleJdk) {
                 findNewModuleRootManager().remove(moduleJdk)
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
index c750be4..eaa7d5d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
@@ -22,37 +22,37 @@ package org.gradle.plugins.ide.idea.model
  */
 class ModuleLibrary implements Dependency {
     /**
-     * A set of  {@link org.gradle.plugins.ide.idea.model.Path}  instances for class libraries.
+     * A set of {@link Path} instances for class libraries. Can be paths to jars or class folders.
      */
-    Set classes
+    Set<Path> classes
 
     /**
-     * A set of  {@link org.gradle.plugins.ide.idea.model.JarDirectory}  instances for directories containing jars.
+     * A set of {@link JarDirectory} instances for directories containing jars.
      */
-    Set jarDirectories
+    Set<JarDirectory> jarDirectories
 
     /**
-     * A set of  {@link org.gradle.plugins.ide.idea.model.Path}  instances for javadoc associated with the library elements.
+     * A set of {@link Path} instances for javadoc associated with the library elements.
      */
-    Set javadoc
+    Set<Path> javadoc
 
     /**
-     * A set of  {@link org.gradle.plugins.ide.idea.model.Path}  instances for source code associated with the library elements.
+     * A set of {@link Path} instances for source code associated with the library elements.
      */
-    Set sources
+    Set<Path> sources
 
     /**
-     * The scope of this dependency. If null the scope attribute is not added.
+     * The scope of this dependency. If <tt>null</tt>, the scope attribute is not added.
      */
     String scope
 
     boolean exported
 
-    def ModuleLibrary(classes, javadoc, sources, jarDirectories, scope) {
-        this.classes = classes;
-        this.jarDirectories = jarDirectories;
-        this.javadoc = javadoc;
-        this.sources = sources;
+    def ModuleLibrary(Collection<Path> classes, Collection<Path> javadoc, Collection<Path> sources, Collection<JarDirectory> jarDirectories, String scope) {
+        this.classes = classes as Set;
+        this.jarDirectories = jarDirectories as Set;
+        this.javadoc = javadoc as Set;
+        this.sources = sources as Set;
         this.scope = scope
         this.exported = !scope || scope == 'COMPILE' || scope == 'RUNTIME'
     }
@@ -80,7 +80,6 @@ class ModuleLibrary implements Dependency {
         return (exported ? [exported: ""] : [:]) + ((scope && scope != 'COMPILE') ? [scope: scope] : [:])
     }
 
-
     boolean equals(o) {
         if (this.is(o)) { return true }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModulePath.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModulePath.groovy
deleted file mode 100644
index cf6a5ee..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModulePath.groovy
+++ /dev/null
@@ -1,65 +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.ide.idea.model
-
-/**
- * Represents a path in a format as used often in ipr and iml files.
- *
- * @author Hans Dockter
- */
-
-class ModulePath {
-    /**
-     * The path string of this path.
-     */
-    final String filePath
-
-    final Path path
-
-    def ModulePath(Path path) {
-        this.path = path
-        filePath = path.relPath
-    }
-
-    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 }
-
-        ModulePath that = (ModulePath) o;
-        return path == that.path && filePath == that.filePath
-    }
-
-    int hashCode() {
-        return path.hashCode() ^ filePath.hashCode()
-    }
-
-    public String toString() {
-        return "ModulePath{" +
-                "path='" + path +
-                ", filePath='" + filePath + '\'' +
-                '}';
-    }
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
index 45c989c..5b37165 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
@@ -31,107 +31,34 @@ class Path {
      */
     final String relPath
 
+    /**
+     * Canonical url
+     */
     final String canonicalUrl
 
-    Path(File rootDir, String rootDirString, File file) {
-        relPath = getRelativePath(rootDir, rootDirString, file)
-        url = relativePathToURI(relPath)
-        canonicalUrl = relativePathToURI(file.absolutePath.replace(File.separator, '/'))
-    }
-
-    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
-    }
-
     Path(String url) {
-        this(url, url)
+        this(url, url, null)
     }
 
-    Path(String url, String canonicalUrl) {
-        this.relPath = null
+    Path(String url, String canonicalUrl, String relPath) {
+        this.relPath = relPath
         this.url = url
         this.canonicalUrl = canonicalUrl
     }
 
-    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 static String relativePathToURI(String relpath) {
-        if (relpath.endsWith('.jar')) {
-            return 'jar://' + relpath + '!/';
-        } else {
-            return 'file://' + relpath;
-        }
-    }
-
-    // This gets a relative path even if neither path is an ancestor of the other.
-    // implementation taken from http://www.devx.com/tips/Tip/13737 and slighly modified
-    //@param relativeTo  the destinationFile
-    //@param fromFile    where the relative path starts
-
-    private static String getRelativePath(File relativeTo, File fromFile) {
-        return matchPathLists(getPathList(relativeTo), getPathList(fromFile))
-    }
-
-    private static List getPathList(File f) {
-        List list = []
-        File r = f.canonicalFile
-        while (r != null) {
-            File parent = r.parentFile
-            list.add(parent ? r.name : r.absolutePath)
-            r = parent
-        }
-
-        return list
-    }
-
-    private static String matchPathLists(List r, List f) {
-        StringBuilder s = new StringBuilder();
-
-        // eliminate the common root
-        int i = r.size() - 1
-        int j = f.size() - 1
-
-        if (r[i] != f[j]) {
-            // no common root
-            return null
-        }
-
-        while ((i >= 0) && (j >= 0) && (r[i] == f[j])) {
-            i--
-            j--
-        }
-
-        // for each remaining level in the relativeTo path, add a ..
-        for (; i >= 0; i--) {
-            s.append('../')
-        }
-
-        // for each level in the file path, add the path
-        for (; j >= 1; j--) {
-            s.append(f[j]).append('/')
+    boolean equals(o) {
+        if (this.is(o)) {
+            return true
         }
-        // add the file name
-        if (j == 0) {
-            s.append(f[j])
+        if (!(o instanceof Path)) {
+            return false
         }
 
-        return s.toString()
-    }
-
-    boolean equals(o) {
-        if (this.is(o)) { return true }
-
-        if (o == null || getClass() != o.class) { return false }
-
         Path path = (Path) o;
 
-        if (canonicalUrl != path.canonicalUrl) { return false }
+        if (canonicalUrl != path.canonicalUrl) {
+            return false
+        }
 
         return true;
     }
@@ -143,7 +70,6 @@ class Path {
     public String toString() {
         return "Path{" +
                 "url='" + url + '\'' +
-                ", canonicalUrl='" + canonicalUrl + '\'' +
                 '}';
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/PathFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/PathFactory.groovy
index ddcbad5..c7735e8 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/PathFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/PathFactory.groovy
@@ -28,11 +28,7 @@ class PathFactory {
     /**
      * Creates a path for the given file.
      */
-    Path path(File file) {
-        createFile(file.canonicalFile)
-    }
-
-    private Path createFile(File file) {
+    FilePath path(File file) {
         Map match = null
         for (variable in variables) {
             if (file.absolutePath == variable.dir.absolutePath) {
@@ -47,23 +43,39 @@ class PathFactory {
         }
 
         if (match) {
-            return new Path(match.dir, match.name, file)
+            return relativePath(match.dir, match.name, file)
         }
 
-        return new Path(file)
+        // IDEA doesn't like the result of file.toURI() so use the absolute path instead
+        def relPath = file.absolutePath.replace(File.separator, '/')
+        def url = relativePathToURI(relPath)
+        return new FilePath(file, url, url, relPath)
     }
 
     /**
      * Creates a path relative to the given path variable.
      */
-    Path relativePath(String pathVar, File file) {
-        return new Path(varsByName[pathVar], "\$${pathVar}\$", file)
+    FilePath relativePath(String pathVar, File file) {
+        return relativePath(varsByName[pathVar], "\$$pathVar\$", file)
     }
 
+    private FilePath relativePath(File rootDir, String rootDirName, File file) {
+        def relPath = getRelativePath(rootDir, rootDirName, file)
+        def url = relativePathToURI(relPath)
+        def canonicalUrl = relativePathToURI(file.absolutePath.replace(File.separator, '/'))
+        return new FilePath(file, url, canonicalUrl, relPath)
+    }
     /**
      * Creates a path for the given URL.
      */
     Path path(String url) {
+        return path(url, null)
+    }
+
+    /**
+     * Creates a path for the given URL.
+     */
+    Path path(String url, String relPath) {
         String expandedUrl = url
         for (variable in variables) {
             expandedUrl = expandedUrl.replace(variable.name, variable.prefix)
@@ -76,10 +88,69 @@ class PathFactory {
                 expandedUrl = toUrl('jar', new File(parts[0]).canonicalFile) + '!' + parts[1]
             }
         }
-        return new Path(url, expandedUrl)
+        return new Path(url, expandedUrl, relPath)
     }
 
-    def toUrl(String scheme, File file) {
+    private def toUrl(String scheme, File file) {
         return scheme + '://' + file.absolutePath.replace(File.separator, '/')
     }
+
+    private static String getRelativePath(File rootDir, String rootDirString, File file) {
+        String relpath = matchPathLists(getPathList(rootDir), getPathList(file))
+        return relpath != null ? rootDirString + '/' + relpath : file.absolutePath.replace(File.separator, '/')
+    }
+
+    private static String relativePathToURI(String relpath) {
+        if (relpath.endsWith('.jar')) {
+            return 'jar://' + relpath + '!/';
+        } else {
+            return 'file://' + relpath;
+        }
+    }
+
+    private static List getPathList(File f) {
+        List list = []
+        File r = f.canonicalFile
+        while (r != null) {
+            File parent = r.parentFile
+            list.add(parent ? r.name : r.absolutePath)
+            r = parent
+        }
+
+        return list
+    }
+
+    private static String matchPathLists(List r, List f) {
+        StringBuilder s = new StringBuilder();
+
+        // eliminate the common root
+        int i = r.size() - 1
+        int j = f.size() - 1
+
+        if (r[i] != f[j]) {
+            // no common root
+            return null
+        }
+
+        while ((i >= 0) && (j >= 0) && (r[i] == f[j])) {
+            i--
+            j--
+        }
+
+        // for each remaining level in the relativeTo path, add a ..
+        for (; i >= 0; i--) {
+            s.append('../')
+        }
+
+        // for each level in the file path, add the path
+        for (; j >= 1; j--) {
+            s.append(f[j]).append('/')
+        }
+        // add the file name
+        if (j == 0) {
+            s.append(f[j])
+        }
+
+        return s.toString()
+    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
index c839d6f..7a1bc67 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
@@ -27,12 +27,12 @@ class Project extends XmlPersistableConfigurationObject {
     /**
      * A set of {@link Path} instances pointing to the modules contained in the ipr.
      */
-    Set modulePaths = []
+    Set<Path> modulePaths = []
 
     /**
      * A set of wildcard string to be included/excluded from the resources.
      */
-    Set wildcards = []
+    Set<String> wildcards = []
 
     /**
      * Represent the jdk information of the project java sdk.
@@ -46,9 +46,9 @@ class Project extends XmlPersistableConfigurationObject {
         this.pathFactory = pathFactory
     }
 
-    def configure(Set modulePaths, String javaVersion, Set wildcards) {
-        if (javaVersion) {
-            jdk = new Jdk(javaVersion)
+    def configure(Collection<Path> modulePaths, String jdkName, IdeaLanguageLevel languageLevel, Collection<String> wildcards) {
+        if (jdkName) {
+            jdk = new Jdk(jdkName, languageLevel)
         }
         this.modulePaths.addAll(modulePaths)
         this.wildcards.addAll(wildcards)
@@ -56,7 +56,7 @@ class Project extends XmlPersistableConfigurationObject {
 
     @Override protected void load(Node xml) {
         findModules().module.each { module ->
-            this.modulePaths.add(new ModulePath(pathFactory.path(module. at fileurl), module. at filepath))
+            this.modulePaths.add(pathFactory.path(module. at fileurl, module. at filepath))
         }
 
         findWildcardResourcePatterns().entry.each { entry ->
@@ -75,8 +75,8 @@ class Project extends XmlPersistableConfigurationObject {
     @Override protected void store(Node xml) {
         findModules().replaceNode {
             modules {
-                modulePaths.each { ModulePath modulePath ->
-                    module(fileurl: modulePath.path.url, filepath: modulePath.filePath)
+                modulePaths.each { Path modulePath ->
+                    module(fileurl: modulePath.url, filepath: modulePath.relPath)
                 }
             }
         }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy
new file mode 100644
index 0000000..c576f9a
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.ide.idea.model
+
+/**
+ * Single entry module library
+ */
+class SingleEntryModuleLibrary extends ModuleLibrary {
+
+    /**
+     * Creates single entry module library
+     *
+     * @param libraryFile jar or class folder
+     * @param library a path to jar or class folder in idea format
+     * @param javadoc a path to javadoc jar or javadoc folder
+     * @param source a path to source jar or source folder
+     * @param scope scope
+     * @return
+     */
+    SingleEntryModuleLibrary(FilePath library, FilePath javadoc, FilePath source, String scope) {
+        super([library] as Set, [javadoc] - null as Set, [source] - null as Set, [] as Set, scope)
+    }
+
+    /**
+     * Creates single entry module library
+     *
+     * @param libraryFile jar or class folder
+     * @param library a path to jar or class folder in Path format
+     * @param scope scope
+     * @return
+     */
+    SingleEntryModuleLibrary(FilePath library, String scope) {
+        this(library, null, null, scope)
+    }
+
+    /**
+     * Returns a single jar or class folder
+     */
+    File getLibraryFile() {
+        this.classes.iterator().next().file
+    }
+
+    /**
+     * Returns a single javadoc jar or javadoc folder
+     */
+    File getJavadocFile() {
+        if (javadoc.size() > 0) {
+            return this.javadoc.iterator().next().file
+        } else {
+            return null
+        }
+    }
+
+    /**
+     * Returns a single source jar or source folder
+     */
+    File getSourceFile() {
+        if (sources.size() > 0) {
+            return this.sources.iterator().next().file
+        } else {
+            return null
+        }
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
index 67b728b..28902d8 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
@@ -17,184 +17,69 @@
 package org.gradle.plugins.ide.idea.model.internal
 
 import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.Dependency
-import org.gradle.api.artifacts.ExternalDependency
-import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.api.artifacts.ResolvedDependency
-import org.gradle.api.artifacts.SelfResolvingDependency
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.api.specs.Spec
 import org.gradle.plugins.ide.idea.model.IdeaModule
-import org.gradle.plugins.ide.idea.model.ModuleLibrary
-import org.gradle.plugins.ide.idea.model.Path
-import org.gradle.plugins.ide.idea.model.PathFactory
+import org.gradle.plugins.ide.idea.model.SingleEntryModuleLibrary
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
 
 /**
- * All code was refactored from the GenerateIdeaModule class.
- * TODO SF: This class should be refactored so that we can reuse it in Eclipse plugin also.
- *
- * Author: Szczepan Faber, created at: 4/1/11
+ * @author Szczepan Faber, created at: 4/1/11
  */
 class IdeaDependenciesProvider {
 
-    Project project
-    Map<String, Map<String, Collection<Configuration>>> scopes
-    boolean downloadSources
-    boolean downloadJavadoc
-    PathFactory pathFactory
-
-    Set<org.gradle.plugins.ide.idea.model.Dependency> provide(IdeaModule ideaModule, PathFactory pathFactory) {
-        //TODO SF: below assignments are funky but I wanted to make little changes to the code refactored from GenerateIdeaModule
-        this.project = ideaModule.project
-        this.scopes = ideaModule.scopes
-        this.downloadSources = ideaModule.downloadSources
-        this.downloadJavadoc = ideaModule.downloadJavadoc
-        this.pathFactory = pathFactory
+    private final IdeDependenciesExtractor dependenciesExtractor = new IdeDependenciesExtractor()
+    Closure getPath;
 
-        scopes.keySet().inject([] as LinkedHashSet) { result, scope ->
-            result.addAll(getModuleLibraries(scope))
-            result.addAll(getModules(scope))
-            result
-        }
-    }
+    Set<org.gradle.plugins.ide.idea.model.Dependency> provide(IdeaModule ideaModule) {
+        getPath = { File file -> file? ideaModule.pathFactory.path(file) : null }
 
-    protected Set getModules(String scope) {
-        if (scopes[scope]) {
-            return getScopeDependencies(scopes[scope], { it instanceof ProjectDependency }).collect { ProjectDependency dependency ->
-                def project = dependency.dependencyProject
-                return new ModuleDependencyBuilder().create(project, scope)
+        Set result = new LinkedHashSet()
+        ideaModule.singleEntryLibraries.each { scope, files ->
+            files.each {
+                if (it && it.isDirectory()) {
+                    result << new SingleEntryModuleLibrary(getPath(it), scope)
+                }
             }
         }
-        return []
-    }
-
-    protected Set getModuleLibraries(String scope) {
-        if (!scopes[scope]) { return [] }
-
-        def allResolvedDependencies = resolveDependencies(scopes[scope].plus, scopes[scope].minus)
 
-        Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-            addSourceArtifact(dependency)
-        }
-        Map sourceFiles = downloadSources ? getFiles(sourceDependencies, "sources") : [:]
-
-        Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-            addJavadocArtifact(dependency)
-        }
-        Map javadocFiles = downloadJavadoc ? getFiles(javadocDependencies, "javadoc") : [:]
-
-        List moduleLibraries = resolveFiles(scopes[scope].plus, scopes[scope].minus).collect { File binaryFile ->
-            File sourceFile = sourceFiles[binaryFile.name]
-            File javadocFile = javadocFiles[binaryFile.name]
-            new ModuleLibrary([getPath(binaryFile)] as Set, javadocFile ? [getPath(javadocFile)] as Set : [] as Set, sourceFile ? [getPath(sourceFile)] as Set : [] as Set, [] as Set, scope)
-        }
-        moduleLibraries.addAll(getSelfResolvingFiles(getScopeDependencies(scopes[scope],
-                { it instanceof SelfResolvingDependency && !(it instanceof ProjectDependency)}), scope))
-        return moduleLibraries as LinkedHashSet
-    }
-
-    private def getSelfResolvingFiles(Collection dependencies, String scope) {
-        dependencies.inject([] as LinkedHashSet) { result, SelfResolvingDependency selfResolvingDependency ->
-            result.addAll(selfResolvingDependency.resolve().collect { File file ->
-                new ModuleLibrary([getPath(file)] as Set, [] as Set, [] as Set, [] as Set, scope)
-            })
+        ideaModule.scopes.each { scopeName, scopeMap ->
+            result.addAll(getModuleLibraries(ideaModule, scopeName, scopeMap))
+            result.addAll(getModules(ideaModule.project, scopeName, scopeMap))
             result
         }
-    }
 
-    private Set getScopeDependencies(Map<String, Collection<Configuration>> configurations, Closure filter) {
-        Set firstLevelDependencies = new LinkedHashSet()
-        configurations.plus.each { Configuration configuration ->
-            firstLevelDependencies.addAll(configuration.getAllDependencies().findAll(filter))
-        }
-        configurations.minus.each { Configuration configuration ->
-            configuration.getAllDependencies().findAll(filter).each { minusDep ->
-                // This deals with dependencies that are defined in different scopes with different
-                // artifacts. Right now we accept the fact, that in such a situation some artifacts
-                // might be duplicated in Idea (they live in different scopes then).
-                if (minusDep instanceof ExternalDependency) {
-                    ExternalDependency removeCandidate = firstLevelDependencies.find { it == minusDep }
-                    if (removeCandidate && removeCandidate.artifacts == minusDep.artifacts) {
-                        firstLevelDependencies.remove(removeCandidate)
-                    }
-                } else {
-                    firstLevelDependencies.remove(minusDep)
-                }
-            }
-        }
-        return firstLevelDependencies
+        return result
     }
 
-    private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(getAllDeps(plusConfiguration.resolvedConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
+    protected Set getModules(Project project, String scopeName, Map scopeMap) {
+        if (!scopeMap) {
+            return []
         }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(getAllDeps(minusConfiguration.resolvedConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
+        return dependenciesExtractor.extractProjectDependencies(scopeMap.plus, scopeMap.minus).collect {
+                new ModuleDependencyBuilder().create(it.project, scopeName)
         }
-        result
     }
 
-    private Set<File> resolveFiles(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(plusConfiguration.files { it instanceof ExternalDependency })
-        }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(minusConfiguration.files { it instanceof ExternalDependency })
+    protected Set getModuleLibraries(IdeaModule ideaModule, String scopeName, Map scopeMap) {
+        if (!scopeMap) {
+            return []
         }
-        result
-    }
 
-    private getFiles(Set dependencies, String classifier) {
-        return project.configurations.detachedConfiguration((dependencies as Dependency[])).files.inject([:]) { result, sourceFile ->
-            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
-            result[key] = sourceFile
-            result
-        }
-    }
+        LinkedHashSet moduleLibraries = []
 
-    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
-        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
-            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
-                    resolvedDependency.configuration)
-            dependency.transitive = false
-            configureClosure.call(dependency)
-            dependency
-        }
-    }
+        if (!ideaModule.offline) {
+            def repoFileDependencies = dependenciesExtractor.extractRepoFileDependencies(
+                    ideaModule.project.configurations, scopeMap.plus, scopeMap.minus, 
+                    ideaModule.downloadSources, ideaModule.downloadJavadoc)
 
-    protected Set getAllDeps(Collection deps, Set allDeps = []) {
-        deps.each { ResolvedDependency resolvedDependency ->
-            def notSeenBefore = allDeps.add(resolvedDependency)
-            if (notSeenBefore) { // defend against circular dependencies
-                getAllDeps(resolvedDependency.children, allDeps)
+            repoFileDependencies.each {
+                moduleLibraries << new SingleEntryModuleLibrary(
+                    getPath(it.file), getPath(it.javadocFile), getPath(it.sourceFile), scopeName)
             }
         }
-        allDeps
-    }
-
-    protected addSourceArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'source'
-            artifact.extension = 'jar'
-            artifact.classifier = 'sources'
-        }
-    }
 
-    protected addJavadocArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'javadoc'
-            artifact.extension = 'jar'
-            artifact.classifier = 'javadoc'
+        dependenciesExtractor.extractLocalFileDependencies(scopeMap.plus, scopeMap.minus).each {
+            moduleLibraries << new SingleEntryModuleLibrary(getPath(it.file), scopeName)
         }
-    }
-
-    protected Path getPath(File file) {
-        return pathFactory.path(file)
+        moduleLibraries
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.groovy
index e18e4b0..4930688 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilder.groovy
@@ -16,17 +16,19 @@
 
 package org.gradle.plugins.ide.idea.model.internal
 
+import org.gradle.api.Project
+import org.gradle.plugins.ide.idea.IdeaPlugin
 import org.gradle.plugins.ide.idea.model.ModuleDependency
 
 /**
  * @author Szczepan Faber, @date: 19.03.11
  */
 class ModuleDependencyBuilder {
-    ModuleDependency create(gradleProject, String scope) {
-        if (gradleProject.hasProperty('ideaModule') && gradleProject.ideaModule) {
-            new ModuleDependency(gradleProject.ideaModule.moduleName, scope)
+    ModuleDependency create(Project project, String scope) {
+        if (project.plugins.hasPlugin(IdeaPlugin)) {
+            new ModuleDependency(project.idea.module.name, scope)
         } else {
-            new ModuleDependency(gradleProject.name, scope)
+            new ModuleDependency(project.name, scope)
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
new file mode 100644
index 0000000..c76bb3e
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2011 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.ide.internal
+
+import org.gradle.api.Project
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+import org.gradle.api.artifacts.*
+
+/**
+ * @author: Szczepan Faber, created at: 7/1/11
+ */
+class IdeDependenciesExtractor {
+
+    static class IdeDependency {
+        Configuration declaredConfiguration
+    }
+
+    static class IdeLocalFileDependency extends IdeDependency {
+        File file
+    }
+
+    static class IdeRepoFileDependency extends IdeDependency {
+        File file
+        File sourceFile
+        File javadocFile
+    }
+
+    static class UnresolvedIdeRepoFileDependency extends IdeRepoFileDependency {
+        Exception problem
+    }
+
+    static class IdeProjectDependency extends IdeDependency {
+        Project project
+    }
+
+    List<IdeProjectDependency> extractProjectDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<ProjectDependency, Configuration> depToConf = [:]
+        for (plusConfiguration in plusConfigurations) {
+            for (ProjectDependency dependency in plusConfiguration.allDependencies.findAll({ it instanceof ProjectDependency })) {
+                depToConf[dependency] = plusConfiguration
+            }
+        }
+        for (minusConfiguration in minusConfigurations) {
+            for(minusDep in minusConfiguration.allDependencies.findAll({ it instanceof ProjectDependency })) {
+                depToConf.remove(minusDep)
+            }
+        }
+        return depToConf.collect { projectDependency, conf ->
+            new IdeProjectDependency(project: projectDependency.dependencyProject, declaredConfiguration: conf)
+        }
+    }
+
+    List<IdeRepoFileDependency> extractRepoFileDependencies(ConfigurationContainer confContainer,
+                                                           Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations,
+                                                           boolean downloadSources, boolean downloadJavadoc) {
+        def out = []
+
+        def allResolvedDependencies = resolveDependencies(plusConfigurations, minusConfigurations)
+
+        Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+            addSourceArtifact(dependency)
+        }
+
+        Map<String, File> sourceFiles = downloadSources ? getFiles(confContainer.detachedConfiguration(sourceDependencies as Dependency[]), "sources") : [:]
+
+        Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+            addJavadocArtifact(dependency)
+        }
+
+        Map<String, File> javadocFiles = downloadJavadoc ? getFiles(confContainer.detachedConfiguration(javadocDependencies as Dependency[]), "javadoc") : [:]
+
+        resolvedExternalDependencies(plusConfigurations, minusConfigurations).each { File binaryFile, Configuration conf ->
+            File sourceFile = sourceFiles[binaryFile.name]
+            File javadocFile = javadocFiles[binaryFile.name]
+            out << new IdeRepoFileDependency( file: binaryFile, sourceFile: sourceFile, javadocFile: javadocFile, declaredConfiguration: conf)
+        }
+
+        unresolvedExternalDependencies(plusConfigurations, minusConfigurations) { config, dep ->
+            out << new UnresolvedIdeRepoFileDependency(problem: dep.problem, file: new File("unresolved dependency - $dep.id"), declaredConfiguration: config)
+        }
+
+        out
+    }
+
+    private void unresolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations, Closure action) {
+        def unresolved = new LinkedHashMap<String, Map>()
+        for (c in plusConfigurations) {
+            def deps = c.resolvedConfiguration.lenientConfiguration.unresolvedModuleDependencies
+            deps.each { unresolved[it.id] = [dep: it, config: c] }
+        }
+        for (c in minusConfigurations) {
+            def deps = c.resolvedConfiguration.lenientConfiguration.unresolvedModuleDependencies
+            deps.each { unresolved.remove(it.id) }
+        }
+        unresolved.values().each {
+            action.call(it.config, it.dep)
+        }
+    }
+
+    List<IdeLocalFileDependency> extractLocalFileDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<File, Configuration> fileToConf = [:]
+        def filter = { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}
+
+        for (plusConfiguration in plusConfigurations) {
+            def deps = plusConfiguration.allDependencies.findAll(filter)
+            def files = deps.collect { it.resolve() }.flatten()
+            files.each { fileToConf[it] = plusConfiguration }
+        }
+        for (minusConfiguration in minusConfigurations) {
+            def deps = minusConfiguration.allDependencies.findAll(filter)
+            def files = deps.collect { it.resolve() }.flatten()
+            files.each { fileToConf.remove(it) }
+        }
+        return fileToConf.collect { file, conf ->
+            new IdeLocalFileDependency( file: file, declaredConfiguration: conf)
+        }
+    }
+
+    private Map<File, Configuration> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<File, Configuration> fileToConf = [:]
+        for (plusConfiguration in plusConfigurations) {
+            for (file in plusConfiguration.resolvedConfiguration.lenientConfiguration.getFiles( { it instanceof ExternalDependency } as Spec)) {
+                fileToConf[file] = plusConfiguration
+            }
+        }
+        for (minusConfiguration in minusConfigurations) {
+            for (file in minusConfiguration.resolvedConfiguration.lenientConfiguration.getFiles({ it instanceof ExternalDependency } as Spec)) {
+                fileToConf.remove(file)
+            }
+        }
+        fileToConf
+    }
+
+    private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        def result = new LinkedHashSet()
+        for (plusConfiguration in plusConfigurations) {
+            result.addAll(getAllDeps(plusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
+        }
+        for (minusConfiguration in minusConfigurations) {
+            result.removeAll(getAllDeps(minusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
+        }
+        result
+    }
+
+    private Set getAllDeps(Collection deps, Set allDeps = new LinkedHashSet()) {
+        deps.each { ResolvedDependency resolvedDependency ->
+            def notSeenBefore = allDeps.add(resolvedDependency)
+            if (notSeenBefore) { // defend against circular dependencies
+                getAllDeps(resolvedDependency.children, allDeps)
+            }
+        }
+        allDeps
+    }
+
+    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
+        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
+            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
+                    resolvedDependency.configuration)
+            dependency.transitive = false
+            configureClosure.call(dependency)
+            dependency
+        }
+    }
+
+    private void addSourceArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'source'
+            artifact.extension = 'jar'
+            artifact.classifier = 'sources'
+        }
+    }
+
+    private void addJavadocArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'javadoc'
+            artifact.extension = 'jar'
+            artifact.classifier = 'javadoc'
+        }
+    }
+
+    private Map getFiles(Configuration configuration, String classifier) {
+        return (Map) configuration.resolvedConfiguration.lenientConfiguration.getFiles(Specs.satisfyAll()).inject([:]) { result, sourceFile ->
+            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
+            result[key] = sourceFile
+            result
+        }
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy
index 77cf591..12e0228 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdePlugin.groovy
@@ -49,15 +49,17 @@ public abstract class IdePlugin implements Plugin<Project> {
         return project.getTasks().getByName(cleanName(worker.getName()));
     }
 
-    private String cleanName(String taskName) {
+    protected 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 addWorker(Task worker, boolean includeInClean = true) {
+        lifecycleTask.dependsOn(worker)
+        Delete cleanWorker = project.getTasks().add(cleanName(worker.getName()), Delete.class)
+        cleanWorker.delete(worker.getOutputs().getFiles())
+        if (includeInClean) {
+            cleanTask.dependsOn(cleanWorker)
+        }
     }
     
     protected void onApply(Project target) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/AbstractPersistableConfigurationObject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/AbstractPersistableConfigurationObject.groovy
index a42a3bd..4cf2c3a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/AbstractPersistableConfigurationObject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/AbstractPersistableConfigurationObject.groovy
@@ -17,7 +17,7 @@ package org.gradle.plugins.ide.internal.generator;
 
 
 import org.gradle.plugins.ide.internal.generator.generator.PersistableConfigurationObject
-import org.gradle.util.UncheckedException
+import org.gradle.internal.UncheckedException
 
 public abstract class AbstractPersistableConfigurationObject implements PersistableConfigurationObject {
     public void load(File inputFile) {
@@ -29,20 +29,24 @@ public abstract class AbstractPersistableConfigurationObject implements Persista
                 inputStream.close();
             }
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
     public void loadDefaults() {
         try {
-            InputStream inputStream = getClass().getResourceAsStream(getDefaultResourceName());
+            String defaultResourceName = getDefaultResourceName();
+            InputStream inputStream = getClass().getResourceAsStream(defaultResourceName);
+            if (inputStream == null) {
+                throw new IllegalStateException(String.format("Failed to load default resource '%s' of persistable configuration object of type '%s' (resource not found)", defaultResourceName, getClass().getName()));
+            }
             try {
                 load(inputStream);
             } finally {
                 inputStream.close();
             }
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
@@ -57,7 +61,7 @@ public abstract class AbstractPersistableConfigurationObject implements Persista
                 outputStream.close();
             }
         } catch (IOException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy
index 54bdfe1..282dc26 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObject.groovy
@@ -13,33 +13,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.plugins.ide.internal.generator;
+package org.gradle.plugins.ide.internal.generator
 
+import org.gradle.api.internal.PropertiesTransformer
 
-import org.gradle.util.UncheckedException;
 
-
-public abstract class PropertiesPersistableConfigurationObject extends AbstractPersistableConfigurationObject {
-    private Properties properties;
+abstract class PropertiesPersistableConfigurationObject extends AbstractPersistableConfigurationObject {
+    private final PropertiesTransformer transformer
+    private Properties properties
+    
+    protected PropertiesPersistableConfigurationObject(PropertiesTransformer transformer) {
+        this.transformer = transformer
+    }
 
     @Override
-    public void load(InputStream inputStream) throws Exception {
-        properties = new Properties();
-        properties.load(inputStream);
-        load(properties);
+    void load(InputStream inputStream) throws Exception {
+        properties = new Properties()
+        properties.load(inputStream)
+        load(properties)
     }
 
     @Override
-    public void store(OutputStream outputStream) {
-        store(properties);
-        try {
-            properties.store(outputStream, "");
-        } catch (IOException e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
+    void store(OutputStream outputStream) {
+        store(properties)
+        transformer.transform(properties, outputStream)
     }
 
-    protected abstract void store(Properties properties);
+    protected abstract void store(Properties properties)
 
-    protected abstract void load(Properties properties);
+    protected abstract void load(Properties properties)
+    
+    void transformAction(Closure action) {
+        transformer.addAction(action)
+    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy
index 38a9b61..042e566 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/XmlPersistableConfigurationObject.groovy
@@ -48,10 +48,18 @@ public abstract class XmlPersistableConfigurationObject extends AbstractPersista
     /**
      * Called immediately after the XML file has been read.
      */
-    protected abstract void load(Node xml);
+    protected void load(Node xml) {
+        // no-op
+    }
 
     /**
      * Called immediately before the XML file is to be written.
      */
-    protected abstract void store(Node xml);
+    protected void store(Node xml) {
+        // no-op
+    }
+
+    public void transformAction(Closure action) {
+        xmlTransformer.addAction(action)
+    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/generator/PersistableConfigurationObjectGenerator.java b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/generator/PersistableConfigurationObjectGenerator.java
index eae0fd6..e7ab667 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/generator/PersistableConfigurationObjectGenerator.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/generator/generator/PersistableConfigurationObjectGenerator.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.internal.generator.generator;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BasicIdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BasicIdeaModelBuilder.java
new file mode 100644
index 0000000..7f251c2
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BasicIdeaModelBuilder.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.tooling.internal.protocol.InternalBasicIdeaProject;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+
+/**
+ * @author: Szczepan Faber, created at: 7/23/11
+ */
+public class BasicIdeaModelBuilder implements BuildsModel {
+    public boolean canBuild(Class<?> type) {
+        return type == InternalBasicIdeaProject.class;
+    }
+
+    public ProjectVersion3 buildAll(GradleInternal gradle) {
+        return new IdeaModelBuilder()
+                .setOfflineDependencyResolution(true)
+                .buildAll(gradle);
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
index 708a759..09cf93d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
@@ -18,31 +18,36 @@ package org.gradle.tooling.internal.provider;
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.tooling.internal.protocol.BuildableProjectVersion1;
 import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
 
 public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
-    private final ModelBuilder builder;
+    private ModelBuildingAdapter modelBuildingAdapter;
 
     public BuildModelAction(Class<? extends ProjectVersion3> type) {
-        if (!type.isAssignableFrom(EclipseProjectVersion3.class)) {
-            throw new UnsupportedOperationException(String.format("Do not know how to build a model of type '%s'.", type.getSimpleName()));
+        List<? extends BuildsModel> modelBuilders = asList(
+                new EclipseModelBuilder(), new IdeaModelBuilder(),
+                new GradleProjectBuilder(), new BasicIdeaModelBuilder());
+
+        for (BuildsModel builder : modelBuilders) {
+            if (builder.canBuild(type)) {
+                modelBuildingAdapter = new ModelBuildingAdapter(builder);
+                return;
+            }
         }
 
-        boolean projectDependenciesOnly = !EclipseProjectVersion3.class.isAssignableFrom(type);
-        boolean includeTasks = BuildableProjectVersion1.class.isAssignableFrom(type);
-        builder = new ModelBuilder(includeTasks, projectDependenciesOnly);
+        throw new UnsupportedOperationException(String.format("I don't know how to build a model of type '%s'.", type.getSimpleName()));
     }
 
     public BuildResult run(GradleLauncher launcher) {
-        ModelBuildingAdapter adapter = new ModelBuildingAdapter(
-                new EclipsePluginApplier(), builder);
-        launcher.addListener(adapter);
+        launcher.addListener(modelBuildingAdapter);
         return launcher.getBuildAnalysis();
     }
 
     public ProjectVersion3 getResult() {
-        return builder.getProject();
+        return modelBuildingAdapter.getProject();
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildsModel.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildsModel.java
new file mode 100644
index 0000000..9bdc3b0
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildsModel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+
+/**
+* @author: Szczepan Faber, created at: 7/23/11
+*/
+public interface BuildsModel {
+    boolean canBuild(Class<?> type);
+    ProjectVersion3 buildAll(GradleInternal gradle);
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
new file mode 100644
index 0000000..e60dbc4
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.plugins.ide.eclipse.EclipsePlugin;
+import org.gradle.plugins.ide.eclipse.model.*;
+import org.gradle.tooling.internal.eclipse.*;
+import org.gradle.tooling.internal.protocol.BuildableProjectVersion1;
+import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.util.GUtil;
+import org.gradle.util.ReflectionUtil;
+
+import java.io.File;
+import java.util.*;
+
+/**
+* @author Adam Murdoch, Szczepan Faber, @date: 17.03.11
+*/
+public class EclipseModelBuilder implements BuildsModel {
+    private boolean projectDependenciesOnly;
+    private EclipseProjectVersion3 currentProject;
+    private final Map<String, EclipseProjectVersion3> projectMapping = new HashMap<String, EclipseProjectVersion3>();
+    private GradleInternal gradle;
+    private TasksFactory tasksFactory;
+    private GradleProjectBuilder gradleProjectBuilder = new GradleProjectBuilder();
+    private GradleProject rootGradleProject;
+
+    public boolean canBuild(Class<?> type) {
+        if (type.isAssignableFrom(EclipseProjectVersion3.class)) {
+            //I don't like preparing the state in this method but for now lets leave it :/
+            boolean includeTasks = BuildableProjectVersion1.class.isAssignableFrom(type);
+            this.tasksFactory = new TasksFactory(includeTasks);
+            this.projectDependenciesOnly = !EclipseProjectVersion3.class.isAssignableFrom(type);
+            return true;
+        }
+        return false;
+    }
+
+    public EclipseProjectVersion3 buildAll(GradleInternal gradle) {
+        this.gradle = gradle;
+        rootGradleProject = gradleProjectBuilder.buildAll(gradle);
+        Project root = gradle.getRootProject();
+        tasksFactory.collectTasks(root);
+        applyEclipsePlugin(root);
+        buildHierarchy(root);
+        populate(root);
+        return currentProject;
+    }
+
+    private void applyEclipsePlugin(Project root) {
+        Set<Project> allprojects = root.getAllprojects();
+        for (Project p : allprojects) {
+            p.getPlugins().apply(EclipsePlugin.class);
+        }
+        root.getPlugins().getPlugin(EclipsePlugin.class).makeSureProjectNamesAreUnique();
+    }
+
+    private void addProject(Project project, EclipseProjectVersion3 eclipseProject) {
+        if (project == gradle.getDefaultProject()) {
+            currentProject = eclipseProject;
+        }
+        projectMapping.put(project.getPath(), eclipseProject);
+    }
+
+    private void populate(Project project) {
+        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
+        EclipseClasspath classpath = eclipseModel.getClasspath();
+
+        classpath.setProjectDependenciesOnly(projectDependenciesOnly);
+        List<ClasspathEntry> entries = classpath.resolveDependencies();
+
+        final List<ExternalDependencyVersion1> externalDependencies = new LinkedList<ExternalDependencyVersion1>();
+        final List<EclipseProjectDependencyVersion2> projectDependencies = new LinkedList<EclipseProjectDependencyVersion2>();
+        final List<EclipseSourceDirectoryVersion1> sourceDirectories = new LinkedList<EclipseSourceDirectoryVersion1>();
+
+        for (ClasspathEntry entry : entries) {
+            if (entry instanceof Library) {
+                AbstractLibrary library = (AbstractLibrary) entry;
+                final File file = library.getLibrary().getFile();
+                final File source = library.getSourcePath() == null ? null : library.getSourcePath().getFile();
+                final File javadoc = library.getJavadocPath() == null ? null : library.getJavadocPath().getFile();
+                externalDependencies.add(new DefaultEclipseExternalDependency(file, javadoc, source));
+            } else if (entry instanceof ProjectDependency) {
+                final ProjectDependency projectDependency = (ProjectDependency) entry;
+                final String path = StringUtils.removeStart(projectDependency.getPath(), "/");
+                projectDependencies.add(new DefaultEclipseProjectDependency(path, projectMapping.get(projectDependency.getGradlePath())));
+            } else if (entry instanceof SourceFolder) {
+                String path = ((SourceFolder) entry).getPath();
+                sourceDirectories.add(new DefaultEclipseSourceDirectory(path, project.file(path)));
+            }
+        }
+
+        final EclipseProjectVersion3 eclipseProject = projectMapping.get(project.getPath());
+        ReflectionUtil.setProperty(eclipseProject, "classpath", externalDependencies);
+        ReflectionUtil.setProperty(eclipseProject, "projectDependencies", projectDependencies);
+        ReflectionUtil.setProperty(eclipseProject, "sourceDirectories", sourceDirectories);
+
+        if (ReflectionUtil.hasProperty(eclipseProject, "linkedResources")) {
+            List<DefaultEclipseLinkedResource> linkedResources = new LinkedList<DefaultEclipseLinkedResource>();
+            for(Link r: eclipseModel.getProject().getLinkedResources()) {
+                linkedResources.add(new DefaultEclipseLinkedResource(r.getName(), r.getType(), r.getLocation(), r.getLocationUri()));
+            }
+            ReflectionUtil.setProperty(eclipseProject, "linkedResources", linkedResources);
+        }
+
+        List<EclipseTaskVersion1> out = new ArrayList<EclipseTaskVersion1>();
+        for (final Task t : tasksFactory.getTasks(project)) {
+            out.add(new DefaultEclipseTask(eclipseProject, t.getPath(), t.getName(), t.getDescription()));
+        }
+        ReflectionUtil.setProperty(eclipseProject, "tasks", out);
+
+        for (Project childProject : project.getChildProjects().values()) {
+            populate(childProject);
+        }
+    }
+
+    private EclipseProjectVersion3 buildHierarchy(Project project) {
+        List<EclipseProjectVersion3> children = new ArrayList<EclipseProjectVersion3>();
+        for (Project child : project.getChildProjects().values()) {
+            children.add(buildHierarchy(child));
+        }
+
+        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
+        org.gradle.plugins.ide.eclipse.model.EclipseProject internalProject = eclipseModel.getProject();
+        String name = internalProject.getName();
+        String description = GUtil.elvis(internalProject.getComment(), null);
+        EclipseProjectVersion3 eclipseProject =
+                new DefaultEclipseProject(name, project.getPath(), description, project.getProjectDir(), children)
+                .setGradleProject(rootGradleProject.findByPath(project.getPath()));
+
+        for (Object child : children) {
+            ReflectionUtil.setProperty(child, "parent", eclipseProject);
+        }
+        addProject(project, eclipseProject);
+        return eclipseProject;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipsePluginApplier.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipsePluginApplier.java
deleted file mode 100644
index 1f6eca5..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipsePluginApplier.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider;
-
-import org.gradle.api.Project;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.plugins.ide.eclipse.EclipsePlugin;
-import org.gradle.plugins.ide.eclipse.internal.EclipseNameDeduper;
-
-import java.util.Set;
-
-/**
- * @author Szczepan Faber, @date: 25.03.11
- */
-public class EclipsePluginApplier {
-    public void apply(GradleInternal gradle) {
-        Set<Project> allprojects = gradle.getRootProject().getAllprojects();
-        for (Project p : allprojects) {
-            p.getPlugins().apply(EclipsePlugin.class);
-            //TODO SF: this is temporary, until we figure out how to tackle this
-            new EclipseNameDeduper().configure(p);
-        }
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/GradleProjectBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/GradleProjectBuilder.java
new file mode 100644
index 0000000..fd8ddaa
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/GradleProjectBuilder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.gradle.DefaultGradleTask;
+import org.gradle.tooling.internal.protocol.InternalGradleProject;
+import org.gradle.tooling.model.GradleTask;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Builds the GradleProject that contains the project hierarchy and task information
+ *
+ * @author: Szczepan Faber, created at: 7/27/11
+ */
+public class GradleProjectBuilder implements BuildsModel {
+    public boolean canBuild(Class<?> type) {
+        return type == InternalGradleProject.class;
+    }
+
+    public DefaultGradleProject buildAll(GradleInternal gradle) {
+        return buildHierarchy(gradle.getRootProject());
+    }
+
+    private DefaultGradleProject buildHierarchy(Project project) {
+        List<DefaultGradleProject> children = new ArrayList<DefaultGradleProject>();
+        for (Project child : project.getChildProjects().values()) {
+            children.add(buildHierarchy(child));
+        }
+
+        DefaultGradleProject gradleProject = new DefaultGradleProject()
+                .setPath(project.getPath())
+                .setName(project.getName())
+                .setDescription(project.getDescription())
+                .setChildren(children);
+
+        gradleProject.setTasks(tasks(gradleProject, project.getTasks()));
+
+        for (DefaultGradleProject child : children) {
+            child.setParent(gradleProject);
+        }
+
+        return gradleProject;
+    }
+
+    private List<GradleTask> tasks(DefaultGradleProject owner, TaskContainer tasks) {
+        List<GradleTask> out = new LinkedList<GradleTask>();
+
+        for (Task t : tasks) {
+            out.add(new DefaultGradleTask()
+                    .setPath(t.getPath())
+                    .setName(t.getName())
+                    .setDescription(t.getDescription())
+                    .setProject(owner));
+        }
+
+        return out;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
new file mode 100644
index 0000000..7035886
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.plugins.ide.idea.IdeaPlugin;
+import org.gradle.plugins.ide.idea.model.*;
+import org.gradle.tooling.internal.idea.*;
+import org.gradle.tooling.internal.protocol.InternalIdeaProject;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.idea.IdeaDependency;
+import org.gradle.tooling.model.idea.IdeaSourceDirectory;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author: Szczepan Faber, created at: 7/23/11
+ */
+public class IdeaModelBuilder implements BuildsModel {
+    public boolean canBuild(Class<?> type) {
+        return type == InternalIdeaProject.class;
+    }
+
+    private final GradleProjectBuilder gradleProjectBuilder = new GradleProjectBuilder();
+    private boolean offlineDependencyResolution;
+
+    public ProjectVersion3 buildAll(GradleInternal gradle) {
+        Project root = gradle.getRootProject();
+        applyIdeaPlugin(root);
+        GradleProject rootGradleProject = gradleProjectBuilder.buildAll(gradle);
+        return build(root, rootGradleProject);
+    }
+
+    private void applyIdeaPlugin(Project root) {
+        Set<Project> allprojects = root.getAllprojects();
+        for (Project p : allprojects) {
+            p.getPlugins().apply(IdeaPlugin.class);
+        }
+        root.getPlugins().getPlugin(IdeaPlugin.class).makeSureModuleNamesAreUnique();
+    }
+
+    private ProjectVersion3 build(Project project, GradleProject rootGradleProject) {
+        IdeaModel ideaModel = project.getPlugins().getPlugin(IdeaPlugin.class).getModel();
+        IdeaProject projectModel = ideaModel.getProject();
+
+        DefaultIdeaProject out = new DefaultIdeaProject()
+                .setName(projectModel.getName())
+                .setJdkName(projectModel.getJdkName())
+                .setLanguageLevel(new DefaultIdeaLanguageLevel(projectModel.getLanguageLevel().getLevel()));
+
+        Map<String, DefaultIdeaModule> modules = new HashMap<String, DefaultIdeaModule>();
+        for (IdeaModule module : projectModel.getModules()) {
+            appendModule(modules, module, out, rootGradleProject);
+        }
+        for (IdeaModule module : projectModel.getModules()) {
+            buildDependencies(modules, module);
+        }
+        out.setChildren(new LinkedList<DefaultIdeaModule>(modules.values()));
+
+        return out;
+    }
+
+    private void buildDependencies(Map<String, DefaultIdeaModule> modules, IdeaModule ideaModule) {
+        ideaModule.setOffline(offlineDependencyResolution);
+        Set<Dependency> resolved = ideaModule.resolveDependencies();
+        List<IdeaDependency> dependencies = new LinkedList<IdeaDependency>();
+        for (Dependency dependency : resolved) {
+            if (dependency instanceof SingleEntryModuleLibrary) {
+                SingleEntryModuleLibrary d = (SingleEntryModuleLibrary) dependency;
+                IdeaDependency defaultDependency = new DefaultIdeaSingleEntryLibraryDependency()
+                        .setFile(d.getLibraryFile())
+                        .setSource(d.getSourceFile())
+                        .setJavadoc(d.getJavadocFile())
+                        .setScope(new DefaultIdeaDependencyScope(d.getScope()))
+                        .setExported(d.getExported());
+                dependencies.add(defaultDependency);
+            } else if (dependency instanceof ModuleDependency) {
+                ModuleDependency d = (ModuleDependency) dependency;
+                IdeaDependency defaultDependency = new DefaultIdeaModuleDependency()
+                        .setExported(d.getExported())
+                        .setScope(new DefaultIdeaDependencyScope(d.getScope()))
+                        .setDependencyModule(modules.get(d.getName()));
+                dependencies.add(defaultDependency);
+            }
+        }
+        modules.get(ideaModule.getName()).setDependencies(dependencies);
+    }
+
+    private void appendModule(Map<String, DefaultIdeaModule> modules, IdeaModule ideaModule, DefaultIdeaProject ideaProject, GradleProject rootGradleProject) {
+        DefaultIdeaContentRoot contentRoot = new DefaultIdeaContentRoot()
+            .setRootDirectory(ideaModule.getContentRoot())
+            .setSourceDirectories(srcDirs(ideaModule.getSourceDirs()))
+            .setTestDirectories(srcDirs(ideaModule.getTestSourceDirs()))
+            .setExcludeDirectories(ideaModule.getExcludeDirs());
+
+        DefaultIdeaModule defaultIdeaModule = new DefaultIdeaModule()
+                .setName(ideaModule.getName())
+                .setParent(ideaProject)
+                .setGradleProject(rootGradleProject.findByPath(ideaModule.getProject().getPath()))
+                .setModuleFileDir(ideaModule.getIml().getGenerateTo())
+                .setContentRoots(Collections.singletonList(contentRoot))
+                .setCompilerOutput(new DefaultIdeaCompilerOutput()
+                    .setInheritOutputDirs(ideaModule.getInheritOutputDirs() != null ? ideaModule.getInheritOutputDirs() : false)
+                    .setOutputDir(ideaModule.getOutputDir())
+                    .setTestOutputDir(ideaModule.getTestOutputDir())
+                );
+
+        modules.put(ideaModule.getName(), defaultIdeaModule);
+    }
+
+    private Set<IdeaSourceDirectory> srcDirs(Set<File> sourceDirs) {
+        Set<IdeaSourceDirectory> out = new LinkedHashSet<IdeaSourceDirectory>();
+        for (File s : sourceDirs) {
+            out.add(new DefaultIdeaSourceDirectory().setDirectory(s));
+        }
+        return out;
+    }
+
+    public IdeaModelBuilder setOfflineDependencyResolution(boolean offlineDependencyResolution) {
+        this.offlineDependencyResolution = offlineDependencyResolution;
+        return this;
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuilder.java
deleted file mode 100644
index 7a746a2..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuilder.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider;
-
-import org.gradle.api.Project;
-import org.gradle.api.internal.GradleInternal;
-import org.gradle.plugins.ide.eclipse.EclipsePlugin;
-import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
-import org.gradle.plugins.ide.eclipse.model.EclipseClasspath;
-import org.gradle.plugins.ide.eclipse.model.EclipseModel;
-import org.gradle.tooling.internal.DefaultEclipseProject;
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
-import org.gradle.tooling.internal.provider.dependencies.EclipseProjectDependenciesFactory;
-import org.gradle.tooling.internal.provider.dependencies.ExternalDependenciesFactory;
-import org.gradle.tooling.internal.provider.dependencies.SourceDirectoriesFactory;
-import org.gradle.util.GUtil;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
-* @author Adam Murdoch, Szczepan Faber, @date: 17.03.11
-*/
-public class ModelBuilder {
-    private final boolean includeTasks;
-    private boolean projectDependenciesOnly;
-    private DefaultEclipseProject currentProject;
-    private final Map<String, DefaultEclipseProject> projectMapping = new HashMap<String, DefaultEclipseProject>();
-    private GradleInternal gradle;
-
-    public ModelBuilder(boolean includeTasks, boolean projectDependenciesOnly) {
-        this.includeTasks = includeTasks;
-        this.projectDependenciesOnly = projectDependenciesOnly;
-    }
-
-    public void buildAll(GradleInternal gradle) {
-        this.gradle = gradle;
-        buildHierarchy(gradle.getRootProject());
-        populate(gradle.getRootProject());
-    }
-
-    public DefaultEclipseProject getProject() {
-        return currentProject;
-    }
-
-    private void addProject(Project project, DefaultEclipseProject eclipseProject) {
-        if (project == gradle.getDefaultProject()) {
-            currentProject = eclipseProject;
-        }
-        projectMapping.put(project.getPath(), eclipseProject);
-    }
-
-    private void populate(Project project) {
-        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
-        EclipseClasspath classpath = eclipseModel.getClasspath();
-
-        classpath.setProjectDependenciesOnly(projectDependenciesOnly);
-        List<ClasspathEntry> entries = classpath.resolveDependencies();
-
-        List<ExternalDependencyVersion1> dependencies = new ExternalDependenciesFactory().create(project, entries);
-        List<EclipseProjectDependencyVersion2> projectDependencies = new EclipseProjectDependenciesFactory().create(projectMapping, entries);
-        List<EclipseSourceDirectoryVersion1> sourceDirectories = new SourceDirectoriesFactory().create(project, entries);
-
-        DefaultEclipseProject eclipseProject = projectMapping.get(project.getPath());
-        eclipseProject.setClasspath(dependencies);
-        eclipseProject.setProjectDependencies(projectDependencies);
-        eclipseProject.setSourceDirectories(sourceDirectories);
-        if (includeTasks) {
-            eclipseProject.setTasks(new TasksFactory().create(project, eclipseProject));
-        }
-
-        for (Project childProject : project.getChildProjects().values()) {
-            populate(childProject);
-        }
-    }
-
-    private DefaultEclipseProject buildHierarchy(Project project) {
-        List<DefaultEclipseProject> children = new ArrayList<DefaultEclipseProject>();
-        for (Project child : project.getChildProjects().values()) {
-            children.add(buildHierarchy(child));
-        }
-
-        EclipseModel eclipseModel = project.getPlugins().getPlugin(EclipsePlugin.class).getModel();
-        org.gradle.plugins.ide.eclipse.model.EclipseProject internalProject = eclipseModel.getProject();
-        String name = internalProject.getName();
-        String description = GUtil.elvis(internalProject.getComment(), null);
-        DefaultEclipseProject eclipseProject = new DefaultEclipseProject(name, project.getPath(), description, project.getProjectDir(), children);
-        for (DefaultEclipseProject child : children) {
-            child.setParent(eclipseProject);
-        }
-        addProject(project, eclipseProject);
-        return eclipseProject;
-    }
-}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuildingAdapter.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuildingAdapter.java
index 43f6141..a011eca 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuildingAdapter.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/ModelBuildingAdapter.java
@@ -16,26 +16,27 @@
 
 package org.gradle.tooling.internal.provider;
 
-import org.gradle.BuildAdapter;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.invocation.Gradle;
+import org.gradle.initialization.ModelConfigurationListener;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
 
 /**
  * @author Szczepan Faber, @date: 25.03.11
  */
-public class ModelBuildingAdapter extends BuildAdapter {
+public class ModelBuildingAdapter implements ModelConfigurationListener {
 
-    EclipsePluginApplier applier;
-    ModelBuilder builder;
+    private final BuildsModel builder;
+    private ProjectVersion3 eclipseProject;
 
-    public ModelBuildingAdapter(EclipsePluginApplier applier, ModelBuilder builder) {
-        this.applier = applier;
+    public ModelBuildingAdapter(BuildsModel builder) {
         this.builder = builder;
     }
 
-    @Override
-    public void projectsEvaluated(Gradle gradle) {
-        applier.apply((GradleInternal) gradle);
-        builder.buildAll((GradleInternal) gradle);
+    public void onConfigure(GradleInternal model) {
+        eclipseProject = builder.buildAll(model);
     }
-}
+
+    public ProjectVersion3 getProject() {
+        return eclipseProject;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java
index 5f33eee..a1215a7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/TasksFactory.java
@@ -17,20 +17,30 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.api.Project;
 import org.gradle.api.Task;
-import org.gradle.tooling.internal.DefaultTask;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
 
 public class TasksFactory {
-    public List<EclipseTaskVersion1> create(Project project, EclipseProjectVersion3 eclipseProject) {
-        List<EclipseTaskVersion1> tasks = new ArrayList<EclipseTaskVersion1>();
-        for (final Task task : project.getTasks()) {
-            tasks.add(new DefaultTask(eclipseProject, task.getPath(), task.getName(), task.getDescription()));
+    Map<Project, Set<Task>> allTasks;
+    private final boolean includeTasks;
+
+    public TasksFactory(boolean includeTasks) {
+        this.includeTasks = includeTasks;
+    }
+
+    public void collectTasks(Project root) {
+        allTasks = root.getAllTasks(true);
+    }
+
+    public Set<Task> getTasks(Project project) {
+        if (includeTasks) {
+            return allTasks.get(project);
+        } else {
+            return emptySet();
         }
-        return tasks;
     }
 
-}
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/EclipseProjectDependenciesFactory.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/EclipseProjectDependenciesFactory.java
deleted file mode 100644
index 17b3775..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/EclipseProjectDependenciesFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider.dependencies;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
-import org.gradle.plugins.ide.eclipse.model.ProjectDependency;
-import org.gradle.tooling.internal.DefaultEclipseProjectDependency;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
-import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author Szczepan Faber, @date: 24.03.11
- */
-public class EclipseProjectDependenciesFactory {
-    public List<EclipseProjectDependencyVersion2> create(final Map<String, ? extends HierarchicalEclipseProjectVersion1> projectMapping, List<ClasspathEntry> entries) {
-        final LinkedList<EclipseProjectDependencyVersion2> dependencies = new LinkedList<EclipseProjectDependencyVersion2>();
-
-        for (ClasspathEntry entry : entries) {
-            if (entry instanceof ProjectDependency) {
-                final ProjectDependency projectDependency = (ProjectDependency) entry;
-                final String path = StringUtils.removeStart(projectDependency.getPath(), "/");
-                dependencies.add(new DefaultEclipseProjectDependency(path, projectMapping.get(projectDependency.getGradlePath())));
-            }
-        }
-        return dependencies;
-    }
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/ExternalDependenciesFactory.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/ExternalDependenciesFactory.java
deleted file mode 100644
index b8d6183..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/ExternalDependenciesFactory.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider.dependencies;
-
-import org.gradle.api.Project;
-import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
-import org.gradle.plugins.ide.eclipse.model.Library;
-import org.gradle.tooling.internal.DefaultExternalDependency;
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-
-import java.io.File;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * @author Szczepan Faber, @date: 23.03.11
- */
-public class ExternalDependenciesFactory {
-    public List<ExternalDependencyVersion1> create(final Project project, List<ClasspathEntry> entries) {
-        List<ExternalDependencyVersion1> dependencies = new LinkedList<ExternalDependencyVersion1>();
-        for (ClasspathEntry entry : entries) {
-            if (entry instanceof Library) {
-                Library library = (Library) entry;
-                final File file = project.file(library.getPath());
-                final File source = library.getSourcePath() == null ? null : project.file(library.getSourcePath());
-                final File javadoc = library.getJavadocPath() == null ? null : project.file(library.getJavadocPath());
-                dependencies.add(new DefaultExternalDependency(file, javadoc, source));
-            }
-        }
-        return dependencies;
-    }
-
-}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/SourceDirectoriesFactory.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/SourceDirectoriesFactory.java
deleted file mode 100644
index 1ee3ed9..0000000
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/dependencies/SourceDirectoriesFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider.dependencies;
-
-import org.gradle.api.Project;
-import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
-import org.gradle.plugins.ide.eclipse.model.SourceFolder;
-import org.gradle.tooling.internal.DefaultEclipseSourceDirectory;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @author Szczepan Faber, @date: 20.03.11
- */
-public class SourceDirectoriesFactory {
-    public List<EclipseSourceDirectoryVersion1> create(Project project, List<ClasspathEntry> entries) {
-        List<EclipseSourceDirectoryVersion1> sourceDirectories = new ArrayList<EclipseSourceDirectoryVersion1>();
-        for (ClasspathEntry entry : entries) {
-            if (entry instanceof SourceFolder) {
-                String path = ((SourceFolder) entry).getPath();
-                sourceDirectories.add(sourceDirectory(project, path));
-            }
-        }
-        return sourceDirectories;
-    }
-
-    private EclipseSourceDirectoryVersion1 sourceDirectory(Project project, String path) {
-        return new DefaultEclipseSourceDirectory(path, project.file(path));
-    }
-}
diff --git a/subprojects/ide/src/main/resources/META-INF/gradle-plugins/eclipse-wtp.properties b/subprojects/ide/src/main/resources/META-INF/gradle-plugins/eclipse-wtp.properties
new file mode 100644
index 0000000..b82a968
--- /dev/null
+++ b/subprojects/ide/src/main/resources/META-INF/gradle-plugins/eclipse-wtp.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.ide.eclipse.EclipseWtpPlugin
diff --git a/subprojects/ide/src/main/resources/org/gradle/plugins/ide/eclipse/model/defaultWtpFacet.xml b/subprojects/ide/src/main/resources/org/gradle/plugins/ide/eclipse/model/defaultWtpFacet.xml
index ebf2004..07e8d74 100644
--- a/subprojects/ide/src/main/resources/org/gradle/plugins/ide/eclipse/model/defaultWtpFacet.xml
+++ b/subprojects/ide/src/main/resources/org/gradle/plugins/ide/eclipse/model/defaultWtpFacet.xml
@@ -1,4 +1 @@
-<faceted-project>
-    <fixed facet="jst.java"/>
-    <fixed facet="jst.web"/>
-</faceted-project>
\ No newline at end of file
+<faceted-project/>
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginTest.groovy
index 3e41d30..f3ec895 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipsePluginTest.groovy
@@ -22,8 +22,6 @@ import org.gradle.api.Task
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.tasks.Delete
 import org.gradle.plugins.ide.eclipse.model.BuildCommand
-import org.gradle.plugins.ide.eclipse.model.Facet
-import org.gradle.plugins.ide.eclipse.model.WbResource
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
@@ -63,29 +61,6 @@ class EclipsePluginTest extends Specification {
         checkEclipseClasspath([project.configurations.testRuntime])
     }
 
-    def applyToWarProject_shouldHaveWebProjectAndClasspathTask() {
-        when:
-        project.apply(plugin: 'war')
-        eclipsePlugin.apply(project)
-
-        then:
-        assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
-        assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
-        assertThatCleanEclipseDependsOn(project, project.cleanEclipseWtpComponent)
-        assertThatCleanEclipseDependsOn(project, project.cleanEclipseWtpFacet)
-        checkEclipseProjectTask([
-                new BuildCommand('org.eclipse.jdt.core.javabuilder'),
-                new BuildCommand('org.eclipse.wst.common.project.facet.core.builder'),
-                new BuildCommand('org.eclipse.wst.validation.validationbuilder')],
-                ['org.eclipse.jdt.core.javanature',
-                        'org.eclipse.wst.common.project.facet.core.nature',
-                        'org.eclipse.wst.common.modulecore.ModuleCoreNature',
-                        'org.eclipse.jem.workbench.JavaEMFNature'])
-        checkEclipseClasspath([project.configurations.testRuntime])
-        checkEclipseWtpComponent()
-        checkEclipseWtpFacet()
-    }
-
     def applyToScalaProject_shouldHaveProjectAndClasspathTaskForScala() {
         when:
         project.apply(plugin: 'scala-base')
@@ -94,8 +69,8 @@ class EclipsePluginTest extends Specification {
         then:
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
         assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
-        checkEclipseProjectTask([new BuildCommand('ch.epfl.lamp.sdt.core.scalabuilder')],
-                ['ch.epfl.lamp.sdt.core.scalanature', 'org.eclipse.jdt.core.javanature'])
+        checkEclipseProjectTask([new BuildCommand('org.scala-ide.sdt.core.scalabuilder')],
+                ['org.scala-ide.sdt.core.scalanature', 'org.eclipse.jdt.core.javanature'])
         checkEclipseClasspath([])
 
         when:
@@ -130,26 +105,38 @@ class EclipsePluginTest extends Specification {
 
         then:
         eclipsePlugin.model.classpath
-        eclipsePlugin.model.classpath.classesOutputDir
+        eclipsePlugin.model.classpath.defaultOutputDir
+    }
+
+    def "configures internal class folders"() {
+        when:
+        eclipsePlugin.apply(project)
+        project.apply(plugin: 'java')
+
+        project.sourceSets.main.output.dir 'generated-folder'
+        project.sourceSets.main.output.dir 'ws-generated'
+
+        project.sourceSets.test.output.dir 'generated-test'
+        project.sourceSets.test.output.dir 'test-resources'
+
+        project.sourceSets.test.output.dir '../some/external/dir'
+
+        then:
+        def folders = project.eclipseClasspath.classpath.classFolders
+        folders == [project.file('generated-folder'), project.file('ws-generated'), project.file('generated-test'), project.file('test-resources'), project.file('../some/external/dir')]
     }
 
     private void checkEclipseProjectTask(List buildCommands, List natures) {
         GenerateEclipseProject eclipseProjectTask = project.eclipseProject
         assert eclipseProjectTask instanceof GenerateEclipseProject
-        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseProjectTask)
-        assert eclipseProjectTask.buildCommands == buildCommands
-        assert eclipseProjectTask.natures == natures
-        assert eclipseProjectTask.links == [] as Set
-        assert eclipseProjectTask.referencedProjects == [] as Set
-        assert eclipseProjectTask.comment == null
-        assert eclipseProjectTask.projectName == project.name
+        assert project.tasks.eclipse.taskDependencies.getDependencies(project.tasks.eclipse).contains(eclipseProjectTask)
         assert eclipseProjectTask.outputFile == project.file('.project')
     }
 
     private void checkEclipseClasspath(def configurations) {
-        GenerateEclipseClasspath eclipseClasspath = project.eclipseClasspath
+        GenerateEclipseClasspath eclipseClasspath = project.tasks.eclipseClasspath
         assert eclipseClasspath instanceof GenerateEclipseClasspath
-        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseClasspath)
+        assert project.tasks.eclipse.taskDependencies.getDependencies(project.tasks.eclipse).contains(eclipseClasspath)
         assert eclipseClasspath.sourceSets == project.sourceSets
         assert eclipseClasspath.plusConfigurations == configurations
         assert eclipseClasspath.minusConfigurations == []
@@ -160,36 +147,10 @@ class EclipsePluginTest extends Specification {
 
     private void checkEclipseJdt() {
         GenerateEclipseJdt eclipseJdt = project.eclipseJdt
-        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseJdt)
-        assert eclipseJdt.sourceCompatibility == project.sourceCompatibility
-        assert eclipseJdt.targetCompatibility == project.targetCompatibility
+        assert project.tasks.eclipse.taskDependencies.getDependencies(project.tasks.eclipse).contains(eclipseJdt)
         assert eclipseJdt.outputFile == project.file('.settings/org.eclipse.jdt.core.prefs')
     }
 
-    private void checkEclipseWtpFacet() {
-        GenerateEclipseWtpFacet eclipseWtpFacet = project.eclipseWtpFacet
-        assert eclipseWtpFacet instanceof GenerateEclipseWtpFacet
-        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseWtpFacet)
-        assert eclipseWtpFacet.inputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-        assert eclipseWtpFacet.outputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
-        assert eclipseWtpFacet.facets == [new Facet("jst.web", "2.4"), new Facet("jst.java", "5.0")]
-    }
-
-    private void checkEclipseWtpComponent() {
-        def eclipseWtpComponent = project.eclipseWtpComponent
-        assert eclipseWtpComponent instanceof GenerateEclipseWtpComponent
-        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseWtpComponent)
-        assert eclipseWtpComponent.sourceDirs == project.sourceSets.main.allSource.srcDirs
-        assert eclipseWtpComponent.plusConfigurations == [project.configurations.runtime] as Set
-        assert eclipseWtpComponent.minusConfigurations == [project.configurations.providedRuntime] as Set
-        assert eclipseWtpComponent.deployName == project.name
-        assert eclipseWtpComponent.contextPath == project.war.baseName
-        assert eclipseWtpComponent.inputFile == project.file('.settings/org.eclipse.wst.common.component')
-        assert eclipseWtpComponent.outputFile == project.file('.settings/org.eclipse.wst.common.component')
-        assert eclipseWtpComponent.variables == [:]
-        assert eclipseWtpComponent.resources == [new WbResource('/', project.convention.plugins.war.webAppDirName)]
-    }
-
     void assertThatCleanEclipseDependsOn(Project project, Task dependsOnTask) {
         assert dependsOnTask instanceof Delete
         assert project.cleanEclipse.taskDependencies.getDependencies(project.cleanEclipse).contains(dependsOnTask)
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPluginTest.groovy
new file mode 100644
index 0000000..eba13fe
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPluginTest.groovy
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2011 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.ide.eclipse
+
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.plugins.ide.eclipse.model.Facet
+import org.gradle.plugins.ide.eclipse.model.Facet.FacetType
+import org.gradle.plugins.ide.eclipse.model.WbResource
+import org.gradle.util.HelperUtil
+import spock.lang.Issue
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 6/28/11
+ */
+class EclipseWtpPluginTest extends Specification {
+
+    private final DefaultProject project = HelperUtil.createRootProject()
+    private final EclipseWtpPlugin wtpPlugin = new EclipseWtpPlugin()
+
+    def "has description"() {
+        when:
+        wtpPlugin.apply(project)
+
+        then:
+        wtpPlugin.lifecycleTask.description
+        wtpPlugin.cleanTask.description
+    }
+
+    def "does not break when eclipse and eclipseWtp applied"() {
+        expect:
+        project.apply plugin: 'eclipse'
+        project.apply plugin: 'eclipse-wtp'
+    }
+
+    def "the eclipse plugin is applied along with eclipseWtp plugin"() {
+        when:
+        wtpPlugin.apply(project)
+
+        then:
+        project.tasks.eclipse.dependsOn.contains(project.eclipseWtp)
+        project.tasks.cleanEclipse.dependsOn.contains(project.cleanEclipseWtp)
+    }
+
+     def applyToWarProject_shouldHaveWebProjectAndClasspathTask() {
+        when:
+        project.apply(plugin: 'war')
+        project.sourceCompatibility = 1.5
+        wtpPlugin.apply(project)
+
+        then:
+        [project.cleanEclipseWtpComponent, project.cleanEclipseWtpFacet].each {
+            assert project.tasks.cleanEclipseWtp.dependsOn.contains(it)
+        }
+
+        checkEclipseClasspath([project.configurations.testRuntime])
+        checkEclipseWtpComponentForWar()
+        checkEclipseWtpFacet([
+                new Facet(FacetType.fixed, "jst.java", null),
+                new Facet(FacetType.fixed, "jst.web", null),
+                new Facet(FacetType.installed, "jst.web", "2.4"),
+                new Facet(FacetType.installed, "jst.java", "5.0")])
+    }
+
+    @Issue("GRADLE-1770")
+    def "wb resource honors web app dir even if configured after plugin appliance"() {
+        when:
+        project.apply(plugin: 'war')
+        wtpPlugin.apply(project)
+        project.webAppDirName = 'foo'
+
+        then:
+        project.eclipseWtpComponent.resources == [new WbResource('/', 'foo')]
+    }
+
+    def applyToEarProject_shouldHaveWebProjectAndClasspathTask() {
+        when:
+        project.apply(plugin: 'java')
+        project.apply(plugin: 'ear')
+        wtpPlugin.apply(project)
+
+        then:
+        [project.cleanEclipseWtpComponent, project.cleanEclipseWtpFacet].each {
+            assert project.cleanEclipseWtp.dependsOn.contains(it)
+        }
+        checkEclipseClasspath([project.configurations.testRuntime])
+        checkEclipseWtpComponentForEar()
+        checkEclipseWtpFacet([
+                new Facet(FacetType.fixed, "jst.ear", null),
+                new Facet(FacetType.installed, "jst.ear", "5.0")])
+    }
+
+    private void checkEclipseWtpComponentForEar() {
+        def eclipseWtpComponent = project.eclipseWtpComponent
+        assert eclipseWtpComponent instanceof GenerateEclipseWtpComponent
+        assert project.tasks.eclipseWtp.taskDependencies.getDependencies(project.tasks.eclipseWtp).contains(eclipseWtpComponent)
+        assert eclipseWtpComponent.sourceDirs == project.sourceSets.main.allSource.srcDirs
+        assert eclipseWtpComponent.component.rootConfigurations == [project.configurations.deploy] as Set
+        assert eclipseWtpComponent.component.libConfigurations == [project.configurations.earlib] as Set
+        assert eclipseWtpComponent.minusConfigurations == [] as Set
+        assert eclipseWtpComponent.deployName == project.name
+        assert eclipseWtpComponent.contextPath == null
+        assert eclipseWtpComponent.inputFile == project.file('.settings/org.eclipse.wst.common.component')
+        assert eclipseWtpComponent.outputFile == project.file('.settings/org.eclipse.wst.common.component')
+        assert eclipseWtpComponent.variables == [:]
+        assert eclipseWtpComponent.resources == []
+        assert eclipseWtpComponent.component.classesDeployPath == "/"
+        assert eclipseWtpComponent.component.libDeployPath == "/lib"
+    }
+
+    private void checkEclipseWtpFacet(def facets) {
+        GenerateEclipseWtpFacet eclipseWtpFacet = project.eclipseWtpFacet
+        assert eclipseWtpFacet instanceof GenerateEclipseWtpFacet
+        assert project.tasks.eclipseWtp.taskDependencies.getDependencies(project.tasks.eclipse).contains(eclipseWtpFacet)
+        assert eclipseWtpFacet.inputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+        assert eclipseWtpFacet.outputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+    }
+
+    private void checkEclipseWtpComponentForWar() {
+        def eclipseWtpComponent = project.eclipseWtpComponent
+        assert eclipseWtpComponent instanceof GenerateEclipseWtpComponent
+        assert project.tasks.eclipseWtp.taskDependencies.getDependencies(project.tasks.eclipse).contains(eclipseWtpComponent)
+        assert eclipseWtpComponent.sourceDirs == project.sourceSets.main.allSource.srcDirs
+        assert eclipseWtpComponent.component.rootConfigurations == [] as Set
+        assert eclipseWtpComponent.component.libConfigurations == [project.configurations.runtime] as Set
+        assert eclipseWtpComponent.minusConfigurations == [project.configurations.providedRuntime] as Set
+        assert eclipseWtpComponent.deployName == project.name
+        assert eclipseWtpComponent.contextPath == project.war.baseName
+        assert eclipseWtpComponent.inputFile == project.file('.settings/org.eclipse.wst.common.component')
+        assert eclipseWtpComponent.outputFile == project.file('.settings/org.eclipse.wst.common.component')
+        assert eclipseWtpComponent.variables == [:]
+        assert eclipseWtpComponent.resources == [new WbResource('/', project.convention.plugins.war.webAppDirName)]
+        assert eclipseWtpComponent.component.classesDeployPath == "/WEB-INF/classes"
+        assert eclipseWtpComponent.component.libDeployPath == "/WEB-INF/lib"
+    }
+
+    private void checkEclipseClasspath(def configurations) {
+        GenerateEclipseClasspath eclipseClasspath = project.tasks.eclipseClasspath
+        assert eclipseClasspath.plusConfigurations == configurations
+    }
+
+    def applyToEarProjectWithoutJavaPlugin_shouldUseAppDirInWtpComponentSource() {
+        when:
+        project.apply(plugin: 'ear')
+        wtpPlugin.apply(project)
+        then:
+        project.eclipseWtpComponent.sourceDirs == [project.file(project.appDirName)] as Set
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProjectTest.groovy
deleted file mode 100644
index c446df0..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProjectTest.groovy
+++ /dev/null
@@ -1,55 +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.ide.eclipse
-
-import org.gradle.api.internal.ConventionTask
-import org.gradle.api.tasks.AbstractSpockTaskTest
-import org.gradle.plugins.ide.eclipse.model.BuildCommand
-import org.gradle.plugins.ide.eclipse.model.EclipseProject
-
-/**
- * @author Hans Dockter
- */
-class GenerateEclipseProjectTest extends AbstractSpockTaskTest {
-    GenerateEclipseProject eclipseProject
-
-    ConventionTask getTask() {
-        return eclipseProject
-    }
-
-    def setup() {
-        eclipseProject = createTask(GenerateEclipseProject.class);
-        eclipseProject.projectModel = new EclipseProject()
-    }
-
-    def natures_shouldAdd() {
-        when:
-        eclipseProject.natures 'nature1'
-        eclipseProject.natures 'nature2'
-
-        then:
-        eclipseProject.natures == ['nature1', 'nature2']
-    }
-
-    def buildCommands_shouldAdd() {
-        when:
-        eclipseProject.buildCommand 'command1', key1: 'value1'
-        eclipseProject.buildCommand 'command2'
-
-        then:
-        eclipseProject.buildCommands as List == [new BuildCommand('command1', [key1: 'value1']), new BuildCommand('command2')]
-    }
-}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy
index 738e4cc..b4a6a59 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponentTest.groovy
@@ -28,7 +28,7 @@ public class GenerateEclipseWtpComponentTest extends AbstractSpockTaskTest {
     private eclipseComponent = createTask(GenerateEclipseWtpComponent)
 
     def setup() {
-        eclipseComponent.component = new EclipseWtpComponent()
+        eclipseComponent.component = new EclipseWtpComponent(project, null)
     }
 
     ConventionTask getTask() { eclipseComponent }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy
index 0b4676c..daec953 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacetTest.groovy
@@ -17,7 +17,6 @@ package org.gradle.plugins.ide.eclipse
 
 import org.gradle.api.tasks.AbstractSpockTaskTest
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet
-import org.gradle.plugins.ide.eclipse.model.Facet
 
 /**
  * @author Hans Dockter
@@ -33,12 +32,4 @@ public class GenerateEclipseWtpFacetTest extends AbstractSpockTaskTest {
         return eclipseFacet
     }
 
-    def "facet should add"() {
-        when:
-        task.facet name: 'facet1', version: '1.0'
-        task.facet name: 'facet2', version: '2.0'
-
-        then:
-        task.facets == [new Facet('facet1', '1.0'), new Facet('facet2', '2.0')]
-    }
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
index 5e187d2..0ea3472 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ClasspathTest.groovy
@@ -20,34 +20,39 @@ import org.gradle.api.internal.XmlTransformer
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
 /**
  * @author Hans Dockter
  */
 public class ClasspathTest extends Specification {
-    private static final CUSTOM_ENTRIES = [
-            new ProjectDependency("/test2", false, null, [] as Set, null),
-            new Container("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6",
-                false, null, [] as Set),
-            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),
+    final fileReferenceFactory = new FileReferenceFactory()
+    final customEntries = [
+            new ProjectDependency("/test2", null),
+            new Container("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"),
+            new Library(fileReferenceFactory.fromPath("/apache-ant-1.7.1/lib/ant-antlr.jar")),
+            new SourceFolder("src", "bin2"),
+            new Variable(fileReferenceFactory.fromVariablePath("GRADLE_CACHE/ant-1.6.5.jar")),
+            new Container("org.eclipse.jdt.USER_LIBRARY/gradle"),
             new Output("bin")]
-    private static final PROJECT_DEPENDENCY = [CUSTOM_ENTRIES[0]]
-    private static final ALL_DEPENDENCIES = [CUSTOM_ENTRIES[0], CUSTOM_ENTRIES[2]]
+    final projectDependency = [customEntries[0]]
+    final allDependencies = [customEntries[0], customEntries[2], customEntries[4]]
 
-    private final Classpath classpath = new Classpath(new XmlTransformer())
+    private final Classpath classpath = new Classpath(new XmlTransformer(), fileReferenceFactory)
 
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder()
 
+    def setup() {
+
+    }
+
     def "load from reader"() {
         when:
         classpath.load(customClasspathReader)
 
         then:
-        classpath.entries == CUSTOM_ENTRIES
+        classpath.entries == customEntries
     }
 
     def "configure overwrites dependencies and appends all other entries"() {
@@ -55,12 +60,12 @@ public class ClasspathTest extends Specification {
 
         when:
         classpath.load(customClasspathReader)
-        def newEntries = constructorEntries + PROJECT_DEPENDENCY
+        def newEntries = constructorEntries + projectDependency
         classpath.configure(newEntries)
 
         then:
-        def entriesToBeKept = CUSTOM_ENTRIES - ALL_DEPENDENCIES
-        classpath.entries ==  entriesToBeKept + newEntries
+        def entriesToBeKept = customEntries - allDependencies
+        classpath.entries == entriesToBeKept + newEntries
     }
 
     def "load defaults"() {
@@ -78,7 +83,7 @@ public class ClasspathTest extends Specification {
         classpath.load(customClasspathReader)
         classpath.configure(constructorEntries)
         def xml = getToXmlReader()
-        def other = new Classpath(new XmlTransformer())
+        def other = new Classpath(new XmlTransformer(), fileReferenceFactory)
         other.load(xml)
 
         then:
@@ -90,7 +95,9 @@ public class ClasspathTest extends Specification {
     }
 
     private Library createSomeLibrary() {
-        return new Library("/somepath", true, null, [] as Set, null, null)
+        Library library = new Library(fileReferenceFactory.fromPath("/somepath"))
+        library.exported = true
+        return library
     }
 
     private InputStream getToXmlReader() {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy
index 78d79ea..c09942c 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ContainerTest.groovy
@@ -59,7 +59,11 @@ class ContainerTest extends Specification {
     }
 
     private Container createContainer() {
-        return new Container('somePath', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set)
+        Container container = new Container('somePath')
+        container.exported = true
+        container.nativeLibraryLocation = 'mynative'
+        container.accessRules += [new AccessRule('nonaccessible', 'secret**')]
+        return container
     }
 
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy
index b271623..6de0370 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy
@@ -19,15 +19,15 @@ package org.gradle.plugins.ide.eclipse.model
 import spock.lang.Specification
 
 /**
- * Author: Szczepan Faber, created at: 4/19/11
+ * @author Szczepan Faber, created at: 4/19/11
  */
 class EclipseModelTest extends Specification {
 
-    EclipseModel model = new EclipseModel(classpath: new EclipseClasspath(), wtp: new EclipseWtp(component: new EclipseWtpComponent()))
+    EclipseModel model = new EclipseModel(classpath: new EclipseClasspath(null))
 
     def "enables setting path variables even if wtp is not configured"() {
         given:
-        model.wtp.component = null
+        model.wtp = null
 
         when:
         model.pathVariables(one: new File('.'))
@@ -36,14 +36,30 @@ class EclipseModelTest extends Specification {
         then:
         model.classpath.pathVariables == [one: new File('.'), two: new File('.')]
     }
+    
+    def "enables setting path variables even if wtp component is not configured"() {
+        given:
+        model.wtp = new EclipseWtp(model.classpath)
+        //for example when wtp+java applied but project is not a dependency to any war/ear.
+        assert model.wtp.component == null
+
+        when:
+        model.pathVariables(one: new File('.'))
+
+        then:
+        model.classpath.pathVariables == [one: new File('.')]
+    }
 
     def "enables setting path variables"() {
+        given:
+        model.wtp = new EclipseWtp(model.classpath)
+        model.wtp.component = new EclipseWtpComponent(null, null)
+        
         when:
         model.pathVariables(one: new File('.'))
-        model.pathVariables(two: new File('.'))
 
         then:
-        model.classpath.pathVariables == [one: new File('.'), two: new File('.')]
-        model.wtp.component.pathVariables == [one: new File('.'), two: new File('.')]
+        model.classpath.pathVariables == [one: new File('.')]
+        model.wtp.component.pathVariables == [one: new File('.')]
     }
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy
index c2ea5f8..d8c4106 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseProjectTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.api.InvalidUserDataException
 import spock.lang.Specification
 
 /**
- * Author: Szczepan Faber, created at: 4/17/11
+ * @author Szczepan Faber, created at: 4/17/11
  */
 class EclipseProjectTest extends Specification {
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy
index 02f115e..89298dd 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/FacetTest.groovy
@@ -15,6 +15,8 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.eclipse.model.Facet.FacetType;
+
 import spock.lang.Specification
 
 /**
@@ -23,6 +25,7 @@ import spock.lang.Specification
 
 class FacetTest extends Specification {
     final static String XML_TEXT = '<installed facet="jst.web" version="2.4"/>'
+    final static String FIXED_XML_TEXT = '<fixed facet="jst.web"/>'
 
     def canReadFromXml() {
         when:
@@ -32,6 +35,14 @@ class FacetTest extends Specification {
         facet == createFacet()
     }
 
+    def canReadFixedFromXml() {
+        when:
+        Facet facet = new Facet(new XmlParser().parseText(FIXED_XML_TEXT))
+
+        then:
+        facet == createFixedFacet()
+    }
+
     def canWriteToXml() {
         Node rootNode = new Node(null, 'root')
 
@@ -42,6 +53,16 @@ class FacetTest extends Specification {
         new Facet(rootNode.installed[0]) == createFacet()
     }
 
+    def canWriteFixedToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createFixedFacet().appendNode(rootNode)
+
+        then:
+        new Facet(rootNode.fixed[0]) == createFixedFacet()
+    }
+
     def equality() {
         Facet facet = createFacet()
         facet.name += 'x'
@@ -50,8 +71,20 @@ class FacetTest extends Specification {
         facet != createFacet()
     }
 
+    def fixedEquality() {
+        Facet facet = createFixedFacet()
+        facet.name += 'x'
+
+        expect:
+        facet != createFixedFacet()
+    }
+
     private Facet createFacet() {
-        return new Facet("jst.web", "2.4")
+        return new Facet(FacetType.installed, "jst.web", "2.4")
+    }
+
+    private Facet createFixedFacet() {
+        return new Facet(FacetType.fixed, "jst.web", null)
     }
 
 
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/JdtTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/JdtTest.groovy
index 606e33c..64c1380 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/JdtTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/JdtTest.groovy
@@ -16,10 +16,12 @@
 package org.gradle.plugins.ide.eclipse.model
 
 import org.gradle.api.JavaVersion
+import org.gradle.api.internal.PropertiesTransformer
+
 import spock.lang.Specification
 
 class JdtTest extends Specification {
-    final Jdt jdt = new Jdt()
+    final Jdt jdt = new Jdt(new PropertiesTransformer())
 
     def defaultsForJava1_3Source() {
         Properties properties = new Properties()
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy
index 06dd97c..ea5c99c 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/LibraryTest.groovy
@@ -15,6 +15,8 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+import org.gradle.util.Matchers
 import spock.lang.Specification
 
 /**
@@ -22,20 +24,28 @@ import spock.lang.Specification
  */
 
 class LibraryTest extends Specification {
-    final static String XML_TEXT = '''
+    final static String XML_TEXT_TEMPLATE = '''
                     <classpathentry exported="true" kind="lib" path="/ant.jar" sourcepath="/ant-src.jar">
                         <attributes>
                             <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
-                            <attribute name="javadoc_location" value="jar:file:/ant-javadoc.jar!/path"/>
+                            <attribute name="javadoc_location" value="jar:%FILE_URI%!/"/>
                         </attributes>
                         <accessrules>
                             <accessrule kind="nonaccessible" pattern="secret**"/>
                         </accessrules>
                     </classpathentry>'''
+    final fileReferenceFactory = new FileReferenceFactory()
+
+    String platformXml;
+
+    def setup(){
+        //xml differs on windows and mac due to required absolute paths for javadoc uri
+        platformXml = XML_TEXT_TEMPLATE.replace("%FILE_URI%", new File("ant-javadoc.jar").toURI().toString());
+    }
 
     def canReadFromXml() {
         when:
-        Library library = new Library(new XmlParser().parseText(XML_TEXT))
+        Library library = new Library(new XmlParser().parseText(platformXml), fileReferenceFactory)
 
         then:
         library == createLibrary()
@@ -48,19 +58,27 @@ class LibraryTest extends Specification {
         createLibrary().appendNode(rootNode)
 
         then:
-        new Library(rootNode.classpathentry[0]) == createLibrary()
+        new Library(rootNode.classpathentry[0], fileReferenceFactory) == createLibrary()
     }
 
     def equality() {
         Library library = createLibrary()
-        library.javadocPath += 'x'
+        Library same = createLibrary()
+        Library differentPath = createLibrary()
+        differentPath.path = '/other'
 
         expect:
-        library != createLibrary()
+        library Matchers.strictlyEqual(same)
+        library != differentPath
     }
 
     private Library createLibrary() {
-        return new Library('/ant.jar', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set,
-                "/ant-src.jar", "jar:file:/ant-javadoc.jar!/path")
+        Library library = new Library(fileReferenceFactory.fromPath('/ant.jar'))
+        library.exported = true
+        library.nativeLibraryLocation = 'mynative'
+        library.accessRules += [new AccessRule('nonaccessible', 'secret**')]
+        library.sourcePath = fileReferenceFactory.fromPath("/ant-src.jar")
+        library.javadocPath = fileReferenceFactory.fromJarURI("jar:${new File("ant-javadoc.jar").toURI()}!/");
+        return library
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy
index eeb5240..bc9c15c 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/ProjectDependencyTest.groovy
@@ -59,8 +59,10 @@ class ProjectDependencyTest extends Specification {
     }
 
     private ProjectDependency createProjectDependency() {
-        return new ProjectDependency('/test2', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set, null)
+        ProjectDependency dependency = new ProjectDependency('/test2', null)
+        dependency.exported = true
+        dependency.nativeLibraryLocation = 'mynative'
+        dependency.accessRules += [new AccessRule('nonaccessible', 'secret**')]
+        return dependency
     }
-
-
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy
index ed2aac4..ba9e92c 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/SourceFolderTest.groovy
@@ -55,7 +55,39 @@ class SourceFolderTest extends Specification {
     }
 
     def createSourceFolder() {
-        return new SourceFolder('src', 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set,
-            'bin2', ['**/Test1*' ,'**/Test2*'], ['**/Test3*' ,'**/Test4*'])
+        SourceFolder folder = new SourceFolder('src', 'bin2')
+        folder.nativeLibraryLocation = 'mynative'
+        folder.accessRules += [new AccessRule('nonaccessible', 'secret**')]
+        folder.includes += ['**/Test1*', '**/Test2*']
+        folder.excludes += ['**/Test3*', '**/Test4*']
+        return folder
+    }
+
+    def "ignores null dir in equality"() {
+        given:
+        def one = createSourceFolder()
+        def two = createSourceFolder()
+        one == two
+        two == one
+
+        when:
+        one.dir = null
+        two.dir = new File('.')
+
+        then:
+        one == two
+        two == one
+    }
+
+    def "trims path"() {
+        given:
+        def one = createSourceFolder()
+        one.dir = new File('/some/path/to/foo')
+
+        when:
+        one.trimPath()
+
+        then:
+        one.path == 'foo'
     }
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy
index 1310e0d..3a4e2a9 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/VariableTest.groovy
@@ -15,6 +15,8 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
+import org.gradle.util.Matchers
 import spock.lang.Specification
 
 /**
@@ -22,20 +24,28 @@ import spock.lang.Specification
  */
 
 class VariableTest extends Specification {
-    final static String XML_TEXT = '''
+    final static String XML_TEXT_TEMPLATE = '''
                 <classpathentry exported="true" kind="var" path="/GRADLE_CACHE/ant.jar" sourcepath="/GRADLE_CACHE/ant-src.jar">
                     <attributes>
                         <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
-                        <attribute name="javadoc_location" value="jar:file:/GRADLE_CACHE/ant-javadoc.jar!/path"/>
+                        <attribute name="javadoc_location" value="jar:%FILE_URI%!/"/>
                     </attributes>
                     <accessrules>
                         <accessrule kind="nonaccessible" pattern="secret**"/>
                     </accessrules>
                 </classpathentry>'''
+    final fileReferenceFactory = new FileReferenceFactory()
+
+    String platformXml;
+
+    def setup(){
+            //xml differs on windows and mac due to required absolute paths for javadoc uri
+            platformXml = XML_TEXT_TEMPLATE.replace("%FILE_URI%", new File("ant-javadoc.jar").toURI().toString());
+        }
 
     def canReadFromXml() {
         when:
-        Variable variable = new Variable(new XmlParser().parseText(XML_TEXT))
+        Variable variable = new Variable(new XmlParser().parseText(platformXml), fileReferenceFactory)
 
         then:
         variable == createVariable()
@@ -48,21 +58,27 @@ class VariableTest extends Specification {
         createVariable().appendNode(rootNode)
 
         then:
-        new Variable(rootNode.classpathentry[0]) == createVariable()
+        new Variable(rootNode.classpathentry[0], fileReferenceFactory) == createVariable()
     }
 
     def equality() {
         Variable variable = createVariable()
-        variable.sourcePath += 'x'
+        Variable same = createVariable()
+        Variable different = createVariable()
+        different.path = '/other'
 
         expect:
-        variable != createVariable()
+        variable Matchers.strictlyEqual(same)
+        variable != different
     }
 
     private Variable createVariable() {
-        return new Variable('/GRADLE_CACHE/ant.jar', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set,
-                "/GRADLE_CACHE/ant-src.jar", "jar:file:/GRADLE_CACHE/ant-javadoc.jar!/path")
+        Variable variable = new Variable(fileReferenceFactory.fromVariablePath('/GRADLE_CACHE/ant.jar'))
+        variable.exported = true
+        variable.nativeLibraryLocation = 'mynative'
+        variable.accessRules += [new AccessRule('nonaccessible', 'secret**')]
+        variable.sourcePath = fileReferenceFactory.fromVariablePath("/GRADLE_CACHE/ant-src.jar")
+        variable.javadocPath = fileReferenceFactory.fromJarURI("jar:${new File("ant-javadoc.jar").toURI()}!/");
+        return variable
     }
-
-
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
index cf7a408..fb64fd3 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/WtpFacetTest.groovy
@@ -17,15 +17,17 @@ package org.gradle.plugins.ide.eclipse.model
 
 import org.custommonkey.xmlunit.XMLUnit
 import org.gradle.api.internal.XmlTransformer
+import org.gradle.plugins.ide.eclipse.model.Facet.FacetType;
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
+
 import spock.lang.Specification
 
 /**
  * @author Hans Dockter
  */
 public class WtpFacetTest extends Specification {
-    private static final List CUSTOM_FACETS = [new Facet('jst.web', '2.4'), new Facet('jst.java', '1.4')]
+    private static final List CUSTOM_FACETS = [new Facet(FacetType.fixed, 'jst.java', null), new Facet(FacetType.fixed, 'jst.web', null), new Facet(FacetType.installed, 'jst.web', '2.4'), new Facet(FacetType.installed, 'jst.java', '1.4')]
 
     private final WtpFacet facet = new WtpFacet(new XmlTransformer())
 
@@ -75,6 +77,6 @@ public class WtpFacetTest extends Specification {
     }
 
     private Facet createSomeFacet() {
-        new Facet('someName', '1.0.0')
+        new Facet(FacetType.installed, 'someName', '1.0.0')
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy
new file mode 100644
index 0000000..d764192
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/FileReferenceFactoryTest.groovy
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 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.ide.eclipse.model.internal
+
+import org.gradle.util.Matchers
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class FileReferenceFactoryTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final TestFile rootDir = tmpDir.createDir("root")
+    final FileReferenceFactory factory = new FileReferenceFactory()
+
+    def setup() {
+        factory.addPathVariable('ROOT_DIR', rootDir)
+    }
+
+    def "creates a reference to a file which is not under any root directory"() {
+        TestFile file = tmpDir.file("file.txt")
+
+        expect:
+        def reference = factory.fromFile(file)
+        reference.file == file
+        reference.path == relpath(file)
+        !reference.relativeToPathVariable
+    }
+
+    def "creates a reference for a file under a root directory"() {
+        TestFile file = rootDir.file("a/file.txt")
+
+        expect:
+        def reference = factory.fromFile(file)
+        reference.file == file
+        reference.path == "ROOT_DIR/a/file.txt"
+        reference.relativeToPathVariable
+    }
+
+    def "creates a reference for a root directory"() {
+        expect:
+        def reference = factory.fromFile(rootDir)
+        reference.file == rootDir
+        reference.path == "ROOT_DIR"
+        reference.relativeToPathVariable
+    }
+
+    def "creates null reference for a null file"() {
+        expect:
+        factory.fromFile(null) == null
+    }
+
+    def "creates a reference from a file path"() {
+        TestFile file = tmpDir.file("file.txt")
+
+        expect:
+        def reference = factory.fromPath(relpath(file))
+        reference.file == file
+        reference.path == relpath(file)
+        !reference.relativeToPathVariable
+    }
+
+    def "creates a reference from a jar url"() {
+        TestFile file = tmpDir.file("file.txt")
+
+        expect:
+        def reference = factory.fromJarURI(jarUrL(file))
+        reference.file == file
+        reference.path == relpath(file)
+        reference.jarURL == jarUrL(file);
+        !reference.relativeToPathVariable
+    }
+
+    def "creates null reference for a null jar url"() {
+        expect:
+        factory.fromJarURI(null) == null
+    }
+
+    def "creates null reference for a null file path"() {
+        expect:
+        factory.fromPath(null) == null
+    }
+
+    def "creates a reference from a variable path"() {
+        TestFile file = rootDir.file("a/file.txt")
+
+        expect:
+        def reference = factory.fromVariablePath("ROOT_DIR/a/file.txt")
+        reference.file == file
+        reference.path == "ROOT_DIR/a/file.txt"
+        reference.relativeToPathVariable
+    }
+
+    def "creates null reference for a null file variable path"() {
+        expect:
+        factory.fromVariablePath(null) == null
+    }
+
+    def "file references are equal when they point to the same file"() {
+        TestFile file = rootDir.file("a/file.txt")
+        def reference = factory.fromFile(file)
+        def sameFile = factory.fromFile(file)
+        def differentFile = factory.fromFile(rootDir)
+
+        expect:
+        reference Matchers.strictlyEqual(sameFile)
+        reference != differentFile
+    }
+
+    private String relpath(File file) {
+        return file.absolutePath.replace(File.separator, '/')
+    }
+
+    private String jarUrL(File file) {
+        return "jar:${file.toURI()}!/"
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy
index 7a48887..ef31bd0 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/internal/ProjectDependencyBuilderTest.groovy
@@ -15,6 +15,8 @@
  */
 package org.gradle.plugins.ide.eclipse.model.internal
 
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 /**
@@ -22,38 +24,27 @@ import spock.lang.Specification
  */
 class ProjectDependencyBuilderTest extends Specification {
 
+    def Project project = HelperUtil.createRootProject()
     def ProjectDependencyBuilder builder = new ProjectDependencyBuilder()
 
-    static class ProjectStub {
-        String name
-        String path
-        GenerateEclipseProjectStub eclipseProject
-    }
-
-    static class GenerateEclipseProjectStub {
-        String projectName
-    }
-
     def "should create dependency using project name"() {
-        given:
-        def project = new ProjectStub(name: 'coolProject')
-
         when:
-        def dependency = builder.build(project)
+        def dependency = builder.build(project, 'compile')
 
         then:
-        dependency.path == '/coolProject'
+        dependency.path == "/$project.name"
+        dependency.declaredConfigurationName == 'compile'
     }
 
     def "should create dependency using eclipse projectName"() {
         given:
-        def eclipseProject = new GenerateEclipseProjectStub(projectName: 'eclipse-project')
-        def project = new ProjectStub(eclipseProject: eclipseProject)
+        project.apply(plugin: 'eclipse')
+        project.eclipse.project.name = 'foo'
 
         when:
-        def dependency = builder.build(project)
+        def dependency = builder.build(project, 'runtime')
 
         then:
-        dependency.path == '/eclipse-project'
+        dependency.path == '/foo'
     }
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/ GenerateIdeaModuleTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/ GenerateIdeaModuleTest.groovy
index 615faaf..5e70852 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/ GenerateIdeaModuleTest.groovy	
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/ GenerateIdeaModuleTest.groovy	
@@ -33,14 +33,14 @@ class GenerateIdeaModuleTest extends Specification {
     def "moduleName controls outputFile"() {
         given:
         applyPluginToProjects()
-        assert childProject.ideaModule.moduleName == "child"
+        assert childProject.idea.module.name == "child"
         def existingOutputFolder = childProject.ideaModule.outputFile.parentFile
 
         when:
-        childProject.ideaModule.moduleName = "foo"
+        childProject.idea.module.name = "foo"
 
         then:
-        childProject.ideaModule.moduleName == "foo"
+        childProject.idea.module.name == "foo"
         childProject.ideaModule.outputFile.name == "foo.iml"
         childProject.ideaModule.outputFile.parentFile == existingOutputFolder
     }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
index 2e7f0ea..0365780 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.gradle.plugins.ide.idea
 
-import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.tasks.Delete
+import org.gradle.plugins.ide.idea.model.IdeaLanguageLevel
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
@@ -29,7 +29,6 @@ import spock.lang.Specification
 class IdeaPluginTest extends Specification {
     private final DefaultProject project = HelperUtil.createRootProject()
     private final Project childProject = HelperUtil.createChildProject(project, "child", new File("."))
-    private final IdeaPlugin ideaPlugin = new IdeaPlugin()
 
     def "adds 'ideaProject' task to root project"() {
         when:
@@ -40,21 +39,31 @@ class IdeaPluginTest extends Specification {
         GenerateIdeaProject ideaProjectTask = project.ideaProject
         ideaProjectTask instanceof GenerateIdeaProject
         ideaProjectTask.outputFile == new File(project.projectDir, project.name + ".ipr")
-        ideaProjectTask.subprojects == project.rootProject.allprojects
-        ideaProjectTask.javaVersion == JavaVersion.VERSION_1_6.toString()
-        ideaProjectTask.wildcards == ['!?*.java', '!?*.groovy'] as Set
+        ideaProjectTask.ideaProject.modules == [project.idea.module, childProject.idea.module]
+        ideaProjectTask.ideaProject.jdkName == "1.6"
+        ideaProjectTask.ideaProject.languageLevel.level == "JDK_1_6"
 
         childProject.tasks.findByName('ideaProject') == null
         childProject.tasks.findByName('cleanIdeaProject') == null
     }
 
+    def "configures idea project"() {
+        when:
+        applyPluginToProjects()
+
+        then:
+        project.idea.project.wildcards == ['!?*.java', '!?*.groovy'] as Set
+    }
+
     def "adds 'ideaWorkspace' task to root project"() {
         when:
         applyPluginToProjects()
 
         then:
         project.ideaWorkspace instanceof GenerateIdeaWorkspace
-        assertThatCleanIdeaDependsOnDeleteTask(project, project.cleanIdeaWorkspace)
+        assert project.cleanIdeaWorkspace instanceof Delete
+        assert !project.cleanIdea.taskDependencies.getDependencies(project.cleanIdea).contains(project.cleanIdeaWorkspace)
+
 
         childProject.tasks.findByName('ideaWorkspace') == null
         childProject.tasks.findByName('cleanIdeaWorkspace') == null
@@ -75,13 +84,11 @@ class IdeaPluginTest extends Specification {
         project.apply(plugin: 'java')
 
         then:
-        project.ideaProject.javaVersion == project.sourceCompatibility.toString()
+        project.idea.project.jdkName == project.sourceCompatibility.toString()
+        project.idea.project.languageLevel.level == new IdeaLanguageLevel(project.sourceCompatibility).level
 
-        GenerateIdeaModule ideaModuleTask = project.ideaModule
-        ideaModuleTask.sourceDirs == project.sourceSets.main.allSource.srcDirs
-        ideaModuleTask.testSourceDirs == project.sourceSets.test.allSource.srcDirs
         def configurations = project.configurations
-        ideaModuleTask.scopes == [
+        project.idea.module.scopes == [
                 COMPILE: [plus: [configurations.compile], minus: []],
                 RUNTIME: [plus: [configurations.runtime], minus: [configurations.compile]],
                 TEST: [plus: [configurations.testRuntime], minus: [configurations.runtime]],
@@ -96,7 +103,7 @@ class IdeaPluginTest extends Specification {
         project.buildDir = project.file('target')
 
         then:
-        project.ideaModule.excludeDirs == [project.buildDir, project.file('.gradle')] as Set
+        project.idea.module.excludeDirs == [project.buildDir, project.file('.gradle')] as Set
     }
 
     def "adds 'cleanIdea' task to projects"() {
@@ -108,15 +115,31 @@ class IdeaPluginTest extends Specification {
         childProject.cleanIdea instanceof Task
     }
 
+     def "adds single entry libraries from source sets"() {
+        when:
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        project.sourceSets.main.output.dir 'generated-folder'
+        project.sourceSets.main.output.dir 'ws-generated'
+
+        project.sourceSets.test.output.dir 'generated-test'
+        project.sourceSets.test.output.dir 'test-resources'
+
+        then:
+        def runtime = project.ideaModule.module.singleEntryLibraries.RUNTIME
+        runtime.any { it.name.contains('generated-folder') }
+        runtime.any { it.name.contains('ws-generated') }
+
+        def test = project.ideaModule.module.singleEntryLibraries.TEST
+        test.any { it.name.contains('generated-test') }
+        test.any { it.name.contains('test-resources') }
+     }
+
     private void assertThatIdeaModuleIsProperlyConfigured(Project project) {
         GenerateIdeaModule ideaModuleTask = project.ideaModule
         assert ideaModuleTask instanceof GenerateIdeaModule
         assert ideaModuleTask.outputFile == new File(project.projectDir, project.name + ".iml")
-        assert ideaModuleTask.moduleDir == project.projectDir
-        assert ideaModuleTask.sourceDirs == [] as Set
-        assert ideaModuleTask.testSourceDirs == [] as Set
-        assert ideaModuleTask.excludeDirs == [project.buildDir, project.file('.gradle')] as Set
-        assert ideaModuleTask.variables == [:]
         assertThatCleanIdeaDependsOnDeleteTask(project, project.cleanIdeaModule)
     }
 
@@ -126,7 +149,7 @@ class IdeaPluginTest extends Specification {
     }
 
     private applyPluginToProjects() {
-        ideaPlugin.apply(project)
-        ideaPlugin.apply(childProject)
+        project.apply plugin: 'idea'
+        childProject.apply plugin: 'idea'
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.groovy
new file mode 100644
index 0000000..cd3eb87
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/IdeaLanguageLevelTest.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.ide.idea.model
+
+import org.gradle.api.JavaVersion
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 7/14/11
+ */
+class IdeaLanguageLevelTest extends Specification {
+
+    def "formats language level in IDEA fancy format"() {
+        expect:
+        new IdeaLanguageLevel(JavaVersion.VERSION_1_3).level == "JDK_1_3"
+        new IdeaLanguageLevel(JavaVersion.VERSION_1_4).level == "JDK_1_4"
+        new IdeaLanguageLevel(JavaVersion.VERSION_1_5).level == "JDK_1_5"
+        new IdeaLanguageLevel(JavaVersion.VERSION_1_6).level == "JDK_1_6"
+        new IdeaLanguageLevel(JavaVersion.VERSION_1_7).level == "JDK_1_7"
+    }
+
+    def "allows arbitrary language level in IDEA's format"() {
+        expect:
+        new IdeaLanguageLevel("JDK_1_8").level == "JDK_1_8"
+    }
+
+    def "fails when invalid format passed"() {
+        when:
+        new IdeaLanguageLevel("foo bar")
+
+        then:
+        thrown(Exception)
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModulePathTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModulePathTest.groovy
deleted file mode 100644
index 74afcf4..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModulePathTest.groovy
+++ /dev/null
@@ -1,31 +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.ide.idea.model
-
-import org.gradle.util.Matchers
-import spock.lang.Specification
-
-class ModulePathTest extends Specification {
-    def pathsAreEqualWhenTheirPathAndFilePathAreEqual() {
-        Path path = new Path('url')
-        String filePath = 'path'
-
-        expect:
-        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/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
index 7add8a6..736721f 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ModuleTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.ide.idea.model
 
+import org.gradle.api.JavaVersion
 import org.gradle.api.internal.XmlTransformer
 import spock.lang.Specification
 
@@ -42,7 +43,7 @@ class ModuleTest extends Specification {
         module.load(customModuleReader)
 
         then:
-        module.javaVersion == "1.6"
+        module.jdkName == "1.6"
         module.sourceFolders == customSourceFolders
         module.testSourceFolders == customTestSourceFolders
         module.excludeFolders == customExcludeFolders
@@ -57,7 +58,7 @@ class ModuleTest extends Specification {
         def constructorExcludeFolders = [path('c')] as Set
         def constructorInheritOutputDirs = false
         def constructorOutputDir = path('someOut')
-        def constructorJavaVersion = '1.6'
+        def constructorJavaVersion = JavaVersion.VERSION_1_6.toString()
         def constructorTestOutputDir = path('someTestOut')
         def constructorModuleDependencies = [
                 customDependencies[0],
@@ -74,16 +75,25 @@ class ModuleTest extends Specification {
         module.excludeFolders == customExcludeFolders + constructorExcludeFolders
         module.outputDir == constructorOutputDir
         module.testOutputDir == constructorTestOutputDir
-        module.javaVersion == constructorJavaVersion
+        module.jdkName == constructorJavaVersion.toString()
         module.dependencies == constructorModuleDependencies
     }
 
+    def "configures default java version"() {
+        when:
+        module.configure(null, [] as Set, [] as Set, [] as Set,
+                true, null, null, [] as Set, null)
+
+        then:
+        module.jdkName == Module.INHERITED
+    }
+
     def loadDefaults() {
         when:
         module.loadDefaults()
 
         then:
-        module.javaVersion == Module.INHERITED
+        module.jdkName == Module.INHERITED
         module.inheritOutputDirs
         module.sourceFolders == [] as Set
         module.dependencies.size() == 0
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy
index 04244b8..96f30b3 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathFactoryTest.groovy
@@ -30,6 +30,7 @@ class PathFactoryTest extends Specification {
         expect:
         def path = factory.path(tmpDir.file('a', 'b'))
         path.url == 'file://$ROOT_DIR$/a/b'
+        path.relPath == '$ROOT_DIR$/a/b'
     }
 
     def createsPathForAFileNotUnderARootDir() {
@@ -40,6 +41,7 @@ class PathFactoryTest extends Specification {
         expect:
         def path = factory.path(file)
         path.url == "file://$relpath"
+        path.relPath == relpath
     }
 
     def usesTheClosestAncestorRootDirForAFileUnderMultipleRootDirs() {
@@ -49,6 +51,7 @@ class PathFactoryTest extends Specification {
         expect:
         def path = factory.path(tmpDir.file('sub', 'a'))
         path.url == 'file://$SUB_DIR$/a'
+        path.relPath == '$SUB_DIR$/a'
     }
 
     def createsPathForARootDir() {
@@ -58,9 +61,11 @@ class PathFactoryTest extends Specification {
         expect:
         def rootDir = factory.path(tmpDir.dir)
         rootDir.url == 'file://$ROOT_DIR$/'
+        rootDir.relPath == '$ROOT_DIR$/'
 
         def subDir = factory.path(tmpDir.file('sub'))
         subDir.url == 'file://$SUB_DIR$/'
+        subDir.relPath == '$SUB_DIR$/'
     }
 
     def createsPathForAJarFile() {
@@ -69,35 +74,78 @@ class PathFactoryTest extends Specification {
         expect:
         def path = factory.path(tmpDir.file('a.jar'))
         path.url == 'jar://$ROOT_DIR$/a.jar!/'
+        path.relPath == '$ROOT_DIR$/a.jar'
     }
 
-    def createsRelativePath() {
+    def createsRelativePathForADescendantOfRootDir() {
         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.relPath == '$ROOT_DIR$/a/b'
+    }
+
+    def createsRelativePathForAnAncestorOfRootDir() {
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
 
-        def parentPath = factory.relativePath('ROOT_DIR', tmpDir.dir.parentFile.parentFile.file('a/b'))
-        parentPath.url == 'file://$ROOT_DIR$/../../a/b'
+        expect:
+        def path = factory.relativePath('ROOT_DIR', tmpDir.dir.parentFile.parentFile.file('a/b'))
+        path.url == 'file://$ROOT_DIR$/../../a/b'
+        path.relPath == '$ROOT_DIR$/../../a/b'
     }
-    
+
+    def createsRelativePathForASiblingOfRootDir() {
+        factory.addPathVariable('ROOT_DIR', tmpDir.dir)
+
+        expect:
+        def path = factory.relativePath('ROOT_DIR', tmpDir.dir.parentFile.file('a'))
+        path.url == 'file://$ROOT_DIR$/../a'
+        path.relPath == '$ROOT_DIR$/../a'
+    }
+
+    def createsRelativePathForAFileOnAnotherFileSystem() {
+        def fileSystemRoots = findFileSystemRoots()
+        if (fileSystemRoots.size() == 1) {
+            return
+        }
+        def rootDir = new File(fileSystemRoots[0], 'root')
+        def file = new File(fileSystemRoots[1], 'file')
+        def relpath = relpath(file)
+        factory.addPathVariable('ROOT_DIR', rootDir)
+
+        expect:
+        def path = factory.relativePath('ROOT_DIR', file)
+        path.url == "file://${relpath}"
+        path.relPath == relpath
+    }
+
     def createsPathForAFileUrl() {
         expect:
         def path = factory.path('file://a/b/c')
         path.url == 'file://a/b/c'
+        path.relPath == null
     }
 
     def createsPathForAJarUrl() {
         expect:
         def path = factory.path('jar://a/b/c.jar!/some/entry')
         path.url == 'jar://a/b/c.jar!/some/entry'
+        path.relPath == null
     }
 
     def createsPathForAUrlWithUnknownScheme() {
         expect:
         def path = factory.path('other:abc')
         path.url == 'other:abc'
+        path.relPath == null
+    }
+
+    def createsPathForAUrlAndRelPath() {
+        expect:
+        def path = factory.path('file://a/b/c', '/path')
+        path.url == 'file://a/b/c'
+        path.relPath == '/path'
     }
 
     def createsPathForAUrlWithPathVariables() {
@@ -106,6 +154,7 @@ class PathFactoryTest extends Specification {
         expect:
         def path = factory.path('file://$ROOT_DIR$/c')
         path.url == 'file://$ROOT_DIR$/c'
+        path.relPath == null
     }
 
     def filePathsAreEqualWhenTheyPointToTheSameFile() {
@@ -167,4 +216,16 @@ class PathFactoryTest extends Specification {
         return file.absolutePath.replace(File.separator, '/')
     }
 
+    def findFileSystemRoots() {
+        File.listRoots().inject([]) {List result, File root ->
+            try {
+                new File(root, 'file').canonicalFile
+                result << root
+            } catch (IOException e) {
+                // skip
+            }
+            return result
+        }
+    }
+
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathTest.groovy
index 03ce245..b387698 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/PathTest.groovy
@@ -16,98 +16,17 @@
 package org.gradle.plugins.ide.idea.model
 
 import org.gradle.util.Matchers
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
 import spock.lang.Specification
 
 class PathTest extends Specification {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
-
-    def generatesUrlAndPathForFileInRootDir() {
-        expect:
-        def path = new Path(tmpDir.dir, '$ROOT_DIR$', tmpDir.file('a', 'b'))
-        path.url == 'file://$ROOT_DIR$/a/b'
-        path.relPath == '$ROOT_DIR$/a/b'
-    }
-
-    def generatesUrlAndPathForRootDir() {
-        expect:
-        def path = new Path(tmpDir.dir, '$ROOT_DIR$', tmpDir.dir)
-        path.url == 'file://$ROOT_DIR$/'
-        path.relPath == '$ROOT_DIR$/'
-    }
-
-    def generatesUrlAndPathForAncestorOfRootDir() {
-        expect:
-        def path = new Path(tmpDir.dir, '$ROOT_DIR$', tmpDir.dir.parentFile.parentFile)
-        path.url == 'file://$ROOT_DIR$/../../'
-        path.relPath == '$ROOT_DIR$/../../'
-    }
-
-    def generatesUrlAndPathForSiblingOfRootDir() {
-        expect:
-        def path = new Path(tmpDir.dir, '$ROOT_DIR$', tmpDir.dir.parentFile.file('a'))
-        path.url == 'file://$ROOT_DIR$/../a'
-        path.relPath == '$ROOT_DIR$/../a'
-    }
-
-    def generatesUrlAndPathForJarFileInRootDir() {
-        expect:
-        def path = new Path(tmpDir.dir, '$ROOT_DIR$', tmpDir.file('a', 'b.jar'))
-        path.url == 'jar://$ROOT_DIR$/a/b.jar!/'
-        path.relPath == '$ROOT_DIR$/a/b.jar'
-    }
-
-    def generatesUrlAndPathForFileWithNoRootDir() {
-        def file = tmpDir.file('a')
-        def relpath = file.absolutePath.replace(File.separator, '/')
-
-        expect:
-        def path = new Path(file)
-        path.url == "file://${relpath}"
-        path.relPath == relpath
-    }
-
-    def generatesUrlAndPathForFileOnDifferentFilesystemToRootDir() {
-        def fileSystemRoots = findFileSystemRoots()
-        if (fileSystemRoots.size() == 1) {
-            return
-        }
-        def rootDir = new File(fileSystemRoots[0], 'root')
-        def file = new File(fileSystemRoots[1], 'file')
-        def relpath = file.absolutePath.replace(File.separator, '/')
-
-        expect:
-        def path = new Path(rootDir, '$ROOT_DIR$', file)
-        path.url == "file://${relpath}"
-        path.relPath == relpath
-    }
-
-    def generatesUrlAndPathForJarFileWithNoRootDir() {
-        def file = tmpDir.file('a.jar')
-        def relpath = file.absolutePath.replace(File.separator, '/')
-
-        expect:
-        def path = new Path(file)
-        path.url == "jar://${relpath}!/"
-        path.relPath == relpath
-    }
-
-    def pathsAreEqualWhenTheyHaveTheSameCanonicalUrl() {
+    def "paths are equal when they have the same canonical url"() {
         expect:
         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')
     }
 
-    def findFileSystemRoots() {
-        File.listRoots().inject([]) {List result, File root ->
-            try {
-                new File(root, 'file').canonicalFile
-                result << root
-            } catch (IOException e) {
-                // skip
-            }
-            return result
-        }
+    def "equals honors subclasses"() {
+        expect:
+        new Path('file://$ROOT_DIR$/file') == new FilePath(null, null, 'file://$ROOT_DIR$/file', null)
     }
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
index 6d1bebe..7268e26 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.ide.idea.model
 
+import org.gradle.api.JavaVersion
 import org.gradle.api.internal.XmlTransformer
 import spock.lang.Specification
 
@@ -23,7 +24,7 @@ import spock.lang.Specification
  */
 class ProjectTest extends Specification {
     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 customModules = [path('file://$PROJECT_DIR$/gradle-idea-plugin.iml')]
     final customWildcards = ["?*.gradle", "?*.grails"] as Set
     Project project = new Project(new XmlTransformer(), pathFactory)
 
@@ -32,22 +33,22 @@ class ProjectTest extends Specification {
         project.load(customProjectReader)
 
         then:
-        project.modulePaths == customModules
+        project.modulePaths as Set == customModules as Set
         project.wildcards == customWildcards
         project.jdk == new Jdk(true, false, null, "1.4")
     }
 
     def customJdkAndWildcards_shouldBeMerged() {
-        def modules = [new ModulePath(path('file://$PROJECT_DIR$/other.iml'), '$PROJECT_DIR$/other.iml')] as Set
+        def modules = [path('file://$PROJECT_DIR$/other.iml')]
 
         when:
         project.load(customProjectReader)
-        project.configure(modules, '1.6', ['?*.groovy'] as Set)
+        project.configure(modules, "1.6", new IdeaLanguageLevel("JDK_1_5"), ['?*.groovy'])
 
         then:
-        project.modulePaths == customModules + modules
+        project.modulePaths as Set == (customModules + modules) as Set
         project.wildcards == customWildcards + ['?*.groovy'] as Set
-        project.jdk == new Jdk("1.6")
+        project.jdk == new Jdk("1.6", new IdeaLanguageLevel(JavaVersion.VERSION_1_5))
     }
 
     def loadDefaults() {
@@ -63,7 +64,7 @@ class ProjectTest extends Specification {
     def toXml_shouldContainCustomValues() {
         when:
         project.loadDefaults()
-        project.configure([] as Set, '1.5', ['?*.groovy'] as Set)
+        project.configure([], "1.6", new IdeaLanguageLevel("JDK_1_5"), ['?*.groovy'])
         def xml = toXmlReader
         def other = new Project(new XmlTransformer(), pathFactory)
         other.load(xml)
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy
index d2c572f..5f6035d 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/internal/ModuleDependencyBuilderTest.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.plugins.ide.idea.model.internal
 
+import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 /**
@@ -23,28 +24,28 @@ import spock.lang.Specification
  */
 class ModuleDependencyBuilderTest extends Specification {
 
+    def project = HelperUtil.createRootProject()
     def builder = new ModuleDependencyBuilder()
 
-    static class ProjectStub {
-        String name
-        IdeaModuleStub ideaModule
-    }
-
-    static class IdeaModuleStub { String moduleName }
-
-    def "builds dependency for project"() {
+    def "builds dependency for nonIdea project"() {
         when:
-        def dependency = builder.create(new ProjectStub(ideaModule: new IdeaModuleStub(moduleName: 'services')), 'compile')
+        def dependency = builder.create(project, 'compile')
+
         then:
         dependency.scope == 'compile'
-        dependency.name == 'services'
+        dependency.name == project.name
     }
 
-    def "builds dependency for nonIdea project"() {
+    def "builds dependency for project"() {
+        given:
+        project.apply(plugin: 'idea')
+        project.idea.module.name = 'foo'
+
         when:
-        def dependency = builder.create(new ProjectStub(name: 'api'), 'compile')
+        def dependency = builder.create(project, 'compile')
+
         then:
         dependency.scope == 'compile'
-        dependency.name == 'api'
+        dependency.name == 'foo'
     }
 }
\ No newline at end of file
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
index 941d50a..9667e33 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/GeneratorTaskTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.plugins.ide.internal
 
-import org.gradle.api.Action
 import org.gradle.plugins.ide.api.GeneratorTask
 import org.gradle.plugins.ide.internal.generator.generator.Generator
 import org.gradle.util.HelperUtil
@@ -77,33 +76,6 @@ class GeneratorTaskTest extends Specification {
         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 {
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractorTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractorTest.groovy
new file mode 100644
index 0000000..681a848
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractorTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.ide.internal
+
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.LenientConfiguration
+import org.gradle.api.artifacts.ResolvedConfiguration
+import spock.lang.Specification
+
+class IdeDependenciesExtractorTest extends Specification {
+    final IdeDependenciesExtractor extractor = new IdeDependenciesExtractor()
+    final Configuration configuration = Mock()
+    final ResolvedConfiguration resolvedConfiguration = Mock()
+    final LenientConfiguration lenientConfiguration = Mock()
+
+    def "returns dependency entries in the order they were resolved in"() {
+        given:
+        def actualDependencies = [module('a'), module('b'), module('c'), module('d'), module('z')] as LinkedHashSet
+        configuration.resolvedConfiguration >> resolvedConfiguration
+        resolvedConfiguration.lenientConfiguration >> lenientConfiguration
+        lenientConfiguration.getFiles(_) >> actualDependencies
+        lenientConfiguration.unresolvedModuleDependencies >> []
+
+        when:
+        def dependencies = extractor.extractRepoFileDependencies(Mock(ConfigurationContainer), [configuration], [], false, false)
+
+        then:
+        assert dependencies.collect {it.file.name} == ['a', 'b', 'c', 'd', 'z']
+    }
+
+    def module(String name) {
+        return new File(name);
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy
index 8a03430..c3f99d9 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/generator/PropertiesPersistableConfigurationObjectTest.groovy
@@ -15,15 +15,17 @@
  */
 package org.gradle.plugins.ide.internal.generator
 
+import org.gradle.api.internal.PropertiesTransformer
 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 org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject object = new org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject() {
+    final org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject object = new org.gradle.plugins.ide.internal.generator.PropertiesPersistableConfigurationObject(new PropertiesTransformer()) {
         @Override protected String getDefaultResourceName() {
             return 'defaultResource.properties'
         }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.groovy
index 77924c1..eae6dbe 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/TasksFactoryTest.groovy
@@ -16,39 +16,37 @@
 package org.gradle.tooling.internal.provider
 
 import org.gradle.api.Project
-import org.gradle.api.Task
-import org.gradle.api.tasks.TaskContainer
+import org.gradle.api.internal.AbstractTask
+import org.gradle.tooling.internal.eclipse.DefaultEclipseProject
+import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 class TasksFactoryTest extends Specification {
     final Project project = Mock()
-    final org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3 eclipseProject = Mock()
-    final TaskContainer tasks = Mock()
-    final TasksFactory factory = new TasksFactory()
+    final org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3 eclipseProject = new DefaultEclipseProject(null, null, null, null, [])
+    final task = HelperUtil.createTask(AbstractTask)
 
-    def "builds the tasks for a project"() {
-        def taskA = task('a')
-        def taskB = task('b')
+    def "does not return tasks"() {
+        TasksFactory factory = new TasksFactory(false)
 
         when:
-        def result = factory.create(project, eclipseProject)
+        factory.allTasks = [:]
+        factory.allTasks.put(project, [task] as Set)
+        def tasks = factory.getTasks(project)
 
         then:
-        result.size() == 2
-        result[0].path == ':a'
-        result[0].name == 'a'
-        result[0].description == 'task a'
-        result[0].project == eclipseProject
-        result[1].name == 'b'
-        1 * project.tasks >> tasks
-        tasks.iterator() >> [taskA, taskB].iterator()
+        tasks.empty
     }
 
-    def task(String name) {
-        Task task = Mock()
-        _ * task.path >> ":$name"
-        _ * task.name >> name
-        _ * task.description >> "task $name"
-        return task
+    def "returns tasks"() {
+        TasksFactory factory = new TasksFactory(true)
+
+        when:
+        factory.allTasks = [:]
+        factory.allTasks.put(project, [task] as Set)
+        def tasks = factory.getTasks(project)
+
+        then:
+        tasks.size() == 1
     }
 }
diff --git a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/EclipseProjectDependenciesFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/EclipseProjectDependenciesFactoryTest.groovy
deleted file mode 100644
index 78562a1..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/EclipseProjectDependenciesFactoryTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider.dependencies
-
-import org.gradle.plugins.ide.eclipse.model.ProjectDependency
-import org.gradle.plugins.ide.eclipse.model.SourceFolder
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3
-import spock.lang.Specification
-
-/**
- * @author Szczepan Faber, @date: 20.03.11
- */
-class EclipseProjectDependenciesFactoryTest extends Specification {
-
-    def factory = new EclipseProjectDependenciesFactory()
-
-    def "creates instances"() {
-        given:
-        def projectAInstance = Mock(EclipseProjectVersion3)
-        def projectMapping = [':projectA' : projectAInstance]
-        def entries = [
-                new SourceFolder('foo', '', [] as Set, '', [], []),
-                new ProjectDependency('/projectA', true, '', [] as Set, ':projectA'),
-                new ProjectDependency('/projectB', true, '', [] as Set, ':projectB') ]
-
-        when:
-        def deps = factory.create(projectMapping, entries)
-
-        then:
-        deps.size() == 2
-        deps[0].path == 'projectA'
-        deps[0].targetProject == projectAInstance
-        deps[1].path == 'projectB'
-        deps[1].targetProject == null
-    }
-}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/ExternalDependenciesFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/ExternalDependenciesFactoryTest.groovy
deleted file mode 100644
index 1beb3dc..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/ExternalDependenciesFactoryTest.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider.dependencies
-
-import org.gradle.api.Project
-import org.gradle.plugins.ide.eclipse.model.Library
-import org.gradle.plugins.ide.eclipse.model.SourceFolder
-import spock.lang.Specification
-
-/**
- * @author Szczepan Faber, @date: 20.03.11
- */
-class ExternalDependenciesFactoryTest extends Specification {
-
-    def factory = new ExternalDependenciesFactory()
-
-    def "creates instances"() {
-        given:
-        def project = Mock(Project)
-        def somePathDir = new File('/projects/someLibrary')
-        project.file('someLibrary') >> { somePathDir }
-        def entries = [
-                new SourceFolder('foo', '', [] as Set, '', [], []),
-                new Library('someLibrary', true, '', [] as Set, '', '') ]
-
-        when:
-        def deps = factory.create(project, entries)
-
-        then:
-        deps.size() == 1
-        deps[0].file == somePathDir
-    }
-}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/SourceDirectoriesFactoryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/SourceDirectoriesFactoryTest.groovy
deleted file mode 100644
index 66dd9da..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/tooling/internal/provider/dependencies/SourceDirectoriesFactoryTest.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.provider.dependencies
-
-import org.gradle.api.Project
-import org.gradle.plugins.ide.eclipse.model.Container
-import org.gradle.plugins.ide.eclipse.model.SourceFolder
-import spock.lang.Specification
-
-/**
- * @author Szczepan Faber, @date: 20.03.11
- */
-class SourceDirectoriesFactoryTest extends Specification {
-
-    def factory = new SourceDirectoriesFactory()
-
-    def "creates instances"() {
-        given:
-        def project = Mock(Project)
-        def somePathDir = new File('/projects/somePath')
-        project.file('somePath') >> { somePathDir }
-        def entries = [
-                new SourceFolder('somePath', '', [] as Set, '', [], []),
-                new Container('foo', true, '', [] as Set) ]
-
-        when:
-        def dirs = factory.create(project, entries)
-
-        then:
-        dirs.size() == 1
-        dirs[0].path == 'somePath'
-        dirs[0].directory == somePathDir
-    }
-}
diff --git a/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml b/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml
index 899cd0e..b5d8ac9 100644
--- a/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml
+++ b/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml
@@ -3,4 +3,4 @@
   <fixed facet="jst.web"/>
   <installed facet="jst.web" version="2.4"/>
   <installed facet="jst.java" version="1.4"/>
-</faceted-project>
\ No newline at end of file
+</faceted-project>
diff --git a/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customProject.xml b/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customProject.xml
index 70ae074..7292145 100644
--- a/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customProject.xml
+++ b/subprojects/ide/src/test/resources/org/gradle/plugins/ide/eclipse/model/customProject.xml
@@ -26,4 +26,4 @@
             <location>somelocation</location>
         </link>
     </linkedResources>
-</projectDescription>
\ No newline at end of file
+</projectDescription>
diff --git a/subprojects/integ-test/integ-test.gradle b/subprojects/integ-test/integ-test.gradle
index d8136bc..b478b75 100644
--- a/subprojects/integ-test/integ-test.gradle
+++ b/subprojects/integ-test/integ-test.gradle
@@ -1,17 +1,14 @@
-apply plugin: 'groovy'
-apply from: "$rootDir/gradle/integTest.gradle"
+apply from: "$rootDir/gradle/classycle.gradle"
 
 dependencies {
-    groovy libraries.groovy_depends
-
-    // TODO - don't include the runtime at compile time. This is here because the Groovy compiler needs the runtime classpath
-//    integTestCompile project(path: ':core', configuration: 'integTestFixtures')
-//    integTestRuntime project(path: ':core', configuration: 'integTestFixturesRuntime')
-    integTestCompile project(path: ':core', configuration: 'integTestFixturesRuntime')
+    groovy libraries.groovy
 
     integTestCompile project(':toolingApi')
+    integTestCompile project(':launcher')
+    integTestCompile project(':coreImpl')
     integTestCompile libraries.ant
     integTestCompile libraries.xmlunit
+    integTestCompile libraries.nekohtml
 
     integTestRuntime rootProject.configurations.testRuntime.allDependencies
 }
@@ -27,10 +24,19 @@ integTestTasks.all {
         systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
         systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
         forkEvery = 15
-        maxParallelForks = guessMaxForks()
 
         if (isDevBuild()) {
             exclude 'org/gradle/integtests/DistributionIntegrationTest.*'
         }
     }
+
+
+    // You can filter the userguide samples to be run by specifying this system property.
+    // E.g. ./gradlew integTest:integTest -D:integTest:integTest.single=UserGuideSamplesIntegrationTest -Dorg.gradle.userguide.samples.filter=signing/.+
+    systemProperty "org.gradle.userguide.samples.filter", System.getProperty("org.gradle.userguide.samples.filter")
+}
+
+daemonIntegTest {
+    exclude "**/CrossVersionCompatibilityIntegrationTest.class" //ignored just in case to avoid old daemon implementation
+    exclude "**/DistributionIntegrationTest.class" //fragile - heavily depends on external repos and does not contribute too much to the daemon suite anyway
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
new file mode 100644
index 0000000..7417783
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.diagnostics
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class DependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        distribution.requireOwnUserHomeDir()
+    }
+
+    def "circular dependencies"() {
+        given:
+        file("settings.gradle") << "include 'client', 'a', 'b', 'c'"
+
+        [a: "b", b: "c", c: "a"].each { module, dep ->
+            def upped = module.toUpperCase()
+            file(module, "build.gradle") << """
+                apply plugin: 'java'
+                group = "group"
+                version = 1.0
+
+                dependencies {
+                    compile project(":$dep")
+                }
+            """
+            file(module, "src", "main", "java", "${upped}.java") << "public class $upped {}"
+        }
+
+        and:
+        file("client", "build.gradle") << """
+            apply plugin: 'java'
+            
+            dependencies {
+                compile project(":a")
+            }
+        """
+        
+        when:
+        run ":client:dependencies"
+        
+        then:
+        output.contains "(*) - dependencies omitted (listed previously)"
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleBuildRunner.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleBuildRunner.groovy
new file mode 100644
index 0000000..fc64f1a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleBuildRunner.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.debug
+
+import org.gradle.launcher.Main
+
+/**
+ * Use this class to run/debug any gradle build from the IDE. Especially useful for learning & debugging Gradle internals.
+ *
+ * @author: Szczepan Faber, created at: 4/30/11
+ */
+class GradleBuildRunner {
+    public static void main(String[] args) {
+        //simplest example:
+        Main.main("-v")
+
+        //To run/debug any build from the IDE you can tweak the parameters:
+        //org.gradle.launcher.Main.main("-p", "/path/to/project/you/want/to/build", "someTask", "someOtherTask");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleRunConfiguration.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleRunConfiguration.groovy
new file mode 100644
index 0000000..02ab189
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/debug/GradleRunConfiguration.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.debug
+
+import org.gradle.launcher.Main
+
+/**
+ * Used by IDEA run config created as a part of 'gradle idea' run.
+ * Required because dependency to 'lanucher' project has a scope 'test'.
+ *
+ * @author: Szczepan Faber, created at: 4/30/11
+ */
+class GradleRunConfiguration {
+    public static void main(String[] args) {
+        Main.main(args)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
index 87d44a7..d88b99d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
@@ -19,7 +19,7 @@ import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.util.TestFile
 import org.junit.Test
 import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 public class AntProjectIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntlrIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntlrIntegrationTest.java
deleted file mode 100644
index 42227b4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntlrIntegrationTest.java
+++ /dev/null
@@ -1,27 +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.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
-import org.junit.Test;
-
-public class AntlrIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void handlesEmptyProject() {
-        testFile("build.gradle").writelns("apply plugin: 'antlr'");
-        inTestDirectory().withTasks("build").run();
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntlrPluginGoodBehaviourTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntlrPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..900bef1
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/AntlrPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.WellBehavedPluginTest
+
+class AntlrPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "build"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy
new file mode 100644
index 0000000..ac87fb6
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationSpec.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2011 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.AbstractIntegrationSpec
+
+class ApplicationIntegrationSpec extends AbstractIntegrationSpec {
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'application'
+            applicationName = 'app'
+            mainClassName = "something"
+        """
+    }
+
+    def "conventional resources are including in dist"() {
+        when:
+        file("src/dist/dir").with {
+            file("r1.txt") << "r1"
+            file("r2.txt") << "r2"
+        }
+
+        then:
+        succeeds "installApp"
+
+        and:
+        def distBase = file("build/install/app")
+        distBase.file("dir").directory
+        distBase.file("dir/r1.txt").text == "r1"
+        distBase.file("dir/r2.txt").text == "r2"
+    }
+
+    def "configure the distribution spec to source from a different dir"() {
+        when:
+        file("src/somewhere-else/dir").with {
+            file("r1.txt") << "r1"
+            file("r2.txt") << "r2"
+        }
+
+        and:
+        buildFile << """
+            applicationDistribution.from("src/somewhere-else") {
+                include "**/r2.*"
+            }
+        """
+
+        then:
+        succeeds "installApp"
+
+        and:
+        def distBase = file("build/install/app")
+        distBase.file("dir").directory
+        !distBase.file("dir/r1.txt").exists()
+        distBase.file("dir/r2.txt").text == "r2"
+    }
+
+    def "distribution file producing tasks are run automatically"() {
+        when:
+        buildFile << """
+            task createDocs {
+                def docs = file("\$buildDir/docs")
+
+                outputs.dir docs
+
+                doLast {
+                    assert docs.mkdirs()
+                    new File(docs, "readme.txt") << "Read me!!!"
+                }
+            }
+
+            applicationDistribution.from(createDocs) {
+                into "docs"
+                rename 'readme(.*)', 'READ-ME\$1'
+            }
+        """
+
+        then:
+        succeeds "installApp"
+
+        and:
+        ":createDocs" in nonSkippedTasks
+
+        and:
+        def distBase = file("build/install/app")
+        distBase.file("docs").directory
+        distBase.file("docs/readme.txt").exists() == false
+        distBase.file("docs/READ-ME.txt").text == "Read me!!!"
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
index 94cc313..12978db 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ApplicationIntegrationTest.groovy
@@ -15,32 +15,33 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.GradleExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.ScriptExecuter
+import org.gradle.internal.os.OperatingSystem
 import org.gradle.util.TestFile
-import org.junit.Rule
-import spock.lang.Specification
+import org.gradle.util.TextUtil
+import static org.hamcrest.Matchers.startsWith
 
-import static org.hamcrest.Matchers.*
+class ApplicationIntegrationTest extends AbstractIntegrationSpec{
 
-class ApplicationIntegrationTest extends Specification {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleExecuter executer = new GradleDistributionExecuter()
-
-    def canUseEnvironmentVariableToPassOptionsToJvmWhenRunningScript() {
-        distribution.testFile('build.gradle') << '''
+    def canUseEnvironmentVariableToPassMultipleOptionsToJvmWhenRunningScript() {
+        file("build.gradle") << '''
 apply plugin: 'application'
 mainClassName = 'org.gradle.test.Main'
 applicationName = 'application'
 '''
-        distribution.testFile('src/main/java/org/gradle/test/Main.java') << '''
+        file('src/main/java/org/gradle/test/Main.java') << '''
 package org.gradle.test;
 
 class Main {
     public static void main(String[] args) {
-        if (System.getProperty("testValue") == null) {
+        if (!"value".equals(System.getProperty("testValue"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+        if (!"some value".equals(System.getProperty("testValue2"))) {
+            throw new RuntimeException("Expected system property not specified");
+        }
+        if (!"some value".equals(System.getProperty("testValue3"))) {
             throw new RuntimeException("Expected system property not specified");
         }
     }
@@ -48,12 +49,16 @@ class Main {
 '''
 
         when:
-        executer.withTasks('install').run()
+        run 'install'
 
         def builder = new ScriptExecuter()
         builder.workingDir distribution.testDir.file('build/install/application/bin')
         builder.executable "application"
-        builder.environment('APPLICATION_OPTS', '-DtestValue=value')
+        if (OperatingSystem.current().windows) {
+            builder.environment('APPLICATION_OPTS', '-DtestValue=value -DtestValue2="some value" -DtestValue3="some value"')
+        } else {
+            builder.environment('APPLICATION_OPTS', '-DtestValue=value -DtestValue2=\'some value\' -DtestValue3=some\\ value')
+        }
 
         def result = builder.run()
 
@@ -62,13 +67,13 @@ class Main {
     }
 
     def "can customize application name"() {
-        distribution.testFile('settings.gradle') << 'rootProject.name = "application"'
-        distribution.testFile('build.gradle') << '''
+        file('settings.gradle') << 'rootProject.name = "application"'
+        file('build.gradle') << '''
 apply plugin: 'application'
 mainClassName = 'org.gradle.test.Main'
 applicationName = 'mega-app'
 '''
-        distribution.testFile('src/main/java/org/gradle/test/Main.java') << '''
+        file('src/main/java/org/gradle/test/Main.java') << '''
 package org.gradle.test;
 
 class Main {
@@ -78,10 +83,10 @@ class Main {
 '''
 
         when:
-        executer.withTasks('install', 'distZip').run()
+        run 'install', 'distZip'
 
         then:
-        def installDir = distribution.testFile('build/install/mega-app')
+        def installDir = file('build/install/mega-app')
         installDir.assertIsDir()
         checkApplicationImage(installDir)
 
@@ -94,19 +99,43 @@ class Main {
     }
 
     def "installApp complains if install directory exists and doesn't look like previous install"() {
-        distribution.testFile('build.gradle') << '''
+        file('build.gradle') << '''
 apply plugin: 'application'
 mainClassName = 'org.gradle.test.Main'
 installApp.destinationDir = buildDir
 '''
 
         when:
-        def result = executer.withTasks('installApp').runWithFailure()
+        runAndFail 'installApp'
 
         then:
         result.assertThatCause(startsWith("The specified installation directory '${distribution.testFile('build')}' is neither empty nor does it contain an installation"))
     }
 
+    def "startScripts respect OS dependent line separators"() {
+        file('build.gradle') << '''
+    apply plugin: 'application'
+    applicationName = 'mega-app'
+    mainClassName = 'org.gradle.test.Main'
+    installApp.destinationDir = buildDir
+    '''
+
+        when:
+        run 'startScripts'
+
+        then:
+        File generatedWindowsStartScript = file("build/scripts/mega-app.bat")
+        generatedWindowsStartScript.exists()
+        assertLineSeparators(generatedWindowsStartScript, TextUtil.windowsLineSeparator, 90);
+
+        File generatedLinuxStartScript = file("build/scripts/mega-app")
+        generatedLinuxStartScript.exists()
+        assertLineSeparators(generatedLinuxStartScript, TextUtil.unixLineSeparator, 164);
+        assertLineSeparators(generatedLinuxStartScript, TextUtil.windowsLineSeparator, 1)
+
+        distribution.testFile("build/scripts/mega-app").exists()
+    }
+
     private void checkApplicationImage(TestFile installDir) {
         installDir.file('bin/mega-app').assertIsFile()
         installDir.file('bin/mega-app.bat').assertIsFile()
@@ -121,4 +150,9 @@ installApp.destinationDir = buildDir
         def result = builder.run()
         result.assertNormalExitValue()
     }
+
+    def assertLineSeparators(TestFile testFile, String lineSeparator, expectedLineCount) {
+        assert testFile.text.split(lineSeparator).length == expectedLineCount
+        true
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
index b9a2202..49cc345 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
@@ -17,11 +17,14 @@
 
 package org.gradle.integtests
 
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.util.TestFile
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
 import org.junit.Test
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
 
 public class ArchiveIntegrationTest extends AbstractIntegrationTest {
     @Test public void canCopyFromAZip() {
@@ -72,6 +75,140 @@ public class ArchiveIntegrationTest extends AbstractIntegrationTest {
         testFile('dest').assertHasDescendants('subdir1/file1.txt', 'subdir2/file2.txt')
     }
 
+    @Test public void "handles gzip compressed tars"() {
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        tar.tgzTo(file('test.tgz'))
+
+        file('build.gradle') << '''
+            task copy(type: Copy) {
+                from tarTree('test.tgz')
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+
+        inTestDirectory().withTasks('copy').run()
+
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    @Test public void "allows user to provide a custom resource for the tarTree"() {
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+            }
+        }
+        tar.tarTo(file('test.tar'))
+
+        file('build.gradle') << '''
+            def res = new ReadableResource() {
+                InputStream read() { new FileInputStream(file('test.tar')) }
+                String getBaseName() { "foo" }
+                URI getURI() { new java.net.URI("foo") }
+                String getDisplayName() { "The foo" }
+            }
+
+            task copy(type: Copy) {
+                from tarTree(res)
+                into 'dest'
+            }
+'''
+
+        inTestDirectory().withTasks('copy').run()
+
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    @Test public void "handles bzip2 compressed tars"() {
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        tar.tbzTo(file('test.tbz2'))
+
+        file('build.gradle') << '''
+            task copy(type: Copy) {
+                from tarTree('test.tbz2')
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+
+        inTestDirectory().withTasks('copy').run()
+
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+     @Test public void "knows compression of the tar"() {
+        TestFile tar = file()
+        tar.tbzTo(file('test.tbz2'))
+
+        file('build.gradle') << '''
+            task myTar(type: Tar) {
+                assert compression == Compression.NONE
+
+                compression = Compression.GZIP
+                assert compression == Compression.GZIP
+
+                compression = Compression.BZIP2
+                assert compression == Compression.BZIP2
+            }
+'''
+
+        inTestDirectory().withTasks('myTar').run()
+    }
+
+    @Test public void "can choose compression method for tarTree"() {
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        //file extension is non-standard:
+        tar.tbzTo(file('test.ext'))
+
+        file('build.gradle') << '''
+            task copy(type: Copy) {
+                from tarTree(resources.bzip2('test.ext'))
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+
+        inTestDirectory().withTasks('copy').run()
+
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test public void "tarTreeFailsGracefully"() {
+        file('build.gradle') << '''
+            task copy(type: Copy) {
+                //the input file comes from the resources to make sure it is truly improper 'tar', see GRADLE-1952
+                from tarTree('compressedTarWithWrongExtension.tar')
+                into 'dest'
+            }
+'''
+
+        def failure = inTestDirectory().withTasks('copy').runWithFailure()
+
+        assert failure.error.contains("Unable to expand TAR")
+        assert failure.error.contains("compression based on the file extension")
+    }
+
     @Test public void cannotCreateAnEmptyZip() {
         testFile('build.gradle') << '''
             task zip(type: Zip) {
@@ -161,16 +298,16 @@ public class ArchiveIntegrationTest extends AbstractIntegrationTest {
 
         expandDir.file('prefix/dir1/renamed_file1.txt').assertContents(equalTo('[abc]'))
 
-        expandDir.file('prefix/dir1').assertPermissions(equalTo("rwxr-xr-x"))
-        expandDir.file('prefix/dir1/renamed_file1.txt').assertPermissions(equalTo("rw-r--r--"))
-        expandDir.file('scripts/dir2').assertPermissions(equalTo("rwxr-x---"))
-        expandDir.file('scripts/dir2/script.sh').assertPermissions(equalTo("rwxr-xr--"))
+        if (TestPrecondition.FILE_PERMISSIONS.fulfilled) {
+            expandDir.file('scripts/dir2').assertPermissions(equalTo("rwxr-x---"))
+            expandDir.file('scripts/dir2/script.sh').assertPermissions(equalTo("rwxr-xr--"))
+        }
     }
 
     @Test public void canCreateATarArchive() {
         createDir('test') {
             dir1 {
-                file('file1.txt') << 'abc'
+                file('file1.txt').write 'abc'
             }
             file 'file1.txt'
             dir2 {
@@ -204,10 +341,10 @@ public class ArchiveIntegrationTest extends AbstractIntegrationTest {
 
         expandDir.file('dir1/file1.txt').assertContents(equalTo('[abc]'))
 
-        expandDir.file('dir1').assertPermissions(equalTo("rwxr-xr-x"))
-        expandDir.file('dir1/file1.txt').assertPermissions(equalTo("rw-r--r--"))
-        expandDir.file('scripts/dir2').assertPermissions(equalTo("rwxr-x---"))
-        expandDir.file('scripts/dir2/script.sh').assertPermissions(equalTo("rwxr-xr--"))
+        if (TestPrecondition.FILE_PERMISSIONS.fulfilled) {
+            expandDir.file('scripts/dir2').assertPermissions(equalTo("rwxr-x---"))
+            expandDir.file('scripts/dir2/script.sh').assertPermissions(equalTo("rwxr-xr--"))
+        }
     }
 
     @Test public void canCreateATgzArchive() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArtifactDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArtifactDependenciesIntegrationTest.groovy
deleted file mode 100644
index a45b682..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArtifactDependenciesIntegrationTest.groovy
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright 2011 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.internal.AbstractIntegrationTest
-import org.gradle.util.TestFile
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.*
-import static org.hamcrest.Matchers.containsString
-import static org.hamcrest.Matchers.startsWith
-
-class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
-    @Rule
-    public final TestResources testResources = new TestResources()
-    @Rule
-    public final HttpServer server = new HttpServer()
-
-    @Before
-    public void setup() {
-        distribution.requireOwnUserHomeDir()
-    }
-
-    @Test
-    public void canResolveDependenciesFromAFlatDir() {
-        File buildFile = testFile("projectWithFlatDir.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void canHaveConfigurationHierarchy() {
-        File buildFile = testFile("projectWithConfigurationHierarchy.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void dependencyReportWithConflicts() {
-        File buildFile = testFile("projectWithConflicts.gradle");
-        usingBuildFile(buildFile).run();
-        usingBuildFile(buildFile).withDependencyList().run();
-    }
-
-    @Test
-    public void canNestModules() throws IOException {
-        File buildFile = testFile("projectWithNestedModules.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void canHaveCycleInDependencyGraph() throws IOException {
-        File buildFile = testFile("projectWithCyclesInDependencyGraph.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void canUseDynamicVersions() throws IOException {
-        File buildFile = testFile("projectWithDynamicVersions.gradle");
-        usingBuildFile(buildFile).run();
-    }
-
-    @Test
-    public void reportsUnknownDependencyError() {
-        File buildFile = testFile("projectWithUnknownDependency.gradle");
-        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
-        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
-        failure.assertHasDescription("Execution failed for task ':listJars'");
-        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
-        failure.assertThatCause(containsString("unresolved dependency: test#unknownProjectA;1.2: not found"));
-        failure.assertThatCause(containsString("unresolved dependency: test#unknownProjectB;2.1.5: not found"));
-    }
-
-    @Test
-    public void reportsProjectDependsOnSelfError() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile << '''
-            configurations { compile }
-            dependencies { compile project(':') }
-            defaultTasks 'listJars'
-            task listJars << { configurations.compile.each { println it } }
-'''
-        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
-        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
-        failure.assertHasDescription("Execution failed for task ':listJars'");
-        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
-        failure.assertThatCause(containsString("a module is not authorized to depend on itself"));
-    }
-
-    @Test
-    public void canSpecifyProducerTasksForFileDependency() {
-        testFile("settings.gradle").write("include 'sub'");
-        testFile("build.gradle") << '''
-            configurations { compile }
-            dependencies { compile project(path: ':sub', configuration: 'compile') }
-            task test(dependsOn: configurations.compile) << {
-                assert file('sub/sub.jar').isFile()
-            }
-'''
-        testFile("sub/build.gradle") << '''
-            configurations { compile }
-            dependencies { compile files('sub.jar') { builtBy 'jar' } }
-            task jar << { file('sub.jar').text = 'content' }
-'''
-
-        inTestDirectory().withTasks("test").run().assertTasksExecuted(":sub:jar", ":test");
-    }
-
-    @Test
-    public void resolvedProjectArtifactsContainProjectVersionInTheirNames() {
-        testFile('settings.gradle').write("include 'a', 'b'");
-        testFile('a/build.gradle') << '''
-            apply plugin: 'base'
-            configurations { compile }
-            task aJar(type: Jar) { }
-            artifacts { compile aJar }
-'''
-        testFile('b/build.gradle') << '''
-            apply plugin: 'base'
-            version = 'early'
-            configurations { compile }
-            task bJar(type: Jar) { }
-            gradle.taskGraph.whenReady { project.version = 'late' }
-            artifacts { compile bJar }
-'''
-        testFile('build.gradle') << '''
-            configurations { compile }
-            dependencies { compile project(path: ':a', configuration: 'compile'), project(path: ':b', configuration: 'compile') }
-            task test(dependsOn: configurations.compile) << {
-                assert configurations.compile.collect { it.name } == ['a.jar', 'b-late.jar']
-            }
-'''
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void canUseArtifactSelectorForProjectDependencies() {
-        testFile('settings.gradle').write("include 'a', 'b'");
-        testFile('a/build.gradle') << '''
-            apply plugin: 'base'
-            configurations { 'default' {} }
-            task aJar(type: Jar) { }
-            artifacts { 'default' aJar }
-'''
-        testFile('b/build.gradle') << '''
-            configurations { compile }
-            dependencies { compile(project(':a')) { artifact { name = 'a'; type = 'jar' } } }
-            task test {
-                inputs.files configurations.compile
-                doFirst {
-                    assert [project(':a').tasks.aJar.archivePath] as Set == configurations.compile.files
-                }
-            }
-'''
-        inTestDirectory().withTasks('test').run()
-    }
-
-    @Test
-    public void canHaveCycleInProjectDependencies() {
-        inTestDirectory().withTasks('listJars').run();
-    }
-
-    @Test
-    public void canHaveNonTransitiveProjectDependencies() {
-        repo().module("group", "externalA", 1.5).publishArtifact()
-
-        testFile('settings.gradle') << "include 'a', 'b'"
-
-        testFile('build.gradle') << '''
-allprojects {
-    apply plugin: 'java'
-    repositories { mavenRepo urls: rootProject.uri('repo') }
-}
-project(':a') {
-    dependencies {
-        compile 'group:externalA:1.5'
-        compile files('libs/externalB.jar')
-    }
-}
-project(':b') {
-    configurations.compile.transitive = false
-    dependencies {
-        compile project(':a') { transitive = false }
-    }
-    task listJars << {
-        assert configurations.compile.collect { it.name } == ['a.jar']
-    }
-}
-'''
-
-        inTestDirectory().withTasks('listJars').run()
-    }
-
-    @Test
-    public void canResolveAndCacheDependenciesFromHttpIvyRepository() {
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publishArtifact()
-
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-        server.start()
-
-        testFile("build.gradle") << """
-apply plugin: 'java'
-repositories {
-    ivy {
-        name = 'gradleReleases'
-        artifactPattern "http://localhost:${server.port}/repo/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-}
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        inTestDirectory().withTasks('listJars').run()
-        inTestDirectory().withTasks('listJars').run()
-    }
-    
-    @Test
-    public void reportsMissingAndFailedHttpDownload() {
-        server.start()
-
-        testFile("build.gradle") << """
-apply plugin: 'java'
-repositories {
-    ivy {
-        name = 'gradleReleases'
-        artifactPattern "http://localhost:${server.port}/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-    }
-}
-dependencies {
-    compile 'group:org:1.2'
-}
-task show << { println configurations.compile.files }
-"""
-
-        def result = executer.withTasks("show").runWithFailure()
-        result.assertHasDescription('Execution failed for task \':show\'.')
-        result.assertHasCause('Could not resolve all dependencies for configuration \':compile\':')
-        assert result.getOutput().contains('group#org;1.2: not found')
-
-        server.addBroken('/')
-
-        result = executer.withTasks("show").runWithFailure()
-        result.assertHasDescription('Execution failed for task \':show\'.')
-        result.assertHasCause('Could not resolve all dependencies for configuration \':compile\':')
-    }
-
-    MavenRepository repo() {
-        return new MavenRepository(testFile('repo'))
-    }
-
-    IvyRepository ivyRepo() {
-        return new IvyRepository(testFile('ivy-repo'))
-    }
-}
-
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BroadcastMessagingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BroadcastMessagingIntegrationTest.groovy
new file mode 100644
index 0000000..e255c23
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BroadcastMessagingIntegrationTest.groovy
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2011 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.messaging.remote.internal.IncomingBroadcast
+import org.gradle.messaging.remote.internal.MessagingServices
+import org.gradle.messaging.remote.internal.OutgoingBroadcast
+import org.gradle.messaging.remote.internal.inet.SocketInetAddress
+import org.gradle.util.ConcurrentSpecification
+import java.util.concurrent.CountDownLatch
+import spock.lang.Ignore
+import java.util.concurrent.TimeUnit
+
+ at Ignore
+class BroadcastMessagingIntegrationTest extends ConcurrentSpecification {
+    static final Random RANDOM = new Random()
+    final def testGroup = "test-group-${RANDOM.nextLong()}"
+    final def address = new SocketInetAddress(InetAddress.getByName("233.253.17.122"), 7914)
+
+    def "client can discover and send messages to server"() {
+        TestService incoming = Mock()
+        def server = newServer()
+        def client = newClient()
+        def discovered = startsAsyncAction()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        discovered.started {
+            client.outgoing.doStuff("message")
+        }
+
+        then:
+        1 * incoming.doStuff("message") >> { discovered.done() }
+
+        cleanup:
+        server?.stop()
+        client?.stop()
+    }
+
+    def "multiple clients can discover server"() {
+        TestService incoming = Mock()
+        def server = newServer()
+        def client1 = newClient()
+        def client2 = newClient()
+        def discovered1 = startsAsyncAction()
+        def discovered2 = startsAsyncAction()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        discovered1.started {
+            client1.outgoing.doStuff("client1")
+        }
+        discovered2.started {
+            client2.outgoing.doStuff("client2")
+        }
+
+        then:
+        1 * incoming.doStuff("client1") >> { discovered1.done() }
+        1 * incoming.doStuff("client2") >> { discovered2.done() }
+
+        cleanup:
+        server?.stop()
+        client1?.stop()
+        client2?.stop()
+    }
+
+    @Ignore
+    def "client can broadcast to multiple servers"() {
+        TestService incoming1 = Mock()
+        TestService incoming2 = Mock()
+        def server1 = newServer()
+        def server2 = newServer()
+        def client = newClient()
+        def broadcast = new CountDownLatch(2)
+
+        given:
+        server1.addIncoming(incoming1)
+        server2.addIncoming(incoming2)
+
+        when:
+        start {
+            client.outgoing.doStuff("message")
+            broadcast.await()
+        }
+        finished()
+
+        then:
+        1 * incoming1.doStuff("message") >> { broadcast.countDown() }
+        1 * incoming2.doStuff("message") >> { broadcast.countDown() }
+
+        cleanup:
+        server1?.stop()
+        server2?.stop()
+        client?.stop()
+    }
+
+    def "client stop flushes messages"() {
+        TestService incoming = Mock()
+        def client
+        def server = newServer()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        start {
+            client = newClient()
+            client.outgoing.doStuff("message1")
+            client.outgoing.doStuff("message2")
+            client.outgoing.doStuff("message3")
+            client.stop()
+        }
+        finished()
+
+        then:
+        1 * incoming.doStuff("message1")
+        1 * incoming.doStuff("message2")
+        1 * incoming.doStuff("message3")
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client can start broadcasting before server started"() {
+        TestService incoming = Mock()
+        def client = newClient()
+        def server
+        def discovered = startsAsyncAction()
+
+        when:
+        discovered.started {
+            client.outgoing.doStuff("message")
+            server = newServer()
+            server.addIncoming(incoming)
+        }
+
+        then:
+        1 * incoming.doStuff("message") >> { discovered.done() }
+
+        cleanup:
+        server?.stop()
+        client?.stop()
+    }
+
+    def "client can start broadcasting after server started"() {
+        TestService incoming = Mock()
+        def client
+        def server = newServer()
+        def discovered = startsAsyncAction()
+
+        given:
+        server.addIncoming(incoming)
+
+        when:
+        discovered.started {
+            client = newClient()
+            client.outgoing.doStuff("message")
+        }
+
+        then:
+        1 * incoming.doStuff("message") >> { discovered.done() }
+
+        cleanup:
+        server?.stop()
+        client?.stop()
+    }
+
+    def "client can discover restarted server"() {
+        TestService incoming1 = Mock()
+        TestService incoming2 = Mock()
+        def server1 = newServer()
+        def server2
+        def client = newClient()
+        def discovered1 = startsAsyncAction()
+        def discovered2 = startsAsyncAction()
+
+        given:
+        server1.addIncoming(incoming1)
+
+        when:
+        discovered1.started {
+            client.outgoing.doStuff("message1")
+        }
+
+        then:
+        1 * incoming1.doStuff("message1") >> { discovered1.done() }
+
+        when:
+        discovered2.started {
+            server1.stop()
+            server2 = newServer()
+            server2.addIncoming(incoming2)
+            client.outgoing.doStuff("message2")
+        }
+
+        then:
+        1 * incoming2.doStuff("message2") >> { discovered2.done() }
+
+        cleanup:
+        client?.stop()
+        server1?.stop()
+        server2?.stop()
+    }
+
+    def "client can stop when no server has been discovered"() {
+        def client = newClient()
+
+        when:
+        start {
+            client.outgoing.doStuff("message1")
+            client.stop()
+        }.completesWithin(6, TimeUnit.SECONDS)
+
+        then:
+        notThrown(RuntimeException)
+
+        cleanup:
+        client?.stop()
+    }
+
+    def "can stop client when it has not sent any messages"() {
+        def client = newClient()
+
+        when:
+        start {
+            client.stop()
+        }
+        finished()
+
+        then:
+        notThrown(RuntimeException)
+
+        cleanup:
+        client?.stop()
+    }
+
+    def "groups are independent"() {
+        def server1 = newServer("${testGroup}-1")
+        def server2 = newServer("${testGroup}-2")
+        def client1 = newClient("${testGroup}-1")
+        def client2 = newClient("${testGroup}-2")
+        def received = new CountDownLatch(2)
+        TestService incoming1 = Mock()
+        TestService incoming2 = Mock()
+
+        given:
+        server1.addIncoming(incoming1)
+        server2.addIncoming(incoming2)
+
+        when:
+        start {
+            client1.outgoing.doStuff("client1")
+            client2.outgoing.doStuff("client2")
+            client1.stop()
+            client2.stop()
+            server1.stop()
+            server2.stop()
+        }
+        finished()
+
+        then:
+        1 * incoming1.doStuff("client1") >> { received.countDown() }
+        1 * incoming2.doStuff("client2") >> { received.countDown() }
+        0 * incoming1._
+        0 * incoming2._
+
+        cleanup:
+        server1?.stop()
+        server2?.stop()
+        client1?.stop()
+        client2?.stop()
+    }
+
+    private Client newClient(String group = testGroup) {
+        return new Client(group)
+    }
+
+    private Server newServer(String group = testGroup) {
+        return new Server(group)
+    }
+
+
+    class Server {
+        final def services
+        final def broadcast
+
+        Server(String group) {
+            services = new MessagingServices(getClass().classLoader, group, address)
+            broadcast = services.get(IncomingBroadcast.class)
+        }
+
+        void addIncoming(TestService value) {
+            broadcast.addIncoming(TestService.class, value)
+        }
+
+        void stop() {
+            services.stop()
+        }
+    }
+
+    class Client {
+        final def services
+        final def lookup
+        final TestService outgoing
+
+        Client(String group) {
+            services = new MessagingServices(getClass().classLoader, group, address)
+            lookup = services.get(OutgoingBroadcast.class)
+            outgoing = lookup.addOutgoing(TestService)
+        }
+
+        void stop() {
+            services.stop()
+        }
+    }
+}
+
+interface TestService {
+    void doStuff(String param)
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
index f762d06..07dfe41 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
@@ -69,7 +69,7 @@ class BuildAggregationIntegrationTest {
     @Test
     public void reportsNestedBuildFailure() {
         TestFile other = dist.testFile('other.gradle') << '''
-            1/0
+            throw new ArithmeticException('broken')
 '''
 
         dist.testFile('build.gradle') << '''
@@ -83,7 +83,7 @@ class BuildAggregationIntegrationTest {
         failure.assertHasFileName("Build file '${other}'")
         failure.assertHasLineNumber(2)
         failure.assertHasDescription('A problem occurred evaluating root project')
-        failure.assertThatCause(containsString('Division by zero'))
+        failure.assertHasCause('broken')
     }
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
index f9faa13..f2ec423 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
@@ -15,13 +15,13 @@
  */
 package org.gradle.integtests;
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.integtests.fixtures.ArtifactBuilder;
 import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
 import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.fail;
 
 public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest {
     @Test
@@ -32,6 +32,23 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
     }
 
     @Test
+    public void canExtendTheDefaultBuildForBuildSrcProject() {
+        ArtifactBuilder builder = artifactBuilder();
+        builder.sourceFile("org/gradle/test/DepClass.java").writelns(
+                "package org.gradle.test;",
+                "public class DepClass { }"
+        );
+        builder.buildJar(testFile("repo/test-1.3.jar"));
+
+        testFile("buildSrc/build.gradle").writelns(
+                "repositories { flatDir { dirs '../repo' } }",
+                "dependencies { compile name: 'test', version: '1.3' }");
+        testFile("buildSrc/src/main/java/BuildClass.java").writelns("public class BuildClass extends org.gradle.test.DepClass { }");
+        testFile("build.gradle").writelns("new BuildClass()");
+        inTestDirectory().withTaskList().run();
+    }
+
+    @Test
     public void buildSrcProjectCanReferToSourceOutsideBuildSrcDir() {
         testFile("gradle/src/BuildClass.java").writelns("public class BuildClass { }");
         testFile("buildSrc/build.gradle").writelns(
@@ -51,6 +68,20 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
     }
 
     @Test
+    public void gradleImplementationClassesDoNotLeakOntoBuildScriptClassPathWhenUsingBuildSrc() {
+        testFile("buildSrc/src/main/java/BuildClass.java").writelns("public class BuildClass { }");
+
+        testFile("build.gradle").writelns(
+                "try {",
+                "    buildscript.classLoader.loadClass('com.google.common.collect.Multimap')",
+                "    assert false: 'should break'",
+                "} catch(ClassNotFoundException e) { /* expected */ }",
+                "gradle.class.classLoader.loadClass('com.google.common.collect.Multimap')");
+
+        inTestDirectory().withTaskList().run();
+    }
+
+    @Test
     public void canDeclareClasspathInBuildScript() {
         ArtifactBuilder builder = artifactBuilder();
         builder.sourceFile("org/gradle/test/ImportedClass.java").writelns(
@@ -78,7 +109,7 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
                 "import org.gradle.test2.*",
                 "buildscript {",
                 "  repositories {",
-                "    flatDir dirs: file('repo')",
+                "    flatDir { dirs 'repo' }",
                 "  }",
                 "  dependencies {",
                 "    classpath name: 'test', version: '1.+'",
@@ -91,10 +122,10 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
                 "  new ImportedClass()",
                 "  new OnDemandImportedClass()",
                 "}",
-                "a = new ImportedClass()",
-                "b = OnDemandImportedClass",
-                "c = someValue",
-                "d = anotherValue",
+                "ext.a = new ImportedClass()",
+                "ext.b = OnDemandImportedClass",
+                "ext.c = someValue",
+                "ext.d = anotherValue",
                 "class TestClass extends ImportedClass { }",
                 "def aMethod() { return new OnDemandImportedClass() }"
         );
@@ -154,7 +185,7 @@ public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest
         testFile("build.gradle").writelns(
                 "assert gradle.scriptClassLoader == buildscript.classLoader.parent",
                 "buildscript {",
-                "    repositories { flatDir(dirs: file('repo')) }",
+                "    repositories { flatDir { dirs 'repo' }}",
                 "    dependencies { classpath name: 'test', version: '1.3' }",
                 "}"
         );
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
index c329c72..a93718a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.integtests;
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
 import org.gradle.util.TestFile;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -56,7 +56,7 @@ public class BuildScriptErrorIntegrationTest extends AbstractIntegrationTest {
 
         TestFile buildFile = testFile("build.gradle");
         buildFile.writelns(
-                "dependsOn 'child'",
+                "evaluationDependsOn 'child'",
                 "task t");
 
         TestFile childBuildFile = testFile("child/build.gradle");
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
index 4f03697..23d8080 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
@@ -22,23 +22,31 @@ import org.gradle.util.TestFile
 import org.junit.Test
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class BuildScriptExecutionIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void executesBuildScriptWithCorrectEnvironment() {
+        def implClassName = 'com.google.common.collect.Multimap'
         TestFile buildScript = testFile('build.gradle')
         buildScript << """
-            println 'quiet message'
-            captureStandardOutput(LogLevel.ERROR)
-            println 'error message'
-            assert project != null
-            assert "${buildScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sourceFile as String
-            assert "${buildScript.toURI()}" == buildscript.sourceURI as String
-            assert buildscript.classLoader == getClass().classLoader.parent
-            assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-            assert gradle.scriptClassLoader == buildscript.classLoader.parent
+println 'quiet message'
+logging.captureStandardOutput(LogLevel.ERROR)
+println 'error message'
+assert project != null
+assert "${buildScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sourceFile as String
+assert "${buildScript.toURI()}" == buildscript.sourceURI as String
+assert buildscript.classLoader == getClass().classLoader.parent
+assert buildscript.classLoader == Thread.currentThread().contextClassLoader
+assert gradle.scriptClassLoader == buildscript.classLoader.parent
+Gradle.class.classLoader.loadClass('${implClassName}')
+try {
+    buildscript.classLoader.loadClass('${implClassName}')
+    assert false: 'should fail'
+} catch (ClassNotFoundException e) {
+    // expected
+}
 
             task doStuff
 """
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
index ae7f57f..f041714 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
@@ -25,7 +25,8 @@ import org.gradle.util.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import static org.junit.Assert.*
+import static org.junit.Assert.assertEquals
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 
 /**
  * @author Hans Dockter
@@ -45,42 +46,102 @@ class CacheProjectIntegrationTest {
 
     @Before
     public void setUp() {
+        // Use own home dir so we don't blast the shared one when we run with -C rebuild
+        dist.requireOwnUserHomeDir()
+
         String version = GradleVersion.current().version
         projectDir = dist.getTestDir().file("project")
         projectDir.mkdirs()
         userHomeDir = dist.getUserHomeDir()
         buildFile = projectDir.file('build.gradle')
         ScriptSource source = new UriScriptSource("build file", buildFile)
-        propertiesFile = userHomeDir.file("caches/$version/scripts/$source.className/cache.properties")
-        classFile = userHomeDir.file("caches/$version/scripts/$source.className/no_buildscript_ProjectScript/${source.className}.class")
-        artifactsCache = projectDir.file(".gradle/$version/taskArtifacts/cache.bin")
+        propertiesFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/cache.properties")
+        classFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/classes/${source.className}.class")
+        artifactsCache = projectDir.file(".gradle/$version/taskArtifacts/taskArtifacts.bin")
     }
 
     @Test
-    public void cachesBuildScript() {
+    public void "caches compiled build script"() {
         createLargeBuildScript()
         testBuild("hello1", "Hello 1")
         TestFile.Snapshot classFileSnapshot = classFile.snapshot()
-        TestFile.Snapshot artifactsCacheSnapshot = artifactsCache.snapshot()
 
         testBuild("hello2", "Hello 2")
         classFile.assertHasNotChangedSince(classFileSnapshot)
-        artifactsCache.assertHasNotChangedSince(artifactsCacheSnapshot)
 
         modifyLargeBuildScript()
         testBuild("newTask", "I am new")
         classFile.assertHasChangedSince(classFileSnapshot)
-        artifactsCache.assertHasNotChangedSince(artifactsCacheSnapshot)
         classFileSnapshot = classFile.snapshot()
-        artifactsCacheSnapshot = artifactsCache.snapshot()
 
-        testBuild("newTask", "I am new", "-Crebuild")
+        testBuild("newTask", "I am new", "--recompile-scripts")
         classFile.assertHasChangedSince(classFileSnapshot)
+    }
+
+    @Test
+    public void "caches incremental build state"() {
+        createLargeBuildScript()
+        testBuild("hello1", "Hello 1")
+        TestFile.Snapshot artifactsCacheSnapshot = artifactsCache.snapshot()
+
+        testBuild("hello1", "Hello 1")
+        artifactsCache.assertHasNotChangedSince(artifactsCacheSnapshot)
+
+        testBuild("hello2", "Hello 2")
+        artifactsCache.assertHasChangedSince(artifactsCacheSnapshot)
+        artifactsCacheSnapshot = artifactsCache.snapshot()
+
+        testBuild("hello2", "Hello 2", "-rerun-tasks")
         artifactsCache.assertHasChangedSince(artifactsCacheSnapshot)
     }
 
+    @Test
+    public void "does not rebuild artifact cache when run with --recompile-scripts"() {
+        createLargeBuildScript()
+        testBuild("hello1", "Hello 1")
+
+        TestFile dependenciesCache = findDependencyCacheDir()
+        assert dependenciesCache.isDirectory() && dependenciesCache.listFiles().length > 0
+
+        modifyLargeBuildScript()
+        testBuild("newTask", "I am new", "--recompile-scripts")
+        assert dependenciesCache.isDirectory() && dependenciesCache.listFiles().length > 0
+    }
+
+    @Test
+    public void "does not rebuild artifact cache when run with --cache rebuild"() {
+        createLargeBuildScript()
+        testBuild("hello1", "Hello 1")
+
+        TestFile dependenciesCache = findDependencyCacheDir()
+        assert dependenciesCache.isDirectory() && dependenciesCache.listFiles().length > 0
+
+        modifyLargeBuildScript()
+        testBuild("newTask", "I am new", "-Crebuild")
+        assert dependenciesCache.isDirectory() && dependenciesCache.listFiles().length > 0
+    }
+
+    @Test
+    public void "does not rebuild artifact cache when run with --rerun-tasks"() {
+        createLargeBuildScript()
+        testBuild("hello1", "Hello 1")
+
+        TestFile dependenciesCache = findDependencyCacheDir()
+        assert dependenciesCache.isDirectory() && dependenciesCache.listFiles().length > 0
+
+        modifyLargeBuildScript()
+        testBuild("newTask", "I am new", "--rerun-tasks")
+        assert dependenciesCache.isDirectory() && dependenciesCache.listFiles().length > 0
+    }
+
+    private TestFile findDependencyCacheDir() {
+        def cacheVersion = DefaultCacheLockingManager.CACHE_LAYOUT_VERSION
+        def resolverArtifactCache = new TestFile(userHomeDir.file("caches/artifacts-${cacheVersion}/filestore"))
+        return resolverArtifactCache.file("commons-io/commons-io/")
+    }
+
     private def testBuild(String taskName, String expected, String... args) {
-        executer.inDirectory(projectDir).withTasks(taskName).withArguments(args).withQuietLogging().run()
+        executer.inDirectory(projectDir).withTasks(taskName).withArguments(args).run()
         assertEquals(expected, projectDir.file(TEST_FILE).text)
         classFile.assertIsFile()
         propertiesFile.assertIsFile()
@@ -92,12 +153,22 @@ class CacheProjectIntegrationTest {
 
     def createLargeBuildScript() {
         File buildFile = projectDir.file('build.gradle')
-        String content = ""
+        String content = """
+repositories { mavenCentral() }
+configurations { compile }
+dependencies { compile 'commons-io:commons-io:1.4 at jar' }
+"""
+
         50.times {i ->
-            content += """task 'hello$i' << {
+            content += """
+task 'hello$i' {
     File file = file('$TEST_FILE')
-    file.parentFile.mkdirs()
-    file.write('Hello $i')
+    outputs.file file
+    doLast {
+        configurations.compile.resolve()
+        file.parentFile.mkdirs()
+        file.write('Hello $i')
+    }
 }
 
 void someMethod$i() {
@@ -112,10 +183,17 @@ void someMethod$i() {
     def void modifyLargeBuildScript() {
         File buildFile = projectDir.file('build.gradle')
         String newContent = buildFile.text + """
-task newTask << {
+configurations { other }
+dependencies { other 'commons-lang:commons-lang:2.6 at jar' }
+
+task newTask {
     File file = file('$TEST_FILE')
-    file.parentFile.mkdirs()
-    file.write('I am new')
+    outputs.file file
+    doLast {
+        configurations.other.resolve()
+        file.parentFile.mkdirs()
+        file.write('I am new')
+    }
 }
 """
         buildFile.write(newContent)
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
new file mode 100644
index 0000000..8a702f3
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * 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.integtests
+
+import java.nio.charset.Charset
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import spock.lang.IgnoreIf
+import spock.lang.Unroll
+
+ at IgnoreIf({ !GradleDistributionExecuter.systemPropertyExecuter.forks })
+class CharacterEncodingIntegTest extends AbstractIntegrationSpec {
+
+    def executerEncoding(String inputEncoding) {
+        if (inputEncoding) {
+            executer.withDefaultCharacterEncoding(inputEncoding)
+        }
+    }
+
+    @Unroll("build default encoding matches specified - input = #inputEncoding, expectedEncoding: #expectedEncoding")
+    def "build default encoding matches specified"(String inputEncoding, String expectedEncoding) {
+        given:
+        executerEncoding inputEncoding
+
+        and:
+        buildFile.write """
+            task echoDefaultEncoding {
+                doFirst {
+                    println "default encoding: " + java.nio.charset.Charset.defaultCharset().name()
+                }
+            }
+        """, expectedEncoding
+
+        when:
+        run "echoDefaultEncoding"
+
+        then:
+        output.contains "default encoding: $expectedEncoding"
+
+        where:
+        inputEncoding | expectedEncoding
+        "UTF-8"       | "UTF-8"
+        "US-ASCII"    | "US-ASCII"
+        null          | Charset.defaultCharset().name()
+    }
+
+    @Unroll("forked java processes inherit default encoding - input = #inputEncoding, expectedEncoding: #expectedEncoding")
+    def "forked java processes inherit default encoding"() {
+        given:
+        executerEncoding inputEncoding
+
+        and:
+        file("src", "main", "java").mkdirs()
+        file("src", "main", "java", "EchoEncoding.java").write """
+            package echo;
+
+            import java.nio.charset.Charset;
+
+            public class EchoEncoding {
+                public static void main(String[] args) {
+                    System.out.println("default encoding: " + Charset.defaultCharset().name());
+                }
+            }
+        """, executer.getDefaultCharacterEncoding()
+
+        
+        and:
+        buildFile.write """
+            apply plugin: "java"
+
+            task echoDefaultEncoding(type: JavaExec) {
+                classpath = project.files(compileJava)
+                main "echo.EchoEncoding"
+            }
+        """, expectedEncoding
+
+        when:
+        run "echoDefaultEncoding"
+
+        then:
+        output.contains "default encoding: $expectedEncoding"
+
+        where:
+        inputEncoding | expectedEncoding
+        "UTF-8"       | "UTF-8"
+        "US-ASCII"    | "US-ASCII"
+        null          | Charset.defaultCharset().name()
+    }
+
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ClientModuleDependenciesResolveIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ClientModuleDependenciesResolveIntegrationTest.java
deleted file mode 100644
index 10e258b..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ClientModuleDependenciesResolveIntegrationTest.java
+++ /dev/null
@@ -1,41 +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.integtests;
-
-import org.gradle.integtests.fixtures.GradleDistribution;
-import org.gradle.integtests.fixtures.GradleDistributionExecuter;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class ClientModuleDependenciesResolveIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution();
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
-
-    @Test
-    public void testResolve() {
-        // the actual testing is done in the build script.
-        File projectDir = new File(dist.getSamplesDir(), "clientModuleDependencies/shared");
-        executer.inDirectory(projectDir).withTasks("testDeps").run();
-
-        projectDir = new File(dist.getSamplesDir(), "clientModuleDependencies/api");
-        executer.inDirectory(projectDir).withTasks("testDeps").run();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CodeQualityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CodeQualityIntegrationTest.groovy
deleted file mode 100644
index 801858d..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CodeQualityIntegrationTest.groovy
+++ /dev/null
@@ -1,188 +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.integtests
-
-import org.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.util.TestFile
-import org.hamcrest.Matcher
-import org.junit.Test
-import static org.gradle.util.Matchers.*
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
-
-class CodeQualityIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void handlesEmptyProjects() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-'''
-        inTestDirectory().withTasks('check').run()
-    }
-
-    @Test
-    public void generatesReportForJavaSource() {
-        testFile('build.gradle') << '''
-apply plugin: 'java'
-apply plugin: 'code-quality'
-'''
-        writeCheckstyleConfig()
-
-        testFile('src/main/java/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
-        testFile('src/test/java/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
-
-        inTestDirectory().withTasks('check').run()
-
-        testFile('build/checkstyle/main.xml').assertContents(containsClass('org.gradle.Class1'))
-        testFile('build/checkstyle/test.xml').assertContents(containsClass('org.gradle.TestClass1'))
-    }
-
-    @Test
-    public void generatesReportForJavaSourceInGroovySourceDirs() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-dependencies { groovy localGroovy() }
-'''
-        writeCheckstyleConfig()
-
-        testFile('src/main/groovy/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
-        testFile('src/test/groovy/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
-
-        inTestDirectory().withTasks('check').run()
-
-        testFile('build/checkstyle/main.xml').assertContents(containsClass('org.gradle.Class1'))
-        testFile('build/checkstyle/test.xml').assertContents(containsClass('org.gradle.TestClass1'))
-    }
-
-    private Matcher<String> containsClass(String classname) {
-        return containsLine(containsString(classname.replace('.', File.separator) + '.java'))
-    }
-
-    @Test
-    public void checkstyleOnlyChecksJavaSource() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-'''
-        writeCheckstyleConfig()
-
-        testFile('src/main/groovy/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
-        testFile('src/main/groovy/org/gradle/Class2.java') << 'package org.gradle; class Class2 { }'
-        testFile('src/main/groovy/org/gradle/class3.groovy') << 'package org.gradle; class class3 { }'
-
-        inTestDirectory().withTasks('checkstyleMain').run()
-
-        testFile('build/checkstyle/main.xml').assertExists()
-        testFile('build/checkstyle/main.xml').assertContents(not(containsClass('org.gradle.class3')))
-    }
-
-    @Test
-    public void checkstyleViolationBreaksBuild() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-'''
-        writeCheckstyleConfig()
-
-        testFile('src/main/java/org/gradle/class1.java') << 'package org.gradle; class class1 { }'
-        testFile('src/main/groovy/org/gradle/class2.java') << 'package org.gradle; class class2 { }'
-
-        ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
-        failure.assertHasDescription('Execution failed for task \':checkstyleMain\'')
-        failure.assertThatCause(startsWith('Checkstyle check violations were found in main Java source. See the report at'))
-
-        testFile('build/checkstyle/main.xml').assertExists()
-    }
-
-    @Test
-    public void generatesReportForGroovySource() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-dependencies { groovy localGroovy() }
-'''
-        writeCodeNarcConfigFile()
-
-        testFile('src/main/groovy/org/gradle/Class1.groovy') << 'package org.gradle; class Class1 { }'
-        testFile('src/test/groovy/org/gradle/TestClass1.groovy') << 'package org.gradle; class TestClass1 { }'
-
-        inTestDirectory().withTasks('check').run()
-
-        testFile('build/reports/codenarc/main.html').assertExists()
-        testFile('build/reports/codenarc/test.html').assertExists()
-    }
-
-    @Test
-    public void codeNarcOnlyChecksGroovySource() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-'''
-
-        writeCodeNarcConfigFile()
-
-        testFile('src/main/groovy/org/gradle/class1.java') << 'package org.gradle; class class1 { }'
-        testFile('src/main/groovy/org/gradle/Class2.groovy') << 'package org.gradle; class Class2 { }'
-
-        inTestDirectory().withTasks('codenarcMain').run()
-
-        testFile('build/reports/codenarc/main.html').assertExists()
-    }
-
-    @Test
-    public void codeNarcViolationBreaksBuild() {
-        testFile('build.gradle') << '''
-apply plugin: 'groovy'
-apply plugin: 'code-quality'
-dependencies { groovy localGroovy() }
-'''
-
-        writeCodeNarcConfigFile()
-
-        testFile('src/main/groovy/org/gradle/class1.groovy') << 'package org.gradle; class class1 { }'
-
-        ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
-        failure.assertHasDescription('Execution failed for task \':codenarcMain\'')
-        failure.assertThatCause(startsWith('CodeNarc check violations were found in main Groovy source. See the report at '))
-
-        testFile('build/reports/codenarc/main.html').assertExists()
-    }
-
-    private TestFile writeCheckstyleConfig() {
-        return testFile('config/checkstyle/checkstyle.xml') << '''
-<!DOCTYPE module PUBLIC
-        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
-        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
-<module name="Checker">
-    <module name="TreeWalker">
-        <module name="TypeName"/>
-    </module>
-</module>'''
-    }
-
-    private TestFile writeCodeNarcConfigFile() {
-        return testFile('config/codenarc/codenarc.xml') << '''
-<ruleset xmlns="http://codenarc.org/ruleset/1.0"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
-        xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
-    <ruleset-ref path='rulesets/naming.xml'/>
-</ruleset>
-'''
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
index aff2b33..115bd01 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
@@ -15,20 +15,23 @@
  */
 package org.gradle.integtests
 
+import org.apache.tools.ant.taskdefs.Chmod
 import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.integtests.fixtures.TestResources
-import org.gradle.util.Jvm
-import org.gradle.util.OperatingSystem
-import org.junit.Ignore
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.internal.os.OperatingSystem
 import org.junit.Rule
 import org.junit.Test
+import org.gradle.util.*
+import org.gradle.internal.jvm.Jvm
 
 public class CommandLineIntegrationTest {
     @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter(GradleDistributionExecuter.Executer.forking)
     @Rule public final TestResources resources = new TestResources()
+    @Rule public final PreconditionVerifier verifier = new PreconditionVerifier()
 
     @Test
     public void hasNonZeroExitCodeOnBuildFailure() {
@@ -37,106 +40,156 @@ public class CommandLineIntegrationTest {
     }
 
     @Test
-    public void canonicalisesWorkingDirectory() {
-        File javaprojectDir;
-        if (OperatingSystem.current().isWindows()) {
-            javaprojectDir = new File(dist.samplesDir, 'java/QUICKS~1')
-        } else if (!OperatingSystem.current().isCaseSensitiveFileSystem()) {
-            javaprojectDir = new File(dist.samplesDir, 'JAVA/QuickStart')
-        } else {
-            javaprojectDir = new File(dist.samplesDir, 'java/multiproject/../quickstart')
-        }
-        executer.inDirectory(javaprojectDir).withTasks('classes').run()
-    }
-
-    @Test
-    public void canDefineJavaHomeViaEnvironmentVariable() {
+    public void canDefineJavaHomeUsingEnvironmentVariable() {
         String javaHome = Jvm.current().javaHome
         String expectedJavaHome = "-PexpectedJavaHome=${javaHome}"
 
-        // Handle java on the system PATH, with no JAVA_HOME specified
-        String path = String.format('%s%s%s', Jvm.current().javaExecutable.parentFile, File.pathSeparator, System.getenv('PATH'))
-        executer.withEnvironmentVars('PATH': path, 'JAVA_HOME': '')
-                .withArguments(expectedJavaHome)
-                .withTasks('checkJavaHome')
-                .run()
-
         // Handle JAVA_HOME specified
-        executer.withEnvironmentVars('JAVA_HOME': javaHome)
-                .withArguments(expectedJavaHome)
-                .withTasks('checkJavaHome')
-                .run()
+        executer.withEnvironmentVars('JAVA_HOME': javaHome).withArguments(expectedJavaHome).withTasks('checkJavaHome').run()
 
         // Handle JAVA_HOME with trailing separator
-        executer.withEnvironmentVars('JAVA_HOME': javaHome + File.separator)
-                .withArguments(expectedJavaHome)
-                .withTasks('checkJavaHome')
-                .run()
+        executer.withEnvironmentVars('JAVA_HOME': javaHome + File.separator).withArguments(expectedJavaHome).withTasks('checkJavaHome').run()
 
         if (!OperatingSystem.current().isWindows()) {
             return
         }
 
         // Handle JAVA_HOME wrapped in quotes
-        executer.withEnvironmentVars('JAVA_HOME': "\"$javaHome\"")
-                .withArguments(expectedJavaHome)
-                .withTasks('checkJavaHome')
-                .run()
+        executer.withEnvironmentVars('JAVA_HOME': "\"$javaHome\"").withArguments(expectedJavaHome).withTasks('checkJavaHome').run()
 
         // Handle JAVA_HOME with slash separators. This is allowed by the JVM
-        executer.withEnvironmentVars('JAVA_HOME': javaHome.replace(File.separator, '/'))
-                .withArguments(expectedJavaHome)
-                .withTasks('checkJavaHome')
-                .run()
+        executer.withEnvironmentVars('JAVA_HOME': javaHome.replace(File.separator, '/')).withArguments(expectedJavaHome).withTasks('checkJavaHome').run()
+    }
+
+    @Test
+    public void usesJavaCommandFromPathWhenJavaHomeNotSpecified() {
+        String javaHome = Jvm.current().javaHome
+        String expectedJavaHome = "-PexpectedJavaHome=${javaHome}"
+
+        String path = String.format('%s%s%s', Jvm.current().javaExecutable.parentFile, File.pathSeparator, System.getenv('PATH'))
+        executer.withEnvironmentVars('PATH': path, 'JAVA_HOME': '').withArguments(expectedJavaHome).withTasks('checkJavaHome').run()
     }
 
     @Test
-    public void failsWhenJavaHomeDoetNotPointToAJavaInstallation() {
-        def failure = executer.withEnvironmentVars('JAVA_HOME': dist.testDir)
-                .withTasks('checkJavaHome')
-                .runWithFailure()
+    public void failsWhenJavaHomeDoesNotPointToAJavaInstallation() {
+        def failure = executer.withEnvironmentVars('JAVA_HOME': dist.testDir).withTasks('checkJavaHome').runWithFailure()
         assert failure.output.contains('ERROR: JAVA_HOME is set to an invalid directory')
     }
 
     @Test
+    @Requires(TestPrecondition.SYMLINKS)
+    public void failsWhenJavaHomeNotSetAndPathDoesNotContainJava() {
+        def path
+        if (OperatingSystem.current().windows) {
+            path = ''
+        } else {
+            // Set up a fake bin directory, containing the things that the script needs, minus any java that might be in /usr/bin
+            def binDir = dist.testFile('fake-bin')
+            ['basename', 'dirname', 'uname', 'which'].each { linkToBinary(it, binDir) }
+            path = binDir.absolutePath
+        }
+
+        def failure = executer.withEnvironmentVars('PATH': path, 'JAVA_HOME': '').withTasks('checkJavaHome').runWithFailure()
+        assert failure.output.contains("ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.")
+    }
+
+    def linkToBinary(String command, TestFile binDir) {
+        binDir.mkdirs()
+        def binary = new File("/usr/bin/$command")
+        if (!binary.exists()) {
+            binary = new File("/bin/$command")
+        }
+        assert binary.exists()
+        FileSystems.default.createSymbolicLink(binDir.file(command), binary.absoluteFile)
+    }
+
+    @Test
     public void canDefineGradleUserHomeViaEnvironmentVariable() {
         // the actual testing is done in the build script.
         File gradleUserHomeDir = dist.testDir.file('customUserHome')
-        executer.withUserHomeDir(null)
-                .withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath)
-                .withTasks("checkGradleUserHomeViaSystemEnv")
-                .run();
+        executer.withUserHomeDir(null).withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath).withTasks("checkGradleUserHomeViaSystemEnv").run();
     }
 
     @Test
     public void checkDefaultGradleUserHome() {
         // the actual testing is done in the build script.
-        executer.withUserHomeDir(null).
-                withTasks("checkDefaultGradleUserHome")
-                .run();
+        executer.withUserHomeDir(null).withTasks("checkDefaultGradleUserHome").run();
+    }
+
+    @Test
+    public void canSpecifySystemPropertiesFromCommandLine() {
+        // the actual testing is done in the build script.
+        executer.withTasks("checkSystemProperty").withArguments('-DcustomProp1=custom-value', '-DcustomProp2=custom value').run();
     }
 
     @Test
-    public void canSpecifySystemPropertyFromCommandLine() {
+    public void canSpecifySystemPropertiesUsingGradleOptsEnvironmentVariable() {
         // the actual testing is done in the build script.
-        executer.withTasks("checkSystemProperty").withArguments('-DcustomSystemProperty=custom-value').run();
+        executer.withTasks("checkSystemProperty").withEnvironmentVars("GRADLE_OPTS": '-DcustomProp1=custom-value "-DcustomProp2=custom value"').run();
     }
 
     @Test
-    public void canSpecifySystemPropertyUsingGradleOptsEnvironmentVariable() {
+    public void canSpecifySystemPropertiesUsingJavaOptsEnvironmentVariable() {
         // the actual testing is done in the build script.
-        executer.withTasks("checkSystemProperty").withEnvironmentVars("GRADLE_OPTS": '-DcustomSystemProperty=custom-value').run();
+        executer.withTasks("checkSystemProperty").withEnvironmentVars("JAVA_OPTS": '-DcustomProp1=custom-value "-DcustomProp2=custom value"').run();
+    }
+
+    @Test
+    public void allowsReconfiguringProjectCacheDirWithRelativeDir() {
+        //given
+        dist.testFile("build.gradle").write "task foo { outputs.file file('out'); doLast { } }"
+
+        //when
+        executer.withTasks("foo").withArguments("--project-cache-dir", ".foo").run()
+
+        //then
+        assert dist.testFile(".foo").exists()
+    }
+
+    @Test
+    public void allowsReconfiguringProjectCacheDirWithAbsoluteDir() {
+        //given
+        dist.testFile("build.gradle").write "task foo { outputs.file file('out'); doLast { } }"
+        File someAbsoluteDir = dist.testFile("foo/bar/baz").absoluteFile
+        assert someAbsoluteDir.absolute
+
+        //when
+        executer.withTasks("foo").withArguments("--project-cache-dir", someAbsoluteDir.toString()).run()
+
+        //then
+        assert someAbsoluteDir.exists()
     }
 
-    @Test @Ignore
+    @Test
     public void systemPropGradleUserHomeHasPrecedenceOverEnvVariable() {
         // the actual testing is done in the build script.
         File gradleUserHomeDir = dist.testFile("customUserHome")
         File systemPropGradleUserHomeDir = dist.testFile("systemPropCustomUserHome")
-        executer.withUserHomeDir(null)
-                .withArguments("-Dgradle.user.home=" + systemPropGradleUserHomeDir.absolutePath)
-                .withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath)
-                .withTasks("checkSystemPropertyGradleUserHomeHasPrecedence")
-                .run()
+        executer.withUserHomeDir(null).withArguments("-Dgradle.user.home=" + systemPropGradleUserHomeDir.absolutePath).withEnvironmentVars('GRADLE_USER_HOME': gradleUserHomeDir.absolutePath).withTasks("checkSystemPropertyGradleUserHomeHasPrecedence").run()
+    }
+
+    @Test
+    @Requires(TestPrecondition.SYMLINKS)
+    public void resolvesLinksWhenDeterminingHomeDirectory() {
+        def script = dist.testFile('bin/my app')
+        script.parentFile.createDir()
+        FileSystems.default.createSymbolicLink(script, dist.gradleHomeDir.file('bin/gradle'))
+
+        def result = executer.usingExecutable(script.absolutePath).withTasks("help").run()
+        assert result.output.contains("my app")
+    }
+
+    @Test
+    public void usesScriptBaseNameAsApplicationNameForUseInLogMessages() {
+        def binDir = dist.gradleHomeDir.file('bin')
+        def newScript = binDir.file(OperatingSystem.current().getScriptName('my app'))
+        binDir.file(OperatingSystem.current().getScriptName('gradle')).copyTo(newScript)
+        def chmod = new Chmod()
+        chmod.file = newScript
+        chmod.perm = "700"
+        AntUtil.execute(chmod)
+
+        def result = executer.usingExecutable(newScript.absolutePath).withTasks("help").run()
+        assert result.output.contains("my app")
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CopyErrorIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CopyErrorIntegrationTest.groovy
deleted file mode 100644
index 28287fd..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CopyErrorIntegrationTest.groovy
+++ /dev/null
@@ -1,76 +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.gradle.integtests.fixtures.ExecutionFailure
-import org.gradle.util.OperatingSystem
-import org.gradle.util.TestFile
-import org.junit.Assert
-import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
-
-class CopyErrorIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void reportsSymLinkWhichPointsToNothing() {
-        if (OperatingSystem.current().isWindows()) {
-            return
-        }
-
-        TestFile link = testFile('src/file')
-        link.linkTo(testFile('missing'))
-
-        Assert.assertFalse(link.isDirectory())
-        Assert.assertFalse(link.isFile())
-        Assert.assertFalse(link.exists())
-
-        testFile('build.gradle') << '''
-            task copy(type: Copy) {
-                from 'src'
-                into 'dest'
-            }
-'''
-
-        ExecutionFailure failure = inTestDirectory().withTasks('copy').runWithFailure()
-        failure.assertHasDescription("Could not list contents of '${link}'.")
-    }
-
-    @Test
-    public void reportsUnreadableSourceDir() {
-        if (OperatingSystem.current().isWindows()) {
-            return
-        }
-
-        TestFile dir = testFile('src').createDir()
-        dir.permissions = '-w-r--r--'
-
-        Assert.assertTrue(dir.isDirectory())
-        Assert.assertTrue(dir.exists())
-        Assert.assertFalse(dir.canRead())
-        Assert.assertTrue(dir.canWrite())
-
-        testFile('build.gradle') << '''
-            task copy(type: Copy) {
-                from 'src'
-                into 'dest'
-            }
-'''
-
-        ExecutionFailure failure = inTestDirectory().withTasks('copy').runWithFailure()
-        failure.assertHasDescription("Could not list contents of directory '${dir}' as it is not readable.")
-
-        dir.permissions = 'rwxr--r--'
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CopyTaskIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CopyTaskIntegrationTest.groovy
deleted file mode 100644
index 7797fce..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CopyTaskIntegrationTest.groovy
+++ /dev/null
@@ -1,368 +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.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
-    @Rule
-    public final TestResources resources = new TestResources("copyTestResources")
-
-    @Test
-    public void testSingleSourceWithIncludeAndExclude() {
-        TestFile buildFile = testFile("build.gradle") << '''
-            task (copy, type:Copy) {
-               from 'src'
-               into 'dest'
-               include '**/sub/**'
-               exclude '**/ignore/**'
-            }
-'''
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                'one/sub/onesub.a',
-                'one/sub/onesub.b'
-        )
-    }
-
-   @Test
-   public void testSingleSourceWithSpecClosures() {
-       TestFile buildFile = testFile("build.gradle").writelns(
-               "task (copy, type:Copy) {",
-               "   from 'src'",
-               "   into 'dest'",
-               "   include { fte -> !fte.file.name.endsWith('b') }",
-               "   exclude { fte -> fte.file.name == 'bad.file' }",
-               "}"
-       )
-       usingBuildFile(buildFile).withTasks("copy").run()
-       testFile('dest').assertHasDescendants(
-               'root.a',
-               'one/one.a',
-               'two/two.a',
-       )
-   }
-
-    @Test
-    public void testMultipleSourceWithInheritedPatterns() {
-        TestFile buildFile = testFile("build.gradle") << '''
-            task (copy, type:Copy) {
-               into 'dest'
-               from('src/one') {
-                  into '1'
-                  include '**/*.a'
-               }
-               from('src/two') {
-                  into '2'
-                  include '**/*.b'
-               }
-               exclude '**/ignore/**'
-            }
-'''
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                '1/one.a',
-                '1/sub/onesub.a',
-                '2/two.b',
-        )
-    }
-
-    @Test
-    public void testMultipleSourcesWithInheritedDestination() {
-        TestFile buildFile = testFile("build.gradle") << '''
-            task (copy, type:Copy) {
-               into 'dest'
-               into('common') {
-                  from('src/one') {
-                     into 'a/one'
-                     include '*.a'
-                  }
-                  into('b') {
-                     from('src/two') {
-                        into 'two'
-                        include '**/*.b'
-                     }
-                  }
-               }
-            }
-'''
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                'common/a/one/one.a',
-                'common/b/two/two.b',
-        )
-    }
-
-    @Test void testRename() {
-        TestFile buildFile = testFile("build.gradle") << '''
-            task (copy, type:Copy) {
-               from 'src'
-               into 'dest'
-               exclude '**/ignore/**'
-               rename '(.*).a', '\$1.renamed'
-               rename { it.startsWith('one.') ? "renamed_$it" : it }
-            }
-'''
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                'root.renamed',
-                'root.b',
-                'one/renamed_one.renamed',
-                'one/renamed_one.b',
-                'one/sub/onesub.renamed',
-                'one/sub/onesub.b',
-                'two/two.renamed',
-                'two/two.b'
-        )
-    }
-
-    @Test
-    public void testCopyAction() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                "task copyIt << {",
-                "   copy {",
-                "      from 'src'",
-                "      into 'dest'",
-                "      exclude '**/ignore/**'",
-                "   }",
-                "}"
-        )
-        usingBuildFile(buildFile).withTasks("copyIt").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'root.b',
-                'one/one.a',
-                'one/one.b',
-                'one/sub/onesub.a',
-                'one/sub/onesub.b',
-                'two/two.a',
-                'two/two.b',
-        )
-    }
-
-    @Test public void copySingleFiles() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                "task copyIt << {",
-                "   copy {",
-                "      from 'src/one/one.a', 'src/two/two.a'",
-                "      into 'dest/two'",
-                "   }",
-                "}"
-        )
-        usingBuildFile(buildFile).withTasks("copyIt").run()
-        testFile('dest').assertHasDescendants(
-                'two/one.a',
-                'two/two.a',
-        )
-    }
-
-    /*
-     * two.a starts off with "$one\n${one+1}\n${one+1+1}\n"
-     * If these filters are chained in the correct order, you should get 6, 11, and 16
-     */
-    @Test public void copyMultipleFilterTest() {
-        TestFile buildFile = testFile('build.gradle').writelns(
-                """task (copy, type:Copy) {
-                   into 'dest'
-                   expand(one: 1)
-                   filter { (Integer.parseInt(it) * 10) as String }
-                   filter { (Integer.parseInt(it) + 2) as String }
-                   from('src/two/two.a') {
-                     filter { (Integer.parseInt(it) / 2) as String }
-                   }
-                }
-                """
-        )
-        usingBuildFile(buildFile).withTasks("copy").run()
-        Iterator<String> it = testFile('dest/two.a').readLines().iterator()
-        assertThat(it.next(), startsWith('6'))
-        assertThat(it.next(), startsWith('11'))
-        assertThat(it.next(), startsWith('16'))
-    }
-
-    @Test public void chainedTransformations() {
-        def buildFile = testFile('build.gradle') << '''
-            task copy(type: Copy) {
-                into 'dest'
-                rename '(.*).a', '\$1.renamed'
-                eachFile { fcd -> if (fcd.path.contains('/ignore/')) { fcd.exclude() } }
-                eachFile { fcd -> if (fcd.relativePath.segments.length > 1) { fcd.relativePath = fcd.relativePath.prepend('prefix') }}
-                filter(org.apache.tools.ant.filters.PrefixLines, prefix: 'line: ')
-                eachFile { fcd -> fcd.filter { it.replaceAll('^line:', 'prefix:') } }
-                from ('src') {
-                    rename '(.*).renamed', '\$1.renamed_twice'
-                    eachFile { fcd -> fcd.path = fcd.path.replaceAll('/one/sub/', '/one_sub/') }
-                    eachFile { fcd -> if (fcd.path.contains('/two/')) { fcd.exclude() } }
-                    eachFile { fcd -> fcd.filter { "[$it]" } }
-                }
-            }
-'''
-        usingBuildFile(buildFile).withTasks('copy').run()
-        testFile('dest').assertHasDescendants(
-                'root.renamed_twice',
-                'root.b',
-                'prefix/one/one.renamed_twice',
-                'prefix/one/one.b',
-                'prefix/one_sub/onesub.renamed_twice',
-                'prefix/one_sub/onesub.b'
-        )
-
-        Iterator<String> it = testFile('dest/root.renamed_twice').readLines().iterator()
-        assertThat(it.next(), equalTo('[prefix: line 1]'))
-        assertThat(it.next(), equalTo('[prefix: line 2]'))
-    }
-
-    @Test public void testCopyFromFileTree() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """task cpy << {
-                   copy {
-                        from fileTree(dir: 'src', excludes: ['**/ignore/**'], includes: ['*', '*/*'])
-                        into 'dest'
-                    }
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("cpy").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'root.b',
-                'one/one.a',
-                'one/one.b',
-                'two/two.a',
-                'two/two.b',
-        )
-    }
-
-    @Test public void testCopyFromFileCollection() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """task copy << {
-                   copy {
-                        from files('src')
-                        into 'dest'
-                        exclude '**/ignore/**'
-                        exclude '*/*/*/**'
-                    }
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'root.b',
-                'one/one.a',
-                'one/one.b',
-                'two/two.a',
-                'two/two.b',
-        )
-    }
-
-    @Test public void testCopyFromCompositeFileCollection() {
-        testFile('a.jar').touch()
-
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """
-                configurations { compile }
-                dependencies { compile files('a.jar') }
-                task copy << {
-                   copy {
-                        from files('src2') + fileTree { from 'src'; exclude '**/ignore/**' } + configurations.compile
-                        into 'dest'
-                        include { fte -> fte.relativePath.segments.length < 3 && (fte.file.directory || fte.file.name.contains('a')) }
-                    }
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'one/one.a',
-                'two/two.a',
-                'three/three.a',
-                'a.jar'
-        )
-    }
-
-    @Test public void testCopyWithCopyspec() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """
-                def spec = copySpec {
-                    from 'src'
-                    exclude '**/ignore/**'
-                    include '*/*.a'
-                    into 'subdir'
-                }
-                task copy(type: Copy) {
-                    into 'dest'
-                    with spec
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("copy").run()
-        testFile('dest').assertHasDescendants(
-                'subdir/one/one.a',
-                'subdir/two/two.a'
-        )
-    }
-
-    // can't use TestResources here because Git doesn't support committing empty directories
-    @Test
-    void emptyDirsAreCopiedByDefault() {
-        file("src999", "emptyDir").createDir()
-        file("src999", "yet", "another", "veryEmptyDir").createDir()
-
-        // need to include a file in the copy, otherwise copy task says "no source files"
-        file("src999", "dummy").createFile()
-
-        def buildFile = testFile("build.gradle") <<
-                """
-                task copy(type: Copy) {
-                    from 'src999'
-                    into 'dest'
-                }
-                """
-        usingBuildFile(buildFile).withTasks("copy").run()
-
-        assert file("dest", "emptyDir").isDirectory()
-        assert file("dest", "emptyDir").list().size() == 0
-        assert file("dest", "yet", "another", "veryEmptyDir").isDirectory()
-        assert file("dest", "yet", "another", "veryEmptyDir").list().size() == 0
-    }
-
-    @Test
-    void emptyDirsAreNotCopiedIfCorrespondingOptionIsSetToFalse() {
-        file("src999", "emptyDir").createDir()
-        file("src999", "yet", "another", "veryEmptyDir").createDir()
-
-        // need to include a file in the copy, otherwise copy task says "no source files"
-        file("src999", "dummy").createFile()
-
-        def buildFile = testFile("build.gradle") <<
-                """
-                task copy(type: Copy) {
-                    from 'src999'
-                    into 'dest'
-
-                    includeEmptyDirs = false
-                }
-                """
-        usingBuildFile(buildFile).withTasks("copy").run()
-
-        assert !file("dest", "emptyDir").exists()
-        assert !file("dest", "yet", "another", "veryEmptyDir").exists()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest.groovy
deleted file mode 100644
index a8cc2a9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest.groovy
+++ /dev/null
@@ -1,83 +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.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
-
-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 gradle09rc3 = dist.previousVersion('0.9-rc-3')
-    private final BasicGradleDistribution gradle09 = dist.previousVersion('0.9')
-    private final BasicGradleDistribution gradle091 = dist.previousVersion('0.9.1')
-    private final BasicGradleDistribution gradle092 = dist.previousVersion('0.9.2')
-    private final BasicGradleDistribution gradle10Milestone1 = dist.previousVersion('1.0-milestone-1')
-    private final BasicGradleDistribution gradle10Milestone2 = dist.previousVersion('1.0-milestone-2')
-
-    @Test
-    public void canBuildJavaProject() {
-        dist.testFile('buildSrc/src/main/groovy').assertIsDir()
-
-        // Upgrade and downgrade from previous version to current version and back again
-        eachVersion([gradle08, gradle09rc3, gradle09, gradle091, gradle092, gradle10Milestone1, gradle10Milestone2]) { version ->
-            version.executer().inDirectory(dist.testDir).withTasks('build').run()
-            dist.executer().inDirectory(dist.testDir).withTasks('build').run()
-            version.executer().inDirectory(dist.testDir).withTasks('build').run()
-        }
-    }
-
-    @Test
-    public void canUseWrapperFromPreviousVersionToRunCurrentVersion() {
-        eachVersion([gradle09rc3, gradle09, gradle091, gradle092, gradle10Milestone1, gradle10Milestone2]) { version ->
-            checkWrapperWorksWith(version, dist)
-        }
-    }
-
-    @Test
-    public void canUseWrapperFromCurrentVersionToRunPreviousVersion() {
-        eachVersion([gradle09rc3, gradle09, gradle091, gradle092, gradle10Milestone1, gradle10Milestone2]) { 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/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
new file mode 100755
index 0000000..2f95431
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CustomPluginIntegrationTest.groovy
@@ -0,0 +1,207 @@
+/*
+ * 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.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.ArtifactBuilder
+
+public class CustomPluginIntegrationTest extends AbstractIntegrationSpec {
+    public void "can reference plugin in buildSrc by id"() {
+        given:
+        file('buildSrc/src/main/java/CustomPlugin.java') << '''
+import org.gradle.api.*;
+import org.gradle.api.internal.plugins.DslObject;
+
+public class CustomPlugin implements Plugin<Project> {
+    public void apply(Project p) {
+      new DslObject(p).getExtensions().getExtraProperties().set("prop", "value");
+    }
+}
+'''
+
+        file('buildSrc/src/main/resources/META-INF/gradle-plugins/custom.properties') << '''
+implementation-class=CustomPlugin
+'''
+
+        file('build.gradle') << '''
+apply plugin: 'custom'
+assert 'value' == prop
+task test
+'''
+
+        expect:
+        succeeds('test')
+    }
+
+    public void "can reference plugin in external jar by id"() {
+        given:
+        ArtifactBuilder builder = artifactBuilder()
+        builder.sourceFile('CustomPlugin.java') << '''
+import org.gradle.api.*;
+import org.gradle.api.internal.plugins.DslObject;
+
+public class CustomPlugin implements Plugin<Project> {
+    public void apply(Project p) {
+      new DslObject(p).getExtensions().getExtraProperties().set("prop", "value");
+    }
+}
+'''
+        builder.resourceFile('META-INF/gradle-plugins/custom.properties') << '''
+implementation-class=CustomPlugin
+'''
+        builder.buildJar(file('external.jar'))
+
+        and:
+        file('build.gradle') << '''
+buildscript {
+    dependencies {
+        classpath files('external.jar')
+    }
+}
+apply plugin: 'custom'
+assert 'value' == prop
+task test
+'''
+
+        expect:
+        succeeds('test')
+    }
+
+    public void "loads plugin in correct environment"() {
+        given:
+        def implClassName = 'com.google.common.collect.Multimap'
+        ArtifactBuilder builder = artifactBuilder()
+        builder.sourceFile('CustomPlugin.groovy') << """
+import org.gradle.api.*
+public class CustomPlugin implements Plugin<Project> {
+    public void apply(Project p) {
+        Project.class.classLoader.loadClass('${implClassName}')
+        try {
+            getClass().classLoader.loadClass('${implClassName}')
+            assert false: 'should fail'
+        } catch (ClassNotFoundException e) {
+            // expected
+        }
+        assert Thread.currentThread().contextClassLoader == getClass().classLoader
+        p.task('test')
+    }
+}
+"""
+        builder.resourceFile('META-INF/gradle-plugins/custom.properties') << '''
+implementation-class=CustomPlugin
+'''
+        builder.buildJar(file('external.jar'))
+
+        and:
+        file('build.gradle') << '''
+buildscript {
+    dependencies {
+        classpath files('external.jar')
+    }
+}
+task test
+'''
+
+        expect:
+        succeeds('test')
+    }
+
+    def "can integration test plugin"() {
+        given:
+        file('src/main/groovy/CustomPlugin.groovy') << """
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+class CustomPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.ext.custom = 'value'
+    }
+}
+        """
+
+        file("src/main/resources/META-INF/gradle-plugins/custom.properties") << """
+implementation-class=CustomPlugin
+"""
+
+        file('src/test/groovy/CustomPluginTest.groovy') << """
+import org.junit.Test
+import org.gradle.testfixtures.ProjectBuilder
+class CustomPluginTest {
+    @Test
+    public void test() {
+        def project = ProjectBuilder.builder().build()
+
+        project.apply plugin: 'custom'
+
+        assert project.custom == 'value'
+    }
+}
+"""
+
+        buildFile << """
+apply plugin: 'groovy'
+repositories { mavenCentral() }
+dependencies {
+    compile gradleApi()
+    groovy localGroovy()
+    testCompile 'junit:junit:4.8.2'
+}
+"""
+
+        expect:
+        succeeds('test')
+    }
+
+    def "can use java plugin from custom plugin and its integration tests"() {
+        given:
+        file('src/main/groovy/CustomPlugin.groovy') << """
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+class CustomPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.apply plugin: 'java'
+    }
+}
+        """
+
+        file('src/test/groovy/CustomPluginTest.groovy') << """
+import org.junit.Test
+import org.gradle.testfixtures.ProjectBuilder
+class CustomPluginTest {
+    @Test
+    public void test() {
+        def project = ProjectBuilder.builder().build()
+
+        project.apply plugin: 'java'
+
+        assert project.sourceSets
+    }
+}
+"""
+
+        buildFile << """
+apply plugin: 'groovy'
+repositories { mavenCentral() }
+dependencies {
+    compile gradleApi()
+    groovy localGroovy()
+    testCompile 'junit:junit:4.8.2'
+}
+"""
+
+        expect:
+        succeeds('test')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DependenciesResolveIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DependenciesResolveIntegrationTest.java
deleted file mode 100644
index fdf16a6..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DependenciesResolveIntegrationTest.java
+++ /dev/null
@@ -1,42 +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.integtests;
-
-import org.gradle.integtests.fixtures.GradleDistribution;
-import org.gradle.integtests.fixtures.GradleDistributionExecuter;
-import org.gradle.integtests.fixtures.Sample;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class DependenciesResolveIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution();
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
-    @Rule public final Sample sample = new Sample("dependencies");
-
-    @Test
-    public void testResolve() {
-        dist.requireOwnUserHomeDir();
-
-        // the actual testing is done in the build script.
-        File projectDir = sample.getDir();
-        executer.inDirectory(projectDir).withTasks("test").run();
-    }   
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
index 969f6f0..e9db413 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
@@ -26,14 +26,18 @@ import org.junit.Test
 import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
+import org.gradle.util.PreconditionVerifier
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 
 class DistributionIntegrationTest {
     @Rule public final GradleDistribution dist = new GradleDistribution()
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final PreconditionVerifier preconditionVerifier = new PreconditionVerifier()
     private String version = GradleVersion.current().version
 
     @Test
-    public void binZipContents() {
+    void binZipContents() {
         TestFile binZip = dist.distributionsDir.file("gradle-$version-bin.zip")
         binZip.usingNativeTools().unzipTo(dist.testDir)
         TestFile contentsDir = dist.testDir.file("gradle-$version")
@@ -47,7 +51,7 @@ class DistributionIntegrationTest {
     }
 
     @Test
-    public void allZipContents() {
+    void allZipContents() {
         TestFile binZip = dist.distributionsDir.file("gradle-$version-all.zip")
         binZip.usingNativeTools().unzipTo(dist.testDir)
         TestFile contentsDir = dist.testDir.file("gradle-$version")
@@ -58,7 +62,7 @@ class DistributionIntegrationTest {
         contentsDir.file('src/org/gradle/api/Project.java').assertIsFile()
         contentsDir.file('src/org/gradle/initialization/defaultBuildSourceScript.txt').assertIsFile()
         contentsDir.file('src/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java').assertIsFile()
-        contentsDir.file('src/org/gradle/wrapper/Wrapper.java').assertIsFile()
+        contentsDir.file('src/org/gradle/wrapper/WrapperExecutor.java').assertIsFile()
 
         // Samples
         contentsDir.file('samples/java/quickstart/build.gradle').assertIsFile()
@@ -85,7 +89,7 @@ class DistributionIntegrationTest {
         contentsDir.file('docs/dsl/index.html').assertContents(containsString("<title>Gradle DSL Version ${version}</title>"))
     }
 
-    private def checkMinimalContents(TestFile contentsDir) {
+    private void checkMinimalContents(TestFile contentsDir) {
         // Check it can be executed
         executer.inDirectory(contentsDir).usingExecutable('bin/gradle').withTaskList().run()
 
@@ -97,12 +101,17 @@ class DistributionIntegrationTest {
         contentsDir.file('LICENSE').assertIsFile()
 
         // Libs
+        assertIsGradleJar(contentsDir.file("lib/gradle-cli-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/gradle-core-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/gradle-ui-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/gradle-launcher-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/gradle-tooling-api-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/gradle-wrapper-${version}.jar"))
+        def wrapperJar = contentsDir.file("lib/gradle-wrapper-${version}.jar")
+        assertIsGradleJar(wrapperJar)
+        assert wrapperJar.length() < 20 * 1024; // wrapper needs to be small. Let's check it's smaller than some arbitrary 'small' limit
 
+        // Plugins
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-core-impl-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-plugins-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-ide-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-scala-${version}.jar"))
@@ -113,25 +122,32 @@ class DistributionIntegrationTest {
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-sonar-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-maven-${version}.jar"))
         assertIsGradleJar(contentsDir.file("lib/plugins/gradle-osgi-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-signing-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-cpp-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-ear-${version}.jar"))
 
         // Docs
         contentsDir.file('getting-started.html').assertIsFile()
+        
+        // Jars that must not be shipped
+        assert !contentsDir.file("lib/tools.jar").exists()
+        assert !contentsDir.file("lib/plugins/tools.jar").exists()
     }
 
-    private def assertIsGradleJar(TestFile jar) {
+    private void assertIsGradleJar(TestFile jar) {
         jar.assertIsFile()
         assertThat(jar.manifest.mainAttributes.getValue('Implementation-Version'), equalTo(version))
         assertThat(jar.manifest.mainAttributes.getValue('Implementation-Title'), equalTo('Gradle'))
     }
 
-    @Test
-    public void sourceZipContents() {
+    @Test @Requires(TestPrecondition.NOT_WINDOWS)
+    void sourceZipContents() {
         TestFile srcZip = dist.distributionsDir.file("gradle-$version-src.zip")
         srcZip.usingNativeTools().unzipTo(dist.testDir)
         TestFile contentsDir = dist.testDir.file("gradle-$version")
 
         // Build self using wrapper in source distribution
-        executer.inDirectory(contentsDir).usingExecutable('gradlew').withTasks('binZip').run()
+        executer.withDeprecationChecksDisabled().inDirectory(contentsDir).usingExecutable('gradlew').withTasks('binZip').run()
 
         File binZip = contentsDir.file('build/distributions').listFiles()[0]
         Expand unpack = new Expand()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionLocatorIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionLocatorIntegrationTest.groovy
new file mode 100644
index 0000000..4674ddc
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionLocatorIntegrationTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.util.DistributionLocator
+import org.gradle.util.GradleVersion
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 8/20/11
+ */
+class DistributionLocatorIntegrationTest extends Specification {
+
+    def locator = new DistributionLocator()
+
+    def "locates release versions"() {
+        expect:
+        urlExist(locator.getDistributionFor(GradleVersion.version("0.8")))
+        urlExist(locator.getDistributionFor(GradleVersion.version("0.9.1")))
+        urlExist(locator.getDistributionFor(GradleVersion.version("1.0-milestone-3")))
+    }
+
+    def "locates snapshot versions"() {
+        expect:
+        urlExist(locator.getDistributionFor(GradleVersion.version("1.0-milestone-7-20111216000006+0100")))
+    }
+
+    void urlExist(URI url) {
+        HttpURLConnection connection = url.toURL().openConnection()
+        connection.requestMethod = "HEAD"
+        connection.connect()
+        assert connection.responseCode == 200
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
index a0656ac..e19b256 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
@@ -13,9 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
-
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.GradleDistribution
@@ -23,6 +20,7 @@ import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.util.TestFile
 import org.junit.Rule
 import org.junit.Test
+import spock.lang.Issue
 
 class DynamicObjectIntegrationTest {
     @Rule public final GradleDistribution dist = new GradleDistribution()
@@ -33,19 +31,30 @@ class DynamicObjectIntegrationTest {
         TestFile testDir = dist.getTestDir();
         testDir.file("settings.gradle").writelns("include 'child'");
         testDir.file("build.gradle").writelns(
-                "rootProperty = 'root'",
-                "sharedProperty = 'ignore me'",
+                "ext.rootProperty = 'root'",
+                "ext.sharedProperty = 'ignore me'",
+                "ext.property = 'value'",
                 "convention.plugins.test = new ConventionBean()",
                 "task rootTask",
                 "task testTask",
                 "class ConventionBean { def getConventionProperty() { 'convention' } }"
         );
         testDir.file("child/build.gradle").writelns(
-                "childProperty = 'child'",
-                "sharedProperty = 'shared'",
+                "ext.childProperty = 'child'",
+                "ext.sharedProperty = 'shared'",
                 "task testTask << {",
                 "  new Reporter().checkProperties(project)",
                 "}",
+                "assert 'root' == rootProperty",
+                "assert 'root' == property('rootProperty')",
+                "assert 'root' == properties.rootProperty",
+                "assert 'child' == childProperty",
+                "assert 'child' == property('childProperty')",
+                "assert 'child' == properties.childProperty",
+                "assert 'shared' == sharedProperty",
+                "assert 'shared' == property('sharedProperty')",
+                "assert 'shared' == properties.sharedProperty",
+                "assert 'convention' == conventionProperty",
                 // Use a separate class, to isolate Project from the script
                 "class Reporter {",
                 "  def checkProperties(object) {",
@@ -53,6 +62,7 @@ class DynamicObjectIntegrationTest {
                 "    assert 'child' == object.childProperty",
                 "    assert 'shared' == object.sharedProperty",
                 "    assert 'convention' == object.conventionProperty",
+                "    assert 'value' == object.property",
                 "    assert ':child:testTask' == object.testTask.path",
                 "    try { object.rootTask; fail() } catch (MissingPropertyException e) { }",
                 "  }",
@@ -97,74 +107,176 @@ class DynamicObjectIntegrationTest {
     }
 
     @Test
+    public void canAddMixinsToProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+convention.plugins.test = new ConventionBean()
+
+assert conventionProperty == 'convention'
+assert conventionMethod('value') == '[value]'
+
+class ConventionBean {
+    def getConventionProperty() { 'convention' }
+    def conventionMethod(String value) { "[$value]" }
+}
+'''
+
+        executer.inDirectory(testDir).run();
+    }
+
+    @Test
+    public void canAddExtensionsToProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+extensions.test = new ExtensionBean()
+
+assert test instanceof ExtensionBean
+test { it ->
+    assert it == project.test
+}
+class ExtensionBean {
+}
+'''
+
+        executer.inDirectory(testDir).run();
+    }
+
+    @Test
+    public void canAddPropertiesToProjectUsingGradlePropertiesFile() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("settings.gradle").writelns("include 'child'");
+        testDir.file("gradle.properties") << '''
+global=some value
+'''
+        testDir.file("build.gradle") << '''
+assert 'some value' == global
+assert hasProperty('global')
+assert 'some value' == property('global')
+assert 'some value' == properties.global
+assert 'some value' == project.global
+assert project.hasProperty('global')
+assert 'some value' == project.property('global')
+assert 'some value' == project.properties.global
+'''
+        testDir.file("child/gradle.properties") << '''
+global=overridden value
+'''
+        testDir.file("child/build.gradle") << '''
+assert 'overridden value' == global
+'''
+
+        executer.inDirectory(testDir).run();
+    }
+
+    @Test
     public void canAddDynamicPropertiesToCoreDomainObjects() {
         TestFile testDir = dist.getTestDir();
         testDir.file('build.gradle') << '''
+            class GroovyTask extends DefaultTask { }
+
+            task defaultTask {
+                ext.custom = 'value'
+            }
+            task javaTask(type: Copy) {
+                ext.custom = 'value'
+            }
+            task groovyTask(type: GroovyTask) {
+                ext.custom = 'value'
+            }
+            configurations {
+                test {
+                    ext.custom = 'value'
+                }
+            }
+            dependencies {
+                test('::name:') {
+                    ext.custom = 'value';
+                }
+                test(module('::other')) {
+                    ext.custom = 'value';
+                }
+                test(project(':')) {
+                    ext.custom = 'value';
+                }
+                test(files('src')) {
+                    ext.custom = 'value';
+                }
+            }
+            repositories {
+                ext.custom = 'repository'
+            }
+            defaultTask.custom = 'another value'
+            javaTask.custom = 'another value'
+            groovyTask.custom = 'another value'
+            assert !project.hasProperty('custom')
+            assert defaultTask.hasProperty('custom')
+            assert defaultTask.custom == 'another value'
+            assert javaTask.custom == 'another value'
+            assert groovyTask.custom == 'another value'
+            assert configurations.test.hasProperty('custom')
+            assert configurations.test.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'
+            repositories {
+                assert custom == 'repository'
+            }
+'''
+
+        executer.inDirectory(testDir).withTasks("defaultTask").run();
+    }
+
+    @Test
+    public void canAddMixInsToCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
             class Extension { def doStuff() { 'method' } }
             class GroovyTask extends DefaultTask { }
 
             task defaultTask {
-                custom = 'value'
                 convention.plugins.custom = new Extension()
             }
             task javaTask(type: Copy) {
-                custom = 'value'
                 convention.plugins.custom = new Extension()
             }
             task groovyTask(type: GroovyTask) {
-                custom = 'value'
                 convention.plugins.custom = new Extension()
             }
             configurations {
                 test {
-                    custom = 'value'
                     convention.plugins.custom = new Extension()
                 }
             }
             dependencies {
                 test('::name:') {
-                    custom = 'value';
                     convention.plugins.custom = new Extension()
                 }
                 test(module('::other')) {
-                    custom = 'value';
                     convention.plugins.custom = new Extension()
                 }
                 test(project(':')) {
-                    custom = 'value';
                     convention.plugins.custom = new Extension()
                 }
                 test(files('src')) {
-                    custom = 'value';
                     convention.plugins.custom = new Extension()
                 }
             }
             repositories {
-                custom = 'repository'
                 convention.plugins.custom = new Extension()
             }
-            defaultTask.custom = 'another value'
-            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.hasProperty('custom')
-                assert it.custom == 'value'
-                assert it.getProperty('custom') == 'value'
+                assert it.doStuff() == 'method'
             }
-            assert repositories.hasProperty('custom')
-            assert repositories.custom == 'repository'
             assert repositories.doStuff() == 'method'
             repositories {
-                assert custom == 'repository'
                 assert doStuff() == 'method'
             }
 '''
@@ -173,12 +285,117 @@ class DynamicObjectIntegrationTest {
     }
 
     @Test
+    public void canAddExtensionsToCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class Extension { def doStuff() { 'method' } }
+            class GroovyTask extends DefaultTask { }
+
+            task defaultTask {
+                extensions.test = new Extension()
+            }
+            task javaTask(type: Copy) {
+                extensions.test = new Extension()
+            }
+            task groovyTask(type: GroovyTask) {
+                extensions.test = new Extension()
+            }
+            configurations {
+                test {
+                    extensions.test = new Extension()
+                }
+            }
+            dependencies {
+                test('::name:') {
+                    extensions.test = new Extension()
+                }
+                test(module('::other')) {
+                    extensions.test = new Extension()
+                }
+                test(project(':')) {
+                    extensions.test = new Extension()
+                }
+                test(files('src')) {
+                    extensions.test = new Extension()
+                }
+            }
+            repositories {
+                extensions.test = new Extension()
+            }
+            assert defaultTask.test instanceof Extension
+            assert javaTask.test instanceof Extension
+            assert groovyTask.test instanceof Extension
+            assert configurations.test.test instanceof Extension
+            configurations.test.dependencies.each {
+                assert it.test instanceof Extension
+            }
+            assert repositories.test instanceof Extension
+            repositories {
+                assert test instanceof Extension
+            }
+'''
+
+        executer.inDirectory(testDir).withTasks("defaultTask").run();
+    }
+
+    @Test
+    public void mixesDslMethodsIntoCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class GroovyTask extends DefaultTask {
+                def String prop
+                void doStuff(Action<Task> action) { action.execute(this) }
+            }
+            tasks.withType(GroovyTask) { conventionMapping.prop = { '[default]' } }
+            task test(type: GroovyTask)
+            assert test.prop == '[default]'
+            test {
+                description 'does something'
+                prop 'value'
+            }
+            assert test.description == 'does something'
+            assert test.prop == 'value'
+            test.doStuff {
+                prop = 'new value'
+            }
+            assert test.prop == 'new value'
+'''
+
+        executer.inDirectory(testDir).withTasks("test").run();
+    }
+
+    @Test
+    void canAddExtensionsToDynamicExtensions() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class Extension {
+                String name
+                Extension(String name) {
+                    this.name = name
+                }
+            }
+
+            project.extensions.create("l1", Extension, "l1")
+            project.l1.extensions.create("l2", Extension, "l2")
+            project.l1.l2.extensions.create("l3", Extension, "l3")
+
+            task test << {
+                assert project.l1.name == "l1"
+                assert project.l1.l2.name == "l2"
+                assert project.l1.l2.l3.name == "l3"
+            }
+        '''
+
+        executer.inDirectory(testDir).withTasks("test").run();
+    }
+
+    @Test
     public void canInjectMethodsFromParentProject() {
         TestFile testDir = dist.getTestDir();
         testDir.file("settings.gradle").writelns("include 'child'");
         testDir.file("build.gradle").writelns(
                 "subprojects {",
-                "  injectedMethod = { project.name }",
+                "  ext.injectedMethod = { project.name }",
                 "}"
         );
         testDir.file("child/build.gradle").writelns(
@@ -189,4 +406,49 @@ class DynamicObjectIntegrationTest {
 
         executer.inDirectory(testDir).withTasks("testTask").run();
     }
+    
+    @Test void canAddNewPropertiesViaTheAdhocNamespace() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("build.gradle") << """
+            ext {
+                add "p1", 1
+            }
+            assert p1 == 1
+            p2 = 2
+            assert ext.p2 = 2
+            
+            task run << {
+                ext {
+                    add "p1", 1
+                }
+                assert p1 == 1
+                p2 = 2
+                assert ext.p2 = 2            
+            }        
+        """
+        
+        executer.withTasks("run")
+    }
+
+    @Issue("GRADLE-2163")
+    @Test void canDecorateBooleanPrimitiveProperties() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("build.gradle") << """
+            class CustomBean {
+                boolean b
+            }
+
+            // best way to decorate right now
+            extensions.create('bean', CustomBean)
+
+            task run << {
+                assert bean.b == false
+                bean.b.conventionMapping.map('b') { true }
+                assert bean.b == true
+            }
+        """
+
+        executer.withTasks("run")
+
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
index 2651e25..7d2030c 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
@@ -20,7 +20,7 @@ package org.gradle.integtests
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class ExecIntegrationTest extends AbstractIntegrationTest {
     @Rule
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalPluginIntegrationTest.groovy
deleted file mode 100644
index beabf67..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalPluginIntegrationTest.groovy
+++ /dev/null
@@ -1,69 +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.gradle.integtests.fixtures.ArtifactBuilder
-import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
-
-public class ExternalPluginIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void canReferencePluginInBuildSrcProjectById() {
-        testFile('buildSrc/src/main/java/CustomPlugin.java') << '''
-import org.gradle.api.*;
-public class CustomPlugin implements Plugin<Project> {
-    public void apply(Project p) { p.setProperty("prop", "value"); }
-}
-'''
-        testFile('buildSrc/src/main/resources/META-INF/gradle-plugins/custom.properties') << '''
-implementation-class=CustomPlugin
-'''
-
-        testFile('build.gradle') << '''
-apply plugin: 'custom'
-assert 'value' == prop
-task test
-'''
-        inTestDirectory().withTasks('test').run()
-    }
-    
-    @Test
-    public void canReferencePluginInExternalJarById() {
-        ArtifactBuilder builder = artifactBuilder()
-        builder.sourceFile('CustomPlugin.java') << '''
-import org.gradle.api.*;
-public class CustomPlugin implements Plugin<Project> {
-    public void apply(Project p) { p.setProperty("prop", "value"); }
-}
-'''
-        builder.resourceFile('META-INF/gradle-plugins/custom.properties') << '''
-implementation-class=CustomPlugin
-'''
-        builder.buildJar(testFile('external.jar'))
-
-        testFile('build.gradle') << '''
-buildscript {
-    dependencies {
-        classpath files('external.jar')
-    }
-}
-apply plugin: 'custom'
-assert 'value' == prop
-task test
-'''
-        inTestDirectory().withTasks('test').run()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
old mode 100644
new mode 100755
index 4ed8fe5..38bce18
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
@@ -19,7 +19,7 @@ import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.util.TestFile
 import org.junit.Test
 import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class ExternalScriptErrorIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
old mode 100644
new mode 100755
index b5e1593..4ae6078
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
@@ -17,15 +17,16 @@
 
 package org.gradle.integtests
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.ArtifactBuilder
 import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.integtests.fixtures.HttpServer
 import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
 import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+import static org.junit.Assert.assertThat
 
 public class ExternalScriptExecutionIntegrationTest extends AbstractIntegrationTest {
     @Rule
@@ -36,25 +37,34 @@ public class ExternalScriptExecutionIntegrationTest extends AbstractIntegrationT
         createExternalJar()
         createBuildSrc()
 
+        def implClassName = 'com.google.common.collect.Multimap'
         TestFile externalScript = testFile('external.gradle')
         externalScript << """
-            buildscript {
-                dependencies { classpath files('repo/test-1.3.jar') }
-            }
-            new org.gradle.test.BuildClass()
-            new BuildSrcClass()
-            println 'quiet message'
-            captureStandardOutput(LogLevel.ERROR)
-            println 'error message'
-            assert project != null
-            assert "${externalScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sourceFile as String
-            assert "${externalScript.toURI()}" == buildscript.sourceURI as String
-            assert buildscript.classLoader == getClass().classLoader.parent
-            assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-            assert gradle.scriptClassLoader == buildscript.classLoader.parent
-            assert project.buildscript.classLoader != buildscript.classLoader
-            task doStuff
-            someProp = 'value'
+buildscript {
+    dependencies { classpath files('repo/test-1.3.jar') }
+}
+new org.gradle.test.BuildClass()
+new BuildSrcClass()
+println 'quiet message'
+logging.captureStandardOutput(LogLevel.ERROR)
+println 'error message'
+assert project != null
+assert "${externalScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sourceFile as String
+assert "${externalScript.toURI()}" == buildscript.sourceURI as String
+assert buildscript.classLoader == getClass().classLoader.parent
+assert buildscript.classLoader == Thread.currentThread().contextClassLoader
+assert gradle.scriptClassLoader == buildscript.classLoader.parent
+assert project.buildscript.classLoader != buildscript.classLoader
+Gradle.class.classLoader.loadClass('${implClassName}')
+try {
+    buildscript.classLoader.loadClass('${implClassName}')
+    assert false: 'should fail'
+} catch (ClassNotFoundException e) {
+    // expected
+}
+
+task doStuff
+ext.someProp = 'value'
 """
         testFile('build.gradle') << '''
 apply { from 'external.gradle' }
@@ -74,7 +84,7 @@ assert 'value' == someProp
 
         testFile('external.gradle') << '''
 println 'quiet message'
-captureStandardOutput(LogLevel.ERROR)
+getLogging().captureStandardOutput(LogLevel.ERROR)
 println 'error message'
 new BuildSrcClass()
 assert 'doStuff' == name
@@ -82,7 +92,7 @@ assert buildscript.classLoader == getClass().classLoader.parent
 assert buildscript.classLoader == Thread.currentThread().contextClassLoader
 assert project.gradle.scriptClassLoader == buildscript.classLoader.parent
 assert project.buildscript.classLoader != buildscript.classLoader
-someProp = 'value'
+ext.someProp = 'value'
 '''
         testFile('build.gradle') << '''
 task doStuff
@@ -156,7 +166,7 @@ class ListenerImpl extends BuildAdapter {
     @Test
     public void cachesScriptClassForAGivenScript() {
         testFile('settings.gradle') << 'include \'a\', \'b\''
-        testFile('external.gradle') << 'appliedScript = this'
+        testFile('external.gradle') << 'ext.appliedScript = this'
         testFile('build.gradle') << '''
 allprojects {
    apply from: "$rootDir/external.gradle"
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/FileTreeCopyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/FileTreeCopyIntegrationTest.groovy
deleted file mode 100644
index ebd4301..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/FileTreeCopyIntegrationTest.groovy
+++ /dev/null
@@ -1,83 +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.integtests
-
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
-
-public class FileTreeCopyIntegrationTest extends AbstractIntegrationTest {
-    @Rule
-    public final TestResources resources = new TestResources("copyTestResources")
-
-    @Test public void testCopyWithClosure() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """task cpy << {
-                   fileTree {
-                      from 'src'
-                      exclude '**/ignore/**'
-                   }.copy { into 'dest'}
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("cpy").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'root.b',
-                'one/one.a',
-                'one/one.b',
-                'one/sub/onesub.a',
-                'one/sub/onesub.b',
-                'two/two.a',
-                'two/two.b',
-        )
-    }
-
-    @Test public void testCopyWithMap() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """task cpy << {
-                   fileTree(dir:'src', excludes:['**/ignore/**', '**/sub/**']).copy { into 'dest'}
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("cpy").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'root.b',
-                'one/one.a',
-                'one/one.b',
-                'two/two.a',
-                'two/two.b',
-        )
-    }
-
-    @Test public void testCopyFluent() {
-        TestFile buildFile = testFile("build.gradle").writelns(
-                """task cpy << {
-                   fileTree(dir:'src').exclude('**/ignore/**', '**/sub/*.?').copy { into 'dest' }
-                }"""
-        )
-        usingBuildFile(buildFile).withTasks("cpy").run()
-        testFile('dest').assertHasDescendants(
-                'root.a',
-                'root.b',
-                'one/one.a',
-                'one/one.b',
-                'two/two.a',
-                'two/two.b',
-        )
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyPluginGoodBehaviourTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..a5c63b1
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.WellBehavedPluginTest
+
+class GroovyPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "build"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy
new file mode 100644
index 0000000..6b6fdd2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.AbstractIntegrationSpec
+
+class GroovyProjectIntegrationTest extends AbstractIntegrationSpec {
+    
+    def handlesJavaSourceOnly() {
+        given:
+        buildFile << "apply plugin: 'groovy'"
+
+        and:
+        file("src/main/java/somepackage/SomeClass.java") << "public class SomeClass { }"
+        file("settings.gradle") << "rootProject.name='javaOnly'"
+        
+        when:
+        run "build"
+
+        then:
+        file("build/libs/javaOnly.jar").exists()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.java
deleted file mode 100644
index 6aca413..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.java
+++ /dev/null
@@ -1,38 +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.integtests;
-
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
-import org.junit.Test;
-
-public class GroovyProjectIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void handlesEmptyProject() {
-        testFile("build.gradle").writelns(
-                "apply plugin: 'groovy'"
-        );
-        inTestDirectory().withTasks("build").run();
-    }
-
-    @Test
-    public void handlesJavaSourceOnly() {
-        testFile("src/main/java/somepackage/SomeClass.java").writelns("public class SomeClass { }");
-        testFile("build.gradle").writelns("apply plugin: 'groovy'");
-        testFile("settings.gradle").write("rootProject.name='javaOnly'");
-        inTestDirectory().withTasks("build").run();
-        testFile("build/libs/javaOnly.jar").assertExists();
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
index 5921f1a..29cac1d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
@@ -18,13 +18,13 @@
 
 package org.gradle.integtests
 
-import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.util.TestFile
 import org.junit.Rule
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
 
 class IncrementalBuildIntegrationTest extends AbstractIntegrationTest {
     @Rule public final TestResources resource = new TestResources()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest.groovy
deleted file mode 100644
index 9ba3fd6..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest.groovy
+++ /dev/null
@@ -1,53 +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.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.junit.Test
-import org.gradle.integtests.fixtures.ExecutionFailure
-
-class IncrementalGroovyCompileIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
-
-    @Test
-    public void recompilesSourceWhenPropertiesChange() {
-        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava')
-
-        distribution.testFile('build.gradle').text += '''
-            compileGroovy.options.debug = false
-'''
-
-        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava')
-
-        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava', ':compileGroovy')
-    }
-
-    @Test
-    public void recompilesDependentClasses() {
-        executer.withTasks("classes").run();
-
-        // Update interface, compile should fail
-        distribution.testFile('src/main/groovy/IPerson.groovy').assertIsFile().copyFrom(distribution.testFile('NewIPerson.groovy'))
-
-        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
-        failure.assertHasDescription("Execution failed for task ':compileGroovy'.");
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaCompileIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaCompileIntegrationTest.groovy
deleted file mode 100644
index 9ee05ab..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaCompileIntegrationTest.groovy
+++ /dev/null
@@ -1,121 +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.Rule
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Test
-import org.gradle.integtests.fixtures.ExecutionFailure
-
-class IncrementalJavaCompileIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
-
-    @Test
-    public void recompilesSourceWhenPropertiesChange() {
-        executer.withTasks('compileJava').run().assertTasksSkipped()
-
-        distribution.testFile('build.gradle').text += '''
-            sourceCompatibility = 1.4
-'''
-
-        executer.withTasks('compileJava').run().assertTasksSkipped()
-
-        distribution.testFile('build.gradle').text += '''
-            compileJava.options.debug = false
-'''
-
-        executer.withTasks('compileJava').run().assertTasksSkipped()
-
-        executer.withTasks('compileJava').run().assertTasksSkipped(':compileJava')
-    }
-
-    @Test
-    public void recompilesDependentClasses() {
-        executer.withTasks("classes").run();
-
-        // Update interface, compile should fail
-        distribution.testFile('src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
-        
-        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
-        failure.assertHasDescription("Execution failed for task ':compileJava'.");
-    }
-
-    @Test
-    public void recompilesDependentClassesAcrossProjectBoundaries() {
-        executer.withTasks("app:classes").run();
-
-        // Update interface, compile should fail
-        distribution.testFile('lib/src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
-
-        ExecutionFailure failure = executer.withTasks("app:classes").runWithFailure();
-        failure.assertHasDescription("Execution failed for task ':app:compileJava'.");
-    }
-
-    @Test
-    public void recompilesDependentClassesWhenUsingAntDepend() {
-        distribution.testFile("build.gradle").writelns(
-                "apply plugin: 'java'",
-                "compileJava.options.depend()"
-        );
-        writeShortInterface();
-        writeTestClass();
-
-        executer.withTasks("classes").run();
-
-        // file system time stamp may not see change without this wait
-        Thread.sleep(1000L);
-
-        // Update interface, compile should fail because depend deletes old class
-        writeLongInterface();
-        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
-        failure.assertHasDescription("Execution failed for task ':compileJava'.");
-
-        // assert that dependency caching is on
-        distribution.testFile("build/dependency-cache/dependencies.txt").assertExists();
-    }
-
-    private void writeShortInterface() {
-        distribution.testFile("src/main/java/IPerson.java").writelns(
-                "interface IPerson {",
-                "    String getName();",
-                "}"
-        );
-    }
-
-    private void writeLongInterface() {
-        distribution.testFile("src/main/java/IPerson.java").writelns(
-                "interface IPerson {",
-                "    String getName();",
-                "    String getAddress();",
-                "}"
-        );
-    }
-
-    private void writeTestClass() {
-        distribution.testFile("src/main/java/Person.java").writelns(
-                "public class Person implements IPerson {",
-                "    private final String name = \"never changes\";",
-                "    public String getName() {",
-                "        return name;\n" +
-                "    }",
-                "}"
-        );
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
index cf63d75..1c984ad 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
@@ -31,17 +31,20 @@ class IncrementalJavaProjectBuildIntegrationTest {
         distribution.testFile('src/main/resources/org/gradle/resource.txt').createFile()
 
         executer.withTasks('classes').run()
-        distribution.testFile('build/classes/main').assertHasDescendants('org/gradle/resource.txt')
+        distribution.testFile('build/resources/main').assertHasDescendants('org/gradle/resource.txt')
 
         distribution.testFile('src/main/resources/org/gradle/resource.txt').assertIsFile().delete()
         distribution.testFile('src/main/resources/org/gradle/resource2.txt').createFile()
 
         executer.withTasks('classes').run()
-        distribution.testFile('build/classes/main').assertHasDescendants('org/gradle/resource2.txt')
+        distribution.testFile('build/resources/main').assertHasDescendants('org/gradle/resource2.txt')
     }
 
     @Test
     public void doesNotRebuildJarIfSourceHasNotChanged() {
+        // Use own home dir so we don't blast the shared one when we run with -C rebuild
+        distribution.requireOwnUserHomeDir()
+
         distribution.testFile("src/main/java/BuildClass.java") << 'public class BuildClass { }'
         distribution.testFile("build.gradle") << "apply plugin: 'java'"
         distribution.testFile("settings.gradle") << "rootProject.name = 'project'"
@@ -56,7 +59,7 @@ class IncrementalJavaProjectBuildIntegrationTest {
 
         jar.assertHasNotChangedSince(snapshot);
 
-        executer.withArguments("-Crebuild").withTasks("jar").run();
+        executer.withArguments("--rerun-tasks").withTasks("jar").run();
 
         jar.assertHasChangedSince(snapshot);
         snapshot = jar.snapshot();
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalScalaCompileIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalScalaCompileIntegrationTest.groovy
deleted file mode 100644
index b31a2ff..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalScalaCompileIntegrationTest.groovy
+++ /dev/null
@@ -1,53 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.ExecutionFailure
-
-class IncrementalScalaCompileIntegrationTest {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
-
-    @Test
-    public void recompilesSourceWhenPropertiesChange() {
-        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
-
-        distribution.testFile('build.gradle').text += '''
-            compileScala.options.debug = false
-'''
-
-        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
-
-        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava', ':compileScala')
-    }
-
-    @Test
-    public void recompilesDependentClasses() {
-        executer.withTasks("classes").run();
-
-        // Update interface, compile should fail
-        distribution.testFile('src/main/scala/IPerson.scala').assertIsFile().copyFrom(distribution.testFile('NewIPerson.scala'))
-
-        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
-        failure.assertHasDescription("Execution failed for task ':compileScala'.");
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
index c82822c..2fe87e9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
@@ -15,15 +15,12 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.TestResources
 import org.junit.Assert
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.testng.TestNGExecutionResult
+import org.gradle.integtests.fixtures.*
+import static org.hamcrest.Matchers.startsWith
 
 class IncrementalTestIntegrationTest {
     @Rule public final GradleDistribution distribution = new GradleDistribution()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
index 3204b77..0b7dffc 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.integtests;
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
 import org.gradle.util.TestFile;
 import org.junit.Test;
 
@@ -32,7 +32,7 @@ public class InitScriptErrorIntegrationTest extends AbstractIntegrationTest {
         failure.assertHasFileName(String.format("Initialization script '%s'", initScript));
         failure.assertHasLineNumber(2);
         failure.assertHasDescription("A problem occurred evaluating initialization script.");
-        failure.assertHasCause("No signature of method: org.gradle.invocation.DefaultGradle.createTakk() is applicable for argument types: (java.lang.String) values: [do-stuff]");
+        failure.assertHasCause("Could not find method createTakk() for arguments [do-stuff] on build.");
     }
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
index 594f7f5..853d59d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
@@ -15,55 +15,100 @@
  */
 package org.gradle.integtests
 
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.ArtifactBuilder
 import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.util.TestFile
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
-
-class InitScriptExecutionIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void executesInitScriptWithCorrectEnvironment() {
+
+class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
+    def "executes init.gradle from user home dir"() {
+        given:
+        distribution.requireOwnUserHomeDir()
+        
+        and:
+        distribution.userHomeDir.file('init.gradle') << 'println "greetings from user home"'
+
+        when:
+        run()
+
+        then:
+        output.contains("greetings from user home")
+    }
+
+    def "executes init scripts from init.d directory in user home dir in alphabetical order"() {
+        given:
+        distribution.requireOwnUserHomeDir()
+
+        and:
+        distribution.userHomeDir.file('init.d/a.gradle') << 'println "init #a#"'
+        distribution.userHomeDir.file('init.d/b.gradle') << 'println "init #b#"'
+        distribution.userHomeDir.file('init.d/c.gradle') << 'println "init #c#"'
+
+        when:
+        run()
+
+        then:
+        def a = output.indexOf('init #a#') 
+        def b = output.indexOf('init #b#') 
+        def c = output.indexOf('init #c#') 
+        a < b
+        b < c
+    }
+
+    def "executes init script with correct environment"() {
+        given:
+        def implClassName = 'com.google.common.collect.Multimap'
         createExternalJar();
 
-        TestFile initScript = testFile('init.gradle')
-        initScript << '''
+        and:
+        TestFile initScript = file('init.gradle')
+        initScript << """
 initscript {
     dependencies { classpath files('repo/test-1.3.jar') }
 }
 new org.gradle.test.BuildClass()
 println 'quiet message'
-captureStandardOutput(LogLevel.ERROR)
+logging.captureStandardOutput(LogLevel.ERROR)
 println 'error message'
 assert gradle != null
 assert initscript.classLoader == getClass().classLoader.parent
 assert initscript.classLoader == Thread.currentThread().contextClassLoader
 assert scriptClassLoader == initscript.classLoader.parent
-assert Gradle.class.classLoader == scriptClassLoader.parent.parent
-'''
-        testFile('build.gradle') << 'task doStuff'
+Gradle.class.classLoader.loadClass('${implClassName}')
+try {
+    initscript.classLoader.loadClass('${implClassName}')
+    assert false: 'should fail'
+} catch (ClassNotFoundException e) {
+    // expected
+}
+"""
 
-        ExecutionResult result = inTestDirectory().usingInitScript(initScript).withTasks('doStuff').run()
-        assertThat(result.output, containsString('quiet message'))
-        assertThat(result.output, not(containsString('error message')))
-        assertThat(result.error, containsString('error message'))
-        assertThat(result.error, not(containsString('quiet message')))
+        and:
+        buildFile << 'task doStuff'
+
+        when:
+        ExecutionResult result = executer.usingInitScript(initScript).withTasks('doStuff').run()
+
+        then:
+        result.output.contains('quiet message')
+        !result.output.contains('error message')
+        result.error.contains('error message')
+        !result.error.contains('quiet message')
     }
 
-    @Test
-    public void eachScriptHasIndependentClassLoader() {
+    def "each init script has independent ClassLoader"() {
+        given:
         createExternalJar()
 
-        TestFile initScript1 = testFile('init1.gradle')
+        and:
+        TestFile initScript1 = file('init1.gradle')
         initScript1 << '''
 initscript {
     dependencies { classpath files('repo/test-1.3.jar') }
 }
 new org.gradle.test.BuildClass()
 '''
-        TestFile initScript2 = testFile('init2.gradle')
+        TestFile initScript2 = file('init2.gradle')
         initScript2 << '''
 try {
     Class.forName('org.gradle.test.BuildClass')
@@ -72,9 +117,35 @@ try {
 }
 '''
 
-        testFile('build.gradle') << 'task doStuff'
+        buildFile << 'task doStuff'
+
+        when:
+        executer.usingInitScript(initScript1).usingInitScript(initScript2)
+
+        then:
+        notThrown(Throwable)
+    }
+    
+    def "init script can inject configuration into the root project and all projects"() {
+        given:
+        settingsFile << "include 'a', 'b'"
+
+        and:
+        file("init.gradle") << """
+allprojects {
+    task worker
+}
+rootProject {
+    task root(dependsOn: allprojects*.worker)
+}
+        """
+        
+        when:
+        executer.withArguments("-I", "init.gradle")
+        run "root"
 
-       inTestDirectory().usingInitScript(initScript1).usingInitScript(initScript2)
+        then:
+        executedTasks == [':worker', ':a:worker', ':b:worker', ':root']
     }
 
     private def createExternalJar() {
@@ -83,6 +154,6 @@ try {
             package org.gradle.test;
             public class BuildClass { }
 '''
-        builder.buildJar(testFile("repo/test-1.3.jar"))
+        builder.buildJar(file("repo/test-1.3.jar"))
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IvyPublishIntegrationTest.groovy
deleted file mode 100644
index da67faa..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IvyPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2011 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.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.HttpServer
-import org.hamcrest.Matchers
-import org.junit.Rule
-import org.junit.Test
-
-public class IvyPublishIntegrationTest {
-    @Rule
-    public final GradleDistribution dist = new GradleDistribution()
-    @Rule
-    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule
-    public final HttpServer server = new HttpServer()
-
-    @Test
-    public void canPublishUsingAnonymousHttp() {
-        server.start()
-
-        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
-        dist.testFile("build.gradle") << """
-apply plugin: 'java'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    repositories {
-        ivy {
-            name = 'gradleReleases'
-            artifactPattern "http://localhost:${server.port}/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        }
-    }
-}
-"""
-        def uploadedJar = dist.testFile('uploaded.jar')
-        def uploadedIvy = dist.testFile('uploaded.xml')
-        server.expectPut('/org.gradle/publish/2/publish-2.jar', uploadedJar)
-        server.expectPut('/org.gradle/publish/2/ivy-2.xml', uploadedIvy)
-
-        executer.withTasks("uploadArchives").run()
-
-        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
-        uploadedIvy.assertIsFile()
-    }
-
-    @Test
-    public void canPublishUsingAuthenticatedHttp() {
-        server.start()
-
-        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
-        dist.testFile("build.gradle") << """
-apply plugin: 'java'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    repositories {
-        ivy {
-            name = 'gradleReleases'
-            userName = 'user'
-            password = 'password'
-            realm = 'test'
-            artifactPattern "http://localhost:${server.port}/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        }
-    }
-}
-"""
-
-        def uploadedJar = dist.testFile('uploaded.jar')
-        def uploadedIvy = dist.testFile('uploaded.xml')
-        server.expectPut('/org.gradle/publish/2/publish-2.jar', 'user', 'password', uploadedJar)
-        server.expectPut('/org.gradle/publish/2/ivy-2.xml', 'user', 'password', uploadedIvy)
-
-        executer.withTasks("uploadArchives").run()
-
-        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
-        uploadedIvy.assertIsFile()
-    }
-
-    @Test
-    public void reportsFailedHttpPublish() {
-        server.start()
-
-        dist.testFile("build.gradle") << """
-apply plugin: 'java'
-uploadArchives {
-    repositories {
-        ivy {
-            name = 'gradleReleases'
-            artifactPattern "http://localhost:${server.port}/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        }
-    }
-}
-"""
-
-        def result = executer.withTasks("uploadArchives").runWithFailure()
-        result.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        result.assertHasCause('Could not publish configurations [configuration \':archives\'].')
-        result.assertThatCause(Matchers.containsString('Received status code 404 from server: Not Found'))
-
-        server.stop()
-
-        result = executer.withTasks("uploadArchives").runWithFailure()
-        result.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        result.assertHasCause('Could not publish configurations [configuration \':archives\'].')
-        result.assertHasCause('java.net.ConnectException: Connection refused')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JUnitIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JUnitIntegrationTest.groovy
deleted file mode 100644
index 656dc5a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JUnitIntegrationTest.groovy
+++ /dev/null
@@ -1,404 +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.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.gradle.integtests.fixtures.*
-import static org.gradle.util.Matchers.*
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-public class JUnitIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final TestResources resources = new TestResources()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-
-    @Test
-    public void executesTestsInCorrectEnvironment() {
-        TestFile testDir = dist.testDir;
-        executer.withTasks('build').run();
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
-        result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.OtherTest')
-        result.testClass('org.gradle.OkTest').assertTestPassed('ok')
-        result.testClass('org.gradle.OkTest').assertStdout(containsString('This is test stdout'))
-        result.testClass('org.gradle.OkTest').assertStdout(containsString('no EOL'))
-        result.testClass('org.gradle.OkTest').assertStdout(containsString('class loaded'))
-        result.testClass('org.gradle.OkTest').assertStdout(containsString('test constructed'))
-        result.testClass('org.gradle.OkTest').assertStdout(containsString('stdout from another thread'))
-        result.testClass('org.gradle.OkTest').assertStderr(containsString('This is test stderr'))
-        result.testClass('org.gradle.OkTest').assertStderr(containsString('this is a warning'))
-        result.testClass('org.gradle.OtherTest').assertTestPassed('ok')
-        result.testClass('org.gradle.OtherTest').assertStdout(containsString('This is other stdout'))
-        result.testClass('org.gradle.OtherTest').assertStdout(containsString('other class loaded'))
-        result.testClass('org.gradle.OtherTest').assertStdout(containsString('other test constructed'))
-        result.testClass('org.gradle.OtherTest').assertStderr(containsString('This is other stderr'))
-        result.testClass('org.gradle.OtherTest').assertStderr(containsString('this is another warning'))
-    }
-
-    @Test
-    public void canRunMixOfJunit3And4Tests() {
-        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
-        executer.withTasks('check').run()
-
-        def result = new JUnitTestExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest')
-        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
-        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
-        result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
-        result.testClass('org.gradle.IgnoredTest').assertTestsExecuted()
-    }
-
-    @Test
-    public void canRunTestsUsingJUnit3() {
-        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
-        executer.withTasks('check').run()
-
-        def result = new JUnitTestExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test')
-        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
-        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
-    }
-
-    @Test
-    public void canRunTestsUsingJUnit4_4() {
-        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
-        resources.maybeCopy('JUnitIntegrationTest/junit4_4Tests')
-        executer.withTasks('check').run()
-
-        def result = new JUnitTestExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest')
-        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
-        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
-        result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
-        result.testClass('org.gradle.IgnoredTest').assertTestsExecuted()
-    }
-
-    @Test
-    public void reportsAndBreaksBuildWhenTestFails() {
-        TestFile testDir = dist.getTestDir();
-        ExecutionFailure failure = executer.withTasks('build').runWithFailure();
-
-        failure.assertHasDescription("Execution failed for task ':test'.");
-        failure.assertThatCause(startsWith('There were failing tests.'));
-
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenTest FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenBefore FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenAfter FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenBeforeAndAfter FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenBeforeClass FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenAfterClass FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenConstructor FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenException FAILED'));
-        assertThat(failure.getError(), containsLine('Test org.gradle.Unloadable FAILED'));
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
-        result.assertTestClassesExecuted(
-                'org.gradle.BrokenTest',
-                'org.gradle.BrokenBefore',
-                'org.gradle.BrokenAfter',
-                'org.gradle.BrokenBeforeClass',
-                'org.gradle.BrokenAfterClass',
-                'org.gradle.BrokenBeforeAndAfter',
-                'org.gradle.BrokenConstructor',
-                'org.gradle.BrokenException',
-                'org.gradle.Unloadable')
-        result.testClass('org.gradle.BrokenTest').assertTestFailed('failure', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenTest').assertTestFailed('broken', equalTo('java.lang.IllegalStateException'))
-        result.testClass('org.gradle.BrokenBeforeClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenAfterClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenBefore').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenBeforeAndAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: before failed'), equalTo('java.lang.AssertionError: after failed'))
-        result.testClass('org.gradle.BrokenConstructor').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
-        result.testClass('org.gradle.BrokenException').assertTestFailed('broken', startsWith('Could not determine failure message for exception of type org.gradle.BrokenException$BrokenRuntimeException: '))
-        result.testClass('org.gradle.Unloadable').assertTestFailed('initializationError', equalTo('java.lang.AssertionError: failed'))
-    }
-
-    @Test
-    public void canRunSingleTests() {
-        executer.withTasks('test').withArguments('-Dtest.single=Ok2').run()
-        def result = new JUnitTestExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('Ok2')
-
-        executer.withTasks('cleanTest', 'test').withArguments('-Dtest.single=Ok').run()
-        result.assertTestClassesExecuted('Ok', 'Ok2')
-
-        def failure = executer.withTasks('test').withArguments('-Dtest.single=DoesNotMatchAClass').runWithFailure()
-        failure.assertHasCause('Could not find matching test for pattern: DoesNotMatchAClass')
-
-        failure = executer.withTasks('test').withArguments('-Dtest.single=NotATest').runWithFailure()
-        failure.assertHasCause('Could not find matching test for pattern: NotATest')
-    }
-
-    @Test
-    public void canUseTestSuperClassesFromAnotherProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('settings.gradle').write("include 'a', 'b'");
-        testDir.file('b/build.gradle') << '''
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { compile 'junit:junit:4.7' }
-        '''
-        testDir.file('b/src/main/java/org/gradle/AbstractTest.java') << '''
-            package org.gradle;
-            public abstract class AbstractTest {
-                @org.junit.Test public void ok() { }
-            }
-        '''
-        TestFile buildFile = testDir.file('a/build.gradle');
-        buildFile << '''
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile project(':b') }
-        '''
-        testDir.file('a/src/test/java/org/gradle/SomeTest.java') << '''
-            package org.gradle;
-            public class SomeTest extends AbstractTest {
-            }
-        '''
-
-        executer.withTasks('a:test').run();
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir.file('a'))
-        result.assertTestClassesExecuted('org.gradle.SomeTest')
-        result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
-    }
-
-    @Test
-    public void canExcludeSuperClassesFromExecution() {
-        TestFile testDir = dist.getTestDir();
-        TestFile buildFile = testDir.file('build.gradle');
-        buildFile << '''
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile 'junit:junit:4.7' }
-            test { exclude '**/BaseTest.*' }
-        '''
-        testDir.file('src/test/java/org/gradle/BaseTest.java') << '''
-            package org.gradle;
-            public class BaseTest {
-                @org.junit.Test public void ok() { }
-            }
-        '''
-        testDir.file('src/test/java/org/gradle/SomeTest.java') << '''
-            package org.gradle;
-            public class SomeTest extends BaseTest {
-            }
-        '''
-
-        executer.withTasks('test').run();
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
-        result.assertTestClassesExecuted('org.gradle.SomeTest')
-        result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
-    }
-
-    @Test
-    public void detectsTestClasses() {
-        executer.withTasks('test').run()
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.EmptyRunWithSubclass', 'org.gradle.TestsOnInner', 'org.gradle.TestsOnInner$SomeInner')
-        result.testClass('org.gradle.EmptyRunWithSubclass').assertTestsExecuted('ok')
-        result.testClass('org.gradle.EmptyRunWithSubclass').assertTestPassed('ok')
-        result.testClass('org.gradle.TestsOnInner').assertTestPassed('ok')
-        result.testClass('org.gradle.TestsOnInner$SomeInner').assertTestPassed('ok')
-    }
-
-    @Test
-    public void runsAllTestsInTheSameForkedJvm() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle').writelns(
-                "apply plugin: 'java'",
-                "repositories { mavenCentral() }",
-                "dependencies { compile 'junit:junit:4.7' }"
-        );
-        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
-                "package org.gradle;",
-                "public abstract class AbstractTest {",
-                "    @org.junit.Test public void ok() {",
-                "        long time = java.lang.management.ManagementFactory.getRuntimeMXBean().getStartTime();",
-                "        System.out.println(String.format(\"VM START TIME = %s\", time));",
-                "    }",
-                "}");
-        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
-                "package org.gradle;",
-                "public class SomeTest extends AbstractTest {",
-                "}");
-        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
-                "package org.gradle;",
-                "public class SomeTest2 extends AbstractTest {",
-                "}");
-
-        executer.withTasks('test').run();
-
-        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
-        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
-        results1.assertIsFile();
-        results2.assertIsFile();
-        assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), equalTo(results2.linesThat(containsString('VM START TIME =')).get(0)));
-    }
-
-    @Test
-    public void canSpecifyMaximumNumberOfTestClassesToExecuteInAForkedJvm() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle').writelns(
-                "apply plugin: 'java'",
-                "repositories { mavenCentral() }",
-                "dependencies { compile 'junit:junit:4.7' }",
-                "test.forkEvery = 1"
-        );
-        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
-                "package org.gradle;",
-                "public abstract class AbstractTest {",
-                "    @org.junit.Test public void ok() {",
-                "        long time = java.lang.management.ManagementFactory.getRuntimeMXBean().getStartTime();",
-                "        System.out.println(String.format(\"VM START TIME = %s\", time));",
-                "    }",
-                "}");
-        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
-                "package org.gradle;",
-                "public class SomeTest extends AbstractTest {",
-                "}");
-        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
-                "package org.gradle;",
-                "public class SomeTest2 extends AbstractTest {",
-                "}");
-
-        executer.withTasks('test').run();
-
-        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
-        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
-        results1.assertIsFile();
-        results2.assertIsFile();
-        assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), not(equalTo(results2.linesThat(
-                containsString('VM START TIME =')).get(0))));
-    }
-
-    @Test
-    public void canListenForTestResults() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('src/main/java/AppException.java').writelns(
-                "public class AppException extends Exception { }"
-        );
-
-        testDir.file('src/test/java/SomeTest.java').writelns(
-                "public class SomeTest {",
-                "@org.junit.Test public void fail() { org.junit.Assert.fail(\"message\"); }",
-                "@org.junit.Test public void knownError() { throw new RuntimeException(\"message\"); }",
-                "@org.junit.Test public void unknownError() throws AppException { throw new AppException(); }",
-                "}"
-        );
-        testDir.file('src/test/java/SomeOtherTest.java').writelns(
-                "public class SomeOtherTest {",
-                "@org.junit.Test public void pass() { }",
-                "}"
-        );
-
-        testDir.file('build.gradle') << '''
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile 'junit:junit:4.7' }
-            def listener = new TestListenerImpl()
-            test.addTestListener(listener)
-            test.ignoreFailures = true
-            class TestListenerImpl implements TestListener {
-                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
-                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name] [$result.resultType] [$result.testCount]" }
-                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
-                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.resultType] [$result.testCount] [$result.exception]" }
-            }
-        '''
-
-        ExecutionResult result = executer.withTasks("test").run();
-        assertThat(result.getOutput(), containsLine("START [tests] []"));
-        assertThat(result.getOutput(), containsLine("FINISH [tests] [] [FAILURE] [4]"));
-
-        assertThat(result.getOutput(), containsLine("START [test process 'Gradle Worker 1'] [Gradle Worker 1]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1] [FAILURE] [4]"));
-
-        assertThat(result.getOutput(), containsLine("START [test class SomeOtherTest] [SomeOtherTest]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test class SomeOtherTest] [SomeOtherTest] [SUCCESS] [1]"));
-        assertThat(result.getOutput(), containsLine("START [test pass(SomeOtherTest)] [pass]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test pass(SomeOtherTest)] [pass] [SUCCESS] [1] [null]"));
-
-        assertThat(result.getOutput(), containsLine("START [test class SomeTest] [SomeTest]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test class SomeTest] [SomeTest] [FAILURE] [3]"));
-        assertThat(result.getOutput(), containsLine("START [test fail(SomeTest)] [fail]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test fail(SomeTest)] [fail] [FAILURE] [1] [java.lang.AssertionError: message]"));
-        assertThat(result.getOutput(), containsLine("START [test knownError(SomeTest)] [knownError]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test knownError(SomeTest)] [knownError] [FAILURE] [1] [java.lang.RuntimeException: message]"));
-        assertThat(result.getOutput(), containsLine("START [test unknownError(SomeTest)] [unknownError]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test unknownError(SomeTest)] [unknownError] [FAILURE] [1] [org.gradle.messaging.remote.internal.PlaceholderException: AppException: null]"));
-    }
-
-    @Test
-    public void canListenForTestResultsWhenJUnit3IsUsed() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('src/test/java/SomeTest.java').writelns(
-                "public class SomeTest extends junit.framework.TestCase {",
-                "public void testPass() { }",
-                "public void testFail() { junit.framework.Assert.fail(\"message\"); }",
-                "public void testError() { throw new RuntimeException(\"message\"); }",
-                "}"
-        );
-
-        testDir.file('build.gradle') << '''
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile 'junit:junit:3.8' }
-            def listener = new TestListenerImpl()
-            test.addTestListener(listener)
-            test.ignoreFailures = true
-            class TestListenerImpl implements TestListener {
-                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
-                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name]" }
-                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
-                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.exception]" }
-            }
-        '''
-
-        ExecutionResult result = executer.withTasks("test").run();
-        assertThat(result.getOutput(), containsLine("START [test class SomeTest] [SomeTest]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test class SomeTest] [SomeTest]"));
-        assertThat(result.getOutput(), containsLine("START [test testPass(SomeTest)] [testPass]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test testPass(SomeTest)] [testPass] [null]"));
-        assertThat(result.getOutput(), containsLine("START [test testFail(SomeTest)] [testFail]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test testFail(SomeTest)] [testFail] [junit.framework.AssertionFailedError: message]"));
-        assertThat(result.getOutput(), containsLine("START [test testError(SomeTest)] [testError]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test testError(SomeTest)] [testError] [java.lang.RuntimeException: message]"));
-    }
-
-    @Test
-    public void canHaveMultipleTestTaskInstances() {
-        executer.withTasks('check').run()
-
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.Test1', 'org.gradle.Test2')
-        result.testClass('org.gradle.Test1').assertTestPassed('ok')
-        result.testClass('org.gradle.Test2').assertTestPassed('ok')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JUnitTestExecutionResult.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JUnitTestExecutionResult.groovy
deleted file mode 100644
index b97d968..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JUnitTestExecutionResult.groovy
+++ /dev/null
@@ -1,167 +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 groovy.util.slurpersupport.GPathResult
-import org.gradle.integtests.fixtures.TestClassExecutionResult
-import org.gradle.integtests.fixtures.TestExecutionResult
-import org.gradle.util.TestFile
-import org.hamcrest.Matcher
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-class JUnitTestExecutionResult implements TestExecutionResult {
-    private final TestFile buildDir
-
-    def JUnitTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
-        this.buildDir = projectDir.file(buildDirName)
-    }
-
-    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
-        Map<String, File> classes = findClasses()
-        assertThat(classes.keySet(), equalTo(testClasses as Set));
-        this
-    }
-
-    TestClassExecutionResult testClass(String testClass) {
-        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
-    }
-
-    private def findTestClass(String testClass) {
-        def classes = findClasses()
-        assertThat(classes.keySet(), hasItem(testClass))
-        def classFile = classes.get(testClass)
-        assertThat(classFile, notNullValue())
-        return new XmlSlurper().parse(classFile)
-    }
-
-    private def findClasses() {
-        buildDir.file('test-results').assertIsDir()
-        buildDir.file('reports/tests/index.html').assertIsFile()
-
-        Map<String, File> classes = [:]
-        buildDir.file('test-results').eachFile { File file ->
-            def matcher = (file.name =~ /TEST-(.+)\.xml/)
-            if (matcher.matches()) {
-                classes[matcher.group(1)] = file
-            }
-        }
-        return classes
-    }
-}
-
-class JUnitTestClassExecutionResult implements TestClassExecutionResult {
-    GPathResult testClassNode
-    String testClassName
-    boolean checked
-
-    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
-        this.testClassNode = testClassNode
-        this.testClassName = testClassName
-    }
-
-    TestClassExecutionResult assertTestsExecuted(String... testNames) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertTestPassed(String name) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-        assertThat(testMethods[name].failure.size(), equalTo(0))
-        this
-    }
-
-    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-
-        def failures = testMethods[name].failure
-        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
-
-        for (int i = 0; i < messageMatchers.length; i++) {
-            assertThat(failures[i]. at message.text(), messageMatchers[i])
-        }
-        this
-    }
-
-    TestClassExecutionResult assertTestSkipped(String name) {
-        throw new UnsupportedOperationException()
-    }
-
-    TestClassExecutionResult assertTestsSkipped(String... testNames) {
-        Map<String, Node> testMethods = findIgnoredTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertConfigMethodPassed(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertConfigMethodFailed(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
-        def stdout = testClassNode.'system-out'[0].text();
-        assertThat(stdout, matcher)
-        this
-    }
-
-    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
-        def stderr = testClassNode.'system-err'[0].text();
-        assertThat(stderr, matcher)
-        this
-    }
-
-    private def findTests() {
-        if (!checked) {
-            assertThat(testClassNode.name(), equalTo('testsuite'))
-            assertThat(testClassNode. at name.text(), equalTo(testClassName))
-            assertThat(testClassNode. at tests.text(), not(equalTo('')))
-            assertThat(testClassNode. at failures.text(), not(equalTo('')))
-            assertThat(testClassNode. at errors.text(), not(equalTo('')))
-            assertThat(testClassNode. at time.text(), not(equalTo('')))
-            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
-            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
-            assertThat(testClassNode.properties.size(), equalTo(1))
-            testClassNode.testcase.each { node ->
-                assertThat(node. at classname.text(), equalTo(testClassName))
-                assertThat(node. at name.text(), not(equalTo('')))
-                assertThat(node. at time.text(), not(equalTo('')))
-                node.failure.each { failure ->
-                    assertThat(failure. at message.size(), equalTo(1))
-                    assertThat(failure. at type.text(), not(equalTo('')))
-                    assertThat(failure.text(), not(equalTo('')))
-                }
-            }
-            assertThat(testClassNode.'system-out'.size(), equalTo(1))
-            assertThat(testClassNode.'system-err'.size(), equalTo(1))
-            checked = true
-        }
-        Map testMethods = [:]
-        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
-
-    private def findIgnoredTests() {
-        Map testMethods = [:]
-        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectCrossVersionIntegrationTest.groovy
new file mode 100755
index 0000000..939ed33
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectCrossVersionIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.CrossVersionIntegrationSpec
+import org.junit.Test
+
+class JavaProjectCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    @Test
+    public void "can upgrade and downgrade Gradle version used to build Java project"() {
+        given:
+        buildFile << """
+if (gradle.gradleVersion == "0.8") {
+    usePlugin('java')
+} else {
+    apply plugin: 'java'
+}
+
+
+task custom(type: org.gradle.CustomTask)
+        """
+
+        and:
+        file('src/main/java/org/gradle/Person.java') << """
+package org.gradle;
+class Person { }
+"""
+
+        and:
+        file('buildSrc/src/main/groovy/org/gradle/CustomTask.groovy') << """
+package org.gradle
+class CustomTask extends org.gradle.api.DefaultTask { }
+"""
+
+        expect:
+        version previous withTasks 'build' run()
+        version current withTasks 'build' run()
+        version previous withTasks 'build' run()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
new file mode 100644
index 0000000..022f3d6
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2011 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.ExecutionFailure
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.util.TestFile
+import org.junit.Test
+
+class JavaProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void compilationFailureBreaksBuild() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("apply plugin: 'java'");
+        testFile("src/main/java/org/gradle/broken.java") << "broken";
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
+
+        failure.assertHasDescription("Execution failed for task ':compileJava'");
+        failure.assertHasCause("Compilation failed; see the compiler error output for details.");
+    }
+
+    @Test
+    public void testCompilationFailureBreaksBuild() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("apply plugin: 'java'");
+        testFile("src/main/java/org/gradle/ok.java") << "package org.gradle; class ok { }"
+        testFile("src/test/java/org/gradle/broken.java") << "broken"
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
+
+        failure.assertHasDescription("Execution failed for task ':compileTestJava'");
+        failure.assertHasCause("Compilation failed; see the compiler error output for details.");
+    }
+
+    @Test
+    public void handlesTestSrcWhichDoesNotContainAnyTestCases() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("apply plugin: 'java'");
+        testFile("src/test/java/org/gradle/NotATest.java") << """
+package org.gradle;
+public class NotATest {}"""
+
+        usingBuildFile(buildFile).withTasks("build").run();
+    }
+
+    @Test
+    public void javadocGenerationFailureBreaksBuild() throws IOException {
+        TestFile buildFile = testFile("javadocs.gradle");
+        buildFile.write("apply plugin: 'java'");
+        testFile("src/main/java/org/gradle/broken.java") << "class Broken { }"
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("javadoc").runWithFailure();
+
+        failure.assertHasDescription("Execution failed for task ':javadoc'");
+        failure.assertHasCause("Javadoc generation failed.");
+    }
+
+    @Test
+    public void handlesResourceOnlyProject() throws IOException {
+        TestFile buildFile = testFile("resources.gradle");
+        buildFile.write("apply plugin: 'java'");
+        testFile("src/main/resources/org/gradle/resource.file") << "test resource"
+
+        usingBuildFile(buildFile).withTasks("build").run();
+        testFile("build/resources/main/org/gradle/resource.file").assertExists();
+    }
+
+    @Test
+    public void separatesOutputResourcesFromCompiledClasses() throws IOException {
+        //given
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.write("apply plugin: 'java'");
+
+        testFile("src/main/resources/prod.resource") << ""
+        testFile("src/main/java/Main.java") << "class Main {}"
+        testFile("src/test/resources/test.resource") << "test resource"
+        testFile("src/test/java/TestFoo.java") << "class TestFoo {}"
+
+        //when
+        usingBuildFile(buildFile).withTasks("build").run();
+
+        //then
+        testFile("build/resources/main/prod.resource").assertExists();
+        testFile("build/classes/main/prod.resource").assertDoesNotExist();
+
+        testFile("build/resources/test/test.resource").assertExists();
+        testFile("build/classes/test/test.resource").assertDoesNotExist();
+
+        testFile("build/classes/main/Main.class").assertExists();
+        testFile("build/classes/test/TestFoo.class").assertExists();
+    }
+
+    @Test
+    public void generatesArtifactsWhenVersionIsEmpty() {
+        testFile("settings.gradle") << "rootProject.name = 'empty'"
+        def buildFile = testFile("build.gradle");
+        buildFile << """
+apply plugin: 'java'
+version = ''
+"""
+
+        testFile("src/main/resources/org/gradle/resource.file") << "some resource"
+
+        usingBuildFile(buildFile).withTasks("jar").run();
+        testFile("build/libs/empty.jar").assertIsFile();
+    }
+
+    @Test
+    public void "task registered as a builder of resources is executed"() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile << '''
+apply plugin: 'java'
+
+task generateResource << {}
+task generateTestResource << {}
+task notRegistered << {}
+
+sourceSets.main.output.dir "$buildDir/generatedResources", builtBy: 'generateResource'
+sourceSets.main.output.dir "$buildDir/generatedResourcesWithoutBuilder"
+
+sourceSets.test.output.dir "$buildDir/generatedTestResources", builtBy: 'generateTestResource'
+'''
+
+        //when
+        def result = usingBuildFile(buildFile).withTasks("classes").run();
+        //then
+        result.assertTasksExecuted(":compileJava", ":generateResource", ":processResources", ":classes")
+
+        //when
+        result = usingBuildFile(buildFile).withTasks("testClasses").run();
+        //then
+        result.output.contains(":generateTestResource")
+    }
+
+    @Test
+    public void "can recursively build dependent and dependee projects"() {
+        testFile("settings.gradle") << "include 'a', 'b', 'c'"
+        testFile("build.gradle") << """
+allprojects { apply plugin: 'java' }
+
+project(':a') {
+    dependencies { compile project(':b') }
+}
+
+project(':b') {
+    dependencies { compile project(':c') }
+}
+
+project(':c') {
+}
+
+"""
+
+        def result = inTestDirectory().withTasks('c:buildDependents').run()
+
+        assert result.executedTasks.contains(':a:build')
+        assert result.executedTasks.contains(':a:jar')
+        assert result.executedTasks.contains(':b:build')
+        assert result.executedTasks.contains(':b:jar')
+        assert result.executedTasks.contains(':c:build')
+        assert result.executedTasks.contains(':c:jar')
+
+        result = inTestDirectory().withTasks('b:buildDependents').run()
+
+        assert result.executedTasks.contains(':a:build')
+        assert result.executedTasks.contains(':a:jar')
+        assert result.executedTasks.contains(':b:build')
+        assert result.executedTasks.contains(':b:jar')
+        assert !result.executedTasks.contains(':c:build')
+        assert result.executedTasks.contains(':c:jar')
+
+        result = inTestDirectory().withTasks('a:buildDependents').run()
+
+        assert result.executedTasks.contains(':a:build')
+        assert result.executedTasks.contains(':a:jar')
+        assert !result.executedTasks.contains(':b:build')
+        assert result.executedTasks.contains(':b:jar')
+        assert !result.executedTasks.contains(':c:build')
+        assert result.executedTasks.contains(':c:jar')
+
+        result = inTestDirectory().withTasks('a:buildNeeded').run()
+
+        assert result.executedTasks.contains(':a:build')
+        assert result.executedTasks.contains(':a:jar')
+        assert result.executedTasks.contains(':b:build')
+        assert result.executedTasks.contains(':b:jar')
+        assert result.executedTasks.contains(':c:build')
+        assert result.executedTasks.contains(':c:jar')
+
+        result = inTestDirectory().withTasks('b:buildNeeded').run()
+
+        assert !result.executedTasks.contains(':a:build')
+        assert !result.executedTasks.contains(':a:jar')
+        assert result.executedTasks.contains(':b:build')
+        assert result.executedTasks.contains(':b:jar')
+        assert result.executedTasks.contains(':c:build')
+        assert result.executedTasks.contains(':c:jar')
+
+        result = inTestDirectory().withTasks(':c:buildNeeded').run()
+
+        assert !result.executedTasks.contains(':a:build')
+        assert !result.executedTasks.contains(':a:jar')
+        assert !result.executedTasks.contains(':b:build')
+        assert !result.executedTasks.contains(':b:jar')
+        assert result.executedTasks.contains(':c:build')
+    }
+
+    @Test
+    public void "project dependency does not drag in source jar from target project"() {
+        testFile("settings.gradle") << "include 'a', 'b'"
+        testFile("build.gradle") << """
+allprojects {
+    apply plugin: 'java'
+
+    task sourcesJar(type: Jar) {
+        classifier = 'sources'
+        from sourceSets.main.allSource
+    }
+
+    artifacts {
+        archives sourcesJar
+    }
+}
+
+project(':a') {
+    dependencies { compile project(':b') }
+    compileJava.doFirst {
+        assert classpath.collect { it.name } == ['b.jar']
+    }
+}
+
+"""
+        testFile("a/src/main/java/org/gradle/test/PersonImpl.java") << """
+package org.gradle.test;
+class PersonImpl implements Person { }
+"""
+
+        testFile("b/src/main/java/org/gradle/test/Person.java") << """
+package org.gradle.test;
+interface Person { }
+"""
+
+        def result = inTestDirectory().withTasks("a:classes").run()
+        result.assertTasksExecuted(":b:compileJava", ":b:processResources", ":b:classes", ":b:jar", ":a:compileJava", ":a:processResources", ":a:classes")
+    }
+
+    @Test
+    public void "can add additional jars to published runtime classpath"() {
+        testFile("settings.gradle") << "include 'a', 'b'"
+        testFile("build.gradle") << """
+allprojects {
+    apply plugin: 'java'
+}
+
+project(':b') {
+    sourceSets { extra }
+
+    task additionalJar(type: Jar) {
+        classifier = 'extra'
+        from sourceSets.extra.output
+    }
+
+    artifacts {
+        runtime additionalJar
+    }
+}
+
+project(':a') {
+    dependencies { compile project(':b') }
+    compileJava.doFirst {
+        assert classpath.collect { it.name } == ['b.jar', 'b-extra.jar']
+    }
+}
+
+"""
+        testFile("a/src/main/java/org/gradle/test/PersonImpl.java") << """
+package org.gradle.test;
+class PersonImpl implements Person { }
+"""
+
+        testFile("b/src/extra/java/org/gradle/test/Person.java") << """
+package org.gradle.test;
+interface Person { }
+"""
+
+        inTestDirectory().withTasks("a:classes").run()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.java
deleted file mode 100644
index 7f7cdc9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.java
+++ /dev/null
@@ -1,102 +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.integtests;
-
-import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
-import org.gradle.util.TestFile;
-import org.junit.Test;
-
-import java.io.IOException;
-
-public class JavaProjectIntegrationTest extends AbstractIntegrationTest {
-    @Test
-    public void handlesEmptyProject() {
-        testFile("build.gradle").writelns("apply plugin: 'java'");
-        inTestDirectory().withTasks("build").run();
-    }
-
-    @Test
-    public void compilationFailureBreaksBuild() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns("apply plugin: 'java'");
-        testFile("src/main/java/org/gradle/broken.java").write("broken");
-
-        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
-
-        failure.assertHasDescription("Execution failed for task ':compileJava'");
-        failure.assertHasCause("Compile failed; see the compiler error output for details.");
-    }
-
-    @Test
-    public void testCompilationFailureBreaksBuild() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns("apply plugin: 'java'");
-        testFile("src/main/java/org/gradle/ok.java").write("package org.gradle; class ok { }");
-        testFile("src/test/java/org/gradle/broken.java").write("broken");
-
-        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
-
-        failure.assertHasDescription("Execution failed for task ':compileTestJava'");
-        failure.assertHasCause("Compile failed; see the compiler error output for details.");
-    }
-
-    @Test
-    public void handlesTestSrcWhichDoesNotContainAnyTestCases() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns("apply plugin: 'java'");
-        testFile("src/test/java/org/gradle/NotATest.java").writelns("package org.gradle;", "public class NotATest {}");
-
-        usingBuildFile(buildFile).withTasks("build").run();
-    }
-
-    @Test
-    public void javadocGenerationFailureBreaksBuild() throws IOException {
-        TestFile buildFile = testFile("javadocs.gradle");
-        buildFile.write("apply plugin: 'java'");
-        testFile("src/main/java/org/gradle/broken.java").write("class Broken { }");
-
-        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("javadoc").runWithFailure();
-
-        failure.assertHasDescription("Execution failed for task ':javadoc'");
-        failure.assertHasCause("Javadoc generation failed.");
-    }
-
-    @Test
-    public void handlesResourceOnlyProject() throws IOException {
-        TestFile buildFile = testFile("resources.gradle");
-        buildFile.write("apply plugin: 'java'");
-        testFile("src/main/resources/org/gradle/resource.file").write("test resource");
-
-        usingBuildFile(buildFile).withTasks("build").run();
-        testFile("build/classes/main/org/gradle/resource.file").assertExists();
-    }
-
-    @Test
-    public void generatesArtifactsWhenVersionIsEmpty() {
-        testFile("settings.gradle").write("rootProject.name = 'empty'");
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "apply plugin: 'java'",
-                "version = ''"
-        );
-        testFile("src/main/resources/org/gradle/resource.file").write("some resource");
-
-        usingBuildFile(buildFile).withTasks("jar").run();
-        testFile("build/libs/empty.jar").assertIsFile();
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
index 85578c6..b06a182 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
@@ -17,15 +17,11 @@
 package org.gradle.integtests
 
 import junit.framework.AssertionFailedError
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.util.TestFile
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.integtests.fixtures.UsesSample
+import org.gradle.integtests.fixtures.*
+import org.gradle.internal.SystemProperties
 
 /**
  * @author Hans Dockter
@@ -106,6 +102,8 @@ class LoggingIntegrationTest {
                 'LOGGER: evaluated project :project1',
                 'LOGGER: evaluated project :project2',
                 'LOGGER: executed task :project1:log',
+                'LOGGER: executed task :project1:logInfo',
+                'LOGGER: executed task :project1:logLifecycle',
                 'LOGGER: task :project1:log starting work',
                 'LOGGER: task :project1:log completed work',
                 'main buildSrc info',
@@ -225,80 +223,143 @@ class LoggingIntegrationTest {
 
         String initScript = new File(loggingDir, 'init.gradle').absolutePath
         String[] allArgs = level.args + ['-I', initScript]
-        return executer.inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
+        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
     }
 
     def runBroken(LogLevel level) {
         TestFile loggingDir = dist.testDir
 
-        return executer.inDirectory(loggingDir).withTasks('broken').runWithFailure()
+        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withTasks('broken').runWithFailure()
     }
 
     def runMultiThreaded(LogLevel level) {
         resources.maybeCopy('LoggingIntegrationTest/multiThreaded')
-        return executer.withArguments(level.args).withTasks('log').run()
+        return executer.setAllowExtraLogging(false).withArguments(level.args).withTasks('log').run()
     }
 
     def runSample(LogLevel level) {
-        return executer.inDirectory(sampleResources.dir).withArguments(level.args).withTasks('log').run()
+        return executer.setAllowExtraLogging(false).inDirectory(sampleResources.dir).withArguments(level.args).withTasks('log').run()
     }
 
     void checkOutput(Closure run, LogLevel level) {
         ExecutionResult result = run.call(level)
         level.checkOuts(result)
     }
+
+    @Test
+    public void deprecatedLogging() {
+        LogLevel deprecated = new LogLevel(
+            args: [],
+            infoMessages: [['A deprecation warning']],
+            errorMessages: [],
+            allMessages: []
+        )
+
+        resources.maybeCopy('LoggingIntegrationTest/deprecated')
+        ExecutionResult result = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
+        deprecated.checkOuts(result)
+
+        // Ensure warnings are logged the second time
+        ExecutionResult secondResult = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
+        deprecated.checkOuts(secondResult)
+    }
+
+    @Test
+    public void deprecatedLoggingIsNotDisplayedWithQuietFlag() {
+        LogLevel deprecated = new LogLevel(
+            args: ['-q'],
+            infoMessages: [],
+            errorMessages: [],
+            allMessages: [['A deprecation warning']]
+        )
+
+        resources.maybeCopy('LoggingIntegrationTest/deprecated')
+        ExecutionResult result = executer.withArguments(deprecated.args).withTasks('log').run()
+
+        deprecated.checkOuts(result)
+    }
 }
 
 class LogLevel {
-    List args
-    List infoMessages
-    List errorMessages
-    List allMessages
-    Closure matchPartialLine = {expected, actual -> expected == actual }
+    List<String> args
+    List<String> infoMessages
+    List<String> errorMessages
+    List<String> allMessages
+    Closure validator = {OutputOccurrence occurrence ->
+        occurrence.assertIsAtEndOfLine()
+        occurrence.assertIsAtStartOfLine()
+    }
 
     def getForbiddenMessages() {
         allMessages - (infoMessages + errorMessages)
     }
 
     def checkOuts(ExecutionResult result) {
-        infoMessages.each {List messages ->
-            checkOuts(true, result.output, messages, matchPartialLine)
+        infoMessages.each {List<String> messages ->
+            checkOuts(true, result.output, messages, validator)
         }
-        errorMessages.each {List messages ->
-            checkOuts(true, result.error, messages, matchPartialLine)
+        errorMessages.each {List<String> messages ->
+            checkOuts(true, result.error, messages, validator)
         }
-        forbiddenMessages.each {List messages ->
-            checkOuts(false, result.output, messages) {expected, actual-> actual.contains(expected)}
-            checkOuts(false, result.error, messages) {expected, actual-> actual.contains(expected)}
+        forbiddenMessages.each {List<String> messages ->
+            checkOuts(false, result.output, messages) {occurrence -> }
+            checkOuts(false, result.error, messages) {occurrence -> }
         }
     }
 
-    def checkOuts(boolean shouldContain, String result, List outs, Closure partialLine) {
+    def checkOuts(boolean shouldContain, String result, List<String> outs, Closure validator) {
         outs.each {String expectedOut ->
-            def found = result.readLines().findAll {partialLine.call(expectedOut, it)}
-            if (found.empty && shouldContain) {
-                throw new AssertionFailedError("Could not find expected line '$expectedOut' in output:\n$result")
+            def filters = outs.findAll { other -> other != expectedOut && other.startsWith(expectedOut) }
+
+            // Find all locations of the expected string in the output
+            List<Integer> matches = []
+            int pos = 0;
+            while (pos < result.length()) {
+                int match = result.indexOf(expectedOut, pos)
+                if (match < 0) {
+                    break
+                }
+
+                // Filter matches with other expected strings that have this string as a prefix
+                boolean filter = filters.find { other -> result.substring(match).startsWith(other)} != null
+                if (!filter) {
+                    matches << match
+                }
+                pos = match + expectedOut.length()
             }
-            if (!found.empty && !shouldContain) {
-                throw new AssertionFailedError("Found unexpected line '$expectedOut' in output:\n$result")
-            }
-            if (found.size() > 1) {
-                throw new AssertionFailedError("Found line '$expectedOut' multiple times in output:\n$result")
+
+            // Check we found the expected number of occurrences of the expected string
+            if (!shouldContain) {
+                if (!matches.empty) {
+                    throw new AssertionFailedError("Found unexpected content '$expectedOut' in output:\n$result")
+                }
+            } else {
+                if (matches.empty) {
+                    throw new AssertionFailedError("Could not find expected content '$expectedOut' in output:\n$result")
+                }
+                if (matches.size() > 1) {
+                    throw new AssertionFailedError("Expected content '$expectedOut' should occur exactly once but found ${matches.size()} times in output:\n$result")
+                }
+
+                // Validate each occurrence
+                matches.each {
+                    validator.call(new OutputOccurrence(expectedOut, result, it))
+                }
             }
         }
     }
 }
 
 class LogOutput {
-    final List quietMessages = []
-    final List errorMessages = []
-    final List warningMessages = []
-    final List lifecycleMessages = []
-    final List infoMessages = []
-    final List debugMessages = []
-    final List traceMessages = []
-    final List forbiddenMessages = []
-    final List allOuts = [
+    final List<String> quietMessages = []
+    final List<String> errorMessages = []
+    final List<String> warningMessages = []
+    final List<String> lifecycleMessages = []
+    final List<String> infoMessages = []
+    final List<String> debugMessages = []
+    final List<String> traceMessages = []
+    final List<String> forbiddenMessages = []
+    final List<String> allOuts = [
             errorMessages,
             quietMessages,
             warningMessages,
@@ -357,6 +418,53 @@ class LogOutput {
             infoMessages: [quietMessages, warningMessages, lifecycleMessages, infoMessages, debugMessages],
             errorMessages: [errorMessages],
             allMessages: allOuts,
-            matchPartialLine: {expected, actual -> actual.endsWith(expected) /*&& actual =~ /\[.+?\] \[.+?\] .+/ */}
+            validator: {OutputOccurrence occurrence ->
+                occurrence.assertIsAtEndOfLine()
+                occurrence.assertHasPrefix(/.+ \[.+\] \[.+\] /)
+            }
     )
+}
+
+class OutputOccurrence {
+    final String expected
+    final String actual
+    final int index
+
+    OutputOccurrence(String expected, String actual, int index) {
+        this.expected = expected
+        this.actual = actual
+        this.index = index
+    }
+
+    void assertIsAtStartOfLine() {
+        if (index == 0) {
+            return
+        }
+        int startLine = index - SystemProperties.lineSeparator.length()
+        if (startLine < 0 || !actual.substring(startLine).startsWith(SystemProperties.lineSeparator)) {
+            throw new AssertionFailedError("Expected content '$expected' is not at the start of a line in output $actual.")
+        }
+    }
+
+    void assertIsAtEndOfLine() {
+        int endLine = index + expected.length()
+        if (endLine == actual.length()) {
+            return
+        }
+        if (!actual.substring(endLine).startsWith(SystemProperties.lineSeparator)) {
+            throw new AssertionFailedError("Expected content '$expected' is not at the end of a line in output $actual.")
+        }
+    }
+
+    void assertHasPrefix(String pattern) {
+        int startLine = actual.lastIndexOf(SystemProperties.lineSeparator, index)
+        if (startLine < 0) {
+            startLine = 0
+        } else {
+            startLine += SystemProperties.lineSeparator.length()
+        }
+        
+        String actualPrefix = actual.substring(startLine, index)
+        assert actualPrefix.matches(pattern): "Unexpected prefix '$actualPrefix' found for line containing content '$expected' in output $actual"
+    }
 }
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenPluginGoodBehaviourTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..8db397f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.WellBehavedPluginTest
+
+class MavenPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "assemble"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy
new file mode 100644
index 0000000..f469170
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MavenProjectIntegrationTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.junit.Test
+
+class MavenProjectIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void handlesSubProjectsWithoutTheMavenPluginApplied() {
+        dist.testFile("settings.gradle").write("include 'subProject'");
+        dist.testFile("build.gradle") << '''
+            apply plugin: 'java'
+            apply plugin: 'maven'
+        '''
+        executer.run();
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedJavaAndWebProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedJavaAndWebProjectIntegrationTest.groovy
new file mode 100644
index 0000000..0bdfc75
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedJavaAndWebProjectIntegrationTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.AbstractIntegrationSpec
+
+class MixedJavaAndWebProjectIntegrationTest extends AbstractIntegrationSpec {
+    def "project can use classes from WAR project"() {
+        given:
+        file("settings.gradle") << 'include "a", "b"'
+
+        and:
+        buildFile << """
+project(":a") {
+    apply plugin: 'war'
+}
+project(":b") {
+    apply plugin: 'java'
+    dependencies {
+        compile project(":a")
+    }
+    compileJava.doFirst {
+        assert classpath.collect { it.name } == ['a.jar']
+    }
+}
+"""
+
+        and:
+        file("a/src/main/java/org/gradle/test/Person.java") << """
+package org.gradle.test;
+interface Person { }
+"""
+
+        and:
+        file("b/src/main/java/org/gradle/test/PersonImpl.java") << """
+package org.gradle.test;
+class PersonImpl implements Person { }
+"""
+
+        expect:
+        succeeds "assemble"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedWarAndEjbProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedWarAndEjbProjectIntegrationTest.groovy
new file mode 100644
index 0000000..88934bd
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MixedWarAndEjbProjectIntegrationTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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.AbstractIntegrationSpec
+
+class MixedWarAndEjbProjectIntegrationTest extends AbstractIntegrationSpec {
+    def "project can use compiled classes from an EAR project"() {
+        given:
+        file("settings.gradle") << 'include "a", "b"'
+
+        and:
+        buildFile << """
+project(":a") {
+    apply plugin: 'ear'
+    apply plugin: 'java'
+}
+project(":b") {
+    apply plugin: 'war'
+    dependencies {
+        compile project(":a")
+    }
+    compileJava.doFirst {
+        assert classpath.collect { it.name } == ['a.jar']
+    }
+}
+"""
+
+        and:
+        file("a/src/main/java/org/gradle/test/Person.java") << """
+package org.gradle.test;
+interface Person { }
+"""
+
+        and:
+        file("b/src/main/java/org/gradle/test/PersonImpl.java") << """
+package org.gradle.test;
+class PersonImpl implements Person { }
+"""
+
+        expect:
+        succeeds "assemble"
+    }
+
+    def "assemble builds only the EAR by default"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'root'"
+
+        and:
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'ear'
+"""
+
+        when:
+        run "assemble"
+
+        then:
+        !file("build/libs/root.jar").exists()
+        !file("build/libs/root.war").exists()
+        file("build/libs/root.ear").exists()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy
old mode 100644
new mode 100755
index 7ead401..f38e06d
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy
@@ -16,9 +16,9 @@
 package org.gradle.integtests
 
 import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
-class MultiProjectIntegrationTest extends AbstractIntegrationTest {
+class MultiprojectIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void canInjectConfigurationFromParentProject() {
         testFile('settings.gradle') << 'include "a", "b"'
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiPluginGoodBehaviourTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..4ac7661
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.WellBehavedPluginTest
+
+class OsgiPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "build"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
index bb834ab..81486f3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -50,7 +50,7 @@ class OsgiProjectSampleIntegrationTest {
     static void checkManifest(Manifest manifest, start) {
         assertEquals('Example Gradle Activator', manifest.mainAttributes.getValue('Bundle-Name'))
         assertEquals('2', manifest.mainAttributes.getValue('Bundle-ManifestVersion'))
-        assertEquals('Bnd-1.15.0', manifest.mainAttributes.getValue('Tool'))
+        assertEquals('Bnd-1.50.0', manifest.mainAttributes.getValue('Tool'))
         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'))
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/PluginCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/PluginCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..1a87b0c
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/PluginCrossVersionIntegrationTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 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.CrossVersionIntegrationSpec
+
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at TargetVersions('0.9-rc-3+')
+class PluginCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    def "can use plugin compiled using previous Gradle version"() {
+        given:
+        file("producer/build.gradle") << """
+apply plugin: 'groovy'
+dependencies {
+    groovy localGroovy()
+    compile gradleApi()
+}
+"""
+        file("producer/src/main/groovy/SomePlugin.groovy") << """
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.*
+import org.gradle.api.internal.ConventionTask
+
+class SomePlugin implements Plugin<Project> {
+    void apply(Project p) {
+        p.tasks.add('do-stuff', CustomTask)
+        p.tasks.add('customConventionTask', CustomConventionTask)
+        p.tasks.add('customSourceTask', CustomSourceTask)
+    }
+}
+
+class CustomTask extends DefaultTask {
+    @TaskAction void go() { }
+}
+
+// ConventionTask leaks a lot of internal API so test for compatibility
+class CustomConventionTask extends ConventionTask {}
+
+// Same reason here, but less direct
+class CustomSourceTask extends SourceTask {}
+
+"""
+
+        buildFile << """
+buildscript {
+    dependencies { classpath fileTree(dir: "producer/build/libs", include: '*.jar') }
+}
+
+apply plugin: SomePlugin
+"""
+
+        expect:
+        version previous withTasks 'assemble' inDirectory(file("producer")) run()
+        version current withTasks 'do-stuff' run()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
new file mode 100644
index 0000000..7861a0a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.AbstractIntegrationSpec
+import org.cyberneko.html.parsers.SAXParser
+
+class ProfilingIntegrationTest extends AbstractIntegrationSpec {
+    def "can generate profiling report"() {
+        file('settings.gradle') << 'include "a", "b", "c"'
+        buildFile << '''
+allprojects {
+    apply plugin: 'java'
+}
+'''
+
+        when:
+        executer.withArguments("--profile").withTasks("build").run()
+
+        then:
+        def reportFile = file('build/reports/profile').listFiles().find { it.name ==~ /profile-.+.html/ }
+        Node content = parse(reportFile)
+        content.depthFirst().find { it.name() == 'TD' && it.text() == ':jar' }
+        content.depthFirst().find { it.name() == 'TD' && it.text() == ':a:jar' }
+        content.depthFirst().find { it.name() == 'TD' && it.text() == ':b:jar' }
+        content.depthFirst().find { it.name() == 'TD' && it.text() == ':c:jar' }
+    }
+
+    private Node parse(File reportFile) {
+        def text = reportFile.getText('utf-8').readLines()
+        def withoutDocType = text.subList(1, text.size()).join('\n')
+        def content = new XmlParser(new SAXParser()).parseText(withoutDocType)
+        return content
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
index c327283..ad81927 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.util.TestFile
 import org.junit.Rule
@@ -36,7 +37,6 @@ apply plugin: 'groovy'
 apply plugin: 'scala'
 
 repositories {
-    mavenRepo urls: 'http://scala-tools.org/repo-releases/'
     mavenCentral()
 }
 dependencies {
@@ -86,8 +86,6 @@ sourceSets.each {
         File buildDir = dist.testFile('build')
 
         buildDir.file('classes/main').assertHasDescendants(
-                'org/gradle/main/resource.txt',
-                'org/gradle/main/resource2.txt',
                 'org/gradle/JavaClass.class',
                 'org/gradle/JavaClass2.class',
                 'org/gradle/GroovyClass.class',
@@ -96,9 +94,12 @@ sourceSets.each {
                 'org/gradle/ScalaClass2.class'
         )
 
+        buildDir.file('resources/main').assertHasDescendants(
+                'org/gradle/main/resource.txt',
+                'org/gradle/main/resource2.txt'
+        )
+
         buildDir.file('classes/test').assertHasDescendants(
-                'org/gradle/test/resource.txt',
-                'org/gradle/test/resource2.txt',
                 'org/gradle/JavaClassTest.class',
                 'org/gradle/JavaClassTest2.class',
                 'org/gradle/GroovyClassTest.class',
@@ -107,6 +108,11 @@ sourceSets.each {
                 'org/gradle/ScalaClassTest2.class'
         )
 
+        buildDir.file('resources/test').assertHasDescendants(
+                'org/gradle/test/resource.txt',
+                'org/gradle/test/resource2.txt',
+        )
+
         TestFile tmpDir = dist.testFile('jarContents')
         buildDir.file('libs/sharedSource.jar').unzipTo(tmpDir)
         tmpDir.assertHasDescendants(
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
index 43896df..b05deeb 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
@@ -15,14 +15,15 @@
  */
 package org.gradle.integtests;
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
 import org.gradle.util.TestFile;
 import org.junit.Test;
 
 import java.io.File;
 
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.startsWith;
 
 public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
     @Test
@@ -45,14 +46,6 @@ public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
     }
 
     @Test
-    public void embeddedBuildFileIgnoresBuildAndScriptFiles() {
-        File rootDir = getTestDir();
-        testFile("settings.gradle").write("throw new RuntimeException()");
-        testFile("build.gradle").write("throw new RuntimeException()");
-        inDirectory(rootDir).usingBuildScript("Task task = task('do-stuff')").withTasks("do-stuff").run();
-    }
-
-    @Test
     public void canDetermineRootProjectAndDefaultProjectBasedOnCurrentDirectory() {
         File rootDir = getTestDir();
         File childDir = new File(rootDir, "child");
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectReportsPluginIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectReportsPluginIntegrationTest.java
new file mode 100644
index 0000000..9ee148b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectReportsPluginIntegrationTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.integtests;
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
+import org.junit.Test;
+
+public class ProjectReportsPluginIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void generatesReportFilesToReportsDirectory() {
+        testFile("build.gradle").writelns(
+                "apply plugin: 'project-report'"
+        );
+        inTestDirectory().withTasks("projectReport").run();
+
+        testFile("build/reports/project/dependencies.txt").assertExists();
+        testFile("build/reports/project/properties.txt").assertExists();
+        testFile("build/reports/project/tasks.txt").assertExists();
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesAntlrIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesAntlrIntegrationTest.groovy
deleted file mode 100644
index cfffbc4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesAntlrIntegrationTest.groovy
+++ /dev/null
@@ -1,41 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesAntlrIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('antlr')
-
-    @Test
-    public void canBuild() {
-        TestFile projectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.GrammarTest')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesApplicationIntegrationTest.groovy
deleted file mode 100644
index f8d0442..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesApplicationIntegrationTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2011 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.util.TestFile
-import org.junit.Rule
-import spock.lang.Specification
-import org.gradle.integtests.fixtures.*
-
-class SamplesApplicationIntegrationTest extends Specification {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('application')
-
-    def canRunTheApplicationUsingRunTask() {
-        when:
-        def result = executer.inDirectory(sample.dir).withTasks('run').run()
-
-        then:
-        result.output.contains('Greetings from the sample application.')
-    }
-
-    def canBuildAndRunTheInstalledApplication() {
-        when:
-        executer.inDirectory(sample.dir).withTasks('installApp').run()
-
-        then:
-        def installDir = sample.dir.file('build/install/application')
-        installDir.assertIsDir()
-
-        checkApplicationImage(installDir)
-    }
-
-    def canBuildAndRunTheZippedDistribution() {
-        when:
-        executer.inDirectory(sample.dir).withTasks('distZip').run()
-
-        then:
-        def distFile = sample.dir.file('build/distributions/application-1.0.2.zip')
-        distFile.assertIsFile()
-
-        def installDir = sample.dir.file('unzip')
-        distFile.usingNativeTools().unzipTo(installDir)
-
-        checkApplicationImage(installDir.file('application-1.0.2'))
-    }
-    
-    private void checkApplicationImage(TestFile installDir) {
-        installDir.file('bin/application').assertIsFile()
-        installDir.file('bin/application.bat').assertIsFile()
-        installDir.file('lib/application-1.0.2.jar').assertIsFile()
-        installDir.file('lib/commons-collections-3.2.1.jar').assertIsFile()
-
-        def builder = new ScriptExecuter()
-        builder.workingDir installDir.file('bin')
-        builder.executable 'application'
-        builder.standardOutput = new ByteArrayOutputStream()
-        builder.errorOutput = new ByteArrayOutputStream()
-
-        def result = builder.run()
-        result.assertNormalExitValue()
-
-        assert builder.standardOutput.toString().contains('Greetings from the sample application.')
-        assert builder.errorOutput.toString() == ''
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCodeQualityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCodeQualityIntegrationTest.groovy
deleted file mode 100644
index 67a8145..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCodeQualityIntegrationTest.groovy
+++ /dev/null
@@ -1,44 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesCodeQualityIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('codeQuality')
-
-    @Test
-    public void checkReportsGenerated() {
-        TestFile projectDir = sample.dir
-        TestFile buildDir = projectDir.file('build')
-
-        executer.inDirectory(projectDir).withTasks('check').run()
-
-        buildDir.file('checkstyle/main.xml').assertIsFile()
-        buildDir.file('reports/codenarc/main.html').assertIsFile()
-        buildDir.file('reports/codenarc/test.html').assertIsFile()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCustomBuildLanguageIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCustomBuildLanguageIntegrationTest.groovy
deleted file mode 100644
index 78e3ca0..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCustomBuildLanguageIntegrationTest.groovy
+++ /dev/null
@@ -1,59 +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.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesCustomBuildLanguageIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('customBuildLanguage')
-
-    @Test
-    public void testBuildProductDistributions() {
-        TestFile rootDir = sample.dir
-        executer.inDirectory(rootDir).withTasks('clean', 'dist').run()
-
-        TestFile expandDir = dist.testFile('expand-basic')
-        rootDir.file('basicEdition/build/distributions/some-company-basic-edition-1.0.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'readme.txt',
-                'end-user-license-agreement.txt',
-                'bin/start.sh',
-                'lib/some-company-identity-management-1.0.jar',
-                'lib/some-company-billing-1.0.jar',
-                'lib/commons-lang-2.4.jar'
-        )
-
-        expandDir = dist.testFile('expand-enterprise')
-        rootDir.file('enterpriseEdition/build/distributions/some-company-enterprise-edition-1.0.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'readme.txt',
-                'end-user-license-agreement.txt',
-                'bin/start.sh',
-                'lib/some-company-identity-management-1.0.jar',
-                'lib/some-company-billing-1.0.jar',
-                'lib/some-company-reporting-1.0.jar',
-                'lib/commons-lang-2.4.jar',
-                'lib/commons-io-1.2.jar'
-        )
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCustomPluginIntegrationTest.groovy
deleted file mode 100644
index 7149743..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesCustomPluginIntegrationTest.groovy
+++ /dev/null
@@ -1,52 +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.Rule
-import org.junit.Test
-import org.gradle.util.TestFile
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesCustomPluginIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('customPlugin')
-
-    @Test
-    public void canTestPluginAndTaskImplementation() {
-        TestFile projectDir = sample.dir
-
-        executer.inDirectory(projectDir).withTasks('check').run()
-
-        def result = new JUnitTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
-    }
-
-    @Test
-    public void canPublishAndUsePluginAndTestImplementations() {
-        TestFile projectDir = sample.dir
-
-        executer.inDirectory(projectDir).withTasks('uploadArchives').run()
-
-        def result = executer.usingBuildScript(projectDir.file('usesCustomTask.gradle')).withTasks('greeting').run()
-        assert result.output.contains('howdy!')
-
-        result = executer.usingBuildScript(projectDir.file('usesCustomPlugin.gradle')).withTasks('hello').run()
-        assert result.output.contains('hello from GreetingTask')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesExcludesAndClassifiersIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesExcludesAndClassifiersIntegrationTest.groovy
deleted file mode 100644
index 4a10743..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesExcludesAndClassifiersIntegrationTest.groovy
+++ /dev/null
@@ -1,51 +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.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesExcludesAndClassifiersIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('userguide/artifacts/excludesAndClassifiers')
-
-    @Test
-    public void checkExcludeAndClassifier() {
-        File projectDir = sample.dir
-        String outputCompile = executer.inDirectory(projectDir).withTasks('clean', 'resolveCompile').run().getOutput()
-        String outputRuntime = executer.inDirectory(projectDir).withTasks('clean', 'resolveRuntime').run().getOutput()
-        assertThat(outputCompile, not(containsString("commons")))
-        assertThat(outputRuntime, not(containsString("commons")))
-        assertThat(outputCompile, not(containsString("reports")))
-        assertThat(outputRuntime, not(containsString("reports")))
-        assertThat(outputCompile, not(containsString("shared")))
-        assertThat(outputRuntime, containsString("shared"))
-
-        assertThat(outputCompile, containsString("service-1.0-jdk15"))
-        assertThat(outputCompile, containsString("service-1.0-jdk14"))
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
deleted file mode 100644
index b7437f7..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
+++ /dev/null
@@ -1,48 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesGroovyCustomizedLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('groovy/customizedLayout')
-
-    @Test
-    public void groovyProjectQuickstartSample() {
-        TestFile groovyProjectDir = sample.dir
-        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
-        groovyProjectDir.file('build/libs/customizedLayout.jar').unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/Person.class'
-        )
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyMultiProjectIntegrationTest.groovy
deleted file mode 100644
index 043cae7..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyMultiProjectIntegrationTest.groovy
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesGroovyMultiProjectIntegrationTest {
-    static final String TEST_PROJECT_NAME = 'testproject'
-
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('groovy/multiproject')
-
-    private List mainFiles = ['JavaPerson', 'GroovyPerson', 'GroovyJavaPerson']
-    private List excludedFiles = ['ExcludeJava', 'ExcludeGroovy', 'ExcludeGroovyJava']
-    private List testFiles = ['JavaPersonTest', 'GroovyPersonTest', 'GroovyJavaPersonTest']
-
-    @Test
-    public void groovyProjectSamples() {
-        String packagePrefix = 'build/classes/main/org/gradle'
-        String testPackagePrefix = 'build/classes/test/org/gradle'
-
-
-        TestFile groovyProjectDir = sample.dir
-        TestFile testProjectDir = groovyProjectDir.file(TEST_PROJECT_NAME)
-
-        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
-
-        // Check compilation
-        mainFiles.each { testProjectDir.file(packagePrefix, it + ".class").assertIsFile() }
-        excludedFiles.each { testProjectDir.file(packagePrefix, it + ".class").assertDoesNotExist() }
-        testFiles.each { testProjectDir.file(testPackagePrefix, it + ".class").assertIsFile() }
-
-        // The test produce marker files with the name of the test class
-        testFiles.each { testProjectDir.file('build', it).assertIsFile() }
-
-        // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
-        testProjectDir.file("build/libs/$TEST_PROJECT_NAME-1.0.jar").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'META-INF/myfile',
-                'org/gradle/main.properties',
-                'org/gradle/JavaPerson.class',
-                'org/gradle/GroovyPerson.class',
-                'org/gradle/GroovyJavaPerson.class'
-        )
-        tmpDir.file('META-INF/MANIFEST.MF').assertContents(containsString('myprop: myvalue'))
-
-        // Build docs
-        executer.inDirectory(groovyProjectDir).withTasks('clean', 'javadoc', 'groovydoc').run()
-        testProjectDir.file('build/docs/javadoc/index.html').assertIsFile()
-        testProjectDir.file('build/docs/groovydoc/index.html').assertIsFile()
-        testProjectDir.file('build/docs/groovydoc/org/gradle/GroovyPerson.html').assertIsFile()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyOldVersionsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyOldVersionsIntegrationTest.groovy
deleted file mode 100644
index 1b814ac..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyOldVersionsIntegrationTest.groovy
+++ /dev/null
@@ -1,55 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-
-class SamplesGroovyOldVersionsIntegrationTest {
-
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-
-    @Test
-    public void groovy156() {
-        TestFile groovyProjectDir = dist.samplesDir.file('groovy/groovy-1.5.6')
-        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check jar exists
-        groovyProjectDir.file("build/libs/groovy-1.5.6.jar").assertIsFile()
-    }
-
-    @Test
-    public void groovy167() {
-        TestFile groovyProjectDir = dist.samplesDir.file('groovy/groovy-1.6.7')
-        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check jar exists
-        groovyProjectDir.file("build/libs/groovy-1.6.7.jar").assertIsFile()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyQuickstartIntegrationTest.groovy
deleted file mode 100644
index 7496fac..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesGroovyQuickstartIntegrationTest.groovy
+++ /dev/null
@@ -1,52 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesGroovyQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('groovy/quickstart')
-
-    @Test
-    public void groovyProjectQuickstartSample() {
-        TestFile groovyProjectDir = sample.dir
-        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
-        groovyProjectDir.file('build/libs/quickstart.jar').unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/Person.class',
-                'org/gradle/Person$_closure1.class',
-                'org/gradle/Person$_closure2.class',
-                'resource.txt',
-                'script.groovy'
-        )
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesIvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesIvyPublishIntegrationTest.groovy
deleted file mode 100644
index 5267d91..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesIvyPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2011 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.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-public class SamplesIvyPublishIntegrationTest {
-    @Rule
-    public final GradleDistribution dist = new GradleDistribution()
-    @Rule
-    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule
-    public final Sample sample = new Sample("ivypublish")
-
-    @Test
-    public void testPublish() {
-        // the actual testing is done in the build script.
-        File projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks("uploadArchives").run()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaBaseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaBaseIntegrationTest.groovy
deleted file mode 100644
index fcc3988..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaBaseIntegrationTest.groovy
+++ /dev/null
@@ -1,57 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-
-class SamplesJavaBaseIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('java/base')
-
-    @Test
-    public void canBuildAndUploadJar() {
-        TestFile javaprojectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file('test'))
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check jar exists
-        javaprojectDir.file("prod/build/libs/prod-1.0.jar").assertIsFile()
-        
-        // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
-        javaprojectDir.file('prod/build/libs/prod-1.0.jar').unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/Person.class'
-        )
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaCustomizedLayoutIntegrationTest.groovy
deleted file mode 100644
index e1fd742..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaCustomizedLayoutIntegrationTest.groovy
+++ /dev/null
@@ -1,57 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-
-class SamplesJavaCustomizedLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('java/customizedLayout')
-
-    @Test
-    public void canBuildAndUploadJar() {
-        TestFile javaprojectDir = sample.dir
-                                                      
-        // Build and test projects
-        executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check jar exists
-        javaprojectDir.file('build/libs/customizedLayout.jar').assertIsFile()
-
-        // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
-        javaprojectDir.file('build/libs/customizedLayout.jar').unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/Person.class'
-        )
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaMultiProjectIntegrationTest.groovy
deleted file mode 100644
index 04f7f5a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaMultiProjectIntegrationTest.groovy
+++ /dev/null
@@ -1,219 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.TestFile
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-
-/**
- * @author Hans Dockter
- */
-class SamplesJavaMultiProjectIntegrationTest {
-
-    static final String JAVA_PROJECT_NAME = 'java/multiproject'
-    static final String SHARED_NAME = 'shared'
-    static final String API_NAME = 'api'
-    static final String WEBAPP_NAME = 'webservice'
-    static final String SERVICES_NAME = 'services'
-    static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
-
-    private TestFile javaprojectDir
-    private List projects;
-
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('java/multiproject')
-
-    @Before
-    void setUp() {
-        javaprojectDir = sample.dir
-        projects = [SHARED_NAME, API_NAME, WEBAPP_PATH].collect {"$JAVA_PROJECT_NAME/$it"} + JAVA_PROJECT_NAME
-    }
-
-    @Test
-    public void multiProjectJavaProjectSample() {
-        // Build and test projects
-        executer.inDirectory(javaprojectDir).withTasks('build').run()
-
-        assertBuildSrcBuilt()
-        assertEverythingBuilt()
-    }
-
-    private void assertBuildSrcBuilt() {
-        TestFile buildSrcDir = javaprojectDir.file('buildSrc')
-
-        buildSrcDir.file('build/libs/buildSrc.jar').assertIsFile()
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(buildSrcDir)
-        result.assertTestClassesExecuted('org.gradle.buildsrc.BuildSrcClassTest')
-    }
-
-    private void assertEverythingBuilt() {
-        String packagePrefix = 'build/classes/main/org/gradle'
-        String testPackagePrefix = 'build/classes/test/org/gradle'
-
-        // Check classes and resources
-        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
-        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'main.properties')
-
-        // Check test classes and resources
-        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'PersonTest.class')
-        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'test.properties')
-        assertExists(javaprojectDir, API_NAME, packagePrefix, API_NAME, 'PersonList.class')
-        assertExists(javaprojectDir, WEBAPP_PATH, packagePrefix, WEBAPP_NAME, 'TestTest.class')
-
-        // Check test results and report
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file(SHARED_NAME))
-        result.assertTestClassesExecuted('org.gradle.shared.PersonTest')
-
-        result = new JUnitTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
-        result.assertTestClassesExecuted('org.gradle.webservice.TestTestTest')
-
-        // Check contents of shared jar
-        TestFile tmpDir = dist.testDir.file("$SHARED_NAME-1.0.jar")
-        javaprojectDir.file(SHARED_NAME, "build/libs/$SHARED_NAME-1.0.jar").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/shared/Person.class',
-                'org/gradle/shared/package-info.class',
-                'org/gradle/shared/main.properties'
-        )
-
-        // Check contents of API jar
-        tmpDir = dist.testDir.file("$API_NAME-1.0.jar")
-        javaprojectDir.file(API_NAME, "build/libs/$API_NAME-1.0.jar").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/api/PersonList.class',
-                'org/gradle/apiImpl/Impl.class')
-
-        // Check contents of API jar
-        tmpDir = dist.testDir.file("$API_NAME-spi-1.0.jar")
-        javaprojectDir.file(API_NAME, "build/libs/$API_NAME-spi-1.0.jar").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/api/PersonList.class')
-
-        // Check contents of War
-        tmpDir = dist.testDir.file("$WEBAPP_NAME-2.5.war")
-        javaprojectDir.file(WEBAPP_PATH, "build/libs/$WEBAPP_NAME-2.5.war").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'WEB-INF/classes/org/gradle/webservice/TestTest.class',
-                "WEB-INF/lib/$SHARED_NAME-1.0.jar".toString(),
-                "WEB-INF/lib/$API_NAME-1.0.jar".toString(),
-                "WEB-INF/lib/$API_NAME-spi-1.0.jar".toString(),
-                'WEB-INF/lib/commons-collections-3.2.jar',
-                'WEB-INF/lib/commons-io-1.2.jar',
-                'WEB-INF/lib/commons-lang-2.4.jar'
-        )
-
-        // Check contents of dist zip
-        tmpDir = dist.testDir.file("$API_NAME-1.0.zip")
-        javaprojectDir.file(API_NAME, "build/distributions/$API_NAME-1.0.zip").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'README.txt',
-                "libs/$API_NAME-spi-1.0.jar".toString(),
-                "libs/$SHARED_NAME-1.0.jar".toString(),
-                'libs/commons-io-1.2.jar',
-                'libs/commons-lang-2.4.jar'
-        )
-    }
-
-    @Test
-    public void multiProjectJavaDoc() {
-        executer.inDirectory(javaprojectDir).withTasks('clean', 'javadoc').run()
-        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/index.html').assertIsFile()
-        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/index.html').assertContents(containsString("shared 1.0 API"))
-        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/org/gradle/shared/Person.html').assertIsFile()
-        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/org/gradle/shared/package-summary.html').assertContents(containsString("These are the shared classes."))
-        javaprojectDir.file(API_NAME, 'build/docs/javadoc/index.html').assertIsFile()
-        javaprojectDir.file(API_NAME, 'build/docs/javadoc/org/gradle/api/PersonList.html').assertIsFile()
-        javaprojectDir.file(API_NAME, 'build/docs/javadoc/org/gradle/api/package-summary.html').assertContents(containsString("These are the API classes"))
-        javaprojectDir.file(WEBAPP_PATH, 'build/docs/javadoc/index.html').assertIsFile()
-    }
-
-    @Test
-    public void multiProjectPartialBuild() {
-        String packagePrefix = 'build/classes/main/org/gradle'
-        String testPackagePrefix = 'build/classes/test/org/gradle'
-
-        // Partial build using current directory
-        executer.inDirectory(javaprojectDir.file("$SERVICES_NAME/$WEBAPP_NAME")).withTasks('buildNeeded').run()
-        checkPartialWebAppBuild(packagePrefix, javaprojectDir, testPackagePrefix)
-
-        // Partial build using task path
-        executer.inDirectory(javaprojectDir).withTasks('clean', "$SHARED_NAME:classes".toString()).run()
-        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
-        assertDoesNotExist(javaprojectDir, false, API_NAME, packagePrefix, API_NAME, 'PersonList.class')
-    }
-
-
-    @Test
-    public void buildDependents() {
-        executer.inDirectory(javaprojectDir).withTasks('clean', "$SHARED_NAME:buildDependents".toString()).run()
-        assertEverythingBuilt()
-    }
-
-    @Test
-    public void clean() {
-        executer.inDirectory(javaprojectDir).withTasks('classes').run()
-        executer.inDirectory(javaprojectDir).withTasks('clean').run()
-        projects.each { javaprojectDir.file("$it/build").assertDoesNotExist() }
-    }
-
-    @Test
-    public void noRebuildOfProjectDependencies() {
-        TestFile apiDir = javaprojectDir.file(API_NAME)
-        executer.inDirectory(apiDir).withTasks('classes').run()
-        TestFile sharedJar = javaprojectDir.file("shared/build/libs/shared-1.0.jar")
-        TestFile.Snapshot snapshot = sharedJar.snapshot()
-        executer.inDirectory(apiDir).withTasks('clean', 'classes').withArguments("-a").run()
-        sharedJar.assertHasNotChangedSince(snapshot)
-    }
-
-    @Test
-    public void shouldNotUseCacheForProjectDependencies() {
-        TestFile apiDir = javaprojectDir.file(API_NAME)
-        executer.inDirectory(apiDir).withTasks('checkProjectDependency').run()
-    }
-           
-    private static def checkPartialWebAppBuild(String packagePrefix, TestFile javaprojectDir, String testPackagePrefix) {
-        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
-        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'main.properties')
-        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'PersonTest.class')
-        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'test.properties')
-        assertExists(javaprojectDir, "$SERVICES_NAME/$WEBAPP_NAME" as String, packagePrefix, WEBAPP_NAME, 'TestTest.class')
-        assertExists(javaprojectDir, "$SERVICES_NAME/$WEBAPP_NAME" as String, 'build', 'libs', 'webservice-2.5.war')
-        assertDoesNotExist(javaprojectDir, false, "$SERVICES_NAME/$WEBAPP_NAME" as String, 'build', 'libs', 'webservice-2.5.jar')
-    }
-
-    private static void assertExists(TestFile baseDir, Object... path) {
-        baseDir.file(path).assertExists()
-    }
-
-    static void assertDoesNotExist(TestFile baseDir, boolean shouldExists, Object... path) {
-        baseDir.file(path).assertDoesNotExist()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaOnlyIfIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaOnlyIfIntegrationTest.groovy
deleted file mode 100644
index a7c8dc4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaOnlyIfIntegrationTest.groovy
+++ /dev/null
@@ -1,92 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-public class SamplesJavaOnlyIfIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('java/onlyif')
-
-    /**
-     * runs a build 3 times.
-     * execute clean dists
-     * check worked correctly
-     *
-     * remove test results
-     * execute dists
-     * check didn't re-run tests
-     *
-     * remove class file
-     * execute dists
-     * check that it re-ran tests 
-     */
-    @Test public void testOptimizedBuild() {
-        TestFile javaprojectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
-        assertExists(javaprojectDir, 'build/reports/tests/index.html')
-
-        // Check jar exists
-        assertExists(javaprojectDir, "build/libs/onlyif.jar")
-
-        // remove test results
-        removeFile(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
-        removeFile(javaprojectDir, 'build/reports/tests/index.html')
-
-        executer.inDirectory(javaprojectDir).withTasks('test').run()
-
-        // assert that tests did not run
-        // (since neither compile nor compileTests should have done anything)
-        assertDoesNotExist(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
-        assertDoesNotExist(javaprojectDir, 'build/reports/tests/index.html')
-
-        // remove a compiled class file
-        removeFile(javaprojectDir, 'build/classes/main/org/gradle/Person.class')
-
-        executer.inDirectory(javaprojectDir).withTasks('test').run()
-
-        // Check tests have run
-        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
-        assertExists(javaprojectDir, 'build/reports/tests/index.html')
-    }
-
-    private static void assertExists(File baseDir, String path) {
-        new TestFile(baseDir).file(path).assertExists()
-    }
-
-    private static void assertDoesNotExist(File baseDir, String path) {
-        new TestFile(baseDir).file(path).assertDoesNotExist()
-    }
-
-    private static void removeFile(File baseDir, String path) {
-        TestFile file = new TestFile(baseDir).file(path)
-        file.assertExists()
-        file.delete()
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaProjectWithIntTestsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
deleted file mode 100644
index e607050..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaProjectWithIntTestsIntegrationTest.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.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesJavaProjectWithIntTestsIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('java/withIntegrationTests')
-
-    @Test
-    public void canRunIntegrationTests() {
-        TestFile javaprojectDir = sample.dir
-        
-        // Run int tests
-        executer.inDirectory(javaprojectDir).withTasks('clean', 'integrationTest').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonIntegrationTest')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaQuickstartIntegrationTest.groovy
deleted file mode 100644
index fbf9352..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesJavaQuickstartIntegrationTest.groovy
+++ /dev/null
@@ -1,71 +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 java.util.jar.Manifest
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesJavaQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('java/quickstart')
-
-    @Test
-    public void canBuildAndUploadJar() {
-        TestFile javaprojectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check jar exists
-        javaprojectDir.file("build/libs/quickstart-1.0.jar").assertIsFile()
-
-        // Check jar uploaded
-        javaprojectDir.file('repos/quickstart-1.0.jar').assertIsFile()
-
-        // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
-        javaprojectDir.file('repos/quickstart-1.0.jar').unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/Person.class',
-                'org/gradle/resource.xml'
-        )
-
-        // Check contents of manifest
-        Manifest manifest = new Manifest()
-        jarContents.file('META-INF/MANIFEST.MF').withInputStream { manifest.read(it) }
-        assertThat(manifest.mainAttributes.size(), equalTo(3))
-        assertThat(manifest.mainAttributes.getValue('Manifest-Version'), equalTo('1.0'))
-        assertThat(manifest.mainAttributes.getValue('Implementation-Title'), equalTo('Gradle Quickstart'))
-        assertThat(manifest.mainAttributes.getValue('Implementation-Version'), equalTo('1.0'))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndGroovyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndGroovyIntegrationTest.groovy
deleted file mode 100644
index 9b94590..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndGroovyIntegrationTest.groovy
+++ /dev/null
@@ -1,71 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesMixedJavaAndGroovyIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('groovy/mixedJavaAndGroovy')
-
-    @Test
-    public void canBuildJar() {
-        TestFile projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.PersonTest')
-
-        // Check contents of jar
-        TestFile tmpDir = dist.testDir.file('jarContents')
-        projectDir.file('build/libs/mixedJavaAndGroovy-1.0.jar').unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/Person.class',
-                'org/gradle/GroovyPerson.class',
-                'org/gradle/JavaPerson.class',
-                'org/gradle/PersonList.class'
-        )
-    }
-
-    @Test
-    public void canBuildDocs() {
-        TestFile projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'groovydoc').run()
-
-        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
-        javadocsDir.file("index.html").assertIsFile()
-        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndGroovy 1.0 API'))
-        javadocsDir.file("org/gradle/Person.html").assertIsFile()
-        javadocsDir.file("org/gradle/JavaPerson.html").assertIsFile()
-
-        TestFile groovydocsDir = projectDir.file("build/docs/groovydoc")
-        groovydocsDir.file("index.html").assertIsFile()
-        groovydocsDir.file("overview-summary.html").assertContents(containsString('mixedJavaAndGroovy 1.0 API'))
-        groovydocsDir.file("org/gradle/JavaPerson.html").assertIsFile()
-        groovydocsDir.file("org/gradle/GroovyPerson.html").assertIsFile()
-        groovydocsDir.file("org/gradle/PersonList.html").assertIsFile()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndScalaIntegrationTest.groovy
deleted file mode 100644
index a9539ff..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndScalaIntegrationTest.groovy
+++ /dev/null
@@ -1,74 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesMixedJavaAndScalaIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('scala/mixedJavaAndScala')
-
-    @Test
-    public void canBuildJar() {
-        TestFile projectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
-
-        // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
-        projectDir.file("build/libs/mixedJavaAndScala-1.0.jar").unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/sample/Person.class',
-                'org/gradle/sample/impl/JavaPerson.class',
-                'org/gradle/sample/impl/PersonImpl.class',
-                'org/gradle/sample/impl/PersonList.class'
-        )
-    }
-
-    @Test
-    public void canBuildDocs() {
-        TestFile projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'scaladoc').run()
-
-        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
-        javadocsDir.file("index.html").assertIsFile()
-        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
-        javadocsDir.file("org/gradle/sample/Person.html").assertIsFile()
-        javadocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
-
-        TestFile scaladocsDir = projectDir.file("build/docs/scaladoc")
-        scaladocsDir.file("index.html").assertIsFile()
-        scaladocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
-        scaladocsDir.file("org/gradle/sample/impl/PersonImpl.html").assertIsFile()
-        scaladocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
-        scaladocsDir.file("org/gradle/sample/impl/PersonList.html").assertIsFile()
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesRepositoriesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesRepositoriesIntegrationTest.groovy
deleted file mode 100644
index fa6f271..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesRepositoriesIntegrationTest.groovy
+++ /dev/null
@@ -1,43 +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.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesRepositoriesIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('userguide/artifacts/defineRepository')
-
-    @Test
-    public void repositoryNotations() {
-        // This test is not very strong. Its main purpose is to the for the correct syntax as we use many
-        // code snippets from this build script in the user's guide.
-        File projectDir = sample.dir
-        String output = executer.inDirectory(projectDir).withQuietLogging().withTasks('lookup').run().getOutput()
-        assertThat(output, equalTo(String.format("localRepository%nlocalRepository%n")))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesScalaCustomizedLayoutIntegrationTest.groovy
deleted file mode 100644
index 8244714..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesScalaCustomizedLayoutIntegrationTest.groovy
+++ /dev/null
@@ -1,51 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesScalaCustomizedLayoutIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('scala/customizedLayout')
-
-    @Test
-    public void canBuildJar() {
-        TestFile projectDir = sample.dir
-
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
-
-        // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
-        projectDir.file("build/libs/customizedLayout.jar").unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/sample/api/Person.class',
-                'org/gradle/sample/impl/PersonImpl.class'
-        )
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesScalaQuickstartIntegrationTest.groovy
deleted file mode 100644
index cf95d16..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesScalaQuickstartIntegrationTest.groovy
+++ /dev/null
@@ -1,66 +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.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import org.gradle.integtests.fixtures.Sample
-
-class SamplesScalaQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('scala/quickstart')
-
-    private TestFile projectDir
-
-    @Before
-    void setUp() {
-        projectDir = sample.dir
-    }
-
-    @Test
-    public void canBuildJar() {
-        // Build and test projects
-        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
-
-        // Check tests have run
-        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
-        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
-
-        // Check contents of Jar
-        TestFile jarContents = dist.testDir.file('jar')
-        projectDir.file("build/libs/quickstart.jar").unzipTo(jarContents)
-        jarContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'org/gradle/sample/api/Person.class',
-                'org/gradle/sample/impl/PersonImpl.class'
-        )
-    }
-
-    @Test
-    public void canBuildScalaDoc() {
-        executer.inDirectory(projectDir).withTasks('clean', 'scaladoc').run()
-
-        projectDir.file('build/docs/scaladoc/index.html').assertExists()
-        projectDir.file('build/docs/scaladoc/org/gradle/sample/api/Person.html').assertContents(containsString("Defines the interface for a person."))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesWebProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesWebProjectIntegrationTest.groovy
deleted file mode 100644
index 294ab9c..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesWebProjectIntegrationTest.groovy
+++ /dev/null
@@ -1,71 +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.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Assert
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesWebProjectIntegrationTest {
-    static final String WEB_PROJECT_NAME = 'customised'
-
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('webApplication/customised')
-
-    @Test
-    public void webProjectSamples() {
-        TestFile webProjectDir = sample.dir
-        executer.inDirectory(webProjectDir).withTasks('clean', 'assemble').run()
-        TestFile tmpDir = dist.testDir.file('unjar')
-        webProjectDir.file("build/libs/customised-1.0.war").unzipTo(tmpDir)
-        tmpDir.assertHasDescendants(
-                'root.txt',
-                'META-INF/MANIFEST.MF',
-                'WEB-INF/classes/org/gradle/HelloServlet.class',
-                'WEB-INF/classes/org/gradle/MyClass.class',
-                'WEB-INF/lib/compile-1.0.jar',
-                'WEB-INF/lib/compile-transitive-1.0.jar',
-                'WEB-INF/lib/runtime-1.0.jar',
-                'WEB-INF/lib/additional-1.0.jar',
-                'WEB-INF/lib/otherLib-1.0.jar',
-                'WEB-INF/additional.xml',
-                'WEB-INF/webapp.xml',
-                'WEB-INF/web.xml',
-                'webapp.html')
-    }
-
-    @Test
-    public void checkJettyPlugin() {
-        TestFile webProjectDir = sample.dir
-        executer.inDirectory(webProjectDir).withTasks('clean', 'runTest').run()
-        checkServletOutput(webProjectDir)
-        executer.inDirectory(webProjectDir).withTasks('clean', 'runWarTest').run()
-        checkServletOutput(webProjectDir)
-    }
-
-    static void checkServletOutput(TestFile webProjectDir) {
-        Assert.assertEquals('Hello Gradle', webProjectDir.file("build/servlet-out.txt").text)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesWebQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesWebQuickstartIntegrationTest.groovy
deleted file mode 100644
index b5c6da1..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SamplesWebQuickstartIntegrationTest.groovy
+++ /dev/null
@@ -1,65 +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.integtests
-
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesWebQuickstartIntegrationTest {
-    static final String WEB_PROJECT_NAME = 'web-project'
-
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('webApplication/quickstart')
-
-    @Test
-    public void webProjectSamples() {
-        TestFile webProjectDir = sample.dir
-        executer.inDirectory(webProjectDir).withTasks('clean', 'build').run()
-
-        // Check contents of War
-        TestFile warContents = dist.testDir.file('jar')
-        webProjectDir.file("build/libs/quickstart.war").unzipTo(warContents)
-        warContents.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'index.jsp',
-                'WEB-INF/classes/org/gradle/sample/Greeter.class',
-                'WEB-INF/classes/greeting.txt',
-                'WEB-INF/lib/log4j-1.2.15.jar',
-                'WEB-INF/lib/commons-io-1.4.jar',
-        )
-        
-        ExecutionResult result = executer.inDirectory(webProjectDir).withTasks('clean', 'runTest').run()
-        checkServletOutput(result)
-        result = executer.inDirectory(webProjectDir).withTasks('clean', 'runWarTest').run()
-        checkServletOutput(result)
-    }
-
-    static void checkServletOutput(ExecutionResult result) {
-        assertThat(result.output, containsString('hello Gradle'))
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..218ce50
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.WellBehavedPluginTest
+
+class ScalaPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "build"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
index ce7bac6..999ebaa 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
@@ -15,19 +15,11 @@
  */
 package org.gradle.integtests;
 
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.junit.Test;
 
 public class ScalaProjectIntegrationTest extends AbstractIntegrationTest {
     @Test
-    public void handlesEmptyProject() {
-        testFile("build.gradle").writelns(
-                "apply plugin: 'scala'"
-        );
-        inTestDirectory().withTasks("build").run();
-    }
-
-    @Test
     public void handlesJavaSourceOnly() {
         testFile("src/main/java/somepackage/SomeClass.java").writelns("public class SomeClass { }");
         testFile("build.gradle").write("apply plugin: 'scala'");
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
index a952b4e..daf42f1 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
@@ -16,8 +16,8 @@
 
 package org.gradle.integtests;
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.integtests.fixtures.ExecutionFailure;
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
 import org.gradle.util.TestFile;
 import org.junit.Test;
 
@@ -26,12 +26,10 @@ import java.io.IOException;
 public class SettingsScriptErrorIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void reportsSettingsScriptEvaluationFailsWithRuntimeException() throws IOException {
-        TestFile buildFile = testFile("some build.gradle");
         TestFile settingsFile = testFile("some settings.gradle");
         settingsFile.writelns("", "", "throw new RuntimeException('<failure message>')");
 
-        ExecutionFailure failure = usingBuildFile(buildFile).usingSettingsFile(settingsFile).withTasks("do-stuff")
-                .runWithFailure();
+        ExecutionFailure failure = executer.usingSettingsFile(settingsFile).runWithFailure();
 
         failure.assertHasFileName(String.format("Settings file '%s'", settingsFile));
         failure.assertHasLineNumber(3);
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
index ac96950..8597246 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
@@ -21,28 +21,36 @@ import org.gradle.util.TestFile
 import org.junit.Test
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class SettingsScriptExecutionIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void executesSettingsScriptWithCorrectEnvironment() {
         createExternalJar()
         createBuildSrc()
+        def implClassName = 'com.google.common.collect.Multimap'
 
-        testFile('settings.gradle') << '''
+        testFile('settings.gradle') << """
 buildscript {
     dependencies { classpath files('repo/test-1.3.jar') }
 }
 new org.gradle.test.BuildClass()
 new BuildSrcClass();
 println 'quiet message'
-captureStandardOutput(LogLevel.ERROR)
+logging.captureStandardOutput(LogLevel.ERROR)
 println 'error message'
 assert settings != null
 assert buildscript.classLoader == getClass().classLoader.parent
 assert buildscript.classLoader == Thread.currentThread().contextClassLoader
-assert gradle.scriptClassLoader.parent == buildscript.classLoader.parent.parent
-'''
+assert gradle.scriptClassLoader.parents[0] == buildscript.classLoader.parent.parent
+Gradle.class.classLoader.loadClass('${implClassName}')
+try {
+    buildscript.classLoader.loadClass('${implClassName}')
+    assert false: 'should fail'
+} catch (ClassNotFoundException e) {
+    // expected
+}
+"""
         testFile('build.gradle') << 'task doStuff'
 
         ExecutionResult result = inTestDirectory().withTasks('doStuff').run()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy
new file mode 100644
index 0000000..671d962
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/StdioIntegrationTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.AbstractIntegrationSpec
+import org.gradle.util.TextUtil
+
+class StdioIntegrationTest extends AbstractIntegrationSpec {
+
+    def "build can read stdin when stdin has bounded length"() {
+        given:
+        distribution.requireOwnUserHomeDir()
+        buildFile << '''
+task echo << {
+    def reader = new BufferedReader(new InputStreamReader(System.in))
+    while (true) {
+        def line = reader.readLine() // readline will chomp the newline off the end
+        if (!line || line == 'close') {
+            break
+        }
+        print "[$line]"
+    }
+}
+'''
+
+        when:
+        executer.withStdIn("abc\n123").withArguments("-s", "--info")
+        run "echo"
+
+        then:
+        output.contains("[abc][123]")
+    }
+
+    def "build can read stdin when stdin has unbounded length"() {
+        def writeEnd = new PipedOutputStream()
+        def readEnd = new PipedInputStream(writeEnd)
+
+        given:
+        distribution.requireOwnUserHomeDir()
+        buildFile << '''
+task echo << {
+    def reader = new BufferedReader(new InputStreamReader(System.in))
+    while (true) {
+        def line = reader.readLine() // readline will chomp the newline off the end
+        if (!line || line == 'close') {
+            break
+        }
+        print "[$line]"
+    }
+}
+'''
+        and:
+        writeEnd.write(TextUtil.toPlatformLineSeparators("abc\n123\nclose\n").bytes)
+
+        when:
+        executer.withStdIn(readEnd).withArguments("-s", "--info")
+        run "echo"
+
+        then:
+        output.contains("[abc][123]")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy
index ae1fe80..58e1468 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.integtests
 
 import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class SyncTaskIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy
index 904aa60..dc430b5 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.integtests
 
 import org.junit.Ignore
 import org.junit.Test
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class TaskAutoDependencyIntegrationTest extends AbstractIntegrationTest {
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java
index 9a0db6c..cff88d9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java
@@ -16,7 +16,7 @@
 
 package org.gradle.integtests;
 
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.junit.Test;
 
 public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
@@ -36,7 +36,7 @@ public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void canDefineTasksUsingTaskKeywordAndGString() {
         testFile("build.gradle").writelns(
-                "v = 'Task'",
+                "ext.v = 'Task'",
                 "task \"nothing$v\"",
                 "task \"withAction$v\" << { }",
                 "task \"emptyOptions$v\"()",
@@ -74,9 +74,9 @@ public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void canDefineTasksUsingTaskMethodExpression() {
         testFile("build.gradle").writelns(
-                "a = 'a' == 'b' ? null: task(withAction) << { }",
+                "ext.a = 'a' == 'b' ? null: task(withAction) << { }",
                 "a = task(nothing)",
-                "a = task(emptyOptions())", "taskName = 'dynamic'",
+                "a = task(emptyOptions())", "ext.taskName = 'dynamic'",
                 "a = task(\"$taskName\") << { }",
                 "a = task('string')",
                 "a = task('stringWithAction') << { }",
@@ -84,7 +84,8 @@ public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
                 "a = task('stringWithOptionsAndAction', description: 'description') << { }",
                 "a = task(withOptions, description: 'description')",
                 "a = task(withOptionsAndAction, description: 'description') << { }",
-                "a = task(anotherWithAction).doFirst\n{}", "task all(dependsOn: tasks.all)");
+                "a = task(anotherWithAction).doFirst\n{}",
+                "task all(dependsOn: tasks as List)");
         inTestDirectory().withTasks("all").run().assertTasksExecuted(":anotherWithAction", ":dynamic", ":emptyOptions",
                 ":nothing", ":string", ":stringWithAction", ":stringWithOptions", ":stringWithOptionsAndAction",
                 ":withAction", ":withOptions", ":withOptionsAndAction", ":all");
@@ -97,7 +98,7 @@ public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
                 "task(asMethod)\n{ description = 'value' }",
                 "task asStatement(type: TestTask) { property = 'value' }",
                 "task \"dynamic\"(type: TestTask) { property = 'value' }",
-                "v = task(asExpression, type: TestTask) { property = 'value' }",
+                "ext.v = task(asExpression, type: TestTask) { property = 'value' }",
                 "task(postConfigure, type: TestTask).configure { property = 'value' }",
                 "[asStatement, dynamic, asExpression, postConfigure].each { ",
                 "    assert 'value' == it.property",
@@ -105,7 +106,7 @@ public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
                 "[withDescription, asMethod].each {",
                 "    assert 'value' == it.description",
                 "}",
-                "task all(dependsOn: tasks.all)",
+                "task all(dependsOn: tasks as List)",
                 "class TestTask extends DefaultTask { String property }");
         inTestDirectory().withTasks("all").run();
     }
@@ -120,17 +121,17 @@ public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
                 "task taskNameMethod('d')",
                 "def addTaskMethod(String methodParam) { task methodParam }",
                 "addTaskMethod('e')",
-                "def addTaskWithClosure(String methodParam) { task(methodParam) { property = 'value' } }",
+                "def addTaskWithClosure(String methodParam) { task(methodParam) { ext.property = 'value' } }",
                 "addTaskWithClosure('f')",
                 "def addTaskWithMap(String methodParam) { task(methodParam, description: 'description') }",
                 "addTaskWithMap('g')",
-                "cl = { String taskNameParam -> task taskNameParam }",
+                "ext.cl = { String taskNameParam -> task taskNameParam }",
                 "cl.call('h')",
-                "cl = { String taskNameParam -> task(taskNameParam) { property = 'value' } }",
+                "cl = { String taskNameParam -> task(taskNameParam) { ext.property = 'value' } }",
                 "cl.call('i')",
                 "assert 'value' == f.property",
                 "assert 'value' == i.property",
-                "task all(dependsOn: tasks.all)");
+                "task all(dependsOn: tasks as List)");
         inTestDirectory().withTasks("all").run().assertTasksExecuted(":a", ":d", ":e", ":f", ":g", ":h", ":i", ":all");
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
index 13b82b1..d0f4cb1 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskErrorExecutionIntegrationTest.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.util.TestFile
 import org.junit.Test
@@ -27,14 +27,14 @@ class TaskErrorExecutionIntegrationTest extends AbstractIntegrationTest {
         buildFile.writelns(
                 "task('do-stuff').doFirst",
                 "{",
-                "1/0",
+                "throw new ArithmeticException('broken')",
                 "}")
         ExecutionFailure failure = usingBuildFile(buildFile).withTasks("do-stuff").runWithFailure()
 
         failure.assertHasFileName(String.format("Build file '%s'", buildFile))
         failure.assertHasLineNumber(3)
         failure.assertHasDescription("Execution failed for task ':do-stuff'")
-        failure.assertHasCause("Division by zero")
+        failure.assertHasCause("broken")
     }
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
index 8360373..5e833ba 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
@@ -16,11 +16,12 @@
 
 package org.gradle.integtests;
 
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.util.TestFile;
 import org.junit.Test;
+import spock.lang.Issue;
 
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.startsWith;
 
 public class TaskExecutionIntegrationTest extends AbstractIntegrationTest {
     @Test
@@ -55,8 +56,8 @@ public class TaskExecutionIntegrationTest extends AbstractIntegrationTest {
     public void executesAllTasksInASingleBuildAndEachTaskAtMostOnce() {
         TestFile buildFile = testFile("build.gradle");
         buildFile.writelns(
-                "gradle.taskGraph.whenReady { assert !project.hasProperty('graphReady'); graphReady = true }",
-                "task a << { task -> project.executedA = task }",
+                "gradle.taskGraph.whenReady { assert !project.hasProperty('graphReady'); ext.graphReady = true }",
+                "task a << { task -> project.ext.executedA = task }",
                 "task b << { ",
                 "    assert a == project.executedA",
                 "    assert gradle.taskGraph.hasTask(':a')",
@@ -122,6 +123,22 @@ public class TaskExecutionIntegrationTest extends AbstractIntegrationTest {
     }
 
     @Test
+    public void executesTaskActionsInCorrectEnvironment() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                // An action attached to built-in task
+                "task a << { assert Thread.currentThread().contextClassLoader == getClass().classLoader }",
+                // An action defined by a custom task
+                "task b(type: CustomTask)",
+                "class CustomTask extends DefaultTask { @TaskAction def go() { assert Thread.currentThread().contextClassLoader == getClass().classLoader } } ",
+                // An action implementation
+                "task c; c.doLast new Action<Task>() { void execute(Task t) { assert Thread.currentThread().contextClassLoader == getClass().classLoader } }"
+        );
+
+        usingBuildFile(buildFile).withTasks("a", "b", "c").run();
+    }
+
+    @Test
     public void excludesTasksWhenExcludePatternSpecified() {
         testFile("settings.gradle").write("include 'sub'");
         TestFile buildFile = testFile("build.gradle");
@@ -151,4 +168,13 @@ public class TaskExecutionIntegrationTest extends AbstractIntegrationTest {
         // Unknown task
         usingBuildFile(buildFile).withTasks("d").withArguments("-x", "unknown").runWithFailure().assertThatDescription(startsWith("Task 'unknown' not found in root project"));
     }
+
+    @Test
+    @Issue("http://issues.gradle.org/browse/GRADLE-2022")
+    public void tryingToInstantiateTaskDirectlyFailsWithGoodErrorMessage() {
+        usingBuildFile(testFile("build.gradle").write("new DefaultTask()")).
+        withTasks("tasks").
+        runWithFailure().
+        assertHasCause("Task of type 'org.gradle.api.DefaultTask' has been instantiated directly which is not supported");
+    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UnicastMessagingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UnicastMessagingIntegrationTest.groovy
new file mode 100644
index 0000000..fbe0d93
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UnicastMessagingIntegrationTest.groovy
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2011 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 java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+import org.gradle.api.Action
+import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.MessagingClient
+import org.gradle.messaging.remote.MessagingServer
+import org.gradle.messaging.remote.ObjectConnection
+import org.gradle.messaging.remote.internal.MessagingServices
+import org.gradle.util.ConcurrentSpecification
+import java.util.concurrent.CountDownLatch
+import spock.lang.Ignore
+
+ at Ignore
+class UnicastMessagingIntegrationTest extends ConcurrentSpecification {
+    def "server can send messages to client"() {
+        RemoteService1 service = Mock()
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        start {
+            client.addIncoming(service)
+        }
+        start {
+            server.outgoingService1.doStuff("1")
+            server.outgoingService1.doStuff("2")
+            server.outgoingService1.doStuff("3")
+            server.outgoingService1.doStuff("4")
+        }
+        finished()
+        server.stop()
+        client.stop()
+
+        then:
+        1 * service.doStuff("1")
+        1 * service.doStuff("2")
+        1 * service.doStuff("3")
+        1 * service.doStuff("4")
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client can send messages to server"() {
+        RemoteService1 service = Mock()
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        start {
+            server.addIncoming(service)
+        }
+        start {
+            client.outgoingService1.doStuff("1")
+            client.outgoingService1.doStuff("2")
+            client.outgoingService1.doStuff("3")
+            client.outgoingService1.doStuff("4")
+        }
+        finished()
+        client.stop()
+        server.stop()
+
+        then:
+        1 * service.doStuff("1")
+        1 * service.doStuff("2")
+        1 * service.doStuff("3")
+        1 * service.doStuff("4")
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "server and client can both send same type of message"() {
+        RemoteService1 serverService = Mock()
+        RemoteService1 clientService = Mock()
+        def received = new CountDownLatch(2)
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        start {
+            server.addIncoming(serverService)
+            server.outgoingService1.doStuff("from server 1")
+            server.outgoingService1.doStuff("from server 2")
+        }
+        start {
+            client.addIncoming(clientService)
+            client.outgoingService1.doStuff("from client 1")
+            client.outgoingService1.doStuff("from client 2")
+        }
+        received.await()
+        client.stop()
+        server.stop()
+
+        then:
+        1 * serverService.doStuff("from client 1")
+        1 * serverService.doStuff("from client 2") >> { received.countDown() }
+        1 * clientService.doStuff("from server 1")
+        1 * clientService.doStuff("from server 2") >> { received.countDown() }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client and server can each send different types of messages"() {
+        RemoteService1 serverService = Mock()
+        RemoteService2 clientService = Mock()
+        def done = new CountDownLatch(2)
+        def server = new Server()
+        def client = new Client(server.address)
+
+        when:
+        start {
+            server.addIncoming(serverService)
+            server.outgoingService2.doStuff("server1")
+            server.outgoingService2.doStuff("server2")
+        }
+        start {
+            client.addIncoming(clientService)
+            client.outgoingService1.doStuff("client1")
+            client.outgoingService1.doStuff("client2")
+        }
+        done.await()
+        client.stop()
+        server.stop()
+
+        then:
+        1 * serverService.doStuff("client1")
+        1 * serverService.doStuff("client2") >> { done.countDown() }
+        1 * clientService.doStuff("server1")
+        1 * clientService.doStuff("server2") >> { done.countDown() }
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    def "client can start sending before server has started"() {
+        RemoteService1 service = Mock()
+        def server = new Server()
+        def client
+
+        when:
+        start {
+            client = new Client(server.address)
+            client.outgoingService1.doStuff("1")
+            client.outgoingService1.doStuff("2")
+            client.stop()
+        }
+        start {
+            server.addIncoming(service)
+        }
+        finished()
+        server.stop()
+        client?.stop()
+
+        then:
+        1 * service.doStuff("1")
+        1 * service.doStuff("2")
+
+        cleanup:
+        client?.stop()
+        server?.stop()
+    }
+
+    abstract class Participant {
+        final def services = new MessagingServices(getClass().classLoader)
+        private RemoteService1 remoteService1
+        private RemoteService2 remoteService2
+
+        abstract ObjectConnection getConnection()
+
+        void addIncoming(RemoteService1 value) {
+            connection.addIncoming(RemoteService1.class, value)
+        }
+
+        void addIncoming(RemoteService2 value) {
+            connection.addIncoming(RemoteService2.class, value)
+        }
+
+        RemoteService1 getOutgoingService1() {
+            if (remoteService1 == null) {
+                remoteService1 = connection.addOutgoing(RemoteService1)
+            }
+            return remoteService1
+        }
+
+        RemoteService2 getOutgoingService2() {
+            if (remoteService2 == null) {
+                remoteService2 = connection.addOutgoing(RemoteService2)
+            }
+            return remoteService2
+        }
+
+        void stop() {
+            services.stop()
+        }
+    }
+
+    class Server extends Participant {
+        private final Lock lock = new ReentrantLock()
+        private final Condition condition = lock.newCondition()
+        private ObjectConnection connection
+        final Address address
+
+        Server() {
+            address = services.get(MessagingServer.class).accept({ event ->
+                lock.lock()
+                try {
+                    connection = event.connection
+                    condition.signalAll()
+                } finally {
+                    lock.unlock()
+                }
+            } as Action)
+        }
+
+        @Override
+        ObjectConnection getConnection() {
+            lock.lock()
+            try {
+                while (connection == null) {
+                    condition.await()
+                }
+                return connection
+            } finally {
+                lock.unlock()
+            }
+        }
+    }
+
+    class Client extends Participant {
+        final ObjectConnection connection
+
+        Client(Address serverAddress) {
+            connection = services.get(MessagingClient.class).getConnection(serverAddress)
+        }
+    }
+}
+
+interface RemoteService1 {
+    def doStuff(String value)
+}
+
+interface RemoteService2 {
+    def doStuff(String value)
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesIntegrationTest.groovy
deleted file mode 100644
index 7bd237a..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesIntegrationTest.groovy
+++ /dev/null
@@ -1,32 +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
-
- at RunWith(UserGuideSamplesRunner.class)
-class UserGuideSamplesIntegrationTest {
-    /*
-
-    Important info:
-
-     If you're working in samples area there're gradle tasks that you should know of:
-     - gradle intTestImage makes sure that the samples' resources are copied to the right place
-     - gradle docs (or one of its dependent tasks) makes sure that samples' info is extracted from XMLs
-
-    */
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
deleted file mode 100644
index cfd303d..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
+++ /dev/null
@@ -1,274 +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 com.google.common.collect.ArrayListMultimap
-import com.google.common.collect.ListMultimap
-import groovy.io.PlatformLineWriter
-import junit.framework.AssertionFailedError
-import org.apache.tools.ant.taskdefs.Delete
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.AntUtil
-import org.gradle.util.SystemProperties
-import org.junit.Assert
-import org.junit.runner.Description
-import org.junit.runner.Runner
-import org.junit.runner.notification.Failure
-import org.junit.runner.notification.RunNotifier
-
-class UserGuideSamplesRunner extends Runner {
-    private static final String NL = SystemProperties.lineSeparator
-
-    Class<?> testClass
-    Description description
-    Map<Description, SampleRun> samples;
-    GradleDistribution dist = new GradleDistribution()
-    GradleDistributionExecuter executer = new GradleDistributionExecuter(dist)
-
-    def UserGuideSamplesRunner(Class<?> testClass) {
-        this.testClass = testClass
-        this.description = Description.createSuiteDescription(testClass)
-        samples = new LinkedHashMap()
-        for (sample in getScriptsForSamples(dist.userGuideInfoDir)) {
-            Description childDescription = Description.createTestDescription(testClass, sample.id)
-            description.addChild(childDescription)
-            samples.put(childDescription, sample)
-
-            println "Sample $sample.id dir: $sample.subDir"
-            sample.runs.each { println "    args: $it.args expect: $it.outputFile" }
-        }
-    }
-
-    Description getDescription() {
-        return description
-    }
-
-    void run(RunNotifier notifier) {
-        for (childDescription in description.children) {
-            notifier.fireTestStarted(childDescription)
-            SampleRun sampleRun = samples.get(childDescription)
-            try {
-                cleanup(sampleRun)
-                for (run in sampleRun.runs) {
-                    runSample(run)
-                }
-            } catch (Throwable t) {
-                notifier.fireTestFailure(new Failure(childDescription, t))
-            }
-            notifier.fireTestFinished(childDescription)
-        }
-    }
-
-    private def cleanup(SampleRun run) {
-        // Clean up previous runs
-        File rootProjectDir = dist.samplesDir.file(run.subDir)
-        if (rootProjectDir.exists()) {
-            def delete = new Delete()
-            delete.dir = rootProjectDir
-            delete.includes = "**/.gradle/** **/build/**"
-            AntUtil.execute(delete)
-        }
-    }
-
-    private def runSample(GradleRun run) {
-        try {
-            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)
-
-            ExecutionResult result = run.expectFailure ? executer.runWithFailure() : executer.run()
-            if (run.outputFile) {
-                String expectedResult = replaceWithPlatformNewLines(dist.userGuideOutputDir.file(run.outputFile).text)
-                try {
-                    compareStrings(expectedResult, result.output, run.ignoreExtraLines)
-                } catch (AssertionFailedError e) {
-                    println 'Expected Result:'
-                    println expectedResult
-                    println 'Actual Result:'
-                    println result.output
-                    println '---'
-                    throw e
-                }
-            }
-
-            run.files.each { path ->
-                println "  checking file '$path' exists"
-                File file = new File(rootProjectDir, path).canonicalFile
-                Assert.assertTrue("Expected file '$file' does not exist.", file.exists())
-                Assert.assertTrue("Expected file '$file' is not a file.", file.isFile())
-            }
-            run.dirs.each { path ->
-                println "  checking directory '$path' exists"
-                File file = new File(rootProjectDir, path).canonicalFile
-                Assert.assertTrue("Expected directory '$file' does not exist.", file.exists())
-                Assert.assertTrue("Expected directory '$file' is not a directory.", file.isDirectory())
-            }
-        } catch (Throwable e) {
-            throw new AssertionError("Integration test for sample '$run.id' in dir '$run.subDir' with args $run.args failed:${NL}$e.message").initCause(e)
-        }
-    }
-
-    private def compareStrings(String expected, String actual, boolean ignoreExtraLines) {
-        List actualLines = normaliseOutput(actual.readLines())
-        List expectedLines = expected.readLines()
-        int pos = 0
-        for (; pos < actualLines.size() && pos < expectedLines.size(); pos++) {
-            String expectedLine = expectedLines[pos]
-            String actualLine = actualLines[pos]
-            boolean matches = compare(expectedLine, actualLine)
-            if (!matches) {
-                if (expectedLine.contains(actualLine)) {
-                    Assert.fail("Missing text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-                }
-                if (actualLine.contains(expectedLine)) {
-                    Assert.fail("Extra text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-                }
-                Assert.fail("Unexpected value at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
-            }
-        }
-        if (pos == actualLines.size() && pos < expectedLines.size()) {
-            Assert.fail("Lines missing from actual result, starting at line ${pos + 1}.${NL}Expected: ${expectedLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
-        }
-        if (!ignoreExtraLines && pos < actualLines.size() && pos == expectedLines.size()) {
-            Assert.fail("Extra lines in actual result, starting at line ${pos + 1}.${NL}Actual: ${actualLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
-        }
-    }
-
-    static String replaceWithPlatformNewLines(String text) {
-        StringWriter stringWriter = new StringWriter()
-        new PlatformLineWriter(stringWriter).withWriter { it.write(text) }
-        stringWriter.toString()
-    }
-
-    List<String> normaliseOutput(List<String> lines) {
-        lines.inject(new ArrayList<String>()) { List values, String line ->
-            if (line.matches('Download .+')) {
-                // ignore
-            } else {
-                values << line
-            }
-            values
-        }
-    }
-
-    boolean compare(String expected, String actual) {
-        if (actual == expected) {
-            return true
-        }
-
-        if (expected == 'Total time: 1 secs') {
-            return actual.matches('Total time: .+ secs')
-        }
-        
-        // Normalise default object toString() values
-        actual = actual.replaceAll('(\\w+(\\.\\w+)*)@\\p{XDigit}+', '$1 at 12345')
-        // Normalise $samplesDir
-        actual = actual.replaceAll(java.util.regex.Pattern.quote(dist.samplesDir.absolutePath), '/home/user/gradle/samples')
-        // Normalise file separators
-        actual = actual.replaceAll(java.util.regex.Pattern.quote(File.separator), '/')
-
-        return actual == expected
-    }
-
-    static Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
-        def samplesXml = new File(userguideInfoDir, 'samples.xml')
-        assertSamplesGenerated(samplesXml.exists())
-        Node samples = new XmlParser().parse(samplesXml)
-        ListMultimap<String, GradleRun> samplesByDir = ArrayListMultimap.create()
-
-        def children = samples.children()
-        assertSamplesGenerated(!children.isEmpty())
-
-        children.each {Node sample ->
-            String id = sample.'@id'
-            String dir = sample.'@dir'
-            String args = sample.'@args'
-            String outputFile = sample.'@outputFile'
-            boolean ignoreExtraLines = Boolean.valueOf(sample.'@ignoreExtraLines')
-
-            GradleRun run = new GradleRun(id: id)
-            run.subDir = dir
-            run.args = args ? args.split('\\s+') as List : []
-            run.outputFile = outputFile
-            run.ignoreExtraLines = ignoreExtraLines as boolean
-
-            sample.file.each { file -> run.files << file.'@path' }
-            sample.dir.each { file -> run.dirs << file.'@path' }
-
-            samplesByDir.put(dir, run)
-        }
-
-        // Some custom values
-        samplesByDir.get('userguide/tutorial/properties').each { it.envs['ORG_GRADLE_PROJECT_envProjectProp'] = 'envPropertyValue' }
-        samplesByDir.get('userguide/buildlifecycle/taskExecutionEvents')*.expectFailure = true
-        samplesByDir.get('userguide/buildlifecycle/buildProjectEvaluateEvents')*.expectFailure = true
-
-        Map<String, SampleRun> samplesById = new TreeMap<String, SampleRun>()
-
-        // Remove duplicates for a given directory.
-        samplesByDir.asMap().values().collect {List<GradleRun> dirSamples ->
-            Collection<GradleRun> runs = dirSamples.findAll {it.mustRun}
-            if (!runs) {
-                // No samples in this dir have any args, so just run gradle tasks in the dir
-                def sample = dirSamples[0]
-                sample.args = ['tasks']
-                sample
-            } else {
-                return runs
-            }
-        }.flatten().each { GradleRun run ->
-            // Collect up into sample runs
-            SampleRun sampleRun = samplesById[run.id]
-            if (!sampleRun) {
-                sampleRun = new SampleRun(id: run.id, subDir: run.subDir)
-                samplesById[run.id] = sampleRun
-            }
-            sampleRun.runs << run
-        }
-
-        return samplesById.values()
-    }
-
-    static void assertSamplesGenerated(boolean assertion) {
-        assert assertion : """Couldn't find any samples. Most likely, samples.xml was not generated.
-Please run 'gradle check devBuild' first (you can skip tests in this case)
-Probably some other task can help you as well, please try: 'gradle docs intTestImage'"""
-    }
-}
-
-class SampleRun {
-    String id
-    String subDir
-    List<GradleRun> runs = []
-}
-
-class GradleRun {
-    String id
-    List args = []
-    String subDir
-    Map envs = [:]
-    String outputFile
-    boolean expectFailure
-    boolean ignoreExtraLines
-    List files = []
-    List dirs = []
-
-    boolean getMustRun() {
-        return args || files || dirs
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WarPluginGoodBehaviourTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WarPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..1e0af5f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WarPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.WellBehavedPluginTest
+
+class WarPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "build"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
index 26fee4b..cc4298d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.integtests.fixtures.ExecutionResult
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.SystemProperties
+import org.gradle.internal.SystemProperties
 import org.junit.Rule
 import org.junit.Test
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
index f438505..d7463ec 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
@@ -15,22 +15,12 @@
  */
 package org.gradle.integtests;
 
-import org.gradle.integtests.fixtures.internal.AbstractIntegrationTest;
+import org.gradle.integtests.fixtures.AbstractIntegrationTest;
 import org.gradle.util.TestFile;
 import org.junit.Test;
 
 public class WebProjectIntegrationTest extends AbstractIntegrationTest {
     @Test
-    public void handlesEmptyProject() {
-        TestFile buildFile = testFile("build.gradle");
-        buildFile.writelns(
-                "apply plugin: 'war'"
-        );
-
-        usingBuildFile(buildFile).withTasks("build").run();
-    }
-
-    @Test
     public void createsAWar() {
         testFile("settings.gradle").writelns("rootProject.name = 'test'");
         TestFile buildFile = testFile("build.gradle");
@@ -49,8 +39,7 @@ public class WebProjectIntegrationTest extends AbstractIntegrationTest {
         TestFile buildFile = testFile("build.gradle");
         buildFile.writelns(
                 "apply plugin: 'war'",
-                "jar.enabled = true",
-                "buildDirName = 'output'",
+                "buildDir = 'output'",
                 "libsDirName = 'archives'",
                 "archivesBaseName = 'test'",
                 "version = '0.5-RC2'"
@@ -58,7 +47,6 @@ public class WebProjectIntegrationTest extends AbstractIntegrationTest {
         testFile("src/main/resources/org/gradle/resource.file").write("some resource");
 
         usingBuildFile(buildFile).withTasks("assemble").run();
-        testFile("output/archives/test-0.5-RC2.jar").assertIsFile();
         testFile("output/archives/test-0.5-RC2.war").assertIsFile();
     }
 
@@ -68,13 +56,11 @@ public class WebProjectIntegrationTest extends AbstractIntegrationTest {
         TestFile buildFile = testFile("build.gradle");
         buildFile.writelns(
                 "apply plugin: 'war'",
-                "jar.enabled = true",
                 "version = ''"
         );
         testFile("src/main/resources/org/gradle/resource.file").write("some resource");
 
         usingBuildFile(buildFile).withTasks("assemble").run();
-        testFile("build/libs/empty.jar").assertIsFile();
         testFile("build/libs/empty.war").assertIsFile();
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
index d6ac43a..a9efcc6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
@@ -20,16 +20,23 @@ import org.apache.tools.ant.Project;
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathProvider;
 import org.gradle.api.internal.DefaultClassPathRegistry;
-import org.gradle.api.internal.file.BaseDirConverter;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.ModuleRegistry;
+import org.gradle.api.internal.file.BaseDirFileResolver;
 import org.gradle.api.logging.LogLevel;
-import org.gradle.cache.DefaultCacheFactory;
-import org.gradle.cache.DefaultCacheRepository;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.internal.*;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.listener.ListenerBroadcast;
-import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.messaging.remote.internal.TcpMessagingServer;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.MessagingServices;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.process.internal.*;
 import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
 import org.gradle.util.LongIdGenerator;
@@ -38,9 +45,7 @@ import org.jmock.Expectations;
 import org.jmock.Sequence;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.*;
 import org.junit.runner.RunWith;
 
 import java.io.ObjectInputStream;
@@ -56,10 +61,15 @@ import static org.junit.Assert.*;
 public class WorkerProcessIntegrationTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final TestListenerInterface listenerMock = context.mock(TestListenerInterface.class);
-    private final TcpMessagingServer server = new TcpMessagingServer(getClass().getClassLoader());
+    private final MessagingServices messagingServices = new MessagingServices(getClass().getClassLoader());
+    private final MessagingServer server = messagingServices.get(MessagingServer.class);
     @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
-    private final ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new WorkerProcessClassPathProvider(new DefaultCacheRepository(tmpDir.getDir(), CacheUsage.ON, new DefaultCacheFactory())));
-    private final DefaultWorkerProcessFactory workerFactory = new DefaultWorkerProcessFactory(LogLevel.INFO, server, classPathRegistry, new BaseDirConverter(tmpDir.getTestDir()), new LongIdGenerator());
+    private final ProcessMetaDataProvider metaDataProvider = new DefaultProcessMetaDataProvider(new NativeServices().get(ProcessEnvironment.class));
+    private final CacheFactory factory = new DefaultCacheFactory(new DefaultFileLockManager(metaDataProvider)).create();
+    private final CacheRepository cacheRepository = new DefaultCacheRepository(tmpDir.getDir(), null, CacheUsage.ON, factory);
+    private final ModuleRegistry moduleRegistry = new DefaultModuleRegistry();
+    private final ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(moduleRegistry), new WorkerProcessClassPathProvider(cacheRepository, moduleRegistry));
+    private final DefaultWorkerProcessFactory workerFactory = new DefaultWorkerProcessFactory(LogLevel.INFO, server, classPathRegistry, new BaseDirFileResolver(FileSystems.getDefault(), tmpDir.getTestDir()), new LongIdGenerator());
     private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(
             TestListenerInterface.class);
     private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast);
@@ -69,6 +79,11 @@ public class WorkerProcessIntegrationTest {
         broadcast.add(listenerMock);
     }
 
+    @After
+    public void tearDown() {
+        messagingServices.stop();
+    }
+
     @Test
     public void workerProcessCanSendMessagesToThisProcess() throws Throwable {
         context.checking(new Expectations() {{
@@ -113,7 +128,7 @@ public class WorkerProcessIntegrationTest {
         execute(worker(new RemoteProcess()), worker(new OtherRemoteProcess()));
     }
 
-    @Test
+    @Test @Ignore
     public void handlesWorkerProcessWhichCrashes() throws Throwable {
         context.checking(new Expectations() {{
             atMost(1).of(listenerMock).send("message 1", 1);
@@ -163,7 +178,7 @@ public class WorkerProcessIntegrationTest {
         for (ChildProcess process : processes) {
             process.waitForStop();
         }
-        server.stop();
+        messagingServices.stop();
         exceptionListener.rethrow();
     }
 
@@ -191,7 +206,7 @@ public class WorkerProcessIntegrationTest {
 
         public void start() {
             WorkerProcessBuilder builder = workerFactory.create();
-            builder.applicationClasspath(classPathRegistry.getClassPathFiles("ANT"));
+            builder.applicationClasspath(classPathRegistry.getClassPath("ANT").getAsFiles());
             builder.sharedPackages("org.apache.tools.ant");
             builder.getJavaCommand().systemProperty("test.system.property", "value");
             builder.getJavaCommand().environment("TEST_ENV_VAR", "value");
@@ -275,7 +290,11 @@ public class WorkerProcessIntegrationTest {
             assertThat(antClassLoader, not(sameInstance(systemClassLoader)));
             assertThat(thisClassLoader, not(sameInstance(systemClassLoader)));
             assertThat(antClassLoader.getParent(), equalTo(systemClassLoader.getParent()));
-            assertThat(thisClassLoader.getParent().getParent().getParent(), sameInstance(antClassLoader));
+            try {
+                assertThat(thisClassLoader.loadClass(Project.class.getName()), sameInstance((Object)Project.class));
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException(e);
+            }
 
             // Send some messages
             TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..e493c13
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperCrossVersionIntegrationTest.groovy
@@ -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.integtests
+
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+
+class WrapperCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    public void canUseWrapperFromPreviousVersionToRunCurrentVersion() {
+        expect:
+        checkWrapperWorksWith(previous, current)
+    }
+
+    public void canUseWrapperFromCurrentVersionToRunPreviousVersion() {
+        expect:
+        checkWrapperWorksWith(current, previous)
+    }
+
+    void checkWrapperWorksWith(BasicGradleDistribution wrapperGenVersion, BasicGradleDistribution executionVersion) {
+        if (!wrapperGenVersion.wrapperCanExecute(executionVersion.version)) {
+            println "skipping $wrapperGenVersion as its wrapper cannot execute version ${executionVersion.version}"
+            return
+        }
+
+        println "use wrapper from $wrapperGenVersion to build using $executionVersion"
+
+        buildFile << """
+
+task wrapper(type: Wrapper) {
+    gradleVersion = '$executionVersion.version'
+}
+
+//(SF) not sure if we want to keep coverage for old 'urlRoot' that was already removed
+//I'm keeping it so that old versions are tested via the urlRoot.
+if (wrapper.hasProperty('urlRoot')) {
+    println "configuring the wrapper using the old way: 'urlRoot'..."
+    wrapper.urlRoot = '${executionVersion.binDistribution.parentFile.toURI()}'
+} else {
+    println "configuring the wrapper using the new way: 'distributionUrl'..."
+    wrapper.distributionUrl = '${executionVersion.binDistribution.toURI()}'
+}
+
+println "using Java version \${System.getProperty('java.version')}"
+
+task hello {
+    doLast { println "hello from \$gradle.gradleVersion" }
+}
+"""
+        version(wrapperGenVersion).withTasks('wrapper').run()
+        def result = version(wrapperGenVersion).usingExecutable('gradlew').withTasks('hello').run()
+        assert result.output.contains("hello from $executionVersion.version")
+    }
+}
+
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
index 07c963a..6596215 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
@@ -16,41 +16,157 @@
 
 package org.gradle.integtests
 
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TextUtil
 import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.integtests.fixtures.ExecutionFailure
+import spock.lang.Issue
+import org.gradle.integtests.fixtures.*
+import static org.hamcrest.Matchers.containsString
+import static org.junit.Assert.assertThat
 
 /**
  * @author Hans Dockter
  */
-class WrapperProjectIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('wrapper-project')
+class WrapperProjectIntegrationTest extends AbstractIntegrationSpec {
+    @Rule HttpServer server = new HttpServer()
+    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
 
-    @Test
-    public void hasNonZeroExitCodeOnBuildFailure() {
-        File wrapperSampleDir = sample.dir
+    void setup() {
+        server.start()
+    }
+    
+    GradleDistributionExecuter getWrapperExecuter() {
+        executer.usingExecutable('gradlew').inDirectory(testDir)
+    }
+
+    private prepareWrapper(String baseUrl) {
+        assert distribution.binDistribution.exists() : "bin distribution must exist to run this test, you need to run the :binZip task"
+
+        file("build.gradle") << """
+    import org.gradle.api.tasks.wrapper.Wrapper
+    task wrapper(type: Wrapper) {
+        archiveBase = Wrapper.PathBase.PROJECT
+        archivePath = 'dist'
+        distributionUrl = '${baseUrl}/gradlew/dist'
+        distributionBase = Wrapper.PathBase.PROJECT
+        distributionPath = 'dist'
+    }
+
+    task hello << {
+        println 'hello'
+    }
+
+    task echoProperty << {
+        println "fooD=" + project.properties["fooD"]
+    }
+"""
+
+        executer.withTasks('wrapper').run()
+
+        server.allowGet("/gradlew/dist", distribution.binDistribution)
+    }
 
-        executer.inDirectory(wrapperSampleDir).withTasks('wrapper').run()
+    public void "has non-zero exit code on build failure"() {
+        given:
+        prepareWrapper("http://localhost:${server.port}")
 
-        ExecutionFailure failure = executer.usingExecutable('gradlew').inDirectory(wrapperSampleDir).withTasks('unknown').runWithFailure()
-        failure.assertHasDescription("Task 'unknown' not found in root project 'wrapper-project'.")
+        expect:
+        server.allowGet("/gradlew/dist", distribution.binDistribution)
+
+        when:
+        ExecutionFailure failure = wrapperExecuter.withTasks('unknown').runWithFailure()
+        
+        then:
+        failure.assertHasDescription("Task 'unknown' not found in root project")
     }
 
-    @Test
-    public void wrapperSample() {
-        File wrapperSampleDir = sample.dir
+    public void "runs sample target using wrapper"() {        
+        given:
+        prepareWrapper("http://localhost:${server.port}")
+
+        when:
+        ExecutionResult result = wrapperExecuter.withTasks('hello').run()
+        
+        then:
+        assertThat(result.output, containsString('hello'))
+    }
 
-        executer.inDirectory(wrapperSampleDir).withTasks('wrapper').run()
+    public void "downloads wrapper via proxy"() {        
+        given:
+        proxyServer.start()
+        prepareWrapper("http://not.a.real.domain")
+        file("gradle.properties") << """
+    systemProp.http.proxyHost=localhost
+    systemProp.http.proxyPort=${proxyServer.port}
+"""
 
-        ExecutionResult result = executer.usingExecutable('gradlew').inDirectory(wrapperSampleDir).withTasks('hello').run()
+        when:
+        ExecutionResult result = wrapperExecuter.withTasks('hello').run()
+        
+        then:
         assertThat(result.output, containsString('hello'))
+
+        and:
+        proxyServer.requestCount == 1
+    }
+
+    public void "downloads wrapper via authenticated proxy"() {
+        given:
+        proxyServer.start()
+        proxyServer.requireAuthentication('my_user', 'my_password')
+
+        and:
+        prepareWrapper("http://not.a.real.domain")
+        file("gradle.properties") << """
+    systemProp.http.proxyHost=localhost
+    systemProp.http.proxyPort=${proxyServer.port}
+    systemProp.http.proxyUser=my_user
+    systemProp.http.proxyPassword=my_password
+"""
+        when:
+        ExecutionResult result = wrapperExecuter.withTasks('hello').run()
+
+        then:
+        assertThat(result.output, containsString('hello'))
+
+        and:
+        proxyServer.requestCount == 1
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-1871")
+    public void "can specify project properties containing D"() {
+        given:
+        prepareWrapper("http://localhost:${server.port}")
+
+        when:
+        ExecutionResult result = wrapperExecuter.withArguments("-PfooD=bar").withTasks('echoProperty').run()
+
+        then:
+        assertThat(result.output, containsString("fooD=bar"))
+    }
+
+    public void "generated wrapper scripts use correct line separators"(){
+        given:
+        assert distribution.binDistribution.exists() : "bin distribution must exist to run this test, you need to run the :binZip task"
+
+        file("build.gradle") << """
+            import org.gradle.api.tasks.wrapper.Wrapper
+            task wrapper(type: Wrapper) {
+                archiveBase = Wrapper.PathBase.PROJECT
+                archivePath = 'dist'
+                distributionUrl = 'http://localhost:${server.port}/gradlew/dist'
+                distributionBase = Wrapper.PathBase.PROJECT
+                distributionPath = 'dist'
+            }
+        """
+
+        when:
+        run "wrapper"
+        then:
+        assert file("gradlew").text.split(TextUtil.unixLineSeparator).length > 1
+        assert file("gradlew").text.split(TextUtil.windowsLineSeparator).length == 1
+        assert file("gradlew.bat").text.split(TextUtil.windowsLineSeparator).length > 1
+        noExceptionThrown()
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy
new file mode 100644
index 0000000..8df532d
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/BuildEnvironmentIntegrationTest.groovy
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2011 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.environment
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.internal.jvm.Jvm
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.TextUtil
+import spock.lang.IgnoreIf
+import spock.lang.Issue
+
+/**
+ * @author: Szczepan Faber, created at: 8/11/11
+ */
+class BuildEnvironmentIntegrationTest extends AbstractIntegrationSpec {
+    def "canonicalizes working directory"() {
+        given:
+        testProject()
+
+        when:
+        def relativeDir = new File(distribution.testDir, 'java/multiproject/../quickstart')
+        executer.inDirectory(relativeDir).run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    @Requires(TestPrecondition.CASE_INSENSITIVE_FS)
+    def "canonicalizes working directory on case insensitive file system"() {
+        testProject()
+
+        when:
+        def mixedCaseDir = new File(distribution.testDir, "JAVA/QuickStart")
+        executer.inDirectory(mixedCaseDir).run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "canonicalizes working directory for short windows path"() {
+        testProject()
+
+        when:
+        def shortDir = new File(distribution.testDir, 'java/QUICKS~1')
+        executer.inDirectory(shortDir).run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    private testProject() {
+        distribution.testFile("java/multiproject").createDir()
+        def projectDir = distribution.testFile("java/quickstart")
+        projectDir.file('build.gradle') << "assert file('.') == new File(new URI('${projectDir.toURI()}'))"
+    }
+
+    @Issue("GRADLE-1762")
+    def "build uses environment variables from where the build was launched"() {
+        file('build.gradle') << "println System.getenv('foo')"
+
+        when:
+        def out = executer.withEnvironmentVars(foo: "gradle rocks!").run().output
+
+        then:
+        out.contains("gradle rocks!")
+
+        when:
+        out = executer.withEnvironmentVars(foo: "and will be even better").run().output
+
+        then:
+        out.contains("and will be even better")
+    }
+
+    def "build is executed with working directory set to where the build was launched from"() {
+        def project1 = distribution.testFile("project1")
+        def project2 = distribution.testFile("project2")
+
+        project1.file('build.gradle') << """
+def expectedDir = new File(new URI('${project1.toURI()}'))
+def dir = new File('.')
+assert dir.canonicalFile == expectedDir.canonicalFile
+assert dir.directory
+def classesDir = new File("build/classes1")
+assert classesDir.mkdirs()
+assert classesDir.directory
+"""
+
+        project2.file('build.gradle') << """
+def expectedDir = new File(new URI('${project2.toURI()}'))
+def dir = new File('.')
+assert dir.canonicalFile == expectedDir.canonicalFile
+assert dir.directory
+def classesDir = new File("build/classes2")
+assert classesDir.mkdirs()
+assert classesDir.directory
+"""
+
+        when:
+        executer.inDirectory(project1).run()
+        executer.inDirectory(project2).run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "system properties should be made available to build"() {
+        file('build.gradle') << "assert System.properties['foo'] == 'bar'"
+
+        when:
+        executer.withArguments("-Dfoo=bar").run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    def "java home from environment should be used to run build"() {
+        def alternateJavaHome = AvailableJavaHomes.bestAlternative
+
+        file('build.gradle') << "println 'javaHome=' + org.gradle.internal.jvm.Jvm.current().javaHome.canonicalPath"
+
+        when:
+        def out = executer.run().output
+
+        then:
+        out.contains("javaHome=" + Jvm.current().javaHome.canonicalPath)
+
+        when:
+        out = executer.withJavaHome(alternateJavaHome).run().output
+
+        then:
+        out.contains("javaHome=" + alternateJavaHome.canonicalPath)
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    def "java home from gradle properties should be used to run build"() {
+        def alternateJavaHome = AvailableJavaHomes.bestAlternative
+
+        file('gradle.properties') << "org.gradle.java.home=${TextUtil.escapeString(alternateJavaHome.canonicalPath)}"
+
+        file('build.gradle') << "println 'javaHome=' + org.gradle.internal.jvm.Jvm.current().javaHome.absolutePath"
+
+        when:
+        // Need the forking executer for this to work. Embedded executer will not fork a new process if jvm doesn't match.
+        def out = executer.withForkingExecuter().run().output
+
+        then:
+        out.contains("javaHome=" + alternateJavaHome.absolutePath)
+    }
+
+    def "jvm args from gradle properties should be used to run build"() {
+        file('gradle.properties') << "org.gradle.jvmargs=-Xmx32m -Dsome-prop=some-value"
+
+        file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx32m')
+assert System.getProperty('some-prop') == 'some-value'
+"""
+
+        when:
+        executer.withForkingExecuter()
+        // TODO:DAZ cleanup the setting of default jvm args for daemon and forking executer
+        if (executer.type == GradleDistributionExecuter.Executer.daemon ) {
+            executer.withArguments("-Dorg.gradle.jvmargs=")
+        }
+        executer.run()
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/SingleUseDaemonIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/SingleUseDaemonIntegrationTest.groovy
new file mode 100644
index 0000000..a212ef1
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/SingleUseDaemonIntegrationTest.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 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.environment
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.util.TextUtil
+import spock.lang.IgnoreIf
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+
+ at IgnoreIf({ GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.daemon })
+class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        // Need forking executer
+        // '-ea' is always set on the forked process. So I've added it explicitly here. // TODO:DAZ Clean this up
+        executer.withForkingExecuter().withEnvironmentVars(["JAVA_OPTS": "-ea"])
+        distribution.requireIsolatedDaemons()
+    }
+
+    def "stops single use daemon on build complete"() {
+        requireJvmArg('-Xmx32m')
+
+        file('build.gradle') << "println 'hello world'"
+
+        when:
+        succeeds()
+
+        then:
+        wasForked()
+        and:
+        executer.getDaemonRegistry().all.empty
+    }
+
+    def "stops single use daemon when build fails"() {
+        requireJvmArg('-Xmx32m')
+
+        file('build.gradle') << "throw new RuntimeException('bad')"
+
+        when:
+        fails()
+
+        then:
+        wasForked()
+        failureHasCause "bad"
+
+        and:
+        executer.getDaemonRegistry().all.empty
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    def "does not fork build if java home from gradle properties matches current process"() {
+        def alternateJavaHome = AvailableJavaHomes.bestAlternative
+
+        file('gradle.properties') << "org.gradle.java.home=${TextUtil.escapeString(alternateJavaHome.canonicalPath)}"
+
+        file('build.gradle') << "println 'javaHome=' + org.gradle.internal.jvm.Jvm.current().javaHome.absolutePath"
+
+        when:
+        executer.withJavaHome(alternateJavaHome)
+        succeeds()
+
+        then:
+        !wasForked();
+    }
+
+    def "forks build to run when immutable jvm args set regardless of the environment"() {
+        when:
+        requireJvmArg('-Xmx32m')
+        runWithJvmArg('-Xmx32m')
+
+        and:
+        file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx32m')
+"""
+
+        then:
+        succeeds()
+
+        and:
+        wasForked()
+    }
+
+    def "does not fork build and configures system properties from gradle properties"() {
+        when:
+        requireJvmArg('-Dsome-prop=some-value')
+
+        and:
+        file('build.gradle') << """
+assert System.getProperty('some-prop') == 'some-value'
+"""
+
+        then:
+        succeeds()
+
+        and:
+        !wasForked()
+    }
+
+    private def requireJvmArg(String jvmArg) {
+        file('gradle.properties') << "org.gradle.jvmargs=$jvmArg"
+    }
+
+    private def runWithJvmArg(String jvmArg) {
+        executer.withEnvironmentVars(["JAVA_OPTS": "$jvmArg -ea"])
+    }
+
+    private def wasForked() {
+        result.output.contains('fork a new JVM')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/DistroTempDirIsUniquePerTestSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/DistroTempDirIsUniquePerTestSpec.groovy
new file mode 100644
index 0000000..8ac5e3f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/DistroTempDirIsUniquePerTestSpec.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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.fixture
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/14/12
+ */
+class DistroTempDirIsUniquePerTestSpec extends Specification {
+
+    @Rule GradleDistribution dist = new GradleDistribution();
+    static tests = new HashSet()
+    static tmpDirs = new HashSet()
+
+    def setup() {
+        //it's very important we try to access the test dir in the setup()
+        dist.testDir
+    }
+    
+    def "testOne"() {
+        when:
+        tests << "testOne"
+        tmpDirs << dist.testDir
+        
+        then:
+        tests.size() == tmpDirs.size()
+    }
+
+    def "testTwo"() {
+        when:
+        tests << "testTwo"
+        tmpDirs << dist.testDir
+
+        then:
+        tests.size() == tmpDirs.size()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
new file mode 100644
index 0000000..f6ce1bd
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/TempDirIsUniquePerTestSpec.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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.fixture
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/14/12
+ */
+class TempDirIsUniquePerTestSpec extends Specification {
+
+    @Rule TemporaryFolder temp = new TemporaryFolder()
+    static tests = new HashSet()
+    static tmpDirs = new HashSet()
+
+    def setup() {
+        //it's very important we try to access the test dir in the setup()
+        temp.testDir
+    }
+    
+    def "testOne"() {
+        when:
+        tests << "testOne"
+        tmpDirs << temp.testDir
+        
+        then:
+        tests.size() == tmpDirs.size()
+    }
+
+    def "testTwo"() {
+        when:
+        tests << "testTwo"
+        tmpDirs << temp.testDir
+
+        then:
+        tests.size() == tmpDirs.size()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
deleted file mode 100644
index 6ae64fd..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
+++ /dev/null
@@ -1,64 +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.maven
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.MavenRepository
-
-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() {
-        dist.testFile("settings.gradle").write("include 'subProject'");
-        dist.testFile("build.gradle") << '''
-            apply plugin: 'java'
-            apply plugin: 'maven'
-        '''
-        executer.withTaskList().run();
-    }
-
-    @Test
-    public void canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration() {
-        executer.withTasks('uploadArchives').run()
-        def module = repo().module('group', 'root', 1.0)
-        module.assertArtifactsDeployed('root-1.0.jar')
-    }
-
-    @Test
-    public void canDeployAProjectWithNoMainArtifact() {
-        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/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenRepoIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenRepoIntegrationTest.groovy
deleted file mode 100644
index b39c744..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenRepoIntegrationTest.groovy
+++ /dev/null
@@ -1,43 +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.integtests.maven
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class MavenRepoIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample('mavenRepo')
-
-    @Test
-    public void mavenRepoSample() {
-        List expectedFiles = ['sillyexceptions-1.0.1.jar', 'repotest-1.0.jar', 'testdep-1.0.jar', 'testdep2-1.0.jar',
-                'classifier-1.0-jdk15.jar', 'classifier-dep-1.0.jar', 'jaronly-1.0.jar']
-
-        File projectDir = sample.dir
-        executer.inDirectory(projectDir).withTasks('retrieve').run()
-        expectedFiles.each { new TestFile(projectDir, 'build', it).assertExists() }
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenSnapshotIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenSnapshotIntegrationTest.groovy
deleted file mode 100644
index ba8a6fe..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/MavenSnapshotIntegrationTest.groovy
+++ /dev/null
@@ -1,95 +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.maven
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.HttpServer
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author Hans Dockter
- */
-class MavenSnapshotIntegrationTest {
-    @Rule public GradleDistribution distribution = new GradleDistribution()
-    @Rule public GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources testResources = new TestResources()
-    @Rule public final HttpServer server = new HttpServer()
-
-    @Before
-    public void setup() {
-        distribution.requireOwnUserHomeDir()
-    }
-
-    @Test
-    public void retrievesAndCacheLocalSnapshot() {
-        def producerProject = distribution.testFile('producer.gradle')
-        def consumerProject = distribution.testFile('projectWithMavenSnapshots.gradle')
-
-        // Publish the first snapshot
-        executer.usingBuildScript(producerProject).withTasks('uploadArchives').run()
-
-        // Retrieve the first snapshot
-        executer.usingBuildScript(consumerProject).withTasks('retrieve').run()
-        def jarFile = distribution.testFile('build/testproject-1.0-SNAPSHOT.jar')
-        def snapshot = jarFile.assertIsFile().snapshot()
-
-        // Retrieve again should use cached snapshot
-        executer.usingBuildScript(consumerProject).withTasks('retrieve').run().assertTasksSkipped(':retrieve')
-        jarFile.assertHasNotChangedSince(snapshot)
-
-        // Publish the second snapshot
-        Thread.sleep(1100)
-        executer.usingBuildScript(producerProject).withTasks('uploadArchives').withArguments("-PemptyJar").run()
-
-        // Retrieve again should use updated snapshot
-        executer.usingBuildScript(consumerProject).withTasks('retrieve').run().assertTasksNotSkipped(':retrieve')
-        jarFile.assertHasChangedSince(snapshot)
-    }
-
-    @Test
-    public void retrievesAndCacheSnapshotViaHttp() {
-        server.allowGet('/repo', distribution.testFile('repo'))
-        server.start()
-        String repoUrl = "-PrepoUrl=http://localhost:${server.port}/repo"
-
-        def producerProject = distribution.testFile('producer.gradle')
-        def consumerProject = distribution.testFile('projectWithMavenSnapshots.gradle')
-
-        // Publish the first snapshot
-        executer.usingBuildScript(producerProject).withTasks('uploadArchives').run()
-
-        // Retrieve the first snapshot
-        executer.usingBuildScript(consumerProject).withTasks('retrieve').withArguments(repoUrl).run()
-        def jarFile = distribution.testFile('build/testproject-1.0-SNAPSHOT.jar')
-        def snapshot = jarFile.assertIsFile().snapshot()
-
-        // Publish the second snapshot
-        Thread.sleep(1100)
-        executer.usingBuildScript(producerProject).withTasks('uploadArchives').withArguments("-PemptyJar").run()
-
-        // Retrieve again should use cached snapshot
-        executer.usingBuildScript(consumerProject).withTasks('retrieve').withArguments(repoUrl).run().assertTasksSkipped(':retrieve')
-        jarFile.assertHasNotChangedSince(snapshot)
-
-        // Retrieve again with zero timeout should use updated snapshot
-        executer.usingBuildScript(consumerProject).withTasks('retrieve').withArguments("-PnoTimeout", repoUrl).run().assertTasksNotSkipped(':retrieve')
-        jarFile.assertHasChangedSince(snapshot)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenPomGenerationIntegrationTest.groovy
deleted file mode 100644
index 8f049d9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenPomGenerationIntegrationTest.groovy
+++ /dev/null
@@ -1,152 +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.integtests.maven
-
-import groovy.text.SimpleTemplateEngine
-import org.custommonkey.xmlunit.Diff
-import org.custommonkey.xmlunit.XMLAssert
-import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.Resources
-import org.gradle.util.TestFile
-import org.hamcrest.Matchers
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import static org.junit.Assert.*
-import org.gradle.integtests.fixtures.Sample
-
-/**
- * @author Hans Dockter
- */
-class SamplesMavenPomGenerationIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-
-    private TestFile pomProjectDir
-    private TestFile repoDir
-    private TestFile snapshotRepoDir
-
-    @Rule public Resources resources = new Resources();
-    @Rule public final Sample sample = new Sample('maven/pomGeneration')
-
-    @Before
-    void setUp() {
-        pomProjectDir = sample.dir
-        repoDir = pomProjectDir.file('pomRepo');
-        snapshotRepoDir = pomProjectDir.file('snapshotRepo');
-    }
-    
-    @Test
-    void checkWithNoCustomVersion() {
-        String version = '1.0'
-        String groupId = "gradle"
-        long start = System.currentTimeMillis();
-        executer.inDirectory(pomProjectDir).withTasks('clean', 'uploadArchives', 'install').run()
-        String repoPath = repoPath(groupId, version)
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId),
-                pomFile(repoDir, repoPath, version).text)
-        repoDir.file("$repoPath/mywar-${version}.war").assertIsCopyOf(pomProjectDir.file("target/libs/mywar-${version}.war"))
-        pomProjectDir.file('build').assertDoesNotExist()
-        pomProjectDir.file('target').assertIsDir()
-        checkInstall(start, pomProjectDir, version, groupId)
-    }
-
-    @Test
-    void checkWithCustomVersion() {
-        long start = System.currentTimeMillis();
-        String version = "1.0MVN"
-        String groupId = "deployGroup"
-        executer.inDirectory(pomProjectDir).withArguments("-PcustomVersion=${version}").withTasks('clean', 'uploadArchives', 'install').run()
-        String repoPath = repoPath(groupId, version)
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId),
-                pomFile(repoDir, repoPath, version).text)
-        repoDir.file("$repoPath/mywar-${version}.war").assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
-        repoDir.file("$repoPath/mywar-${version}-javadoc.zip").assertIsCopyOf(pomProjectDir.file("target/distributions/mywar-1.0-javadoc.zip"))
-        pomProjectDir.file('build').assertDoesNotExist()
-        pomProjectDir.file('target').assertIsDir()
-        checkInstall(start, pomProjectDir, version, 'installGroup')
-    }
-
-    @Test
-    void checkWithSnapshotVersion() {
-        String version = '1.0-SNAPSHOT'
-        String groupId = "deployGroup"
-        long start = System.currentTimeMillis();
-        executer.inDirectory(pomProjectDir).withArguments("-PcustomVersion=${version}").withTasks('clean', 'uploadArchives', 'install').run()
-        String repoPath = repoPath(groupId, version)
-        File pomFile = pomFile(snapshotRepoDir, repoPath, version)
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomFile.text)
-        new TestFile(new File(pomFile.absolutePath.replace(".pom", ".war"))).assertIsFile()
-        pomProjectDir.file('build').assertDoesNotExist()
-        pomProjectDir.file('target').assertIsDir()
-        checkInstall(start, pomProjectDir, version, 'installGroup')
-    }
-
-    @Test
-    void writeNewPom() {
-        executer.inDirectory(pomProjectDir).withTasks('clean', 'writeNewPom').run()
-        compareXmlWithIgnoringOrder(expectedPom(null, null, 'pomGeneration/expectedNewPom.txt'),
-                pomProjectDir.file("target/newpom.xml").text)
-    }
-
-    @Test
-    void writeDeployerPom() {
-        String version = '1.0'
-        String groupId = "gradle"
-        executer.inDirectory(pomProjectDir).withTasks('clean', 'writeDeployerPom').run()
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomProjectDir.file("target/deployerpom.xml").text)
-    }
-    
-    static String repoPath(String group, String version) {
-        "$group/mywar/$version"
-    }
-
-    static File pomFile(TestFile repoDir, String repoPath, String version) {
-        TestFile versionDir = repoDir.file(repoPath)
-        List matches = versionDir.listFiles().findAll { it.name.endsWith('.pom') }
-        assert matches.size() == 1
-        matches[0]
-    }
-
-    void checkInstall(long start, TestFile pomProjectDir, String version, String groupId) {
-        TestFile localMavenRepo = new TestFile(pomProjectDir.file("target/localRepoPath.txt").text as File)
-        TestFile installedFile = localMavenRepo.file("$groupId/mywar/$version/mywar-${version}.war")
-        TestFile installedJavadocFile = localMavenRepo.file("$groupId/mywar/$version/mywar-${version}-javadoc.zip")
-        TestFile installedPom = localMavenRepo.file("$groupId/mywar/$version/mywar-${version}.pom")
-        installedFile.assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
-        installedJavadocFile.assertIsCopyOf(pomProjectDir.file("target/distributions/mywar-1.0-javadoc.zip"))
-        installedPom.assertIsFile()
-        assert start.intdiv(2000) <= installedFile.lastModified().intdiv(2000)
-        assert start.intdiv(2000) <= installedJavadocFile.lastModified().intdiv(2000)
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId), installedPom.text)
-    }
-    
-    private String expectedPom(String version, String groupId, String path = 'pomGeneration/expectedPom.txt') {
-        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
-        String text = resources.getResource(path).text
-        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
-    }
-
-    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
-        Diff diff = new Diff(expectedXml, actualXml)
-        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
-        XMLAssert.assertXMLEqual(diff, true);
-        Assert.assertThat(actualXml, Matchers.startsWith(String.format('<?xml version="1.0" encoding="UTF-8"?>')))
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenQuickstartIntegrationTest.groovy
deleted file mode 100644
index 254c4ba..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenQuickstartIntegrationTest.groovy
+++ /dev/null
@@ -1,96 +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.maven
-
-import groovy.text.SimpleTemplateEngine
-import org.custommonkey.xmlunit.Diff
-import org.custommonkey.xmlunit.XMLAssert
-import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.Resources
-import org.gradle.util.TestFile
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import static org.junit.Assert.assertEquals
-
-/**
- * @author Hans Dockter
- */
-class SamplesMavenQuickstartIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public Resources resources = new Resources();
-    @Rule public final Sample sample = new Sample('maven/quickstart')
-
-    private TestFile pomProjectDir
-    private TestFile repoDir
-
-    @Before
-    void setUp() {
-        pomProjectDir = sample.dir
-        repoDir = pomProjectDir.file('pomRepo');
-    }
-
-    @Test
-    void checkDeployAndInstall() {
-        String version = '1.0'
-        String groupId = "gradle"
-        long start = System.currentTimeMillis();
-        executer.inDirectory(pomProjectDir).withTasks('clean', 'uploadArchives', 'install').run()
-        String repoPath = repoPath(groupId, version)
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId),
-                pomFile(repoDir, repoPath, version).text)
-        repoDir.file("$repoPath/quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
-        checkInstall(start, pomProjectDir, version, groupId)
-    }
-
-    static String repoPath(String group, String version) {
-        "$group/quickstart/$version"
-    }
-
-    static File pomFile(TestFile repoDir, String repoPath, String version) {
-        TestFile versionDir = repoDir.file(repoPath)
-        List matches = versionDir.listFiles().findAll { it.name.endsWith('.pom') }
-        assertEquals(1, matches.size())
-        matches[0]
-    }
-
-    void checkInstall(long start, TestFile pomProjectDir, String version, String groupId) {
-        TestFile localMavenRepo = new TestFile(pomProjectDir.file("build/localRepoPath.txt").text as File)
-        TestFile installedFile = localMavenRepo.file("$groupId/quickstart/$version/quickstart-${version}.jar")
-        TestFile installedPom = localMavenRepo.file("$groupId/quickstart/$version/quickstart-${version}.pom")
-        installedFile.assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
-        installedPom.assertIsFile()
-        assert start.intdiv(2000) <= installedFile.lastModified().intdiv(2000)
-        compareXmlWithIgnoringOrder(expectedPom(version, groupId), installedPom.text)
-    }
-
-    private String expectedPom(String version, String groupId) {
-        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
-        String text = resources.getResource('pomGeneration/expectedQuickstartPom.txt').text
-        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
-    }
-
-    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
-        Diff diff = new Diff(expectedXml, actualXml)
-        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
-        XMLAssert.assertXMLEqual(diff, true);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..84aa66f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.publish.ivy
+
+import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class IvyEarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void "publishes EAR only for mixed java and WAR and EAR project"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'ear'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        ivy {
+            url 'ivy-repo'
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.ear")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..7e48989
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.IvyRepository
+
+class IvyJavaProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void "can publish jar and meta-data to ivy repository"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'java'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        ivy {
+            url 'ivy-repo'
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.jar")
+        ivyModule.ivy.configurations.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
+        ivyModule.ivy.configurations.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyPublishIntegrationTest.groovy
new file mode 100644
index 0000000..34ff389
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyPublishIntegrationTest.groovy
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2011 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.publish.ivy
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.HttpServer
+import org.hamcrest.Matchers
+import org.junit.Rule
+import org.junit.Test
+import org.mortbay.jetty.HttpStatus
+import spock.lang.Issue
+
+public class IvyPublishIntegrationTest {
+    @Rule
+    public final GradleDistribution dist = new GradleDistribution()
+    @Rule
+    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule
+    public final HttpServer server = new HttpServer()
+
+    @Test
+    public void canPublishToLocalFileRepository() {
+        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
+        dist.testFile("build.gradle") << '''
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "build/repo/"
+        }
+    }
+}
+'''
+        executer.withTasks("uploadArchives").run()
+
+        def uploadedJar = dist.testFile('build/repo/org.gradle/publish/2/publish-2.jar')
+        def uploadedIvy = dist.testFile('build/repo/org.gradle/publish/2/ivy-2.xml')
+        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
+        uploadedIvy.assertIsFile()
+    }
+
+    @Issue("GRADLE-1811")
+    @Test
+    public void canGenerateTheIvyXmlWithoutPublishing() {
+        //this is more like documenting the current behavior.
+        //Down the road we should add explicit task to create ivy.xml file
+
+        //given
+        dist.testFile("build.gradle") << '''
+apply plugin: 'java'
+
+configurations {
+  myJars
+}
+
+task myJar(type: Jar)
+
+artifacts {
+  'myJars' myJar
+}
+
+task ivyXml(type: Upload) {
+  descriptorDestination = file('ivy.xml')
+  uploadDescriptor = true
+  configuration = configurations.myJars
+}
+'''
+        //when
+        executer.withTasks("ivyXml").run()
+
+        //then
+        def ivyXml = dist.file('ivy.xml')
+        ivyXml.assertIsFile()
+    }
+
+    @Test
+    public void canPublishToUnauthenticatedHttpRepository() {
+        server.start()
+
+        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
+        dist.testFile("build.gradle") << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+        def uploadedJar = dist.testFile('uploaded.jar')
+        def uploadedIvy = dist.testFile('uploaded.xml')
+        server.expectPut('/org.gradle/publish/2/publish-2.jar', uploadedJar, HttpStatus.ORDINAL_200_OK)
+        server.expectPut('/org.gradle/publish/2/ivy-2.xml', uploadedIvy, HttpStatus.ORDINAL_201_Created)
+
+        executer.withTasks("uploadArchives").run()
+
+        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
+        uploadedIvy.assertIsFile()
+    }
+
+    @Test
+    public void canPublishToAuthenticatedHttpRepository() {
+        server.start()
+
+        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
+        dist.testFile("build.gradle") << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            credentials {
+                username 'user'
+                password 'password'
+            }
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+
+        def uploadedJar = dist.testFile('uploaded.jar')
+        def uploadedIvy = dist.testFile('uploaded.xml')
+        server.expectPut('/org.gradle/publish/2/publish-2.jar', 'user', 'password', uploadedJar)
+        server.expectPut('/org.gradle/publish/2/ivy-2.xml', 'user', 'password', uploadedIvy)
+
+        executer.withTasks("uploadArchives").run()
+
+        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
+        uploadedIvy.assertIsFile()
+    }
+
+    @Test
+    public void reportsFailedPublishToHttpRepository() {
+        server.start()
+        def repositoryUrl = "http://localhost:${server.port}"
+        server.addBroken("/")
+
+        dist.testFile("build.gradle") << """
+apply plugin: 'java'
+uploadArchives {
+    repositories {
+        ivy {
+            url "${repositoryUrl}"
+        }
+    }
+}
+"""
+
+        def result = executer.withTasks("uploadArchives").runWithFailure()
+        result.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        result.assertHasCause('Could not publish configuration \':archives\'.')
+        result.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
+
+        server.stop()
+
+        result = executer.withTasks("uploadArchives").runWithFailure()
+        result.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        result.assertHasCause('Could not publish configuration \':archives\'.')
+        result.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
+    }
+
+    @Test
+    public void usesFirstConfiguredPatternForPublication() {
+        server.start()
+
+        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
+        dist.testFile("build.gradle") << """
+    apply plugin: 'java'
+    version = '2'
+    group = 'org.gradle'
+    uploadArchives {
+        repositories {
+            ivy {
+                artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
+                artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
+                ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
+                ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
+            }
+        }
+    }
+    """
+        def uploadedJar = dist.testFile('uploaded.jar')
+        def uploadedIvy = dist.testFile('uploaded.xml')
+        server.expectPut('/primary/publish/publish-2.jar', uploadedJar, HttpStatus.ORDINAL_200_OK)
+        server.expectPut('/primary-ivy/publish/ivy-2.xml', uploadedIvy, HttpStatus.ORDINAL_201_Created)
+
+        executer.withTasks("uploadArchives").run()
+
+        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
+        uploadedIvy.assertIsFile()
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
new file mode 100644
index 0000000..9008dcb
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 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.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.SFTPServer
+import org.junit.Rule
+
+class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final SFTPServer sftpServer = new SFTPServer(distribution.temporaryFolder)
+
+    public void "can publish using SftpResolver"() {
+        given:
+        file("settings.gradle") << 'rootProject.name = "publish"'
+
+        and:
+        buildFile << """
+        apply plugin: 'java'
+        version = '2'
+        group = 'org.gradle'
+        uploadArchives {
+            repositories {
+                add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
+                    addArtifactPattern "repos/libs/[organisation]/[module]/[artifact]-[revision].[ext]"
+                    host = "${sftpServer.hostAddress}"
+                    port = ${sftpServer.port}
+                    user = "user"
+                    userPassword = "user"
+                }
+            }
+        }
+        """
+        when:
+        run "uploadArchives"
+        then:
+        true
+        sftpServer.hasFile("repos/libs/org.gradle/publish/publish-2.jar")
+        sftpServer.hasFile("repos/libs/org.gradle/publish/ivy-2.xml");
+        sftpServer.file("repos/libs/org.gradle/publish/publish-2.jar").assertIsCopyOf(file('build/libs/publish-2.jar'))
+    }
+
+    public void "reports Authentication Errors"() {
+        given:
+        file("settings.gradle") << 'rootProject.name = "publish"'
+
+        and:
+        buildFile << """
+        apply plugin: 'java'
+        version = '2'
+        group = 'org.gradle'
+        uploadArchives {
+            repositories {
+                add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
+                    addArtifactPattern "repos/libs/[organisation]/[module]/[artifact]-[revision].[ext]"
+                    host = "${sftpServer.hostAddress}"
+                    port = ${sftpServer.port}
+                    user = "simple"
+                    userPassword = "wrongPassword"
+                }
+            }
+        }
+        """
+        when:
+        fails "uploadArchives"
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertHasCause("java.io.IOException: Auth fail")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..013f8a9
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.publish.ivy
+
+import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class IvyWarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void "published WAR only for mixed java and WAR project"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'war'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        ivy {
+            url 'ivy-repo'
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.war")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
new file mode 100644
index 0000000..cdaf94d
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.publish.ivy
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+public class SamplesIvyPublishIntegrationTest {
+    @Rule
+    public final GradleDistribution dist = new GradleDistribution()
+    @Rule
+    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule
+    public final Sample sample = new Sample("ivypublish")
+
+    @Test
+    public void testPublish() {
+        // the actual testing is done in the build script.
+        File projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks("uploadArchives").run()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..d6fcd13
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenEarProjectPublishIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.publish.maven
+
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class MavenEarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void "publishes EAR only for mixed java and WAR and EAR project"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'ear'
+apply plugin: 'war'
+apply plugin: 'maven'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("maven-repo"))
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def mavenModule = new MavenRepository(file("maven-repo")).module("org.gradle.test", "publishTest", "1.9")
+        mavenModule.assertArtifactsPublished("publishTest-1.9.pom", "publishTest-1.9.ear")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..4820264
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 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.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+
+class MavenJavaProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void "can publish jar and meta-data to maven repository"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("maven-repo"))
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def mavenModule = new MavenRepository(file("maven-repo")).module("org.gradle.test", "publishTest", "1.9")
+        mavenModule.assertArtifactsPublished("publishTest-1.9.pom", "publishTest-1.9.jar")
+        mavenModule.pom.scopes.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
+        mavenModule.pom.scopes.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenNewPublicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenNewPublicationIntegrationTest.groovy
new file mode 100644
index 0000000..fafd2e9
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenNewPublicationIntegrationTest.groovy
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2011 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.publish.maven
+
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.internal.SystemProperties
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author: Szczepan Faber, created at: 6/16/11
+ */
+class MavenNewPublicationIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+
+    void "publishes snapshot to a local maven repository"() {
+        given:
+        file('build.gradle') << """
+apply plugin: 'java'
+apply plugin: 'maven'
+new org.gradle.api.publication.PublicationPlugin().apply(project)
+
+group = 'org.test'
+archivesBaseName = 'someCoolProject'
+version = '5.0-SNAPSHOT'
+
+publications.maven.repository.url = '${repo().uri}'
+"""
+
+        when:
+        executer.withTasks('publishArchives').run()
+
+        then:
+        def module = repo().module('org.test', 'someCoolProject', '5.0-SNAPSHOT')
+        module.assertArtifactsPublished("someCoolProject-5.0-SNAPSHOT.jar", "someCoolProject-5.0-SNAPSHOT.pom")
+    }
+
+    @Test
+    void "installs archives to local maven repo"() {
+        given:
+        file('build.gradle') << """
+apply plugin: 'java'
+apply plugin: 'maven'
+new org.gradle.api.publication.PublicationPlugin().apply(project)
+
+group = 'org.test'
+archivesBaseName = 'someCoolProject'
+version = '5.0-SNAPSHOT'
+
+"""
+
+        when:
+        executer.withTasks('installArchives').run()
+
+        then:
+        def localRepo = new MavenRepository(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def module = localRepo.module('org.test', 'someCoolProject', '5.0-SNAPSHOT')
+
+        def files = module.moduleDir.list() as List
+        assert files.contains('maven-metadata-local.xml')
+        assert files.any { it =~ /someCoolProject-5.0-.*\.jar/ }
+        assert files.any { it =~ /someCoolProject-5.0-.*\.pom/ }
+    }
+
+    void "publishes to remote maven repo"() {
+        given:
+        server.start()
+        file('build.gradle') << """
+apply plugin: 'java'
+apply plugin: 'maven'
+new org.gradle.api.publication.PublicationPlugin().apply(project)
+
+group = 'org.test'
+archivesBaseName = 'someCoolProject'
+version = '5.0'
+
+publications {
+    maven {
+        repository {
+            url = 'http://localhost:${server.port}/repo'
+            credentials {
+                username = 'szczepiq'
+                password = 'secret'
+            }
+        }
+    }
+}
+
+"""
+        server.expectPut("/repo/org/test/someCoolProject/5.0/someCoolProject-5.0.jar", file("jar"))
+        server.expectPut("/repo/org/test/someCoolProject/5.0/someCoolProject-5.0.jar.md5", file("jar.md5"))
+        server.expectPut("/repo/org/test/someCoolProject/5.0/someCoolProject-5.0.jar.sha1", file("jar.sha1"))
+        server.expectPut("/repo/org/test/someCoolProject/5.0/someCoolProject-5.0.pom", file("pom"))
+        server.expectPut("/repo/org/test/someCoolProject/5.0/someCoolProject-5.0.pom.md5", file("pom.md5"))
+        server.expectPut("/repo/org/test/someCoolProject/5.0/someCoolProject-5.0.pom.sha1", file("pom.sha1"))
+        server.expectGetMissing("/repo/org/test/someCoolProject/maven-metadata.xml")
+        server.expectPut("/repo/org/test/someCoolProject/maven-metadata.xml", file("metadata"))
+        server.expectPut("/repo/org/test/someCoolProject/maven-metadata.xml.md5", file("metadata.md5"))
+        server.expectPut("/repo/org/test/someCoolProject/maven-metadata.xml.sha1", file("metadata.sha1"))
+
+        when:
+        executer.withTasks('publishArchives').run()
+
+        then:
+        notThrown(Throwable)
+    }
+
+    //maven {
+//      groupId
+//      artifactId
+//      classifier "jdk15"
+//      extension "jar"
+//      artifacts {
+//        main "build/foo.jar"
+//        sources "build/sources.jar"
+//        javadoc "build/javadoc.jar"
+//        others?
+//      }
+//      dependencies {
+//        compile ...
+//        runtime ...
+//        test ...
+//        provided ...
+//        system ....
+//      }
+//      pom {
+//        whenConfigured {}
+//        contributors {
+//          contributor {
+//            name "fred firestone"
+//          }
+//        }
+//      }
+//      pom.whenConfigured { Model model -> }
+//      pom.withXml { }
+//    }
+
+    def MavenRepository repo() {
+        new MavenRepository(distribution.testFile('mavenRepo'))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest.groovy
new file mode 100644
index 0000000..d4f5893
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.publish.maven
+
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.MavenRepository
+import org.junit.Test
+
+class MavenPublicationIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources testResources = new TestResources()
+
+    @Test
+    public void canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration() {
+        executer.withTasks('uploadArchives').run()
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.pom')
+    }
+
+    @Test
+    public void canPublishAProjectWithNoMainArtifact() {
+        executer.withTasks('uploadArchives').run()
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0-source.jar')
+    }
+
+    @Test
+    public void canPublishAProjectWithMetadataArtifacts() {
+        executer.withTasks('uploadArchives').run()
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.jar.sig', 'root-1.0.pom', 'root-1.0.pom.sig')
+    }
+
+    @Test
+    public void canPublishASnapshotVersion() {
+        dist.testFile('build.gradle') << """
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'org.gradle'
+version = '1.0-SNAPSHOT'
+archivesBaseName = 'test'
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            snapshotRepository(url: uri("mavenRepo"))
+        }
+    }
+}
+"""
+
+        executer.withTasks('uploadArchives').run()
+
+        def module = repo().module('org.gradle', 'test', '1.0-SNAPSHOT')
+        module.assertArtifactsPublished('test-1.0-SNAPSHOT.jar', 'test-1.0-SNAPSHOT.pom')
+    }
+
+    def MavenRepository repo() {
+        new MavenRepository(dist.testFile('mavenRepo'))
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
new file mode 100644
index 0000000..b7c8846
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 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.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+import spock.lang.Ignore
+
+class MavenPublishRespectsPomConfigurationTest extends AbstractIntegrationSpec {
+
+    @Ignore
+    def "project dependencies in pom respect renamed artifactId"() {
+        setup:
+        def mvnRepo = distribution.testFile(".m2")
+        def root = distribution.testFile("root")
+        root.file("settings.gradle") << """
+    rootProject.name = "publish"
+    include 'project1'
+    include 'project2'
+    """
+        root.file("build.gradle") << """
+    subprojects{
+        apply plugin:'java'
+        apply plugin:'maven'
+
+        group "org.gradle.test"
+        version = 0.1
+
+        uploadArchives {
+            repositories {
+                mavenDeployer {
+                    repository(url: uri("file://${mvnRepo.absolutePath}"))
+                }
+            }
+        }
+    }"""
+
+        def project1 = root.file("project1")
+        project1.file("build.gradle") << """
+        uploadArchives {
+            repositories {
+                mavenDeployer {
+                    pom.project {
+                        artifactId 'custom_project1'
+                    }
+                }
+            }
+        }
+    """
+        def project2 = root.file("project2")
+        project2.file("build.gradle") << """
+    dependencies{
+        compile project(":project1")
+    }
+    """
+
+
+        when:
+        executer.inDirectory(root).withTasks('uploadArchives').run()
+
+        then:
+        noExceptionThrown()
+
+        def project1Module = new MavenRepository(mvnRepo).module("org.gradle.test", "custom_project1", "0.1")
+        project1Module.assertArtifactsPublished("custom_project1-0.1.pom", "custom_project1-0.1.jar")
+        def project2Module = new MavenRepository(mvnRepo).module("org.gradle.test", "project2", "0.1")
+        project2Module.assertArtifactsPublished("project2-0.1.pom", "project2-0.1.jar")
+        project2Module.pom.scopes.compile.assertDependsOn("org.gradle.test", "custom_project1", "0.1")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..4c7fccc
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenWarProjectPublishIntegrationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+
+class MavenWarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void "publishes WAR only for mixed java and WAR project"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+apply plugin: 'war'
+apply plugin: 'maven'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("maven-repo"))
+        }
+    }
+}
+"""
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def mavenModule = new MavenRepository(file("maven-repo")).module("org.gradle.test", "publishTest", "1.9")
+        mavenModule.assertArtifactsPublished("publishTest-1.9.pom", "publishTest-1.9.war")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
new file mode 100644
index 0000000..ac2b056
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
@@ -0,0 +1,114 @@
+/*
+ * 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.integtests.publish.maven
+
+import groovy.text.SimpleTemplateEngine
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.XMLAssert
+import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.Resources
+import org.gradle.internal.SystemProperties
+import org.gradle.util.TestFile
+import org.hamcrest.Matchers
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesMavenPomGenerationIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    private TestFile pomProjectDir
+
+    @Rule public Resources resources = new Resources();
+    @Rule public final Sample sample = new Sample('maven/pomGeneration')
+
+    @Before
+    void setUp() {
+        pomProjectDir = sample.dir
+    }
+    
+    @Test
+    void "can deploy to local repository"() {
+        def repo = new MavenRepository(pomProjectDir.file('pomRepo'))
+        def module = repo.module('deployGroup', 'mywar', '1.0MVN')
+
+        executer.inDirectory(pomProjectDir).withTasks('uploadArchives').withArguments("--stacktrace").run()
+
+        compareXmlWithIgnoringOrder(expectedPom('1.0MVN', "deployGroup"), module.pomFile.text)
+        module.moduleDir.file("mywar-1.0MVN.war").assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
+
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+    }
+
+    @Test
+    void "can install to local repository"() {
+        def repo = new MavenRepository(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def module = repo.module('installGroup', 'mywar', '1.0MVN')
+        module.moduleDir.deleteDir()
+
+        executer.inDirectory(pomProjectDir).withTasks('install').run()
+
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+
+        TestFile installedFile = module.moduleDir.file("mywar-1.0MVN.war")
+        TestFile installedJavadocFile = module.moduleDir.file("mywar-1.0MVN-javadoc.zip")
+        TestFile installedPom = module.moduleDir.file("mywar-1.0MVN.pom")
+
+        installedFile.assertIsCopyOf(pomProjectDir.file("target/libs/mywar-1.0.war"))
+        installedJavadocFile.assertIsCopyOf(pomProjectDir.file("target/distributions/mywar-1.0-javadoc.zip"))
+        installedPom.assertIsFile()
+
+        compareXmlWithIgnoringOrder(expectedPom("1.0MVN", "installGroup"), installedPom.text)
+    }
+
+    @Test
+    void writeNewPom() {
+        executer.inDirectory(pomProjectDir).withTasks('writeNewPom').run()
+        compareXmlWithIgnoringOrder(expectedPom(null, null, 'pomGeneration/expectedNewPom.txt'), pomProjectDir.file("target/newpom.xml").text)
+    }
+
+    @Test
+    void writeDeployerPom() {
+        String version = '1.0MVN'
+        String groupId = "deployGroup"
+        executer.inDirectory(pomProjectDir).withTasks('writeDeployerPom').run()
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomProjectDir.file("target/deployerpom.xml").text)
+    }
+    
+    private String expectedPom(String version, String groupId, String path = 'pomGeneration/expectedPom.txt') {
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
+        String text = resources.getResource(path).text
+        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
+    }
+
+    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+        Diff diff = new Diff(expectedXml, actualXml)
+        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
+        XMLAssert.assertXMLEqual(diff, true);
+        Assert.assertThat(actualXml, Matchers.startsWith(String.format('<?xml version="1.0" encoding="UTF-8"?>')))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
new file mode 100755
index 0000000..aa7dbee
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * 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.publish.maven
+
+import groovy.text.SimpleTemplateEngine
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.XMLAssert
+import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.Resources
+import org.gradle.internal.SystemProperties
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesMavenQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public Resources resources = new Resources();
+    @Rule public final Sample sample = new Sample('maven/quickstart')
+
+    private TestFile pomProjectDir
+
+    @Before
+    void setUp() {
+        pomProjectDir = sample.dir
+    }
+
+    @Test
+    void "can publish to a local repository"() {
+        executer.inDirectory(pomProjectDir).withTasks('uploadArchives').run()
+
+        def repo = new MavenRepository(pomProjectDir.file('pomRepo'))
+        def module = repo.module('gradle', 'quickstart', '1.0')
+        module.assertArtifactsPublished('quickstart-1.0.jar', 'quickstart-1.0.pom')
+        compareXmlWithIgnoringOrder(expectedPom('1.0', "gradle"), module.pomFile.text)
+        module.moduleDir.file("quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
+    }
+
+    @Test
+    void "can install to local repository"() {
+        def repo = new MavenRepository(new TestFile("$SystemProperties.userHome/.m2/repository"))
+        def module = repo.module('gradle', 'quickstart', '1.0')
+        module.moduleDir.deleteDir()
+
+        executer.inDirectory(pomProjectDir).withTasks('install').run()
+
+        module.moduleDir.file("quickstart-1.0.jar").assertIsFile()
+        module.moduleDir.file("quickstart-1.0.pom").assertIsFile()
+        module.moduleDir.file("quickstart-1.0.jar").assertIsCopyOf(pomProjectDir.file('build/libs/quickstart-1.0.jar'))
+
+        compareXmlWithIgnoringOrder(expectedPom('1.0', 'gradle'), module.pomFile.text)
+    }
+
+    private String expectedPom(String version, String groupId) {
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
+        String text = resources.getResource('pomGeneration/expectedQuickstartPom.txt').text
+        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
+    }
+
+    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+        Diff diff = new Diff(expectedXml, actualXml)
+        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
+        XMLAssert.assertXMLEqual(diff, true);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
new file mode 100644
index 0000000..00134fd
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.junit.Rule
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.integtests.fixtures.MavenRepository
+
+abstract class AbstractDependencyResolutionTest extends AbstractIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+
+    def "setup"() {
+        requireOwnUserHomeDir()
+    }
+
+    IvyRepository ivyRepo(def dir = 'ivy-repo') {
+        return new IvyRepository(distribution.testFile(dir))
+    }
+
+    MavenRepository mavenRepo() {
+        return new MavenRepository(file('repo'))
+    }
+
+    def cleanup() {
+        server.resetExpectations()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
new file mode 100644
index 0000000..bd6c5b2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
@@ -0,0 +1,655 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Issue
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.startsWith
+
+class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Before
+    public void setup() {
+        distribution.requireOwnUserHomeDir()
+    }
+
+    @Test
+    public void canHaveConfigurationHierarchy() {
+        File buildFile = testFile("projectWithConfigurationHierarchy.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void dependencyReportWithConflicts() {
+        File buildFile = testFile("projectWithConflicts.gradle");
+        usingBuildFile(buildFile).run();
+        usingBuildFile(buildFile).withDependencyList().run();
+    }
+
+    @Test
+    public void canNestModules() throws IOException {
+        File buildFile = testFile("projectWithNestedModules.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canHaveCycleInDependencyGraph() throws IOException {
+        File buildFile = testFile("projectWithCyclesInDependencyGraph.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canUseDynamicVersions() throws IOException {
+        File buildFile = testFile("projectWithDynamicVersions.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void resolutionFailsWhenProjectHasNoRepositoriesEvenWhenArtifactIsCachedLocally() {
+        testFile('settings.gradle') << 'include "a", "b"'
+        testFile('build.gradle') << """
+subprojects {
+    configurations {
+        compile
+    }
+    task listDeps << { configurations.compile.each { } }
+}
+project(':a') {
+    repositories {
+        maven { url '${repo.uri}' }
+    }
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+project(':b') {
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+"""
+        repo.module('org.gradle.test', 'external1', '1.0').publish()
+
+        inTestDirectory().withTasks('a:listDeps').run()
+        def result = inTestDirectory().withTasks('b:listDeps').runWithFailure()
+        result.assertThatCause(containsString('Could not find group:org.gradle.test, module:external1, version:1.0.'))
+    }
+
+    @Test
+    public void resolutionFailsForMissingArtifact() {
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    compile; missingExt; missingClassifier
+}
+dependencies {
+    compile "org.gradle.test:lib:1.0"
+    missingExt "org.gradle.test:lib:1.0 at zip"
+    missingClassifier "org.gradle.test:lib:1.0:classifier1"
+}
+task listJar << { configurations.compile.each { } }
+task listMissingExt << { configurations.missingExt.each { } }
+task listMissingClassifier << { configurations.missingClassifier.each { } }
+"""
+        repo.module('org.gradle.test', 'lib', '1.0').publish()
+
+        inTestDirectory().withTasks('listJar').run()
+
+        def result = inTestDirectory().withTasks('listMissingExt').runWithFailure()
+        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0 at zip' not found"))
+
+        result = inTestDirectory().withTasks('listMissingClassifier').runWithFailure()
+        result.assertThatCause(containsString("Artifact 'org.gradle.test:lib:1.0:classifier1 at jar' not found"))
+    }
+
+    @Test
+    @Issue("GRADLE-1342")
+    public void resolutionDoesNotUseCachedArtifactFromDifferentRepository() {
+        def repo1 = new MavenRepository(testFile('repo1'))
+        repo1.module('org.gradle.test', 'external1', '1.0').publish()
+        def repo2 = new MavenRepository(testFile('repo2'))
+
+        testFile('settings.gradle') << 'include "a", "b"'
+        testFile('build.gradle') << """
+subprojects {
+    configurations {
+        compile
+    }
+    task listDeps << { configurations.compile.each { } }
+}
+project(':a') {
+    repositories {
+        maven { url '${repo1.uri}' }
+    }
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+project(':b') {
+    repositories {
+        maven { url '${repo2.uri}' }
+    }
+    dependencies {
+        compile 'org.gradle.test:external1:1.0'
+    }
+}
+"""
+
+        inTestDirectory().withTasks('a:listDeps').run()
+        def result = inTestDirectory().withTasks('b:listDeps').runWithFailure()
+        result.assertThatCause(containsString('Could not find group:org.gradle.test, module:external1, version:1.0.'))
+    }
+
+    @Test
+    public void exposesMetaDataAboutResolvedArtifactsInAFixedOrder() {
+        def module = repo.module('org.gradle.test', 'lib', '1.0')
+        module.artifact(type: 'zip')
+        module.artifact(classifier: 'classifier')
+        module.publish()
+        repo.module('org.gradle.test', 'dist', '1.0').hasType('zip').publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    compile
+}
+dependencies {
+    compile "org.gradle.test:lib:1.0"
+    compile "org.gradle.test:lib:1.0:classifier"
+    compile "org.gradle.test:lib:1.0 at zip"
+    compile "org.gradle.test:dist:1.0"
+}
+task test << {
+    assert configurations.compile.files.collect { it.name } == ['lib-1.0.jar', 'lib-1.0.zip', 'lib-1.0-classifier.jar', 'dist-1.0.zip']
+    def artifacts = configurations.compile.resolvedConfiguration.resolvedArtifacts as List
+    assert artifacts.size() == 4
+    assert artifacts[0].name == 'lib'
+    assert artifacts[0].type == 'jar'
+    assert artifacts[0].extension == 'jar'
+    assert artifacts[0].classifier == null
+    assert artifacts[1].name == 'lib'
+    assert artifacts[1].type == 'jar'
+    assert artifacts[1].extension == 'jar'
+    assert artifacts[1].classifier == 'classifier'
+    assert artifacts[2].name == 'lib'
+    assert artifacts[2].type == 'zip'
+    assert artifacts[2].extension == 'zip'
+    assert artifacts[2].classifier == null
+    assert artifacts[3].name == 'dist'
+    assert artifacts[3].type == 'zip'
+    assert artifacts[3].extension == 'zip'
+    assert artifacts[3].classifier == null
+}
+"""
+
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    @Issue("GRADLE-1567")
+    public void resolutionDifferentiatesBetweenArtifactsThatDifferOnlyInClassifier() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(classifier: 'classifier1')
+        module.artifact(classifier: 'classifier2')
+        module.publish()
+
+        testFile('settings.gradle') << 'include "a", "b", "c"'
+        testFile('build.gradle') << """
+subprojects {
+    repositories {
+        maven { url '${repo.uri}' }
+    }
+    configurations {
+        compile
+    }
+}
+project(':a') {
+    dependencies {
+        compile 'org.gradle.test:external1:1.0:classifier1'
+    }
+    task test(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['external1-1.0-classifier1.jar']
+        assert configurations.compile.resolvedConfiguration.resolvedArtifacts.collect { "\${it.name}-\${it.classifier}" } == ['external1-classifier1']
+    }
+}
+project(':b') {
+    dependencies {
+        compile 'org.gradle.test:external1:1.0:classifier2'
+    }
+    task test(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['external1-1.0-classifier2.jar']
+        assert configurations.compile.resolvedConfiguration.resolvedArtifacts.collect { "\${it.name}-\${it.classifier}" } == ['external1-classifier2']
+    }
+}
+"""
+
+        inTestDirectory().withTasks('a:test').run()
+        inTestDirectory().withTasks('b:test').run()
+    }
+
+    @Test
+    @Issue("GRADLE-739")
+    public void singleConfigurationCanContainMultipleArtifactsThatOnlyDifferByClassifier() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(classifier: 'baseClassifier')
+        module.artifact(classifier: 'extendedClassifier')
+        module.publish()
+        repo.module('org.gradle.test', 'other', '1.0').publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    base
+    extendedWithClassifier.extendsFrom base
+    extendedWithOther.extendsFrom base
+    justDefault
+    justClassifier
+    rawBase
+    rawExtended.extendsFrom rawBase
+    cBase
+    cExtended.extendsFrom cBase
+}
+dependencies {
+    base 'org.gradle.test:external1:1.0'
+    base 'org.gradle.test:external1:1.0:baseClassifier'
+    extendedWithClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
+    extendedWithOther 'org.gradle.test:other:1.0'
+    justDefault 'org.gradle.test:external1:1.0'
+    justClassifier 'org.gradle.test:external1:1.0:baseClassifier'
+    justClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
+    rawBase 'org.gradle.test:external1:1.0'
+    rawExtended 'org.gradle.test:external1:1.0:extendedClassifier'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.base, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar']
+    checkDeps configurations.extendedWithOther, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar', 'other-1.0.jar']
+    checkDeps configurations.extendedWithClassifier, ['external1-1.0.jar', 'external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
+    checkDeps configurations.justDefault, ['external1-1.0.jar']
+    checkDeps configurations.justClassifier, ['external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
+    checkDeps configurations.rawBase, ['external1-1.0.jar']
+    checkDeps configurations.rawExtended, ['external1-1.0.jar', 'external1-1.0-extendedClassifier.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    @Issue("GRADLE-739")
+    public void canUseClassifiersCombinedWithArtifactWithNonStandardPackaging() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(type: 'txt')
+        module.artifact(classifier: 'baseClassifier', type: 'jar')
+        module.artifact(classifier: 'extendedClassifier', type: 'jar')
+        module.hasType('zip')
+        module.publish()
+        repo.module('org.gradle.test', 'other', '1.0').publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    base
+    extended.extendsFrom base
+    extendedWithClassifier.extendsFrom base
+    extendedWithType.extendsFrom base
+}
+dependencies {
+    base 'org.gradle.test:external1:1.0'
+    base 'org.gradle.test:external1:1.0:baseClassifier'
+    extended 'org.gradle.test:other:1.0'
+    extendedWithClassifier 'org.gradle.test:external1:1.0:extendedClassifier'
+    extendedWithType 'org.gradle.test:external1:1.0 at txt'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.base, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar']
+    checkDeps configurations.extended, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'other-1.0.jar']
+    checkDeps configurations.extendedWithClassifier, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0-extendedClassifier.jar']
+    checkDeps configurations.extendedWithType, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0.txt']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    @Issue("GRADLE-739")
+    public void configurationCanContainMultipleArtifactsThatOnlyDifferByType() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(type: 'zip')
+        module.artifact(classifier: 'classifier')
+        module.artifact(classifier: 'classifier', type: 'bin')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    base
+    extended.extendsFrom base
+    extended2.extendsFrom base
+}
+dependencies {
+    base 'org.gradle.test:external1:1.0'
+    base 'org.gradle.test:external1:1.0 at zip'
+    extended 'org.gradle.test:external1:1.0:classifier'
+    extended2 'org.gradle.test:external1:1.0:classifier at bin'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.base, ['external1-1.0.jar', 'external1-1.0.zip']
+    checkDeps configurations.extended, ['external1-1.0.jar', 'external1-1.0.zip', 'external1-1.0-classifier.jar']
+    checkDeps configurations.extended2, ['external1-1.0.jar', 'external1-1.0.zip', 'external1-1.0-classifier.bin']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "dependencies that are excluded by a dependency are not retrieved"() {
+        repo.module('org.gradle.test', 'one', '1.0').publish()
+        repo.module('org.gradle.test', 'two', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.dependsOn('org.gradle.test', 'one', '1.0')
+        module.artifact(classifier: 'classifier')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    reference
+    excluded
+    extendedExcluded.extendsFrom excluded
+    excludedWithClassifier
+}
+dependencies {
+    reference 'org.gradle.test:external1:1.0'
+    excluded 'org.gradle.test:external1:1.0', { exclude module: 'one' }
+    extendedExcluded 'org.gradle.test:two:1.0'
+    excludedWithClassifier 'org.gradle.test:external1:1.0', { exclude module: 'one' }
+    excludedWithClassifier 'org.gradle.test:external1:1.0:classifier', { exclude module: 'one' }
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config*.name as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.reference, ['external1-1.0.jar', 'one-1.0.jar']
+    checkDeps configurations.excluded, ['external1-1.0.jar']
+    checkDeps configurations.extendedExcluded, ['external1-1.0.jar', 'two-1.0.jar']
+    checkDeps configurations.excludedWithClassifier, ['external1-1.0.jar', 'external1-1.0-classifier.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "dependencies that are globally excluded are not retrieved"() {
+        repo.module('org.gradle.test', 'direct', '1.0').publish()
+        repo.module('org.gradle.test', 'transitive', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external', '1.0')
+        module.dependsOn('org.gradle.test', 'transitive', '1.0')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    excluded {
+        exclude module: 'direct'
+        exclude module: 'transitive'
+    }
+    extendedExcluded.extendsFrom excluded
+}
+dependencies {
+    excluded 'org.gradle.test:external:1.0'
+    excluded 'org.gradle.test:direct:1.0'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config*.name as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.excluded, ['external-1.0.jar']
+    checkDeps configurations.extendedExcluded, ['external-1.0.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "does not attempt to resolve an excluded dependency"() {
+        def module = repo.module('org.gradle.test', 'external', '1.0')
+        module.dependsOn('org.gradle.test', 'unknown1', '1.0')
+        module.dependsOn('org.gradle.test', 'unknown2', '1.0')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    excluded {
+        exclude module: 'unknown2'
+    }
+}
+dependencies {
+    excluded 'org.gradle.test:external:1.0', { exclude module: 'unknown1' }
+    excluded 'org.gradle.test:unknown2:1.0'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config*.name as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.excluded, ['external-1.0.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void nonTransitiveDependenciesAreNotRetrieved() {
+        repo.module('org.gradle.test', 'one', '1.0').publish()
+        repo.module('org.gradle.test', 'two', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.dependsOn('org.gradle.test', 'one', '1.0')
+        module.artifact(classifier: 'classifier')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    transitive
+    nonTransitive
+    extendedNonTransitive.extendsFrom nonTransitive
+    extendedBoth.extendsFrom transitive, nonTransitive
+    mergedNonTransitive
+}
+dependencies {
+    transitive 'org.gradle.test:external1:1.0'
+    nonTransitive 'org.gradle.test:external1:1.0', { transitive = false }
+    extendedNonTransitive 'org.gradle.test:two:1.0'
+    mergedNonTransitive 'org.gradle.test:external1:1.0', {transitive = false }
+    mergedNonTransitive 'org.gradle.test:external1:1.0:classifier', { transitive = false }
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.transitive, ['external1-1.0.jar', 'one-1.0.jar']
+    checkDeps configurations.nonTransitive, ['external1-1.0.jar']
+    checkDeps configurations.extendedNonTransitive, ['external1-1.0.jar', 'two-1.0.jar']
+    checkDeps configurations.extendedBoth, ['external1-1.0.jar', 'one-1.0.jar']
+    checkDeps configurations.mergedNonTransitive, ['external1-1.0.jar', 'external1-1.0-classifier.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void "configuration transitive = false overrides dependency transitive flag"() {
+        repo.module('org.gradle.test', 'one', '1.0').publish()
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.dependsOn('org.gradle.test', 'one', '1.0')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    override { transitive = false }
+}
+dependencies {
+    override 'org.gradle.test:external1:1.0'
+}
+
+task test << {
+    assert configurations.override.collect { it.name } == ['external1-1.0.jar']
+}
+"""
+
+        inTestDirectory().withTasks('test').run()
+    }
+
+    /*
+     * Originally, we were aliasing dependency descriptors that were identical. This caused alias errors when we subsequently modified one of these descriptors.
+     */
+
+    @Test
+    public void addingClassifierToDuplicateDependencyDoesNotAffectOriginal() {
+        def module = repo.module('org.gradle.test', 'external1', '1.0')
+        module.artifact(classifier: 'withClassifier')
+        module.publish()
+
+        testFile('build.gradle') << """
+repositories {
+    maven { url '${repo.uri}' }
+}
+configurations {
+    a
+    b
+}
+dependencies {
+    a 'org.gradle.test:external1:1.0'
+    b 'org.gradle.test:external1:1.0', 'org.gradle.test:external1:1.0:withClassifier'
+}
+
+def checkDeps(config, expectedDependencies) {
+    assert config.collect({ it.name }) as Set == expectedDependencies as Set
+}
+
+task test << {
+    checkDeps configurations.a, ['external1-1.0.jar']
+    checkDeps configurations.b, ['external1-1.0-withClassifier.jar', 'external1-1.0.jar']
+}
+"""
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void reportsUnknownDependencyError() {
+        File buildFile = testFile("projectWithUnknownDependency.gradle");
+        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
+        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
+        failure.assertHasDescription("Execution failed for task ':listJars'");
+        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
+        failure.assertThatCause(containsString("Could not find group:test, module:unknownProjectA, version:1.2."));
+        failure.assertThatCause(containsString("Could not find group:test, module:unknownProjectB, version:2.1.5."));
+    }
+
+    @Test
+    public void projectCanDependOnItself() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile << '''
+            configurations { compile; add('default') }
+            dependencies { compile project(':') }
+            task jar1(type: Jar) { destinationDir = buildDir; baseName = '1' }
+            task jar2(type: Jar) { destinationDir = buildDir; baseName = '2' }
+            artifacts { compile jar1; 'default' jar2 }
+            task listJars << {
+                assert configurations.compile.collect { it.name } == ['2.jar']
+            }
+'''
+
+        inTestDirectory().withTasks("listJars").run()
+    }
+
+    @Test
+    public void canSpecifyProducerTasksForFileDependency() {
+        testFile("settings.gradle").write("include 'sub'");
+        testFile("build.gradle") << '''
+            configurations { compile }
+            dependencies { compile project(path: ':sub', configuration: 'compile') }
+            task test(dependsOn: configurations.compile) << {
+                assert file('sub/sub.jar').isFile()
+            }
+'''
+        testFile("sub/build.gradle") << '''
+            configurations { compile }
+            dependencies { compile files('sub.jar') { builtBy 'jar' } }
+            task jar << { file('sub.jar').text = 'content' }
+'''
+
+        inTestDirectory().withTasks("test").run().assertTasksExecuted(":sub:jar", ":test");
+    }
+
+    def getRepo() {
+        return maven(testFile('repo'))
+    }
+}
+
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..7924f3e
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.MavenModule
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.junit.Rule
+
+class ArtifactOnlyResolutionIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+    @Rule public final TestResources resources = new TestResources();
+
+    MavenModule projectA
+
+    def "setup"() {
+        requireOwnUserHomeDir()
+
+        projectA = repo().module('group', 'projectA').publish()
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0 at jar'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+    }
+
+    def "can resolve and cache artifact-only dependencies from a HTTP repository"() {
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+
+        and:
+        newRequestForModuleDoesNotContactServer()
+    }
+
+    def "can resolve and cache artifact-only dependencies from a HTTP repository with no descriptor"() {
+        when:
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
+        server.expectHead('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+
+        and:
+        newRequestForModuleDoesNotContactServer()
+    }
+
+    private newRequestForModuleDoesNotContactServer() {
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+        server.resetExpectations()
+        def result = run 'retrieve'
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+        return result
+    }
+
+    MavenRepository repo() {
+        return new MavenRepository(file('repo'))
+    }
+
+    def cleanup() {
+        server.resetExpectations()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..f04c5c4
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2011 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.resolve;
+
+
+public class CacheDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "cache handles manual deletion of cached artifacts"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        def cacheDir = distribution.userHomeDir.file('caches').toURI()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+task deleteCacheFiles(type: Delete) {
+    delete fileTree(dir: '${cacheDir}', includes: ['**/projectA/**'])
+}
+"""
+
+        and:
+        server.allowGet("/repo", repo.rootDir)
+
+        and:
+        succeeds('listJars')
+        succeeds('deleteCacheFiles')
+        
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "cache entries are segregated between different repositories"() {
+        server.start()
+        given:
+        def repo1 = ivyRepo('ivy-repo-a')
+        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
+        def repo2 = ivyRepo('ivy-repo-b')
+        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publish()
+        module2.jarFile << "Some extra content"
+
+        and:
+        settingsFile << "include 'a','b'"
+        buildFile << """
+subprojects {
+    configurations {
+        test
+    }
+    dependencies {
+        test "org.gradle:testproject:1.0"
+    }
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.test
+    }
+}
+project('a') {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo-a" }
+    }
+}
+project('b') {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo-b" }
+    }
+}
+"""
+
+        when:
+        server.expectGet('/repo-a/org.gradle/testproject/1.0/ivy-1.0.xml', module1.ivyFile)
+        server.expectGet('/repo-a/org.gradle/testproject/1.0/testproject-1.0.jar', module1.jarFile)
+
+        module2.expectIvyHead(server, "/repo-b")
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml.sha1', module2.sha1File(module2.ivyFile))
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml', module2.ivyFile)
+        module2.expectArtifactHead(server, "/repo-b")
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar.sha1', module2.sha1File(module2.jarFile))
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar', module2.jarFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
+        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
new file mode 100644
index 0000000..d128a30
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ClientModuleDependenciesResolveIntegrationTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * 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.integtests.resolve;
+
+
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleDependenciesResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Test
+    public void testResolve() {
+        when:
+        // the actual testing is done in the build script.
+        File projectDir = new File(distribution.getSamplesDir(), "clientModuleDependencies/shared");
+        then:
+        executer.inDirectory(projectDir).withTasks("testDeps").run();
+
+        when:
+        projectDir = new File(distribution.getSamplesDir(), "clientModuleDependencies/api");
+        then:
+        executer.inDirectory(projectDir).withTasks("testDeps").run();
+    }
+
+    @Test
+    public void "uses metadata from Client Module and looks up artifact in declared repositories"() {
+        distribution.requireOwnUserHomeDir()
+        given:
+        def repo = ivyRepo()
+        def projectA = repo.module('group', 'projectA', '1.2')
+        def projectB = repo.module('group', 'projectB', '1.3')
+        projectA.publish()
+        projectB.publish()
+
+        server.start()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+    ivy { url "http://localhost:${server.port}/repo2" }
+}
+configurations { compile }
+dependencies {
+    compile module("group:projectA:1.2") {
+       dependency("group:projectB:1.3")
+    }
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar', 'projectB-1.3.jar']
+}
+"""
+        server.expectGet('/repo/group/projectB/1.3/ivy-1.3.xml', projectB.ivyFile)
+        server.expectGetMissing('/repo/group/projectA/1.2/ivy-1.2.xml')
+        server.expectHeadMissing('/repo/group/projectA/1.2/projectA-1.2.jar')
+        server.expectGet('/repo2/group/projectA/1.2/ivy-1.2.xml', projectA.ivyFile)
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', projectA.jarFile)
+        server.expectGet('/repo/group/projectB/1.3/projectB-1.3.jar', projectB.jarFile)
+
+        expect:
+        succeeds('listJars')
+
+//        given:
+        server.resetExpectations()
+
+//        expect:
+        succeeds('listJars')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
new file mode 100644
index 0000000..37374f0
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.integtests.resolve;
+
+import org.gradle.integtests.fixtures.GradleDistribution;
+import org.gradle.integtests.fixtures.GradleDistributionExecuter;
+import org.gradle.integtests.fixtures.Sample;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependenciesResolveIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution();
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+    @Rule public final Sample sample = new Sample("dependencies");
+
+    @Test
+    public void testResolve() {
+        dist.requireOwnUserHomeDir();
+
+        // the actual testing is done in the build script.
+        File projectDir = sample.getDir();
+        executer.inDirectory(projectDir).withTasks("test").run();
+    }   
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
new file mode 100644
index 0000000..055f959
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/DependencyNotationIntegrationSpec.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.hamcrest.Matchers
+
+/**
+ * by Szczepan Faber, created at: 11/9/11
+ */
+class DependencyNotationIntegrationSpec extends AbstractIntegrationSpec {
+
+    def "understands dependency notations"() {
+        when:
+        buildFile <<  """
+import org.gradle.api.internal.artifacts.dependencies.*
+configurations {
+    conf
+    gradleStuff
+    allowsCollections
+}
+
+def someDependency = new DefaultSelfResolvingDependency(files('foo.txt'))
+dependencies {
+    conf someDependency
+    conf "org.mockito:mockito-core:1.8"
+    conf group: 'org.spockframework', name: 'spock-core', version: '1.0'
+    conf module('org.foo:moduleOne:1.0'), module('org.foo:moduleTwo:1.0')
+
+    gradleStuff gradleApi()
+
+    allowsCollections "org.mockito:mockito-core:1.8", someDependency
+}
+
+task checkDeps << {
+    def deps = configurations.conf.incoming.dependencies
+    assert deps.contains(someDependency)
+    assert deps.find { it instanceof ExternalDependency && it.group == 'org.mockito' && it.name == 'mockito-core' && it.version == '1.8'  }
+    assert deps.find { it instanceof ExternalDependency && it.group == 'org.spockframework' && it.name == 'spock-core' && it.version == '1.0'  }
+    assert deps.find { it instanceof ClientModule && it.name == 'moduleOne' && it.group == 'org.foo' }
+    assert deps.find { it instanceof ClientModule && it.name == 'moduleTwo' && it.version == '1.0' }
+
+    deps = configurations.gradleStuff.dependencies
+    assert deps.findAll { it instanceof SelfResolvingDependency }.size() > 0 : "should include gradle api jars"
+
+    deps = configurations.allowsCollections.dependencies
+    assert deps.size() == 2
+    assert deps.find { it instanceof ExternalDependency && it.group == 'org.mockito' }
+    assert deps.contains(someDependency)
+}
+"""
+        then:
+        succeeds 'checkDeps'
+    }
+
+    def "understands project notations"() {
+        when:
+        settingsFile << "include 'otherProject'"
+
+        buildFile <<  """
+configurations {
+    conf
+    confTwo
+}
+
+project(':otherProject') {
+    configurations {
+        otherConf
+    }
+}
+
+dependencies {
+    conf project(':otherProject')
+    confTwo project(path: ':otherProject', configuration: 'otherConf')
+}
+
+task checkDeps << {
+    def deps = configurations.conf.incoming.dependencies
+    assert deps.size() == 1
+    assert deps.find { it.dependencyProject.path == ':otherProject' }
+
+    deps = configurations.confTwo.incoming.dependencies
+    assert deps.size() == 1
+    assert deps.find { it.dependencyProject.path == ':otherProject' && it.projectConfiguration.name == 'otherConf' }
+}
+"""
+        then:
+        succeeds 'checkDeps'
+    }
+
+    def "understands client module notation with dependencies"() {
+        when:
+        buildFile <<  """
+configurations {
+    conf
+}
+
+dependencies {
+    conf module('org.foo:moduleOne:1.0') {
+        dependency 'org.foo:bar:1.0'
+        dependencies ('org.foo:one:1', 'org.foo:two:1')
+        dependency ('high:five:5') { transitive = false }
+    }
+}
+
+task checkDeps << {
+    def deps = configurations.conf.incoming.dependencies
+    assert deps.size() == 1
+    def dep = deps.find { it instanceof ClientModule && it.name == 'moduleOne' }
+    assert dep
+    assert dep.dependencies.size() == 4
+    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'bar' && it.version == '1.0' && it.transitive == true }
+    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'one' && it.version == '1' }
+    assert dep.dependencies.find { it.group == 'org.foo' && it.name == 'two' && it.version == '1' }
+    assert dep.dependencies.find { it.group == 'high' && it.name == 'five' && it.version == '5' && it.transitive == false }
+}
+"""
+        then:
+        succeeds 'checkDeps'
+    }
+
+    def "fails gracefully for invalid notations"() {
+        when:
+        buildFile <<  """
+configurations {
+    conf
+}
+
+dependencies {
+    conf 100
+}
+
+task checkDeps
+"""
+        then:
+        fails 'checkDeps'
+        failure.assertThatCause(Matchers.startsWith("Cannot convert the provided notation to an object of type Dependency: 100."))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy
new file mode 100644
index 0000000..0ff38ed
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/FlatDirResolveIntegrationTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class FlatDirResolveIntegrationTest extends AbstractIntegrationSpec {
+    def "can resolve dependencies from a flat dir repository"() {
+        given:
+        file("repo/a-1.4.jar").createFile()
+        file("repo/b.jar").createFile()
+        file("repo/c.jar").createFile()
+
+        and:
+        buildFile << """
+repositories { flatDir { dir 'repo' } }
+configurations { compile }
+dependencies {
+    compile 'group:a:1.4'
+    compile 'group:b:2.0'
+    compile 'group:c:'
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['a-1.4.jar', 'b.jar', 'c.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "can use a classifier to refer to artifacts in flat dir repository"() {
+        given:
+        file("repo/a-1.4-classifier.jar").createFile()
+        file("repo/a-1.4.jar").createFile()
+
+        and:
+        buildFile << """
+repositories { flatDir { dir 'repo' } }
+configurations { compile }
+dependencies {
+    compile 'group:a:1.4:classifier'
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['a-1.4-classifier.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "can use a type to refer to artifacts in flat dir repository"() {
+        given:
+        file("repo/a-1.4.zip").createFile()
+
+        and:
+        buildFile << """
+repositories { flatDir { dir 'repo' } }
+configurations { compile }
+dependencies {
+    compile 'group:a:1.4 at zip'
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['a-1.4.zip']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpEncodingDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpEncodingDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..d0fd694
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpEncodingDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.resolve;
+
+
+public class HttpEncodingDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "handles gzip encoded content"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        server.expectGetGZipped('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGetGZipped('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpProxyDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpProxyDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..c1eeb50
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpProxyDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.TestProxyServer
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+
+class HttpProxyDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+
+    public void "uses configured proxy to access remote HTTP repository"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://not.a.real.domain/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}")
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+
+    public void "uses authenticated proxy to access remote HTTP repository"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://not.a.real.domain/repo"
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.nonProxyHosts=foo",
+                               "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
+
+        and:
+        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+
+    public void "passes target credentials to target server via proxy"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://not.a.real.domain/repo"
+        credentials {
+            username 'targetUser'
+            password 'targetPassword'
+        }
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
+
+        and:
+        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'targetUser', 'targetPassword', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'targetUser', 'targetPassword', module.jarFile)
+
+        then:
+        executer.withDeprecationChecksDisabled()
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpRedirectDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpRedirectDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..9d0c8a5
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpRedirectDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 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.resolve
+
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Issue
+
+class HttpRedirectDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+
+    public void "resolves module artifacts via HTTP redirect"() {
+        server.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGetRedirected('/repo/group/projectA/1.0/ivy-1.0.xml', '/redirected/group/projectA/1.0/ivy-1.0.xml')
+        server.expectGet('/redirected/group/projectA/1.0/ivy-1.0.xml', module.ivyFile)
+        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.jar', '/redirected/group/projectA/1.0/projectA-1.0.jar')
+        server.expectGet('/redirected/group/projectA/1.0/projectA-1.0.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+
+    @Issue('GRADLE-2196')
+    public void "resolves artifact-only module via HTTP redirect"() {
+        server.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0 at zip' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.zip']
+}
+"""
+
+        when:
+        server.expectGetMissing('/repo/group/projectA/1.0/ivy-1.0.xml')
+        server.expectHeadRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', '/redirected/group/projectA/1.0/projectA-1.0.zip')
+        server.expectHead('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
+        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', '/redirected/group/projectA/1.0/projectA-1.0.zip')
+        server.expectGet('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..3529728
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+
+class ProjectDependencyResolveIntegrationTest extends AbstractIntegrationSpec {
+    public void "project dependency includes artifacts and transitive dependencies of default configuration in target project"() {
+        given:
+        repo.module("org.other", "externalA", 1.2).publish()
+        repo.module("org.other", "externalB", 2.1).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+allprojects {
+    repositories { maven { url '$repo.uri' } }
+}
+project(":a") {
+    configurations {
+        api
+        'default' { extendsFrom api }
+    }
+    dependencies {
+        api "org.other:externalA:1.2"
+        'default' "org.other:externalB:2.1"
+    }
+    task jar(type: Jar) { baseName = 'a' }
+    artifacts { api jar }
+}
+project(":b") {
+    configurations {
+        compile
+    }
+    dependencies {
+        compile project(':a')
+    }
+    task check(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar', 'externalB-2.1.jar']
+    }
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    public void "project dependency that specifies a target configuration includes artifacts and transitive dependencies of selected configuration"() {
+        given:
+        repo.module("org.other", "externalA", 1.2).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+allprojects {
+    repositories { maven { url '$repo.uri' } }
+}
+project(":a") {
+    configurations {
+        api
+        runtime { extendsFrom api }
+    }
+    dependencies {
+        api "org.other:externalA:1.2"
+    }
+    task jar(type: Jar) { baseName = 'a' }
+    artifacts { api jar }
+}
+project(":b") {
+    configurations {
+        compile
+    }
+    dependencies {
+        compile project(path: ':a', configuration: 'runtime')
+    }
+    task check(dependsOn: configurations.compile) << {
+        assert configurations.compile.collect { it.name } == ['a.jar', 'externalA-1.2.jar']
+    }
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    public void "resolved project artifacts contain project version in their names"() {
+        given:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        file('a/build.gradle') << '''
+            apply plugin: 'base'
+            configurations { compile }
+            task aJar(type: Jar) { }
+            artifacts { compile aJar }
+'''
+        file('b/build.gradle') << '''
+            apply plugin: 'base'
+            version = 'early'
+            configurations { compile }
+            task bJar(type: Jar) { }
+            gradle.taskGraph.whenReady { project.version = 'late' }
+            artifacts { compile bJar }
+'''
+        file('build.gradle') << '''
+            configurations { compile }
+            dependencies { compile project(path: ':a', configuration: 'compile'), project(path: ':b', configuration: 'compile') }
+            task test(dependsOn: configurations.compile) << {
+                assert configurations.compile.collect { it.name } == ['a.jar', 'b-late.jar']
+            }
+'''
+
+        expect:
+        succeeds "test"
+    }
+
+    public void "project dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        repo.module("group", "externalA", 1.5).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << """
+allprojects {
+    apply plugin: 'base'
+    repositories { maven { url '${repo.uri}' } }
+}
+
+project(":a") {
+    configurations { 'default' {} }
+    dependencies { 'default' 'group:externalA:1.5' }
+    task aJar(type: Jar) { }
+    artifacts { 'default' aJar }
+}
+
+project(":b") {
+    configurations { compile }
+    dependencies { compile(project(':a')) { artifact { name = 'a'; type = 'jar' } } }
+    task test {
+        inputs.files configurations.compile
+        doFirst {
+            assert configurations.compile.files.collect { it.name } == ['a.jar', 'externalA-1.5.jar']
+        }
+    }
+}
+"""
+
+        expect:
+        succeeds 'test'
+    }
+
+    public void "non-transitive project dependency includes only the artifacts of the target configuration"() {
+        given:
+        repo.module("group", "externalA", 1.5).publish()
+
+        and:
+        file('settings.gradle') << "include 'a', 'b'"
+
+        and:
+        buildFile << '''
+allprojects {
+    apply plugin: 'java'
+    repositories { maven { url rootProject.uri('repo') } }
+}
+project(':a') {
+    dependencies {
+        compile 'group:externalA:1.5'
+        compile files('libs/externalB.jar')
+    }
+}
+project(':b') {
+    dependencies {
+        compile project(':a'), { transitive = false }
+    }
+    task listJars << {
+        assert configurations.compile.collect { it.name } == ['a.jar']
+    }
+}
+'''
+
+        expect:
+        succeeds "listJars"
+    }
+
+    public void "can have cycle in project dependencies"() {
+        given:
+        file('settings.gradle') << "include 'a', 'b', 'c'"
+
+        and:
+        buildFile << """
+
+subprojects {
+    apply plugin: 'base'
+    configurations {
+        'default'
+        other
+    }
+    task jar(type: Jar)
+    artifacts {
+        'default' jar
+    }
+}
+
+project('a') {
+    dependencies {
+        'default' project(':b')
+        other project(':b')
+    }
+    task listJars {
+        dependsOn configurations.default
+        dependsOn configurations.other
+        doFirst {
+            def jars = configurations.default.collect { it.name } as Set
+            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
+
+            jars = configurations.other.collect { it.name } as Set
+            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
+        }
+    }
+}
+
+project('b') {
+    dependencies {
+        'default' project(':c')
+    }
+}
+
+project('c') {
+    dependencies {
+        'default' project(':a')
+    }
+}
+"""
+
+        expect:
+        succeeds "listJars"
+    }
+
+    // this test is largely covered by other tests, but does ensure that there is nothing special about
+    // project dependencies that are “built” by built in plugins like the Java plugin's created jars
+    def "can use zip files as project dependencies"() {
+        given:
+        file("settings.gradle") << "include 'a'; include 'b'"
+        file("a/some.txt") << "foo"
+        file("a/build.gradle") << """
+            group = "g"
+            version = 1.0
+            
+            apply plugin: 'base'
+            task zip(type: Zip) {
+                from "some.txt"
+            }
+
+            artifacts {
+                delegate.default zip
+            }
+        """
+        file("b/build.gradle") << """
+            configurations { conf }
+            dependencies {
+                conf project(":a")
+            }
+            
+            task copyZip(type: Copy) {
+                from configurations.conf
+                into "\$buildDir/copied"
+            }
+        """
+        
+        when:
+        succeeds ":b:copyZip"
+        
+        then:
+        ":b:copyZip" in  nonSkippedTasks 
+        
+        and:
+        file("b/build/copied/a-1.0.zip").exists()
+    }
+    
+    def getRepo() {
+        return new MavenRepository(file('repo'))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..6883df0
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolveCrossVersionIntegrationTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+
+class ResolveCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+
+    def "can upgrade and downgrade Gradle version"() {
+        given:
+        buildFile << """
+repositories {
+    mavenCentral()
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile 'commons-io:commons-io:1.4'
+    compile 'commons-lang:commons-lang:2.+'
+}
+
+task check << {
+    assert configurations.compile*.name as Set == ['commons-io-1.4.jar', 'commons-lang-2.6.jar'] as Set
+}
+"""
+
+        expect:
+        version previous withTasks 'check' run()
+        version current withTasks 'check' run()
+        version previous withTasks 'check' run()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
new file mode 100644
index 0000000..4374671
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.LenientConfiguration
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.specs.Specs
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.util.HelperUtil
+import org.junit.Before
+import org.junit.Test
+
+public class ResolvedConfigurationIntegrationTest extends AbstractIntegrationTest {
+
+    def DefaultProject project = HelperUtil.createRootProject()
+    def Project childProject = HelperUtil.createChildProject(project, "child", new File("."))
+    def File repo = testFile('repo')
+
+    @Before
+    public void boringSetup() {
+        project.allprojects { apply plugin: 'java' }
+
+        project.repositories {
+            maven { url repo }
+        }
+    }
+
+    @Test
+    public void "resolves leniently"() {
+        maven(repo).module('org.foo', 'hiphop').publish()
+        maven(repo).module('org.foo', 'rock').dependsOn("some unresolved dependency").publish()
+
+        project.dependencies {
+            compile 'org.foo:hiphop:1.0'
+            compile 'org.foo:hiphopxx:1.0' //does not exist
+            compile childProject
+
+            compile 'org.foo:rock:1.0' //contains unresolved transitive dependency
+        }
+
+        LenientConfiguration compile = project.configurations.compile.resolvedConfiguration.lenientConfiguration
+
+        def unresolved = compile.getUnresolvedModuleDependencies()
+        def resolved = compile.getFirstLevelModuleDependencies(Specs.SATISFIES_ALL)
+
+        assert resolved.size() == 3
+        assert resolved.find { it.moduleName == 'hiphop' }
+        assert resolved.find { it.moduleName == 'rock' }
+        assert resolved.find { it.moduleName == 'child' }
+
+        assert unresolved.size() == 2
+        assert unresolved.find { it.id.contains 'hiphopxx' }
+        assert unresolved.find { it.id.contains 'some unresolved dependency' }
+    }
+
+    public MavenRepository maven(File repo) {
+        return new MavenRepository(repo)
+    }
+
+    @Test
+    public void "resolves leniently from mixed confs"() {
+        maven(repo).module('org.foo', 'hiphop').publish()
+        maven(repo).module('org.foo', 'rock').dependsOn("some unresolved dependency").publish()
+
+        project.allprojects { apply plugin: 'java' }
+
+        project.repositories {
+            maven { url repo }
+        }
+
+        project.configurations {
+            someConf
+        }
+
+        project.dependencies {
+            compile 'org.foo:hiphop:1.0'
+            someConf 'org.foo:hiphopxx:1.0' //does not exist
+        }
+
+        LenientConfiguration compile = project.configurations.compile.resolvedConfiguration.lenientConfiguration
+
+        def unresolved = compile.getUnresolvedModuleDependencies()
+        def resolved = compile.getFirstLevelModuleDependencies(Specs.SATISFIES_ALL)
+
+        assert resolved.size() == 1
+        assert resolved.find { it.moduleName == 'hiphop' }
+        assert unresolved.size() == 0
+
+        LenientConfiguration someConf = project.configurations.someConf.resolvedConfiguration.lenientConfiguration
+
+        unresolved = someConf.getUnresolvedModuleDependencies()
+        resolved = someConf.getFirstLevelModuleDependencies(Specs.SATISFIES_ALL)
+
+        assert resolved.size() == 0
+        assert unresolved.size() == 1
+        assert unresolved.find { it.id.contains 'hiphopxx' }
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegTest.groovy
new file mode 100644
index 0000000..820824c
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegTest.groovy
@@ -0,0 +1,691 @@
+/*
+ * Copyright 2011 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.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+
+/**
+ * @author Szczepan Faber, @date 03.03.11
+ */
+class VersionConflictResolutionIntegTest extends AbstractIntegrationTest {
+    @Test
+    void "strict conflict resolution should fail due to conflict"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+
+	configurations.compile.resolutionStrategy.failOnVersionConflict()
+}
+"""
+
+        //when
+        def result = executer.withTasks("tool:dependencies").runWithFailure()
+
+        //then
+        result.assertThatCause(containsString('A conflict was found between the following modules:'))
+    }
+
+    @Test
+    void "strict conflict resolution should pass when no conflicts"() {
+        repo.module("org", "foo", '1.3.3').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+
+	configurations.all { resolutionStrategy.failOnVersionConflict() }
+}
+"""
+
+        //when
+        executer.withTasks("tool:dependencies").run()
+
+        //then no exceptions are thrown
+    }
+
+    @Test
+    void "strict conflict strategy can be used with forced modules"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+        repo.module("org", "foo", '1.5.5').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+		compile('org:foo:1.5.5'){
+		    force = true
+		}
+	}
+
+	configurations.all { resolutionStrategy.failOnVersionConflict() }
+}
+"""
+
+        //when
+        executer.withTasks("tool:dependencies").run()
+
+        //then no exceptions are thrown because we forced a certain version of conflicting dependency
+    }
+
+    @Test
+    void "can force already resolved version of a module and avoid conflict"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+}
+
+allprojects {
+    configurations.all {
+	    resolutionStrategy {
+	        force 'org:foo:1.3.3'
+	        failOnVersionConflict()
+	    }
+	}
+}
+
+"""
+
+        //when
+        executer.withTasks("api:dependencies", "tool:dependencies").run()
+
+        //then no exceptions are thrown because we forced a certain version of conflicting dependency
+    }
+
+    @Test
+    void "can force arbitrary version of a module and avoid conflict"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foobar", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+        repo.module("org", "foo", '1.5.5').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+	group = 'org.foo.unittests'
+	version = '1.0'
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api-1.0.jar', 'impl-1.0.jar', 'foo-1.5.5.jar']
+        def metaData = configurations.compile.resolvedConfiguration
+        def api = metaData.firstLevelModuleDependencies.find { it.moduleName == 'api' }
+        assert api.children.size() == 1
+        assert api.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
+        def impl = metaData.firstLevelModuleDependencies.find { it.moduleName == 'impl' }
+        assert impl.children.size() == 1
+        assert impl.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
+    }
+}
+
+allprojects {
+    configurations.all {
+        resolutionStrategy {
+            failOnVersionConflict()
+            force 'org:foo:1.5.5'
+        }
+    }
+}
+
+"""
+
+        // expect
+        executer.withTasks(":tool:checkDeps").run()
+    }
+
+    @Test
+    void "resolves to the latest version by default"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.4.4.jar']
+    }
+}
+"""
+
+        //expect
+        executer.withTasks("tool:checkDeps").run()
+    }
+
+    @Test
+    void "latest strategy respects forced modules"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+	configurations.all {
+	    resolutionStrategy {
+	        failOnVersionConflict()
+	        force 'org:foo:1.3.3'
+	    }
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.3.3.jar']
+    }
+}
+"""
+
+        //expect
+        executer.withTasks("tool:checkDeps").run()
+    }
+
+    @Test
+    void "can force the version of a particular module"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:foo:1.3.3'
+}
+
+configurations.all {
+    resolutionStrategy.force 'org:foo:1.4.4'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.4.4.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "can force the version of a direct dependency"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:foo:1.4.4'
+    compile ('org:foo:1.3.3') { force = true }
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.3.3.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "forcing transitive dependency does not add extra dependency"() {
+        repo.module("org", "foo", '1.3.3').publish()
+        repo.module("hello", "world", '1.4.4').publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:foo:1.3.3'
+}
+
+configurations.all {
+    resolutionStrategy.force 'hello:world:1.4.4'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.3.3.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "does not attempt to resolve an evicted dependency"() {
+        repo.module("org", "external", "1.2").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.0").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "resolves dynamic dependency before resolving conflict"() {
+        repo.module("org", "external", "1.2").publish()
+        repo.module("org", "external", "1.4").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.+").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['dep-2.2.jar', 'external-1.4.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "fails when version selected by conflict resolution does not exist"() {
+        repo.module("org", "external", "1.2").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['external-1.2.jar', 'dep-2.2.jar']
+}
+"""
+
+        //expect
+        def failure = executer.withTasks("checkDeps").runWithFailure()
+        failure.assertHasCause("Could not find group:org, module:external, version:1.4.")
+    }
+
+    @Test
+    void "does not fail when evicted version does not exist"() {
+        repo.module("org", "external", "1.4").publish()
+        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile 'org:external:1.2'
+    compile 'org:dep:2.2'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['dep-2.2.jar', 'external-1.4.jar']
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").run()
+    }
+
+    @Test
+    void "takes newest dynamic version when dynamic version forced"() {
+        //given
+        repo.module("org", "foo", '1.3.0').publish()
+
+        repo.module("org", "foo", '1.4.1').publish()
+        repo.module("org", "foo", '1.4.4').publish()
+        repo.module("org", "foo", '1.4.9').publish()
+
+        repo.module("org", "foo", '1.6.0').publish()
+
+        def settingsFile = file("settings.gradle")
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${repo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile 'org:foo:1.4.4'
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile 'org:foo:1.4.1'
+	}
+}
+
+project(':tool') {
+
+	dependencies {
+		compile project(':api'), project(':impl'), 'org:foo:1.3.0'
+	}
+
+	configurations.all {
+	    resolutionStrategy {
+	        force 'org:foo:1.4+'
+	        failOnVersionConflict()
+	    }
+	}
+
+	task checkDeps << {
+        assert configurations.compile*.name.contains('foo-1.4.9.jar')
+    }
+}
+
+"""
+
+        //expect
+        executer.withTasks("tool:checkDeps").run()
+    }
+
+    @Test
+    void "parent pom does not participate in forcing mechanism"() {
+        //given
+        repo.module("org", "foo", '1.3.0').publish()
+        repo.module("org", "foo", '2.4.0').publish()
+
+        //TODO SF generalize
+        def parent = repo.module("org", "someParent", "1.0")
+        parent.type = 'pom'
+        parent.dependsOn("org", "foo", "1.3.0")
+        parent.publish()
+
+        def otherParent = repo.module("org", "someParent", "2.0")
+        otherParent.type = 'pom'
+        otherParent.dependsOn("org", "foo", "2.4.0")
+        otherParent.publish()
+
+        def module = repo.module("org", "someArtifact", '1.0')
+        module.parentPomSection = """
+<parent>
+  <groupId>org</groupId>
+  <artifactId>someParent</artifactId>
+  <version>1.0</version>
+</parent>
+"""
+        module.publish()
+
+        def buildFile = file("build.gradle")
+        buildFile << """
+apply plugin: 'java'
+repositories {
+    maven { url "${repo.uri}" }
+}
+
+dependencies {
+    compile 'org:someArtifact:1.0'
+}
+
+configurations.all {
+    resolutionStrategy {
+        force 'org:someParent:2.0'
+        failOnVersionConflict()
+    }
+}
+
+task checkDeps << {
+    def deps = configurations.compile*.name
+    assert deps.contains('someArtifact-1.0.jar')
+    assert deps.contains('foo-1.3.0.jar')
+    assert deps.size() == 2
+}
+"""
+
+        //expect
+        executer.withTasks("checkDeps").withArguments('-s').run()
+    }
+
+    def getRepo() {
+        return maven(file("repo"))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..09defaa
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2011 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.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.IvyModule
+import org.gradle.integtests.fixtures.MavenModule
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.junit.Rule
+import spock.lang.Ignore
+
+class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule public final TestResources resources = new TestResources();
+
+    MavenModule projectB
+    IvyModule ivyProjectB
+
+    def mRepo1 = "/mavenRepo1"
+    def mRepo2 = "/mavenRepo2"
+
+    def iRepo1 = "/ivyRepo1"
+    def iRepo2 = "/ivyRepo2"
+
+    def "setup"() {
+        init()
+        projectB = mavenRepo().module('org.name', 'projectB').publish()
+        ivyProjectB = ivyRepo().module('org.name', 'projectB').publish()
+    }
+
+    def "does not re-download maven artifact downloaded from a different maven repository when sha1 matches"() {
+        when:
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        projectB.expectPomHead(server, "/mavenRepo2")
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.pom.sha1', projectB.sha1File(projectB.pomFile))
+        projectB.expectArtifactHead(server, "/mavenRepo2")
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.jar.sha1', projectB.sha1File(projectB.artifactFile))
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def "does not re-download ivy artifact downloaded from a different ivy repository when sha1 matches"() {
+        when:
+        server.expectGet('/ivyRepo1/org.name/projectB/1.0/ivy-1.0.xml', ivyProjectB.ivyFile)
+        server.expectGet('/ivyRepo1/org.name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'ivyRepository1'
+
+        when:
+        ivyProjectB.expectIvyHead(server, iRepo2)
+        server.expectGet('/ivyRepo2/org.name/projectB/1.0/ivy-1.0.xml.sha1', ivyProjectB.sha1File(ivyProjectB.ivyFile))
+        ivyProjectB.expectArtifactHead(server, iRepo2)
+        server.expectGet('/ivyRepo2/org.name/projectB/1.0/projectB-1.0.jar.sha1', projectB.sha1File(projectB.artifactFile))
+
+        then:
+        succeedsWith 'ivyRepository2'
+    }
+
+    def "does not re-download ivy artifact downloaded from a maven repository when sha1 matches"() {
+        when:
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        server.expectGet('/ivyRepo1/org.name/projectB/1.0/ivy-1.0.xml', ivyProjectB.ivyFile)
+        ivyProjectB.expectArtifactHead(server, iRepo1)
+        server.expectGet('/ivyRepo1/org.name/projectB/1.0/projectB-1.0.jar.sha1', projectB.sha1File(projectB.artifactFile))
+
+        then:
+        succeedsWith 'ivyRepository1'
+    }
+
+    def "does not re-download maven artifact downloaded from a ivy repository when sha1 matches"() {
+        when:
+        server.expectGet('/ivyRepo1/org.name/projectB/1.0/ivy-1.0.xml', ivyProjectB.ivyFile)
+        server.expectGet('/ivyRepo1/org.name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'ivyRepository1'
+
+        when:
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        projectB.expectArtifactHead(server, mRepo1)
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.jar.sha1', projectB.sha1File(projectB.artifactFile))
+
+        then:
+        succeedsWith 'mavenRepository1'
+    }
+
+    @Ignore("File repository does not cache artifacts locally, so they are not used to prevent download")
+    def "does not download artifact previously accessed from a file uri when sha1 matches"() {
+        given:
+        succeedsWith 'fileRepository'
+
+        when:
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.pom.sha1', projectB.sha1File(projectB.pomFile))
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.jar.sha1', projectB.sha1File(projectB.artifactFile))
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def "does re-download maven artifact downloaded from a different URI when sha1 not found"() {
+        when:
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        projectB.expectPomHead(server, "/mavenRepo2")
+        server.expectGetMissing('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.pom.sha1')
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        projectB.expectArtifactHead(server, "/mavenRepo2")
+        server.expectGetMissing('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.jar.sha1')
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    def "does re-download maven artifact downloaded from a different URI when sha1 does not match"() {
+        when:
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/mavenRepo1/org/name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'mavenRepository1'
+
+        when:
+        projectB.expectPomHead(server, mRepo2)
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.pom.sha1', projectB.md5File(projectB.pomFile))
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        projectB.expectArtifactHead(server, mRepo2)
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.jar.sha1', projectB.md5File(projectB.artifactFile))
+        server.expectGet('/mavenRepo2/org/name/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeedsWith 'mavenRepository2'
+    }
+
+    private init() {
+        server.start()
+
+        buildFile << """
+repositories {
+    if (project.hasProperty('mavenRepository1')) {
+        maven { url 'http://localhost:${server.port}/mavenRepo1' }
+    } else if (project.hasProperty('mavenRepository2')) {
+        maven { url 'http://localhost:${server.port}/mavenRepo2' }
+    } else if (project.hasProperty('ivyRepository1')) {
+        ivy { url 'http://localhost:${server.port}/ivyRepo1' }
+    } else if (project.hasProperty('ivyRepository2')) {
+        ivy { url 'http://localhost:${server.port}/ivyRepo2' }
+    } else if (project.hasProperty('fileRepository')) {
+        maven { url '${mavenRepo().uri}' }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+    }
+
+    def succeedsWith(repository) {
+        executer.withArguments('-i', "-P${repository}")
+        def result = succeeds 'retrieve'
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        server.resetExpectations()
+        return result
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..45e76be
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.MavenRepository
+
+import org.junit.Rule
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at TargetVersions('1.0-milestone-6+')
+class CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+
+    def "uses cached artifacts from previous Gradle version when no sha1 header"() {
+        given:
+        def projectB = new MavenRepository(file('repo')).module('org.name', 'projectB').publish()
+        server.sendSha1Header = false
+        server.start()
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        server.allowGet('/org', file('repo/org'))
+
+        and:
+        version previous withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        projectB.expectPomHead(server)
+        server.expectGet("/org/name/projectB/1.0/projectB-1.0.pom.sha1", projectB.sha1File(projectB.pomFile))
+        projectB.expectArtifactHead(server)
+        server.expectGet("/org/name/projectB/1.0/projectB-1.0.jar.sha1", projectB.sha1File(projectB.artifactFile))
+
+        and:
+        version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+
+    def "uses cached artifacts from previous Gradle version with sha1 header"() {
+        given:
+        def projectB = new MavenRepository(file('repo')).module('org.name', 'projectB').publish()
+        server.sendSha1Header = true
+        server.start()
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}' }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        server.allowGet('/org', file('repo/org'))
+
+        and:
+        version previous withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        projectB.expectPomHead(server)
+        projectB.expectArtifactHead(server)
+
+        and:
+        version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+
+    def cleanup() {
+        server.resetExpectations()
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..47f6285
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 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.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.junit.Rule
+
+// TODO:DAZ Support for milestone-3 does not include POM reuse. We should probably ditch milestone-3 support after 1.0.
+ at TargetVersions('1.0-milestone-3')
+class M3CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+
+    def "uses cached artifacts from previous Gradle version"() {
+        given:
+        def projectB = new MavenRepository(file('repo')).module('org.name', 'projectB').publish()
+
+        server.start()
+        buildFile << """
+repositories {
+    mavenRepo(urls: ['http://localhost:${server.port}'])
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectB:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        def userHome = file('user-home')
+
+        when:
+        server.allowGet("/org", file('repo/org'));
+
+        and:
+        version previous withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        def snapshot = file('libs/projectB-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        server.expectGet("/org/name/projectB/1.0/projectB-1.0.pom", projectB.pomFile)
+        projectB.expectArtifactHead(server)
+        server.expectGet("/org/name/projectB/1.0/projectB-1.0.jar.sha1", projectB.sha1File(projectB.artifactFile))
+
+        and:
+        version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
+
+        then:
+        file('libs').assertHasDescendants('projectB-1.0.jar')
+        file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
+    }
+
+    def cleanup() {
+        server.resetExpectations()
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenLocalCacheReuseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenLocalCacheReuseIntegrationTest.groovy
new file mode 100644
index 0000000..12d9058
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenLocalCacheReuseIntegrationTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 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.resolve.artifactreuse
+
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TestFile
+import org.junit.Rule
+
+class MavenLocalCacheReuseIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+    TestFile repoFile
+    
+    def "setup"() {
+        requireOwnUserHomeDir()
+        repoFile = file('repo')
+    }
+
+    def "uses cached artifacts from maven local cache"() {
+        given:
+        publishAndInstallToMaven()
+        server.start()
+
+        buildFile.text = """
+repositories {
+    maven { url "http://localhost:${server.port}" }
+}
+configurations { compile }
+dependencies {
+    compile 'gradletest.maven.local.cache.test:foo:1.0'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        server.expectHead('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom'))
+        server.expectGet('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom.sha1', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom.sha1'))
+        server.expectHead('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar'))
+        server.expectGet('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar.sha1', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar.sha1'))
+
+        then:
+        run 'retrieve'
+    }
+
+    private def publishAndInstallToMaven() {
+
+        settingsFile << """
+            rootProject.name = 'foo'
+"""
+
+        buildFile.text = """
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'gradletest.maven.local.cache.test'
+version = '1.0'
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "${repoFile.toURI().toURL()}")
+        }
+    }
+}
+"""
+
+        run 'install'
+        run 'uploadArchives'
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
new file mode 100644
index 0000000..b0295cb
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2011 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.resolve.artifactreuse
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.util.SetSystemProperties
+import org.hamcrest.Matchers
+import org.junit.Rule
+
+class ResolutionOverrideIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule
+    public SetSystemProperties systemProperties = new SetSystemProperties()
+
+    public void "will non-changing module when run with --refresh-dependencies"() {
+        given:
+        server.start()
+        def module = mavenRepo().module('org.name', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'org.name:projectA:1.2' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+        and:
+        server.allowGet('/repo', mavenRepo().rootDir)
+
+        when:
+        succeeds 'retrieve'
+        
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        def snapshot = file('libs/projectA-1.2.jar').snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        and:
+        executer.withArguments('--refresh-dependencies')
+        succeeds 'retrieve'
+        
+        then:
+        file('libs/projectA-1.2.jar').assertIsCopyOf(module.artifactFile).assertHasChangedSince(snapshot)
+    }
+
+    public void "will recover from missing module when run with --refresh-dependencies"() {
+        server.start()
+
+        given:
+        def module = mavenRepo().module('org.name', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { missing }
+dependencies {
+    missing 'org.name:projectA:1.2'
+}
+task showMissing << { println configurations.missing.files }
+"""
+
+        when:
+        server.expectGetMissing('/org/name/projectA/1.2/projectA-1.2.pom')
+        server.expectHeadMissing('/org/name/projectA/1.2/projectA-1.2.jar')
+
+        then:
+        fails("showMissing")
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/org/name/projectA/1.2/projectA-1.2.pom', module.pomFile)
+        server.expectGet('/org/name/projectA/1.2/projectA-1.2.jar', module.artifactFile)
+
+        then:
+        executer.withArguments("--refresh-dependencies")
+        succeeds('showMissing')
+    }
+
+    public void "will recover from missing artifact when run with --refresh-dependencies"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = mavenRepo().module('org.name', 'projectA', '1.2').publish()
+
+        when:
+        server.expectGet('/org/name/projectA/1.2/projectA-1.2.pom', module.pomFile)
+        server.expectGetMissing('/org/name/projectA/1.2/projectA-1.2.jar')
+
+        then:
+        fails "retrieve"
+
+        when:
+        server.resetExpectations()
+        module.expectPomHead(server)
+        module.expectArtifactGet(server)
+
+        then:
+        executer.withArguments("--refresh-dependencies")
+        succeeds 'retrieve'
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    public void "will not expire cache entries when run with offline flag"() {
+
+        given:
+        server.start()
+        def module = mavenRepo().module("org.name", "unique", "1.0-SNAPSHOT").publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+dependencies {
+    compile "org.name:unique:1.0-SNAPSHOT"
+}
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:  "Server handles requests"
+        server.allowGet("/repo", mavenRepo().rootDir)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar')
+        def snapshot = file('libs/unique-1.0-SNAPSHOT.jar').snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        and: "We resolve again, offline"
+        server.resetExpectations()
+        executer.withArguments('--offline')
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar')
+        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    public void "does not attempt to contact server when run with offline flag"() {
+        given:
+        server.start()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'org.name:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("--offline")
+
+        then:
+        fails 'listJars'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':listJars\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString('No cached version available for offline mode'))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..25126a7
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/caching/CachedDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2012 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.resolve.caching
+
+import org.gradle.integtests.fixtures.IvyModule
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.HttpServer
+
+/**
+ * We are using Ivy here, but the strategy is the same for any kind of repository.
+ */
+class CachedDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    IvyModule module
+
+    TestFile downloaded
+    TestFile.Snapshot lastState
+
+    def setup() {
+        server.start()
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        module = ivyRepo().module("group", "projectA", "1.1")
+        module.publish()
+
+        downloaded = file('build/projectA-1.1.jar')
+    }
+
+    void initialResolve() {
+        module.expectIvyGet(server)
+        module.expectArtifactGet(server)
+
+        resolve()
+    }
+
+    void resolve() {
+        if (downloaded.exists()) {
+            lastState = downloaded.snapshot()
+        }
+
+        succeeds ":retrieve"
+    }
+
+    void headOnlyRequests() {
+        module.expectIvyHead(server)
+        module.expectArtifactHead(server)
+    }
+
+    void headSha1ThenGetRequests() {
+        module.expectIvyHead(server)
+        module.expectIvySha1Get(server)
+        module.expectIvyGet(server)
+
+        module.expectArtifactHead(server)
+        module.expectArtifactSha1Get(server)
+        module.expectArtifactGet(server)
+    }
+
+    void sha1OnlyRequests() {
+        module.expectIvySha1Get(server)
+        module.expectArtifactSha1Get(server)
+    }
+
+    void sha1ThenGetRequests() {
+        module.expectIvySha1Get(server)
+        module.expectIvyGet(server)
+
+        module.expectArtifactSha1Get(server)
+        module.expectArtifactGet(server)
+    }
+
+    void headThenSha1Requests() {
+        module.expectIvyHead(server)
+        module.expectIvySha1Get(server)
+
+        module.expectArtifactHead(server)
+        module.expectArtifactSha1Get(server)
+    }
+
+    void headThenGetRequests() {
+        module.expectIvyHead(server)
+        module.expectIvyGet(server)
+
+        module.expectArtifactHead(server)
+        module.expectArtifactGet(server)
+    }
+
+    void unchangedResolve() {
+        resolve()
+        downloaded.assertHasNotChangedSince(lastState)
+    }
+
+    void changedResolve() {
+        resolve()
+        downloaded.assertHasChangedSince(lastState)
+    }
+
+    void change() {
+        module.publishWithChangedContent()
+    }
+
+    def "etags are used to determine changed"() {
+        given:
+        server.etags = HttpServer.EtagStrategy.RAW_SHA1_HEX
+        server.sendLastModified = false
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headSha1ThenGetRequests()
+        changedResolve()
+    }
+
+    def "last modified and content length are used to determine changed"() {
+        given:
+        server.etags = null
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headSha1ThenGetRequests()
+        changedResolve()
+    }
+
+    def "checksum is used when last modified and content length can't be used"() {
+        given:
+        server.etags = null
+        server.sendLastModified = false
+        initialResolve()
+
+        expect:
+        headThenSha1Requests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headSha1ThenGetRequests()
+        changedResolve()
+    }
+
+    def "no need for sha1 request if we get it in the metadata"() {
+        given:
+        server.sendSha1Header = true
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headThenGetRequests()
+        changedResolve()
+    }
+
+    def "no need for sha1 request if we know the etag is sha1"() {
+        given:
+        server.etags = HttpServer.EtagStrategy.NEXUS_ENCODED_SHA1
+        initialResolve()
+
+        expect:
+        headOnlyRequests()
+        unchangedResolve()
+
+        when:
+        change()
+
+        then:
+        headThenGetRequests()
+        changedResolve()
+    }
+
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FilerSystemResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FilerSystemResolverIntegrationTest.groovy
new file mode 100644
index 0000000..947ebff
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/FilerSystemResolverIntegrationTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.resolve.custom
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.util.TextUtil
+
+class FilerSystemResolverIntegrationTest extends AbstractIntegrationSpec {
+
+    def "file system resolvers use item at source by default"() {
+        when:
+        def repoDir = testDir.createDir("repo")
+        def repo = new IvyRepository(repoDir)
+        def repoDirPath = TextUtil.escapeString(repoDir.absolutePath)
+        def module = repo.module("group", "projectA", "1.2")
+        module.publish()
+        def jar = module.jarFile
+        jar.text = "1"
+        
+        buildFile << """
+            repositories {
+                add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+                    name = "repo"
+                    addIvyPattern("$repoDirPath/[organization]/[module]/[revision]/ivy-[module]-[revision].xml")
+                    addArtifactPattern("$repoDirPath/[organization]/[module]/[revision]/[module]-[revision].[ext]")
+                }
+            }
+            configurations { compile }
+            dependencies { compile 'group:projectA:1.2' }
+            task echoContent << {
+                def dep = configurations.compile.singleFile
+                println "content: " + dep.text
+                println "path: " + dep.canonicalPath
+            }
+        """
+
+        then:
+        succeeds 'echoContent'
+        scrapeValue("content") == "1"
+        scrapeValue("path") == jar.canonicalPath
+
+        when:
+        jar.text = "2"
+
+        then:
+        succeeds 'echoContent'
+        scrapeValue("content") == "2"
+        scrapeValue("path") == jar.canonicalPath
+    }
+
+    protected scrapeValue(label) {
+        def fullLabel = "$label: "
+        for (line in output.readLines()) {
+            if (line.startsWith(fullLabel))  {
+                return line - fullLabel
+            }
+        }
+
+        null
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
new file mode 100644
index 0000000..39a14a2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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.resolve.custom
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.integtests.fixtures.SFTPServer
+import org.junit.Rule
+
+class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule
+    public final SFTPServer server = new SFTPServer(distribution.temporaryFolder)
+
+    def "setup"() {
+        requireOwnUserHomeDir()
+    }
+
+    public void "can resolve and cache dependencies from an SFTP Ivy repository"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish();
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.SFTPResolver()) {
+        name = "sftprepo"
+        host = "${server.hostAddress}"
+        port = ${server.port}
+        user = "simple"
+        userPassword = "simple"
+        addIvyPattern "repos/libs/[organization]/[module]/[revision]/ivy-[revision].xml"
+        addArtifactPattern "repos/libs/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        withDebugLogging()
+
+        succeeds 'listJars'
+
+        then:
+        server.fileRequests == ["repos/libs/group/projectA/1.2/ivy-1.2.xml",
+                                "repos/libs/group/projectA/1.2/projectA-1.2.jar"
+                               ] as Set
+
+        when:
+        server.clearRequests()
+        succeeds 'listJars'
+
+        then:
+        server.fileRequests.empty
+    }
+
+    IvyRepository ivyRepo() {
+            return new IvyRepository(server.file("repos/libs/"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
new file mode 100644
index 0000000..a22a21b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2011 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.resolve.custom
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+        name = "repo"
+        addIvyPattern("http://localhost:${server.port}/repo/[organization]/ivy-[module]-[revision].xml")
+        addArtifactPattern("http://localhost:${server.port}/repo/[organization]/[module]-[revision].[ext]")
+    }
+}
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        server.expectHead('/repo/group/ivy-projectA-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/ivy-projectA-1.2.xml', module.ivyFile)
+        server.expectHead('/repo/group/projectA-1.2.jar', module.jarFile)
+        server.expectGet('/repo/group/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds 'listJars'
+    }
+
+    public void "honours changing patterns from custom resolver"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2-SNAPSHOT')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+        name = "repo"
+        addIvyPattern("http://localhost:${server.port}/repo/[organization]/ivy-[module]-[revision].xml")
+        addArtifactPattern("http://localhost:${server.port}/repo/[organization]/[module]-[revision].[ext]")
+        changingMatcher = 'regexp'
+        changingPattern = '.*SNAPSHOT.*'
+    }
+}
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+        when:
+        server.expectHead('/repo/group/ivy-projectA-1.2-SNAPSHOT.xml', module.ivyFile)
+        server.expectGet('/repo/group/ivy-projectA-1.2-SNAPSHOT.xml', module.ivyFile)
+        server.expectHead('/repo/group/projectA-1.2-SNAPSHOT.jar', module.jarFile)
+        server.expectGet('/repo/group/projectA-1.2-SNAPSHOT.jar', module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        def jarFile = file('libs/projectA-1.2-SNAPSHOT.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectHead('/repo/group/ivy-projectA-1.2-SNAPSHOT.xml', module.ivyFile)
+        server.expectGet('/repo/group/ivy-projectA-1.2-SNAPSHOT.xml', module.ivyFile)
+        server.expectHead('/repo/group/projectA-1.2-SNAPSHOT.jar', module.jarFile)
+        server.expectGet('/repo/group/projectA-1.2-SNAPSHOT.jar', module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        def changedJarFile = file('libs/projectA-1.2-SNAPSHOT.jar')
+        changedJarFile.assertHasChangedSince(snapshot)
+        changedJarFile.assertIsCopyOf(module.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..63f04c1
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2011 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.resolve.ivy
+
+import org.hamcrest.Matchers
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.startsWith
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyBrokenRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "reports and caches missing module"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { missing }
+dependencies {
+    missing 'group:projectA:1.2'
+}
+if (project.hasProperty('doNotCacheChangingModules')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+task showMissing << { println configurations.missing.files }
+"""
+
+        when:
+        server.expectGetMissing('/group/projectA/1.2/ivy-1.2.xml')
+        server.expectHeadMissing('/group/projectA/1.2/projectA-1.2.jar')
+        fails("showMissing")
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showMissing\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
+        failure.assertThatCause(Matchers.containsString('Could not find group:group, module:projectA, version:1.2.'))
+
+        when:
+        server.resetExpectations() // Missing status is cached
+        fails('showMissing')
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showMissing\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
+        failure.assertThatCause(Matchers.containsString('Could not find group:group, module:projectA, version:1.2.'))
+    }
+
+    public void "reports and recovers from broken module"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.3')
+        module.publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { broken }
+dependencies {
+    broken 'group:projectA:1.3'
+}
+task showBroken << { println configurations.broken.files }
+"""
+
+        when:
+        server.addBroken('/')
+        fails("showBroken")
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showBroken\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':broken\'.')
+        failure.assertHasCause('Could not resolve group:group, module:projectA, version:1.3')
+        failure.assertHasCause("Could not GET 'http://localhost:${server.port}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/group/projectA/1.3/ivy-1.3.xml', module.ivyFile)
+        server.expectGet('/group/projectA/1.3/projectA-1.3.jar', module.jarFile)
+        
+        then:
+        succeeds("showBroken")
+    }
+
+    public void "reports and caches missing artifacts"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = ivyRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        when:
+        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGetMissing('/group/projectA/1.2/projectA-1.2.jar')
+
+        then:
+        fails "retrieve"
+
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+
+        when:
+        server.resetExpectations()
+
+        then:
+        fails "retrieve"
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+    }
+
+    public void "reports and recovers from failed artifact download"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = ivyRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        when:
+        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.addBroken('/group/projectA/1.2/projectA-1.2.jar')
+
+        then:
+        fails "retrieve"
+        failure.assertHasCause("Could not download artifact 'group:projectA:1.2 at jar'")
+        failure.assertThatCause(startsWith("Could not GET"))
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds "retrieve"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..93228de
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolutionIntegrationTest.groovy
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2012 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.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyChangingModuleRemoteResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "detects changed module descriptor when flagged as changing"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1.1 is published"
+        def module = ivyRepo().module("group", "projectA", "1.1")
+        module.publish()
+
+        and: "Server handles requests"
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+
+        and: "We request 1.1 (changing)"
+        run 'retrieve'
+
+        then: "Version 1.1 jar is downloaded"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Module meta-data is changed (new artifact)"
+        module.artifact([name: 'other'])
+        module.dependsOn("group", "projectB", "2.0")
+        module.publish()
+        def moduleB = ivyRepo().module("group", "projectB", "2.0")
+        moduleB.publish();
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml.sha1', module.sha1File(module.ivyFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar.sha1', module.sha1File(module.jarFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+        server.expectHeadThenGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
+        server.expectGet('/repo/group/projectB/2.0/ivy-2.0.xml', moduleB.ivyFile)
+        server.expectGet('/repo/group/projectB/2.0/projectB-2.0.jar', moduleB.jarFile)
+
+        and: "We request 1.1 again"
+        run 'retrieve'
+
+        then: "We get all artifacts, including the new ones"
+        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar', 'projectB-2.0.jar')
+    }
+
+    def "can mark a module as changing after first retrieval"() {
+        server.start()
+
+        given:
+        buildFile << """
+def isChanging = project.hasProperty('isChanging') ? true : false
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: isChanging
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+        and:
+        def module = ivyRepo().module("group", "projectA", "1.1")
+        module.publish()
+        server.allowGet('/repo', ivyRepo().rootDir)
+
+        when: 'original retrieve'
+        run 'retrieve'
+
+        then:
+        def jarSnapshot = file('build/projectA-1.1.jar').snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        and:
+        executer.withArguments('-PisChanging')
+        run 'retrieve'
+
+        then:
+        file('build/projectA-1.1.jar').assertHasChangedSince(jarSnapshot)
+    }
+
+    def "detects changed artifact when flagged as changing"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        and:
+        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+
+        when:
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        def jarFile = file('build/projectA-1.1.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead(server, '/repo')
+        module.expectIvySha1Get(server, '/repo')
+        module.expectIvyGet(server, '/repo')
+        module.expectArtifactHead(server, '/repo')
+        module.expectArtifactSha1Get(server, '/repo')
+        module.expectArtifactGet(server, '/repo')
+
+        run 'retrieve'
+
+        then:
+        def changedJarFile = file('build/projectA-1.1.jar')
+        changedJarFile.assertHasChangedSince(snapshot)
+        changedJarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "caches changing module descriptor and artifacts until cache expiry"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+
+if (project.hasProperty('doNotCacheChangingModules')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1.1 is published"
+        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+
+        and: "Server handles requests"
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+
+        and: "We request 1.1 (changing)"
+        run 'retrieve'
+
+        then: "Version 1.1 jar is downloaded"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+        def jarFile = file('build/projectA-1.1.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when: "Module meta-data is changed and artifacts are modified"
+        module.artifact([name: 'other'])
+        module.publishWithChangedContent()
+
+        and: "We request 1.1 (changing), with module meta-data cached. No server requests."
+        run 'retrieve'
+
+        then: "Original module meta-data and artifacts are used"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+        jarFile.assertHasNotChangedSince(snapshot)
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml.sha1', module.sha1File(module.ivyFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar.sha1', module.sha1File(module.jarFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+        server.expectHeadThenGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
+
+        and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
+        executer.withArguments("-PdoNotCacheChangingModules")
+        run 'retrieve'
+
+        then: "We get new artifacts based on the new meta-data"
+        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar')
+        jarFile.assertHasChangedSince(snapshot)
+        jarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "can use cache-control DSL to mimic changing pattern for ivy repository"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+import static java.util.concurrent.TimeUnit.SECONDS
+configurations.all {
+    resolutionStrategy.resolutionRules.with {
+        eachModule({ moduleResolve ->
+            if (moduleResolve.request.version.endsWith('-CHANGING')) {
+                moduleResolve.cacheFor(0, SECONDS)
+            }
+        } as Action)
+
+        eachArtifact({ artifactResolve ->
+            if (artifactResolve.request.moduleVersionIdentifier.version.endsWith('-CHANGING')) {
+                artifactResolve.cacheFor(0, SECONDS)
+            }
+        } as Action)
+    }
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1-CHANGING"
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1-CHANGING is published"
+        def module = ivyRepo().module("group", "projectA", "1-CHANGING").publish()
+
+        and: "Server handles requests"
+        server.expectGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar', module.jarFile)
+
+        and: "We request 1-CHANGING"
+        run 'retrieve'
+
+        then: "Version 1-CHANGING jar is used"
+        file('build').assertHasDescendants('projectA-1-CHANGING.jar')
+        def jarFile = file('build/projectA-1-CHANGING.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when: "Module meta-data is changed and artifacts are modified"
+        module.artifact([name: 'other'])
+        module.publishWithChangedContent()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml.sha1', module.sha1File(module.ivyFile))
+        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar.sha1', module.sha1File(module.jarFile))
+        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar', module.jarFile)
+        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/other-1-CHANGING.jar', module.moduleDir.file('other-1-CHANGING.jar'))
+
+        and: "We request 1-CHANGING again"
+        executer.withArguments()
+        run 'retrieve'
+
+        then: "We get new artifacts based on the new meta-data"
+        file('build').assertHasDescendants('projectA-1-CHANGING.jar', 'other-1-CHANGING.jar')
+        jarFile.assertHasChangedSince(snapshot)
+        jarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "avoid redownload unchanged artifact when no checksum available"() {
+        server.start()
+
+        given:
+        buildFile << """
+            repositories {
+                ivy { url "http://localhost:${server.port}/repo" }
+            }
+
+            configurations { compile }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile group: "group", name: "projectA", version: "1.1", changing: true
+            }
+
+            task retrieve(type: Copy) {
+                into 'build'
+                from configurations.compile
+            }
+        """
+
+        and:
+        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+        
+        // Set the last modified to something that's not going to be anything “else”.
+        // There are lots of dates floating around in a resolution and we want to make
+        // sure we use this.
+        module.jarFile.setLastModified(2000)
+        module.ivyFile.setLastModified(6000)
+
+        def base = "/repo/group/projectA/1.1"
+        def ivyPath = "$base/$module.ivyFile.name"
+        def ivySha1Path = "${ivyPath}.sha1"
+        def originalIvyLastMod = module.ivyFile.lastModified()
+        def originalIvyContentLength = module.ivyFile.length()
+        def jarPath = "$base/$module.jarFile.name"
+        def jarSha1Path = "${jarPath}.sha1"
+        def originalJarLastMod = module.jarFile.lastModified()
+        def originalJarContentLength = module.jarFile.length()
+
+        when:
+        server.expectGet(ivyPath, module.ivyFile)
+        server.expectGet(jarPath, module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        def downloadedJar = file('build/projectA-1.1.jar')
+        downloadedJar.assertIsCopyOf(module.jarFile)
+        def snapshot = downloadedJar.snapshot()
+
+        // Do change the jar, so we can check that the new version wasn't downloaded
+        module.publishWithChangedContent()
+
+        when:
+        server.resetExpectations()
+        server.expectGetMissing(ivySha1Path)
+        server.expectHead(ivyPath, module.ivyFile, originalIvyLastMod, originalIvyContentLength)
+        server.expectGetMissing(jarSha1Path)
+        server.expectHead(jarPath, module.jarFile, originalJarLastMod, originalJarContentLength)
+
+        run 'retrieve'
+
+        then:
+        downloadedJar.assertHasNotChangedSince(snapshot)
+
+        when:
+        server.resetExpectations()
+        server.expectGetMissing(ivySha1Path)
+        server.expectHead(ivyPath, module.ivyFile)
+        server.expectGet(ivyPath, module.ivyFile)
+        server.expectGetMissing(jarSha1Path)
+        server.expectHead(jarPath, module.jarFile)
+        server.expectGet(jarPath, module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        downloadedJar.assertHasChangedSince(snapshot)
+        downloadedJar.assertIsCopyOf(module.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDependencyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDependencyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..c57f0dd
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDependencyResolveIntegrationTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2011 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.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "dependency includes all artifacts and transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: "classifier")
+        module.artifact(name: "test-extra")
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'test-1.45-classifier.jar', 'test-extra-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references a classifier includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        repo.module("org.gradle", "test", "1.45")
+                .dependsOn("org.gradle", "other", "preview-1")
+                .artifact(classifier: "classifier")
+                .artifact(name: "test-extra")
+                .publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45:classifier"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: "classifier")
+        module.artifact(name: "test-extra")
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile ("org.gradle:test:1.45") {
+        artifact {
+            name = 'test-extra'
+            type = 'jar'
+        }
+    }
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-extra-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "transitive flag of referenced configuration affects its transitive dependencies only"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.nonTransitive('default')
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
+        repo.module("org.gradle", "other2", "7").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations {
+    compile
+    runtime.extendsFrom compile
+}
+dependencies {
+    compile "org.gradle:test:1.45"
+    runtime "org.gradle:other:preview-1"
+}
+
+task check << {
+    def spec = { it.name == 'test' } as Spec
+
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+
+    assert configurations.runtime.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar', 'other2-7.jar']
+    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..7abfb00
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolutionIntegrationTest.groovy
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2011 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.resolve.ivy
+
+import org.gradle.integtests.fixtures.IvyModule
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyDynamicRevisionRemoteResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "uses latest version from version range and latest status"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+    compile group: "group", name: "projectB", version: "latest.integration"
+}
+
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def projectA1 = repo.module("group", "projectA", "1.1").publish()
+        repo.module("group", "projectA", "2.0").publish()
+        def projectB1 = repo.module("group", "projectB", "1.1").publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(projectA1)
+        serveUpDynamicRevision(projectB1)
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "New versions are published"
+        def projectA2 = repo.module("group", "projectA", "1.2").publish()
+        def projectB2 = repo.module("group", "projectB", "2.2").publish()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        serveUpDynamicRevision(projectA2)
+        serveUpDynamicRevision(projectB2)
+
+        and:
+        run 'retrieve'
+
+        then: "New versions are used"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+    }
+
+
+    def "determines latest version with jar only"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+  ivy {
+      url "http://localhost:${server.port}"
+  }
+}
+
+configurations { compile }
+
+dependencies {
+  compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def projectA11 = repo.module("group", "projectA", "1.1").publish()
+        def projectA12 = repo.module("group", "projectA", "1.2").publish()
+        repo.module("group", "projectA", "2.0").publish()
+
+        and: "Server handles requests"
+        server.expectGetDirectoryListing("/${projectA12.organisation}/${projectA12.module}/", projectA12.moduleDir.parentFile)
+        server.expectGetMissing("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/ivy-${projectA12.revision}.xml")
+        server.expectGetMissing("/${projectA11.organisation}/${projectA11.module}/${projectA11.revision}/ivy-${projectA11.revision}.xml")
+
+        // TODO:DAZ Should not list twice
+        server.expectGetDirectoryListing("/${projectA12.organisation}/${projectA12.module}/", projectA12.moduleDir.parentFile)
+        server.expectHead("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/${projectA12.module}-${projectA12.revision}.jar", projectA12.jarFile)
+        server.expectGet("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/${projectA12.module}-${projectA12.revision}.jar", projectA12.jarFile)
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "uses latest version with correct status for latest.release and latest.milestone"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/repo"
+    }
+}
+
+configurations {
+    release
+    milestone
+}
+
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+}
+
+dependencies {
+    release group: "group", name: "projectA", version: "latest.release"
+    milestone group: "group", name: "projectA", version: "latest.milestone"
+}
+
+task retrieve(dependsOn: ['retrieveRelease', 'retrieveMilestone'])
+
+task retrieveRelease(type: Sync) {
+    from configurations.release
+    into 'release'
+}
+
+task retrieveMilestone(type: Sync) {
+    from configurations.milestone
+    into 'milestone'
+}
+"""
+
+        when: "Versions are published"
+        repo.module("group", "projectA", "1.0").withStatus('release').publish()
+        repo.module('group', 'projectA', '1.1').withStatus('milestone').publish()
+        repo.module('group', 'projectA', '1.2').withStatus('integration').publish()
+        repo.module("group", "projectA", "2.0").withStatus('release').publish()
+        repo.module('group', 'projectA', '2.1').withStatus('milestone').publish()
+        repo.module('group', 'projectA', '2.2').withStatus('integration').publish()
+
+        and: "Server handles requests"
+        server.allowGet('/repo', repo.rootDir)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('release').assertHasDescendants('projectA-2.0.jar')
+        file('milestone').assertHasDescendants('projectA-2.1.jar')
+    }
+
+    def "checks new repositories before returning any cached value"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo1" }
+}
+
+if (project.hasProperty('addRepo2')) {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo2" }
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
+        projectA11.publish()
+        def projectA12 = ivyRepo('repo2').module("group", "projectA", "1.2")
+        projectA12.publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(projectA11, "/repo1")
+
+        and: "Retrieve with only repo1"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        serveUpDynamicRevision(projectA12, "/repo2")
+
+        and: "Retrieve with both repos"
+        executer.withArguments("-PaddRepo2")
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "uses and caches latest of versions obtained from multiple HTTP repositories"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo1" }
+    ivy { url "http://localhost:${server.port}/repo2" }
+    ivy { url "http://localhost:${server.port}/repo3" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Versions are published"
+        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
+        projectA11.publish()
+        def projectA12 = ivyRepo('repo3').module("group", "projectA", "1.2")
+        projectA12.publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(projectA11, "/repo1")
+        // TODO Should only list missing directory once
+        server.expectGetMissing("/repo2/group/projectA/")
+        server.expectGetMissing("/repo2/group/projectA/")
+        serveUpDynamicRevision(projectA12, "/repo3")
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when: "Run again with cached dependencies"
+        server.resetExpectations()
+        def result = run 'retrieve'
+
+        then: "No server requests, task skipped"
+        result.assertTaskSkipped(':retrieve')
+    }
+
+    def "caches resolved revisions until cache expiry"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+if (project.hasProperty('noDynamicRevisionCache')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def version1 = repo.module("group", "projectA", "1.1")
+        version1.publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(version1)
+
+        and: "We request 1.+"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+
+        when: "Version 1.2 is published"
+        def version2 = repo.module("group", "projectA", "1.2")
+        version2.publish()
+
+        and: "We request 1.+, with dynamic mappings cached. No server requests."
+        run 'retrieve'
+
+        then: "Version 1.1 is still used, as the 1.+ -> 1.1 mapping is cached"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+
+        when: "Server handles requests"
+        serveUpDynamicRevision(version2)
+
+        and: "We request 1.+, with zero expiry for dynamic revision cache"
+        executer.withDeprecationChecksDisabled().withArguments("-d", "-PnoDynamicRevisionCache").withTasks('retrieve').run()
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(version2.jarFile)
+    }
+
+    def "uses and caches dynamic revisions for transitive dependencies"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "main", version: "1.0"
+}
+
+if (project.hasProperty('noDynamicRevisionCache')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version is published"
+        def mainProject = repo.module("group", "main", "1.0")
+        mainProject.dependsOn("group", "projectA", "1.+")
+        mainProject.dependsOn("group", "projectB", "latest.integration")
+        mainProject.publish()
+
+        and: "transitive dependencies have initial values"
+        def projectA1 = repo.module("group", "projectA", "1.1")
+        projectA1.publish()
+        def projectB1 = repo.module("group", "projectB", "1.1")
+        projectB1.publish()
+
+        and: "Server handles requests"
+        server.expectGet("/group/main/1.0/ivy-1.0.xml", mainProject.ivyFile)
+        server.expectGet("/group/main/1.0/main-1.0.jar", mainProject.jarFile)
+        serveUpDynamicRevision(projectA1)
+        serveUpDynamicRevision(projectB1)
+
+        and:
+        run 'retrieve'
+
+        then: "Initial transitive dependencies are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "New versions are published"
+        def projectA2 = repo.module("group", "projectA", "1.2")
+        projectA2.publish()
+        def projectB2 = repo.module("group", "projectB", "2.2")
+        projectB2.publish()
+
+        and: "No server requests"
+        server.resetExpectations()
+
+        and:
+        run 'retrieve'
+
+        then: "Cached versions are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        serveUpDynamicRevision(projectA2)
+        serveUpDynamicRevision(projectB2)
+
+        and: "DynamicRevisionCache is bypassed"
+        executer.withArguments("-PnoDynamicRevisionCache")
+        run 'retrieve'
+
+        then: "New versions are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.2.jar', 'projectB-2.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+    }
+
+    private def serveUpDynamicRevision(IvyModule module, String prefix = "") {
+        server.expectGetDirectoryListing("${prefix}/${module.organisation}/${module.module}/", module.moduleDir.parentFile)
+        server.expectGet("${prefix}/${module.organisation}/${module.module}/${module.revision}/ivy-${module.revision}.xml", module.ivyFile)
+        server.expectGet("${prefix}/${module.organisation}/${module.module}/${module.revision}/${module.module}-${module.revision}.jar", module.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyLocalDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyLocalDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..bb59d8c
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyLocalDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyLocalDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "does not cache local artifacts or metadata"() {
+        given:
+        def repo = ivyRepo()
+        def moduleA = repo.module('group', 'projectA', '1.2')
+        moduleA.publish()
+        def moduleB = repo.module('group', 'projectB', '9-beta')
+        moduleB.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9-beta')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-9-beta.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
+        file('libs/projectB-9-beta.jar').assertIsCopyOf(moduleB.jarFile)
+    }
+
+    public void "does not cache resolution of dynamic versions or changing modules"() {
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+    compile group: "group", name: "projectB", version: "latest.integration"
+    compile group: "group", name: "projectC", version: "1.0", changing: true
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        def projectA1 = repo.module("group", "projectA", "1.1")
+        projectA1.publish()
+        def projectB1 = repo.module("group", "projectB", "1.0")
+        projectB1.publish()
+        def projectC1 = repo.module("group", "projectC", "1.0")
+        projectC1.publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.0.jar', 'projectC-1.0.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.0.jar').assertIsCopyOf(projectB1.jarFile)
+        def jarC = file('libs/projectC-1.0.jar')
+        jarC.assertIsCopyOf(projectC1.jarFile)
+        def jarCsnapshot = jarC.snapshot()
+
+        when:
+        def projectA2 = repo.module("group", "projectA", "1.2")
+        projectA2.publish()
+        def projectB2 = repo.module("group", "projectB", "2.0")
+        projectB2.publish()
+        projectC1.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.0.jar', 'projectC-1.0.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.0.jar').assertIsCopyOf(projectB2.jarFile)
+
+        def jarC1 = file('libs/projectC-1.0.jar')
+        jarC1.assertIsCopyOf(projectC1.jarFile)
+        jarC1.assertHasChangedSince(jarCsnapshot)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyRemoteDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..2310987
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyRemoteDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2011 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.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds 'listJars'
+    }
+
+    public void "can resolve and cache artifact-only dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+
+        when:
+        // TODO: Should meta-data be fetched for an artifact-only dependency?
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "can resolve and cache dependencies from multiple HTTP Ivy repositories"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def moduleA = repo.module('group', 'projectA').publish()
+        def moduleB = repo.module('group', 'projectB').publish()
+        def moduleC = repo.module('group', 'projectC').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo1" }
+    ivy { url "http://localhost:${server.port}/repo2" }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0', 'group:projectC:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/ivy-1.0.xml', moduleA.ivyFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', moduleA.jarFile)
+
+        // Handles missing in repo1
+        server.expectGetMissing('/repo1/group/projectB/1.0/ivy-1.0.xml')
+        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+
+        server.expectGet('/repo2/group/projectB/1.0/ivy-1.0.xml', moduleB.ivyFile)
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', moduleB.jarFile)
+
+        // Handles from broken url in repo1 (but does not cache)
+        server.addBroken('/repo1/group/projectC')
+        server.expectGet('/repo2/group/projectC/1.0/ivy-1.0.xml', moduleC.ivyFile)
+        server.expectGet('/repo2/group/projectC/1.0/projectC-1.0.jar', moduleC.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        server.addBroken('/repo1/group/projectC') // Will always re-attempt a broken repository
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "can resolve dependencies from password protected HTTP Ivy repository"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/repo"
+
+        credentials {
+            password 'password'
+            username 'username'
+        }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "uses all configured patterns to resolve artifacts and caches result"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/first"
+        artifactPattern "http://localhost:${server.port}/second/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        artifactPattern "http://localhost:${server.port}/third/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        ivyPattern "http://localhost:${server.port}/second/[module]/[revision]/ivy.xml"
+        ivyPattern "http://localhost:${server.port}/third/[module]/[revision]/ivy.xml"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task show << { println configurations.compile.files }
+"""
+
+        when:
+        server.expectGetMissing('/first/group/projectA/1.2/ivy-1.2.xml')
+        server.expectGetMissing('/first/group/projectA/1.2/projectA-1.2.jar')
+        server.expectGetMissing('/second/projectA/1.2/ivy.xml')
+        server.expectGetMissing('/second/projectA/1.2/projectA-1.2.jar')
+        server.expectGet('/third/projectA/1.2/ivy.xml', module.ivyFile)
+        server.expectGet('/third/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('show')
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds('show')
+    }
+
+    public void "replaces org.name with org/name when using maven layout"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('org.name.here', 'projectA', '1.2')
+        module.publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+        layout "maven"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name.here:projectA:1.2'
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        server.expectGet('/org/name/here/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/org/name/here/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        and:
+        succeeds('retrieve')
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileDependenciesIntegrationTest.groovy
new file mode 100644
index 0000000..52d2d76
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileDependenciesIntegrationTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import spock.lang.Issue
+
+class BadPomFileDependenciesIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-1005")
+    def "can handle self referencing dependency"() {
+        given:
+        file("settings.gradle") << "include 'client'"
+
+        and:
+        mavenRepo().module('group', 'artifact', '1.0').dependsOn('group', 'artifact', '1.0').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                maven { url "${mavenRepo().uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile "group:artifact:1.0"
+            }
+            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar'] }
+        """
+
+        expect:
+        succeeds ":libs"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..6d83cdd
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDependencyResolveIntegrationTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2011 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.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "dependency includes main artifact and runtime dependencies of referenced module"() {
+        given:
+        def module = mavenRepo().module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: 'classifier')
+        module.publish()
+        mavenRepo().module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url "${mavenRepo().uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references a classifier includes the matching artifact only plus the runtime dependencies of referenced module"() {
+        given:
+        def module = mavenRepo().module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: 'classifier')
+        module.artifact(classifier: 'some-other')
+        module.publish()
+        mavenRepo().module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url "${mavenRepo().uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45:classifier"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references an artifact includes the matching artifact only plus the runtime dependencies of referenced module"() {
+        given:
+        def module = mavenRepo().module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: 'classifier')
+        module.publish()
+        mavenRepo().module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { maven { url "${mavenRepo().uri}" } }
+configurations { compile }
+dependencies {
+    compile ("org.gradle:test:1.45") {
+        artifact {
+            name = 'test'
+            type = 'jar'
+            classifier = 'classifier'
+        }
+    }
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "resolves dependencies on real projects"() {
+        // Hibernate core brings in conflicts, exclusions and parent poms
+        // Add a direct dependency on an earlier version of commons-collection than required by hibernate core
+        // Logback classic depends on a later version of slf4j-api than required by hibernate core
+
+        given:
+        buildFile << """
+repositories {
+    mavenCentral()
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.0"
+    compile "ch.qos.logback:logback-classic:0.9.30"
+    compile "org.hibernate:hibernate-core:3.6.7.Final"
+}
+
+task check << {
+    def compile = configurations.compile
+    assert compile.resolvedConfiguration.firstLevelModuleDependencies.collect { it.name } == [
+        'ch.qos.logback:logback-classic:0.9.30',
+        'org.hibernate:hibernate-core:3.6.7.Final',
+        'commons-collections:commons-collections:3.1'
+    ]
+
+    def filteredDependencies = compile.resolvedConfiguration.getFirstLevelModuleDependencies({ it.name == 'logback-classic' } as Spec)
+    assert filteredDependencies.collect { it.name } == [
+        'ch.qos.logback:logback-classic:0.9.30'
+    ]
+
+    def filteredFiles = compile.resolvedConfiguration.getFiles({ it.name == 'logback-classic' } as Spec)
+    assert filteredFiles.collect { it.name } == [
+        'logback-classic-0.9.30.jar',
+         'logback-core-0.9.30.jar',
+          'slf4j-api-1.6.2.jar'
+    ]
+
+    assert compile.collect { it.name } == [
+        'logback-classic-0.9.30.jar',
+        'hibernate-core-3.6.7.Final.jar',
+        'commons-collections-3.1.jar',
+        'logback-core-0.9.30.jar',
+        'slf4j-api-1.6.2.jar',
+        'antlr-2.7.6.jar',
+        'dom4j-1.6.1.jar',
+        'hibernate-commons-annotations-3.2.0.Final.jar',
+        'hibernate-jpa-2.0-api-1.0.1.Final.jar',
+        'jta-1.1.jar'
+    ]
+
+    assert compile.resolvedConfiguration.resolvedArtifacts.collect { it.file.name } == [
+        'logback-classic-0.9.30.jar',
+        'hibernate-core-3.6.7.Final.jar',
+        'logback-core-0.9.30.jar',
+        'slf4j-api-1.6.2.jar',
+        'antlr-2.7.6.jar',
+        'dom4j-1.6.1.jar',
+        'hibernate-commons-annotations-3.2.0.Final.jar',
+        'hibernate-jpa-2.0-api-1.0.1.Final.jar',
+        'jta-1.1.jar',
+        'commons-collections-3.1.jar'
+    ]
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..daa0c74
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2011 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.resolve.maven
+
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenLocalDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "can resolve snapshots uncached from local Maven repository"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants(moduleA.artifactFile.name)
+        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9.1')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        buildDir.assertHasDescendants(moduleA.artifactFile.name, 'projectB-9.1.jar')
+        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+
+    public void "does not cache artifacts and metadata from local Maven repository"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:1.2' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants('projectA-1.2.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9.1')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+
+    public void "uses artifactUrls to resolve artifacts"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        def artifactsRepo = new MavenRepository(distribution.testFile('artifactsRepo'));
+        // Create a module to get the correct module directory, but do not publish the module
+        def artifactsModuleA = artifactsRepo.module('group', 'projectA', '1.2')
+        moduleA.artifactFile.moveToDirectory(artifactsModuleA.moduleDir)
+
+        and:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenRepo().uri}"
+        artifactUrls "${artifactsRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:9.1'
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(artifactsModuleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..4301f3f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2011 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.resolve.maven
+
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.util.TestFile
+import org.junit.Rule
+
+class MavenRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule public final TestResources resources = new TestResources();
+\
+    def canResolveDependenciesFromMultipleMavenRepositories() {
+        given:
+        List expectedFiles = ['sillyexceptions-1.0.1.jar', 'repotest-1.0.jar', 'testdep-1.0.jar', 'testdep2-1.0.jar',
+                'classifier-1.0-jdk15.jar', 'classifier-dep-1.0.jar', 'jaronly-1.0.jar']
+
+        File projectDir = distribution.testDir
+        executer.inDirectory(projectDir).withTasks('retrieve').run()
+        
+        expect:
+        expectedFiles.each { new TestFile(projectDir, 'build', it).assertExists() }
+    }
+
+    def "can resolve and cache dependencies from HTTP Maven repository"() {
+        given:
+        server.start()
+
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        and:
+        run 'retrieve'
+        
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+        
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+        
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "can resolve and cache artifact-only dependencies from an HTTP Maven repository"() {
+        server.start()
+        given:
+        def module = mavenRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo1" }
+    maven { url "http://localhost:${server.port}/repo2" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        // TODO: Should meta-data be fetched for an artifact-only dependency?
+        server.expectGetMissing('/repo1/group/projectA/1.2/projectA-1.2.pom')
+        server.expectHeadMissing('/repo1/group/projectA/1.2/projectA-1.2.jar')
+
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.pom', module.pomFile)
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', module.artifactFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA', '1.0')
+        projectA.artifact(classifier: 'sources')
+        projectA.artifact(classifier: 'javadoc')
+        projectA.publish()
+        def sourceJar = projectA.artifactFile(classifier: 'sources')
+        def javadocJar = projectA.artifactFile(classifier: 'javadoc')
+
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+dependencies {
+    compile 'group:projectA:1.0'
+}
+eclipse { classpath { downloadJavadoc = true } }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-sources.jar', sourceJar)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar', javadocJar)
+
+        then:
+        succeeds 'eclipseClasspath'
+    }
+
+    def "only attempts to download missing artifacts from HTTP Maven repository once"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA', '1.0')
+        projectA.publish()
+
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+dependencies {
+    compile 'group:projectA:1.0'
+}
+eclipse { classpath { downloadJavadoc = true } }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-sources.jar')
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar')
+
+        then:
+        succeeds 'eclipseClasspath'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds 'eclipseClasspath'
+    }
+
+    def "can resolve and cache dependencies from multiple HTTP Maven repositories"() {
+        given:
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
+}
+"""
+
+        def projectA = mavenRepo().module('group', 'projectA').publish()
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // Looks for POM and JAR in repo1 before looking in repo2 (jar is an attempt to handle publication without module descriptor)
+        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.pom')
+        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        // No server requests when all jars cached
+
+        then:
+        succeeds 'listJars'
+    }
+
+    def "uses artifactsUrl to resolve artifacts"() {
+        given:
+        server.start()
+
+        buildFile << """
+repositories {
+    maven {
+        url 'http://localhost:${server.port}/repo1'
+        artifactUrls 'http://localhost:${server.port}/repo2'
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
+}
+"""
+
+        def projectA = mavenRepo().module('group', 'projectA')
+        def projectB = mavenRepo().module('group', 'projectB')
+        projectA.publish()
+        projectB.publish()
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeeds 'listJars'
+    }
+
+    def "can resolve dependencies from password protected HTTP Maven repository"() {
+        given:
+        server.start()
+        
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}/repo"
+
+        credentials {
+            password 'password'
+            username 'username'
+        }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+        and:
+        def module = mavenRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        when:
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', module.artifactFile)
+
+        and:
+        run 'retrieve'
+        
+        then:
+        file('build').assertHasDescendants('projectA-1.2.jar')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemotePomResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemotePomResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..18a9895
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemotePomResolutionIntegrationTest.groovy
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2011 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.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenRemotePomResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "looks for jar artifact for pom with packing of type 'pom' in the same repository only"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA')
+        projectA.type = 'pom'
+        projectA.publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        // First attempts to resolve in repo1
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+
+        // Then resolves pom (with 'pom' packaging) in repo2, looks there for jar as well
+        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHead('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "includes dependencies from parent pom"() {
+        given:
+        server.start()
+
+        def parentDep = mavenRepo().module("org", "parent_dep").publish()
+        def childDep = mavenRepo().module("org", "child_dep").publish()
+
+        def parent = mavenRepo().module("org", "parent")
+        parent.type = 'pom'
+        parent.dependsOn("parent_dep")
+        parent.publish()
+
+        def child = mavenRepo().module("org", "child")
+        child.dependsOn("child_dep")
+        child.parentPomSection = """
+<parent>
+  <groupId>org</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0</version>
+</parent>
+"""
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
+        server.expectGet('/repo1/org/parent/1.0/parent-1.0.pom', parent.pomFile)
+
+        // Will always check for a default artifact with a module with 'pom' packaging
+        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
+
+        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
+
+        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.pom', parentDep.pomFile)
+        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.jar', parentDep.artifactFile)
+        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.pom', childDep.pomFile)
+        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.jar', childDep.artifactFile)
+
+        // TODO: These shouldn't be required. Or at most should be HEAD requests
+        server.expectGet('/repo1/org/parent/1.0/parent-1.0.pom', parent.pomFile)
+        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.0.jar', 'child_dep-1.0.jar')
+    }
+
+    def "looks for parent pom in different repository"() {
+        given:
+        server.start()
+
+        def parent = mavenRepo().module("org", "parent")
+        parent.type = 'pom'
+        parent.publish()
+
+        def child = mavenRepo().module("org", "child")
+        child.parentPomSection = """
+<parent>
+  <groupId>org</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0</version>
+</parent>
+"""
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
+        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
+
+        server.expectGetMissing('/repo1/org/parent/1.0/parent-1.0.pom')
+        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
+        server.expectGet('/repo2/org/parent/1.0/parent-1.0.pom', parent.pomFile)
+        server.expectHead('/repo2/org/parent/1.0/parent-1.0.jar', parent.artifactFile)
+
+        // TODO: These shouldn't be required. Or at most should be HEAD requests
+        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
+        server.expectGet('/repo2/org/parent/1.0/parent-1.0.pom', parent.pomFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotRemoteDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..58f6719
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotRemoteDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,589 @@
+/*
+ * Copyright 2011 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.resolve.maven
+
+import org.gradle.integtests.fixtures.MavenModule
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenSnapshotRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "can find and cache snapshots in multiple Maven HTTP repositories"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo1" }
+    maven { url "http://localhost:${server.port}/repo2" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle:projectA:1.0-SNAPSHOT"
+    compile "org.gradle:projectB:1.0-SNAPSHOT"
+    compile "org.gradle:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and: "snapshot modules are published"
+        def projectA = mavenRepo().module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
+        def projectB = mavenRepo().module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
+        def nonUnique = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        when: "Server provides projectA from repo1"
+        expectModuleServed(projectA, '/repo1')
+
+        and: "Server provides projectB from repo2"
+        expectModuleMissing(projectB, '/repo1')
+        expectModuleServed(projectB, '/repo2')
+
+        and: "Server provides nonunique snapshot from repo2"
+        expectModuleMissing(nonUnique, '/repo1')
+        expectModuleServed(nonUnique, '/repo2')
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
+        def snapshotNonUnique = file('libs/nonunique-1.0-SNAPSHOT.jar').snapshot()
+
+        when: "We resolve with snapshots cached: no server requests"
+        server.resetExpectations()
+        def result = run('retrieve')
+
+        then: "Everything is up to date"
+        result.assertTaskSkipped(':retrieve')
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotNonUnique);
+    }
+
+    def "can find and cache snapshots in Maven HTTP repository with additional artifact urls"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}/repo1"
+        artifactUrls "http://localhost:${server.port}/repo2"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle:projectA:1.0-SNAPSHOT"
+    compile "org.gradle:projectB:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and: "snapshot modules are published"
+        def projectA = mavenRepo().module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
+        def projectB = mavenRepo().module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
+
+        when: "Server provides projectA from repo1"
+        expectModuleServed(projectA, '/repo1')
+
+        and: "Server provides projectB with artifact in repo2"
+        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/maven-metadata.xml", projectB.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${projectB.pomFile.name}", projectB.pomFile)
+        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/maven-metadata.xml", projectB.moduleDir.file("maven-metadata.xml"))
+        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${projectB.artifactFile.name}")
+        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar")
+
+        // TODO: This is not correct - should be looking for jar with unique version to fetch snapshot
+        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar", projectB.artifactFile)
+//        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/${projectB.artifactFile.name}",  projectB.artifactFile)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
+        def snapshotB = file('libs/projectB-1.0-SNAPSHOT.jar').snapshot()
+
+        when: "We resolve with snapshots cached: no server requests"
+        server.resetExpectations()
+        def result = run('retrieve')
+
+        then: "Everything is up to date"
+        result.assertTaskSkipped(':retrieve')
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
+        file('libs/projectB-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotB);
+    }
+
+    def "will detect changed snapshot artifacts when pom has not changed"() {
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+
+dependencies { 
+    compile "org.gradle:unique:1.0-SNAPSHOT" 
+    compile "org.gradle:nonunique:1.0-SNAPSHOT" 
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "snapshot modules are published"
+        def uniqueVersionModule = mavenRepo().module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
+        def nonUniqueVersionModule = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        and: "Server handles requests"
+        expectModuleServed(uniqueVersionModule, '/repo', false, true)
+        expectModuleServed(nonUniqueVersionModule, '/repo', false, true)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+        def nonUniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+
+        when: "Change the snapshot artifacts directly: do not change the pom"
+        uniqueVersionModule.artifactFile << 'more content'
+        nonUniqueVersionModule.artifactFile << 'more content'
+
+        and: "No server requests"
+        expectModuleServed(uniqueVersionModule, '/repo', true, true)
+        expectModuleServed(nonUniqueVersionModule, '/repo', true, true)
+
+        and: "Resolve dependencies again"
+        run 'retrieve'
+
+        then:
+        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot)
+    }
+
+    def "uses cached snapshots from a Maven HTTP repository until the snapshot timeout is reached"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+if (project.hasProperty('noTimeout')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+
+dependencies {
+    compile "org.gradle:unique:1.0-SNAPSHOT"
+    compile "org.gradle:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "snapshot modules are published"
+        def uniqueVersionModule = mavenRepo().module("org.gradle", "unique", "1.0-SNAPSHOT")
+        uniqueVersionModule.publish()
+        def nonUniqueVersionModule = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots()
+        nonUniqueVersionModule.publish()
+
+        and: "Server handles requests"
+        expectModuleServed(uniqueVersionModule, '/repo')
+        expectModuleServed(nonUniqueVersionModule, '/repo')
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
+
+        when: "Republish the snapshots"
+        uniqueVersionModule.publishWithChangedContent()
+        nonUniqueVersionModule.publishWithChangedContent()
+
+        and: "No server requests"
+        server.resetExpectations()
+
+        and: "Resolve dependencies again, with cached versions"
+        run 'retrieve'
+
+        then:
+        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(nonUniqueJarSnapshot)
+
+        when: "Server handles requests"
+        expectModuleServed(uniqueVersionModule, '/repo', true, true)
+        expectModuleServed(nonUniqueVersionModule, '/repo', true, true)
+
+        and: "Resolve dependencies with cache expired"
+        executer.withArguments("-PnoTimeout")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot);
+    }
+
+    def "does not download snapshot artifacts after expiry when snapshot has not changed"() {
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile "org.gradle:testproject:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Publish the first snapshot"
+        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
+        module.publish()
+
+        and: "Server handles requests"
+        expectModuleServed(module, '/repo')
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        def snapshot = file('build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile).snapshot()
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        expectChangedProbe('/repo', module, false)
+
+        // Retrieve again with zero timeout should check for updated snapshot
+        and:
+        def result = run 'retrieve'
+
+        then:
+        result.assertTaskSkipped(':retrieve')
+        file('build/testproject-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot);
+    }
+
+    def "does not download snapshot artifacts more than once per build"() {
+        server.start()
+        given:
+        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
+        module.publish()
+
+        and:
+        settingsFile << "include 'a', 'b'"
+        buildFile << """
+allprojects {
+    repositories {
+        maven { url "http://localhost:${server.port}/repo" }
+    }
+
+    configurations { compile }
+
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+
+    dependencies {
+        compile "org.gradle:testproject:1.0-SNAPSHOT"
+    }
+
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.compile
+    }
+}
+"""
+        when: "Module is requested once"
+        expectModuleServed(module, '/repo')
+
+        then:
+        run 'retrieve'
+
+        and:
+        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        file('a/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        file('b/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+    }
+
+    def "can update snapshot artifact during build even if it is locked earlier in build"() {
+        server.start()
+        given:
+        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+        def module2 = new MavenRepository(file('repo2')).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publishWithChangedContent()
+        module2.artifactFile << module2.artifactFile.bytes // ensure it's a different length to the first one
+
+        and:
+        settingsFile << "include 'lock', 'resolve'"
+        buildFile << """
+def fileLocks = [:]
+subprojects {
+    repositories {
+        maven { url "http://localhost:${server.port}/repo" }
+    }
+
+    configurations { compile }
+
+    configurations.all {
+        resolutionStrategy.resolutionRules.eachModule({ module ->
+            module.refresh()
+        } as Action)
+    }
+
+    dependencies {
+        compile "org.gradle:testproject:1.0-SNAPSHOT"
+    }
+
+    task lock << {
+        configurations.compile.each { file ->
+            println "locking " + file
+            def lockFile = new RandomAccessFile(file.canonicalPath, 'r')
+            fileLocks[file] = lockFile
+        }
+    }
+
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.compile
+    }
+    retrieve.dependsOn 'lock'
+}
+project('resolve') {
+    retrieve.dependsOn ':lock:retrieve'
+
+    task cleanup << {
+        fileLocks.each { key, value ->
+            println "unlocking " + key
+            value.close()
+        }
+    }
+    cleanup.dependsOn 'retrieve'
+}
+"""
+        when: "Module is requested once"
+        def moduleName = module.artifactId
+        def prefix = "/repo"
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module2.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module2.sha1File(module2.pomFile))
+        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module2.pomFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module2.pomFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module2.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module2.sha1File(module2.artifactFile))
+        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
+
+        then:
+        run 'cleanup'
+
+        and:
+        file('lock/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile)
+        file('resolve/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module2.artifactFile)
+    }
+
+    def "avoid redownload unchanged artifact when no checksum available"() {
+        server.start()
+
+        given:
+        buildFile << """
+            repositories {
+                maven { url "http://localhost:${server.port}/repo" }
+            }
+
+            configurations { compile }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile group: "group", name: "projectA", version: "1.1-SNAPSHOT"
+            }
+
+            task retrieve(type: Copy) {
+                into 'build'
+                from configurations.compile
+            }
+        """
+
+        and:
+        def module = mavenRepo().module("group", "projectA", "1.1-SNAPSHOT").withNonUniqueSnapshots().publish()
+        // Set the last modified to something that's not going to be anything “else”.
+        // There are lots of dates floating around in a resolution and we want to make
+        // sure we use this.
+        module.artifactFile.setLastModified(2000)
+        module.pomFile.setLastModified(6000)
+
+        def base = "/repo/group/projectA/1.1-SNAPSHOT"
+        def metaDataPath = "$base/maven-metadata.xml"
+        def pomPath = "$base/$module.pomFile.name"
+        def pomSha1Path = "${pomPath}.sha1"
+        def originalPomLastMod = module.pomFile.lastModified()
+        def originalPomContentLength = module.pomFile.length()
+        def jarPath = "$base/$module.artifactFile.name"
+        def jarSha1Path = "${jarPath}.sha1"
+        def originalJarLastMod = module.artifactFile.lastModified()
+        def originalJarContentLength = module.artifactFile.length()
+
+        when:
+        server.expectGet(metaDataPath, module.mavenMetaDataFile)
+        server.expectGet(pomPath, module.pomFile)
+        server.expectGet(metaDataPath, module.mavenMetaDataFile)
+        server.expectGet(jarPath, module.artifactFile)
+
+        run "retrieve"
+
+        then:
+        def downloadedJarFile = file("build/projectA-1.1-SNAPSHOT.jar")
+        downloadedJarFile.assertIsCopyOf(module.artifactFile)
+        def initialDownloadJarFileSnapshot = downloadedJarFile.snapshot()
+
+        // Do change the jar, so we can check that the new version wasn't downloaded
+        module.publishWithChangedContent()
+
+        when:
+        server.resetExpectations()
+        server.expectGet(metaDataPath, module.mavenMetaDataFile)
+        server.expectGetMissing(pomSha1Path)
+        server.expectHead(pomPath, module.pomFile, originalPomLastMod, originalPomContentLength)
+        server.expectGet(metaDataPath, module.mavenMetaDataFile)
+        server.expectGetMissing(jarSha1Path)
+        server.expectHead(jarPath, module.artifactFile, originalJarLastMod, originalJarContentLength)
+
+        run "retrieve"
+
+        then:
+        downloadedJarFile.assertHasNotChangedSince(initialDownloadJarFileSnapshot)
+
+        when:
+        server.resetExpectations()
+        server.expectGet(metaDataPath, module.mavenMetaDataFile)
+        server.expectGetMissing(pomSha1Path)
+        server.expectHead(pomPath, module.pomFile)
+        server.expectGet(pomPath, module.pomFile)
+        server.expectGet(metaDataPath, module.mavenMetaDataFile)
+        server.expectGetMissing(jarSha1Path)
+        server.expectHead(jarPath, module.artifactFile)
+        server.expectGet(jarPath, module.artifactFile)
+
+
+        run "retrieve"
+
+        then:
+        downloadedJarFile.assertHasChangedSince(initialDownloadJarFileSnapshot)
+        downloadedJarFile.assertIsCopyOf(module.artifactFile)
+    }
+
+    private expectModuleServed(MavenModule module, def prefix, boolean sha1requests = false, boolean headRequests = false) {
+        def moduleName = module.artifactId;
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+        // TODO - should only ask for metadata once
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+
+        if (sha1requests) {
+            server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module.sha1File(module.pomFile))
+            server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
+        }
+
+        if (headRequests) {
+            server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+            server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+        }
+    }
+
+    private expectReuseModuleArtifacts(MavenModule module, def prefix, boolean metaDataMatch) {
+        def moduleName = module.artifactId;
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module.sha1File(module.pomFile))
+        // TODO - should only ask for metadata once
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
+    }
+
+    private expectChangedProbe(prefix, MavenModule module, boolean expectSha1) {
+        module.expectMetaDataGet(server, prefix)
+        module.expectPomHead(server, prefix)
+        if (expectSha1) {
+            module.expectPomSha1Get(server, prefix)
+        }
+
+        module.expectMetaDataGet(server, prefix)
+        module.expectArtifactHead(server, prefix)
+        if (expectSha1) {
+            module.expectArtifactSha1Get(server, prefix)
+        }
+    }
+    
+    private expectModuleMissing(MavenModule module, def prefix) {
+        def moduleName = module.artifactId;
+        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml")
+        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${moduleName}-1.0-SNAPSHOT.pom")
+        // TODO - should only ask for metadata once
+        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml")
+        server.expectHeadMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${moduleName}-1.0-SNAPSHOT.jar")
+    }
+
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy
new file mode 100644
index 0000000..baf8110
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesCoreIntegrationTest.groovy
@@ -0,0 +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.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+/**
+ * @author Szczepan Faber, created at: 3/28/11
+ */
+class AutoTestedSamplesCoreIntegrationTest extends AbstractAutoTestedSamplesTest {
+
+    @Test
+    void runSamples() {
+        //Uncomment below to run test only for single class (much faster)
+//        includeOnly '**/Copy.java'
+        runSamplesFrom("subprojects/core/src/main/groovy/org/gradle/api")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy
new file mode 100644
index 0000000..44e5f1b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/AutoTestedSamplesPluginsIntegrationTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.samples
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+/**
+ * @author Szczepan Faber, created at: 3/28/11
+ */
+class AutoTestedSamplesPluginsIntegrationTest extends AbstractAutoTestedSamplesTest {
+
+    @Test
+    void runSamples() {
+        //for debugging purposes you can samples for a single class
+//        includeOnly '**/Test.java'
+        runSamplesFrom("subprojects/plugins/src/main")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/CoreAutoTestedSamplesTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/CoreAutoTestedSamplesTest.groovy
deleted file mode 100644
index 0cbc2c0..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/CoreAutoTestedSamplesTest.groovy
+++ /dev/null
@@ -1,31 +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.samples
-
-import org.gradle.integtests.fixtures.internal.AbstractAutoTestedSamplesTest
-import org.junit.Test
-
-/**
- * Author: Szczepan Faber, created at: 3/28/11
- */
-class CoreAutoTestedSamplesTest extends AbstractAutoTestedSamplesTest {
-
-    @Test
-    void runSamples() {
-        runSamplesFrom("subprojects/core/src/main/groovy/org/gradle/api/tasks")
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/PluginsAutoTestedSamplesTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/PluginsAutoTestedSamplesTest.groovy
deleted file mode 100644
index 630dd2c..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/PluginsAutoTestedSamplesTest.groovy
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2011 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.samples
-
-import org.gradle.integtests.fixtures.internal.AbstractAutoTestedSamplesTest
-import org.junit.Test
-
-/**
- * Author: Szczepan Faber, created at: 3/28/11
- */
-class PluginsAutoTestedSamplesTest extends AbstractAutoTestedSamplesTest {
-
-    @Test
-    void runSamples() {
-        runSamplesFrom("subprojects/plugins/src/main")
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
new file mode 100644
index 0000000..97dedec
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesAntlrIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('antlr')
+
+    @Test
+    public void canBuild() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.GrammarTest')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
new file mode 100644
index 0000000..61c1150
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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.samples
+
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.integtests.fixtures.*
+
+class SamplesApplicationIntegrationTest extends Specification {
+    @Rule GradleDistribution distribution = new GradleDistribution()
+    @Rule GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule Sample sample = new Sample('application')
+
+    def canRunTheApplicationUsingRunTask() {
+        when:
+        def result = executer.inDirectory(sample.dir).withTasks('run').run()
+
+        then:
+        result.output.contains('Greetings from the sample application.')
+    }
+
+    def canBuildAndRunTheInstalledApplication() {
+        when:
+        executer.inDirectory(sample.dir).withTasks('installApp').run()
+
+        then:
+        def installDir = sample.dir.file('build/install/application')
+        installDir.assertIsDir()
+
+        checkApplicationImage(installDir)
+    }
+
+    def canBuildAndRunTheZippedDistribution() {
+        when:
+        executer.inDirectory(sample.dir).withTasks('distZip').run()
+
+        then:
+        def distFile = sample.dir.file('build/distributions/application-1.0.2.zip')
+        distFile.assertIsFile()
+
+        def installDir = sample.dir.file('unzip')
+        distFile.usingNativeTools().unzipTo(installDir)
+
+        checkApplicationImage(installDir.file('application-1.0.2'))
+    }
+    
+    private void checkApplicationImage(TestFile installDir) {
+        installDir.file('bin/application').assertIsFile()
+        installDir.file('bin/application.bat').assertIsFile()
+        installDir.file('lib/application-1.0.2.jar').assertIsFile()
+        installDir.file('lib/commons-collections-3.2.1.jar').assertIsFile()
+
+        installDir.file('LICENSE').assertIsFile()
+        installDir.file('docs/readme.txt').assertIsFile()
+
+        def builder = new ScriptExecuter()
+        builder.workingDir installDir.file('bin')
+        builder.executable 'application'
+        builder.standardOutput = new ByteArrayOutputStream()
+        builder.errorOutput = new ByteArrayOutputStream()
+
+        def result = builder.run()
+        result.assertNormalExitValue()
+
+        assert builder.standardOutput.toString().contains('Greetings from the sample application.')
+        assert builder.errorOutput.toString() == ''
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
new file mode 100755
index 0000000..00947a4
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
@@ -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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesCodeQualityIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('codeQuality')
+
+    @Test
+    public void checkReportsGenerated() {
+        TestFile projectDir = sample.dir
+        TestFile buildDir = projectDir.file('build')
+
+        executer.inDirectory(projectDir).withForkingExecuter().withTasks('check').run()
+
+        buildDir.file('reports/checkstyle/main.xml').assertIsFile()
+        buildDir.file('reports/codenarc/main.html').assertIsFile()
+        buildDir.file('reports/codenarc/test.html').assertIsFile()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
new file mode 100644
index 0000000..5bb4318
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesCustomBuildLanguageIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('customBuildLanguage')
+
+    @Test
+    public void testBuildProductDistributions() {
+        TestFile rootDir = sample.dir
+        executer.inDirectory(rootDir).withTasks('clean', 'dist').run()
+
+        TestFile expandDir = dist.testFile('expand-basic')
+        rootDir.file('basicEdition/build/distributions/some-company-basic-edition-1.0.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'readme.txt',
+                'end-user-license-agreement.txt',
+                'bin/start.sh',
+                'lib/some-company-identity-management-1.0.jar',
+                'lib/some-company-billing-1.0.jar',
+                'lib/commons-lang-2.4.jar'
+        )
+
+        expandDir = dist.testFile('expand-enterprise')
+        rootDir.file('enterpriseEdition/build/distributions/some-company-enterprise-edition-1.0.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'readme.txt',
+                'end-user-license-agreement.txt',
+                'bin/start.sh',
+                'lib/some-company-identity-management-1.0.jar',
+                'lib/some-company-billing-1.0.jar',
+                'lib/some-company-reporting-1.0.jar',
+                'lib/commons-lang-2.4.jar',
+                'lib/commons-io-1.2.jar'
+        )
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
new file mode 100644
index 0000000..63a453b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
@@ -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.samples
+
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.junit.Rule
+
+class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final Sample sample = new Sample('customPlugin')
+
+    def getProducerDir() {
+        return sample.dir.file('plugin')
+    }
+
+    def getConsumerDir() {
+        return sample.dir.file('consumer')
+    }
+
+    public void canTestPluginAndTaskImplementation() {
+        when:
+        executer.inDirectory(producerDir).withTasks('check').run()
+
+        then:
+        def result = new JUnitTestExecutionResult(producerDir)
+        result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
+    }
+
+    public void canPublishAndUsePluginAndTestImplementations() {
+        given:
+        executer.inDirectory(producerDir).withTasks('uploadArchives').run()
+
+        when:
+        def result = executer.inDirectory(consumerDir).withTasks('greeting').run()
+
+        then:
+        result.output.contains('howdy!')
+
+        when:
+        result = executer.inDirectory(consumerDir).withTasks('hello').run()
+
+        then:
+        result.output.contains('hello from GreetingTask')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
new file mode 100644
index 0000000..4879df5
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesExcludesAndClassifiersIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('userguide/artifacts/excludesAndClassifiers')
+
+    @Test
+    public void checkExcludeAndClassifier() {
+        File projectDir = sample.dir
+        String outputCompile = executer.inDirectory(projectDir).withTasks('clean', 'resolveCompile').run().getOutput()
+        String outputRuntime = executer.inDirectory(projectDir).withTasks('clean', 'resolveRuntime').run().getOutput()
+        assertThat(outputCompile, not(containsString("commons")))
+        assertThat(outputRuntime, not(containsString("commons")))
+        assertThat(outputCompile, not(containsString("reports")))
+        assertThat(outputRuntime, not(containsString("reports")))
+        assertThat(outputCompile, not(containsString("shared")))
+        assertThat(outputRuntime, containsString("shared"))
+
+        assertThat(outputCompile, containsString("service-1.0-jdk15"))
+        assertThat(outputCompile, containsString("service-1.0-jdk14"))
+    }
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..0f25636
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesGroovyCustomizedLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/customizedLayout')
+
+    @Test
+    public void groovyProjectQuickstartSample() {
+        TestFile groovyProjectDir = sample.dir
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        groovyProjectDir.file('build/libs/customizedLayout.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
new file mode 100644
index 0000000..1842034
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesGroovyMultiProjectIntegrationTest {
+    static final String TEST_PROJECT_NAME = 'testproject'
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/multiproject')
+
+    private List mainFiles = ['JavaPerson', 'GroovyPerson', 'GroovyJavaPerson']
+    private List excludedFiles = ['ExcludeJava', 'ExcludeGroovy', 'ExcludeGroovyJava']
+    private List testFiles = ['JavaPersonTest', 'GroovyPersonTest', 'GroovyJavaPersonTest']
+
+    @Test
+    public void groovyProjectSamples() {
+        String packagePrefix = 'build/classes/main/org/gradle'
+        String testPackagePrefix = 'build/classes/test/org/gradle'
+
+
+        TestFile groovyProjectDir = sample.dir
+        TestFile testProjectDir = groovyProjectDir.file(TEST_PROJECT_NAME)
+
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check compilation
+        mainFiles.each { testProjectDir.file(packagePrefix, it + ".class").assertIsFile() }
+        excludedFiles.each { testProjectDir.file(packagePrefix, it + ".class").assertDoesNotExist() }
+        testFiles.each { testProjectDir.file(testPackagePrefix, it + ".class").assertIsFile() }
+
+        // The test produce marker files with the name of the test class
+        testFiles.each { testProjectDir.file('build', it).assertIsFile() }
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        testProjectDir.file("build/libs/$TEST_PROJECT_NAME-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'META-INF/myfile',
+                'org/gradle/main.properties',
+                'org/gradle/JavaPerson.class',
+                'org/gradle/GroovyPerson.class',
+                'org/gradle/GroovyJavaPerson.class'
+        )
+        tmpDir.file('META-INF/MANIFEST.MF').assertContents(containsString('myprop: myvalue'))
+
+        // Build docs
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'javadoc', 'groovydoc').run()
+        testProjectDir.file('build/docs/javadoc/index.html').assertIsFile()
+        testProjectDir.file('build/docs/groovydoc/index.html').assertIsFile()
+        testProjectDir.file('build/docs/groovydoc/org/gradle/GroovyPerson.html').assertIsFile()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..81e8ede
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesGroovyQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/quickstart')
+
+    @Test
+    public void groovyProjectQuickstartSample() {
+        TestFile groovyProjectDir = sample.dir
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        groovyProjectDir.file('build/libs/quickstart.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class',
+                'org/gradle/Person$_closure1.class',
+                'org/gradle/Person$_closure2.class',
+                'resource.txt',
+                'script.groovy'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
new file mode 100644
index 0000000..5c8e233
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 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.samples
+
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.junit.Rule
+
+class SamplesJavaApiAndImplIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule public final Sample apiAndImpl = new Sample('java/apiAndImpl')
+
+    static combined = ""
+    static api = "-api"
+    static impl = "-impl"
+
+    def "test classpath contains impl and api classes"() {
+        given:
+        sample apiAndImpl
+
+        when:
+        run "test"
+
+        then:
+        ":test" in executedTasks
+        ":compileApiJava" in executedTasks
+        ":compileImplJava" in executedTasks
+    }
+
+    def "poms contain the right dependencies"() {
+        given:
+        sample apiAndImpl
+
+        when:
+        run "uploadArchives"
+
+        then: // artifacts published
+        module(api).assertArtifactsPublished("apiAndImpl-api-1.0.jar", "apiAndImpl-api-1.0.pom")
+        module(impl).assertArtifactsPublished("apiAndImpl-impl-1.0.jar", "apiAndImpl-impl-1.0.pom")
+        module(combined).assertArtifactsPublished("apiAndImpl-1.0.jar", "apiAndImpl-1.0.pom")
+
+        and: // poms have the right dependencies
+        compileDependenciesOf(api).assertDependsOnArtifacts("commons-codec")
+        compileDependenciesOf(impl).assertDependsOnArtifacts("commons-lang")
+        compileDependenciesOf(combined).assertDependsOnArtifacts("commons-lang", "commons-codec")
+
+        and: // the fat jar contains classes from api and impl
+        jar(combined).file("doubler/Doubler.class").exists()
+        jar(combined).file("doubler/impl/DoublerImpl.class").exists()
+    }
+
+    def jar(type) {
+        def unzipped = apiAndImpl.dir.file("build/unzipped/jar$type")
+        if (!unzipped.exists()) {
+            artifact(type).unzipTo(unzipped)
+        }
+        unzipped
+    }
+
+    def compileDependenciesOf(type) {
+        pom(type).scopes.compile
+    }
+
+    def pom(suffix) {
+        module(suffix).pom
+    }
+
+    def module(suffix) {
+        return new MavenRepository(apiAndImpl.dir.file("build/repo")).module("myorg", "apiAndImpl${suffix}", "1.0")
+    }
+
+    def artifact(type) {
+        module(type).artifactFile
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
new file mode 100644
index 0000000..22eee53
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+
+class SamplesJavaBaseIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/base')
+
+    @Test
+    public void canBuildAndUploadJar() {
+        TestFile javaprojectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file('test'))
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        javaprojectDir.file("prod/build/libs/prod-1.0.jar").assertIsFile()
+        
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        javaprojectDir.file('prod/build/libs/prod-1.0.jar').unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..5e3e3b4
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+
+class SamplesJavaCustomizedLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/customizedLayout')
+
+    @Test
+    public void canBuildAndUploadJar() {
+        TestFile javaprojectDir = sample.dir
+                                                      
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        javaprojectDir.file('build/libs/customizedLayout.jar').assertIsFile()
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        javaprojectDir.file('build/libs/customizedLayout.jar').unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
new file mode 100644
index 0000000..a38a4e8
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
@@ -0,0 +1,224 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesJavaMultiProjectIntegrationTest {
+
+    static final String JAVA_PROJECT_NAME = 'java/multiproject'
+    static final String SHARED_NAME = 'shared'
+    static final String API_NAME = 'api'
+    static final String WEBAPP_NAME = 'webservice'
+    static final String SERVICES_NAME = 'services'
+    static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+    private TestFile javaprojectDir
+    private List projects;
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/multiproject')
+
+    @Before
+    void setUp() {
+        javaprojectDir = sample.dir
+        projects = [SHARED_NAME, API_NAME, WEBAPP_PATH].collect {"$JAVA_PROJECT_NAME/$it"} + JAVA_PROJECT_NAME
+    }
+
+    @Test
+    public void multiProjectJavaProjectSample() {
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('build').run()
+
+        assertBuildSrcBuilt()
+        assertEverythingBuilt()
+    }
+
+    private void assertBuildSrcBuilt() {
+        TestFile buildSrcDir = javaprojectDir.file('buildSrc')
+
+        buildSrcDir.file('build/libs/buildSrc.jar').assertIsFile()
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(buildSrcDir)
+        result.assertTestClassesExecuted('org.gradle.buildsrc.BuildSrcClassTest')
+    }
+
+    private void assertEverythingBuilt() {
+        String packagePrefix = 'build/classes/main/org/gradle'
+        String testPackagePrefix = 'build/classes/test/org/gradle'
+        String resPackagePrefix = 'build/resources/main/org/gradle'
+        String testResPackagePrefix = 'build/resources/test/org/gradle'
+
+        // Check classes and resources
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
+        assertExists(javaprojectDir, SHARED_NAME, resPackagePrefix, SHARED_NAME, 'main.properties')
+
+        // Check test classes and resources
+        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'PersonTest.class')
+        assertExists(javaprojectDir, SHARED_NAME, testResPackagePrefix, SHARED_NAME, 'test.properties')
+        assertExists(javaprojectDir, API_NAME, packagePrefix, API_NAME, 'PersonList.class')
+        assertExists(javaprojectDir, WEBAPP_PATH, packagePrefix, WEBAPP_NAME, 'TestTest.class')
+
+        // Check test results and report
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file(SHARED_NAME))
+        result.assertTestClassesExecuted('org.gradle.shared.PersonTest')
+
+        result = new JUnitTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
+        result.assertTestClassesExecuted('org.gradle.webservice.TestTestTest')
+
+        // Check contents of shared jar
+        TestFile tmpDir = dist.testDir.file("$SHARED_NAME-1.0.jar")
+        javaprojectDir.file(SHARED_NAME, "build/libs/$SHARED_NAME-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/shared/Person.class',
+                // package-info.java only gets compiled into class if it contains at least one annotation
+                // 'org/gradle/shared/package-info.class',
+                'org/gradle/shared/main.properties'
+        )
+
+        // Check contents of API jar
+        tmpDir = dist.testDir.file("$API_NAME-1.0.jar")
+        javaprojectDir.file(API_NAME, "build/libs/$API_NAME-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/api/PersonList.class',
+                'org/gradle/apiImpl/Impl.class')
+
+        // Check contents of API jar
+        tmpDir = dist.testDir.file("$API_NAME-spi-1.0.jar")
+        javaprojectDir.file(API_NAME, "build/libs/$API_NAME-spi-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/api/PersonList.class')
+
+        // Check contents of War
+        tmpDir = dist.testDir.file("$WEBAPP_NAME-2.5.war")
+        javaprojectDir.file(WEBAPP_PATH, "build/libs/$WEBAPP_NAME-2.5.war").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'WEB-INF/classes/org/gradle/webservice/TestTest.class',
+                "WEB-INF/lib/$SHARED_NAME-1.0.jar".toString(),
+                "WEB-INF/lib/$API_NAME-1.0.jar".toString(),
+                "WEB-INF/lib/$API_NAME-spi-1.0.jar".toString(),
+                'WEB-INF/lib/commons-collections-3.2.jar',
+                'WEB-INF/lib/commons-io-1.2.jar',
+                'WEB-INF/lib/commons-lang-2.4.jar'
+        )
+
+        // Check contents of dist zip
+        tmpDir = dist.testDir.file("$API_NAME-1.0.zip")
+        javaprojectDir.file(API_NAME, "build/distributions/$API_NAME-1.0.zip").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'README.txt',
+                "libs/$API_NAME-spi-1.0.jar".toString(),
+                "libs/$SHARED_NAME-1.0.jar".toString(),
+                'libs/commons-io-1.2.jar',
+                'libs/commons-lang-2.4.jar'
+        )
+    }
+
+    @Test
+    public void multiProjectJavaDoc() {
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'javadoc').run()
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/index.html').assertIsFile()
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/index.html').assertContents(containsString("shared 1.0 API"))
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/org/gradle/shared/Person.html').assertIsFile()
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/org/gradle/shared/package-summary.html').assertContents(containsString("These are the shared classes."))
+        javaprojectDir.file(API_NAME, 'build/docs/javadoc/index.html').assertIsFile()
+        javaprojectDir.file(API_NAME, 'build/docs/javadoc/org/gradle/api/PersonList.html').assertIsFile()
+        javaprojectDir.file(API_NAME, 'build/docs/javadoc/org/gradle/api/package-summary.html').assertContents(containsString("These are the API classes"))
+        javaprojectDir.file(WEBAPP_PATH, 'build/docs/javadoc/index.html').assertIsFile()
+    }
+
+    @Test
+    public void multiProjectPartialBuild() {
+        String packagePrefix = 'build/classes/main/org/gradle'
+        String testPackagePrefix = 'build/classes/test/org/gradle'
+
+        // Partial build using current directory
+        executer.inDirectory(javaprojectDir.file("$SERVICES_NAME/$WEBAPP_NAME")).withTasks('buildNeeded').run()
+        checkPartialWebAppBuild(packagePrefix, javaprojectDir, testPackagePrefix)
+
+        // check resources
+        assertExists(javaprojectDir, SHARED_NAME, 'build/resources/main/org/gradle', SHARED_NAME, 'main.properties')
+        assertExists(javaprojectDir, SHARED_NAME, 'build/resources/test/org/gradle', SHARED_NAME, 'test.properties')
+
+        // Partial build using task path
+        executer.inDirectory(javaprojectDir).withTasks('clean', "$SHARED_NAME:classes".toString()).run()
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
+        assertDoesNotExist(javaprojectDir, false, API_NAME, packagePrefix, API_NAME, 'PersonList.class')
+    }
+
+
+    @Test
+    public void buildDependents() {
+        executer.inDirectory(javaprojectDir).withTasks('clean', "$SHARED_NAME:buildDependents".toString()).run()
+        assertEverythingBuilt()
+    }
+
+    @Test
+    public void clean() {
+        executer.inDirectory(javaprojectDir).withTasks('classes').run()
+        executer.inDirectory(javaprojectDir).withTasks('clean').run()
+        projects.each { javaprojectDir.file("$it/build").assertDoesNotExist() }
+    }
+
+    @Test
+    public void noRebuildOfProjectDependencies() {
+        TestFile apiDir = javaprojectDir.file(API_NAME)
+        executer.inDirectory(apiDir).withTasks('classes').run()
+        TestFile sharedJar = javaprojectDir.file("shared/build/libs/shared-1.0.jar")
+        TestFile.Snapshot snapshot = sharedJar.snapshot()
+        executer.inDirectory(apiDir).withTasks('clean', 'classes').withArguments("-a").run()
+        sharedJar.assertHasNotChangedSince(snapshot)
+    }
+
+    @Test
+    public void shouldNotUseCacheForProjectDependencies() {
+        TestFile apiDir = javaprojectDir.file(API_NAME)
+        executer.inDirectory(apiDir).withTasks('checkProjectDependency').run()
+    }
+           
+    private static def checkPartialWebAppBuild(String packagePrefix, TestFile javaprojectDir, String testPackagePrefix) {
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
+        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'PersonTest.class')
+        assertExists(javaprojectDir, "$SERVICES_NAME/$WEBAPP_NAME" as String, packagePrefix, WEBAPP_NAME, 'TestTest.class')
+        assertExists(javaprojectDir, "$SERVICES_NAME/$WEBAPP_NAME" as String, 'build', 'libs', 'webservice-2.5.war')
+    }
+
+    private static void assertExists(TestFile baseDir, Object... path) {
+        baseDir.file(path).assertExists()
+    }
+
+    static void assertDoesNotExist(TestFile baseDir, boolean shouldExists, Object... path) {
+        baseDir.file(path).assertDoesNotExist()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
new file mode 100644
index 0000000..4ace76d
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+public class SamplesJavaOnlyIfIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/onlyif')
+
+    /**
+     * runs a build 3 times.
+     * execute clean dists
+     * check worked correctly
+     *
+     * remove test results
+     * execute dists
+     * check didn't re-run tests
+     *
+     * remove class file
+     * execute dists
+     * check that it re-ran tests 
+     */
+    @Test public void testOptimizedBuild() {
+        TestFile javaprojectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/reports/tests/index.html')
+
+        // Check jar exists
+        assertExists(javaprojectDir, "build/libs/onlyif.jar")
+
+        // remove test results
+        removeFile(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        removeFile(javaprojectDir, 'build/reports/tests/index.html')
+
+        executer.inDirectory(javaprojectDir).withTasks('test').run()
+
+        // assert that tests did not run
+        // (since neither compile nor compileTests should have done anything)
+        assertDoesNotExist(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertDoesNotExist(javaprojectDir, 'build/reports/tests/index.html')
+
+        // remove a compiled class file
+        removeFile(javaprojectDir, 'build/classes/main/org/gradle/Person.class')
+
+        executer.inDirectory(javaprojectDir).withTasks('test').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/reports/tests/index.html')
+    }
+
+    private static void assertExists(File baseDir, String path) {
+        new TestFile(baseDir).file(path).assertExists()
+    }
+
+    private static void assertDoesNotExist(File baseDir, String path) {
+        new TestFile(baseDir).file(path).assertDoesNotExist()
+    }
+
+    private static void removeFile(File baseDir, String path) {
+        TestFile file = new TestFile(baseDir).file(path)
+        file.assertExists()
+        file.delete()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
new file mode 100644
index 0000000..a8ac3b2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
@@ -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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesJavaProjectWithIntTestsIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/withIntegrationTests')
+
+    @Test
+    public void canRunIntegrationTests() {
+        TestFile javaprojectDir = sample.dir
+        
+        // Run int tests
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'integrationTest').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonIntegrationTest')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..4138243
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * 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.samples
+
+import java.util.jar.Manifest
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesJavaQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/quickstart')
+
+    @Test
+    public void canBuildAndUploadJar() {
+        TestFile javaprojectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        javaprojectDir.file("build/libs/quickstart-1.0.jar").assertIsFile()
+
+        // Check jar uploaded
+        javaprojectDir.file('repos/quickstart-1.0.jar').assertIsFile()
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        javaprojectDir.file('repos/quickstart-1.0.jar').unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class',
+                'org/gradle/resource.xml'
+        )
+
+        // Check contents of manifest
+        Manifest manifest = new Manifest()
+        jarContents.file('META-INF/MANIFEST.MF').withInputStream { manifest.read(it) }
+        assertThat(manifest.mainAttributes.size(), equalTo(3))
+        assertThat(manifest.mainAttributes.getValue('Manifest-Version'), equalTo('1.0'))
+        assertThat(manifest.mainAttributes.getValue('Implementation-Title'), equalTo('Gradle Quickstart'))
+        assertThat(manifest.mainAttributes.getValue('Implementation-Version'), equalTo('1.0'))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
new file mode 100644
index 0000000..4f1e060
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+
+class SamplesMixedJavaAndGroovyIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/mixedJavaAndGroovy')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        projectDir.file('build/libs/mixedJavaAndGroovy-1.0.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class',
+                'org/gradle/GroovyPerson.class',
+                'org/gradle/JavaPerson.class',
+                'org/gradle/PersonList.class'
+        )
+    }
+
+    @Test
+    public void canBuildDocs() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'groovydoc').run()
+
+        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
+        javadocsDir.file("index.html").assertIsFile()
+        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndGroovy 1.0 API'))
+        javadocsDir.file("org/gradle/Person.html").assertIsFile()
+        javadocsDir.file("org/gradle/JavaPerson.html").assertIsFile()
+
+        TestFile groovydocsDir = projectDir.file("build/docs/groovydoc")
+        groovydocsDir.file("index.html").assertIsFile()
+        groovydocsDir.file("overview-summary.html").assertContents(containsString('mixedJavaAndGroovy 1.0 API'))
+        groovydocsDir.file("org/gradle/JavaPerson.html").assertIsFile()
+        groovydocsDir.file("org/gradle/GroovyPerson.html").assertIsFile()
+        groovydocsDir.file("org/gradle/PersonList.html").assertIsFile()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
new file mode 100644
index 0000000..da5fabf
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * 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.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+
+class SamplesMixedJavaAndScalaIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('scala/mixedJavaAndScala')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/mixedJavaAndScala-1.0.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/Person.class',
+                'org/gradle/sample/impl/JavaPerson.class',
+                'org/gradle/sample/impl/PersonImpl.class',
+                'org/gradle/sample/impl/PersonList.class'
+        )
+    }
+
+    @Test
+    public void canBuildDocs() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'scaladoc').run()
+
+        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
+        javadocsDir.file("index.html").assertIsFile()
+        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
+        javadocsDir.file("org/gradle/sample/Person.html").assertIsFile()
+        javadocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
+
+        TestFile scaladocsDir = projectDir.file("build/docs/scaladoc")
+        scaladocsDir.file("index.html").assertIsFile()
+        scaladocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
+        scaladocsDir.file("org/gradle/sample/impl/PersonImpl.html").assertIsFile()
+        scaladocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
+        scaladocsDir.file("org/gradle/sample/impl/PersonList.html").assertIsFile()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy
new file mode 100644
index 0000000..bff4cca
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMultiProjectBuildSrcIntegrationTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.samples
+
+import org.junit.*
+
+import org.gradle.integtests.fixtures.*
+
+class SamplesMultiProjectBuildSrcIntegrationTest extends AbstractIntegrationSpec {
+
+  @Rule public final Sample sample = new Sample()
+
+  @UsesSample("multiProjectBuildSrc")
+  def "plugins from buildSrc subprojects are available"() {
+    given:
+    inDirectory "multiProjectBuildSrc"
+
+    when:
+    run "showPlugins"
+
+    then:
+    output.contains "plugina.PluginA"
+    output.contains "pluginb.PluginB"
+  }
+
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
new file mode 100644
index 0000000..f008756
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * 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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesRepositoriesIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('userguide/artifacts/defineRepository')
+
+    @Test
+    public void repositoryNotations() {
+        // This test is not very strong. Its main purpose is to the for the correct syntax as we use many
+        // code snippets from this build script in the user's guide.
+        File projectDir = sample.dir
+        String output = executer.inDirectory(projectDir).withQuietLogging().withTasks('lookup').run().getOutput()
+        assertThat(output, equalTo(String.format("localRepository%nlocalRepository%n")))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..7d9b41f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
@@ -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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+class SamplesScalaCustomizedLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('scala/customizedLayout')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/customizedLayout.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..7d0dd78
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
@@ -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.integtests.samples
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.containsString
+
+class SamplesScalaQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('scala/quickstart')
+
+    private TestFile projectDir
+
+    @Before
+    void setUp() {
+        projectDir = sample.dir
+    }
+
+    @Test
+    public void canBuildJar() {
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/quickstart.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+
+    @Test
+    public void canBuildScalaDoc() {
+        executer.inDirectory(projectDir).withTasks('clean', 'scaladoc').run()
+
+        projectDir.file('build/docs/scaladoc/index.html').assertExists()
+        projectDir.file('build/docs/scaladoc/org/gradle/sample/api/Person.html').assertContents(containsString("Defines the interface for a person."))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
new file mode 100644
index 0000000..76f549e
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebProjectIntegrationTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * 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.integtests.samples
+
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.TestFile
+import org.junit.Rule
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
+    static final String WEB_PROJECT_NAME = 'customised'
+
+    @Rule public final Sample sample = new Sample('webApplication/customised')
+
+    def "can build war"() {
+        when:
+        sample sample
+        run 'clean', 'assemble'
+        
+        then:
+        TestFile tmpDir = file('unjar')
+        sample.dir.file("build/libs/customised-1.0.war").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'root.txt',
+                'META-INF/MANIFEST.MF',
+                'WEB-INF/classes/org/gradle/HelloServlet.class',
+                'WEB-INF/classes/org/gradle/MyClass.class',
+                'WEB-INF/lib/compile-1.0.jar',
+                'WEB-INF/lib/compile-transitive-1.0.jar',
+                'WEB-INF/lib/runtime-1.0.jar',
+                'WEB-INF/lib/additional-1.0.jar',
+                'WEB-INF/lib/otherLib-1.0.jar',
+                'WEB-INF/additional.xml',
+                'WEB-INF/webapp.xml',
+                'WEB-INF/web.xml',
+                'webapp.html')
+    }
+
+    def "can execute servlet"() {
+        given:
+        // Inject some int test stuff
+        sample.dir.file('build.gradle') << """
+def portFinder = org.gradle.util.AvailablePortFinder.createPrivate()
+
+httpPort = portFinder.nextAvailable
+stopPort = portFinder.nextAvailable
+println "http port = \$httpPort, stop port = \$stopPort"
+
+[jettyRun, jettyRunWar]*.daemon = true
+
+task runTest(dependsOn: jettyRun) << {
+    callServlet()
+}
+
+task runWarTest(dependsOn: jettyRunWar) << {
+    callServlet()
+}
+
+private void callServlet() {
+    URL url = new URL("http://localhost:\$httpPort/customised/hello")
+    println url.text
+    jettyStop.execute()
+}
+
+"""
+
+        when:
+        sample sample
+        run 'runTest'
+
+        then:
+        output.contains('Hello Gradle')
+
+        when:
+        sample sample
+        run 'runWarTest'
+
+        then:
+        output.contains('Hello Gradle')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..dc9f5f2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * 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.integtests.samples
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Timeout
+import spock.lang.Unroll
+
+/**
+ * @author Hans Dockter
+ */
+class SamplesWebQuickstartIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final Sample sample = new Sample('webApplication/quickstart')
+
+    def "can build a war"() {
+        given:
+        sample sample
+
+        when:
+        run 'clean', 'build'
+
+        then:
+        // Check contents of War
+        TestFile warContents = file('war-tmp')
+        sample.dir.file("build/libs/quickstart.war").unzipTo(warContents)
+        warContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'index.jsp',
+                'WEB-INF/classes/org/gradle/sample/Greeter.class',
+                'WEB-INF/classes/greeting.txt',
+                'WEB-INF/lib/log4j-1.2.15.jar',
+                'WEB-INF/lib/commons-io-1.4.jar',
+        )
+    }
+
+    @Timeout(120)
+    @Unroll
+    def "can use #jettyTask for testing"() {
+        expect:
+        jettyLifecycle(jettyTask)
+
+        where:
+        jettyTask << ["jettyRun", "jettyRunWar"]
+    }
+
+    private void jettyLifecycle(String jettyStartTask) {
+        def portFinder = org.gradle.util.AvailablePortFinder.createPrivate()
+        def httpPort = portFinder.nextAvailable
+        def stopPort = portFinder.nextAvailable
+
+        // Inject some int test stuff
+        sample.dir.file('build.gradle') << """
+httpPort = ${httpPort}
+stopPort = ${stopPort}
+
+task runTest << {
+    URL url = new URL("http://localhost:\$httpPort/quickstart")
+    println url.text
+}
+
+task sayHearthyGoodbye << {
+    //this task should last for a few seconds
+    //to neatly expose issues with jetty killing the main process
+    println "About to say goodbye..."
+    Thread.sleep(2000)
+    println "Jetty will miss you!"
+}
+"""
+
+        //starting jetty
+        sample sample
+        def runJetty = executer.withTasks(jettyStartTask, "sayHearthyGoodbye").withArguments("-d").start()
+
+        //jetty is started
+        available("http://localhost:$httpPort/quickstart")
+
+        //running web test then stopping jetty
+        sample sample
+        def jettyStop = executer.withTasks('runTest', 'jettyStop').withArguments("-d").run()
+
+        //test has completed
+        assert jettyStop.output.contains('hello Gradle')
+
+        //jetty completed gracefully
+        runJetty.waitForFinish()
+        assert runJetty.standardOutput.contains("Jetty will miss you!")
+    }
+
+    void available(String theUrl) {
+        URL url = new URL(theUrl)
+        long expiry = System.currentTimeMillis() + 30000
+        while (System.currentTimeMillis() <= expiry) {
+            try {
+                url.text
+                return
+            } catch (ConnectException e) {
+                Thread.sleep(200)
+            }
+        }
+        throw new RuntimeException("Timeout waiting for jetty to become available.")
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..1679201
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy
@@ -0,0 +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.integtests.samples
+
+import org.gradle.integtests.fixtures.UserGuideSamplesRunner
+import org.junit.runner.RunWith
+
+ at RunWith(UserGuideSamplesRunner.class)
+class UserGuideSamplesIntegrationTest {
+    /*
+    Important info:
+
+     If you're working in samples area there're gradle tasks that you should know of:
+     - gradle intTestImage makes sure that the samples' resources are copied to the right place
+     - gradle docs:userguideDocbook makes sure that samples' info is extracted from XMLs
+     - the 'expected' content of the asserting mechanism lives under docs/samples/userguideOutput
+
+    */
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/SampleTestNGIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/SampleTestNGIntegrationTest.groovy
deleted file mode 100644
index db4c470..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/SampleTestNGIntegrationTest.groovy
+++ /dev/null
@@ -1,71 +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.testng
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.integtests.fixtures.UsesSample
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * @author Tom Eyckmans
- */
-public class SampleTestNGIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample()
-
-    @Test @UsesSample('testng/suitexmlbuilder')
-    public void suiteXmlBuilder() {
-        executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
-
-        def result = new TestNGExecutionResult(sample.dir)
-        result.assertTestClassesExecuted('org.gradle.testng.UserImplTest')
-        result.testClass('org.gradle.testng.UserImplTest').assertTestsExecuted('testOkFirstName')
-        result.testClass('org.gradle.testng.UserImplTest').assertTestPassed('testOkFirstName')
-    }
-
-    @Test @UsesSample('testng/java-jdk14-passing')
-    public void javaJdk14Passing() {
-        executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
-
-        def result = new TestNGExecutionResult(sample.dir)
-        result.assertTestClassesExecuted('org.gradle.OkTest')
-        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
-    }
-    
-    @Test @UsesSample('testng/java-jdk15-passing')
-    public void javaJdk15Passing() {
-        executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
-
-        def result = new TestNGExecutionResult(sample.dir)
-        result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.ConcreteTest', 'org.gradle.SuiteSetup', 'org.gradle.SuiteCleanup', 'org.gradle.TestSetup', 'org.gradle.TestCleanup')
-        result.testClass('org.gradle.OkTest').assertTestsExecuted('passingTest', 'expectedFailTest')
-        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
-        result.testClass('org.gradle.OkTest').assertTestPassed('expectedFailTest')
-        result.testClass('org.gradle.ConcreteTest').assertTestsExecuted('ok', 'alsoOk')
-        result.testClass('org.gradle.ConcreteTest').assertTestPassed('ok')
-        result.testClass('org.gradle.ConcreteTest').assertTestPassed('alsoOk')
-        result.testClass('org.gradle.SuiteSetup').assertConfigMethodPassed('setupSuite')
-        result.testClass('org.gradle.SuiteCleanup').assertConfigMethodPassed('cleanupSuite')
-        result.testClass('org.gradle.TestSetup').assertConfigMethodPassed('setupTest')
-        result.testClass('org.gradle.TestCleanup').assertConfigMethodPassed('cleanupTest')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGExecutionResult.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGExecutionResult.groovy
deleted file mode 100644
index 6942fb9..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGExecutionResult.groovy
+++ /dev/null
@@ -1,161 +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.testng
-
-import groovy.util.slurpersupport.GPathResult
-import org.gradle.integtests.fixtures.TestClassExecutionResult
-import org.gradle.integtests.fixtures.TestExecutionResult
-import org.gradle.util.TestFile
-import org.hamcrest.Matcher
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-class TestNGExecutionResult implements TestExecutionResult {
-    private final TestFile projectDir
-    private final GPathResult resultsXml
-
-    def TestNGExecutionResult(projectDir) {
-        this.projectDir = projectDir;
-        resultsXml = new XmlSlurper().parse(projectDir.file('build/reports/tests/testng-results.xml').assertIsFile())
-    }
-
-    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
-        projectDir.file('build/reports/tests/index.html').assertIsFile()
-        def actualTestClasses = findTestClasses().keySet()
-        assertThat(actualTestClasses, equalTo(testClasses as Set))
-        this
-    }
-
-    TestClassExecutionResult testClass(String testClass) {
-        return new TestNgTestClassExecutionResult(testClass, findTestClass(testClass))
-    }
-
-    private def findTestClass(String testClass) {
-        def testClasses = findTestClasses()
-        if (!testClasses.containsKey(testClass)) {
-            fail("Could not find test class ${testClass}. Found ${testClasses.keySet()}")
-        }
-        testClasses[testClass]
-    }
-
-    private def findTestClasses() {
-        Map testClasses = [:]
-        resultsXml.suite.test.'class'.each {
-            testClasses.put(it. at name as String, it)
-        }
-        testClasses
-    }
-}
-
-private class TestNgTestClassExecutionResult implements TestClassExecutionResult {
-    def String testClass
-    def GPathResult testClassNode
-
-    def TestNgTestClassExecutionResult(String testClass, GPathResult resultXml) {
-        this.testClass = testClass
-        this.testClassNode = resultXml
-    }
-
-    TestClassExecutionResult assertTestsExecuted(String... testNames) {
-        def actualTestMethods = findTestMethods().keySet()
-        assertThat(actualTestMethods, equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertTestPassed(String name) {
-        def testMethodNode = findTestMethod(name)
-        assertEquals('PASS', testMethodNode. at status as String)
-        this
-    }
-
-    TestClassExecutionResult assertTestsSkipped(String... testNames) {
-        throw new UnsupportedOperationException()
-    }
-
-    TestClassExecutionResult assertTestSkipped(String name) {
-        def testMethodNode = findTestMethod(name)
-        assertEquals('SKIP', testMethodNode. at status as String)
-        this
-    }
-
-    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
-        def testMethodNode = findTestMethod(name)
-        assertEquals('FAIL', testMethodNode. at status as String)
-
-        def exceptions = testMethodNode.exception
-        assertThat(exceptions.size(), equalTo(messageMatchers.length))
-
-        for (int i = 0; i < messageMatchers.length; i++) {
-            assertThat(exceptions[i].message[0].text().trim(), messageMatchers[i])
-        }
-        this
-    }
-
-    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
-        throw new UnsupportedOperationException();
-    }
-
-    TestClassExecutionResult assertConfigMethodPassed(String name) {
-        def testMethodNode = findConfigMethod(name)
-        assertEquals('PASS', testMethodNode. at status as String)
-        this
-    }
-
-    TestClassExecutionResult assertConfigMethodFailed(String name) {
-        def testMethodNode = findConfigMethod(name)
-        assertEquals('FAIL', testMethodNode. at status as String)
-        this
-    }
-
-    private def findConfigMethod(String testName) {
-        def testMethods = findConfigMethods()
-        if (!testMethods.containsKey(testName)) {
-            fail("Could not find configuration method ${testClass}.${testName}. Found ${testMethods.keySet()}")
-        }
-        testMethods[testName]
-    }
-
-    private def findConfigMethods() {
-        Map testMethods = [:]
-        testClassNode.'test-method'.findAll { it.'@is-config' == 'true' }.each {
-            testMethods.put(it. at name as String, it)
-        }
-        testMethods
-    }
-
-    private def findTestMethod(String testName) {
-        def testMethods = findTestMethods()
-        if (!testMethods.containsKey(testName)) {
-            fail("Could not find test ${testClass}.${testName}. Found ${testMethods.keySet()}")
-        }
-        testMethods[testName]
-    }
-
-    private def findTestMethods() {
-        Map testMethods = [:]
-        testClassNode.'test-method'.findAll { it.'@is-config' != 'true' }.each {
-            testMethods.put(it. at name as String, it)
-        }
-        testMethods
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationProject.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationProject.groovy
deleted file mode 100644
index e853130..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationProject.groovy
+++ /dev/null
@@ -1,67 +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.testng
-
-/**
- * @author Tom Eyckmans
- */
-
-public class TestNGIntegrationProject {
-    String name
-    boolean expectFailure
-    Closure assertClosure
-
-    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, null, assertClosure)
-    }
-
-    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, nameSuffix, assertClosure)
-    }
-
-    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, null, assertClosure)
-    }
-
-    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
-    {
-        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, nameSuffix, assertClosure)
-    }
-
-    public TestNGIntegrationProject(String name, boolean expectFailure, String nameSuffix, assertClosure)
-    {
-        if ( nameSuffix == null ) {
-            this.name = name
-        } else {
-            this.name = name + nameSuffix
-        }
-        this.expectFailure = expectFailure
-        this.assertClosure = assertClosure
-    }
-
-    void doAssert(projectDir, result) {
-        if (assertClosure.maximumNumberOfParameters == 3) {
-            assertClosure(name, projectDir, new TestNGExecutionResult(projectDir))
-        } else {
-            assertClosure(name, projectDir, new TestNGExecutionResult(projectDir), result)
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationTest.groovy
deleted file mode 100644
index 9a64e43..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationTest.groovy
+++ /dev/null
@@ -1,112 +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.testng
-
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-import org.junit.Test
-import static org.gradle.util.Matchers.containsLine
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-
-/**
- * @author Tom Eyckmans
- */
-public class TestNGIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
-
-    @Test
-    public void executesTestsInCorrectEnvironment() {
-        ExecutionResult result = executer.withTasks('test').run();
-
-        assertThat(result.output, not(containsString('stdout')))
-        assertThat(result.error, not(containsString('stderr')))
-        assertThat(result.error, not(containsString('a warning')))
-
-        new TestNGExecutionResult(dist.testDir).testClass('org.gradle.OkTest').assertTestPassed('ok')
-    }
-
-    @Test
-    public void canListenForTestResults() {
-        ExecutionResult result = executer.withTasks("test").run();
-
-        assertThat(result.getOutput(), containsLine("START [tests] []"));
-        assertThat(result.getOutput(), containsLine("FINISH [tests] []"));
-        assertThat(result.getOutput(), containsLine("START [test process 'Gradle Worker 1'] [Gradle Worker 1]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1]"));
-        assertThat(result.getOutput(), containsLine("START [test 'Gradle test'] [Gradle test]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test 'Gradle test'] [Gradle test]"));
-        assertThat(result.getOutput(), containsLine("START [test method pass(SomeTest)] [pass]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test method pass(SomeTest)] [pass] [null]"));
-        assertThat(result.getOutput(), containsLine("START [test method fail(SomeTest)] [fail]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test method fail(SomeTest)] [fail] [java.lang.AssertionError]"));
-        assertThat(result.getOutput(), containsLine("START [test method knownError(SomeTest)] [knownError]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test method knownError(SomeTest)] [knownError] [java.lang.RuntimeException: message]"));
-        assertThat(result.getOutput(), containsLine("START [test method unknownError(SomeTest)] [unknownError]"));
-        assertThat(result.getOutput(), containsLine("FINISH [test method unknownError(SomeTest)] [unknownError] [org.gradle.messaging.remote.internal.PlaceholderException: AppException: null]"));
-    }
-
-    @Test
-    public void groovyJdk15Failing() {
-        executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
-
-        def result = new TestNGExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.BadTest')
-        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
-    }
-
-    @Test
-    public void groovyJdk15Passing() {
-        executer.withTasks("test").run()
-
-        def result = new TestNGExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.OkTest')
-        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
-    }
-
-    @Test
-    public void javaJdk14Failing() {
-        executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
-
-        def result = new TestNGExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.BadTest')
-        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
-    }
-
-    @Test
-    public void javaJdk15Failing() {
-        def execution = executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
-
-        def result = new TestNGExecutionResult(dist.testDir)
-        result.assertTestClassesExecuted('org.gradle.BadTest', 'org.gradle.TestWithBrokenSetup', 'org.gradle.BrokenAfterSuite', 'org.gradle.TestWithBrokenMethodDependency')
-        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
-        result.testClass('org.gradle.TestWithBrokenSetup').assertConfigMethodFailed('setup')
-        result.testClass('org.gradle.BrokenAfterSuite').assertConfigMethodFailed('cleanup')
-        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('broken'))
-        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestSkipped('okTest')
-        assertThat(execution.error, containsString('Test org.gradle.BadTest FAILED'))
-        assertThat(execution.error, containsString('Test org.gradle.TestWithBrokenSetup FAILED'))
-        assertThat(execution.error, containsString('Test org.gradle.BrokenAfterSuite FAILED'))
-        assertThat(execution.error, containsString('Test org.gradle.TestWithBrokenMethodDependency FAILED'))
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
deleted file mode 100644
index 7e2af46..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.integtests.fixtures.ExecutionResult
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.Sample
-import org.gradle.integtests.fixtures.internal.IntegrationTestHint
-import org.junit.Rule
-import spock.lang.Specification
-import org.gradle.integtests.fixtures.UsesSample
-
-class SamplesToolingApiIntegrationTest extends Specification {
-    @Rule public final GradleDistribution distribution = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final Sample sample = new Sample()
-
-    @UsesSample('toolingApi/model')
-    def canUseToolingApiToDetermineProjectClasspath() {
-        def projectDir = sample.dir
-        Properties props = new Properties()
-        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
-        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
-        projectDir.file('gradle.properties').withOutputStream {outstr ->
-            props.store(outstr, 'props')
-        }
-        projectDir.file('settings.gradle').text = '// to stop search upwards'
-
-        when:
-        def result = run(projectDir, 'run')
-
-        then:
-        result.output.contains("gradle-tooling-api-${distribution.version}.jar")
-        result.output.contains("gradle-core-${distribution.version}.jar")
-        result.output.contains("gradle-wrapper-${distribution.version}.jar")
-    }
-
-    @UsesSample('toolingApi/build')
-    def canUseToolingApiToRunABuild() {
-        def projectDir = sample.dir
-        Properties props = new Properties()
-        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
-        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
-        projectDir.file('gradle.properties').withOutputStream {outstr ->
-            props.store(outstr, 'props')
-        }
-        projectDir.file('settings.gradle').text = '// to stop search upwards'
-
-        when:
-        def result = run(projectDir, 'run')
-
-        then:
-        result.output.contains("Welcome to Gradle ${distribution.version}.")
-    }
-
-    private ExecutionResult run(dir, task) {
-        try {
-            return executer.inDirectory(dir).withTasks(task).run()
-        } catch (Exception e) {
-            throw new IntegrationTestHint(e);
-        }
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApi.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApi.groovy
deleted file mode 100644
index c1dab27..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApi.groovy
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.RuleHelper
-import org.gradle.integtests.fixtures.internal.IntegrationTestHint
-import org.gradle.tooling.GradleConnector
-import org.gradle.tooling.ProjectConnection
-import org.gradle.tooling.UnsupportedVersionException
-import org.junit.rules.MethodRule
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
-import java.util.concurrent.TimeUnit
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.slf4j.LoggerFactory
-import org.slf4j.Logger
-import org.gradle.integtests.fixtures.DaemonGradleExecuter
-
-class ToolingApi implements MethodRule {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ToolingApi)
-    File projectDir
-    private GradleDistribution dist
-    private final List<Closure> connectorConfigurers = []
-
-    Statement apply(Statement base, FrameworkMethod method, Object target) {
-        dist = RuleHelper.getField(target, GradleDistribution.class)
-        return base
-    }
-
-    def withConnector(Closure cl) {
-        connectorConfigurers << cl
-    }
-
-    def withConnection(Closure cl) {
-        GradleConnector connector = connector()
-        try {
-            withConnectionRaw(connector, cl)
-        } catch (UnsupportedVersionException e) {
-            throw new IntegrationTestHint(e);
-        }
-    }
-
-    def maybeFailWithConnection(Closure cl) {
-        GradleConnector connector = connector()
-        try {
-            withConnectionRaw(connector, cl)
-        } catch (Throwable e) {
-            return e
-        }
-        return null
-    }
-
-    private def withConnectionRaw(GradleConnector connector, Closure cl) {
-        ProjectConnection connection = connector.connect()
-        try {
-            return cl.call(connection)
-        } finally {
-            connection.close()
-        }
-    }
-
-    def File getProjectDir() {
-        return projectDir ?: dist.testDir
-    }
-
-    private def connector() {
-        GradleConnector connector = GradleConnector.newConnector()
-        connector.useGradleUserHomeDir(new File(dist.userHomeDir.absolutePath))
-        connector.forProjectDirectory(new File(getProjectDir().absolutePath))
-        connector.searchUpwards(false)
-        if (GradleDistributionExecuter.executer == GradleDistributionExecuter.Executer.embedded) {
-            LOGGER.info("Using embedded tooling API provider");
-            connector.useClasspathDistribution()
-            connector.embedded(true)
-        } else {
-            LOGGER.info("Using daemon tooling API provider");
-            connector.useInstallation(new File(dist.gradleHomeDir.absolutePath))
-            connector.embedded(false)
-            connector.daemonMaxIdleTime(5, TimeUnit.MINUTES)
-            DaemonGradleExecuter.registerDaemon(dist.userHomeDir)
-        }
-        connectorConfigurers.each {
-            it.call(connector)
-        }
-        return connector
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiBuildExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiBuildExecutionIntegrationTest.groovy
deleted file mode 100644
index 0c0169e..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiBuildExecutionIntegrationTest.groovy
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.tooling.model.BuildableProject
-import org.gradle.tooling.model.eclipse.EclipseProject
-import org.gradle.tooling.model.Task
-import org.gradle.tooling.BuildException
-import org.gradle.tooling.ProgressListener
-
-class ToolingApiBuildExecutionIntegrationTest extends ToolingApiSpecification {
-    def "can build the set of tasks for a project"() {
-        dist.testFile('build.gradle') << '''
-task a {
-   description = 'this is task a'
-}
-task b
-task c
-'''
-
-        when:
-        def project = withConnection { connection -> connection.getModel(BuildableProject.class) }
-
-        then:
-        def taskA = project.tasks.find { it.name == 'a' }
-        taskA != null
-        taskA.path == ':a'
-        taskA.description == 'this is task a'
-        taskA.project == project
-        project.tasks.find { it.name == 'b' }
-        project.tasks.find { it.name == 'c' }
-    }
-
-    def "can execute a build for a project"() {
-        dist.testFile('settings.gradle') << 'rootProject.name="test"'
-        dist.testFile('build.gradle') << '''
-apply plugin: 'java'
-'''
-        when:
-        withConnection { connection ->
-            def build = connection.newBuild()
-            build.forTasks('jar')
-            build.run()
-        }
-
-        then:
-        dist.testFile('build/libs/test.jar').assertIsFile()
-
-        when:
-        withConnection { connection ->
-            BuildableProject project = connection.getModel(BuildableProject.class)
-            Task clean = project.tasks.find { it.name == 'clean' }
-            def build = connection.newBuild()
-            build.forTasks(clean)
-            build.run()
-        }
-
-        then:
-        dist.testFile('build/libs/test.jar').assertDoesNotExist()
-    }
-
-    def "receives progress and logging while the build is executing"() {
-        dist.testFile('build.gradle') << '''
-System.out.println 'this is stdout'
-System.err.println 'this is stderr'
-'''
-        def stdout = new ByteArrayOutputStream()
-        def stderr = new ByteArrayOutputStream()
-        def progressMessages = []
-
-        when:
-        withConnection { connection ->
-            def build = connection.newBuild()
-            build.standardOutput = stdout
-            build.standardError = stderr
-            build.addProgressListener({ event -> progressMessages << event.description } as ProgressListener)
-            build.run()
-        }
-
-        then:
-        stdout.toString().contains('this is stdout')
-        stderr.toString().contains('this is stderr')
-        progressMessages.size() >= 2
-        progressMessages.pop() == ''
-        progressMessages.every { it }
-    }
-
-    def "tooling api reports build failure"() {
-        dist.testFile('build.gradle') << 'broken'
-
-        when:
-        withConnection { connection ->
-            return connection.newBuild().forTasks('jar').run()
-        }
-
-        then:
-        BuildException e = thrown()
-        e.message.startsWith('Could not execute build using Gradle')
-        e.cause.message.contains('A problem occurred evaluating root project')
-    }
-
-    def "can build the set of tasks for an Eclipse project"() {
-        dist.testFile('build.gradle') << '''
-task a {
-   description = 'this is task a'
-}
-task b
-task c
-'''
-
-        when:
-        def project = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        def taskA = project.tasks.find { it.name == 'a' }
-        taskA != null
-        taskA.path == ':a'
-        taskA.description == 'this is task a'
-        taskA.project == project
-        project.tasks.find { it.name == 'b' }
-        project.tasks.find { it.name == 'c' }
-    }
-
-    def "does not resolve dependencies when building the set of tasks for a project"() {
-        dist.testFile('build.gradle') << '''
-apply plugin: 'java'
-dependencies {
-    compile files { throw new RuntimeException('broken') }
-}
-'''
-
-        when:
-        def project = withConnection { connection -> connection.getModel(BuildableProject.class) }
-
-        then:
-        !project.tasks.empty
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiEclipseModelIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiEclipseModelIntegrationTest.groovy
deleted file mode 100644
index b3e0a16..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiEclipseModelIntegrationTest.groovy
+++ /dev/null
@@ -1,336 +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.tooling
-
-import org.gradle.tooling.model.ExternalDependency
-import org.gradle.tooling.model.eclipse.EclipseProject
-import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
-
-class ToolingApiEclipseModelIntegrationTest extends ToolingApiSpecification {
-
-    def "can build the eclipse model for a java project"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
-apply plugin: 'java'
-description = 'this is a project'
-'''
-        projectDir.file('settings.gradle').text = 'rootProject.name = \"test project\"'
-
-        when:
-        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
-
-        then:
-        minimalProject.path == ':'
-        minimalProject.name == 'test project'
-        minimalProject.description == 'this is a project'
-        minimalProject.projectDirectory == projectDir
-        minimalProject.parent == null
-        minimalProject.children.empty
-
-        when:
-        EclipseProject fullProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        fullProject.path == ':'
-        fullProject.name == 'test project'
-        fullProject.description == 'this is a project'
-        fullProject.projectDirectory == projectDir
-        fullProject.parent == null
-        fullProject.children.empty
-    }
-
-    def "can build the eclipse model for an empty project"() {
-        when:
-        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
-
-        then:
-        minimalProject != null
-
-        minimalProject.description == null
-        minimalProject.parent == null
-        minimalProject.children.empty
-        minimalProject.sourceDirectories.empty
-        minimalProject.projectDependencies.empty
-
-        when:
-        EclipseProject fullProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        fullProject != null
-
-        fullProject.description == null
-        fullProject.parent == null
-        fullProject.children.empty
-        fullProject.sourceDirectories.empty
-        fullProject.classpath.empty
-        fullProject.projectDependencies.empty
-    }
-
-    def "can build the eclipse source directories for a java project"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = "apply plugin: 'java'"
-
-        projectDir.create {
-            src {
-                main {
-                    java {}
-                    resources {}
-                }
-                test {
-                    java {}
-                    resources {}
-                }
-            }
-        }
-
-        when:
-        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
-
-        then:
-        minimalProject != null
-
-        minimalProject.sourceDirectories.size() == 4
-        minimalProject.sourceDirectories[0].path == 'src/main/java'
-        minimalProject.sourceDirectories[0].directory == projectDir.file('src/main/java')
-        minimalProject.sourceDirectories[1].path == 'src/main/resources'
-        minimalProject.sourceDirectories[1].directory == projectDir.file('src/main/resources')
-        minimalProject.sourceDirectories[2].path == 'src/test/java'
-        minimalProject.sourceDirectories[2].directory == projectDir.file('src/test/java')
-        minimalProject.sourceDirectories[3].path == 'src/test/resources'
-        minimalProject.sourceDirectories[3].directory == projectDir.file('src/test/resources')
-
-        when:
-        EclipseProject fullProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        fullProject != null
-
-        fullProject.sourceDirectories.size() == 4
-        fullProject.sourceDirectories[0].path == 'src/main/java'
-        fullProject.sourceDirectories[0].directory == projectDir.file('src/main/java')
-        fullProject.sourceDirectories[1].path == 'src/main/resources'
-        fullProject.sourceDirectories[1].directory == projectDir.file('src/main/resources')
-        fullProject.sourceDirectories[2].path == 'src/test/java'
-        fullProject.sourceDirectories[2].directory == projectDir.file('src/test/java')
-        fullProject.sourceDirectories[3].path == 'src/test/resources'
-        fullProject.sourceDirectories[3].directory == projectDir.file('src/test/resources')
-    }
-
-    def "can build the eclipse external dependencies for a java project"() {
-        def projectDir = dist.testDir
-        projectDir.file('settings.gradle').text = '''
-include "a"
-rootProject.name = 'root'
-'''
-        projectDir.file('build.gradle').text = '''
-allprojects { apply plugin: 'java' }
-repositories { mavenCentral() }
-dependencies {
-    compile 'commons-lang:commons-lang:2.5'
-    compile project(':a')
-    runtime 'commons-io:commons-io:1.4'
-}
-'''
-
-        when:
-        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        eclipseProject != null
-
-        eclipseProject.classpath.size() == 2
-        eclipseProject.classpath.every { it instanceof ExternalDependency }
-        eclipseProject.classpath.collect { it.file.name } as Set == ['commons-lang-2.5.jar', 'commons-io-1.4.jar' ] as Set
-        eclipseProject.classpath.collect { it.source?.name } as Set == ['commons-lang-2.5-sources.jar', 'commons-io-1.4-sources.jar'] as Set
-        eclipseProject.classpath.collect { it.javadoc?.name } as Set == [null, null] as Set
-    }
-
-    def "minimal Eclipse model does not attempt to resolve external dependencies"() {
-        def projectDir = dist.testDir
-        projectDir.file('settings.gradle').text = 'include "child"'
-        projectDir.file('build.gradle').text = '''
-apply plugin: 'java'
-dependencies {
-    compile project(':child')
-    compile files { throw new RuntimeException() }
-}
-project(':child') {
-    apply plugin: 'java'
-    dependencies { compile files { throw new RuntimeException() } }
-}
-'''
-
-        when:
-        HierarchicalEclipseProject project = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
-
-        then:
-        project.projectDependencies.size() == 1
-        project.projectDependencies[0].path == 'child'
-    }
-
-    //TODO SF: write a test that checks if minimal project has necessary project dependencies
-
-    def "can build the minimal Eclipse model for a java project with the idea plugin applied"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
-apply plugin: 'java'
-apply plugin: 'idea'
-
-dependencies {
-    compile files { throw new RuntimeException('should not be resolving this') }
-}
-'''
-
-        when:
-        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
-
-        then:
-        minimalProject != null
-    }
-
-    def "can build the eclipse project dependencies for a java project"() {
-        def projectDir = dist.testDir
-        projectDir.file('settings.gradle').text = '''
-include "a", "a:b"
-rootProject.name = 'root'
-'''
-        projectDir.file('build.gradle').text = '''
-allprojects {
-    apply plugin: 'java'
-}
-project(':a') {
-    dependencies {
-        compile project(':')
-        compile project(':a:b')
-    }
-}
-'''
-
-        when:
-        HierarchicalEclipseProject minimalModel = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
-
-        then:
-        HierarchicalEclipseProject minimalProject = minimalModel.children[0]
-
-        minimalProject.projectDependencies.size() == 2
-
-        minimalProject.projectDependencies[0].path == 'root'
-        minimalProject.projectDependencies[0].targetProject == minimalModel
-
-        minimalProject.projectDependencies[1].path == 'b'
-        minimalProject.projectDependencies[1].targetProject == minimalProject.children[0]
-
-        when:
-        EclipseProject fullModel = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        EclipseProject fullProject = fullModel.children[0]
-
-        fullProject.projectDependencies.size() == 2
-
-        fullProject.projectDependencies[0].path == 'root'
-        fullProject.projectDependencies[0].targetProject == fullModel
-
-        fullProject.projectDependencies[1].path == 'b'
-        fullProject.projectDependencies[1].targetProject == fullProject.children[0]
-    }
-
-    def "can build project dependencies with targetProject references for complex scenarios"() {
-        def projectDir = dist.testDir
-        projectDir.file('settings.gradle').text = '''
-include "c", "a", "a:b"
-rootProject.name = 'root'
-'''
-        projectDir.file('build.gradle').text = '''
-allprojects {
-    apply plugin: 'java'
-}
-project(':a') {
-    dependencies {
-        compile project(':')
-        compile project(':a:b')
-        compile project(':c')
-    }
-}
-project(':c') {
-    dependencies {
-        compile project(':a:b')
-    }
-}
-'''
-
-        when:
-        EclipseProject rootProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        def projectC = rootProject.children.find { it.name == 'c'}
-        def projectA = rootProject.children.find { it.name == 'a'}
-        def projectAB = projectA.children.find { it.name == 'b' }
-
-        projectC.projectDependencies.any {it.targetProject == projectAB}
-
-        projectA.projectDependencies.any {it.targetProject == projectAB}
-        projectA.projectDependencies.any {it.targetProject == projectC}
-        projectA.projectDependencies.any {it.targetProject == rootProject}
-    }
-
-    def "can build the eclipse project hierarchy for a multi-project build"() {
-        def projectDir = dist.testDir
-        projectDir.file('settings.gradle').text = '''
-            include "child1", "child2", "child1:grandChild1"
-            rootProject.name = 'root'
-'''
-        projectDir.file('child1').mkdirs()
-
-        when:
-        EclipseProject rootProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        rootProject != null
-        rootProject.name == 'root'
-        rootProject.parent == null
-
-        rootProject.children.size() == 2
-
-        EclipseProject child1 = rootProject.children[0]
-        child1.name == 'child1'
-        child1.parent == rootProject
-        child1.children.size() == 1
-
-        EclipseProject child1Child1 = child1.children[0]
-        child1Child1.name == 'grandChild1'
-        child1Child1.parent == child1
-        child1Child1.children.size() == 0
-
-        EclipseProject child2 = rootProject.children[1]
-        child2.name == 'child2'
-        child2.parent == rootProject
-        child2.children.size() == 0
-
-        when:
-        toolingApi.withConnector { connector ->
-            connector.searchUpwards(true)
-            connector.forProjectDirectory(new File(projectDir, 'child1'))
-        }
-        EclipseProject child = toolingApi.withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        child.name == 'child1'
-        child.parent != null
-        child.parent.name == 'root'
-        child.children.size() == 1
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiHonorsProjectCustomizationsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiHonorsProjectCustomizationsIntegrationTest.groovy
deleted file mode 100644
index 91da9d8..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiHonorsProjectCustomizationsIntegrationTest.groovy
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.tooling.model.eclipse.EclipseProject
-
-class ToolingApiHonorsProjectCustomizationsIntegrationTest extends ToolingApiSpecification {
-
-    def "should honour reconfigured project names"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
-allprojects {
-    apply plugin: 'java'
-    apply plugin: 'eclipse'
-}
-
-project(':api') {
-    eclipseProject { projectName = 'gradle-api' }
-}
-
-project(':impl') {
-    eclipseProject { projectName = 'gradle-impl' }
-}
-'''
-        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
-
-        when:
-        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        EclipseProject api = eclipseProject.children[1]
-        assert api.name == 'gradle-api'
-        EclipseProject impl = eclipseProject.children[0]
-        assert impl.name == 'gradle-impl'
-    }
-
-    def "should deduplicate project names"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
-allprojects {
-    apply plugin: 'java'
-}
-'''
-        projectDir.file('settings.gradle').text = "include 'services:api', 'contrib:api'"
-
-        when:
-        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        String grandChildOne = eclipseProject.children[0].children[0].name
-        String grandChildTwo = eclipseProject.children[1].children[0].name
-        assert grandChildOne != grandChildTwo : "Deduplication logic should make that project names are not the same."
-    }
-
-    def "can have overlapping source and resource directories"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
-apply plugin: 'java'
-apply plugin: 'eclipse'
-
-sourceSets {
-    main {
-        java { srcDir 'src' }
-        resources { srcDir 'src' }
-    }
-
-    test {
-        java { srcDir 'test' }
-        resources { srcDir 'testResources' }
-    }
-}
-'''
-        //if we don't create the folders eclipse plugin will not build the classpath
-        projectDir.create {
-            src {}
-            test {}
-            testResources {}
-        }
-
-        when:
-        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        eclipseProject.sourceDirectories.size() == 3
-        eclipseProject.sourceDirectories[0].path == 'src'
-        eclipseProject.sourceDirectories[1].path == 'testResources'
-        eclipseProject.sourceDirectories[2].path == 'test'
-    }
-
-    def "can enable download of Javadoc for external dependencies"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = '''
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories { mavenCentral() }
-dependencies {
-    compile 'commons-lang:commons-lang:2.5'
-    runtime 'commons-io:commons-io:1.4'
-}
-eclipse { classpath { downloadJavadoc = true } }
-'''
-
-        when:
-        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
-
-        then:
-        eclipseProject.classpath.size() == 2
-        eclipseProject.classpath.collect { it.file.name } as Set == ['commons-lang-2.5.jar', 'commons-io-1.4.jar' ] as Set
-        eclipseProject.classpath.collect { it.source?.name } as Set == ['commons-lang-2.5-sources.jar', 'commons-io-1.4-sources.jar'] as Set
-        eclipseProject.classpath.collect { it.javadoc?.name } as Set == ['commons-lang-2.5-javadoc.jar', 'commons-io-1.4-javadoc.jar'] as Set
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
deleted file mode 100644
index 235b34f..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.integtests.fixtures.BasicGradleDistribution
-import org.gradle.tooling.UnsupportedVersionException
-import org.gradle.tooling.model.Project
-import org.gradle.util.GradleVersion
-
-class ToolingApiIntegrationTest extends ToolingApiSpecification {
-    final BasicGradleDistribution otherVersion = dist.previousVersion('1.0-milestone-3-20110424172210+1000')
-
-    def "tooling api uses to the current version of gradle when none has been specified"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${GradleVersion.current().version}'"
-
-        when:
-        Project model = toolingApi.withConnection { connection -> connection.getModel(Project.class) }
-
-        then:
-        model != null
-    }
-
-    def "tooling api uses the wrapper properties to determine which version to use"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = """
-task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
-task check << { assert gradle.gradleVersion == '${GradleVersion.current().version}' }
-"""
-        dist.executer().withTasks('wrapper').run()
-
-        when:
-        toolingApi.withConnection { connection -> connection.newBuild().forTasks('check').run() }
-
-        then:
-        notThrown(Throwable)
-    }
-
-    def "can specify a gradle installation to use"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
-
-        when:
-        toolingApi.withConnector { connector -> connector.useInstallation(otherVersion.gradleHomeDir) }
-        Project model = toolingApi.withConnection { connection -> connection.getModel(Project.class) }
-
-        then:
-        model != null
-    }
-
-    def "can specify a gradle distribution to use"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
-
-        when:
-        toolingApi.withConnector { connector -> connector.useDistribution(otherVersion.binDistribution.toURI()) }
-        Project model = toolingApi.withConnection { connection -> connection.getModel(Project.class) }
-
-        then:
-        model != null
-    }
-
-    def "can specify a gradle version to use"() {
-        def projectDir = dist.testDir
-        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
-
-        when:
-        toolingApi.withConnector { connector -> connector.useGradleVersion(otherVersion.version) }
-        Project model = toolingApi.withConnection { connection -> connection.getModel(Project.class) }
-
-        then:
-        model != null
-    }
-
-    def "tooling api reports an error when the specified gradle version does not support the tooling api"() {
-        def dist = dist.previousVersion('0.9.2').binDistribution
-
-        when:
-        toolingApi.withConnector { connector -> connector.useDistribution(dist.toURI()) }
-        def e = toolingApi.maybeFailWithConnection { connection -> connection.getModel(Project.class) }
-
-        then:
-        e.class == UnsupportedVersionException
-        e.message == "The specified Gradle distribution '${dist.toURI()}' is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiModelIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiModelIntegrationTest.groovy
deleted file mode 100644
index 1c9a191..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiModelIntegrationTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.tooling.BuildException
-import org.gradle.tooling.model.Project
-import org.gradle.tooling.ProgressListener
-
-class ToolingApiModelIntegrationTest extends ToolingApiSpecification {
-    def "receives progress and logging while the model is building"() {
-        dist.testFile('build.gradle') << '''
-System.out.println 'this is stdout'
-System.err.println 'this is stderr'
-'''
-
-        def stdout = new ByteArrayOutputStream()
-        def stderr = new ByteArrayOutputStream()
-        def progressMessages = []
-
-        when:
-        withConnection { connection ->
-            def model = connection.model(Project.class)
-            model.standardOutput = stdout
-            model.standardError = stderr
-            model.addProgressListener({ event -> progressMessages << event.description } as ProgressListener)
-            return model.get()
-        }
-
-        then:
-        stdout.toString().contains('this is stdout')
-        stderr.toString().contains('this is stderr')
-        progressMessages.size() >= 2
-        progressMessages.pop() == ''
-        progressMessages.every { it }
-    }
-
-    def "tooling api reports failure to build model"() {
-        dist.testFile('build.gradle') << 'broken'
-
-        when:
-        withConnection { connection ->
-            return connection.getModel(Project.class)
-        }
-
-        then:
-        BuildException e = thrown()
-        e.message.startsWith('Could not fetch model of type \'Project\' using Gradle')
-        e.cause.message.contains('A problem occurred evaluating root project')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiSpecification.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiSpecification.groovy
deleted file mode 100644
index c5430f1..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiSpecification.groovy
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 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.tooling
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-import spock.lang.Specification
-
-abstract class ToolingApiSpecification extends Specification {
-    @Rule public final SetSystemProperties sysProperties = new SetSystemProperties()
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final ToolingApi toolingApi = new ToolingApi()
-
-    def withConnection(Closure cl) {
-        toolingApi.withConnection(cl)
-    }
-
-    def maybeFailWithConnection(Closure cl) {
-        toolingApi.maybeFailWithConnection(cl)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar
new file mode 100644
index 0000000..f818958
Binary files /dev/null and b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar differ
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
deleted file mode 100644
index 8626e19..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
+++ /dev/null
@@ -1,59 +0,0 @@
-import static org.junit.Assert.*
-
-configurations {
-    compile
-    runtime { extendsFrom compile }
-}
-dependencies {
-    repositories {
-        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-            name = 'resolver'
-            addArtifactPattern(projectDir.absolutePath + '/[artifact]-[revision].jar')
-            addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
-        }
-    }
-    compile group: 'test', name: 'projectA', version: '1.2', configuration: 'api'
-    runtime group: 'test', name: 'projectA', version: '1.2'
-    runtime group: 'test', name: 'projectB', version: '1.5', configuration: 'extraRuntime'
-}
-
-file("projectA-1.2.jar").text = ''
-file("projectB-1.5.jar").text = ''
-file("projectB-api-1.5.jar").text = ''
-file("projectB-extraRuntime-1.5.jar").text = ''
-
-defaultTasks 'listJars'
-
-task listJars << {
-    def compile = configurations.compile
-
-    Set jars = compile.collect { it.name } as Set
-    assertEquals(['projectA-1.2.jar', 'projectB-api-1.5.jar'] as Set, jars)
-
-    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' }
-    def root = (projectA.parents as List)[0]
-    def artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
-    assertEquals(['projectA', 'projectB-api'] as Set, artifacts)
-
-    def projectB = projectA.children.find { it.moduleName == 'projectB' }
-    artifacts = projectB.getAllArtifacts(projectA).collect { it.name } as Set
-    assertEquals(['projectB-api'] as Set, artifacts)
-
-    def runtime = configurations.runtime
-
-    jars = runtime.collect { it.name } as Set
-    assertEquals(['projectA-1.2.jar', 'projectB-api-1.5.jar', 'projectB-1.5.jar', 'projectB-extraRuntime-1.5.jar'] as Set, jars)
-
-    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
-    root = (projectA.parents as List)[0]
-    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
-    // TODO - this is not right
-//    assertEquals(['projectA', 'projectB-api', 'projectB'] as Set, artifacts)
-    assertEquals(['projectA', 'projectB-api', 'projectB', 'projectB-extraRuntime'] as Set, artifacts)
-
-    projectB = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectB' && it.configuration == 'extraRuntime' }
-    artifacts = projectB.getAllArtifacts(root).collect { it.name } as Set
-    // TODO - this is not right
-//    assertEquals(['projectB', 'projectB-extraRuntime'] as Set, artifacts)
-    assertEquals(['projectB-api', 'projectB', 'projectB-extraRuntime'] as Set, artifacts)
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
deleted file mode 100644
index 1ecaba2..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-import static org.junit.Assert.*
-
-configurations {
-    compile
-}
-dependencies {
-    repositories {
-        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-            name = 'resolver'
-            addArtifactPattern(projectDir.absolutePath + '/[module]-[revision].jar')
-            addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
-        }
-    }
-    compile 'test:projectA:1.2'
-}
-
-file("projectA-1.2.jar").text = ''
-file("projectB-1.5.jar").text = ''
-
-defaultTasks 'listJars'
-
-task listJars << {
-    def compile = configurations.compile
-
-    Set jars = compile.collect { it.name } as Set
-    assertEquals(['projectA-1.2.jar', 'projectB-1.5.jar'] as Set, jars)
-
-    Set artifacts = compile.resolvedConfiguration.resolvedArtifacts.collect {
-        "${it.name}-${it.type}-${it.extension}" as String
-    } as Set
-    assertEquals(['projectA-jar-jar', 'projectB-jar-jar'] as Set, artifacts)
-
-    Set modules = compile.resolvedConfiguration.resolvedArtifacts.collect {
-        def dep = it.resolvedDependency
-        "${dep.moduleGroup}-${dep.moduleName}-${dep.moduleVersion}" as String
-    } as Set
-    assertEquals(['test-projectA-1.2', 'test-projectB-1.5'] as Set, modules)
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/build.gradle
deleted file mode 100644
index e4374f4..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/build.gradle
+++ /dev/null
@@ -1,42 +0,0 @@
-
-subprojects {
-    apply plugin: 'base'
-    configurations {
-        'default'
-        other
-    }
-    task jar(type: Jar)
-    artifacts {
-        'default' jar
-    }
-}
-
-project('a') {
-    dependencies {
-        'default' project(':b')
-        other project(':b')
-    }
-    task listJars {
-        dependsOn configurations.default
-        dependsOn configurations.other
-        doFirst {
-            def jars = configurations.default.collect { it.name } as Set
-            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
-
-            jars = configurations.other.collect { it.name } as Set
-            assert jars == ['a.jar', 'b.jar', 'c.jar'] as Set
-        }
-    }
-}
-
-project('b') {
-    dependencies {
-        'default' project(':c')
-    }
-}
-
-project('c') {
-    dependencies {
-        'default' project(':a')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/settings.gradle
deleted file mode 100644
index ebaf9bc..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include 'a', 'b', 'c'
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
deleted file mode 100644
index e405a51..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
+++ /dev/null
@@ -1,31 +0,0 @@
-import org.junit.Assert
-
-configurations {
-    compile
-}
-dependencies {
-    repositories {
-        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-            name = 'resolver'
-            addArtifactPattern(projectDir.absolutePath + '/[module]-[revision].jar')
-        }
-    }
-    compile module('test:projectA:1.2') {
-        module('test:projectB:1.5') {
-            dependencies('test:projectC:2.0')
-        }
-    }
-}
-
-defaultTasks 'listJars'
-
-file("projectA-1.2.jar").text = ''
-file("projectB-1.5.jar").text = ''
-file("projectC-2.0.jar").text = ''
-
-task listJars << {
-    List jars = configurations.compile.collect { it.name }
-    Assert.assertTrue(jars.contains('projectA-1.2.jar'))
-    Assert.assertTrue(jars.contains('projectB-1.5.jar'))
-    Assert.assertTrue(jars.contains('projectC-2.0.jar'))
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canResolveDependenciesFromAFlatDir/projectWithFlatDir.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canResolveDependenciesFromAFlatDir/projectWithFlatDir.gradle
deleted file mode 100644
index 165ec86..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canResolveDependenciesFromAFlatDir/projectWithFlatDir.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-defaultTasks 'list'
-
-repositories {
-    flatDir dirs: file('repo')
-}
-
-configurations {
-    compile
-}
-
-dependencies {
-    compile 'group:a:1.4'
-    compile 'group:b:2.0'
-    compile 'group:c:'
-}
-
-task list << {
-    def a = file('repo/a-1.4.jar')
-    def b = file('repo/b.jar')
-    def c = file('repo/c.jar')
-    a.parentFile.mkdirs()
-    a << 'content'
-    b << 'content'
-    c << 'content'
-
-    def files = configurations.compile.files
-    assert files == [a, b, c] as Set
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
deleted file mode 100644
index 285f98f..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-import static org.junit.Assert.*
-
-configurations {
-    compile
-}
-dependencies {
-    repositories {
-        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-            name = 'resolver'
-            addArtifactPattern(projectDir.absolutePath + '/[artifact]-[revision].jar')
-            addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
-        }
-    }
-    compile group: 'test', name: 'projectA', version: '1.+'
-}
-
-file("projectA-1.2.jar").text = ''
-file("projectB-1.5.jar").text = ''
-
-defaultTasks 'listJars'
-
-task listJars << {
-    def compile = configurations.compile
-
-    Set jars = compile.collect { it.name } as Set
-    assertEquals(['projectA-1.2.jar', 'projectB-1.5.jar'] as Set, jars)
-
-    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' }
-    assertEquals('1.2', projectA.moduleVersion)
-
-    def projectB = projectA.children.find { it.moduleName == 'projectB' }
-    assertEquals('1.5', projectB.moduleVersion)
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
deleted file mode 100644
index a027959..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
+++ /dev/null
@@ -1,57 +0,0 @@
-import static org.junit.Assert.*
-
-allprojects {
-    configurations {
-        evictedTransitive
-        evictedDirect
-        multiProject
-        add('default')
-    }
-    dependencies {
-        repositories {
-            add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-                name = 'resolver'
-                addArtifactPattern(projectDir.absolutePath + '/[module]-[revision].jar')
-                addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
-            }
-        }
-    }
-}
-
-dependencies {
-    // projectA-1.2 depends on projectB-1.5, which should be evicted by projectB-2.1.5
-    evictedTransitive 'test:projectA:1.2'
-    evictedTransitive 'test:projectB:2.1.5'
-
-    // projectA-2.0 depends on projectB-2.1.5, which should evict projectB-1.5
-    evictedDirect 'test:projectA:2.0'
-    evictedDirect 'test:projectB:1.5'
-
-    // subproject depends on projectA-2.0, which should evict projectA-1.2
-    multiProject 'test:projectA:1.2'
-    multiProject project(':subproject')
-}
-
-project(':subproject') {
-    dependencies {
-        add('default', 'test:projectA:2.0')
-    }
-}
-
-file("projectA-1.2.jar").text = ''
-file("projectA-2.0.jar").text = ''
-file("projectB-1.5.jar").text = ''
-file("projectB-2.1.5.jar").text = ''
-
-defaultTasks 'check'
-
-task check << {
-    def jars = configurations.evictedTransitive.collect { it.name }
-    assertEquals(['projectA-1.2.jar', 'projectB-2.1.5.jar'] as Set, jars as Set)
-
-    jars = configurations.evictedDirect.collect { it.name }
-    assertEquals(['projectA-2.0.jar', 'projectB-2.1.5.jar'] as Set, jars as Set)
-
-    jars = configurations.multiProject.collect { it.name }
-    assertEquals(['projectA-2.0.jar', 'projectB-2.1.5.jar'] as Set, jars as Set)
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
deleted file mode 100644
index bbb94dc..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-import org.junit.Assert
-
-configurations {
-    compile
-}
-dependencies {
-    compile 'test:unknownProjectA:1.2'
-    compile 'test:unknownProjectB:2.1.5'
-}
-
-defaultTasks 'listJars'
-
-task listJars << {
-    configurations.compile.resolve()
-    Assert.fail()
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CommandLineIntegrationTest/shared/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CommandLineIntegrationTest/shared/build.gradle
old mode 100644
new mode 100755
index e2386c8..cd9ee94
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CommandLineIntegrationTest/shared/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CommandLineIntegrationTest/shared/build.gradle
@@ -1,3 +1,4 @@
+import org.gradle.internal.jvm.Jvm
 
 task checkGradleUserHomeViaSystemEnv << {
     assert gradle.gradleUserHomeDir == file('customUserHome')
@@ -16,5 +17,6 @@ task checkJavaHome << {
 }
 
 task checkSystemProperty << {
-    assert System.getProperty('customSystemProperty') == 'custom-value'
+    assert System.getProperty('customProp1') == 'custom-value'
+    assert System.getProperty('customProp2') == 'custom value'
 }
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CommandLineIntegrationTest/shared/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CommandLineIntegrationTest/shared/settings.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
deleted file mode 100644
index 1aa6152..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-usePlugin('java')
-
-task custom(type: org.gradle.CustomTask)
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
deleted file mode 100644
index 549422b..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.gradle
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.TaskAction
-
-class CustomTask extends DefaultTask {
-    @TaskAction
-    def go() {
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
deleted file mode 100644
index 8df4e4e..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.gradle;
-
-public interface Person {
-    String getName();
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/shared/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
deleted file mode 100644
index eb60aff..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
+++ /dev/null
@@ -1,11 +0,0 @@
-
-task wrapper(type: Wrapper) {
-    if (project.hasProperty('distVersion')) {
-        gradleVersion = distVersion
-        urlRoot = new File(project.distZip).parentFile.toURI().toString()
-    }
-}
-
-task hello {
-    doLast { println "hello from $gradle.gradleVersion" }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
index bd60577..1950843 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
@@ -1,10 +1,12 @@
+import org.gradle.internal.jvm.Jvm
+
 apply plugin: 'java'
 
 defaultTasks 'execTask', 'execByMethod'
 
 task execTask(type: Exec) {
     dependsOn sourceSets.main.runtimeClasspath
-    testFile = file("$buildDir/$name")
+    ext.testFile = file("$buildDir/$name")
     executable = Jvm.current().getJavaExecutable()
     args '-cp', sourceSets.main.runtimeClasspath.asPath, 'org.gradle.TestMain', projectDir, testFile
     doLast {
@@ -14,7 +16,7 @@ task execTask(type: Exec) {
 
 task execByMethod {
     dependsOn sourceSets.main.runtimeClasspath
-    testFile = file("$buildDir/$name")
+    ext.testFile = file("$buildDir/$name")
     doFirst {
         exec {
             executable = Jvm.current().getJavaExecutable()
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
index febcd4f..1e3a688 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
@@ -3,8 +3,8 @@ apply plugin: 'java'
 defaultTasks 'javaexecTask', 'javaexecByMethod'
 
 task javaexecTask(type: JavaExec, dependsOn: classes) {
-    testFile = file("$buildDir/$name")
-    classpath(sourceSets.main.classesDir)
+    ext.testFile = file("$buildDir/$name")
+    classpath(sourceSets.main.output.classesDir)
     main = 'org.gradle.TestMain'
     args projectDir, testFile
     doLast {
@@ -13,10 +13,10 @@ task javaexecTask(type: JavaExec, dependsOn: classes) {
 }
 
 task javaexecByMethod(dependsOn: classes) {
-    testFile = file("$buildDir/$name")
+    ext.testFile = file("$buildDir/$name")
     doFirst {
         javaexec {
-            classpath(sourceSets.main.classesDir)
+            classpath(sourceSets.main.output.classesDir)
             main = 'org.gradle.TestMain'
             args projectDir, testFile
         }
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java
deleted file mode 100644
index 6ef4192..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java
+++ /dev/null
@@ -1,4 +0,0 @@
-
-interface Person {
-    String getName();
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.Groovy b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.Groovy
deleted file mode 100644
index f5d5b6e..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.Groovy
+++ /dev/null
@@ -1,4 +0,0 @@
-
-class PersonImpl implements Person {
-    def String name
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
deleted file mode 100644
index adf21b6..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
+++ /dev/null
@@ -1,4 +0,0 @@
-apply plugin: 'java'
-
-sourceCompatibility = 1.5
-compileJava.debug = true
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
index 9ec301a..d272675 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
@@ -6,7 +6,7 @@ repositories {
 
 dependencies {
     testCompile 'junit:junit:4.8.2'
-    testCompile 'org.testng:testng:5.14.10'
+    testCompile 'org.testng:testng:6.3.1'
 }
 
 test {
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
deleted file mode 100644
index 92b1017..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
+++ /dev/null
@@ -1,8 +0,0 @@
-apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'junit:junit:4.8.2', 'ant:ant:1.6.1', 'ant:ant-launcher:1.6.1' }
-test {
-    systemProperties.testSysProperty = 'value'
-    systemProperties.projectDir = projectDir
-    environment.TEST_ENV_VAR = 'value'
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
deleted file mode 100644
index 365d645..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.gradle;
-
-import static org.junit.Assert.*;
-
-import java.io.PrintStream;
-import java.util.logging.Logger;
-
-public class OkTest {
-    static {
-        System.out.println("class loaded");
-    }
-
-    public OkTest() {
-        System.out.println("test constructed");
-    }
-
-    @org.junit.Test
-    public void ok() throws Exception {
-        // check JUnit version
-        assertEquals("4.8.2", new org.junit.runner.JUnitCore().getVersion());
-        // check Ant version
-        assertTrue(org.apache.tools.ant.Main.getAntVersion().contains("1.6.1"));
-        // check working dir
-        assertEquals(System.getProperty("projectDir"), System.getProperty("user.dir"));
-        // check classloader
-        assertSame(ClassLoader.getSystemClassLoader(), getClass().getClassLoader());
-        assertSame(getClass().getClassLoader(), Thread.currentThread().getContextClassLoader());
-        // check Gradle and impl classes not visible
-        try {
-            getClass().getClassLoader().loadClass("org.gradle.api.Project");
-            fail();
-        } catch (ClassNotFoundException e) {
-        }
-        try {
-            getClass().getClassLoader().loadClass("org.slf4j.Logger");
-            fail();
-        } catch (ClassNotFoundException e) {
-        }
-        // check sys properties
-        assertEquals("value", System.getProperty("testSysProperty"));
-        // check env vars
-        assertEquals("value", System.getenv("TEST_ENV_VAR"));
-
-        // check stdout and stderr and logging
-        System.out.println("This is test stdout");
-        System.out.print("no EOL");
-        System.out.println();
-        System.err.println("This is test stderr");
-        Logger.getLogger("test-logger").warning("this is a warning");
-
-        final PrintStream out = System.out;
-        // logging from a shutdown hook
-        Runtime.getRuntime().addShutdownHook(new Thread() {
-            @Override
-            public void run() {
-                out.println("stdout from a shutdown hook.");
-                Logger.getLogger("test-logger").info("info from a shutdown hook.");
-            }
-        });
-
-        // logging from another thread
-        Thread thread = new Thread() {
-            @Override
-            public void run() {
-                System.out.println("stdout from another thread");
-                Logger.getLogger("test-logger").info("info from another thread.");
-            }
-        };
-        thread.start();
-        thread.join();
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/deprecated/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/deprecated/build.gradle
new file mode 100644
index 0000000..597f51c
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/deprecated/build.gradle
@@ -0,0 +1,7 @@
+
+task log {
+    DeprecationLogger.nagUserWith("A deprecation warning")
+    doLast {
+        DeprecationLogger.nagUserWith("A deprecation warning")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/buildSrc/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/buildSrc/build.gradle
index e9238dc..f57df29 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/buildSrc/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/buildSrc/build.gradle
@@ -1,22 +1,2 @@
 println 'main buildSrc quiet'
 logger.info 'main buildSrc info'
-
-task classes << {}
-
-convention.plugins.java = new ProjectInfo(project: project);
-
-class ProjectInfo implements org.gradle.api.internal.plugins.EmbeddableJavaProject {
-    def Project project
-
-    Collection<String> getRebuildTasks() {
-        return ['classes']
-    }
-
-    Collection<String> getBuildTasks() {
-        return ['classes']
-    }
-
-    FileCollection getRuntimeClasspath() {
-        return project.files()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/external.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/external.gradle
old mode 100644
new mode 100755
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle
index 807e0b2..b8f4081 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle
@@ -1,22 +1,2 @@
 println 'nestedBuild buildSrc quiet'
 logger.info 'nestedBuild buildSrc info'
-
-task classes << { }
-
-convention.plugins.java = new ProjectInfo(project: project);
-
-class ProjectInfo implements org.gradle.api.internal.plugins.EmbeddableJavaProject {
-    def Project project
-
-    Collection<String> getRebuildTasks() {
-        return ['classes']
-    }
-
-    Collection<String> getBuildTasks() {
-        return ['classes']
-    }
-
-    FileCollection getRuntimeClasspath() {
-        return project.files()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project2/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project2/build.gradle
index aac6f2a..585751b 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project2/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project2/build.gradle
@@ -5,7 +5,7 @@ buildscript {
     logger.info('infoProject2ScriptClassPathOut')
 }
 
-dependsOn(':project1')
+evaluationDependsOn(':project1')
 // stdout capture config injected
 println('infoProject2Out')
 
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
index ad09095..d227c19 100644
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
@@ -8,6 +8,6 @@ dependencies {
     testCompile 'junit:junit:4.8.2'
 }
 
-buildDirName = 'target'
-sourceSets.main.classesDir = new File(buildDir, 'main-classes')
-sourceSets.test.classesDir = new File(buildDir, 'test-classes')
\ No newline at end of file
+buildDir = 'target'
+sourceSets.main.output.classesDir = new File(buildDir, 'main-classes')
+sourceSets.test.output.classesDir = new File(buildDir, 'test-classes')
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedClasspathFile.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedClasspathFile.txt
old mode 100644
new mode 100755
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedProjectFile.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedProjectFile.txt
old mode 100644
new mode 100755
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedClasspathFile.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedClasspathFile.txt
old mode 100644
new mode 100755
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt
old mode 100644
new mode 100755
index fd5ca0a..163de46
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt
@@ -6,11 +6,11 @@
   <projects/>
   <natures>
     <nature>org.eclipse.jdt.core.javanature</nature>
-    <nature>ch.epfl.lamp.sdt.core.scalanature</nature>
+    <nature>org.scala-ide.sdt.core.scalanature</nature>
   </natures>
   <buildSpec>
     <buildCommand>
-      <name>ch.epfl.lamp.sdt.core.scalabuilder</name>
+      <name>org.scala-ide.sdt.core.scalabuilder</name>
       <arguments/>
     </buildCommand>
   </buildSpec>
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/build.gradle
deleted file mode 100644
index fa6c551..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-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/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/projectWithMavenSnapshots.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/projectWithMavenSnapshots.gradle
deleted file mode 100644
index 5053600..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/projectWithMavenSnapshots.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-apply plugin: 'java'
-
-def repoUrl = hasProperty('repoUrl') ? repoUrl : uri('repo')
-
-repositories {
-    mavenRepo(urls: repoUrl) {
-        if (project.hasProperty('noTimeout')) {
-            setSnapshotTimeout(0)
-        }
-    }
-}
-
-dependencies {
-    compile "org.gradle:testproject:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/build.gradle
new file mode 100644
index 0000000..a9cad0b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = 1.0
+
+task signature {
+    ext.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/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithMetadataArtifacts/settings.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenProjectIntegrationTest/canDeployAProjectWithNoMainArtifact/settings.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedNewPom.txt
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedNewPom.txt
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedPom.txt
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedPom.txt
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedQuickstartPom.txt b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedQuickstartPom.txt
old mode 100644
new mode 100755
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedQuickstartPom.txt
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/pomGeneration/expectedQuickstartPom.txt
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
new file mode 100644
index 0000000..3ab8766
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
@@ -0,0 +1,58 @@
+configurations {
+    compile
+    runtime { extendsFrom compile }
+}
+dependencies {
+    repositories {
+        ivy {
+            artifactPattern projectDir.absolutePath + '/[artifact]-[revision].jar'
+            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+        }
+    }
+    compile group: 'test', name: 'projectA', version: '1.2', configuration: 'api'
+    runtime group: 'test', name: 'projectA', version: '1.2'
+    runtime group: 'test', name: 'projectB', version: '1.5', configuration: 'extraRuntime'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectB-api-1.5.jar").text = ''
+file("projectB-extraRuntime-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    Set jars = compile.collect { it.name } as Set
+    assert ['projectA-1.2.jar', 'projectB-api-1.5.jar'] as Set == jars
+
+    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
+    def root = (projectA.parents as List)[0]
+    def artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectA', 'projectB-api'] as Set == artifacts
+
+    def projectB = projectA.children.find { it.moduleName == 'projectB' && it.configuration == 'compileTime' }
+    artifacts = projectB.getAllArtifacts(projectA).collect { it.name } as Set
+    assert ['projectB-api'] as Set == artifacts
+
+    def runtime = configurations.runtime
+
+    jars = runtime.collect { it.name } as Set
+    assert ['projectA-1.2.jar', 'projectB-api-1.5.jar', 'projectB-1.5.jar', 'projectB-extraRuntime-1.5.jar'] as Set == jars
+
+    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
+    root = (projectA.parents as List)[0]
+    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectA', 'projectB-api'] as Set == artifacts
+
+    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'default' }
+    root = (projectA.parents as List)[0]
+    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectA', 'projectB'] as Set == artifacts
+
+    projectB = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectB' && it.configuration == 'extraRuntime' }
+    artifacts = projectB.getAllArtifacts(root).collect { it.name } as Set
+    assert ['projectB', 'projectB-extraRuntime'] as Set == artifacts
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
new file mode 100644
index 0000000..32ffcee
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
@@ -0,0 +1,35 @@
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy {
+            artifactPattern projectDir.absolutePath + '/[module]-[revision].jar'
+            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+        }
+    }
+    compile 'test:projectA:1.2'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    Set jars = compile.collect { it.name } as Set
+    assert ['projectA-1.2.jar', 'projectB-1.5.jar'] as Set == jars
+
+    Set artifacts = compile.resolvedConfiguration.resolvedArtifacts.collect {
+        "${it.name}-${it.type}-${it.extension}" as String
+    } as Set
+    assert ['projectA-jar-jar', 'projectB-jar-jar'] as Set == artifacts
+
+    Set modules = compile.resolvedConfiguration.resolvedArtifacts.collect {
+        def dep = it.moduleVersion
+        "${dep.id.group}-${dep.id.name}-${dep.id.version}" as String
+    } as Set
+    assert ['test-projectA-1.2', 'test-projectB-1.5'] as Set == modules
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
new file mode 100644
index 0000000..d104d48
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
@@ -0,0 +1,26 @@
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy {
+            artifactPattern projectDir.absolutePath + '/[module]-[revision].[ext]'
+        }
+    }
+    compile module('test:projectA:1.2') {
+        module('test:projectB:1.5') {
+            dependencies('test:projectC:2.0')
+        }
+    }
+}
+
+defaultTasks 'listJars'
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectC-2.0.jar").text = ''
+
+task listJars << {
+    List jars = configurations.compile.collect { it.name }
+    assert jars == ['projectA-1.2.jar', 'projectC-2.0.jar', 'projectB-1.5.jar']
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
new file mode 100644
index 0000000..f1a09c2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
@@ -0,0 +1,33 @@
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy {
+            artifactPattern projectDir.absolutePath + '/[artifact]-[revision].jar'
+            ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+        }
+    }
+    compile group: 'test', name: 'projectA', version: '1.+'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    def jars = compile.collect { it.name }
+    assert ['projectA-1.2.jar', 'projectB-1.5.jar'] == jars
+
+    def artifacts = compile.resolvedConfiguration.resolvedArtifacts.collect { "$it.name-$it.moduleVersion.id.version.$it.extension" }
+    assert ['projectA-1.2.jar', 'projectB-1.5.jar'] == artifacts
+
+    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' }
+    assert '1.2' == projectA.moduleVersion
+
+    def projectB = projectA.children.find { it.moduleName == 'projectB' }
+    assert '1.5' == projectB.moduleVersion
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
new file mode 100644
index 0000000..0deaf43
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
@@ -0,0 +1,54 @@
+allprojects {
+    configurations {
+        evictedTransitive
+        evictedDirect
+        multiProject
+        add('default')
+    }
+    dependencies {
+        repositories {
+            ivy {
+                artifactPattern projectDir.absolutePath + '/[module]-[revision].jar'
+                ivyPattern projectDir.absolutePath + '/[module]-[revision]-ivy.xml'
+            }
+        }
+    }
+}
+
+dependencies {
+    // projectA-1.2 depends on projectB-1.5, which should be evicted by projectB-2.1.5
+    evictedTransitive 'test:projectA:1.2'
+    evictedTransitive 'test:projectB:2.1.5'
+
+    // projectA-2.0 depends on projectB-2.1.5, which should evict projectB-1.5
+    evictedDirect 'test:projectA:2.0'
+    evictedDirect 'test:projectB:1.5'
+
+    // subproject depends on projectA-2.0, which should evict projectA-1.2
+    multiProject 'test:projectA:1.2'
+    multiProject project(':subproject')
+}
+
+project(':subproject') {
+    dependencies {
+        add('default', 'test:projectA:2.0')
+    }
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectA-2.0.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectB-2.1.5.jar").text = ''
+
+defaultTasks 'check'
+
+task check << {
+    def jars = configurations.evictedTransitive.collect { it.name }
+    assert ['projectA-1.2.jar', 'projectB-2.1.5.jar'] as Set == jars as Set
+
+    jars = configurations.evictedDirect.collect { it.name }
+    assert ['projectA-2.0.jar', 'projectB-2.1.5.jar'] as Set == jars as Set
+
+    jars = configurations.multiProject.collect { it.name }
+    assert ['projectA-2.0.jar', 'projectB-2.1.5.jar'] as Set == jars as Set
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
new file mode 100644
index 0000000..12f2baa
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
@@ -0,0 +1,14 @@
+configurations {
+    compile
+}
+dependencies {
+    compile 'test:unknownProjectA:1.2'
+    compile 'test:unknownProjectB:2.1.5'
+}
+
+defaultTasks 'listJars'
+
+task listJars << {
+    configurations.compile.resolve()
+    throw new RuntimeException("Should not reach here")
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
new file mode 100644
index 0000000..0c3f64e
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
@@ -0,0 +1,80 @@
+ext.sillyexceptions = 'sillyexceptions'
+ext.repotest = 'repotest'
+
+/*
+ * gradle_sourceforge:
+ * - repotest
+ * -- repotest
+ * --- 1.0
+ * ---- repotest-1.0.pom (-> testdep-1.0)
+ *
+ * - repotest
+ * -- classifier
+ * --- 1.0
+ * ---- classifier-1.0.pom (-> classifier-dep-1.0)
+ * ---- classifier-1.0-jdk14.jar
+ * ---- classifier-1.0-jdk15.jar
+ *
+ * - repotest
+ * -- classifier-dep
+ * --- 1.0
+ * ---- classifier-dep-1.0.pom
+ * ---- classifier-dep-1.0.jar
+ *
+ * gradle_sourceforge2
+ * - repotest
+ * -- repotest
+ * --- 1.0
+ * ---- repotest-1.0.jar
+ *
+ * - testdep
+ * -- testdep
+ * --- 1.0
+ * ---- testdep-1.0.pom
+ * ---- testdep-1.0.jar
+ *
+ * - testdep2
+ * -- testdep2
+ * --- 1.0
+ * ---- testdep2-1.0.jar
+ * ---- testdep2-1.0.pom
+ *
+ * - jaronly
+ * -- jaronly
+ * --- 1.0
+ * ---- jaronly-1.0.jar
+ *
+ * Maven Repo:
+ *
+ * - sillyexceptions
+ * -- sillyexceptions
+ * --- 1.0.1
+ * ---- sillyexceptions-1.0.1.jar
+ * ---- sillyexceptions-1.0.1.pom
+ *
+ * Transitive Dependencies
+ *
+ * repotest -> testdep
+ * testdep -> testdep2
+ */
+
+configurations {
+    test
+}
+repositories {
+    maven {
+        url 'http://gradle.sourceforge.net/repository/'
+        artifactUrls = ['http://gradle.sourceforge.net/otherrepo/']
+    }
+    maven { url 'http://gradle.sourceforge.net/otherrepo/' }
+    mavenCentral()
+}
+
+dependencies {
+    test "$sillyexceptions:$sillyexceptions:1.0.1 at jar", "$repotest:$repotest:1.0", "$repotest:classifier:1.0:jdk15", "jaronly:jaronly:1.0"
+}
+
+task retrieve(type: Sync) {
+    from configurations.test
+    into buildDir
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/producer.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/producer.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/producer.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/producer.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/projectWithMavenSnapshots.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/projectWithMavenSnapshots.gradle
new file mode 100644
index 0000000..acabcda
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/projectWithMavenSnapshots.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'java'
+
+def repoUrl = hasProperty('repoUrl') ? repoUrl : uri('repo')
+
+repositories {
+    mavenRepo(url: repoUrl) {
+        if (project.hasProperty('noTimeout')) {
+            setSnapshotTimeout(0)
+        }
+    }
+}
+
+dependencies {
+    compile "org.gradle:testproject:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/src/main/java/org/gradle/Test.java b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/src/main/java/org/gradle/Test.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/shared/src/main/java/org/gradle/Test.java
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/src/main/java/org/gradle/Test.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/build.gradle
deleted file mode 100644
index 0e8aeb2..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/build.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'org.testng:testng:5.14.10' }
-def listener = new TestListenerImpl()
-
-test {
-    useTestNG()
-    addTestListener(listener)
-    ignoreFailures = true
-}
-
-class TestListenerImpl implements TestListener {
-    void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
-
-    void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name]" }
-
-    void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
-
-    void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.exception]" }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
deleted file mode 100644
index ba38fcf..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
+++ /dev/null
@@ -1,9 +0,0 @@
-apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'org.testng:testng:5.14.10' }
-test {
-    useTestNG()
-    systemProperties.testSysProperty = 'value'
-    systemProperties.testDir = projectDir
-    environment.TEST_ENV_VAR = 'value'
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
deleted file mode 100644
index 48b3b43..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-apply plugin: 'groovy'
-
-sourceCompatibility=1.5
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-	groovy "org.codehaus.groovy:groovy-all:1.7.10"
-    testCompile 'org.testng:testng:5.14.10'
-}
-
-test {
-   useTestNG() 
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
deleted file mode 100644
index 48b3b43..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-apply plugin: 'groovy'
-
-sourceCompatibility=1.5
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-	groovy "org.codehaus.groovy:groovy-all:1.7.10"
-    testCompile 'org.testng:testng:5.14.10'
-}
-
-test {
-   useTestNG() 
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
deleted file mode 100644
index 98cc7ff..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-apply plugin: 'java'
-
-sourceCompatibility=1.5
-
-repositories {
-    mavenCentral()
-}
-
-dependencies {
-    testCompile 'org.testng:testng:5.14.10'
-}
-
-test {
-   useTestNG()
-}
diff --git a/subprojects/internal-integ-testing/internal-integ-testing.gradle b/subprojects/internal-integ-testing/internal-integ-testing.gradle
new file mode 100644
index 0000000..8307cfa
--- /dev/null
+++ b/subprojects/internal-integ-testing/internal-integ-testing.gradle
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.
+ */
+apply from: "$rootDir/gradle/classycle.gradle"
+
+dependencies {
+    groovy libraries.groovy
+    compile project(":internalTesting")
+    compile project(':cli')
+    compile project(':launcher')
+    compile project(':native')
+    compile libraries.jetty
+    compile module("org.littleshoot:littleproxy:0.4") {
+        dependency libraries.slf4j_api
+        dependency "org.jboss.netty:netty:3.2.4.Final"
+    }
+    compile "org.apache.sshd:sshd-core:0.6.0"
+}
+
+useTestFixtures(sourceSet: 'main')
+
+task prepareVersionsInfo {
+    ext.destDir = file("$buildDir/generated-resources/main")
+
+    doLast {
+        def url = "http://services.gradle.org/versions/all"
+        logger.info "Getting the released versions from: $url"
+
+        def json = new URL("http://services.gradle.org/versions/all").text
+        def destFile = new File(ext.destDir, "all-released-versions.json")
+        assert destDir.mkdirs() || destDir.exists()
+        destFile.text = json
+
+        logger.info "Saved released versions information in: $destFile"
+    }
+}
+
+sourceSets.main.output.dir prepareVersionsInfo.destDir, builtBy: prepareVersionsInfo
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
new file mode 100644
index 0000000..966ff2e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractAutoTestedSamplesTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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
+
+/**
+ * @author Szczepan Faber, created at: 4/2/11
+ */
+class AbstractAutoTestedSamplesTest extends AbstractIntegrationTest {
+
+     def util = new AutoTestedSamplesUtil()
+
+     void runSamplesFrom(String dir) {
+        util.findSamples(dir) { file, sample ->
+            println "Found sample: ${sample.split("\n")[0]} (...) in $file"
+            def buildFile = testFile('build.gradle')
+            buildFile.text = sample
+
+            usingBuildFile(buildFile).withQuietLogging().withTasks('help').withArguments("-s").run()
+        }
+    }
+
+    /**
+     * Useful for quick dev cycles when you need to run test against a single file.
+     *
+     * @param includes ant-like includes, e.g. '**\SomeClass.java'
+     */
+    void includeOnly(String includes) {
+        util.includes = includes
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
new file mode 100755
index 0000000..a623e8b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractCompatibilityTestRunner.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 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.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A base class for those test runners which execute a test multiple times against a set of Gradle versions.
+ */
+public abstract class AbstractCompatibilityTestRunner extends AbstractMultiTestRunner {
+    protected final GradleDistribution current = new GradleDistribution();
+    protected final List<BasicGradleDistribution> previous;
+
+    protected AbstractCompatibilityTestRunner(Class<?> target) {
+        this(target, null);
+    }
+
+    protected AbstractCompatibilityTestRunner(Class<?> target, String versionStr) {
+        super(target);
+        validateTestName(target);
+
+        previous = new ArrayList<BasicGradleDistribution>();
+        if (versionStr == null) {
+            versionStr = System.getProperty("org.gradle.integtest.versions", "latest");
+        }
+        ReleasedVersions previousVersions = new ReleasedVersions(current);
+        if (!versionStr.equals("all")) {
+            previous.add(previousVersions.getLast());
+        } else {
+            List<BasicGradleDistribution> all = previousVersions.getAll();
+            for (BasicGradleDistribution previous : all) {
+                if (!previous.worksWith(Jvm.current())) {
+                    add(new IgnoredVersion(previous, "does not work with current JVM"));
+                    continue;
+                }
+                if (!previous.worksWith(OperatingSystem.current())) {
+                    add(new IgnoredVersion(previous, "does not work with current OS"));
+                    continue;
+                }
+                this.previous.add(previous);
+            }
+        }
+    }
+
+    /**
+     * Makes sure the test adhers to the naming convention.
+     *
+     * @param target test class
+     */
+    private void validateTestName(Class<?> target) {
+        if (!target.getSimpleName().contains("CrossVersion")) {
+            throw new RuntimeException("The tests that use " + this.getClass().getSimpleName()
+                    + " must follow a certain naming convention, e.g. name must contain 'CrossVersion' substring.\n"
+                    + "This way we can include/exclude those test nicely and it is easier to configure the CI.\n"
+                    + "Please include 'CrossVersion' in the name of the test: '" + target.getSimpleName() + "'");
+        }
+    }
+
+    public List<BasicGradleDistribution> getPrevious() {
+        return previous;
+    }
+
+    private static class IgnoredVersion extends Execution {
+        private final BasicGradleDistribution distribution;
+        private final String why;
+
+        private IgnoredVersion(BasicGradleDistribution distribution, String why) {
+            this.distribution = distribution;
+            this.why = why;
+        }
+
+        @Override
+        protected boolean isEnabled() {
+            return false;
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return String.format("%s %s", distribution.getVersion(), why);
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDelegatingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDelegatingGradleExecuter.java
new file mode 100644
index 0000000..4dda18a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDelegatingGradleExecuter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.launcher.daemon.registry.DaemonRegistry;
+
+public abstract class AbstractDelegatingGradleExecuter extends AbstractGradleExecuter {
+    @Override
+    protected ExecutionResult doRun() {
+        return checkResult(configureExecuter().run());
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        return checkResult(configureExecuter().runWithFailure());
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        return configureExecuter().getDaemonRegistry();
+    }
+
+    @Override
+    public GradleHandle doStart() {
+        return configureExecuter().start();
+    }
+
+    protected abstract GradleExecuter configureExecuter();
+
+    protected <T extends ExecutionResult> T checkResult(T result) {
+        return result;
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
new file mode 100644
index 0000000..81f33b9
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
@@ -0,0 +1,340 @@
+/*
+ * 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.internal.jvm.Jvm;
+import org.gradle.util.TextUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.*;
+
+import static java.util.Arrays.asList;
+
+public abstract class AbstractGradleExecuter implements GradleExecuter {
+    private final List<String> args = new ArrayList<String>();
+    private final List<String> tasks = new ArrayList<String>();
+    private File workingDir;
+    private boolean quiet;
+    private boolean taskList;
+    private boolean dependencyList;
+    private boolean searchUpwards;
+    private Map<String, String> environmentVars = new HashMap<String, String>();
+    private List<File> initScripts = new ArrayList<File>();
+    private String executable;
+    private File userHomeDir;
+    private File javaHome;
+    private File buildScript;
+    private File projectDir;
+    private File settingsFile;
+    private InputStream stdin;
+    private String defaultCharacterEncoding;
+    //gradle opts make sense only for forking executer but having them here makes more sense
+    protected final List<String> gradleOpts = new ArrayList<String>();
+
+    public GradleExecuter reset() {
+        args.clear();
+        tasks.clear();
+        initScripts.clear();
+        workingDir = null;
+        projectDir = null;
+        buildScript = null;
+        settingsFile = null;
+        quiet = false;
+        taskList = false;
+        dependencyList = false;
+        searchUpwards = false;
+        executable = null;
+        userHomeDir = null;
+        javaHome = null;
+        environmentVars.clear();
+        stdin = null;
+        defaultCharacterEncoding = null;
+        return this;
+    }
+
+    public GradleExecuter inDirectory(File directory) {
+        workingDir = directory;
+        return this;
+    }
+
+    public File getWorkingDir() {
+        return workingDir;
+    }
+
+    protected void copyTo(GradleExecuter executer) {
+        if (workingDir != null) {
+            executer.inDirectory(workingDir);
+        }
+        if (projectDir != null) {
+            executer.usingProjectDirectory(projectDir);
+        }
+        if (buildScript != null) {
+            executer.usingBuildScript(buildScript);
+        }
+        if (settingsFile != null) {
+            executer.usingSettingsFile(settingsFile);
+        }
+        if (javaHome != null) {
+            executer.withJavaHome(javaHome);
+        }
+        for (File initScript : initScripts) {
+            executer.usingInitScript(initScript);
+        }
+        executer.withTasks(tasks);
+        executer.withArguments(args);
+        executer.withEnvironmentVars(getAllEnvironmentVars());
+        executer.usingExecutable(executable);
+        if (quiet) {
+            executer.withQuietLogging();
+        }
+        if (taskList) {
+            executer.withTaskList();
+        }
+        if (dependencyList) {
+            executer.withDependencyList();
+        }
+        executer.withUserHomeDir(userHomeDir);
+        if (stdin != null) {
+            executer.withStdIn(stdin);
+        }
+        if (defaultCharacterEncoding != null) {
+            executer.withDefaultCharacterEncoding(defaultCharacterEncoding);
+        }
+        executer.withGradleOpts(gradleOpts.toArray(new String[gradleOpts.size()]));
+    }
+
+    public GradleExecuter usingBuildScript(File buildScript) {
+        this.buildScript = buildScript;
+        return this;
+    }
+
+    public File getBuildScript() {
+        return buildScript;
+    }
+
+    public GradleExecuter usingProjectDirectory(File projectDir) {
+        this.projectDir = projectDir;
+        return this;
+    }
+
+    public GradleExecuter usingSettingsFile(File settingsFile) {
+        this.settingsFile = settingsFile;
+        return this;
+    }
+
+    public File getSettingsFile() {
+        return settingsFile;
+    }
+
+    public GradleExecuter usingInitScript(File initScript) {
+        initScripts.add(initScript);
+        return this;
+    }
+
+    public File getUserHomeDir() {
+        return userHomeDir;
+    }
+
+    public GradleExecuter withUserHomeDir(File userHomeDir) {
+        this.userHomeDir = userHomeDir;
+        return this;
+    }
+
+    public File getJavaHome() {
+        return javaHome == null ? Jvm.current().getJavaHome() : javaHome;
+    }
+
+    public GradleExecuter withJavaHome(File javaHome) {
+        this.javaHome = javaHome;
+        return this;
+    }
+
+    public GradleExecuter usingExecutable(String script) {
+        this.executable = script;
+        return this;
+    }
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public GradleExecuter withStdIn(String text) {
+        this.stdin = new ByteArrayInputStream(TextUtil.toPlatformLineSeparators(text).getBytes());
+        return this;
+    }
+
+    public GradleExecuter withStdIn(InputStream stdin) {
+        this.stdin = stdin;
+        return this;
+    }
+
+    public InputStream getStdin() {
+        return stdin == null ? new ByteArrayInputStream(new byte[0]) : stdin;
+    }
+
+    public GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        this.defaultCharacterEncoding = defaultCharacterEncoding;
+        return this;
+    }
+
+    public String getDefaultCharacterEncoding() {
+        return defaultCharacterEncoding == null ? Charset.defaultCharset().name() : defaultCharacterEncoding;
+    }
+
+    public GradleExecuter withSearchUpwards() {
+        searchUpwards = true;
+        return this;
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+
+    public GradleExecuter withQuietLogging() {
+        quiet = true;
+        return this;
+    }
+
+    public GradleExecuter withTaskList() {
+        taskList = true;
+        return this;
+    }
+
+    public GradleExecuter withDependencyList() {
+        dependencyList = true;
+        return this;
+    }
+
+    public GradleExecuter withArguments(String... args) {
+        return withArguments(Arrays.asList(args));
+    }
+
+    public GradleExecuter withArguments(List<String> args) {
+        this.args.clear();
+        this.args.addAll(args);
+        return this;
+    }
+
+    public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
+        environmentVars.clear();
+        for (Map.Entry<String, ?> entry : environment.entrySet()) {
+            environmentVars.put(entry.getKey(), entry.getValue().toString());
+        }
+        return this;
+    }
+
+    protected Map<String, String> getAllEnvironmentVars() {
+        return environmentVars;
+    }
+
+    public Map<String, String> getEnvironmentVars() {
+        return environmentVars;
+    }
+
+    public GradleExecuter withTasks(String... names) {
+        return withTasks(Arrays.asList(names));
+    }
+
+    public GradleExecuter withTasks(List<String> names) {
+        tasks.clear();
+        tasks.addAll(names);
+        return this;
+    }
+
+    protected List<String> getAllArgs() {
+        List<String> allArgs = new ArrayList<String>();
+        if (buildScript != null) {
+            allArgs.add("--build-file");
+            allArgs.add(buildScript.getAbsolutePath());
+        }
+        if (projectDir != null) {
+            allArgs.add("--project-dir");
+            allArgs.add(projectDir.getAbsolutePath());
+        }
+        for (File initScript : initScripts) {
+            allArgs.add("--init-script");
+            allArgs.add(initScript.getAbsolutePath());
+        }
+        if (settingsFile != null) {
+            allArgs.add("--settings-file");
+            allArgs.add(settingsFile.getAbsolutePath());
+        }
+        if (quiet) {
+            allArgs.add("--quiet");
+        }
+        if (taskList) {
+            allArgs.add("tasks");
+        }
+        if (dependencyList) {
+            allArgs.add("dependencies");
+        }
+        if (!searchUpwards) {
+            allArgs.add("--no-search-upward");
+        }
+        if (userHomeDir != null) {
+            allArgs.add("--gradle-user-home");
+            allArgs.add(userHomeDir.getAbsolutePath());
+        }
+        allArgs.addAll(args);
+        allArgs.addAll(tasks);
+        return allArgs;
+    }
+
+    public final GradleHandle start() {
+        try {
+            return doStart();
+        } finally {
+            reset();
+        }
+    }
+
+    public final ExecutionResult run() {
+        try {
+            return doRun();
+        } finally {
+            reset();
+        }
+    }
+
+    public final ExecutionFailure runWithFailure() {
+        try {
+            return doRunWithFailure();
+        } finally {
+            reset();
+        }
+    }
+
+    protected GradleHandle doStart() {
+        throw new UnsupportedOperationException(String.format("%s does not support running asynchronously.", getClass().getSimpleName()));
+    }
+
+    protected abstract ExecutionResult doRun();
+
+    protected abstract ExecutionFailure doRunWithFailure();
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractGradleExecuter withGradleOpts(String ... gradleOpts) {
+        this.gradleOpts.addAll(asList(gradleOpts));
+        return this;
+//        throw new UnsupportedOperationException("This executor: " + this.getClass().getSimpleName()
+//                + " does not support the gradle opts");
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
new file mode 100644
index 0000000..fedd4e9
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2011 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 org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * Spockified version of AbstractIntegrationTest.
+ * 
+ * Plan is to bring features over as needed.
+ */
+class AbstractIntegrationSpec extends Specification {
+    
+    @Rule final GradleDistribution distribution = new GradleDistribution()
+    @Rule final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    ExecutionResult result
+    ExecutionFailure failure
+
+    protected TestFile getBuildFile() {
+        testDir.file('build.gradle')
+    }
+
+    protected TestFile getSettingsFile() {
+        testDir.file('settings.gradle')
+    }
+
+    protected TestFile getTestDir() {
+        distribution.getTestDir();
+    }
+
+    protected TestFile file(Object... path) {
+        getTestDir().file(path);
+    }
+
+    protected GradleExecuter sample(Sample sample) {
+        inDirectory(sample.dir)
+    }
+
+    protected GradleExecuter inDirectory(String path) {
+        inDirectory(testDir.file(path))
+    }
+
+    protected GradleExecuter inDirectory(File directory) {
+        executer.inDirectory(directory);
+    }
+
+    protected GradleDistribution requireOwnUserHomeDir() {
+        distribution.requireOwnUserHomeDir()
+        distribution
+    }
+
+    /**
+     * Synonym for succeeds()
+     */
+    protected ExecutionResult run(String... tasks) {
+        succeeds(*tasks)
+    }
+
+    protected GradleExecuter withDebugLogging() {
+        executer.withArguments("-d")
+    }
+
+    protected ExecutionResult succeeds(String... tasks) {
+        if (settingsFile.exists()) {
+            executer.usingSettingsFile(settingsFile)
+        }
+        result = executer.withTasks(*tasks).run()
+    }
+
+    protected ExecutionFailure runAndFail(String... tasks) {
+        fails(*tasks)
+    }
+    
+    protected ExecutionFailure fails(String... tasks) {
+        failure = executer.withTasks(*tasks).runWithFailure()
+        result = failure
+    }
+    
+    protected List<String> getExecutedTasks() {
+        assertHasResult()
+        result.executedTasks
+    }
+    
+    protected Set<String> getSkippedTasks() {
+        assertHasResult()
+        result.skippedTasks
+    }
+    
+    protected List<String> getNonSkippedTasks() {
+        executedTasks - skippedTasks
+    }
+    
+    protected void executedAndNotSkipped(String... tasks) {
+        tasks.each {
+            assert it in executedTasks
+            assert !skippedTasks.contains(it)
+        }
+    }
+    
+    protected void failureHasCause(String cause) {
+        failure.assertHasCause(cause)
+    }
+    
+    private assertHasResult() {
+        assert result != null : "result is null, you haven't run succeeds()"
+    }
+
+    String getOutput() {
+        result.output
+    }
+
+    String getErrorOutput() {
+        result.error
+    }
+
+    ArtifactBuilder artifactBuilder() {
+        def executer = new InProcessGradleExecuter()
+        executer.withUserHomeDir(distribution.getUserHomeDir())
+        return new GradleBackedArtifactBuilder(executer, getTestDir().file("artifacts"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java
new file mode 100644
index 0000000..3fa108c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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 org.gradle.util.TestFileContext;
+import org.junit.Rule;
+
+import java.io.File;
+
+public abstract class AbstractIntegrationTest implements TestFileContext {
+    @Rule public final GradleDistribution distribution = new GradleDistribution();
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+
+    public TestFile getTestDir() {
+        return distribution.getTestDir();
+    }
+
+    public TestFile file(Object... path) {
+        return getTestDir().file(path);
+    }
+
+    public TestFile testFile(String name) {
+        return file(name);
+    }
+
+    protected GradleExecuter inTestDirectory() {
+        return inDirectory(getTestDir());
+    }
+
+    protected GradleExecuter inDirectory(File directory) {
+        return executer.inDirectory(directory);
+    }
+
+    protected GradleExecuter usingBuildFile(File file) {
+        return executer.usingBuildScript(file);
+    }
+
+    protected GradleExecuter usingProjectDir(File projectDir) {
+        return executer.usingProjectDirectory(projectDir);
+    }
+
+    protected ArtifactBuilder artifactBuilder() {
+        InProcessGradleExecuter gradleExecuter = new InProcessGradleExecuter();
+        gradleExecuter.withUserHomeDir(distribution.getUserHomeDir());
+        return new GradleBackedArtifactBuilder(gradleExecuter, getTestDir().file("artifacts"));
+    }
+
+    public MavenRepository maven(TestFile repo) {
+        return new MavenRepository(repo);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractMultiTestRunner.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractMultiTestRunner.java
new file mode 100755
index 0000000..be95f83
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractMultiTestRunner.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2012 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.junit.internal.runners.ErrorReportingRunner;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+
+import java.util.*;
+
+/**
+ * A base class for those test runners which execute a test multiple times.
+ */
+public abstract class AbstractMultiTestRunner extends Runner {
+    protected final Class<?> target;
+    private Description description;
+    private final List<Execution> executions = new ArrayList<Execution>();
+
+    protected AbstractMultiTestRunner(Class<?> target) {
+        this.target = target;
+    }
+
+    @Override
+    public Description getDescription() {
+        init();
+        return description;
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        init();
+        for (Execution execution : executions) {
+            execution.run(notifier);
+        }
+    }
+
+    private void init() {
+        if (description == null) {
+            createExecutions();
+            description = Description.createSuiteDescription(target);
+            for (Execution execution : executions) {
+                execution.init(target);
+                execution.addDescriptions(description);
+            }
+        }
+    }
+
+    protected abstract void createExecutions();
+
+    protected void add(Execution execution) {
+        executions.add(execution);
+    }
+
+    protected static abstract class Execution {
+        private Runner runner;
+        protected Class<?> target;
+        private final Map<Description, Description> descriptionTranslations = new HashMap<Description, Description>();
+
+        final void init(Class<?> target) {
+            this.target = target;
+            if (isEnabled()) {
+                List<? extends Class<?>> targetClasses = loadTargetClasses();
+                RunnerBuilder runnerBuilder = new RunnerBuilder() {
+                    @Override
+                    public Runner runnerForClass(Class<?> testClass) {
+                        try {
+                            for (Class<?> candidate = testClass; candidate != null; candidate = candidate.getSuperclass()) {
+                                RunWith runWith = candidate.getAnnotation(RunWith.class);
+                                if (runWith != null && !AbstractMultiTestRunner.class.isAssignableFrom(runWith.value())) {
+                                    try {
+                                        return (Runner)runWith.value().getConstructors()[0].newInstance(testClass);
+                                    } catch (Exception e) {
+                                        return new ErrorReportingRunner(testClass, e);
+                                    }
+                                }
+                            }
+                            return new BlockJUnit4ClassRunner(testClass);
+                        } catch (InitializationError initializationError) {
+                            return new ErrorReportingRunner(testClass, initializationError);
+                        }
+                    }
+                };
+                try {
+                    runner = new Suite(runnerBuilder, targetClasses.toArray(new Class<?>[targetClasses.size()]));
+                } catch (InitializationError initializationError) {
+                    runner = new ErrorReportingRunner(target, initializationError);
+                }
+            }
+        }
+
+        final void addDescriptions(Description parent) {
+            if (runner != null) {
+                map(runner.getDescription(), parent);
+            }
+        }
+
+        final void run(final RunNotifier notifier) {
+            if (runner == null) {
+                Description description = Description.createSuiteDescription(String.format("%s(%s)", getDisplayName(), target.getName()));
+                notifier.fireTestIgnored(description);
+                return;
+            }
+
+            RunNotifier nested = new RunNotifier();
+            nested.addListener(new RunListener() {
+                @Override
+                public void testStarted(Description description) {
+                    Description translated = descriptionTranslations.get(description);
+                    notifier.fireTestStarted(translated);
+                }
+
+                @Override
+                public void testFailure(Failure failure) {
+                    Description translated = descriptionTranslations.get(failure.getDescription());
+                    notifier.fireTestFailure(new Failure(translated, failure.getException()));
+                }
+
+                @Override
+                public void testAssumptionFailure(Failure failure) {
+                    Description translated = descriptionTranslations.get(failure.getDescription());
+                    notifier.fireTestAssumptionFailed(new Failure(translated, failure.getException()));
+                }
+
+                @Override
+                public void testIgnored(Description description) {
+                    Description translated = descriptionTranslations.get(description);
+                    notifier.fireTestIgnored(translated);
+                }
+
+                @Override
+                public void testFinished(Description description) {
+                    Description translated = descriptionTranslations.get(description);
+                    notifier.fireTestFinished(translated);
+                }
+            });
+
+            before();
+            try {
+                runner.run(nested);
+            } finally {
+                after();
+            }
+        }
+
+        protected void before() {
+        }
+
+        protected void after() {
+        }
+
+        private void map(Description source, Description parent) {
+            for (Description child : source.getChildren()) {
+                Description mappedChild;
+                if (child.getMethodName()!= null) {
+                    mappedChild = Description.createSuiteDescription(String.format("%s [%s](%s)", child.getMethodName(), getDisplayName(), child.getClassName()));
+                    parent.addChild(mappedChild);
+                } else {
+                    mappedChild = Description.createSuiteDescription(child.getClassName());
+                }
+                descriptionTranslations.put(child, mappedChild);
+                map(child, parent);
+            }
+        }
+
+        /**
+         * Returns a display name for this execution. Used in the Junit descriptions for test execution.
+         */
+        protected abstract String getDisplayName();
+
+        /**
+         * Returns true if this execution should be executed, false if it should be ignored. Default is true.
+         */
+        protected boolean isEnabled() {
+            return true;
+        }
+
+        /**
+         * Loads the target classes for this execution. Default is the target class that this runner was constructed with.
+         */
+        protected List<? extends Class<?>> loadTargetClasses() {
+            return Arrays.asList(target);
+        }
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
new file mode 100644
index 0000000..f6aab87
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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
+
+/**
+ * @author: Szczepan Faber, created at: 3/28/11
+ */
+class AutoTestedSamplesUtil {
+
+    String includes = '**/*.groovy **/*.java'
+
+    void findSamples(String dir, Closure runner) {
+        def sources = findDir(dir)
+        def ant = new AntBuilder()
+
+        def list = ant.fileScanner {
+            fileset(dir: sources, includes: includes)
+        }
+
+        list.each() { runSamplesFromFile(it, runner) }
+    }
+
+    String findDir(String dir) {
+        def workDir = System.getProperty("user.dir")
+        def candidates = [
+            "$workDir/$dir",        //when ran from IDEA
+            "$workDir/../../$dir"  //when ran from command line
+        ]
+        for (c in candidates) {
+            if (new File(c).exists()) {
+                return c
+            }
+        }
+        throw new RuntimeException("""Couldn't find the root folder :-( Please update the logic so that it detects the root folder correctly.
+I tried looking for a root folder here: $candidates
+""")
+    }
+
+    void runSamplesFromFile(File file, Closure runner) {
+        file.text.eachMatch(/(?ms).*?<pre autoTested.*?>(.*?)<\/pre>(.*?)/) {
+            def sample = it[1]
+            sample = sample.replaceAll(/(?m)^\s*?\*/, '')
+            try {
+                runner.call(file, sample)
+            } catch (Exception e) {
+                throw new RuntimeException("""
+*****
+Failed to execute sample:
+-File: $file
+-Sample:
+$sample
+-Problem: see the full stactrace below.
+*****
+""", e);
+            }
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java
new file mode 100644
index 0000000..215d3a5
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 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.internal.os.OperatingSystem;
+import org.gradle.util.GFileUtils;
+import org.gradle.internal.jvm.Jvm;
+
+import java.io.File;
+
+/**
+ * Allows the tests to get hold of an alternative Java installation when needed.
+ */
+abstract public class AvailableJavaHomes {
+
+    private static File getJavaHome(String label) {
+        String value = System.getenv().get(String.format("JDK_%s", label));
+        return value == null ? null : GFileUtils.canonicalise(new File(value));
+    }
+
+    public static File getBestAlternative() {
+        Jvm jvm = Jvm.current();
+
+        // Use environment variables
+        File javaHome = null;
+        if (jvm.isJava6Compatible()) {
+            javaHome = firstAvailable("15", "17");
+        } else if (jvm.isJava5Compatible()) {
+            javaHome = firstAvailable("16", "17");
+        }
+        if (javaHome != null) {
+            return javaHome;
+        }
+
+        if (OperatingSystem.current().isMacOsX()) {
+            File registeredJvms = new File("/Library/Java/JavaVirtualMachines");
+            if (registeredJvms.isDirectory()) {
+                for (File candidate : registeredJvms.listFiles()) {
+                    javaHome = GFileUtils.canonicalise(new File(candidate, "Contents/Home"));
+                    if (!javaHome.equals(jvm.getJavaHome()) && javaHome.isDirectory() && new File(javaHome, "bin/java").isFile()) {
+                        return javaHome;
+                    }
+                }
+            }
+        } else if (OperatingSystem.current().isLinux()) {
+            // Ubuntu specific
+            File installedJvms = new File("/usr/lib/jvm");
+            if (installedJvms.isDirectory()) {
+                for (File candidate : installedJvms.listFiles()) {
+                    javaHome = GFileUtils.canonicalise(candidate);
+                    if (!javaHome.equals(jvm.getJavaHome()) && javaHome.isDirectory() && new File(javaHome, "bin/java").isFile()) {
+                        return javaHome;
+                    }
+                }
+            }
+        } else if (OperatingSystem.current().isWindows()) {
+            //very simple algorithm trying to find java on windows
+            File installedJavas = new File("c:/Program Files/Java");
+            File[] files = installedJavas.listFiles();
+            for (File file : files) {
+                if (file.getName().startsWith("jdk")) {
+                    if (jvm.isJava6() && !file.getName().contains("1.6")) {
+                        return file;
+                    }
+                    if (jvm.isJava7() && !file.getName().contains("1.7")) {
+                        return file;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public static File firstAvailable(String... labels) {
+        for (String label : labels) {
+            File found = getJavaHome(label);
+            if (found != null) {
+                return found;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
new file mode 100644
index 0000000..7b1cc51
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/BasicGradleDistribution.java
@@ -0,0 +1,78 @@
+/*
+ * 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.integtests.fixtures;
+
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.jvm.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);
+
+    /**
+     * Returns true if this distribution supports the given Operating system.
+     */
+    boolean worksWith(OperatingSystem os);
+
+    /**
+     * Returns true if the daemon is supported by this distribution.
+     */
+    boolean isDaemonSupported();
+
+    /**
+     * Returns true if the configuring daemon idle timeout feature is supported by this distribution.
+     */
+    boolean isDaemonIdleTimeoutConfigurable();
+
+    /**
+     *
+     * Returns true if the tooling API is supported by this distribution.
+     */
+    boolean isToolingApiSupported();
+
+    /**
+     * Returns true if the open API is supported by this distribution.
+     */
+    boolean isOpenApiSupported();
+
+    /**
+     * Returns true if the wrapper from this distribution can execute a build using the specified version.
+     */
+    boolean wrapperCanExecute(String version);
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy
new file mode 100755
index 0000000..21c4b16
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionIntegrationSpec.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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 org.junit.Rule
+import org.junit.runner.RunWith
+import spock.lang.Specification
+
+ at RunWith(CrossVersionTestRunner)
+abstract class CrossVersionIntegrationSpec extends Specification {
+    @Rule public final GradleDistribution current = new GradleDistribution()
+    static BasicGradleDistribution previous
+
+    BasicGradleDistribution getPrevious() {
+        return previous
+    }
+
+    protected TestFile getBuildFile() {
+        testDir.file('build.gradle')
+    }
+
+    protected TestFile getTestDir() {
+        current.getTestDir();
+    }
+
+    protected TestFile file(Object... path) {
+        testDir.file(path);
+    }
+
+    def version(BasicGradleDistribution dist) {
+        def executer = dist.executer();
+        if (executer instanceof GradleDistributionExecuter) {
+            executer.withDeprecationChecksDisabled()
+        }
+        executer.inDirectory(testDir)
+        return executer;
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
new file mode 100755
index 0000000..4e3d911
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CrossVersionTestRunner.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.GradleVersion
+
+/**
+ * <p>Executes instances of {@link CrossVersionIntegrationSpec} against each previous Gradle version.
+ *
+ * <p>Sets the {@link CrossVersionIntegrationSpec#previous} property of the test instance before executing it.
+ *
+ * <p>A test class can be annotated with {@link TargetVersions} to specify the set of versions the test is compatible with.
+ */
+class CrossVersionTestRunner extends AbstractCompatibilityTestRunner {
+    CrossVersionTestRunner(Class<? extends CrossVersionIntegrationSpec> target) {
+        super(target)
+    }
+
+    @Override
+    protected void createExecutions() {
+        previous.each { add(new PreviousVersionExecution(it)) }
+    }
+
+    private static class PreviousVersionExecution extends AbstractMultiTestRunner.Execution {
+        final BasicGradleDistribution previousVersion
+
+        PreviousVersionExecution(BasicGradleDistribution previousVersion) {
+            this.previousVersion = previousVersion
+        }
+
+        @Override
+        String getDisplayName() {
+            return previousVersion.version
+        }
+
+        @Override
+        protected void before() {
+            target.previous = previousVersion
+        }
+
+        @Override
+        protected boolean isEnabled() {
+            TargetVersions targetGradleVersions = target.getAnnotation(TargetVersions)
+            if (!targetGradleVersions) {
+                return true
+            }
+            for (String targetGradleVersion: targetGradleVersions.value()) {
+                if (isMatching(targetGradleVersion, previousVersion.version)) {
+                    return true
+                }
+            }
+            return false
+        }
+        
+        private boolean isMatching(String targetGradleVersion, String candidate) {
+            if (targetGradleVersion.endsWith('+')) {
+                def minVersion = targetGradleVersion.substring(0, targetGradleVersion.length() - 1)
+                return GradleVersion.version(minVersion) <= GradleVersion.version(candidate)
+            }
+            return targetGradleVersion == candidate
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
new file mode 100644
index 0000000..e69ca9c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
@@ -0,0 +1,102 @@
+/*
+ * 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.apache.commons.collections.CollectionUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+public class 
+        DaemonGradleExecuter extends ForkingGradleExecuter {
+    private static final String DAEMON_REGISTRY_SYS_PROP = "org.gradle.integtest.daemon.registry";
+    private final GradleDistribution distribution;
+    private final File daemonBaseDir;
+    private final boolean allowExtraLogging;
+
+    public DaemonGradleExecuter(GradleDistribution distribution, File daemonBaseDir, boolean allowExtraLogging) {
+        super(distribution.getGradleHomeDir());
+        this.distribution = distribution;
+        this.daemonBaseDir = daemonBaseDir;
+        this.allowExtraLogging = allowExtraLogging;
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> originalArgs = super.getAllArgs();
+
+        List<String> args = new ArrayList<String>();
+        args.add("--daemon");
+
+        args.addAll(originalArgs);
+
+        String daemonRegistryBase = getDaemonRegistryBase();
+        if (daemonRegistryBase != null) {
+            args.add("-Dorg.gradle.daemon.registry.base=" + daemonRegistryBase);
+            configureJvmArgs(args, daemonRegistryBase);
+        } else {
+            configureJvmArgs(args, distribution.getUserHomeDir().getAbsolutePath());
+        }
+
+        if (!args.toString().contains("-Dorg.gradle.daemon.idletimeout=")) {
+            //isolated daemons/daemon with custom base dir cannot be connected again
+            //so they should have shorter timeout
+            boolean preferShortTimeout = distribution.isUsingIsolatedDaemons() || daemonBaseDir != null;
+            int timeout = preferShortTimeout? 20000 : 5 * 60 * 1000;
+            args.add("-Dorg.gradle.daemon.idletimeout=" + timeout);
+        }
+
+        configureDefaultLogging(args);
+
+        return args;
+    }
+
+    private void configureDefaultLogging(List<String> args) {
+        if(!allowExtraLogging) {
+            return;
+        }
+        List logOptions = asList("-i", "--info", "-d", "--debug", "-q", "--quite");
+        boolean alreadyConfigured = CollectionUtils.containsAny(args, logOptions);
+        if (!alreadyConfigured) {
+            args.add("-i");
+        }
+    }
+
+    private void configureJvmArgs(List<String> args, String registryBase) {
+        // TODO - clean this up. It's a workaround to provide some way for the client of this executer to
+        // specify that no jvm args should be provided
+        if(!args.remove("-Dorg.gradle.jvmargs=")){
+            args.add(0, "-Dorg.gradle.jvmargs=-ea -XX:MaxPermSize=256m"
+                    + " -XX:+HeapDumpOnOutOfMemoryError");
+        }
+    }
+
+    String getDaemonRegistryBase() {
+        if (daemonBaseDir != null) {
+            return daemonBaseDir.getAbsolutePath();
+        }
+
+        String customDaemonRegistryDir = System.getProperty(DAEMON_REGISTRY_SYS_PROP);
+        if (customDaemonRegistryDir != null && !distribution.isUsingIsolatedDaemons()) {
+            return customDaemonRegistryDir;
+        }
+
+        return null;
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
new file mode 100644
index 0000000..d438937
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 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.StartParameter;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.launcher.cli.ExecuteBuildAction;
+import org.gradle.launcher.daemon.client.DaemonClient;
+import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.internal.StreamBackedStandardOutputListener;
+
+import java.lang.management.ManagementFactory;
+
+public class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
+
+    private final EmbeddedDaemonClientServices daemonClientServices = new EmbeddedDaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), false);
+
+    public DaemonRegistry getDaemonRegistry() {
+        return daemonClientServices.get(DaemonRegistry.class);
+    }
+
+    protected ExecutionResult doRun() {
+        return doRun(false);
+    }
+
+    protected ExecutionFailure doRunWithFailure() {
+        return (ExecutionFailure)doRun(true);
+    }
+
+    protected ExecutionResult doRun(boolean expectFailure) {
+        StringBuilder output = new StringBuilder();
+        StringBuilder error = new StringBuilder();
+
+        LoggingManagerInternal loggingManager = createLoggingManager(output, error);
+        loggingManager.start();
+
+        ExecuteBuildAction buildAction = createBuildAction();
+        BuildActionParameters buildActionParameters = createBuildActionParameters();
+        DaemonClient daemonClient = daemonClientServices.get(DaemonClient.class);
+
+        Exception failure = null;
+        try {
+            daemonClient.execute(buildAction, buildActionParameters);
+        } catch (Exception e) {
+            failure = e;
+        } finally {
+            daemonClient.stop();
+            loggingManager.stop();
+        }
+
+        boolean didFail = failure != null;
+        if (expectFailure != didFail) {
+            String didOrDidntSnippet = didFail ? "DID fail" : "DID NOT fail";
+            throw new RuntimeException(String.format("Gradle execution in %s %s with: %nOutput:%n%s%nError:%n%s%n-----%n", getWorkingDir(), didOrDidntSnippet, output, error), failure);
+        }
+
+        if (expectFailure) {
+            return new OutputScrapingExecutionFailure(output.toString(), error.toString());
+        } else {
+            return new OutputScrapingExecutionResult(output.toString(), error.toString());
+        }
+    }
+
+    private LoggingManagerInternal createLoggingManager(StringBuilder output, StringBuilder error) {
+        LoggingManagerInternal loggingManager = daemonClientServices.getLoggingServices().newInstance(LoggingManagerInternal.class);
+        loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(output));
+        loggingManager.addStandardErrorListener(new StreamBackedStandardOutputListener(error));
+        return loggingManager;
+    }
+
+    private ExecuteBuildAction createBuildAction() {
+        DefaultCommandLineConverter commandLineConverter = new DefaultCommandLineConverter();
+        StartParameter startParameter = new StartParameter();
+        startParameter.setCurrentDir(getWorkingDir());
+        commandLineConverter.convert(getAllArgs(), startParameter);
+        return new ExecuteBuildAction(startParameter);
+    }
+
+    private BuildActionParameters createBuildActionParameters() {
+        return new DefaultBuildActionParameters(daemonClientServices.get(BuildClientMetaData.class), getStartTime(), System.getProperties(), getEnvironmentVars(), getWorkingDir());
+    }
+
+    private long getStartTime() {
+        return ManagementFactory.getRuntimeMXBean().getStartTime();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
new file mode 100644
index 0000000..9a41173
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
@@ -0,0 +1,60 @@
+/*
+ * 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.integtests.fixtures;
+
+import java.util.List;
+import java.util.Set;
+
+public interface ExecutionResult {
+    String getOutput();
+
+    String getError();
+
+    /**
+     * Returns the tasks have been executed in order (includes tasks that were skipped). Note: ignores buildSrc tasks.
+     */
+    List<String> getExecutedTasks();
+    
+    /**
+     * Asserts that exactly the given set of tasks have been executed in the given order. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTasksExecuted(String... taskPaths);
+
+    /**
+     * Returns the tasks that were skipped, in an undefined order. Note: ignores buildSrc tasks.
+     */
+    Set<String> getSkippedTasks();
+    
+    /**
+     * Asserts that exactly the given set of tasks have been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTasksSkipped(String... taskPaths);
+
+    /**
+     * Asserts the given task has been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTaskSkipped(String taskPath);
+
+    /**
+     * Asserts that exactly the given set of tasks have not been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTasksNotSkipped(String... taskPaths);
+
+    /**
+     * Asserts that the given task has not been skipped. Note: ignores buildSrc tasks.
+     */
+    ExecutionResult assertTaskNotSkipped(String taskPath);
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
new file mode 100644
index 0000000..aca014a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
@@ -0,0 +1,218 @@
+/*
+ * 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.StartParameter;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.SystemPropertiesCommandLineConverter;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.gradle.util.TestFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.fail;
+
+public class ForkingGradleExecuter extends AbstractGradleExecuter {
+    private static final Logger LOG = LoggerFactory.getLogger(ForkingGradleExecuter.class);
+    private final TestFile gradleHomeDir;
+
+    public ForkingGradleExecuter(TestFile gradleHomeDir) {
+        this.gradleHomeDir = gradleHomeDir;
+        gradleOpts.add("-ea");
+        //uncomment for debugging
+//        gradleOpts.add("-Xdebug");
+//        gradleOpts.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
+    }
+
+    public TestFile getGradleHomeDir() {
+        return gradleHomeDir;
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        File userHome = getUserHomeDir();
+        if (userHome == null) {
+            userHome = StartParameter.DEFAULT_GRADLE_USER_HOME;
+        }
+
+        DaemonParameters parameters = new DaemonParameters();
+        parameters.configureFromGradleUserHome(userHome);
+        parameters.configureFromSystemProperties(getSystemPropertiesFromArgs());
+        return new DaemonRegistryServices(parameters.getBaseDir()).get(DaemonRegistry.class);
+    }
+
+    protected Map<String, String> getSystemPropertiesFromArgs() {
+        SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter();
+        CommandLineParser commandLineParser = new CommandLineParser();
+        converter.configure(commandLineParser);
+        commandLineParser.allowUnknownOptions();
+        return converter.convert(commandLineParser.parse(getAllArgs()));
+    }
+
+    /**
+     * Adds some options to the GRADLE_OPTS environment variable to use.
+     */
+    public void addGradleOpts(String... opts) {
+        gradleOpts.addAll(Arrays.asList(opts));
+    }
+
+    @Override
+    protected List<String> getAllArgs() {
+        List<String> args = new ArrayList<String>();
+        args.addAll(super.getAllArgs());
+        args.add("--stacktrace");
+        return args;
+    }
+
+    private ExecHandleBuilder createExecHandleBuilder() {
+        if (!gradleHomeDir.isDirectory()) {
+            fail(gradleHomeDir + " is not a directory.\n"
+                    + "If you are running tests from IDE make sure that gradle tasks that prepare the test image were executed. Last time it was 'intTestImage' task.");
+        }
+
+        ExecHandleBuilder builder = new ExecHandleBuilder() {
+            @Override
+            public File getWorkingDir() {
+                // Override this, so that the working directory is not canonicalised. Some int tests require that
+                // the working directory is not canonicalised
+                return ForkingGradleExecuter.this.getWorkingDir();
+            }
+        };
+
+        // Override some of the user's environment
+        builder.environment("GRADLE_HOME", "");
+        builder.environment("JAVA_HOME", getJavaHome());
+        builder.environment("GRADLE_OPTS", formatGradleOpts());
+        builder.environment("JAVA_OPTS", "");
+
+        builder.environment(getAllEnvironmentVars());
+        builder.workingDir(getWorkingDir());
+        builder.setStandardInput(getStdin());
+
+        ExecHandlerConfigurer configurer = OperatingSystem.current().isWindows() ? new WindowsConfigurer()
+                : new UnixConfigurer();
+        configurer.configure(builder);
+
+        builder.args(getAllArgs());
+
+        LOG.info(String.format("Execute in %s with: %s %s", builder.getWorkingDir(), builder.getExecutable(),
+                builder.getArgs()));
+
+        return builder;
+    }
+
+    @Override
+    public GradleHandle doStart() {
+        return new ForkingGradleHandle(getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
+            public ExecHandleBuilder create() {
+                return createExecHandleBuilder();
+            }
+        }).start();
+    }
+
+    protected ExecutionResult doRun() {
+        return start().waitForFinish();
+    }
+
+    protected ExecutionFailure doRunWithFailure() {
+        return start().waitForFailure();
+    }
+
+    private String formatGradleOpts() {
+        StringBuilder result = new StringBuilder();
+        for (String gradleOpt : gradleOpts) {
+            if (result.length() > 0) {
+                result.append(" ");
+            }
+            if (gradleOpt.contains(" ")) {
+                assert !gradleOpt.contains("\"");
+                result.append('"');
+                result.append(gradleOpt);
+                result.append('"');
+            } else {
+                result.append(gradleOpt);
+            }
+        }
+        
+        result.append(" -Dfile.encoding=");
+        result.append(getDefaultCharacterEncoding());
+        result.append(" -Dorg.gradle.deprecation.trace=true");
+
+        return result.toString();
+    }
+
+    private interface ExecHandlerConfigurer {
+        void configure(ExecHandleBuilder builder);
+    }
+
+    private class WindowsConfigurer implements ExecHandlerConfigurer {
+        public void configure(ExecHandleBuilder builder) {
+            String cmd;
+            if (getExecutable() != null) {
+                cmd = getExecutable().replace('/', File.separatorChar);
+            } else {
+                cmd = "gradle";
+            }
+            builder.executable("cmd");
+            builder.args("/c", cmd);
+            String gradleHome = gradleHomeDir.getAbsolutePath();
+
+            // NOTE: Windows uses Path, but allows asking for PATH, and PATH
+            //       is set within builder object for some things such
+            //       as CommandLineIntegrationTest, try PATH first, and
+            //       then revert to default of Path if null
+            Object path = builder.getEnvironment().get("PATH");
+            if (path == null) {
+                path = builder.getEnvironment().get("Path");
+            }
+            builder.environment("Path", String.format("%s\\bin;%s",
+                                                      gradleHome,
+                                                      path));
+            builder.environment("GRADLE_EXIT_CONSOLE", "true");
+
+            LOG.info("Initializing windows process so that child process will be fully detached...");
+            new WindowsHandlesManipulator().uninheritStandardStreams();
+        }
+    }
+
+    private class UnixConfigurer implements ExecHandlerConfigurer {
+        public void configure(ExecHandleBuilder builder) {
+            if (getExecutable() != null) {
+                File exe = new File(getExecutable());
+                if (exe.isAbsolute()) {
+                    builder.executable(exe.getAbsolutePath());
+                } else {
+                    builder.executable(String.format("%s/%s", getWorkingDir().getAbsolutePath(), getExecutable()));
+                }
+            } else {
+                builder.executable(String.format("%s/bin/gradle", gradleHomeDir.getAbsolutePath()));
+            }
+        }
+    }
+
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
new file mode 100644
index 0000000..3b83010
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 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.apache.commons.io.output.CloseShieldOutputStream;
+import org.apache.commons.io.output.TeeOutputStream;
+import org.gradle.internal.Factory;
+import org.gradle.internal.UncheckedException;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.AbstractExecHandleBuilder;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleState;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+class ForkingGradleHandle extends OutputScrapingGradleHandle {
+    final private Factory<? extends AbstractExecHandleBuilder> execHandleFactory;
+
+    final private ByteArrayOutputStream standardOutput = new ByteArrayOutputStream();
+    final private ByteArrayOutputStream errorOutput = new ByteArrayOutputStream();
+
+    private ExecHandle execHandle;
+    private final String outputEncoding;
+
+    public ForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+        this.execHandleFactory = execHandleFactory;
+        this.outputEncoding = outputEncoding;
+    }
+
+    public String getStandardOutput() {
+        try {
+            return standardOutput.toString(outputEncoding);
+        } catch (UnsupportedEncodingException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public String getErrorOutput() {
+        try {
+            return errorOutput.toString(outputEncoding);
+        } catch (UnsupportedEncodingException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public GradleHandle start() {
+        if (execHandle != null) {
+            throw new IllegalStateException("you have already called start() on this handle");
+        }
+
+        AbstractExecHandleBuilder execBuilder = execHandleFactory.create();
+        execBuilder.setStandardOutput(new CloseShieldOutputStream(new TeeOutputStream(System.out, standardOutput)));
+        execBuilder.setErrorOutput(new CloseShieldOutputStream(new TeeOutputStream(System.err, errorOutput)));
+        execHandle = execBuilder.build();
+
+        execHandle.start();
+
+        return this;
+    }
+
+    public GradleHandle abort() {
+        getExecHandle().abort();
+        return this;
+    }
+
+    public boolean isRunning() {
+        return execHandle != null && execHandle.getState() == ExecHandleState.STARTED;
+    }
+
+    protected ExecHandle getExecHandle() {
+        if (execHandle == null) {
+            throw new IllegalStateException("you must call start() before calling this method");
+        }
+
+        return execHandle;
+    }
+
+    public ExecutionResult waitForFinish() {
+        return waitForStop(false);
+    }
+
+    public ExecutionFailure waitForFailure() {
+        return (ExecutionFailure)waitForStop(true);
+    }
+
+    protected ExecutionResult waitForStop(boolean expectFailure) {
+        ExecHandle execHandle = getExecHandle();
+        ExecResult execResult = execHandle.waitForFinish();
+        execResult.rethrowFailure(); // nop if all ok
+
+        String output = getStandardOutput();
+        String error = getErrorOutput();
+
+        boolean didFail = execResult.getExitValue() != 0;
+        if (didFail != expectFailure) {
+            String message = String.format("Gradle execution %s in %s with: %s %nOutput:%n%s%n-----%nError:%n%s%n-----%n",
+                    expectFailure ? "did not fail" : "failed", execHandle.getDirectory(), execHandle.getCommand(), output, error);
+            throw new RuntimeException(message);
+        }
+
+        return expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
new file mode 100644
index 0000000..7a93f1e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
@@ -0,0 +1,244 @@
+/*
+ * 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.integtests.fixtures;
+
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.util.*;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+
+/**
+ * Provides access to a Gradle distribution for integration testing.
+ */
+public class GradleDistribution implements MethodRule, TestFileContext, BasicGradleDistribution {
+    private static final TestFile USER_HOME_DIR;
+    private static final TestFile GRADLE_HOME_DIR;
+    private static final TestFile SAMPLES_DIR;
+    private static final TestFile USER_GUIDE_OUTPUT_DIR;
+    private static final TestFile USER_GUIDE_INFO_DIR;
+    private static final TestFile DISTS_DIR;
+    private static final TestFile LIBS_REPO;
+
+    private final TemporaryFolder temporaryFolder = new TemporaryFolder();
+    private TestFile userHome;
+    private boolean usingOwnUserHomeDir;
+    private boolean usingIsolatedDaemons;
+    private boolean avoidsConfiguringTmpDir;
+
+    static {
+        USER_HOME_DIR = file("integTest.gradleUserHomeDir", "intTestHomeDir").file("worker-1");
+        GRADLE_HOME_DIR = file("integTest.gradleHomeDir", null);
+        SAMPLES_DIR = file("integTest.samplesdir", "subprojects/docs/build/samples");
+        USER_GUIDE_OUTPUT_DIR = file("integTest.userGuideOutputDir",
+                "subprojects/docs/src/samples/userguideOutput");
+        USER_GUIDE_INFO_DIR = file("integTest.userGuideInfoDir", "subprojects/docs/build/src");
+        DISTS_DIR = file("integTest.distsDir", "build/distributions");
+        LIBS_REPO = file("integTest.libsRepo", "build/repo");
+    }
+
+    public GradleDistribution() {
+        this.userHome = USER_HOME_DIR;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Gradle %s", GradleVersion.current().getVersion());
+    }
+
+    public boolean worksWith(Jvm jvm) {
+        // Works with anything >= Java 5
+        return jvm.isJava5Compatible();
+    }
+
+    public boolean worksWith(OperatingSystem os) {
+        return true;
+    }
+
+    public boolean isDaemonSupported() {
+        return true;
+    }
+
+    public boolean isDaemonIdleTimeoutConfigurable() {
+        return true;
+    }
+
+    public boolean isOpenApiSupported() {
+        return true;
+    }
+
+    public boolean isToolingApiSupported() {
+        return true;
+    }
+
+    public boolean wrapperCanExecute(String version) {
+        // Current wrapper works with anything > 0.8
+        return GradleVersion.version(version).compareTo(GradleVersion.version("0.8")) > 0;
+    }
+
+    public boolean isUsingOwnUserHomeDir() {
+        return usingOwnUserHomeDir;
+    }
+
+    public void requireOwnUserHomeDir() {
+        usingOwnUserHomeDir = true;
+        userHome = getTestDir().file("user-home");
+    }
+
+    public boolean isUsingIsolatedDaemons() {
+        return usingIsolatedDaemons;
+    }
+
+    public void requireIsolatedDaemons() {
+        requireOwnUserHomeDir();
+        this.usingIsolatedDaemons = true;
+    }
+
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        return temporaryFolder.apply(base, method, target);
+    }
+
+    private static TestFile file(String propertyName, String defaultFile) {
+        String path = System.getProperty(propertyName, defaultFile);
+        if (path == null) {
+            throw new RuntimeException(String.format("You must set the '%s' property to run the integration tests. The default passed was: '%s'",
+                    propertyName, defaultFile));
+        }
+        return new TestFile(new File(path));
+    }
+
+    /**
+     * The user home dir used for the current test. This is usually shared with other tests unless
+     * {@link #requireOwnUserHomeDir()} is called.
+     */
+    public TestFile getUserHomeDir() {
+        return userHome;
+    }
+
+    /**
+     * The distribution for the current test. This is usually shared with other tests.
+     */
+    public TestFile getGradleHomeDir() {
+        return GRADLE_HOME_DIR;
+    }
+
+    public String getVersion() {
+        return GradleVersion.current().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.
+     */
+    public TestFile getSamplesDir() {
+        return SAMPLES_DIR;
+    }
+
+    public TestFile getUserGuideInfoDir() {
+        return USER_GUIDE_INFO_DIR;
+    }
+
+    public TestFile getUserGuideOutputDir() {
+        return USER_GUIDE_OUTPUT_DIR;
+    }
+
+    /**
+     * The directory containing the distribution Zips
+     */
+    public TestFile getDistributionsDir() {
+        return DISTS_DIR;
+    }
+
+    public TestFile getLibsRepo() {
+        return LIBS_REPO;
+    }
+
+    public TestFile getPreviousVersionsDir() {
+        return USER_HOME_DIR.getParentFile().file("previousVersion");
+    }
+
+    /**
+     * Returns true if the given file is either part of the distributions, samples, or test files.
+     */
+    public boolean isFileUnderTest(File file) {
+        return GRADLE_HOME_DIR.isSelfOrDescendent(file)
+                || SAMPLES_DIR.isSelfOrDescendent(file)
+                || getTestDir().isSelfOrDescendent(file)
+                || getUserHomeDir().isSelfOrDescendent(file);
+    }
+
+    /**
+     * Returns a scratch-pad directory for the current test. This directory is not shared with any other tests.
+     */
+    public TestFile getTestDir() {
+        return temporaryFolder.getDir();
+    }
+
+    public TemporaryFolder getTemporaryFolder() {
+        return temporaryFolder;
+    }
+
+    /**
+     * Returns a previous version of Gradle.
+     *
+     * @param version The Gradle version
+     * @return An executer
+     */
+    public BasicGradleDistribution previousVersion(String version) {
+        if (version.equals(this.getVersion())) {
+            return this;
+        }
+        return new PreviousGradleVersionExecuter(this, version);
+    }
+
+    public GradleDistributionExecuter executer() {
+        return new GradleDistributionExecuter(this);
+    }
+
+    /**
+     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
+     */
+    public TestFile file(Object... path) {
+        return getTestDir().file(path);
+    }
+
+    /**
+     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
+     */
+    public TestFile testFile(Object... path) {
+        return getTestDir().file(path);
+    }
+
+    /**
+     * avoids configuring -Djava.io.tmpdir=xxx property
+     */
+    public GradleDistribution avoidsConfiguringTmpDir() {
+        this.avoidsConfiguringTmpDir = true;
+        return this;
+    }
+
+    public boolean shouldAvoidConfiguringTmpDir() {
+        return avoidsConfiguringTmpDir;
+    }
+}
+
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
new file mode 100644
index 0000000..0f402d9
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
@@ -0,0 +1,263 @@
+/*
+ * 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.DeprecationLogger;
+import org.gradle.util.TestFile;
+import org.gradle.util.TextUtil;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.util.Matchers.containsLine;
+import static org.gradle.util.Matchers.matchesRegexp;
+
+/**
+ * A JUnit rule which provides a {@link GradleExecuter} implementation that executes Gradle using a given {@link
+ * 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.
+ */
+public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter implements MethodRule {
+    private static final String EXECUTER_SYS_PROP = "org.gradle.integtest.executer";
+
+    private GradleDistribution dist;
+    private boolean workingDirSet;
+    private boolean userHomeSet;
+    private boolean deprecationChecksOn = true;
+    private Executer executerType;
+    private File daemonBaseDir;
+    private boolean allowExtraLogging = true;
+
+    public enum Executer {
+        embedded(false),
+        forking(true),
+        daemon(true),
+        embeddedDaemon(false);
+
+        final public boolean forks;
+
+        Executer(boolean forks) {
+            this.forks = forks;
+        }
+    }
+
+    public static Executer getSystemPropertyExecuter() {
+        return Executer.valueOf(System.getProperty(EXECUTER_SYS_PROP, Executer.forking.toString()));
+    }
+
+    public GradleDistributionExecuter() {
+        this(getSystemPropertyExecuter());
+    }
+
+    public GradleDistributionExecuter(Executer executerType) {
+        this.executerType = executerType;
+    }
+
+    public GradleDistributionExecuter(GradleDistribution dist) {
+        this(getSystemPropertyExecuter(), dist);
+    }
+
+    public GradleDistributionExecuter(Executer executerType, GradleDistribution dist) {
+        this(executerType);
+        this.dist = dist;
+        reset();
+    }
+
+    public Executer getType() {
+        return executerType;
+    }
+
+    public Statement apply(Statement base, final FrameworkMethod method, Object target) {
+        if (dist == null) {
+            dist = RuleHelper.getField(target, GradleDistribution.class);
+        }
+        return base;
+    }
+
+    @Override
+    public GradleDistributionExecuter reset() {
+        super.reset();
+        workingDirSet = false;
+        userHomeSet = false;
+        deprecationChecksOn = true;
+        DeprecationLogger.reset();
+        return this;
+    }
+
+    @Override
+    public GradleDistributionExecuter inDirectory(File directory) {
+        super.inDirectory(directory);
+        workingDirSet = true;
+        return this;
+    }
+
+    @Override
+    public GradleDistributionExecuter withUserHomeDir(File userHomeDir) {
+        super.withUserHomeDir(userHomeDir);
+        userHomeSet = true;
+        return this;
+    }
+
+    public GradleDistributionExecuter withDaemonBaseDir(File daemonBaseDir) {
+        assert daemonBaseDir != null;
+        assert daemonBaseDir.isDirectory();
+        this.daemonBaseDir = daemonBaseDir;
+        return this;
+    }
+
+    public GradleDistributionExecuter withDeprecationChecksDisabled() {
+        deprecationChecksOn = false;
+        return this;
+    }
+    
+    public GradleDistributionExecuter withForkingExecuter() {
+        if (!executerType.forks) {
+            executerType = Executer.forking;
+        }
+        return this;
+    }
+
+    protected <T extends ExecutionResult> T checkResult(T result) {
+        // Assert that nothing unexpected was logged
+        assertOutputHasNoStackTraces(result);
+        assertErrorHasNoStackTraces(result);
+        if (deprecationChecksOn) {
+            assertOutputHasNoDeprecationWarnings(result);
+        }
+
+        if (getExecutable() == null) {
+            // Assert that no temp files are left lying around
+            // Note: don't do this if a custom executable is used, as we don't know (and probably don't care) whether the executable cleans up or not
+            List<String> unexpectedFiles = new ArrayList<String>();
+            for (File file : getTmpDir().listFiles()) {
+                if (!file.getName().matches("maven-artifact\\d+.tmp")) {
+                    unexpectedFiles.add(file.getName());
+                }
+            }
+//            Assert.assertThat(unexpectedFiles, Matchers.isEmpty());
+        }
+
+        return result;
+    }
+
+    private void assertOutputHasNoStackTraces(ExecutionResult result) {
+        assertNoStackTraces(result.getOutput(), "Standard output");
+    }
+
+    public void assertErrorHasNoStackTraces(ExecutionResult result) {
+        String error = result.getError();
+        if (result instanceof ExecutionFailure) {
+            // Axe everything after the expected exception
+            int pos = error.lastIndexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
+            if (pos >= 0) {
+                error = error.substring(0, pos);
+            }
+        }
+        assertNoStackTraces(error, "Standard error");
+    }
+
+    public void assertOutputHasNoDeprecationWarnings(ExecutionResult result) {
+        assertNoDeprecationWarnings(result.getOutput(), "Standard output");
+        assertNoDeprecationWarnings(result.getError(), "Standard error");
+    }
+
+    private void assertNoDeprecationWarnings(String output, String displayName) {
+        boolean javacWarning = containsLine(matchesRegexp(".*use(s)? or override(s)? a deprecated API\\.")).matches(output);
+        boolean deprecationWarning = containsLine(matchesRegexp(".* deprecated.*")).matches(output);
+        if (deprecationWarning && !javacWarning) {
+            throw new AssertionError(String.format("%s contains a deprecation warning:%n=====%n%s%n=====%n", displayName, output));
+        }
+    }
+
+    private void assertNoStackTraces(String output, String displayName) {
+        if (containsLine(matchesRegexp("\\s+at [\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
+            throw new AssertionError(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
+        }
+    }
+
+    /**
+     * set true to allow the executer to increase the log level if necessary
+     * to help out debugging. Set false to make the executer never update the log level.
+     */
+    public GradleDistributionExecuter setAllowExtraLogging(boolean allowExtraLogging) {
+        this.allowExtraLogging = allowExtraLogging;
+        return this;
+    }
+
+    protected GradleExecuter configureExecuter() {
+        if (!workingDirSet) {
+            inDirectory(dist.getTestDir());
+        }
+        if (!userHomeSet) {
+            withUserHomeDir(dist.getUserHomeDir());
+        }
+
+        if (!getClass().desiredAssertionStatus()) {
+            throw new RuntimeException("Assertions must be enabled when running integration tests.");
+        }
+
+        InProcessGradleExecuter inProcessGradleExecuter = new InProcessGradleExecuter();
+        copyTo(inProcessGradleExecuter);
+
+        GradleExecuter returnedExecuter = inProcessGradleExecuter;
+
+        TestFile tmpDir = getTmpDir();
+        tmpDir.deleteDir().createDir();
+
+        if (executerType.forks || !inProcessGradleExecuter.canExecute()) {
+            boolean useDaemon = executerType == Executer.daemon && getExecutable() == null;
+            ForkingGradleExecuter forkingGradleExecuter = useDaemon ? new DaemonGradleExecuter(dist, daemonBaseDir, !isQuiet() && allowExtraLogging) : new ForkingGradleExecuter(dist.getGradleHomeDir());
+            copyTo(forkingGradleExecuter);
+            if (!dist.shouldAvoidConfiguringTmpDir()) {
+                forkingGradleExecuter.addGradleOpts(String.format("-Djava.io.tmpdir=%s", tmpDir));
+            }
+            returnedExecuter = forkingGradleExecuter;
+//        } else {
+//            System.setProperty("java.io.tmpdir", tmpDir.getAbsolutePath());
+        }
+
+        if (executerType == Executer.embeddedDaemon) {
+            GradleExecuter embeddedDaemonExecutor = new EmbeddedDaemonGradleExecuter();
+            copyTo(embeddedDaemonExecutor);
+            returnedExecuter = embeddedDaemonExecutor;
+        }
+
+        boolean settingsFound = false;
+        for (
+                TestFile dir = new TestFile(getWorkingDir()); dir != null && dist.isFileUnderTest(dir) && !settingsFound;
+                dir = dir.getParentFile()) {
+            if (dir.file("settings.gradle").isFile()) {
+                settingsFound = true;
+            }
+        }
+        if (settingsFound) {
+            returnedExecuter.withSearchUpwards();
+        }
+
+        return returnedExecuter;
+    }
+
+    private TestFile getTmpDir() {
+        return dist.getTestDir().file("tmp");
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
new file mode 100644
index 0000000..56f606a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
@@ -0,0 +1,151 @@
+/*
+ * 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.integtests.fixtures;
+
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+public interface GradleExecuter {
+    /**
+     * Sets the working directory to use. Defaults to the test's temporary directory.
+     */
+    GradleExecuter inDirectory(File directory);
+
+    /**
+     * Enables search upwards. Defaults to false.
+     */
+    GradleExecuter withSearchUpwards();
+
+    /**
+     * Sets the task names to execute. Defaults to an empty list.
+     */
+    GradleExecuter withTasks(String... names);
+
+    /**
+     * Sets the task names to execute. Defaults to an empty list.
+     */
+    GradleExecuter withTasks(List<String> names);
+
+    GradleExecuter withTaskList();
+
+    GradleExecuter withDependencyList();
+
+    GradleExecuter withQuietLogging();
+
+    /**
+     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
+     */
+    GradleExecuter withArguments(String... args);
+
+    /**
+     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
+     */
+    GradleExecuter withArguments(List<String> args);
+
+    /**
+     * Sets the environment variables to use when executing the build. Defaults to the environment of this process.
+     */
+    GradleExecuter withEnvironmentVars(Map<String, ?> environment);
+
+    GradleExecuter usingSettingsFile(File settingsFile);
+
+    GradleExecuter usingInitScript(File initScript);
+
+    /**
+     * Uses the given project directory
+     */
+    GradleExecuter usingProjectDirectory(File projectDir);
+
+    /**
+     * Uses the given build script
+     */
+    GradleExecuter usingBuildScript(File buildScript);
+
+    /**
+     * Sets the user home dir. Set to null to use the default user home dir.
+     */
+    GradleExecuter withUserHomeDir(File userHomeDir);
+
+    /**
+     * Sets the java home dir. Set to null to use the default java home dir.
+     */
+    GradleExecuter withJavaHome(File userHomeDir);
+
+    /**
+     * Sets the executable to use. Set to null to use the default executable (if any)
+     */
+    GradleExecuter usingExecutable(String script);
+
+    /**
+     * Sets the stdin to use for the build. Defaults to an empty string.
+     */
+    GradleExecuter withStdIn(String text);
+
+    /**
+     * Sets the stdin to use for the build. Defaults to an empty string.
+     */
+    GradleExecuter withStdIn(InputStream stdin);
+
+    /**
+     * Executes the requested build, asserting that the build succeeds. Resets the configuration of this executer.
+     *
+     * @return The result.
+     */
+    ExecutionResult run();
+
+    /**
+     * Executes the requested build, asserting that the build fails. Resets the configuration of this executer.
+     *
+     * @return The result.
+     */
+    ExecutionFailure runWithFailure();
+
+    /**
+     * Provides a daemon registry for any daemons started by this executer, which may be none.
+     *
+     * @return the daemon registry, never null.
+     */
+    DaemonRegistry getDaemonRegistry();
+
+    /**
+     * Starts executing the build asynchronously.
+     *
+     * @return the handle, never null.
+     */
+    GradleHandle start();
+
+    /**
+     * Only makes sense for the forking executor or foreground daemon.
+     *
+     * @param gradleOpts the jvm opts
+     *
+     * @return this executer
+     */
+    GradleExecuter withGradleOpts(String ... gradleOpts);
+
+    /**
+     * Sets the default character encoding to use.
+     *
+     * Only makes sense for forking executers.
+     *
+     * @return this executer
+     */
+    GradleExecuter withDefaultCharacterEncoding(String defaultCharacterEncoding);
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleHandle.java
new file mode 100644
index 0000000..9fa1d26
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleHandle.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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;
+
+public interface GradleHandle {
+    String getStandardOutput();
+    String getErrorOutput();
+
+    GradleHandle abort();
+
+    ExecutionResult waitForFinish();
+    ExecutionFailure waitForFailure();
+
+    boolean isRunning();
+
+    // ExecutionResult waitForFinish(double secondsToWait);
+    // ExecutionFailure waitForFailure(double secondsToWait);
+
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
new file mode 100755
index 0000000..5963f52
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
@@ -0,0 +1,437 @@
+/*
+ * 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.integtests.fixtures
+
+import java.security.Principal
+import java.util.zip.GZIPOutputStream
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+import org.gradle.util.hash.HashUtil
+import org.junit.rules.ExternalResource
+import org.mortbay.jetty.handler.AbstractHandler
+import org.mortbay.jetty.handler.HandlerCollection
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.mortbay.jetty.*
+import org.mortbay.jetty.security.*
+
+class HttpServer extends ExternalResource {
+
+    private static Logger logger = LoggerFactory.getLogger(HttpServer.class)
+
+    private final Server server = new Server(0)
+    private final HandlerCollection collection = new HandlerCollection()
+    private Throwable failure
+    private TestUserRealm realm
+    
+    enum EtagStrategy {
+        NONE({ null }),
+        RAW_SHA1_HEX({ HashUtil.sha1(it as byte[]).asHexString() }),
+        NEXUS_ENCODED_SHA1({ "{SHA1{" + HashUtil.sha1(it as byte[]).asHexString() + "}}" })
+        
+        private final Closure generator
+        
+        EtagStrategy(Closure generator) {
+            this.generator = generator
+        }
+        
+        String generate(byte[] bytes) {
+            generator.call(bytes)                        
+        }
+    }
+
+    // Can be an EtagStrategy, or a closure that receives a byte[] and returns an etag string, or anything that gets toString'd
+    def etags = EtagStrategy.NONE
+
+    boolean sendLastModified = true
+    boolean sendSha1Header = false
+
+    HttpServer() {
+        HandlerCollection handlers = new HandlerCollection()
+        handlers.addHandler(new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                println("handling http request: $request.method $target")
+            }
+        })
+        handlers.addHandler(collection)
+        handlers.addHandler(new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                if (request.handled) {
+                    return
+                }
+                failure = new AssertionError("Received unexpected ${request.method} request to ${target}.")
+                logger.error(failure.message)
+                response.sendError(404, "'$target' does not exist")
+            }
+        })
+        server.setHandler(handlers)
+    }
+
+    void start() {
+        server.start()
+    }
+
+    void stop() {
+        server?.stop()
+    }
+
+    void resetExpectations() {
+        if (failure != null) {
+            throw failure
+        }
+        collection.setHandlers()
+    }
+
+    @Override
+    protected void after() {
+        stop()
+    }
+
+    /**
+     * Adds a given file at the given URL. The source file can be either a file or a directory.
+     */
+    void allowGet(String path, File srcFile) {
+        allow(path, true, ['GET', 'HEAD'], fileHandler(path, srcFile))
+    }
+
+    private AbstractHandler fileHandler(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
+        return new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                def file
+                if (request.pathInfo == path) {
+                    file = srcFile
+                } else {
+                    def relativePath = request.pathInfo.substring(path.length() + 1)
+                    file = new File(srcFile, relativePath)
+                }
+                if (file.isFile()) {
+                    sendFile(response, file, lastModified, contentLength)
+                } else if (file.isDirectory()) {
+                    sendDirectoryListing(response, file)
+                } else {
+                    response.sendError(404, "'$target' does not exist")
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds a broken resource at the given URL.
+     */
+    void addBroken(String path) {
+        allow(path, true, null, new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                response.sendError(500, "broken")
+            }
+        })
+    }
+
+    /**
+     * Allows one GET request for the given URL, which return 404 status code
+     */
+    void expectGetMissing(String path) {
+        expect(path, false, ['GET'], new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                response.sendError(404, "not found")
+            }
+        })
+    }
+
+    /**
+     * Allows one HEAD request for the given URL, which return 404 status code
+     */
+    void expectHeadMissing(String path) {
+        expect(path, false, ['HEAD'], new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                response.sendError(404, "not found")
+            }
+        })
+    }
+
+    /**
+     * Allows one HEAD request for the given URL.
+     */
+    void expectHead(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
+        expect(path, false, ['HEAD'], fileHandler(path, srcFile, lastModified, contentLength))
+    }
+
+    /**
+     * Allows one GET request for the given URL. Reads the request content from the given file.
+     */
+    void expectGet(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
+        expect(path, false, ['GET'], fileHandler(path, srcFile, lastModified, contentLength))
+    }
+
+    /**
+     * Allows one HEAD request, then one GET request for the given URL. Reads the request content from the given file.
+     */
+    void expectHeadThenGet(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
+        expectHead(path, srcFile, lastModified, contentLength)
+        expectGet(path, srcFile, lastModified, contentLength)
+    }
+
+    /**
+     * Allows one GET request for the given URL, with the given credentials. Reads the request content from the given file.
+     */
+    void expectGet(String path, String username, String password, File srcFile) {
+        expect(path, false, ['GET'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
+    }
+
+    /**
+     * Allows one GET request for the given URL, with the response being GZip encoded.
+     */
+    void expectGetGZipped(String path, File srcFile) {
+        expect(path, false, ['GET'], new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                def file = srcFile
+                if (file.isFile()) {
+                    response.setHeader("Content-Encoding", "gzip")
+                    response.setDateHeader(HttpHeaders.LAST_MODIFIED, srcFile.lastModified())
+                    def stream = new GZIPOutputStream(response.outputStream)
+                    stream.write(file.bytes)
+                    stream.close()
+                } else {
+                    response.sendError(404, "'$target' does not exist")
+                }
+            }
+        });
+    }
+
+    /**
+     * Allow one GET request for the given URL, responding with a redirect.
+     */
+    void expectGetRedirected(String path, String location) {
+        expectRedirected('GET', path, location)
+    }
+
+    /**
+     * Allow one HEAD request for the given URL, responding with a redirect.
+     */
+    void expectHeadRedirected(String path, String location) {
+        expectRedirected('HEAD', path, location)
+    }
+
+    private void expectRedirected(String method, String path, String location) {
+        expect(path, false, [method], new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                response.sendRedirect(location)
+            }
+        })
+    }
+
+    /**
+     * Allows one GET request for the given URL, returning an apache-compatible directory listing with the given File names.
+     */
+    void expectGetDirectoryListing(String path, File directory) {
+        expect(path, false, ['GET'], new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                sendDirectoryListing(response, directory)
+            }
+        })
+    }
+
+    private sendFile(HttpServletResponse response, File file, Long lastModified, Long contentLength) {
+        if (sendLastModified) {
+            response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified ?: file.lastModified())
+        }
+        response.setContentLength((contentLength ?: file.length()) as int)
+        response.setContentType(new MimeTypes().getMimeByExtension(file.name).toString())
+        if (sendSha1Header) {
+            response.addHeader("X-Checksum-Sha1", HashUtil.sha1(file).asHexString())
+        }
+        addEtag(response, file.bytes, etags)
+        response.outputStream << new FileInputStream(file)
+    }
+
+    private addEtag(HttpServletResponse response, byte[] bytes, etagStrategy) {
+        if (etagStrategy != null) {
+            String value
+            if (etags instanceof EtagStrategy) {
+                value = etags.generate(bytes)
+            } else if (etagStrategy instanceof Closure) {
+                value = etagStrategy.call(bytes)
+            } else {
+                value = etagStrategy.toString()
+            }
+
+            if (value != null) {
+                response.addHeader(HttpHeaders.ETAG, value)
+            }
+        }
+    }
+
+    private sendDirectoryListing(HttpServletResponse response, File directory) {
+        def directoryListing = ""
+        for (String fileName: directory.list()) {
+            directoryListing += "<a href=\"$fileName\">$fileName</a>"
+        }
+
+        response.setContentLength(directoryListing.length())
+        response.setContentType("text/plain")
+        response.outputStream.bytes = directoryListing.bytes
+    }
+
+    /**
+     * Allows one PUT request for the given URL. Writes the request content to the given file.
+     */
+    void expectPut(String path, File destFile, int statusCode = HttpStatus.ORDINAL_200_OK) {
+        expect(path, false, ['PUT'], new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                destFile.bytes = request.inputStream.bytes
+                response.setStatus(statusCode)
+            }
+        })
+    }
+
+    /**
+     * Allows one PUT request for the given URL, with the given credentials. Writes the request content to the given file.
+     */
+    void expectPut(String path, String username, String password, File destFile) {
+        expect(path, false, ['PUT'], withAuthentication(path, username, password, new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                if (request.remoteUser != username) {
+                    response.sendError(500, "unexpected username '${request.remoteUser}'")
+                    return
+                }
+                destFile.bytes = request.inputStream.bytes
+            }
+        }))
+    }
+
+    private Handler withAuthentication(String path, String username, String password, Handler handler) {
+        if (realm != null) {
+            assert realm.username == username
+            assert realm.password == password
+        } else {
+            realm = new TestUserRealm()
+            realm.username = username
+            realm.password = password
+            def constraint = new Constraint()
+            constraint.name = Constraint.__BASIC_AUTH
+            constraint.authenticate = true
+            constraint.roles = ['*'] as String[]
+            def constraintMapping = new ConstraintMapping()
+            constraintMapping.pathSpec = path
+            constraintMapping.constraint = constraint
+            def securityHandler = new SecurityHandler()
+            securityHandler.userRealm = realm
+            securityHandler.constraintMappings = [constraintMapping] as ConstraintMapping[]
+            securityHandler.authenticator = new BasicAuthenticator()
+            collection.addHandler(securityHandler)
+        }
+
+        return new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                if (request.remoteUser != username) {
+                    response.sendError(500, "unexpected username '${request.remoteUser}'")
+                    return
+                }
+                handler.handle(target, request, response, dispatch)
+            }
+        }
+    }
+
+    private void expect(String path, boolean recursive, Collection<String> methods, Handler handler) {
+        boolean run
+        add(path, recursive, methods, new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                if (run) {
+                    return
+                }
+                run = true
+                handler.handle(target, request, response, dispatch)
+                request.handled = true
+            }
+        })
+    }
+
+    private void allow(String path, boolean recursive, Collection<String> methods, Handler handler) {
+        add(path, recursive, methods, new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                handler.handle(target, request, response, dispatch)
+                request.handled = true
+            }
+        })
+    }
+
+    private void add(String path, boolean recursive, Collection<String> methods, Handler handler) {
+        assert path.startsWith('/')
+//        assert path == '/' || !path.endsWith('/')
+        def prefix = path == '/' ? '/' : path + '/'
+        collection.addHandler(new AbstractHandler() {
+            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+                if (methods != null && !methods.contains(request.method)) {
+                    return
+                }
+                boolean match = request.pathInfo == path || (recursive && request.pathInfo.startsWith(prefix))
+                if (match && !request.handled) {
+                    handler.handle(target, request, response, dispatch)
+                }
+            }
+        })
+    }
+
+    int getPort() {
+        return server.connectors[0].localPort
+    }
+
+    static class TestUserRealm implements UserRealm {
+        String username
+        String password
+
+        Principal authenticate(String username, Object credentials, Request request) {
+            if (username == this.username && password == credentials) {
+                return getPrincipal(username)
+            }
+            return null
+        }
+
+        String getName() {
+            return "test"
+        }
+
+        Principal getPrincipal(String username) {
+            return new Principal() {
+                String getName() {
+                    return username
+                }
+            }
+        }
+
+        boolean reauthenticate(Principal user) {
+            return false
+        }
+
+        boolean isUserInRole(Principal user, String role) {
+            return false
+        }
+
+        void disassociate(Principal user) {
+        }
+
+        Principal pushRole(Principal user, String role) {
+            return user
+        }
+
+        Principal popRole(Principal user) {
+            return user
+        }
+
+        void logout(Principal user) {
+        }
+
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
new file mode 100644
index 0000000..1623c14
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
@@ -0,0 +1,381 @@
+/*
+ * 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 junit.framework.AssertionFailedError;
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.internal.LocationAwareException;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.internal.Factory;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.launcher.Main;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.util.DeprecationLogger;
+import org.hamcrest.Matcher;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.util.*;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class InProcessGradleExecuter extends AbstractGradleExecuter {
+    private final ProcessEnvironment processEnvironment = new NativeServices().get(ProcessEnvironment.class);
+
+    @Override
+    protected ExecutionResult doRun() {
+        OutputListenerImpl outputListener = new OutputListenerImpl();
+        OutputListenerImpl errorListener = new OutputListenerImpl();
+        BuildListenerImpl buildListener = new BuildListenerImpl();
+        BuildResult result = doRun(outputListener, errorListener, buildListener);
+        result.rethrowFailure();
+        return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
+                outputListener.toString(), errorListener.toString());
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        OutputListenerImpl outputListener = new OutputListenerImpl();
+        OutputListenerImpl errorListener = new OutputListenerImpl();
+        BuildListenerImpl buildListener = new BuildListenerImpl();
+        try {
+            doRun(outputListener, errorListener, buildListener).rethrowFailure();
+            throw new AssertionFailedError("expected build to fail but it did not.");
+        } catch (GradleException e) {
+            return new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
+                    outputListener.writer.toString(), errorListener.writer.toString(), e);
+        }
+    }
+
+    @Override
+    protected GradleHandle doStart() {
+        return new ForkingGradleHandle(getDefaultCharacterEncoding(), new Factory<JavaExecHandleBuilder>() {
+            public JavaExecHandleBuilder create() {
+                JavaExecHandleBuilder builder = new JavaExecHandleBuilder(new IdentityFileResolver());
+                builder.workingDir(getWorkingDir());
+                Set<File> classpath = new DefaultModuleRegistry().getFullClasspath();
+                builder.classpath(classpath);
+                builder.setMain(Main.class.getName());
+                builder.args(getAllArgs());
+                return builder;
+            }
+        }).start();
+    }
+
+    private BuildResult doRun(final OutputListenerImpl outputListener, OutputListenerImpl errorListener,
+                              BuildListenerImpl listener) {
+        assertCanExecute();
+
+        InputStream originalStdIn = System.in;
+        System.setIn(getStdin());
+        
+        File userDir = new File(System.getProperty("user.dir"));
+        StartParameter parameter = new StartParameter();
+        parameter.setLogLevel(LogLevel.INFO);
+        parameter.setSearchUpwards(true);
+        parameter.setCurrentDir(getWorkingDir());
+
+        CommandLineParser parser = new CommandLineParser();
+        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
+        converter.configure(parser);
+        converter.convert(parser.parse(getAllArgs()), parameter);
+
+        Properties originalSysProperties = new Properties();
+        originalSysProperties.putAll(System.getProperties());
+        processEnvironment.maybeSetProcessDir(getWorkingDir());
+        Map<String, String> previousEnv = new HashMap<String, String>();
+        for (Map.Entry<String, String> entry : getEnvironmentVars().entrySet()) {
+            previousEnv.put(entry.getKey(), System.getenv(entry.getKey()));
+            processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), entry.getValue());
+        }
+
+        DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
+        factory.addListener(listener);
+        GradleLauncher gradleLauncher = GradleLauncher.newInstance(parameter);
+        gradleLauncher.addStandardOutputListener(outputListener);
+        gradleLauncher.addStandardErrorListener(errorListener);
+        try {
+            return gradleLauncher.run();
+        } finally {
+            System.setProperties(originalSysProperties);
+            processEnvironment.maybeSetProcessDir(userDir);
+            for (Map.Entry<String, String> entry : previousEnv.entrySet()) {
+                String oldValue = entry.getValue();
+                if (oldValue != null) {
+                    processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), oldValue);
+                } else {
+                    processEnvironment.maybeRemoveEnvironmentVariable(entry.getKey());
+                }
+            }
+            factory.removeListener(listener);
+            System.setIn(originalStdIn);
+        }
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void assertCanExecute() {
+        assertNull(getExecutable());
+        assertEquals(getJavaHome(), Jvm.current().getJavaHome());
+        assertEquals(getDefaultCharacterEncoding(), Charset.defaultCharset().name());
+    }
+
+    public boolean canExecute() {
+        try {
+            assertCanExecute();
+        } catch (AssertionError e) {
+            return false;
+        }
+        return true;
+    }
+
+    private static class BuildListenerImpl implements TaskExecutionGraphListener, BuildListener {
+        private final List<String> executedTasks = new ArrayList<String>();
+        private final Set<String> skippedTasks = new HashSet<String>();
+
+        public void graphPopulated(TaskExecutionGraph graph) {
+            List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
+            graph.addTaskExecutionListener(new TaskListenerImpl(planned, executedTasks, skippedTasks));
+        }
+
+        public void buildStarted(Gradle gradle) {
+            DeprecationLogger.setLogTrace(true);
+        }
+
+        public void settingsEvaluated(Settings settings) {
+
+        }
+
+        public void projectsLoaded(Gradle gradle) {
+
+        }
+
+        public void projectsEvaluated(Gradle gradle) {
+
+        }
+
+        public void buildFinished(BuildResult result) {
+            DeprecationLogger.setLogTrace(false);
+        }
+    }
+
+    private static class OutputListenerImpl implements StandardOutputListener {
+        private StringWriter writer = new StringWriter();
+
+        @Override
+        public String toString() {
+            return writer.toString();
+        }
+
+        public void onOutput(CharSequence output) {
+            writer.append(output);
+        }
+    }
+
+    private static class TaskListenerImpl implements TaskExecutionListener {
+        private final List<Task> planned;
+        private final List<String> executedTasks;
+        private final Set<String> skippedTasks;
+        private Task current;
+
+        public TaskListenerImpl(List<Task> planned, List<String> executedTasks, Set<String> skippedTasks) {
+            this.planned = planned;
+            this.executedTasks = executedTasks;
+            this.skippedTasks = skippedTasks;
+        }
+
+        public void beforeExecute(Task task) {
+            assertThat(current, nullValue());
+            assertTrue(planned.contains(task));
+            current = task;
+
+            String taskPath = path(task);
+            if (taskPath.startsWith(":buildSrc:")) {
+                return;
+            }
+
+            executedTasks.add(taskPath);
+        }
+
+        public void afterExecute(Task task, TaskState state) {
+            assertThat(task, sameInstance(current));
+            current = null;
+
+            String taskPath = path(task);
+            if (taskPath.startsWith(":buildSrc:")) {
+                return;
+            }
+
+            if (state.getSkipped()) {
+                skippedTasks.add(taskPath);
+            }
+        }
+
+        private String path(Task task) {
+            return task.getProject().getGradle().getParent() == null ? task.getPath() : ":" + task.getProject().getRootProject().getName() + task.getPath();
+        }
+    }
+
+    public static class InProcessExecutionResult implements ExecutionResult {
+        private final List<String> plannedTasks;
+        private final Set<String> skippedTasks;
+        private final String output;
+        private final String error;
+
+        public InProcessExecutionResult(List<String> plannedTasks, Set<String> skippedTasks, String output,
+                                        String error) {
+            this.plannedTasks = plannedTasks;
+            this.skippedTasks = skippedTasks;
+            this.output = output;
+            this.error = error;
+        }
+
+        public String getOutput() {
+            return output;
+        }
+
+        public String getError() {
+            return error;
+        }
+
+        public List<String> getExecutedTasks() {
+            return new ArrayList<String>(plannedTasks);
+        }
+
+        public ExecutionResult assertTasksExecuted(String... taskPaths) {
+            List<String> expected = Arrays.asList(taskPaths);
+            assertThat(plannedTasks, equalTo(expected));
+            return this;
+        }
+
+        public Set<String> getSkippedTasks() {
+            return new HashSet<String>(skippedTasks);
+        }
+
+        public ExecutionResult assertTasksSkipped(String... taskPaths) {
+            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
+            assertThat(skippedTasks, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertTaskSkipped(String taskPath) {
+            assertThat(skippedTasks, hasItem(taskPath));
+            return this;
+        }
+
+        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
+            Set<String> expected = new HashSet<String>(Arrays.asList(taskPaths));
+            Set<String> notSkipped = getNotSkippedTasks();
+            assertThat(notSkipped, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertTaskNotSkipped(String taskPath) {
+            assertThat(getNotSkippedTasks(), hasItem(taskPath));
+            return this;
+        }
+
+        private Set<String> getNotSkippedTasks() {
+            Set<String> notSkipped = new HashSet<String>(plannedTasks);
+            notSkipped.removeAll(skippedTasks);
+            return notSkipped;
+        }
+    }
+
+    private static class InProcessExecutionFailure extends InProcessExecutionResult implements ExecutionFailure {
+        private final GradleException failure;
+
+        public InProcessExecutionFailure(List<String> tasks, Set<String> skippedTasks, String output, String error,
+                                         GradleException failure) {
+            super(tasks, skippedTasks, output, error);
+            this.failure = failure;
+        }
+
+        public ExecutionFailure assertHasLineNumber(int lineNumber) {
+            assertThat(failure.getMessage(), containsString(String.format(" line: %d", lineNumber)));
+            return this;
+
+        }
+
+        public ExecutionFailure assertHasFileName(String filename) {
+            assertThat(failure.getMessage(), startsWith(String.format("%s", filename)));
+            return this;
+        }
+
+        public ExecutionFailure assertHasCause(String description) {
+            assertThatCause(startsWith(description));
+            return this;
+        }
+
+        public ExecutionFailure assertThatCause(final Matcher<String> matcher) {
+            if (failure instanceof LocationAwareException) {
+                LocationAwareException exception = (LocationAwareException) failure;
+                assertThat(exception.getReportableCauses(), hasItem(hasMessage(matcher)));
+            } else {
+                assertThat(failure.getCause(), notNullValue());
+                assertThat(failure.getCause().getMessage(), matcher);
+            }
+            return this;
+        }
+
+        public ExecutionFailure assertHasNoCause() {
+            if (failure instanceof LocationAwareException) {
+                LocationAwareException exception = (LocationAwareException) failure;
+                assertThat(exception.getReportableCauses(), isEmpty());
+            } else {
+                assertThat(failure.getCause(), nullValue());
+            }
+            return this;
+        }
+
+        public ExecutionFailure assertHasDescription(String context) {
+            assertThatDescription(startsWith(context));
+            return this;
+        }
+
+        public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
+            assertThat(failure.getMessage(), containsLine(matcher));
+            return this;
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java
new file mode 100644
index 0000000..8ce0ca2
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IntegrationTestHint.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * @author Szczepan Faber, @date: 25.03.11
+ */
+public class IntegrationTestHint extends RuntimeException {
+
+    public IntegrationTestHint(Throwable cause) {
+        super("****\n"
++"This test is one of the integration tests that requires specific tasks to be ran first.\n"
++"Please run: gradle binZip intTestImage publishLocalArchives\n"
++"If the problem persists after running tasks then it probably means it's a genuine test failure.\n"
++"If the task list above is out-of-date please update it.\n"
++"****\n", cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
new file mode 100644
index 0000000..d526699
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2011 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 java.util.regex.Pattern
+import junit.framework.AssertionFailedError
+
+import org.gradle.util.TestFile
+import org.gradle.util.hash.HashUtil
+
+class IvyRepository {
+    final TestFile rootDir
+
+    IvyRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    IvyModule module(String organisation, String module, Object revision = '1.0') {
+        def moduleDir = rootDir.file("$organisation/$module/$revision")
+        return new IvyModule(moduleDir, organisation, module, revision as String)
+    }
+}
+
+class IvyModule {
+    final TestFile moduleDir
+    final String organisation
+    final String module
+    final String revision
+    final List dependencies = []
+    final Map<String, Map> configurations = [:]
+    final List artifacts = []
+    String status = "integration"
+    int publishCount
+
+    IvyModule(TestFile moduleDir, String organisation, String module, String revision) {
+        this.moduleDir = moduleDir
+        this.organisation = organisation
+        this.module = module
+        this.revision = revision
+        artifact([:])
+        configurations['runtime'] = [extendsFrom: [], transitive: true]
+        configurations['default'] = [extendsFrom: ['runtime'], transitive: true]
+    }
+
+    /**
+     * Adds an additional artifact to this module.
+     * @param options Can specify any of name, type or classifier
+     * @return this
+     */
+    IvyModule artifact(Map<String, ?> options) {
+        artifacts << [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
+        return this
+    }
+
+    IvyModule dependsOn(String organisation, String module, String revision) {
+        dependencies << [organisation: organisation, module: module, revision: revision]
+        return this
+    }
+
+    IvyModule nonTransitive(String config) {
+        configurations[config].transitive = false
+        return this
+    }
+
+    IvyModule withStatus(String status) {
+        this.status = status;
+        return this
+    }
+
+    File getIvyFile() {
+        return moduleDir.file("ivy-${revision}.xml")
+    }
+
+    TestFile getJarFile() {
+        return moduleDir.file("$module-${revision}.jar")
+    }
+
+    /**
+     * Publishes ivy.xml plus all artifacts with different content to previous publication.
+     */
+    IvyModule publishWithChangedContent() {
+        publishCount++
+        publish()
+    }
+
+    /**
+     * Publishes ivy.xml plus all artifacts
+     */
+    IvyModule publish() {
+        moduleDir.createDir()
+
+        ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
+	<info organisation="${organisation}"
+		module="${module}"
+		revision="${revision}"
+		status="${status}"
+	/>
+	<configurations>"""
+        configurations.each { name, config ->
+            ivyFile << "<conf name='$name' visibility='public'"
+            if (config.extendsFrom) {
+                ivyFile << " extends='${config.extendsFrom.join(',')}'"
+            }
+            if (!config.transitive) {
+                ivyFile << " transitive='false'"
+            }
+            ivyFile << "/>"
+        }
+	ivyFile << """</configurations>
+	<publications>
+"""
+        artifacts.each { artifact ->
+            file(artifact) << "add some content so that file size isn't zero: $publishCount"
+            ivyFile << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.type}" conf="*" m:classifier="${artifact.classifier ?: ''}"/>
+"""
+        }
+        ivyFile << """
+	</publications>
+	<dependencies>
+"""
+        dependencies.each { dep ->
+            ivyFile << """<dependency org="${dep.organisation}" name="${dep.module}" rev="${dep.revision}"/>
+"""
+        }
+        ivyFile << """
+    </dependencies>
+</ivy-module>
+        """
+
+        return this
+    }
+
+    private File file(def artifact) {
+        return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.type}")
+    }
+
+    /**
+     * Asserts that exactly the given artifacts have been published.
+     */
+    void assertArtifactsPublished(String... names) {
+        assert moduleDir.list() as Set == names as Set
+    }
+
+    private String getHash(File file, String algorithm) {
+        return HashUtil.createHash(file, algorithm).asHexString()
+    }
+
+    TestFile sha1File(File file) {
+        def sha1File = moduleDir.file("${file.name}.sha1")
+        sha1File.text = getHash(file, "SHA1")
+        return sha1File
+    }
+
+    IvyDescriptor getIvy() {
+        return new IvyDescriptor(ivyFile)
+    }
+
+    public expectIvyHead(HttpServer server, prefix = null) {
+        server.expectHead(ivyPath(prefix), ivyFile)
+    }
+
+    public expectIvyGet(HttpServer server, prefix = null) {
+        server.expectGet(ivyPath(prefix), ivyFile)
+    }
+
+    public ivyPath(prefix = null) {
+        path(prefix, ivyFile.name)
+    }
+
+    public expectIvySha1Get(HttpServer server, prefix = null) {
+        server.expectGet(ivySha1Path(prefix), sha1File(ivyFile))
+    }
+
+    public ivySha1Path(prefix = null) {
+        ivyPath(prefix) + ".sha1"
+    }
+
+    public expectArtifactHead(HttpServer server, prefix = null) {
+        server.expectHead(artifactPath(prefix), jarFile)
+    }
+
+    public expectArtifactGet(HttpServer server, prefix = null) {
+        server.expectGet(artifactPath(prefix), jarFile)
+    }
+
+    public artifactPath(prefix = null) {
+        path(prefix, jarFile.name)
+    }
+
+    public expectArtifactSha1Get(HttpServer server, prefix = null) {
+        server.expectGet(artifactSha1Path(prefix), sha1File(jarFile))
+    }
+
+    public artifactSha1Path(prefix = null) {
+        artifactPath(prefix) + ".sha1"
+    }
+
+    public path(prefix = null, String filename) {
+        "${prefix == null ? "" : prefix}/${organisation}/${module}/${revision}/${filename}"
+    }
+
+}
+
+class IvyDescriptor {
+    final Map<String, IvyConfiguration> configurations = [:]
+
+    IvyDescriptor(File ivyFile) {
+        def ivy = new XmlParser().parse(ivyFile)
+        ivy.dependencies.dependency.each { dep ->
+            def configName = dep. at conf ?: "default"
+            def matcher = Pattern.compile("(\\w+)->\\w+").matcher(configName)
+            if (matcher.matches()) {
+                configName = matcher.group(1)
+            }
+            def config = configurations[configName]
+            if (!config) {
+                config = new IvyConfiguration()
+                configurations[configName] = config
+            }
+            config.addDependency(dep. at org, dep. at name, dep. at rev)
+        }
+    }
+}
+
+class IvyConfiguration {
+    final dependencies = []
+
+    void addDependency(String org, String module, String revision) {
+        dependencies << [org: org, module: module, revision: revision]
+    }
+
+    void assertDependsOn(String org, String module, String revision) {
+        def dep = [org: org, module: module, revision: revision]
+        if (!dependencies.find { it == dep}) {
+            throw new AssertionFailedError("Could not find expected dependency $dep. Actual: $dependencies")
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestExecutionResult.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestExecutionResult.groovy
new file mode 100644
index 0000000..32c7162
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitTestExecutionResult.groovy
@@ -0,0 +1,165 @@
+/*
+ * 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 groovy.util.slurpersupport.GPathResult
+import org.gradle.util.TestFile
+import org.hamcrest.Matcher
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+class JUnitTestExecutionResult implements TestExecutionResult {
+    private final TestFile buildDir
+
+    def JUnitTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
+        this.buildDir = projectDir.file(buildDirName)
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        Map<String, File> classes = findClasses()
+        assertThat(classes.keySet(), equalTo(testClasses as Set));
+        this
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
+    }
+
+    private def findTestClass(String testClass) {
+        def classes = findClasses()
+        assertThat(classes.keySet(), hasItem(testClass))
+        def classFile = classes.get(testClass)
+        assertThat(classFile, notNullValue())
+        return new XmlSlurper().parse(classFile)
+    }
+
+    private def findClasses() {
+        buildDir.file('test-results').assertIsDir()
+        buildDir.file('reports/tests/index.html').assertIsFile()
+
+        Map<String, File> classes = [:]
+        buildDir.file('test-results').eachFile { File file ->
+            def matcher = (file.name =~ /TEST-(.+)\.xml/)
+            if (matcher.matches()) {
+                classes[matcher.group(1)] = file
+            }
+        }
+        return classes
+    }
+}
+
+class JUnitTestClassExecutionResult implements TestClassExecutionResult {
+    GPathResult testClassNode
+    String testClassName
+    boolean checked
+
+    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
+        this.testClassNode = testClassNode
+        this.testClassName = testClassName
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+        assertThat(testMethods[name].failure.size(), equalTo(0))
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+
+        def failures = testMethods[name].failure
+        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
+
+        for (int i = 0; i < messageMatchers.length; i++) {
+            assertThat(failures[i]. at message.text(), messageMatchers[i])
+        }
+        this
+    }
+
+    TestClassExecutionResult assertTestSkipped(String name) {
+        throw new UnsupportedOperationException()
+    }
+
+    TestClassExecutionResult assertTestsSkipped(String... testNames) {
+        Map<String, Node> testMethods = findIgnoredTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        def stdout = testClassNode.'system-out'[0].text();
+        assertThat(stdout, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        def stderr = testClassNode.'system-err'[0].text();
+        assertThat(stderr, matcher)
+        this
+    }
+
+    private def findTests() {
+        if (!checked) {
+            assertThat(testClassNode.name(), equalTo('testsuite'))
+            assertThat(testClassNode. at name.text(), equalTo(testClassName))
+            assertThat(testClassNode. at tests.text(), not(equalTo('')))
+            assertThat(testClassNode. at failures.text(), not(equalTo('')))
+            assertThat(testClassNode. at errors.text(), not(equalTo('')))
+            assertThat(testClassNode. at time.text(), not(equalTo('')))
+            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
+            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
+            assertThat(testClassNode.properties.size(), equalTo(1))
+            testClassNode.testcase.each { node ->
+                assertThat(node. at classname.text(), equalTo(testClassName))
+                assertThat(node. at name.text(), not(equalTo('')))
+                assertThat(node. at time.text(), not(equalTo('')))
+                node.failure.each { failure ->
+                    assertThat(failure. at message.size(), equalTo(1))
+                    assertThat(failure. at type.text(), not(equalTo('')))
+                    assertThat(failure.text(), not(equalTo('')))
+                }
+            }
+            assertThat(testClassNode.'system-out'.size(), equalTo(1))
+            assertThat(testClassNode.'system-err'.size(), equalTo(1))
+            checked = true
+        }
+        Map testMethods = [:]
+        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+
+    private def findIgnoredTests() {
+        Map testMethods = [:]
+        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
new file mode 100644
index 0000000..f071250
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
@@ -0,0 +1,353 @@
+/*
+ * 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 java.text.SimpleDateFormat
+import junit.framework.AssertionFailedError
+
+import org.gradle.util.TestFile
+import org.gradle.util.hash.HashUtil
+
+/**
+ * A fixture for dealing with Maven repositories.
+ */
+class MavenRepository {
+    final TestFile rootDir
+
+    MavenRepository(TestFile rootDir) {
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    MavenModule module(String groupId, String artifactId, Object version = '1.0') {
+        def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version")
+        return new MavenModule(artifactDir, groupId, artifactId, version as String)
+    }
+}
+
+class MavenModule {
+    final TestFile moduleDir
+    final String groupId
+    final String artifactId
+    final String version
+    String parentPomSection
+    String type = 'jar'
+    private final List dependencies = []
+    int publishCount = 1
+    final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
+    final timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss")
+    private final List artifacts = []
+    private boolean uniqueSnapshots = true;
+
+    MavenModule(TestFile moduleDir, String groupId, String artifactId, String version) {
+        this.moduleDir = moduleDir
+        this.groupId = groupId
+        this.artifactId = artifactId
+        this.version = version
+    }
+
+    MavenModule dependsOn(String dependencyArtifactId) {
+        dependsOn(groupId, dependencyArtifactId, '1.0')
+        return this
+    }
+
+    MavenModule dependsOn(String group, String artifactId, String version) {
+        this.dependencies << [groupId: group, artifactId: artifactId, version: version]
+        return this
+    }
+
+    /**
+     * Specifies the type of the main artifact.
+     */
+    MavenModule hasType(String type) {
+        this.type = type
+        return this
+    }
+
+    /**
+     * Adds an additional artifact to this module.
+     * @param options Can specify any of: type or classifier
+     */
+    MavenModule artifact(Map<String, ?> options) {
+        artifacts << options
+        return this
+    }
+
+    MavenModule withNonUniqueSnapshots() {
+        uniqueSnapshots = false;
+        return this;
+    }
+
+    File getMavenMetaDataFile() {
+        moduleDir.file("maven-metadata.xml")
+    }
+
+    /**
+     * Asserts that exactly the given artifacts have been deployed, along with their checksum files
+     */
+    void assertArtifactsPublished(String... names) {
+        def artifactNames = names
+        if (uniqueSnapshots && version.endsWith('-SNAPSHOT')) {
+            def metaData = new XmlParser().parse(moduleDir.file('maven-metadata.xml'))
+            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
+            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
+            artifactNames = names.collect { it.replace('-SNAPSHOT', "-${timestamp}-${build}")}
+            artifactNames.add("maven-metadata.xml")
+        }
+        Set actual = moduleDir.list() as Set
+        for (name in artifactNames) {
+            assert actual.remove(name)
+            assert actual.remove("${name}.md5" as String)
+            assert actual.remove("${name}.sha1" as String)
+        }
+        assert actual.isEmpty()
+    }
+
+    MavenPom getPom() {
+        return new MavenPom(pomFile)
+    }
+
+    TestFile getPomFile() {
+        return moduleDir.file("$artifactId-${publishArtifactVersion}.pom")
+    }
+    
+    TestFile getMetaDataFile() {
+        moduleDir.file("maven-metadata.xml")
+    }
+
+    TestFile getArtifactFile() {
+        return artifactFile([:])
+    }
+
+    TestFile artifactFile(Map<String, ?> options) {
+        def artifact = toArtifact(options)
+        def fileName = "$artifactId-${publishArtifactVersion}.${artifact.type}"
+        if (artifact.classifier) {
+            fileName = "$artifactId-$publishArtifactVersion-${artifact.classifier}.${artifact.type}"
+        }
+        return moduleDir.file(fileName)
+    }
+
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with changed content to any
+     * previous publication.
+     */
+    MavenModule publishWithChangedContent() {
+        publishCount++
+        return publish()
+    }
+
+    String getPublishArtifactVersion() {
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            return "${version.replaceFirst('-SNAPSHOT$', '')}-${timestampFormat.format(publishTimestamp)}-${publishCount}"
+        }
+        return version
+    }
+
+    Date getPublishTimestamp() {
+        return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000)
+    }
+
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module.
+     */
+    MavenModule publish() {
+        moduleDir.createDir()
+
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            def metaDataFile = moduleDir.file('maven-metadata.xml')
+            metaDataFile.text = """
+<metadata>
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <version>$version</version>
+  <versioning>
+    <snapshot>
+      <timestamp>${timestampFormat.format(publishTimestamp)}</timestamp>
+      <buildNumber>$publishCount</buildNumber>
+    </snapshot>
+    <lastUpdated>${updateFormat.format(publishTimestamp)}</lastUpdated>
+  </versioning>
+</metadata>
+"""
+            createHashFiles(metaDataFile)
+        }
+
+        pomFile.text = ""
+        pomFile << """
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <packaging>$type</packaging>
+  <version>$version</version>
+  <description>Published on $publishTimestamp</description>"""
+
+        if (parentPomSection) {
+            pomFile << "\n$parentPomSection\n"
+        }
+
+        dependencies.each { dependency ->
+            pomFile << """
+  <dependencies>
+    <dependency>
+      <groupId>$dependency.groupId</groupId>
+      <artifactId>$dependency.artifactId</artifactId>
+      <version>$dependency.version</version>
+    </dependency>3.2.1
+  </dependencies>"""
+        }
+
+        pomFile << "\n</project>"
+
+        createHashFiles(pomFile)
+
+        artifacts.each { artifact ->
+            publishArtifact(artifact)
+        }
+        publishArtifact([:])
+        return this
+    }
+
+    private File publishArtifact(Map<String, ?> artifact) {
+        def artifactFile = artifactFile(artifact)
+        if (type != 'pom') {
+            artifactFile << "add some content so that file size isn't zero: $publishCount"
+        }
+        createHashFiles(artifactFile)
+        return artifactFile
+    }
+
+    private Map<String, Object> toArtifact(Map<String, ?> options) {
+        options = new HashMap<String, Object>(options)
+        def artifact = [type: options.remove('type') ?: type, classifier: options.remove('classifier') ?: null]
+        assert options.isEmpty(): "Unknown options : ${options.keySet()}"
+        return artifact
+    }
+
+    private void createHashFiles(File file) {
+        sha1File(file)
+        md5File(file)
+    }
+
+    TestFile sha1File(File file) {
+        hashFile(file, "sha1");
+    }
+
+    TestFile md5File(File file) {
+        hashFile(file, "md5")
+    }
+
+    private TestFile hashFile(File file, String algorithm) {
+        def hashFile = moduleDir.file("${file.name}.${algorithm}")
+        hashFile.text = HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
+        return hashFile
+    }
+
+    public expectMetaDataGet(HttpServer server, prefix = null) {
+        server.expectGet(metadataPath(prefix), metaDataFile)
+    }
+
+    public metadataPath(prefix = null) {
+        path(prefix, "maven-metadata.xml")
+    }
+
+    public expectPomHead(HttpServer server, prefix = null) {
+        server.expectHead(pomPath(prefix), pomFile)
+    }
+
+    public expectPomGet(HttpServer server, prefix = null) {
+        server.expectGet(pomPath(prefix), pomFile)
+    }
+
+    public pomPath(prefix = null) {
+        path(prefix, pomFile.name)
+    }
+
+    public expectPomSha1Get(HttpServer server, prefix = null) {
+        server.expectGet(pomSha1Path(prefix), sha1File(pomFile))
+    }
+
+    public pomSha1Path(prefix = null) {
+        pomPath(prefix) + ".sha1"
+    }
+
+    public expectArtifactHead(HttpServer server, prefix = null) {
+        server.expectHead(artifactPath(prefix), artifactFile)
+    }
+
+    public expectArtifactGet(HttpServer server, prefix = null) {
+        server.expectGet(artifactPath(prefix), pomFile)
+    }
+
+    public artifactPath(prefix = null) {
+        path(prefix, artifactFile.name)
+    }
+
+    public expectArtifactSha1Get(HttpServer server, prefix = null) {
+        server.expectGet(artifactSha1Path(prefix), sha1File(artifactFile))
+    }
+
+    public artifactSha1Path(prefix = null) {
+        artifactPath(prefix) + ".sha1"
+    }
+
+    public path(prefix = null, String filename) {
+        "${prefix == null ? "" : prefix}/${groupId.replace('.', '/')}/${artifactId}/${version}/${filename}"
+    }
+
+}
+
+class MavenPom {
+    final Map<String, MavenScope> scopes = [:]
+
+    MavenPom(File pomFile) {
+        def pom = new XmlParser().parse(pomFile)
+        pom.dependencies.dependency.each { dep ->
+            def scopeElement = dep.scope
+            def scopeName = scopeElement ? scopeElement.text() : "runtime"
+            def scope = scopes[scopeName]
+            if (!scope) {
+                scope = new MavenScope()
+                scopes[scopeName] = scope
+            }
+            scope.addDependency(dep.groupId.text(), dep.artifactId.text(), dep.version.text())
+        }
+    }
+
+}
+
+class MavenScope {
+    final dependencies = []
+
+    void addDependency(String groupId, String artifactId, String version) {
+        dependencies << [groupId: groupId, artifactId: artifactId, version: version]
+    }
+
+    void assertDependsOnArtifacts(String... artifactIds) {
+        assert dependencies.collect { it.artifactId} as Set == artifactIds as Set
+    }
+
+    void assertDependsOn(String groupId, String artifactId, String version) {
+        def dep = [groupId: groupId, artifactId: artifactId, version: version]
+        if (!dependencies.find { it == dep }) {
+            throw new AssertionFailedError("Could not find expected dependency $dep. Actual: $dependencies")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
new file mode 100644
index 0000000..7cabe19
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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.junit.runner.RunWith
+
+ at RunWith(MultiVersionSpecRunner)
+abstract class MultiVersionIntegrationSpec extends AbstractIntegrationSpec {
+    static def version
+
+    def getVersion() {
+        return version
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy
new file mode 100644
index 0000000..c04872d
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionSpecRunner.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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
+
+/**
+ * Runs the target test class against the versions specified in a {@link TargetVersions}
+ */
+class MultiVersionSpecRunner extends AbstractMultiTestRunner {
+    MultiVersionSpecRunner(Class<?> target) {
+        super(target)
+    }
+
+    @Override
+    protected void createExecutions() {
+        def versions = target.getAnnotation(TargetVersions)
+        if (versions == null) {
+            throw new RuntimeException("Target class '$target' is not annotated with @${TargetVersions.simpleName}.")
+        }
+        versions.value().each { add(new VersionExecution(it)) }
+    }
+
+    private static class VersionExecution extends AbstractMultiTestRunner.Execution {
+        final String version
+
+        VersionExecution(String version) {
+            this.version = version
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return version
+        }
+
+        @Override
+        protected void before() {
+            target.version = version
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionFailure.java
new file mode 100644
index 0000000..23f1a0e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionFailure.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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.hamcrest.Matcher;
+
+import java.util.regex.Pattern;
+
+import static org.gradle.util.Matchers.containsLine;
+import static org.gradle.util.Matchers.matchesRegexp;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class OutputScrapingExecutionFailure extends OutputScrapingExecutionResult implements ExecutionFailure {
+    private final Pattern causePattern = Pattern.compile("(?m)\\s*> ");
+
+    public OutputScrapingExecutionFailure(String output, String error) {
+        super(output, error);
+    }
+
+    public ExecutionFailure assertHasLineNumber(int lineNumber) {
+        assertThat(getError(), containsString(String.format(" line: %d", lineNumber)));
+        return this;
+    }
+
+    public ExecutionFailure assertHasFileName(String filename) {
+        assertThat(getError(), containsLine(startsWith(filename)));
+        return this;
+    }
+
+    public ExecutionFailure assertHasCause(String description) {
+        assertThatCause(startsWith(description));
+        return this;
+    }
+
+    public ExecutionFailure assertThatCause(Matcher<String> matcher) {
+        String error = getError();
+        java.util.regex.Matcher regExpMatcher = causePattern.matcher(error);
+        int pos = 0;
+        while (pos < error.length()) {
+            if (!regExpMatcher.find(pos)) {
+                break;
+            }
+            int start = regExpMatcher.end();
+            String cause;
+            if (regExpMatcher.find(start)) {
+                cause = error.substring(start, regExpMatcher.start());
+                pos = regExpMatcher.start();
+            } else {
+                cause = error.substring(start);
+                pos = error.length();
+            }
+            if (matcher.matches(cause)) {
+                return this;
+            }
+        }
+        fail(String.format("No matching cause found in '%s'", error));
+        return this;
+    }
+
+    public ExecutionFailure assertHasNoCause() {
+        assertThat(getError(), not(matchesRegexp(causePattern)));
+        return this;
+    }
+
+    public ExecutionFailure assertHasDescription(String context) {
+        assertThatDescription(startsWith(context));
+        return this;
+    }
+
+    public ExecutionFailure assertThatDescription(Matcher<String> matcher) {
+        assertThat(getError(), containsLine(matcher));
+        return this;
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionResult.java
new file mode 100644
index 0000000..2ea90f2
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingExecutionResult.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2011 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.apache.commons.collections.CollectionUtils;
+import org.gradle.api.Action;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.*;
+import java.util.regex.Pattern;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.assertThat;
+
+public class OutputScrapingExecutionResult implements ExecutionResult {
+    private final String output;
+    private final String error;
+
+    //for example: ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a'
+    private final Pattern skippedTaskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)\\s+((SKIPPED)|(UP-TO-DATE))");
+
+    //for example: ':hey' or ':a SKIPPED' or ':foo:bar:baz UP-TO-DATE' but not ':a FOO'
+    private final Pattern taskPattern = Pattern.compile("(:\\S+?(:\\S+?)*)((\\s+SKIPPED)|(\\s+UP-TO-DATE)|(\\s*))");
+
+    public OutputScrapingExecutionResult(String output, String error) {
+        this.output = output;
+        this.error = error;
+    }
+
+    public String getOutput() {
+        return output;
+    }
+
+    public String getError() {
+        return error;
+    }
+
+    public List<String> getExecutedTasks() {
+        return grepTasks(taskPattern);
+    }
+    
+    public ExecutionResult assertTasksExecuted(String... taskPaths) {
+        List<String> expectedTasks = Arrays.asList(taskPaths);
+        assertThat(String.format("Expected tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getExecutedTasks(), equalTo(expectedTasks));
+        return this;
+    }
+
+    public Set<String> getSkippedTasks() {
+        return new HashSet<String>(grepTasks(skippedTaskPattern));
+    }
+    
+    public ExecutionResult assertTasksSkipped(String... taskPaths) {
+        Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
+        assertThat(String.format("Expected skipped tasks %s not found in process output:%n%s", expectedTasks, getOutput()), getSkippedTasks(), equalTo(expectedTasks));
+        return this;
+    }
+
+    public ExecutionResult assertTaskSkipped(String taskPath) {
+        Set<String> tasks = new HashSet<String>(getSkippedTasks());
+        assertThat(String.format("Expected skipped task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
+        return this;
+    }
+
+    public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
+        Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
+        Set<String> expectedTasks = new HashSet<String>(Arrays.asList(taskPaths));
+        assertThat(String.format("Expected executed tasks %s not found in process output:%n%s", expectedTasks, getOutput()), tasks, equalTo(expectedTasks));
+        return this;
+    }
+
+    private Collection<String> getNotSkippedTasks() {
+        List all = getExecutedTasks();
+        Set skipped = getSkippedTasks();
+        return CollectionUtils.subtract(all, skipped);
+    }
+
+    public ExecutionResult assertTaskNotSkipped(String taskPath) {
+        Set<String> tasks = new HashSet<String>(getNotSkippedTasks());
+        assertThat(String.format("Expected executed task %s not found in process output:%n%s", taskPath, getOutput()), tasks, hasItem(taskPath));
+        return this;
+    }
+
+    private List<String> grepTasks(final Pattern pattern) {
+        final LinkedList<String> tasks = new LinkedList<String>();
+
+        eachLine(new Action<String>() {
+            public void execute(String s) {
+                java.util.regex.Matcher matcher = pattern.matcher(s);
+                if (matcher.matches()) {
+                    String taskName = matcher.group(1);
+                    if (!taskName.startsWith(":buildSrc:")) {
+                        //for INFO/DEBUG level the task may appear twice - once for the execution, once for the UP-TO-DATE
+                        //so I'm not adding the task to the list if it is the same as previously added task.
+                        if (tasks.size() == 0 || !tasks.getLast().equals(taskName)) {
+                            tasks.add(taskName);
+                        }
+                    }
+                }
+            }
+        });
+
+        return tasks;
+    }
+
+    private void eachLine(Action<String> action) {
+        BufferedReader reader = new BufferedReader(new StringReader(getOutput()));
+        String line;
+        try {
+            while ((line = reader.readLine()) != null) {
+                action.execute(line);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingGradleHandle.java
new file mode 100644
index 0000000..c609124
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/OutputScrapingGradleHandle.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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;
+
+abstract public class OutputScrapingGradleHandle implements GradleHandle {
+
+    protected ExecutionResult toExecutionResult(String output, String error) {
+        return new OutputScrapingExecutionResult(transformStandardOutput(output), transformErrorOutput(error));
+    }
+
+    protected ExecutionResult toExecutionFailure(String output, String error) {
+        return new OutputScrapingExecutionFailure(transformStandardOutput(output), transformErrorOutput(error));
+    }
+
+    protected String transformStandardOutput(String output) {
+        return output;
+    }
+
+    protected String transformErrorOutput(String error) {
+        return error;
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
new file mode 100644
index 0000000..25cc0b1
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2011 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.CacheUsage
+import org.gradle.api.Action
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.CacheFactory
+import org.gradle.cache.internal.DefaultCacheFactory
+import org.gradle.cache.internal.DefaultFileLockManager
+import org.gradle.cache.internal.DefaultProcessMetaDataProvider
+import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.util.DistributionLocator
+import org.gradle.util.GradleVersion
+import org.gradle.internal.jvm.Jvm
+import org.gradle.util.TestFile
+
+public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implements BasicGradleDistribution {
+    private static final CACHE_FACTORY = createCacheFactory()
+
+    private static CacheFactory createCacheFactory() {
+        return new DefaultCacheFactory(
+                new DefaultFileLockManager(
+                        new DefaultProcessMetaDataProvider(
+                                new NativeServices().get(ProcessEnvironment)))).create()
+    }
+
+    private final GradleDistribution dist
+    final GradleVersion version
+    private final TestFile versionDir
+    private final TestFile zipFile
+    private final TestFile homeDir
+    private PersistentCache cache
+
+    PreviousGradleVersionExecuter(GradleDistribution dist, String version) {
+        this.dist = dist
+        this.version = GradleVersion.version(version)
+        versionDir = dist.previousVersionsDir.file(version)
+        zipFile = versionDir.file("gradle-$version-bin.zip")
+        homeDir = versionDir.file("gradle-$version")
+    }
+
+    def String toString() {
+        version.toString()
+    }
+
+    String getVersion() {
+        return version.version
+    }
+
+    boolean worksWith(Jvm jvm) {
+        // Milestone 4 was broken on the IBM jvm
+        if (jvm.ibmJvm && version == GradleVersion.version('1.0-milestone-4')) {
+            return false
+        }
+        // 0.9-rc-1 was broken for Java 5
+        if (version == GradleVersion.version('0.9-rc-1')) {
+            return jvm.isJava6Compatible()
+        }
+
+        return jvm.isJava5Compatible()
+    }
+
+    boolean worksWith(OperatingSystem os) {
+        // 1.0-milestone-5 was broken where jna was not available
+        if (version == GradleVersion.version("1.0-milestone-5")) {
+            return os.windows || os.macOsX || os.linux
+        }
+        return true
+    }
+
+    DaemonRegistry getDaemonRegistry() {
+        throw new UnsupportedOperationException()
+    }
+    
+    boolean isDaemonSupported() {
+        // Milestone 7 was broken on the IBM jvm
+        if (Jvm.current().ibmJvm && version == GradleVersion.version('1.0-milestone-7')) {
+            return false
+        }
+
+        if (OperatingSystem.current().isWindows()) {
+            // On windows, daemon is ok for anything > 1.0-milestone-3
+            return version > GradleVersion.version('1.0-milestone-3')
+        } else {
+            // Daemon is ok for anything >= 0.9
+            return version >= GradleVersion.version('0.9')
+        }
+    }
+
+    boolean isDaemonIdleTimeoutConfigurable() {
+        return version > GradleVersion.version('1.0-milestone-6')
+    }
+
+    boolean isOpenApiSupported() {
+        return version >= GradleVersion.version('0.9-rc-1')
+    }
+
+    boolean isToolingApiSupported() {
+        return version >= GradleVersion.version('1.0-milestone-3')
+    }
+
+    boolean wrapperCanExecute(String version) {
+        if (version == '0.8' || this.version == GradleVersion.version('0.8')) {
+            // There was a breaking change after 0.8
+            return false
+        }
+        if (this.version == GradleVersion.version('0.9.1')) {
+            // 0.9.1 couldn't handle anything with a timestamp whose timezone was behind GMT
+            return version.matches('.*+\\d{4}')
+        }
+        if (this.version >= GradleVersion.version('0.9.2') && this.version <= GradleVersion.version('1.0-milestone-2')) {
+            // These versions couldn't handle milestone patches
+            if (version.matches('1.0-milestone-\\d+[a-z]-.+')) {
+                return false
+            }
+        }
+        return true
+    }
+
+    protected ExecutionResult doRun() {
+        ForkingGradleExecuter executer = new ForkingGradleExecuter(gradleHomeDir)
+        executer.inDirectory(dist.testDir)
+        copyTo(executer)
+        return executer.run()
+    }
+
+    GradleExecuter executer() {
+        this
+    }
+
+    TestFile getBinDistribution() {
+        download()
+        return zipFile
+    }
+
+    private URL getBinDistributionUrl() {
+        return new DistributionLocator().getDistributionFor(version).toURL()
+    }
+
+    def TestFile getGradleHomeDir() {
+        download()
+        return homeDir
+    }
+
+    private void download() {
+        if (cache == null) {
+            def downloadAction = { cache ->
+                URL url = binDistributionUrl
+                System.out.println("downloading $url");
+                zipFile.copyFrom(url)
+                zipFile.usingNativeTools().unzipTo(versionDir)
+            }
+            cache = CACHE_FACTORY.open(versionDir, version.toString(), CacheUsage.ON, null, [:], LockMode.Shared, downloadAction as Action)
+        }
+        zipFile.assertIsFile()
+        homeDir.assertIsDir()
+    }
+
+    protected ExecutionFailure doRunWithFailure() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ReleasedVersions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ReleasedVersions.java
new file mode 100644
index 0000000..24913a5
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ReleasedVersions.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 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.api.Transformer;
+import org.gradle.integtests.fixtures.versions.VersionsInfo;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GradleVersion;
+
+import java.util.List;
+
+import static org.gradle.util.GradleVersion.version;
+
+/**
+ * by Szczepan Faber, created at: 1/12/12
+ */
+public class ReleasedVersions {
+
+    private final static List<String> VERSIONS = new VersionsInfo().getVersions();
+
+    private final GradleDistribution current;
+
+    public ReleasedVersions(GradleDistribution current) {
+        this.current = current;
+    }
+
+    /**
+     * @return last released version. Never returns the RC.
+     */
+    public BasicGradleDistribution getLast() {
+        for (String v : VERSIONS) {
+            if (!version(v).isSnapshot()) {
+                return current.previousVersion(v);
+            }
+        }
+        throw new RuntimeException("Unable to get the last version");
+    }
+
+    public List<BasicGradleDistribution> getAll() {
+        return CollectionUtils.collect(VERSIONS, new Transformer<BasicGradleDistribution, String>() {
+            public BasicGradleDistribution transform(String original) {
+                return current.previousVersion(original);
+            }
+        });
+    }
+
+    public BasicGradleDistribution getPreviousOf(BasicGradleDistribution distro) {
+        GradleVersion distroVersion = version(distro.getVersion());
+
+        for (String candidate : VERSIONS) {
+            GradleVersion candidateVersion = version(candidate);
+            if (distroVersion.compareTo(candidateVersion) > 0) {
+                return current.previousVersion(candidate);
+            }
+        }
+
+        throw new RuntimeException("I don't know the previous version of: " + distro);
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/RuleHelper.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RuleHelper.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/RuleHelper.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RuleHelper.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SFTPServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SFTPServer.groovy
new file mode 100644
index 0000000..23f7b21
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/SFTPServer.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012 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 com.jcraft.jsch.JSch
+import com.jcraft.jsch.UserInfo
+import java.security.PublicKey
+import org.apache.commons.io.FileUtils
+import org.apache.sshd.SshServer
+import org.apache.sshd.common.NamedFactory
+import org.apache.sshd.server.Command
+import org.apache.sshd.server.PasswordAuthenticator
+import org.apache.sshd.server.PublickeyAuthenticator
+import org.apache.sshd.server.command.ScpCommandFactory
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
+import org.apache.sshd.server.session.ServerSession
+import org.apache.sshd.server.sftp.SftpSubsystem
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.rules.ExternalResource
+
+class SFTPServer extends ExternalResource {
+    final String hostAddress;
+    int port
+
+    private final TemporaryFolder tmpDir
+    private TestFile baseDir
+    private TestFile configDir
+
+    private SshServer sshd;
+    private com.jcraft.jsch.Session session
+
+    def fileRequests = [] as Set
+
+    public SFTPServer(TemporaryFolder tmpDir) {
+        this.tmpDir = tmpDir;
+        def portFinder = org.gradle.util.AvailablePortFinder.createPrivate()
+        port = portFinder.nextAvailable
+        this.hostAddress = "127.0.0.1"
+    }
+
+    protected void before() throws Throwable {
+        baseDir = tmpDir.createDir("sshd/files")
+        configDir = tmpDir.createDir("sshd/config")
+
+        sshd = setupConfiguredTestSshd();
+        sshd.start();
+        createSshSession();
+    }
+
+    protected void after() {
+        session?.disconnect();
+        sshd?.stop()
+    }
+
+    private createSshSession() {
+        JSch sch = new JSch();
+        session = sch.getSession("sshd", "localhost", port);
+        session.setUserInfo(new UserInfo() {
+            public String getPassphrase() {
+                return null;
+            }
+
+            public String getPassword() {
+                return "sshd";
+            }
+
+            public boolean promptPassword(String message) {
+                return true;
+            }
+
+            public boolean promptPassphrase(String message) {
+                return false;
+            }
+
+            public boolean promptYesNo(String message) {
+                return true;
+            }
+
+            public void showMessage(String message) {
+            }
+        });
+        session.connect()
+    }
+
+    private SshServer setupConfiguredTestSshd() {
+        //copy dsa key to config directory
+        URL fileUrl = ClassLoader.getSystemResource("sshd-config/test-dsa.key");
+        FileUtils.copyURLToFile(fileUrl, new File(configDir, "test-dsa.key"));
+
+        SshServer sshServer = SshServer.setUpDefaultServer();
+        sshServer.setPort(port);
+        sshServer.setFileSystemFactory(new TestNativeFileSystemFactory(baseDir.absolutePath, new FileRequestLogger() {
+            void logRequest(String message) {
+                fileRequests << message;
+            }
+        }));
+        sshServer.setSubsystemFactories(Arrays.<NamedFactory<Command>> asList(new SftpSubsystem.Factory()));
+        sshServer.setCommandFactory(new ScpCommandFactory());
+        sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("${configDir}/test-dsa.key"));
+        sshServer.setPasswordAuthenticator(new DummyPasswordAuthenticator());
+        sshServer.setPublickeyAuthenticator(new PublickeyAuthenticator() {
+            boolean authenticate(String username, PublicKey key, ServerSession session) {
+                return true
+            }
+        });
+        return sshServer;
+    }
+
+    boolean hasFile(String filePathToCheck) {
+        new File(baseDir, filePathToCheck).exists()
+    }
+
+    TestFile file(String expectedPath) {
+        new TestFile(new File(baseDir, expectedPath))
+    }
+
+    public Set<String> getFileRequests() {
+        return fileRequests
+    }
+
+    public void clearRequests() {
+        fileRequests.clear();
+    }
+}
+
+abstract class FileRequestLogger {
+    abstract void logRequest(String message);
+}
+
+public class DummyPasswordAuthenticator implements PasswordAuthenticator {
+
+    // every combination where username == password is accepted
+    boolean authenticate(String username, String password, org.apache.sshd.server.session.ServerSession session) {
+        return username && password && username == password;
+    }
+}
+
+
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/Sample.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/Sample.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
new file mode 100755
index 0000000..002e561
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.process.internal.ExecHandleBuilder
+import org.gradle.process.internal.ExecHandle
+import org.gradle.process.ExecResult
+import org.gradle.internal.os.OperatingSystem
+
+class ScriptExecuter extends ExecHandleBuilder {
+    @Override
+    ExecHandle build() {
+        if (OperatingSystem.current().isWindows()) {
+            args = ['/c', executable.replace('/', File.separator)] + args
+            executable = 'cmd'
+        } else {
+            executable = "${workingDir}/${executable}"
+        }
+        return super.build()
+    }
+
+    ExecResult run() {
+        return build().start().waitForFinish()
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TargetVersions.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TargetVersions.java
new file mode 100644
index 0000000..475850c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TargetVersions.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface TargetVersions {
+    String[] value();
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
old mode 100644
new mode 100755
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy
new file mode 100644
index 0000000..e0c5bb2
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNGExecutionResult.groovy
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2011 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 groovy.util.slurpersupport.GPathResult
+import org.gradle.util.TestFile
+import org.hamcrest.Matcher
+
+/**
+ * by Szczepan Faber, created at: 11/3/11
+ */
+class TestNGExecutionResult implements TestExecutionResult {
+    private final TestFile projectDir
+    private final GPathResult resultsXml
+
+    def TestNGExecutionResult(projectDir) {
+        this.projectDir = projectDir;
+        resultsXml = new XmlSlurper().parse(projectDir.file('build/reports/tests/testng-results.xml').assertIsFile())
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        projectDir.file('build/reports/tests/index.html').assertIsFile()
+        def actualTestClasses = findTestClasses().keySet()
+        org.junit.Assert.assertThat(actualTestClasses, org.hamcrest.Matchers.equalTo(testClasses as Set))
+        this
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new org.gradle.integtests.fixtures.TestNgTestClassExecutionResult(testClass, findTestClass(testClass))
+    }
+
+    private def findTestClass(String testClass) {
+        def testClasses = findTestClasses()
+        if (!testClasses.containsKey(testClass)) {
+            org.junit.Assert.fail("Could not find test class ${testClass}. Found ${testClasses.keySet()}")
+        }
+        testClasses[testClass]
+    }
+
+    private def findTestClasses() {
+        Map testClasses = [:]
+        resultsXml.suite.test.'class'.each {
+            testClasses.put(it. at name as String, it)
+        }
+        testClasses
+    }
+}
+
+private class TestNgTestClassExecutionResult implements TestClassExecutionResult {
+    def String testClass
+    def GPathResult testClassNode
+
+    def TestNgTestClassExecutionResult(String testClass, GPathResult resultXml) {
+        this.testClass = testClass
+        this.testClassNode = resultXml
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        def actualTestMethods = findTestMethods().keySet()
+        org.junit.Assert.assertThat(actualTestMethods, org.hamcrest.Matchers.equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        def testMethodNode = findTestMethod(name)
+        org.junit.Assert.assertEquals('PASS', testMethodNode. at status as String)
+        this
+    }
+
+    TestClassExecutionResult assertTestsSkipped(String... testNames) {
+        throw new UnsupportedOperationException()
+    }
+
+    TestClassExecutionResult assertTestSkipped(String name) {
+        def testMethodNode = findTestMethod(name)
+        org.junit.Assert.assertEquals('SKIP', testMethodNode. at status as String)
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+        def testMethodNode = findTestMethod(name)
+        org.junit.Assert.assertEquals('FAIL', testMethodNode. at status as String)
+
+        def exceptions = testMethodNode.exception
+        org.junit.Assert.assertThat(exceptions.size(), org.hamcrest.Matchers.equalTo(messageMatchers.length))
+
+        for (int i = 0; i < messageMatchers.length; i++) {
+            org.junit.Assert.assertThat(exceptions[i].message[0].text().trim(), messageMatchers[i])
+        }
+        this
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        def testMethodNode = findConfigMethod(name)
+        org.junit.Assert.assertEquals('PASS', testMethodNode. at status as String)
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        def testMethodNode = findConfigMethod(name)
+        org.junit.Assert.assertEquals('FAIL', testMethodNode. at status as String)
+        this
+    }
+
+    private def findConfigMethod(String testName) {
+        def testMethods = findConfigMethods()
+        if (!testMethods.containsKey(testName)) {
+            org.junit.Assert.fail("Could not find configuration method ${testClass}.${testName}. Found ${testMethods.keySet()}")
+        }
+        testMethods[testName]
+    }
+
+    private def findConfigMethods() {
+        Map testMethods = [:]
+        testClassNode.'test-method'.findAll { it.'@is-config' == 'true' }.each {
+            testMethods.put(it. at name as String, it)
+        }
+        testMethods
+    }
+
+    private def findTestMethod(String testName) {
+        def testMethods = findTestMethods()
+        if (!testMethods.containsKey(testName)) {
+            org.junit.Assert.fail("Could not find test ${testClass}.${testName}. Found ${testMethods.keySet()}")
+        }
+        testMethods[testName]
+    }
+
+    private def findTestMethods() {
+        Map testMethods = [:]
+        testClassNode.'test-method'.findAll { it.'@is-config' != 'true' }.each {
+            testMethods.put(it. at name as String, it)
+        }
+        testMethods
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNativeFileSystem.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNativeFileSystem.groovy
new file mode 100644
index 0000000..b1d1de0
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestNativeFileSystem.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012 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.apache.sshd.common.Session
+import org.apache.sshd.server.FileSystemView
+import org.apache.sshd.server.SshFile
+import org.apache.sshd.server.filesystem.NativeFileSystemFactory
+import org.apache.sshd.server.filesystem.NativeSshFile
+
+/**
+ * This is a patched version of {@link org.apache.sshd.server.filesystem.NativeFileSystemView}
+ * to allow the usage of a custom currentDir (rootpath) without manipulating System properties.
+ * */
+class TestNativeFileSystemView implements FileSystemView {
+    // the first and the last character will always be '/'
+    // It is always with respect to the root directory.
+    private String currDir;
+
+    private String userName;
+
+    private boolean caseInsensitive = false;
+
+    List<FileRequestLogger> logger
+
+    /**
+     * Constructor - internal do not use directly, use {@link NativeFileSystemFactory} instead
+     */
+    public TestNativeFileSystemView(String rootpath, String userName, List<FileRequestLogger> requestLoggerList, boolean caseInsensitive) {
+        if (!rootpath) {
+            throw new IllegalArgumentException("rootPath must be set");
+        }
+
+        if (!userName) {
+            throw new IllegalArgumentException("user can not be null");
+        }
+
+        this.logger = requestLoggerList;
+        this.caseInsensitive = caseInsensitive;
+
+        currDir = rootpath;
+        this.userName = userName;
+    }
+
+    /**
+     * Get file object.
+     */
+    public SshFile getFile(String file) {
+        return getFile(currDir, file);
+    }
+
+    public SshFile getFile(SshFile baseDir, String file) {
+        return getFile(baseDir.getAbsolutePath(), file);
+    }
+
+    protected SshFile getFile(String dir, String file) {
+        // get actual file object
+
+        String physicalName = NativeSshFile.getPhysicalName("/", dir, file, caseInsensitive);
+        File fileObj = new File(physicalName);
+        logFileRequest(dir, fileObj.absolutePath);
+        // strip the root directory and return
+        String userFileName = physicalName.substring("/".length() - 1);
+        return new NativeSshFile(userFileName, fileObj, userName);
+    }
+
+    void logFileRequest(String dir, String file) {
+        //log xml and jar requests only
+        if (file.endsWith("xml") || file.endsWith(".jar")) {
+            String normalizedPath = (file - dir).replaceAll("\\\\", '/') - "/"
+            logger.each {
+                it.logRequest(normalizedPath)
+            }
+        }
+    }
+}
+
+class TestNativeFileSystemFactory extends NativeFileSystemFactory {
+
+    String rootPath
+
+    List<FileRequestLogger> logger
+
+    public TestNativeFileSystemFactory(String rootPath, FileRequestLogger... logger) {
+        this.rootPath = rootPath
+        this.logger = Arrays.asList(logger)
+    }
+
+    /**
+     * Create the appropriate user file system view.
+     */
+    public FileSystemView createFileSystemView(Session session) {
+        String userName = session.getUsername();
+        FileSystemView fsView = new TestNativeFileSystemView(rootPath, userName, logger, caseInsensitive);
+        return fsView;
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestProxyServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestProxyServer.groovy
new file mode 100644
index 0000000..4ef413b
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestProxyServer.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.AvailablePortFinder
+import org.jboss.netty.handler.codec.http.HttpRequest
+import org.junit.rules.ExternalResource
+import org.littleshoot.proxy.DefaultHttpProxyServer
+import org.littleshoot.proxy.HttpProxyServer
+import org.littleshoot.proxy.HttpRequestFilter
+import org.littleshoot.proxy.ProxyAuthorizationHandler
+
+/**
+ * A Proxy Server used for testing that http proxies are correctly supported.
+ * This proxy server will forward all requests to the supplied HttpServer. The true target of the request is ignored.
+ * This is necessary because we can't force java to use a proxy for localhost addresses (using the default java ProxySelector).
+ */
+class TestProxyServer extends ExternalResource {
+    private HttpProxyServer proxyServer
+    private HttpServer httpServer
+
+    int port
+    int requestCount
+
+    TestProxyServer(HttpServer httpServer) {
+        this.httpServer = httpServer
+    }
+
+    @Override
+    protected void after() {
+        stop()
+    }
+
+    void start() {
+        port = new AvailablePortFinder().nextAvailable
+        String remote = "localhost:${httpServer.port}"
+        proxyServer = new DefaultHttpProxyServer(port, [:], remote, null, new HttpRequestFilter() {
+            void filter(HttpRequest httpRequest) {
+                requestCount++
+            }
+        })
+        proxyServer.start()
+    }
+
+    void stop() {
+        proxyServer?.stop()
+    }
+
+    void requireAuthentication(final String expectedUsername, final String expectedPassword) {
+        proxyServer.addProxyAuthenticationHandler(new ProxyAuthorizationHandler() {
+            boolean authenticate(String username, String password) {
+                return username == expectedUsername && password == expectedPassword
+            }
+        })
+    }
+}
+
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/TestResources.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/TestResources.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
new file mode 100644
index 0000000..b6b5cc3
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
@@ -0,0 +1,289 @@
+/*
+ * 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 com.google.common.collect.ArrayListMultimap
+import com.google.common.collect.ListMultimap
+import groovy.io.PlatformLineWriter
+import junit.framework.AssertionFailedError
+import org.apache.tools.ant.taskdefs.Delete
+import org.gradle.util.AntUtil
+import org.gradle.internal.SystemProperties
+import org.junit.Assert
+import org.junit.runner.Description
+import org.junit.runner.Runner
+import org.junit.runner.notification.Failure
+import org.junit.runner.notification.RunNotifier
+import java.util.regex.Pattern
+
+class UserGuideSamplesRunner extends Runner {
+    private static final String NL = SystemProperties.lineSeparator
+
+    Class<?> testClass
+    Description description
+    Map<Description, SampleRun> samples;
+    GradleDistribution dist = new GradleDistribution()
+    GradleDistributionExecuter executer = new GradleDistributionExecuter(dist)
+    Pattern dirFilter = null 
+
+    def UserGuideSamplesRunner(Class<?> testClass) {
+        this.testClass = testClass
+        this.description = Description.createSuiteDescription(testClass)
+        this.dirFilter = getDirFilterPattern()
+        samples = new LinkedHashMap()
+        for (sample in getScriptsForSamples(dist.userGuideInfoDir)) {
+            if (shouldInclude(sample)) {
+                Description childDescription = Description.createTestDescription(testClass, sample.id)
+                description.addChild(childDescription)
+                samples.put(childDescription, sample)
+
+                println "Sample $sample.id dir: $sample.subDir"
+                sample.runs.each { println "    args: $it.args expect: $it.outputFile" }
+            }
+        }
+    }
+
+    private static Pattern getDirFilterPattern() {
+        String filter = System.properties["org.gradle.userguide.samples.filter"]
+        filter ? Pattern.compile(filter) : null
+    }
+
+    Description getDescription() {
+        return description
+    }
+
+    private boolean shouldInclude(SampleRun run) {
+        dirFilter ? run.subDir ==~ dirFilter : true
+    }
+
+    void run(RunNotifier notifier) {
+        for (childDescription in description.children) {
+            notifier.fireTestStarted(childDescription)
+            SampleRun sampleRun = samples.get(childDescription)
+            try {
+                cleanup(sampleRun)
+                for (run in sampleRun.runs) {
+                    runSample(run)
+                }
+            } catch (Throwable t) {
+                notifier.fireTestFailure(new Failure(childDescription, t))
+            }
+            notifier.fireTestFinished(childDescription)
+        }
+    }
+
+    private def cleanup(SampleRun run) {
+        // Clean up previous runs
+        File rootProjectDir = dist.samplesDir.file(run.subDir)
+        if (rootProjectDir.exists()) {
+            def delete = new Delete()
+            delete.dir = rootProjectDir
+            delete.includes = "**/.gradle/** **/build/**"
+            AntUtil.execute(delete)
+        }
+    }
+
+    private def runSample(GradleRun run) {
+        try {
+            println("Test Id: $run.id, dir: $run.subDir, args: $run.args")
+            File rootProjectDir = dist.samplesDir.file(run.subDir)
+
+            executer.setAllowExtraLogging(false).inDirectory(rootProjectDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
+
+            ExecutionResult result = run.expectFailure ? executer.runWithFailure() : executer.run()
+            if (run.outputFile) {
+                String expectedResult = replaceWithPlatformNewLines(dist.userGuideOutputDir.file(run.outputFile).text)
+                try {
+                    compareStrings(expectedResult, result.output, run.ignoreExtraLines)
+                } catch (AssertionFailedError e) {
+                    println 'Expected Result:'
+                    println expectedResult
+                    println 'Actual Result:'
+                    println result.output
+                    println '---'
+                    throw e
+                }
+            }
+
+            run.files.each { path ->
+                println "  checking file '$path' exists"
+                File file = new File(rootProjectDir, path).canonicalFile
+                Assert.assertTrue("Expected file '$file' does not exist.", file.exists())
+                Assert.assertTrue("Expected file '$file' is not a file.", file.isFile())
+            }
+            run.dirs.each { path ->
+                println "  checking directory '$path' exists"
+                File file = new File(rootProjectDir, path).canonicalFile
+                Assert.assertTrue("Expected directory '$file' does not exist.", file.exists())
+                Assert.assertTrue("Expected directory '$file' is not a directory.", file.isDirectory())
+            }
+        } catch (Throwable e) {
+            throw new AssertionError("Integration test for sample '$run.id' in dir '$run.subDir' with args $run.args failed:${NL}$e.message").initCause(e)
+        }
+    }
+
+    private def compareStrings(String expected, String actual, boolean ignoreExtraLines) {
+        List actualLines = normaliseOutput(actual.readLines())
+        List expectedLines = expected.readLines()
+        int pos = 0
+        for (; pos < actualLines.size() && pos < expectedLines.size(); pos++) {
+            String expectedLine = expectedLines[pos]
+            String actualLine = actualLines[pos]
+            boolean matches = compare(expectedLine, actualLine)
+            if (!matches) {
+                if (expectedLine.contains(actualLine)) {
+                    Assert.fail("Missing text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+                }
+                if (actualLine.contains(expectedLine)) {
+                    Assert.fail("Extra text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+                }
+                Assert.fail("Unexpected value at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+            }
+        }
+        if (pos == actualLines.size() && pos < expectedLines.size()) {
+            Assert.fail("Lines missing from actual result, starting at line ${pos + 1}.${NL}Expected: ${expectedLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
+        }
+        if (!ignoreExtraLines && pos < actualLines.size() && pos == expectedLines.size()) {
+            Assert.fail("Extra lines in actual result, starting at line ${pos + 1}.${NL}Actual: ${actualLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
+        }
+    }
+
+    static String replaceWithPlatformNewLines(String text) {
+        StringWriter stringWriter = new StringWriter()
+        new PlatformLineWriter(stringWriter).withWriter { it.write(text) }
+        stringWriter.toString()
+    }
+
+    List<String> normaliseOutput(List<String> lines) {
+        if (lines.empty) {
+            return lines;
+        }
+        List<String> result = new ArrayList<String>()
+        for (String line : lines) {
+            if (line.matches('Download .+')) {
+                // ignore
+            } else {
+                result << line
+            }
+        }
+        return result
+    }
+
+    boolean compare(String expected, String actual) {
+        if (actual == expected) {
+            return true
+        }
+
+        if (expected == 'Total time: 1 secs') {
+            return actual.matches('Total time: .+ secs')
+        }
+        
+        // Normalise default object toString() values
+        actual = actual.replaceAll('(\\w+(\\.\\w+)*)@\\p{XDigit}+', '$1 at 12345')
+        // Normalise $samplesDir
+        actual = actual.replaceAll(java.util.regex.Pattern.quote(dist.samplesDir.absolutePath), '/home/user/gradle/samples')
+        // Normalise file separators
+        actual = actual.replaceAll(java.util.regex.Pattern.quote(File.separator), '/')
+
+        return actual == expected
+    }
+
+    static Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
+        def samplesXml = new File(userguideInfoDir, 'samples.xml')
+        assertSamplesGenerated(samplesXml.exists())
+        Node samples = new XmlParser().parse(samplesXml)
+        ListMultimap<String, GradleRun> samplesByDir = ArrayListMultimap.create()
+
+        def children = samples.children()
+        assertSamplesGenerated(!children.isEmpty())
+
+        children.each {Node sample ->
+            String id = sample.'@id'
+            String dir = sample.'@dir'
+            String args = sample.'@args'
+            String outputFile = sample.'@outputFile'
+            boolean ignoreExtraLines = Boolean.valueOf(sample.'@ignoreExtraLines')
+
+            GradleRun run = new GradleRun(id: id)
+            run.subDir = dir
+            run.args = args ? args.split('\\s+') as List : []
+            run.outputFile = outputFile
+            run.ignoreExtraLines = ignoreExtraLines as boolean
+
+            sample.file.each { file -> run.files << file.'@path' }
+            sample.dir.each { file -> run.dirs << file.'@path' }
+
+            samplesByDir.put(dir, run)
+        }
+
+        // Some custom values
+        samplesByDir.get('userguide/tutorial/properties').each { it.envs['ORG_GRADLE_PROJECT_envProjectProp'] = 'envPropertyValue' }
+        samplesByDir.get('userguide/buildlifecycle/taskExecutionEvents')*.expectFailure = true
+        samplesByDir.get('userguide/buildlifecycle/buildProjectEvaluateEvents')*.expectFailure = true
+
+        Map<String, SampleRun> samplesById = new TreeMap<String, SampleRun>()
+
+        // Remove duplicates for a given directory.
+        samplesByDir.asMap().values().collect {List<GradleRun> dirSamples ->
+            Collection<GradleRun> runs = dirSamples.findAll {it.mustRun}
+            if (!runs) {
+                // No samples in this dir have any args, so just run gradle tasks in the dir
+                def sample = dirSamples[0]
+                sample.args = ['tasks']
+                sample
+            } else {
+                return runs
+            }
+        }.flatten().each { GradleRun run ->
+            // Collect up into sample runs
+            SampleRun sampleRun = samplesById[run.id]
+            if (!sampleRun) {
+                sampleRun = new SampleRun(id: run.id, subDir: run.subDir)
+                samplesById[run.id] = sampleRun
+            }
+            sampleRun.runs << run
+        }
+
+        return samplesById.values()
+    }
+
+    static void assertSamplesGenerated(boolean assertion) {
+        assert assertion : """Couldn't find any samples. Most likely, samples.xml was not generated.
+Please run 'gradle docs:userguideDocbook' first"""
+    }
+}
+
+class SampleRun {
+    String id
+    String subDir
+    List<GradleRun> runs = []
+}
+
+class GradleRun {
+    String id
+    List args = []
+    String subDir
+    Map envs = [:]
+    String outputFile
+    boolean expectFailure
+    boolean ignoreExtraLines
+    List files = []
+    List dirs = []
+
+    boolean getMustRun() {
+        return args || files || dirs
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/UsesSample.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UsesSample.java
similarity index 100%
rename from subprojects/core/src/integTest/groovy/org/gradle/integtests/fixtures/UsesSample.java
rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UsesSample.java
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy
new file mode 100644
index 0000000..59ce7a0
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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 java.util.regex.Pattern
+
+abstract class WellBehavedPluginTest extends AbstractIntegrationSpec {
+
+    String getPluginId() {
+        def matcher = Pattern.compile("(\\w+)Plugin(GoodBehaviour)?(Integration)?Test").matcher(getClass().simpleName)
+        if (matcher.matches()) {
+            return matcher.group(1).toLowerCase()
+        }
+        throw new UnsupportedOperationException("Cannot determine plugin id from class name '${getClass().simpleName}.")
+    }
+    
+    String getMainTask() {
+        return "assemble"
+    }
+
+    def "plugin does not force creation of build dir during configuration"() {
+        given:
+        buildFile << "apply plugin: '${getPluginId()}'"
+
+        when:
+        run "tasks"
+
+        then:
+        !file("build").exists()
+    }
+
+    def "plugin can build with empty project"() {
+        given:
+        buildFile << "apply plugin: '${getPluginId()}'"
+
+        expect:
+        succeeds mainTask
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionsInfo.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionsInfo.groovy
new file mode 100644
index 0000000..907874c
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/versions/VersionsInfo.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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.versions
+
+import groovy.json.JsonSlurper
+import org.gradle.util.GradleVersion
+import static org.gradle.util.GradleVersion.version
+
+/**
+ * by Szczepan Faber, created at: 3/12/12
+ */
+class VersionsInfo {
+    
+    String lowestInterestingVersion = "0.8"
+    List<String> excludedVersions = ["0.9-rc-1", "0.9-rc-2", "1.0-milestone-4", GradleVersion.current().version]
+
+    Closure getVersionsJson = {
+        def fileName = "all-released-versions.json"
+        def resource = getClass().classLoader.getResource(fileName)
+        if (resource == null) {
+            throw new RuntimeException("""Unable to find the released versions information.
+The resource '$fileName' was not found.
+Most likely, you haven't ran the 'prepareVersionsInfo' task.
+If you have trouble running tests from your IDE, please run gradlew idea|eclipse first.
+""")
+        }
+        def jsonText = resource.text
+        return new JsonSlurper().parseText(jsonText)
+    }
+
+    /**
+     * @return the versions we are interested in covering in our cross-compatibility tests.
+     * Always contains the current release. May contain the RC. Never contains nightly.
+     * Excludes 'current' version. Excludes certain less-interesting versions. The list is ordered,
+     * latest version first.
+     */
+    List<String> getVersions() {
+        def json = getVersionsJson()
+
+        def rc = json.find { it.rcFor }
+        def current = json.find { it.current }
+
+        def out = new LinkedHashSet<String>()
+
+        //exclude nightly and old versions
+        def releases = json.findAll { !it.nightly && version(it.version) >= version(lowestInterestingVersion) &&
+                !excludedVersions.contains(it.version) }
+
+        out += releases.collect { it.version }
+
+        if (rc) {
+            if (rc.rcFor == current.version) {
+                //RC released - remove from list:
+                out.remove(rc.version)
+            } else {
+                //if the RC has not been already released it should be higher then the current
+                assert version(rc.version) > version(current.version)
+            }
+        }
+
+        def sorted = out.sort( { a,b -> version(b).compareTo(version(a)) })
+        new LinkedList(sorted)
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/resources/sshd-config/test-dsa.key b/subprojects/internal-integ-testing/src/main/resources/sshd-config/test-dsa.key
new file mode 100644
index 0000000..98db71a
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/resources/sshd-config/test-dsa.key
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDJhbBmBIlrSeF4YKcP7cB38Qf+lxlJsZVBU+OA8bJo5XsJ6g7x
+hGkWGUbv/DjkNBnRvB9nQmFho/pXDNAhz/9tjIqmZifMqVn7yVfIYgNWW6eekzEv
+qr1LWIvzm97PRLgNOUwWNp9qHYVg4srZxC1u5F5a+Su04rDMUtvnSwipQwIVAJ9N
+DIm1l3DXczHUSSWT1jvRUfdlAoGBALzjClBUBz/XfmMI6Wy2VwATXbI0DPkh7Abn
+kNFWQCvm3ob9WTTYlKiRtoo1d71BW/7WB3fWvPahJAPfAxpI8v3k5KibQqpcXjj6
+Bo2opzKidLMnpduWyectUw8JiKdGq4gXrgjIU6ucrm/MahAK8xFQhAxHf3PJE0fz
+XByjSMC8AoGBAMAD1PkgtwK/sl3mji9bQDofUurHof1aQh/7q91TdYlhY/HBXpv5
+rs09sOCCAsRK2yaEcsU967xiMJ4zeABGt3uXBeddG+540JlcBqSl45RaJJcO6AH3
+hVk7TAy1pLgMhpOt0XE1MylRF8cY9VfZ9Thj56jWylCDv4m8JaRYBpxrAhRNOllu
+5nUYR0Te6j8XEufluVJ64Q==
+-----END DSA PRIVATE KEY-----
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionsInfoTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionsInfoTest.groovy
new file mode 100644
index 0000000..12edfbe
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/versions/VersionsInfoTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012 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.versions
+
+import org.gradle.util.GradleVersion
+import spock.lang.Specification
+import static org.gradle.util.GradleVersion.version
+
+/**
+ * by Szczepan Faber, created at: 3/12/12
+ */
+class VersionsInfoTest extends Specification {
+
+    def info = new VersionsInfo()
+
+    def "reads versions information from the actual resource"() {
+        expect:
+        !info.versions.empty
+        info.versions.size() > 5
+        info.versions.find { "1.0-milestone-3"  }
+        info.versions.find { "1.0-milestone-8a" }
+    }
+
+    def "versions are ordered latest first"() {
+        expect:
+        version(info.versions[0]) > version(info.versions[1])
+        info.versions == info.versions.sort( { a,b -> version(b).compareTo(version(a)) })
+    }
+    
+    def "contains release candidate if not yet released"() {
+        when:
+        info.getVersionsJson = {[
+            [version: "1.0-milestone-8a", current:true, nightly:false, rcFor:null],
+            [version: "1.0-milestone-9-20120309103546+0100", current:false, nightly:false, rcFor:"1.0-milestone-9"],
+            [version: "1.0-rc-1-20120312000043+0100", current:false, nightly:true, rcFor:null],
+            [version: "1.0-milestone-8", current:false, nightly:false, rcFor:null],
+            [version: "1.0-milestone-7", current:false, nightly:false, rcFor:null]
+        ]}
+
+        then:
+        info.versions == ["1.0-milestone-9-20120309103546+0100", "1.0-milestone-8a", "1.0-milestone-8", "1.0-milestone-7"]
+    }
+
+    def "excludes release candidate if already released"() {
+        when:
+        info.getVersionsJson = {[
+            [version: "1.0-milestone-9", current:true, nightly:false, rcFor:null],
+            [version: "1.0-milestone-9-20120309103546+0100", current:false, nightly:false, rcFor:"1.0-milestone-9"],
+            [version: "1.0-rc-1-20120312000043+0100", current:false, nightly:true, rcFor:null],
+            [version: "1.0-milestone-8", current:false, nightly:false, rcFor:null],
+            [version: "1.0-milestone-7", current:false, nightly:false, rcFor:null]
+        ]}
+
+        then:
+        info.versions == ["1.0-milestone-9", "1.0-milestone-8", "1.0-milestone-7"]
+    }
+
+    def "excludes certain versions"() {
+        given:
+        info.lowestInterestingVersion = "1.0-milestone-7"
+        info.excludedVersions = ["1.0-milestone-8"]
+
+        when:
+        info.getVersionsJson = {[
+            [version: "1.0-milestone-9", current:true, nightly:false, rcFor:null],
+            [version: "1.0-milestone-8", current:false, nightly:false, rcFor:null],
+            [version: "1.0-milestone-7", current:false, nightly:false, rcFor:null],
+            [version: "1.0-milestone-6", current:false, nightly:false, rcFor:null],
+            [version: "1.0-milestone-5", current:false, nightly:false, rcFor:null]
+        ]}
+
+        then:
+        info.versions == ["1.0-milestone-9", "1.0-milestone-7"]
+    }
+
+    def "excludes current version"() {
+        when:
+        info.getVersionsJson = {[
+                [version: GradleVersion.current().version, current:false, nightly:false, rcFor:null]
+        ]}
+
+        then:
+        info.versions == []
+    }
+}
diff --git a/subprojects/internal-testing/internal-testing.gradle b/subprojects/internal-testing/internal-testing.gradle
new file mode 100644
index 0000000..ba2dd9f
--- /dev/null
+++ b/subprojects/internal-testing/internal-testing.gradle
@@ -0,0 +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.
+ */
+
+/*
+    Provides generally useful test utilities, used for unit and integration testing.
+*/
+apply from: "$rootDir/gradle/classycle.gradle"
+
+dependencies {
+    groovy libraries.groovy
+
+    compile project(":baseServices")
+    compile project(":native")
+    compile libraries.commons_lang
+    compile libraries.commons_io
+    compile libraries.ant
+    compile libraries.junit
+    compile libraries.jmock
+    compile libraries.spock
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/ExceptionAssert.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/ExceptionAssert.groovy
new file mode 100644
index 0000000..aeb26b5
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/ExceptionAssert.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.testing.internal.util
+
+/**
+ * Helps testing if the exception messages chain (including causes)
+ * contain given information.
+ * <p>
+ * by Szczepan Faber, created at: 3/6/12
+ */
+public class ExceptionAssert {
+
+    private final Throwable target
+
+    public ExceptionAssert(Throwable target) {
+        this.target = target
+    }
+
+    public static assertThat(Throwable t) {
+        return new ExceptionAssert(t);
+    }
+
+    /**
+     * Asserts if given exception message/ exception classname contains given information (substring)
+     * Recursively checks the cause chain.
+     *
+     * @param information wanted substring
+     */
+    void containsInfo(String information) {
+        def ex = target
+        def checked = []
+        while(ex != null) {
+            checked << ex.toString()
+            if (ex.toString().contains(information)) {
+                return
+            }
+            
+            ex = ex.cause
+        }
+        throw new AssertionError((Object) "Unable to find '$information' in the exception messages chain."
+            + "\nTested following messages: \n------\n > ${checked.join('\n > ')}\n------")
+    }
+
+    String toString() {
+        return "target=$target"
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/IdeQuickCheckRunner.java b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/IdeQuickCheckRunner.java
new file mode 100644
index 0000000..4146918
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/IdeQuickCheckRunner.java
@@ -0,0 +1,68 @@
+/*
+ * 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.testing.internal.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+public class IdeQuickCheckRunner {
+    public static void main(String[] args) {
+        Process process = null;
+
+        try {
+            ProcessBuilder builder = new ProcessBuilder().command("./gradlew", "quickCheck", "--daemon");
+            process = builder.start();
+            final Process finalProcess = process;
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                public void run() {
+                    finalProcess.destroy();
+                }
+            }));
+            forwardAsync(process.getInputStream(), System.out);
+            forwardAsync(process.getErrorStream(), System.err);
+            process.waitFor();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            process.destroy();
+        }
+    }
+    
+    private static void forwardAsync(final InputStream input, final OutputStream output) {
+        new Thread(new Runnable() {
+            public void run() {
+                int bufferSize = 4096;
+                byte[] buffer = new byte[bufferSize];
+
+                int read = 0;
+                try {
+                    read = input.read(buffer);
+                    while(read != -1) {
+                        output.write(buffer, 0, read);
+                        read = input.read(buffer);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace(new PrintWriter(output));
+                }
+            }
+        }).start();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/Network.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/Network.groovy
new file mode 100644
index 0000000..3475ac1
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/Network.groovy
@@ -0,0 +1,29 @@
+/*
+ * 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.testing.internal.util
+
+class Network {
+    
+    static boolean isOffline() {
+        try {
+            new URL("http://google.com").openConnection().openStream()
+            false
+        } catch (IOException) {
+            true
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/EmptyStatement.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/EmptyStatement.groovy
new file mode 100644
index 0000000..4e67942
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/EmptyStatement.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.util
+
+import org.junit.runners.model.Statement
+
+class EmptyStatement extends Statement {
+    public static final INSTANCE = new EmptyStatement()
+
+    @Override
+    void evaluate() {}
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/FailsWithMessage.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/FailsWithMessage.java
new file mode 100644
index 0000000..b617f1f
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/FailsWithMessage.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.util;
+
+import org.spockframework.runtime.extension.ExtensionAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.TYPE})
+ at ExtensionAnnotation(FailsWithMessageExtension.class)
+public @interface FailsWithMessage {
+    Class<? extends Throwable> type();
+
+    String message();
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/FailsWithMessageExtension.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/FailsWithMessageExtension.java
new file mode 100644
index 0000000..679ce12
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/FailsWithMessageExtension.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.util;
+
+import junit.framework.ComparisonFailure;
+import org.spockframework.runtime.WrongExceptionThrownError;
+import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension;
+import org.spockframework.runtime.extension.IMethodInterceptor;
+import org.spockframework.runtime.extension.IMethodInvocation;
+import org.spockframework.runtime.model.FeatureInfo;
+
+public class FailsWithMessageExtension extends AbstractAnnotationDrivenExtension<FailsWithMessage> {
+    @Override
+    public void visitFeatureAnnotation(FailsWithMessage annotation, FeatureInfo feature) {
+        feature.getFeatureMethod().addInterceptor(new FailsWithMessageInterceptor(annotation));
+    }
+
+    private class FailsWithMessageInterceptor implements IMethodInterceptor {
+        private final FailsWithMessage annotation;
+
+        public FailsWithMessageInterceptor(FailsWithMessage annotation) {
+            this.annotation = annotation;
+        }
+
+        public void intercept(IMethodInvocation invocation) throws Throwable {
+            try {
+                invocation.proceed();
+            } catch (Throwable t) {
+                if (!annotation.type().isInstance(t)) {
+                    throw new WrongExceptionThrownError(annotation.type(), t);
+                }
+                if (!annotation.message().equals(t.getMessage())) {
+                    throw new ComparisonFailure("Unexpected message for exception.", annotation.message(), t.getMessage());
+                }
+                return;
+            }
+
+            throw new WrongExceptionThrownError(annotation.type(), null);
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java
new file mode 100644
index 0000000..0adf6f3
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/JUnit4GroovyMockery.java
@@ -0,0 +1,118 @@
+/*
+ * 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.util;
+
+/**
+ * @author Hans Dockter
+ */
+
+import groovy.lang.Closure;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+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.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class JUnit4GroovyMockery extends JUnit4Mockery {
+    private final ConcurrentMap<String, AtomicInteger> names = new ConcurrentHashMap<String, AtomicInteger>();
+
+    public JUnit4GroovyMockery() {
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }
+
+    @Override
+    public <T> T mock(Class<T> typeToMock) {
+        return mock(typeToMock, typeToMock.getSimpleName());
+    }
+
+    @Override
+    public <T> T mock(Class<T> typeToMock, String name) {
+        names.putIfAbsent(name, new AtomicInteger());
+        int count = names.get(name).getAndIncrement();
+        T mock;
+        if (count == 0) {
+            mock = super.mock(typeToMock, name);
+        } else {
+            mock = super.mock(typeToMock, name + count);
+        }
+        if (mock instanceof ClassLoader) {
+            for (Field field : ClassLoader.class.getDeclaredFields()) {
+                if (field.getName().equals("initialized")) {
+                    field.setAccessible(true);
+                    try {
+                        field.set(mock, true);
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException(e);
+                    }
+                    break;
+                }
+            }
+        }
+        return mock;
+    }
+
+    class ClosureExpectations extends Expectations {
+        void closureInit(Closure cl, Object delegate) {
+            cl.setDelegate(delegate);
+            cl.call();
+        }
+
+        <T> void withParam(Matcher<T> matcher) {
+            this.with(matcher);
+        }
+
+        void will(final Closure cl) {
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("execute closure");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    List<Object> params = Arrays.asList(invocation.getParametersAsArray());
+                    Object result;
+                    try {
+                        List<Object> subParams = params.subList(0, Math.min(invocation.getParametersAsArray().length,
+                                cl.getMaximumNumberOfParameters()));
+                        result = cl.call(subParams.toArray(new Object[subParams.size()]));
+                    } catch (InvokerInvocationException e) {
+                        throw e.getCause();
+                    }
+                    if (invocation.getInvokedMethod().getReturnType().isInstance(result)) {
+                        return result;
+                    }
+                    return null;
+                }
+            });
+        }
+    }
+
+    public void checking(Closure c) {
+        ClosureExpectations expectations = new ClosureExpectations();
+        expectations.closureInit(c, expectations);
+        super.checking(expectations);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/PreconditionVerifier.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/PreconditionVerifier.groovy
new file mode 100644
index 0000000..078b28d
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/PreconditionVerifier.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.util
+
+import org.junit.rules.TestRule
+import org.junit.runners.model.Statement
+import org.junit.runner.Description
+
+class PreconditionVerifier implements TestRule {
+    Statement apply(Statement base, Description description) {
+        def preconditions = description.annotations.findAll { it instanceof Requires }*.value().flatten()
+        preconditions.every { it.fulfilled } ? base : EmptyStatement.INSTANCE
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java
new file mode 100644
index 0000000..aad2be1
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdIn.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.util;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.InputStream;
+
+/**
+ * A Junit rule which restores System.in at the end of the test.
+ */
+public class RedirectStdIn implements MethodRule {
+    private InputStream originalStdIn;
+
+    public Statement apply(final Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                originalStdIn = System.in;
+                try {
+                    base.evaluate();
+                } finally {
+                    System.setIn(originalStdIn);
+                    originalStdIn = null;
+                }
+            }
+        };
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/RedirectStdOutAndErr.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdOutAndErr.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/util/RedirectStdOutAndErr.java
rename to subprojects/internal-testing/src/main/groovy/org/gradle/util/RedirectStdOutAndErr.java
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ReflectionEqualsMatcher.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/ReflectionEqualsMatcher.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/util/ReflectionEqualsMatcher.java
rename to subprojects/internal-testing/src/main/groovy/org/gradle/util/ReflectionEqualsMatcher.java
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy
new file mode 100644
index 0000000..35d7f36
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Requires.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.util
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Target
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Retention
+
+import org.spockframework.runtime.extension.ExtensionAnnotation
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target([ElementType.METHOD, ElementType.TYPE])
+ at ExtensionAnnotation(TestPreconditionExtension.class)
+public @interface Requires {
+    TestPrecondition[] value()
+}
+
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
new file mode 100755
index 0000000..df582ca
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/Resources.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import static org.junit.Assert.*;
+
+/**
+ * A JUnit rule which helps locate test resources.
+ */
+public class Resources implements MethodRule {
+    private Class<?> testClass;
+
+    /**
+     * Locates the resource with the given name, relative to the current test class. Asserts that the resource exists.
+     */
+    public TestFile getResource(String name) {
+        assertNotNull(testClass);
+        TestFile file = findResource(name);
+        assertNotNull(String.format("Could not locate resource '%s' for test class %s.", name, testClass.getName()), file);
+        return file;
+    }
+
+    /**
+     * Locates the resource with the given name, relative to the current test class.
+     * @return the resource, or null if not found.
+     */
+    public TestFile findResource(String name) {
+        assertNotNull(testClass);
+        URL resource = testClass.getResource(name);
+        if (resource == null) {
+            return null;
+        }
+        assertEquals("file", resource.getProtocol());
+        File file;
+        try {
+            file = new File(resource.toURI());
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+        return new TestFile(file);
+    }
+
+    public Statement apply(final Statement statement, FrameworkMethod frameworkMethod, Object o) {
+        testClass = frameworkMethod.getMethod().getDeclaringClass();
+        return statement;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/SetSystemProperties.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/SetSystemProperties.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/util/SetSystemProperties.java
rename to subprojects/internal-testing/src/main/groovy/org/gradle/util/SetSystemProperties.java
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TemporaryFolder.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TemporaryFolder.java
new file mode 100644
index 0000000..238603d
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TemporaryFolder.java
@@ -0,0 +1,114 @@
+/*
+ * 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.util;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A JUnit rule which provides a unique temporary folder for the test.
+ */
+public class TemporaryFolder implements MethodRule, TestFileContext {
+    private TestFile dir;
+    private String prefix;
+    private static TestFile root;
+    private static AtomicInteger testCounter = new AtomicInteger(1);
+
+    static {
+        // NOTE: the space in the directory name is intentional
+        root = new TestFile(new File("build/tmp/test files"));
+    }
+
+    public TestFile getDir() {
+        if (dir == null) {
+            if (prefix == null) {
+                // This can happen if this is used in a constructor or a @Before method. It also happens when using
+                // @RunWith(SomeRunner) when the runner does not support rules.
+                prefix = determinePrefix();
+            }
+            for (int counter = 1; true; counter++) {
+                dir = root.file(counter == 1 ? prefix : String.format("%s%d", prefix, counter));
+                if (dir.mkdirs()) {
+                    break;
+                }
+            }
+        }
+        return dir;
+    }
+
+    private String determinePrefix() {
+        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
+        for (StackTraceElement element : stackTrace) {
+            if (element.getClassName().endsWith("Test") || element.getClassName().endsWith("Spec")) {
+                return StringUtils.substringAfterLast(element.getClassName(), ".") + "/unknown-test-" + testCounter.getAndIncrement();
+            }
+        }
+        return "unknown-test-class-" + testCounter.getAndIncrement();
+    }
+
+    public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
+        init(method, target);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                getDir().maybeDeleteDir();
+                // Don't delete on failure
+            }
+        };
+    }
+
+    private void init(FrameworkMethod method, Object target) {
+        if (prefix == null) {
+            String safeMethodName = method.getName().replaceAll("\\s", "_").replace(File.pathSeparator, "_").replace(":", "_");
+            if (safeMethodName.length() > 64) {
+                safeMethodName = safeMethodName.substring(0, 32) + "..." + safeMethodName.substring(safeMethodName.length() - 32);
+            }
+            prefix = String.format("%s/%s", target.getClass().getSimpleName(), safeMethodName);
+        }
+    }
+
+    public static TemporaryFolder newInstance() {
+        return new TemporaryFolder();
+    }
+
+    public static TemporaryFolder newInstance(FrameworkMethod method, Object target) {
+        TemporaryFolder temporaryFolder = new TemporaryFolder();
+        temporaryFolder.init(method, target);
+        return temporaryFolder;
+    }
+
+    public TestFile getTestDir() {
+        return getDir();
+    }
+
+    public TestFile file(Object... path) {
+        return getDir().file((Object[]) path);
+    }
+
+    public TestFile createFile(Object... path) {
+        return file((Object[]) path).createFile();
+    }
+
+    public TestFile createDir(Object... path) {
+        return file((Object[]) path).createDir();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TestDirHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/util/TestDirHelper.groovy
rename to subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
new file mode 100644
index 0000000..ee8c154
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
@@ -0,0 +1,553 @@
+/*
+ * 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.util;
+
+import groovy.lang.Closure;
+import org.apache.commons.io.FileUtils;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Tar;
+import org.apache.tools.ant.taskdefs.Zip;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.hamcrest.Matcher;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import static org.junit.Assert.*;
+
+public class TestFile extends File implements TestFileContext {
+    private boolean useNativeTools;
+
+    public TestFile(File file, Object... path) {
+        super(join(file, path).getAbsolutePath());
+    }
+
+    public TestFile(URI uri) {
+        this(new File(uri));
+    }
+
+    public TestFile(String path) {
+        this(new File(path));
+    }
+
+    public TestFile(URL url) {
+        this(toUri(url));
+    }
+
+    public TestFile getTestDir() {
+        return this;
+    }
+
+    public TestFile usingNativeTools() {
+        useNativeTools = true;
+        return this;
+    }
+
+    Object writeReplace() throws ObjectStreamException {
+        return new File(getAbsolutePath());
+    }
+
+    private static URI toUri(URL url) {
+        try {
+            return url.toURI();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static File join(File file, Object[] path) {
+        File current = file.getAbsoluteFile();
+        for (Object p : path) {
+            current = new File(current, p.toString());
+        }
+        try {
+            return current.getCanonicalFile();
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not canonicalise '%s'.", current), e);
+        }
+    }
+
+    public TestFile file(Object... path) {
+        try {
+            return new TestFile(this, path);
+        } catch (RuntimeException e) {
+            throw new RuntimeException(String.format("Could not locate file '%s' relative to '%s'.", Arrays.toString(path), this), e);
+        }
+    }
+
+    public List<TestFile> files(Object... paths) {
+        List<TestFile> files = new ArrayList<TestFile>();
+        for (Object path : paths) {
+            files.add(file(path));
+        }
+        return files;
+    }
+
+    public TestFile writelns(String... lines) {
+        return writelns(Arrays.asList(lines));
+    }
+
+    public TestFile write(Object content) {
+        try {
+            FileUtils.writeStringToFile(this, content.toString());
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not write to test file '%s'", this), e);
+        }
+        return this;
+    }
+
+    public TestFile leftShift(Object content) {
+        getParentFile().mkdirs();
+        try {
+            DefaultGroovyMethods.leftShift(this, content);
+            return this;
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not append to test file '%s'", this), e);
+        }
+    }
+
+    public String getText() {
+        assertIsFile();
+        try {
+            return FileUtils.readFileToString(this);
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not read from test file '%s'", this), e);
+        }
+    }
+
+    public Map<String, String> getProperties() {
+        assertIsFile();
+        Properties properties = new Properties();
+        try {
+            FileInputStream inStream = new FileInputStream(this);
+            try {
+                properties.load(inStream);
+            } finally {
+                inStream.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        Map<String, String> map = new HashMap<String, String>();
+        for (Object key : properties.keySet()) {
+            map.put(key.toString(), properties.getProperty(key.toString()));
+        }
+        return map;
+    }
+
+    public Manifest getManifest() {
+        assertIsFile();
+        try {
+            JarFile jarFile = new JarFile(this);
+            try {
+                return jarFile.getManifest();
+            } finally {
+                jarFile.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public List<String> linesThat(Matcher<? super String> matcher) {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(this));
+            try {
+                List<String> lines = new ArrayList<String>();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    if (matcher.matches(line)) {
+                        lines.add(line);
+                    }
+                }
+                return lines;
+            } finally {
+                reader.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void unzipTo(File target) {
+        assertIsFile();
+        new TestFileHelper(this).unzipTo(target, useNativeTools);
+    }
+
+    public void untarTo(File target) {
+        assertIsFile();
+
+        new TestFileHelper(this).untarTo(target, useNativeTools);
+    }
+
+    public void copyTo(File target) {
+        if (isDirectory()) {
+            try {
+                FileUtils.copyDirectory(this, target);
+            } catch (IOException e) {
+                throw new RuntimeException(String.format("Could not copy test directory '%s' to '%s'", this,
+                        target), e);
+            }
+        } else {
+            try {
+                FileUtils.copyFile(this, target);
+            } catch (IOException e) {
+                throw new RuntimeException(String.format("Could not copy test file '%s' to '%s'", this, target), e);
+            }
+        }
+    }
+
+    public void copyFrom(File target) {
+        new TestFile(target).copyTo(this);
+    }
+
+    public void copyFrom(URL resource) {
+        try {
+            FileUtils.copyURLToFile(resource, this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public void moveToDirectory(File target) {
+        if (target.exists() && !target.isDirectory()) {
+                throw new RuntimeException(String.format("Target '%s' is not a directory", target));
+        }
+        try {
+            FileUtils.moveFileToDirectory(this, target, true);
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not move test file '%s' to directory '%s'", this, target), e);
+        }
+    }
+
+    public TestFile touch() {
+        try {
+            FileUtils.touch(this);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        assertIsFile();
+        return this;
+    }
+
+    /**
+     * Creates a directory structure specified by the given closure.
+     * <pre>
+     * dir.create {
+     *     subdir1 {
+     *        file 'somefile.txt'
+     *     }
+     *     subdir2 { nested { file 'someFile' } }
+     * }
+     * </pre>
+     */
+    public TestFile create(Closure structure) {
+        assertTrue(isDirectory() || mkdirs());
+        new TestDirHelper(this).apply(structure);
+        return this;
+    }
+
+    @Override
+    public TestFile getParentFile() {
+        return super.getParentFile() == null ? null : new TestFile(super.getParentFile());
+    }
+
+    @Override
+    public String toString() {
+        return getPath();
+    }
+
+    public TestFile writelns(Iterable<String> lines) {
+        Formatter formatter = new Formatter();
+        for (String line : lines) {
+            formatter.format("%s%n", line);
+        }
+        return write(formatter);
+    }
+
+    public TestFile assertExists() {
+        assertTrue(String.format("%s does not exist", this), exists());
+        return this;
+    }
+
+    public TestFile assertIsFile() {
+        assertTrue(String.format("%s is not a file", this), isFile());
+        return this;
+    }
+
+    public TestFile assertIsDir() {
+        assertTrue(String.format("%s is not a directory", this), isDirectory());
+        return this;
+    }
+
+    public TestFile assertDoesNotExist() {
+        assertFalse(String.format("%s should not exist", this), exists());
+        return this;
+    }
+
+    public TestFile assertContents(Matcher<String> matcher) {
+        assertThat(getText(), matcher);
+        return this;
+    }
+
+    public TestFile assertIsCopyOf(TestFile other) {
+        assertIsFile();
+        other.assertIsFile();
+        assertEquals(String.format("%s is not the same length as %s", this, other), other.length(), this.length());
+        assertTrue(String.format("%s does not have the same content as %s", this, other), Arrays.equals(getHash("MD5"), other.getHash("MD5")));
+        return this;
+    }
+
+    private byte[] getHash(String algorithm) {
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
+            messageDigest.update(FileUtils.readFileToByteArray(this));
+            return messageDigest.digest();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public TestFile assertPermissions(Matcher<String> matcher) {
+        assertThat(String.format("mismatched permissions for '%s'", this), getPermissions(), matcher);
+        return this;
+    }
+
+    public String readLink() {
+        assertExists();
+        return new TestFileHelper(this).readLink();
+    }
+
+    public String getPermissions() {
+        assertExists();
+        return new TestFileHelper(this).getPermissions();
+    }
+
+    public TestFile setPermissions(String permissions) {
+        assertExists();
+        new TestFileHelper(this).setPermissions(permissions);
+        return this;
+    }
+
+    public int getMode() {
+        assertExists();
+        return new TestFileHelper(this).getMode();
+    }
+
+    /**
+     * Asserts that this file contains exactly the given set of descendants.
+     */
+    public TestFile assertHasDescendants(String... descendants) {
+        Set<String> actual = new TreeSet<String>();
+        assertIsDir();
+        visit(actual, "", this);
+        Set<String> expected = new TreeSet<String>(Arrays.asList(descendants));
+
+        Set<String> extras = new TreeSet<String>(actual);
+        extras.removeAll(expected);
+        Set<String> missing = new TreeSet<String>(expected);
+        missing.removeAll(actual);
+
+        assertEquals(String.format("For dir: %s, extra files: %s, missing files: %s, expected: %s", this, extras, missing, expected), expected, actual);
+
+        return this;
+    }
+
+    public TestFile assertIsEmptyDir() {
+        if (exists()) {
+            assertIsDir();
+            assertHasDescendants();
+        }
+        return this;
+    }
+
+    private void visit(Set<String> names, String prefix, File file) {
+        for (File child : file.listFiles()) {
+            if (child.isFile()) {
+                names.add(prefix + child.getName());
+            } else if (child.isDirectory()) {
+                visit(names, prefix + child.getName() + "/", child);
+            }
+        }
+    }
+
+    public boolean isSelfOrDescendent(File file) {
+        if (file.getAbsolutePath().equals(getAbsolutePath())) {
+            return true;
+        }
+        return file.getAbsolutePath().startsWith(getAbsolutePath() + File.separatorChar);
+    }
+
+    public TestFile createDir() {
+        if (isDirectory()) {
+            return this;
+        }
+        if (mkdirs()) {
+            return this;
+        }
+        throw new AssertionError("Problems creating dir: " + this
+                + ". Diagnostics: exists=" + this.exists() + ", isFile=" + this.isFile() + ", isDirectory=" + this.isDirectory());
+    }
+
+    public TestFile createDir(Object path) {
+        return new TestFile(this, path).createDir();
+    }
+
+    public TestFile deleteDir() {
+        FileUtils.deleteQuietly(this);
+        return this;
+    }
+
+    /**
+     * Attempts to delete this directory, ignoring failures to do so.
+     * @return this
+     */
+    public TestFile maybeDeleteDir() {
+        try {
+            deleteDir();
+        } catch (RuntimeException e) {
+            // Ignore
+        }
+        return this;
+    }
+
+    public TestFile createFile() {
+        new TestFile(getParentFile()).createDir();
+        try {
+            assertTrue(isFile() || createNewFile());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return this;
+    }
+
+    public TestFile createFile(Object path) {
+        return file(path).createFile();
+    }
+
+    public TestFile createZip(Object path) {
+        Zip zip = new Zip();
+        zip.setWhenempty((Zip.WhenEmpty) Zip.WhenEmpty.getInstance(Zip.WhenEmpty.class, "create"));
+        TestFile zipFile = file(path);
+        zip.setDestFile(zipFile);
+        zip.setBasedir(this);
+        zip.setExcludes("**");
+        execute(zip);
+        return zipFile;
+    }
+
+    public TestFile zipTo(TestFile zipFile) {
+        Zip zip = new Zip();
+        zip.setBasedir(this);
+        zip.setDestFile(zipFile);
+        execute(zip);
+        return this;
+    }
+
+    public TestFile tarTo(TestFile zipFile) {
+        Tar tar = new Tar();
+        tar.setBasedir(this);
+        tar.setDestFile(zipFile);
+        execute(tar);
+        return this;
+    }
+
+    public TestFile tgzTo(TestFile tarFile) {
+        Tar tar = new Tar();
+        tar.setBasedir(this);
+        tar.setDestFile(tarFile);
+        tar.setCompression((Tar.TarCompressionMethod) EnumeratedAttribute.getInstance(Tar.TarCompressionMethod.class, "gzip"));
+        execute(tar);
+        return this;
+    }
+
+    public TestFile tbzTo(TestFile tarFile) {
+        Tar tar = new Tar();
+        tar.setBasedir(this);
+        tar.setDestFile(tarFile);
+        tar.setCompression((Tar.TarCompressionMethod) EnumeratedAttribute.getInstance(Tar.TarCompressionMethod.class, "bzip2"));
+        execute(tar);
+        return this;
+    }
+
+    private void execute(Task task) {
+        task.setProject(new Project());
+        task.execute();
+    }
+
+    public Snapshot snapshot() {
+        assertIsFile();
+        return new Snapshot();
+    }
+
+    public void assertHasChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash));
+    }
+
+    public void assertContentsHaveNotChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
+    }
+
+    public void assertHasNotChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertEquals(String.format("last modified time of %s has changed", this), snapshot.modTime, now.modTime);
+        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
+    }
+
+    public void writeProperties(Map<?, ?> properties) {
+        Properties props = new Properties();
+        props.putAll(properties);
+        try {
+            FileOutputStream stream = new FileOutputStream(this);
+            try {
+                props.store(stream, "comment");
+            } finally {
+                stream.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    public Map<String, ?> exec(Object... args) {
+        return new TestFileHelper(this).exec(args);
+    }
+
+    public class Snapshot {
+        private final long modTime;
+        private final byte[] hash;
+
+        public Snapshot() {
+            modTime = lastModified();
+            hash = getHash("MD5");
+        }
+
+        public long lastModified() {
+            return modTime;
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TestFileContext.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileContext.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/util/TestFileContext.java
rename to subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileContext.java
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
new file mode 100755
index 0000000..7f56b7c
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
@@ -0,0 +1,156 @@
+/*
+ * 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.util
+
+import java.util.zip.ZipInputStream
+import org.apache.commons.lang.StringUtils
+import org.apache.tools.ant.taskdefs.Expand
+import org.apache.tools.ant.taskdefs.Untar
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.assertTrue
+import org.apache.tools.ant.Project
+
+class TestFileHelper {
+    TestFile file
+
+    TestFileHelper(TestFile file) {
+        this.file = file
+    }
+
+    void unzipTo(File target, boolean nativeTools) {
+        // Check that each directory in hierarchy is present
+        file.withInputStream {InputStream instr ->
+            def dirs = [] as Set
+            def zipStr = new ZipInputStream(instr)
+            def entry
+            while (entry = zipStr.getNextEntry()) {
+                if (entry.directory) {
+                    assertTrue("Duplicate directory '$entry.name'", dirs.add(entry.name))
+                }
+                if (!entry.name.contains('/')) {
+                    continue
+                }
+                def parent = StringUtils.substringBeforeLast(entry.name, '/') + '/'
+                assertTrue("Missing dir '$parent'", dirs.contains(parent))
+            }
+        }
+
+        if (nativeTools && isUnix()) {
+            def process = ['unzip', '-o', file.absolutePath, '-d', target.absolutePath].execute()
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+            return
+        }
+
+        def unzip = new Expand()
+        unzip.src = file
+        unzip.dest = target
+
+        unzip.project = new Project()
+        unzip.execute()
+    }
+
+    void untarTo(File target, boolean nativeTools) {
+        if (nativeTools && isUnix()) {
+            target.mkdirs()
+            def builder = new ProcessBuilder(['tar', '-xf', file.absolutePath])
+            builder.directory(target)
+            def process = builder.start()
+            process.consumeProcessOutput()
+            assertThat(process.waitFor(), equalTo(0))
+            return
+        }
+
+        def untar = new Untar()
+        untar.setSrc(file)
+        untar.setDest(target)
+
+        if (file.name.endsWith(".tgz")) {
+            def method = new Untar.UntarCompressionMethod()
+            method.value = "gzip"
+            untar.compression = method
+        } else if (file.name.endsWith(".tbz2")) {
+            def method = new Untar.UntarCompressionMethod()
+            method.value = "bzip2"
+            untar.compression = method
+        }
+
+        untar.project = new Project()
+        untar.execute()
+    }
+
+    private boolean isUnix() {
+        return !System.getProperty('os.name').toLowerCase().contains('windows')
+    }
+
+    String getPermissions() {
+        def process = ["ls", "-ld", file.absolutePath].execute()
+        def result = process.inputStream.text
+        def error = process.errorStream.text
+        def retval = process.waitFor()
+        if (retval != 0) {
+            throw new RuntimeException("Could not list permissions for '$file': $error")
+        }
+        def perms = result.split()[0]
+        assert perms.matches("[d\\-][rwx\\-]{9}[@\\+]?")
+        return perms.substring(1, 10)
+    }
+
+    void setPermissions(String permissions) {
+        int m = toMode(permissions)
+        def process = ["chmod", Integer.toOctalString(m), file.absolutePath].execute()
+        def error = process.errorStream.text
+        def retval = process.waitFor()
+        if (retval != 0) {
+            throw new RuntimeException("Could not set permissions for '$file': $error")
+        }
+    }
+
+    private int toMode(String permissions) {
+        int m = [6, 3, 0].inject(0) { mode, pos ->
+            mode |= permissions[9 - pos - 3] == 'r' ? 4 << pos : 0
+            mode |= permissions[9 - pos - 2] == 'w' ? 2 << pos : 0
+            mode |= permissions[9 - pos - 1] == 'x' ? 1 << pos : 0
+            return mode
+        }
+        return m
+    }
+
+    int getMode() {
+        return toMode(getPermissions())
+    }
+
+    String readLink() {
+        def process = ["readlink", file.absolutePath].execute()
+        def error = process.errorStream.text
+        def retval = process.waitFor()
+        if (retval != 0) {
+            throw new RuntimeException("Could not set permissions for '$file': $error")
+        }
+        return process.inputStream.text.trim()
+    }
+
+    Map<String, ?> exec(Object... args) {
+        def process = ([file.absolutePath] + (args as List)).execute()
+        def output = process.inputStream.text
+        def error = process.errorStream.text
+        if (process.waitFor() != 0) {
+            throw new RuntimeException("Could not execute $file. Error: $error, Output: $output")
+        }
+        return [out: output, error: error]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
new file mode 100755
index 0000000..7e9b839
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 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.util
+
+import org.gradle.internal.os.OperatingSystem
+
+enum TestPrecondition {
+    SWING({
+        !UNKNOWN_OS.fulfilled
+    }),
+    JNA({
+        !UNKNOWN_OS.fulfilled
+    }),
+    NO_JNA({
+        UNKNOWN_OS.fulfilled
+    }),
+    SYMLINKS({
+        MAC_OS_X.fulfilled || LINUX.fulfilled
+    }),
+    NO_SYMLINKS({
+        !SYMLINKS.fulfilled
+    }),
+    CASE_INSENSITIVE_FS({
+        MAC_OS_X.fulfilled || WINDOWS.fulfilled
+    }),
+    FILE_PERMISSIONS({
+        MAC_OS_X.fulfilled || LINUX.fulfilled
+    }),
+    NO_FILE_PERMISSIONS({
+        !FILE_PERMISSIONS.fulfilled
+    }),
+    SET_ENV_VARIABLE({
+        !UNKNOWN_OS.fulfilled
+    }),
+    WORKING_DIR({
+        !UNKNOWN_OS.fulfilled
+    }),
+    PROCESS_ID({
+        !UNKNOWN_OS.fulfilled
+    }),
+    NO_FILE_LOCK_ON_OPEN({
+        MAC_OS_X.fulfilled || LINUX.fulfilled
+    }),
+    WINDOWS({
+        OperatingSystem.current().windows
+    }),
+    NOT_WINDOWS({
+        !OperatingSystem.current().windows
+    }),
+    MAC_OS_X({
+        OperatingSystem.current().macOsX
+    }),
+    LINUX({
+        OperatingSystem.current().linux
+    }),
+    UNIX({
+        OperatingSystem.current().unix
+    }),
+    UNKNOWN_OS({
+        OperatingSystem.current().name == "unknown operating system"
+    }),
+    JDK5({
+        System.getProperty("java.version").startsWith("1.5")
+    }),
+    JDK6({
+        System.getProperty("java.version").startsWith("1.6")
+    }),
+    JDK7({
+        System.getProperty("java.version").startsWith("1.7")
+    }),
+    NOT_JDK7({
+        !JDK7.fulfilled
+    }),
+    JDK7_POSIX({
+        JDK7.fulfilled && NOT_WINDOWS.fulfilled
+    });
+
+    /**
+     * A predicate for testing whether the precondition is fulfilled.
+     */
+    private Closure predicate
+
+    TestPrecondition(Closure predicate) {
+        this.predicate = predicate
+    }
+
+    /**
+     * Tells if the precondition is fulfilled.
+     */
+    boolean isFulfilled() {
+        predicate()
+    }
+}
+
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy
new file mode 100644
index 0000000..858b89f
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPreconditionExtension.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.util
+
+import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
+import org.spockframework.runtime.model.SpecInfo
+import org.spockframework.runtime.model.FeatureInfo
+
+class TestPreconditionExtension extends AbstractAnnotationDrivenExtension<Requires> {
+    @Override
+    void visitSpecAnnotation(Requires annotation, SpecInfo spec) {
+        spec.skipped = annotation.value().any() { !it.fulfilled }
+    }
+
+    @Override
+    void visitFeatureAnnotation(Requires annotation, FeatureInfo feature) {
+        feature.skipped = annotation.value().any() { !it.fulfilled }
+    }
+}
diff --git a/subprojects/jetty/jetty.gradle b/subprojects/jetty/jetty.gradle
index 6b72d0c..6f54116 100644
--- a/subprojects/jetty/jetty.gradle
+++ b/subprojects/jetty/jetty.gradle
@@ -14,22 +14,31 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
 
-    compile libraries.slf4j_api,
-            libraries.jetty_depends,
-            "org.mortbay.jetty:jetty-plus:6.1.25 at jar"
+    compile libraries.slf4j_api
+    compile libraries.jetty
+    compile libraries.jetty_util
+    compile libraries.servlet_api
+    compile module("org.mortbay.jetty:jetty-plus:6.1.25") {
+        dependency libraries.jetty
+        module("org.mortbay.jetty:jetty-naming:6.1.25") {
+            dependency libraries.jetty
+        }
+    }
 
-    runtime "org.mortbay.jetty:jsp-api-2.1:6.1.14 at jar",
-            "org.mortbay.jetty:jsp-2.1:6.1.14 at jar",
-            "org.eclipse.jdt:core:3.1.1 at jar",
-            "org.mortbay.jetty:jetty-naming:6.1.25 at jar",
-            "org.mortbay.jetty:jetty-annotations:6.1.25 at jar",
-            "org.apache.geronimo.specs:geronimo-annotation_1.0_spec:1.0 at jar"
+    runtime module("org.mortbay.jetty:jsp-2.1:6.1.14") {
+        dependency "org.eclipse.jdt:core:3.1.1 at jar"
+        dependency "org.mortbay.jetty:jsp-api-2.1:6.1.14 at jar"
+        dependency libraries.jetty_util
+        dependency libraries.servlet_api
+    }
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    runtime "org.mortbay.jetty:jetty-annotations:6.1.25 at jar"
+    runtime "org.apache.geronimo.specs:geronimo-annotation_1.0_spec:1.0 at jar"
 }
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
index 304d52b..378f96d 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
@@ -22,7 +22,12 @@ import org.gradle.api.plugins.jetty.internal.ConsoleScanner;
 import org.gradle.api.plugins.jetty.internal.JettyPluginServer;
 import org.gradle.api.plugins.jetty.internal.JettyPluginWebAppContext;
 import org.gradle.api.plugins.jetty.internal.Monitor;
-import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.util.GFileUtils;
 import org.mortbay.jetty.Connector;
 import org.mortbay.jetty.RequestLog;
@@ -65,26 +70,23 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
     private File webDefaultXml;
 
     /**
-     * A web.xml file to be applied AFTER the webapp's web.xml file. Useful for applying different build profiles, eg
-     * test, production etc. Optional.
+     * A web.xml file to be applied AFTER the webapp's web.xml file. Useful for applying different build profiles, eg test, production etc. Optional.
      */
     private File overrideWebXml;
 
     /**
-     * The interval in seconds to scan the webapp for changes and restart the context if necessary. Ignored if reload is
-     * enabled. Disabled by default.
+     * The interval in seconds to scan the webapp for changes and restart the context if necessary. Ignored if reload is enabled. Disabled by default.
      */
     private int scanIntervalSeconds;
 
     /**
-     * reload can be set to either 'automatic' or 'manual' <p/> if 'manual' then the context can be reloaded by a
-     * linefeed in the console if 'automatic' then traditional reloading on changed files is enabled.
+     * reload can be set to either 'automatic' or 'manual' <p/> if 'manual' then the context can be reloaded by a linefeed in the console if 'automatic' then traditional reloading on changed files is
+     * enabled.
      */
     protected String reload;
 
     /**
-     * Location of a jetty xml configuration file whose contents will be applied before any plugin configuration.
-     * Optional.
+     * Location of a jetty xml configuration file whose contents will be applied before any plugin configuration. Optional.
      */
     private File jettyConfig;
 
@@ -99,11 +101,9 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
     private String stopKey;
 
     /**
-     * <p> Determines whether or not the server blocks when started. The default behavior (daemon = false) will cause
-     * the server to pause other processes while it continues to handle web requests. This is useful when starting the
-     * server with the intent to work with it interactively. </p><p> Often, it is desirable to let the server start and
-     * continue running subsequent processes in an automated build environment. This can be facilitated by setting
-     * daemon to true. </p>
+     * <p> Determines whether or not the server blocks when started. The default behavior (daemon = false) will cause the server to pause other processes while it continues to handle web requests.
+     * This is useful when starting the server with the intent to work with it interactively. </p><p> Often, it is desirable to let the server start and continue running subsequent processes in an
+     * automated build environment. This can be facilitated by setting daemon to true. </p>
      */
     private boolean daemon;
 
@@ -132,7 +132,7 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
     /**
      * List of Listeners for the scanner.
      */
-    protected ArrayList scannerListeners;
+    protected List<Scanner.Listener> scannerListeners;
 
     /**
      * A scanner to check ENTER hits on the console.
@@ -180,11 +180,11 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
         this.server = server;
     }
 
-    public void setScannerListeners(ArrayList listeners) {
-        this.scannerListeners = new ArrayList(listeners);
+    public void setScannerListeners(List<Scanner.Listener> listeners) {
+        this.scannerListeners = new ArrayList<Scanner.Listener>(listeners);
     }
 
-    public ArrayList getScannerListeners() {
+    public List<Scanner.Listener> getScannerListeners() {
         return this.scannerListeners;
     }
 
@@ -199,9 +199,12 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
     }
 
     public void startJettyInternal() {
+        ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class);
+        ProgressLogger progressLogger = progressLoggerFactory.newOperation(AbstractJettyRunTask.class);
+        progressLogger.setDescription("Start Jetty server");
+        progressLogger.setShortDescription("Starting Jetty");
+        progressLogger.started();
         try {
-            logger.debug("Starting Jetty Server ...");
-
             setServer(createServer());
 
             applyJettyXml();
@@ -243,11 +246,12 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
             // start Jetty
             server.start();
 
-            logger.info("Started Jetty Server");
+            if (daemon) {
+                return;
+            }
 
             if (getStopPort() != null && getStopPort() > 0 && getStopKey() != null) {
-                Monitor monitor = new Monitor(getStopPort(), getStopKey(),
-                        new Server[]{(Server) server.getProxiedObject()}, !daemon);
+                Monitor monitor = new Monitor(getStopPort(), getStopKey(), (Server) server.getProxiedObject());
                 monitor.start();
             }
 
@@ -258,16 +262,23 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
             // start the new line scanner thread if necessary
             startConsoleScanner();
 
+        } catch (Exception e) {
+            throw new GradleException("Could not start the Jetty server.", e);
+        } finally {
+            progressLogger.completed();
+        }
+
+        progressLogger = progressLoggerFactory.newOperation(AbstractJettyRunTask.class);
+        progressLogger.setDescription(String.format("Run Jetty at http://localhost:%d/%s", getHttpPort(), getContextPath()));
+        progressLogger.setShortDescription(String.format("Running at http://localhost:%d/%s", getHttpPort(), getContextPath()));
+        progressLogger.started();
+        try {
             // keep the thread going if not in daemon mode
-            if (!daemon) {
-                server.join();
-            }
+            server.join();
         } catch (Exception e) {
-            throw new GradleException("An error occurred starting the Jetty server.", e);
+            throw new GradleException("Failed to wait for the Jetty server to stop.", e);
         } finally {
-            if (!daemon) {
-                logger.info("Jetty server exiting.");
-            }
+            progressLogger.completed();
         }
     }
 
@@ -310,8 +321,7 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
     }
 
     /**
-     * Run a scanner thread on the given list of files and directories, calling stop/start on the given list of
-     * LifeCycle objects if any of the watched files change.
+     * Run a scanner thread on the given list of files and directories, calling stop/start on the given list of LifeCycle objects if any of the watched files change.
      */
     private void startScanner() {
 
@@ -474,8 +484,8 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
     }
 
     /**
-     * Specifies whether the Jetty server should run in the background. When {@code true}, this task completes as
-     * soon as the server has started. When {@code false}, this task blocks until the Jetty server is stopped.
+     * Specifies whether the Jetty server should run in the background. When {@code true}, this task completes as soon as the server has started. When {@code false}, this task blocks until the Jetty
+     * server is stopped.
      */
     public boolean isDaemon() {
         return daemon;
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
index 2d7353b..f08091b 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
@@ -18,16 +18,15 @@ package org.gradle.api.plugins.jetty;
 import org.gradle.api.Action;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
-import org.gradle.api.internal.IConventionAware;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.JavaPluginConvention;
 import org.gradle.api.plugins.WarPlugin;
 import org.gradle.api.plugins.WarPluginConvention;
-import org.gradle.api.tasks.ConventionValue;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.bundling.War;
 
 import java.io.File;
+import java.util.concurrent.Callable;
 
 /**
  * <p>A {@link Plugin} which extends the {@link WarPlugin} to add tasks which run the web application using an embedded
@@ -67,8 +66,8 @@ public class JettyPlugin implements Plugin<Project> {
         project.getTasks().withType(JettyRunWar.class, new Action<JettyRunWar>() {
             public void execute(JettyRunWar jettyRunWar) {
                 jettyRunWar.dependsOn(WarPlugin.WAR_TASK_NAME);
-                jettyRunWar.getConventionMapping().map("webApp", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                jettyRunWar.getConventionMapping().map("webApp", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return ((War) project.getTasks().getByName(WarPlugin.WAR_TASK_NAME)).getArchivePath();
                     }
                 });
@@ -84,13 +83,13 @@ public class JettyPlugin implements Plugin<Project> {
         JettyStop jettyStop = project.getTasks().add(JETTY_STOP, JettyStop.class);
         jettyStop.setDescription("Stops Jetty.");
         jettyStop.setGroup(WarPlugin.WEB_APP_GROUP);
-        jettyStop.getConventionMapping().map("stopPort", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        jettyStop.getConventionMapping().map("stopPort", new Callable<Object>() {
+            public Object call() throws Exception {
                 return jettyConvention.getStopPort();
             }
         });
-        jettyStop.getConventionMapping().map("stopKey", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        jettyStop.getConventionMapping().map("stopKey", new Callable<Object>() {
+            public Object call() throws Exception {
                 return jettyConvention.getStopKey();
             }
         });
@@ -99,18 +98,18 @@ public class JettyPlugin implements Plugin<Project> {
     private void configureJettyRun(final Project project) {
         project.getTasks().withType(JettyRun.class, new Action<JettyRun>() {
             public void execute(JettyRun jettyRun) {
-                jettyRun.getConventionMapping().map("webXml", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                jettyRun.getConventionMapping().map("webXml", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return getWebXml(project);
                     }
                 });
-                jettyRun.getConventionMapping().map("classpath", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                jettyRun.getConventionMapping().map("classpath", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return getJavaConvention(project).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
                     }
                 });
-                jettyRun.getConventionMapping().map("webAppSourceDirectory", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                jettyRun.getConventionMapping().map("webAppSourceDirectory", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return getWarConvention(project).getWebAppDir();
                     }
                 });
@@ -137,23 +136,23 @@ public class JettyPlugin implements Plugin<Project> {
         jettyTask.setDaemon(false);
         jettyTask.setReload(RELOAD_AUTOMATIC);
         jettyTask.setScanIntervalSeconds(0);
-        jettyTask.getConventionMapping().map("contextPath", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        jettyTask.getConventionMapping().map("contextPath", new Callable<Object>() {
+            public Object call() throws Exception {
                 return ((War) project.getTasks().getByName(WarPlugin.WAR_TASK_NAME)).getBaseName();
             }
         });
-        jettyTask.getConventionMapping().map("httpPort", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        jettyTask.getConventionMapping().map("httpPort", new Callable<Object>() {
+            public Object call() throws Exception {
                 return jettyConvention.getHttpPort();
             }
         });
-        jettyTask.getConventionMapping().map("stopPort", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        jettyTask.getConventionMapping().map("stopPort", new Callable<Object>() {
+            public Object call() throws Exception {
                 return jettyConvention.getStopPort();
             }
         });
-        jettyTask.getConventionMapping().map("stopKey", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        jettyTask.getConventionMapping().map("stopKey", new Callable<Object>() {
+            public Object call() throws Exception {
                 return jettyConvention.getStopKey();
             }
         });
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
index 13319a1..440ccde 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.plugins.jetty;
 
+import com.google.common.collect.Sets;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.ConfigurableFileTree;
 import org.gradle.api.file.FileCollection;
@@ -102,7 +103,7 @@ public class JettyRun extends AbstractJettyRunTask {
     /**
      * Extra scan targets as a list.
      */
-    private List<File> extraScanTargets;
+    private Set<File> extraScanTargets;
 
     private FileCollection classpath;
 
@@ -161,11 +162,11 @@ public class JettyRun extends AbstractJettyRunTask {
                 ConfigurableFileTree files = getProject().fileTree(scanTargetPattern.getDirectory());
                 files.include(scanTargetPattern.getIncludes());
                 files.exclude(scanTargetPattern.getExcludes());
-                List<File> currentTargets = getExtraScanTargets();
+                Set<File> currentTargets = getExtraScanTargets();
                 if (currentTargets != null && !currentTargets.isEmpty()) {
                     currentTargets.addAll(files.getFiles());
                 } else {
-                    setExtraScanTargets((List) files.asType(List.class));
+                    setExtraScanTargets(files.getFiles());
                 }
             }
         }
@@ -206,7 +207,7 @@ public class JettyRun extends AbstractJettyRunTask {
         scanList.add(getProject().getBuildFile());
         scanList.addAll(getClassPathFiles());
         getScanner().setScanDirs(scanList);
-        ArrayList listeners = new ArrayList();
+        List<Scanner.Listener> listeners = new ArrayList<Scanner.Listener>();
         listeners.add(new Scanner.BulkListener() {
             public void filesChanged(List changes) {
                 try {
@@ -399,12 +400,12 @@ public class JettyRun extends AbstractJettyRunTask {
         this.scanTargets = scanTargets;
     }
 
-    public List<File> getExtraScanTargets() {
+    public Set<File> getExtraScanTargets() {
         return extraScanTargets;
     }
 
-    public void setExtraScanTargets(List<File> extraScanTargets) {
-        this.extraScanTargets = extraScanTargets;
+    public void setExtraScanTargets(Iterable<File> extraScanTargets) {
+        this.extraScanTargets = Sets.newLinkedHashSet(extraScanTargets);
     }
 
     @InputFile
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java
index 98886d5..62ffdbf 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java
@@ -59,7 +59,7 @@ public class JettyRunWar extends AbstractJettyRunTask {
         scanList.add(getWebApp());
         getScanner().setScanDirs(scanList);
 
-        ArrayList listeners = new ArrayList();
+        List<Scanner.Listener> listeners = new ArrayList<Scanner.Listener>();
         listeners.add(new Scanner.BulkListener() {
             public void filesChanged(List changes) {
                 try {
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
index d47d678..3456432 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
@@ -19,6 +19,8 @@ package org.gradle.api.plugins.jetty;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -46,6 +48,10 @@ public class JettyStop extends ConventionTask {
             throw new InvalidUserDataException("Please specify a valid stopKey");
         }
 
+        ProgressLogger progressLogger = getServices().get(ProgressLoggerFactory.class).newOperation(JettyStop.class);
+        progressLogger.setDescription("Stop Jetty server");
+        progressLogger.setShortDescription("Stopping Jetty");
+        progressLogger.started();
         try {
             Socket s = new Socket(InetAddress.getByName("127.0.0.1"), getStopPort());
             s.setSoLinger(false, 0);
@@ -58,6 +64,8 @@ public class JettyStop extends ConventionTask {
             logger.info("Jetty not running!");
         } catch (Exception e) {
             logger.error("Exception during stopping", e);
+        } finally {
+            progressLogger.completed();
         }
     }
 
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java
index 58dbf35..ea30b96 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java
@@ -25,8 +25,8 @@ import java.util.List;
  */
 public class ScanTargetPattern {
     private File directory;
-    private List includes = Collections.EMPTY_LIST;
-    private List excludes = Collections.EMPTY_LIST;
+    private List<String> includes = Collections.emptyList();
+    private List<String> excludes = Collections.emptyList();
 
     public File getDirectory() {
         return directory;
@@ -36,19 +36,19 @@ public class ScanTargetPattern {
         this.directory = directory;
     }
 
-    public void setIncludes(List includes) {
+    public void setIncludes(List<String> includes) {
         this.includes = includes;
     }
 
-    public void setExcludes(List excludes) {
+    public void setExcludes(List<String> excludes) {
         this.excludes = excludes;
     }
 
-    public List getIncludes() {
+    public List<String> getIncludes() {
         return includes;
     }
 
-    public List getExcludes() {
+    public List<String> getExcludes() {
         return excludes;
     }
 }
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java
index a8ed54a..03ca8ac 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java
@@ -36,14 +36,14 @@ import org.mortbay.log.Log;
 import org.mortbay.util.LazyList;
 
 public class JettyConfiguration extends Configuration {
-    private List classPathFiles;
+    private List<File> classPathFiles;
     private File webXmlFile;
 
     public JettyConfiguration() {
         super();
     }
 
-    public void setClassPathConfiguration(List classPathFiles) {
+    public void setClassPathConfiguration(List<File> classPathFiles) {
         this.classPathFiles = classPathFiles;
     }
 
@@ -61,10 +61,9 @@ public class JettyConfiguration extends Configuration {
             Log.debug("Setting up classpath ...");
 
             //put the classes dir and all dependencies into the classpath
-            Iterator itor = classPathFiles.iterator();
-            while (itor.hasNext()) {
+            for (File classPathFile : classPathFiles) {
                 ((WebAppClassLoader) getWebAppContext().getClassLoader()).addClassPath(
-                        ((File) itor.next()).getCanonicalPath());
+                        classPathFile.getCanonicalPath());
             }
 
             if (Log.isDebugEnabled()) {
@@ -102,7 +101,7 @@ public class JettyConfiguration extends Configuration {
             //the org.mortbay.jetty.annotations.Configuration class, but it's too difficult?
 
             //able to use annotations on on jdk1.5 and above
-            Class annotationParserClass = Thread.currentThread().getContextClassLoader().loadClass(
+            Class<?> annotationParserClass = Thread.currentThread().getContextClassLoader().loadClass(
                     "org.mortbay.jetty.annotations.AnnotationParser");
             Method parseAnnotationsMethod = annotationParserClass.getMethod("parseAnnotations", WebAppContext.class,
                     Class.class, RunAsCollection.class, InjectionCollection.class, LifeCycleCallbackCollection.class);
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java
index 8aa8c8a..7e13851 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java
@@ -30,7 +30,7 @@ import org.mortbay.jetty.webapp.WebInfConfiguration;
  * Jetty6PluginWebAppContext
  */
 public class JettyPluginWebAppContext extends WebAppContext {
-    private List classpathFiles;
+    private List<File> classpathFiles;
     private File jettyEnvXmlFile;
     private File webXmlFile;
     private WebInfConfiguration webInfConfig = new WebInfConfiguration();
@@ -47,11 +47,11 @@ public class JettyPluginWebAppContext extends WebAppContext {
         setConfigurations(configs);
     }
 
-    public void setClassPathFiles(List classpathFiles) {
+    public void setClassPathFiles(List<File> classpathFiles) {
         this.classpathFiles = classpathFiles;
     }
 
-    public List getClassPathFiles() {
+    public List<File> getClassPathFiles() {
         return this.classpathFiles;
     }
 
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java
index 238bc70..7f3fb08 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java
@@ -16,6 +16,10 @@
 
 package org.gradle.api.plugins.jetty.internal;
 
+import org.mortbay.jetty.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
@@ -23,10 +27,6 @@ import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 
-import org.mortbay.jetty.Server;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /**
  * Monitor <p/> Listens for stop commands eg via mvn jetty:stop and causes jetty to stop either by exiting the jvm, or
  * by stopping the Server instances. The choice of behaviour is controlled by either passing true (exit jvm) or false
@@ -36,12 +36,12 @@ public class Monitor extends Thread {
     private static final Logger LOGGER = LoggerFactory.getLogger(Monitor.class);
 
     private String key;
-    private Server[] servers;
 
     ServerSocket serverSocket;
-    boolean kill;
+    private final Server server;
 
-    public Monitor(int port, String key, Server[] servers, boolean kill) throws IOException {
+    public Monitor(int port, String key, Server server) throws IOException {
+        this.server = server;
         if (port <= 0) {
             throw new IllegalStateException("Bad stop port");
         }
@@ -50,8 +50,6 @@ public class Monitor extends Thread {
         }
 
         this.key = key;
-        this.servers = servers;
-        this.kill = kill;
         setDaemon(true);
         setName("StopJettyPluginMonitor");
         serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
@@ -90,19 +88,15 @@ public class Monitor extends Thread {
 
                     serverSocket = null;
 
-                    if (kill) {
-                        LOGGER.info("Killing Jetty");
-                        System.exit(0);
-                    } else {
-                        for (int i = 0; servers != null && i < servers.length; i++) {
-                            try {
-                                LOGGER.info("Stopping server " + i);
-                                servers[i].stop();
-                            } catch (Exception e) {
-                                LOGGER.error("Exception when stopping server", e);
-                            }
-                        }
+                    try {
+                        LOGGER.info("Stopping server due to received '{}' command...", cmd);
+                        server.stop();
+                    } catch (Exception e) {
+                        LOGGER.error("Exception when stopping server", e);
                     }
+
+                    //We've stopped the server. No point hanging around any more...
+                    return;
                 } else {
                     LOGGER.info("Unsupported monitor operation");
                 }
diff --git a/subprojects/jetty/src/main/resources/META-INF/gradle-plugins/jetty.properties b/subprojects/jetty/src/main/resources/META-INF/gradle-plugins/jetty.properties
old mode 100644
new mode 100755
diff --git a/subprojects/launcher/launcher.gradle b/subprojects/launcher/launcher.gradle
index 360eb45..bad4351 100644
--- a/subprojects/launcher/launcher.gradle
+++ b/subprojects/launcher/launcher.gradle
@@ -1,35 +1,76 @@
-import org.gradle.build.startscripts.StartScriptsGenerator
+configurations {
+    startScriptGenerator
+}
 
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
+    compile project(':cli')
     compile project(':ui')
     compile project(':toolingApi')
+    compile project(':native')
 
     compile libraries.slf4j_api
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    startScriptGenerator project(':plugins')
 }
 
-jar.manifest.mainAttributes('Main-Class': "org.gradle.launcher.GradleMain")
-jar.doFirst {
-    jar.manifest.mainAttributes('Class-Path': "${project(':core').jar.archivePath.name}")
-}
+useTestFixtures()
 
-task startScripts << {
-    ant.mkdir(dir: startScriptsDir)
-    StartScriptsGenerator.generate(jar.archiveName, startScriptsDir, 'gradle')
+jar {
+    manifest.mainAttributes('Main-Class': "org.gradle.launcher.GradleMain")
+    doFirst {
+        jar.manifest.mainAttributes('Class-Path': "${project(':core').jar.archivePath.name}")
+    }
 }
 
-ideaModule {
-    scopes.RUNTIME.plus << rootProject.configurations.runtime
-    scopes.RUNTIME.plus << rootProject.configurations.plugins
-    scopes.RUNTIME.plus << rootProject.configurations.coreImpl
+task startScripts(type: StartScriptGenerator) {
+    startScriptsDir = new File("$buildDir/startScripts")
+    classpath = configurations.startScriptGenerator
+    launcherJar = jar.outputs.files
 }
-eclipseClasspath {
-    plusConfigurations << rootProject.configurations.runtime
-    plusConfigurations << rootProject.configurations.plugins
-    plusConfigurations << rootProject.configurations.coreImpl
+
+class StartScriptGenerator extends DefaultTask {
+    File startScriptsDir
+
+    @InputFiles
+    FileCollection classpath
+
+    @InputFiles
+    FileCollection launcherJar
+
+    @OutputFile
+    File getShellScript() {
+        return new File(startScriptsDir, "gradle")
+    }
+
+    @OutputFile
+    File getBatchFile() {
+        return new File(startScriptsDir, "gradle.bat")
+    }
+
+    @TaskAction
+    def generate() {
+        logging.captureStandardOutput(LogLevel.INFO)
+        def factory = services.get(ClassLoaderFactory)
+        def classLoader = factory.createIsolatedClassLoader(classpath.collect { it.toURI() })
+        def generator = classLoader.loadClass('org.gradle.api.internal.plugins.StartScriptGenerator').newInstance()
+        generator.applicationName = 'Gradle'
+        generator.optsEnvironmentVar = 'GRADLE_OPTS'
+        generator.exitEnvironmentVar = 'GRADLE_EXIT_CONSOLE'
+        generator.mainClassName = 'org.gradle.launcher.GradleMain'
+        generator.scriptRelPath = 'bin/gradle'
+        generator.classpath = ["lib/${launcherJar.singleFile.name}" as String]
+        generator.appNameSystemProperty = 'org.gradle.appname'
+        generator.generateUnixScript(shellScript)
+        generator.generateWindowsScript(batchFile)
+    }
 }
+
+daemonIntegTest {
+    //those tests are always using the daemon, they use exclusive daemons and they are a part of a regular check-in build anyway.
+    //since they are using exclusive daemons they don't contribute to the daemonIntegTest stress/load test.
+    //excluding to avoid unnecessary re-running and stealing resources.
+    exclude "org/gradle/launcher/daemon/**/*"
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy
new file mode 100644
index 0000000..8b6280f
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/SystemClassLoaderTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.IgnoreIf
+import static org.gradle.integtests.fixtures.GradleDistributionExecuter.getSystemPropertyExecuter
+
+/**
+ * Verifies that Gradle doesn't pollute the system class loader.
+ *
+ * This is important for plugins that need to use isolated class loaders to avoid conflicts.
+ *
+ * When running without the daemon, success is dependant on the start scripts doing the right thing.
+ * When running with the daemon, success is dependent on DaemonConnector forking the daemon process with the right classpath.
+ *
+ * This test is not meaningful when running the embedded integration test mode, so we ignore it in that case.
+ */
+class SystemClassLoaderTest extends AbstractIntegrationSpec {
+
+    static heading = "systemClassLoader info"
+    static noInfoHeading = "no systemClassLoader info"
+
+    /*
+        Note: The IBM ClassLoader.systemClassLoader does not throw ClassNotFoundException like it should when you ask
+        for a class it doesn't have, it simply returns null. I've not been able to find any official documentation
+        explaining why this is.
+    */
+    @IgnoreIf({ !getSystemPropertyExecuter().forks })
+    def "daemon bootstrap classpath is bare bones"() {
+        given:
+        buildFile << """
+            task loadClasses << {
+                def systemLoader = ClassLoader.systemClassLoader
+
+                systemLoader.loadClass(org.gradle.launcher.GradleMain.name) // this should be on the classpath, it's from the launcher package
+
+                def nonLauncherOrCoreClass = "org.apache.commons.lang.WordUtils"
+
+                // Check that this is a dependency (somewhat redundant, but for good measure)
+                assert Project.classLoader.loadClass(nonLauncherOrCoreClass) != null
+
+                try {
+                    def clazz = systemLoader.loadClass(nonLauncherOrCoreClass)
+                    assert clazz == null : "ClassNotFoundException should have been thrown trying to load a “\${nonLauncherOrCoreClass}” class from the system classloader as its not a launcher or core class (loaded class: \$clazz)"
+                } catch (ClassNotFoundException e) {
+                    //
+                }
+
+                if (systemLoader instanceof java.net.URLClassLoader) {
+                    def systemLoaderUrls = systemLoader.URLs
+                    println "$heading"
+                    println systemLoaderUrls.size()
+                    println systemLoaderUrls[0]
+                } else {
+                    println "$noInfoHeading"
+                }
+            }
+        """
+
+        when:
+        succeeds "loadClasses"
+
+        then:
+        def lines = output.readLines()
+        if (lines.find { it == noInfoHeading }) { return }
+
+        lines.find { it == heading } // here for nicer output if the output isn't what we expect
+        def headingIndex = lines.indexOf(heading)
+        lines[headingIndex + 1] == "1"
+        lines[headingIndex + 2].contains("gradle-launcher")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy
new file mode 100644
index 0000000..f01be3a
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy
@@ -0,0 +1,75 @@
+/*
+ * 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.daemon
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.util.TextUtil
+import spock.lang.IgnoreIf
+
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+class DaemonConfigurabilityIntegrationSpec extends DaemonIntegrationSpec {
+
+    def cleanup() {
+        stopDaemonsNow()
+    }
+
+    def "honours jvm args specified in gradle.properties"() {
+        given:
+        distribution.file("gradle.properties") << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
+
+        expect:
+        buildSucceeds """
+assert System.getProperty('some-prop') == 'some-value'
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
+        """
+    }
+
+    //TODO SF add coverage for reconnecting to those daemons.
+    def "honours jvm sys property that contain a space in gradle.properties"() {
+        given:
+        distribution.file("gradle.properties") << 'org.gradle.jvmargs=-Dsome-prop="i have space"'
+
+        expect:
+        buildSucceeds """
+assert System.getProperty('some-prop').toString() == 'i have space'
+        """
+    }
+
+    def "honours jvm option that contain a space in gradle.properties"() {
+        given:
+        distribution.file("gradle.properties") << 'org.gradle.jvmargs=-XX:HeapDumpPath="/tmp/with space" -Dsome-prop="and some more stress..."'
+
+        expect:
+        buildSucceeds """
+def inputArgs = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments()
+assert inputArgs.find { it.contains('-XX:HeapDumpPath=') }
+"""
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "honours java home specified in gradle.properties"() {
+        given:
+        File javaHome = AvailableJavaHomes.bestAlternative
+        String javaPath = TextUtil.escapeString(javaHome.canonicalPath)
+        distribution.file("gradle.properties") << "org.gradle.java.home=$javaPath"
+
+        expect:
+        buildSucceeds "assert System.getProperty('java.home').startsWith('$javaPath')"
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
new file mode 100644
index 0000000..262167a
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonFeedbackIntegrationSpec.groovy
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2011 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.daemon
+
+import org.gradle.launcher.daemon.client.DaemonDisappearedException
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.util.TextUtil
+import spock.lang.Timeout
+import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
+
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+class DaemonFeedbackIntegrationSpec extends DaemonIntegrationSpec {
+
+    def cleanup() {
+        stopDaemonsNow()
+    }
+
+    def "daemon keeps logging to the file even if the build is started"() {
+        given:
+        def baseDir = distribution.file("daemonBaseDir").createDir()
+        executer.withDaemonBaseDir(baseDir)
+        distribution.file("build.gradle") << """
+task sleep << {
+    println 'taking a nap...'
+    Thread.sleep(10000)
+    println 'finished the nap...'
+}
+"""
+
+        when:
+        def sleeper = executer.withArguments('-i').withTasks('sleep').start()
+
+        then:
+        poll(60) {
+            assert readLog(baseDir).contains("taking a nap...")
+        }
+
+        when:
+        executer.withDaemonBaseDir(baseDir).withArguments("--stop").run()
+
+        then:
+        sleeper.waitForFailure()
+
+        def log = readLog(baseDir)
+        assert log.contains(DaemonMessages.REMOVING_PRESENCE_DUE_TO_STOP)
+        assert log.contains(DaemonMessages.DAEMON_VM_SHUTTING_DOWN)
+    }
+
+    @Timeout(25)
+    def "promptly shows decent message when daemon cannot be started"() {
+        when:
+        executer.withArguments("-Dorg.gradle.jvmargs=-Xyz").run()
+
+        then:
+        def ex = thrown(Exception)
+        ex.message.contains(DaemonMessages.UNABLE_TO_START_DAEMON)
+        ex.message.contains("-Xyz")
+    }
+
+    @Timeout(25)
+    def "promptly shows decent message when awkward java home used"() {
+        def dummyJdk = distribution.file("dummyJdk").createDir()
+        assert dummyJdk.isDirectory()
+        def jdkPath = TextUtil.escapeString(dummyJdk.canonicalPath)
+        
+        when:
+        executer.withArguments("-Dorg.gradle.java.home=$jdkPath").run()
+
+        then:
+        def ex = thrown(Exception)
+        ex.message.contains('org.gradle.java.home')
+        ex.message.contains(jdkPath)
+    }
+
+    def "daemon log contains all necessary logging"() {
+        given:
+        def baseDir = distribution.file("daemonBaseDir").createDir()
+        executer.withDaemonBaseDir(baseDir)
+        distribution.file("build.gradle") << "println 'Hello build!'"
+
+        when:
+        executer.withArguments("-i").run()
+
+        then:
+        def log = readLog(baseDir)
+
+        //output before started relying logs via connection
+        log.count(DaemonMessages.PROCESS_STARTED) == 1
+        //output after started relying logs via connection
+        log.count(DaemonMessages.STARTED_RELAYING_LOGS) == 1
+        //output from the build
+        log.count('Hello build!') == 1
+
+        when: "another build requested with the same daemon"
+        executer.withArguments("-i").run()
+
+        then:
+        def aLog = readLog(baseDir)
+
+        aLog.count(DaemonMessages.PROCESS_STARTED) == 1
+        aLog.count(DaemonMessages.STARTED_RELAYING_LOGS) == 2
+        aLog.count('Hello build!') == 2
+    }
+
+    def "background daemon infrastructure logs with DEBUG"() {
+        given:
+        def baseDir = distribution.file("daemonBaseDir").createDir()
+        executer.withDaemonBaseDir(baseDir)
+        distribution.file("build.gradle") << "task foo << { println 'hey!' }"
+
+        when: "runing build with --info"
+        executer.withArguments("-i").withTasks('foo').run()
+
+        then:
+        def log = readLog(baseDir)
+        log.findAll(DaemonMessages.STARTED_EXECUTING_COMMAND).size() == 1
+
+        poll(60) {
+            //in theory the client could have received result and complete
+            // but the daemon has not yet finished processing hence polling
+            def daemonLog = readLog(baseDir)
+            daemonLog.findAll(DaemonMessages.FINISHED_EXECUTING_COMMAND).size() == 1
+            daemonLog.findAll(DaemonMessages.FINISHED_BUILD).size() == 1
+        }
+
+        when: "another build requested with the same daemon with --info"
+        executer.withArguments("-i").withTasks('foo').run()
+
+        then:
+        def aLog = readLog(baseDir)
+        aLog.findAll(DaemonMessages.STARTED_EXECUTING_COMMAND).size() == 2
+    }
+
+    def "daemon log honors log levels for logging"() {
+        given:
+        def baseDir = distribution.file("daemonBaseDir").createDir()
+        executer.withDaemonBaseDir(baseDir)
+        distribution.file("build.gradle") << """
+            println 'println me!'
+
+            logger.debug('debug me!')
+            logger.info('info me!')
+            logger.quiet('quiet me!')
+            logger.lifecycle('lifecycle me!')
+            logger.warn('warn me!')
+            logger.error('error me!')
+        """
+
+        when:
+        executer.withArguments("-q").run()
+
+        then:
+        def log = readLog(baseDir)
+
+        //daemon logs to file eagerly regardless of the build log level
+        log.count(DaemonMessages.STARTED_RELAYING_LOGS) == 1
+        //output from the build:
+        log.count('debug me!') == 0
+        log.count('info me!') == 0
+        log.count('println me!') == 1
+        log.count('quiet me!') == 1
+        log.count('lifecycle me!') == 0
+        log.count('warn me!') == 0
+        log.count('error me!') == 1
+    }
+
+    def "disappearing daemon makes client log useful information"() {
+        given:
+        def baseDir = distribution.file("daemonBaseDir").createDir()
+        executer.withDaemonBaseDir(baseDir)
+        distribution.file("build.gradle") << "System.exit(0)"
+
+        when:
+        def failure = executer.withArguments("-q").runWithFailure()
+
+        then:
+        failure.error.contains(DaemonDisappearedException.MESSAGE)
+        failure.error.contains(DaemonMessages.DAEMON_VM_SHUTTING_DOWN)
+    }
+
+    def "foreground daemon log honors log levels for logging"() {
+        given:
+        def baseDir = distribution.file("daemonBaseDir").createDir()
+        distribution.file("build.gradle") << """
+            logger.debug('debug me!')
+            logger.info('info me!')
+        """
+
+        when:
+        def daemon = executer.setAllowExtraLogging(false).withDaemonBaseDir(baseDir).withArguments("--foreground").start()
+        
+        then:
+        poll(60) { assert daemon.standardOutput.contains(DaemonMessages.PROCESS_STARTED) }
+
+        when:
+        def infoBuild = executer.withDaemonBaseDir(baseDir).withArguments("-i", "-Dorg.gradle.jvmargs=-ea").run()
+
+        then:
+        getLogs(baseDir).size() == 0 //we should connect to the foreground daemon so no log was created
+
+        daemon.standardOutput.count(DaemonMessages.ABOUT_TO_START_RELAYING_LOGS) == 0
+        daemon.standardOutput.count("info me!") == 1
+
+        infoBuild.output.count("debug me!") == 0
+        infoBuild.output.count("info me!") == 1
+
+        when:
+        def debugBuild = executer.withDaemonBaseDir(baseDir).withArguments("-d", "-Dorg.gradle.jvmargs=-ea").run()
+
+        then:
+        daemon.standardOutput.count(DaemonMessages.ABOUT_TO_START_RELAYING_LOGS) == 0
+        daemon.standardOutput.count("debug me!") == 1
+
+        debugBuild.output.count("debug me!") == 1
+    }
+
+    List<File> getLogs(baseDir) {
+        //the gradle version dir
+        assert baseDir.listFiles().length == 1
+        def daemonFiles = baseDir.listFiles()[0].listFiles()
+
+        daemonFiles.findAll { it.name.endsWith('.log') }
+    }
+
+    String readLog(baseDir) {
+        def logs = getLogs(baseDir)
+
+        //assert single log
+        assert logs.size() == 1
+
+        logs[0].text
+    }
+    
+    void printAllLogs(baseDir) {
+        getLogs(baseDir).each { println "\n---- ${it.name} ----\n${it.text}\n--------\n" }
+    }
+
+    File firstLog(baseDir) {
+        getLogs(baseDir)[0]
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.groovy
new file mode 100644
index 0000000..3518bc0
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonIntegrationSpec.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.launcher.daemon
+
+import ch.qos.logback.classic.Level
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+import static org.gradle.integtests.fixtures.GradleDistributionExecuter.Executer.daemon
+
+/**
+ * by Szczepan Faber, created at: 2/1/12
+ */
+class DaemonIntegrationSpec extends Specification {
+
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter(daemon)
+
+    def setup() {
+        distribution.requireIsolatedDaemons()
+        LoggerFactory.getLogger("org.gradle.cache.internal.DefaultFileLockManager").level = Level.INFO
+    }
+
+    void stopDaemonsNow() {
+        executer.withArguments("--stop", "--info").run()
+    }
+
+    void buildSucceeds(String script) {
+        distribution.file('build.gradle') << script
+        executer.withArguments("--info", "-Dorg.gradle.jvmargs=").run()
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
new file mode 100644
index 0000000..6bb4369
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonLifecycleSpec.groovy
@@ -0,0 +1,502 @@
+/*
+ * Copyright 2011 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.daemon
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.fixtures.GradleHandle
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.launcher.daemon.client.DaemonDisappearedException
+import org.gradle.launcher.daemon.testing.DaemonContextParser
+import org.gradle.launcher.daemon.testing.DaemonEventSequenceBuilder
+import org.gradle.internal.jvm.Jvm
+import spock.lang.IgnoreIf
+import static org.gradle.tests.fixtures.ConcurrentTestUtil.poll
+
+/**
+ * Outlines the lifecycle of the daemon given different sequences of events.
+ *
+ * These tests are a little different due to their async nature. We use the classes
+ * from the org.gradle.launcher.daemon.testing.* package to model a sequence of expected
+ * daemon registry state changes, executing actions at certain state changes.
+ */
+class DaemonLifecycleSpec extends DaemonIntegrationSpec {
+
+    int daemonIdleTimeout = 100
+    //normally, state transition timeout must be lower than the daemon timeout
+    //so that the daemon does not timeout in the middle of the state verification
+    //effectively hiding some bugs or making tests fail
+    int stateTransitionTimeout = daemonIdleTimeout/2
+
+    final List<GradleHandle> builds = []
+    final List<GradleHandle> foregroundDaemons = []
+
+    // set this to change the java home used to launch any gradle, set back to null to use current JVM
+    def javaHome = null
+    
+    // set this to change the desired default encoding for the build request
+    def buildEncoding = null
+
+    @Delegate DaemonEventSequenceBuilder sequenceBuilder =
+        new DaemonEventSequenceBuilder(stateTransitionTimeout * 1000)
+
+    def setup() {
+        //to work around an issue with the daemon having awkward jvm input arguments
+        //when GRADLE_OPTS contains -Djava.io.tmpdir with value that has spaces
+        //once this problem is fixed we could get rid of this workaround
+        //TODO SF - validate if it is still needed
+        distribution.avoidsConfiguringTmpDir()
+    }
+
+    def buildDir(buildNum) {
+        distribution.file("builds/$buildNum")
+    }
+
+    def buildDirWithScript(buildNum, buildScript) {
+        def dir = buildDir(buildNum)
+        dir.file("build.gradle") << buildScript
+        dir
+    }
+
+    void startBuild(String javaHome = null, String buildEncoding = null) {
+        run {
+            executer.withTasks("watch")
+            executer.withArguments(
+                    "-Dorg.gradle.daemon.idletimeout=${daemonIdleTimeout * 1000}",
+                    "--info",
+                    "-Dorg.gradle.jvmargs=-ea")
+            if (javaHome) {
+                executer.withJavaHome(javaHome)
+            }
+            if (buildEncoding) {
+                executer.withDefaultCharacterEncoding(buildEncoding)
+            }
+            executer.usingProjectDirectory buildDirWithScript(builds.size(), """
+                task('watch') << {
+                    println "waiting for stop file"
+                    long sanityCheck = System.currentTimeMillis() + 120000L
+                    while(!file("stop").exists()) {
+                        sleep 100
+                        if (file("exit").exists()) {
+                            System.exit(1)
+                        }
+                        if (System.currentTimeMillis() > sanityCheck) {
+                            throw new RuntimeException("It seems the stop file was never created")
+                        }
+                    }
+                    println 'noticed stop file, finishing'
+                }
+            """)
+            builds << executer.start()
+        }
+        //TODO SF - figure out how to add waitForBuildToWait somewhere here
+    }
+
+    void completeBuild(buildNum = 0) {
+        run {
+            buildDir(buildNum).file("stop") << "stop"
+        }
+    }
+
+    void waitForBuildToWait(buildNum = 0) {
+        run {
+            poll { assert builds[buildNum].standardOutput.contains("waiting for stop file"); }
+        }
+    }
+
+    void stopDaemons() {
+        run { stopDaemonsNow() }
+    }
+
+    void stopDaemonsNow() {
+        executer.withArguments("--stop", "--info")
+        if (javaHome) {
+            executer.withJavaHome(javaHome)
+        }
+        executer.run()
+    }
+
+    void startForegroundDaemon() {
+        run { startForegroundDaemonNow() }
+    }
+
+    void startForegroundDaemonWithAlternateJavaHome() {
+        run {
+            javaHome = AvailableJavaHomes.bestAlternative
+            startForegroundDaemonNow()
+            javaHome = null
+        }
+    }
+
+    void startForegroundDaemonWithDefaultCharacterEncoding(String encoding) {
+        run {
+            executer.withDefaultCharacterEncoding(encoding)
+            startForegroundDaemonNow()
+            javaHome = null
+        }
+    }
+
+    void startForegroundDaemonNow() {
+        if (javaHome) {
+            executer.withJavaHome(javaHome)
+        }
+        executer.withArguments("--foreground", "--info", "-Dorg.gradle.daemon.idletimeout=${daemonIdleTimeout * 1000}")
+        foregroundDaemons << executer.start()
+    }
+
+    //this is a windows-safe way of killing the process
+    void disappearDaemon(int num = 0) {
+        run {
+            buildDir(num).file("exit") << "exit"
+        }
+    }
+
+    void killForegroundDaemon(int num = 0) {
+        run { foregroundDaemons[num].abort().waitForFailure() }
+    }
+
+    void killBuild(int num = 0) {
+        run { builds[num].abort().waitForFailure() }
+    }
+
+    void buildFailed(int num = 0) {
+        run { failed builds[num] }
+    }
+
+    void foregroundDaemonFailed(int num = 0) {
+        run { failed foregroundDaemons[num] }
+    }
+
+    void failed(GradleHandle handle) {
+        assert handle.waitForFailure()
+    }
+
+    void buildFailedWithDaemonDisappearedMessage(num = 0) {
+        run {
+            def build = builds[num]
+            failed build
+            assert build.errorOutput.contains(DaemonDisappearedException.MESSAGE)
+        }
+    }
+
+    void daemonContext(num = 0, Closure assertions) {
+        run { doDaemonContext(builds[num], assertions) }
+    }
+
+    void foregroundDaemonContext(num = 0, Closure assertions) {
+        run { doDaemonContext(foregroundDaemons[num], assertions) }
+    }
+
+    void doDaemonContext(gradleHandle, Closure assertions) {
+        DaemonContextParser.parseFrom(gradleHandle.standardOutput).with(assertions)
+    }
+
+    def "daemons do some work - sit idle - then timeout and die"() {
+        //in this particular test we need to make the daemon timeout
+        //shorter than the state transition timeout so that
+        //we can detect the daemon idling out within state verification window
+        daemonIdleTimeout = stateTransitionTimeout/2
+
+        when:
+        startBuild()
+
+        then:
+        busy()
+
+        when:
+        completeBuild()
+
+        then:
+        idle()
+
+        and:
+        stopped()
+    }
+
+    def "existing foreground idle daemons are used"() {
+        when:
+        startForegroundDaemon()
+
+        then:
+        idle()
+
+        when:
+        startBuild()
+        waitForBuildToWait()
+
+        then:
+        busy()
+    }
+
+    def "existing idle background daemons are used"() {
+        when:
+        startBuild()
+        waitForBuildToWait()
+
+        then:
+        busy()
+        
+        when:
+        completeBuild()
+        
+        then:
+        idle()
+
+        when:
+        startBuild()
+        waitForBuildToWait()
+
+        then:
+        busy()
+    }
+
+    def "a new daemon is started if all existing are busy"() {
+        when:
+        startBuild()
+
+        then:
+        busy()
+
+        when:
+        startBuild()
+
+        then:
+        busy 2
+    }
+
+    def "sending stop to idle daemons causes them to terminate immediately"() {
+        when:
+        startBuild()
+
+        then:
+        busy()
+
+        when:
+        completeBuild()
+
+        then:
+        idle()
+
+        when:
+        stopDaemons()
+
+        then:
+        stopped()
+    }
+
+    def "sending stop to busy daemons causes them to disappear from the registry"() {
+        when:
+        startBuild()
+
+        then:
+        busy()
+
+        when:
+        stopDaemons()
+
+        then:
+        stopped()
+    }
+
+    def "sending stop to busy daemons cause them to disappear from the registry and disconnect from the client, and terminates the daemon process"() {
+        when:
+        startForegroundDaemon()
+
+        then:
+        idle()
+
+        when:
+        startBuild()
+        waitForBuildToWait()
+
+        then:
+        busy()
+
+        when:
+        stopDaemons()
+
+        then:
+        stopped() // just means the daemon has disappeared from the registry
+
+        then:
+        buildFailedWithDaemonDisappearedMessage()
+
+        and:
+        foregroundDaemonFailed()
+    }
+
+    @IgnoreIf({OperatingSystem.current().windows})
+    //(SF) On windows at the moment, we cannot reliably kill the client without waiting for the daemon to complete
+    //It's because of the way windows handles pipes for child processes.
+    //basically, process.waitFor() completes and you can get hold of the exit value,
+    //however, the process still sits there blocked on reading the child process' outputs.
+    //Next steps:
+    // 1. We can revisit this problem once we solve the daemon feedback story and we have a jna process starter that is able to consume the inputs
+    // 2. We can make this test working on java7 (because processbuilder in jre7 is more powerful)
+    def "tearing down client while daemon is building tears down daemon"() {
+        when:
+        startBuild()
+        waitForBuildToWait()
+
+        then:
+        busy()
+
+        when:
+        killBuild()
+
+        then:
+        stopped()
+    }
+
+    @IgnoreIf({OperatingSystem.current().windows})
+    //See the comment in the previous test
+    def "tearing down client while daemon is building tears down daemon _process_"() {
+        when:
+        startForegroundDaemon()
+
+        then:
+        idle()
+
+        when:
+        startBuild()
+        waitForBuildToWait()
+
+        then:
+        busy()
+
+        when:
+        killBuild()
+
+        then:
+        stopped() // just means the daemon has disappeared from the registry
+
+        and:
+        foregroundDaemonFailed()
+    }
+
+    def "tearing down daemon process produces nice error message for client"() {
+        when:
+        startForegroundDaemon()
+
+        then:
+        idle()
+
+        when:
+        startBuild()
+
+        then:
+        busy()
+
+        when:
+        disappearDaemon()
+
+        then:
+        buildFailedWithDaemonDisappearedMessage()
+
+        and:
+        // The daemon crashed so didn't remove itself from the registry.
+        // This doesn't produce a registry state change, so we have to test
+        // That we are still in the same state this way
+        run { assert executer.daemonRegistry.busy.size() == 1; }
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    def "if a daemon exists but is using a different java home, a new compatible daemon will be created and used"() {
+        when:
+        startForegroundDaemonWithAlternateJavaHome()
+
+        then:
+        idle()
+
+        and:
+        foregroundDaemonContext {
+            assert javaHome == AvailableJavaHomes.bestAlternative
+        }
+
+        when:
+        startBuild()
+
+        then:
+        numDaemons 2
+        busy 1
+
+        when:
+        waitForBuildToWait()
+        completeBuild()
+
+        then:
+        daemonContext {
+            assert javaHome == Jvm.current().javaHome
+        }
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    def "can stop a daemon that is using a different java home"() {
+        when:
+        startForegroundDaemonWithAlternateJavaHome()
+
+        then:
+        idle()
+
+        when:
+        stopDaemons()
+
+        then:
+        stopped()
+    }
+
+    def "if a daemon exists but is using a file encoding, a new compatible daemon will be created and used"() {
+        when:
+        startBuild(null, "US-ASCII")
+        waitForBuildToWait()
+
+        then:
+        busy()
+        daemonContext {
+            assert daemonOpts.contains("-Dfile.encoding=US-ASCII")
+        }
+
+        then:
+        completeBuild()
+
+        then:
+        idle()
+
+        when:
+        startBuild(null, "UTF-8")
+        waitForBuildToWait()
+
+        then:
+        state 1, 1
+
+        then:
+        completeBuild(1)
+        
+        then:
+        idle 2
+        daemonContext(1) {
+            assert daemonOpts.contains("-Dfile.encoding=UTF-8")
+        }
+    }
+    
+    def cleanup() {
+        try {
+            sequenceBuilder.build(executer.daemonRegistry).run()
+        } finally {
+            stopDaemonsNow()
+        }
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
new file mode 100644
index 0000000..75d7fd0
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DispachingFailureIntegrationSpec.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.launcher.daemon
+
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+class DispachingFailureIntegrationSpec extends DaemonIntegrationSpec {
+
+    def "failing build does not make the daemon send corrupted message"() {
+        expect:
+        //This kind of failure more likely reproduces the problem
+        def settingsFile = distribution.file("settings.gradle") << "// empty"
+        def projectdir = distribution.file("project dir").createDir()
+
+        //requesting x failing builds creates enough stress to expose issues with unsynchronized dispatch
+        50.times {
+            executer.usingSettingsFile(settingsFile)
+                    .usingProjectDirectory(projectdir)
+                    .runWithFailure()
+        }
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy
new file mode 100644
index 0000000..b255653
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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.launcher.daemon
+
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Timeout
+import static org.gradle.integtests.fixtures.GradleDistributionExecuter.Executer.daemon
+
+/**
+ * by Szczepan Faber, created at: 1/20/12
+ */
+class StoppingDaemonSmokeIntegrationSpec extends DaemonIntegrationSpec {
+    def concurrent = new ConcurrentTestUtil(120000)
+    @Rule TemporaryFolder temp = new TemporaryFolder()
+
+    @Timeout(300)
+    def "does not deadlock when multiple stop requests are sent"() {
+        given: "multiple daemons started"
+        3.times { idx ->
+            concurrent.start {
+                runBuild("build$idx")
+            }
+        }
+        concurrent.finished()
+        
+        when: "multiple stop requests are issued"
+        5.times { idx ->
+            concurrent.start {
+                stopDaemon("stop$idx")
+            }
+        }
+        concurrent.finished()
+
+        then:
+        def out = stopDaemon("stop").output
+        out.contains(DaemonMessages.NO_DAEMONS_RUNNING)
+
+        cleanup: "just in case"
+        stopDaemon("cleanup")
+    }
+
+    //using separate dist/executer so that we can run them concurrently
+    ExecutionResult stopDaemon(dirName) {
+        def dist = new GradleDistribution()
+        def dir = dist.file(dirName).createDir()
+        return new GradleDistributionExecuter(daemon, dist).withDaemonBaseDir(temp.testDir).inDirectory(dir)
+                .withArguments("-Dorg.gradle.jvmargs=", "--stop", "--info").run()
+    }
+
+    ExecutionResult runBuild(idx) {
+        def dist = new GradleDistribution()
+        def dir = dist.file("dir$idx").createDir()
+        return new GradleDistributionExecuter(daemon, dist).withDaemonBaseDir(temp.testDir).inDirectory(dir)
+                .withArguments("-Dorg.gradle.jvmargs=", "help", "--info").run()
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonContextParser.java b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonContextParser.java
new file mode 100644
index 0000000..c4ed7a0
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonContextParser.java
@@ -0,0 +1,50 @@
+/*
+ * 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.launcher.daemon.testing;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.context.DefaultDaemonContext;
+
+import java.io.File;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * by Szczepan Faber, created at: 2/13/12
+ */
+public class DaemonContextParser {
+    public static DaemonContext parseFrom(String source) {
+        Pattern pattern = Pattern.compile("^.*DefaultDaemonContext\\[uid=([^\\n]+),javaHome=([^\\n]+),daemonRegistryDir=([^\\n]+),pid=([^\\n]+),idleTimeout=(.+?),daemonOpts=([^\\n]+)].*",
+                Pattern.MULTILINE + Pattern.DOTALL);
+        Matcher matcher = pattern.matcher(source);
+
+        if (matcher.matches()) {
+            String uid = matcher.group(1);
+            String javaHome = matcher.group(2);
+            String daemonRegistryDir = matcher.group(3);
+            Long pid = Long.parseLong(matcher.group(4));
+            Integer idleTimeout = Integer.decode(matcher.group(5));
+            List<String> jvmOpts = Lists.newArrayList(Splitter.on(',').split(matcher.group(6)));
+            return new DefaultDaemonContext(uid, new File(javaHome), new File(daemonRegistryDir), pid, idleTimeout, jvmOpts);
+        } else {
+            throw new IllegalStateException("unable to parse DefaultDaemonContext from source: [" + source + "].");
+        }
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonEventSequenceBuilder.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonEventSequenceBuilder.groovy
new file mode 100644
index 0000000..97baba3
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonEventSequenceBuilder.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.daemon.testing
+
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+
+class DaemonEventSequenceBuilder {
+
+    int pollRegistryMs = 300
+    final int stateTransitionTimeoutMs
+
+    DaemonsState currentState = null
+    List<Runnable> actions = []
+
+    private final List<DaemonsStateCheckpoint> checkpoints = []
+
+    int numDaemons = 1
+
+    DaemonEventSequenceBuilder(int stateTransitionTimeoutMs) {
+        this.stateTransitionTimeoutMs = stateTransitionTimeoutMs
+    }
+
+    DaemonsEventSequence build(DaemonRegistry registry) {
+        finishCheckpoint()
+        new DaemonsEventSequence(pollRegistryMs, stateTransitionTimeoutMs, registry, *checkpoints)
+    }
+
+    void run(Closure action) {
+        actions << action
+    }
+
+    void busy() {
+        busy(numDaemons)
+    }
+
+    void busy(int busy) {
+        state(busy, numDaemons - busy)
+    }
+
+    void idle() {
+        idle(numDaemons)
+    }
+
+    void idle(int idle) {
+        state(numDaemons - idle, idle)
+    }
+
+    void stopped() {
+        numDaemons = 0
+        state(0, 0)
+    }
+
+    void state(int busy, int idle) {
+        state(new DaemonsState(busy, idle))
+    }
+
+    void state(DaemonsState checkpointState) {
+        finishCheckpoint()
+        currentState = checkpointState
+    }
+
+    void numDaemons(int numDaemons) {
+        this.numDaemons = numDaemons
+    }
+
+    private finishCheckpoint() {
+        if (currentState == null) {
+            if (!actions.empty) {
+                currentState = DaemonsState.getWildcard()
+                finishCheckpoint()
+            }
+        } else {
+            checkpoints << new DaemonsStateCheckpoint(currentState, *actions)
+            actions.clear()
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
new file mode 100644
index 0000000..231f613
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2011 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.daemon.testing
+
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.messaging.concurrent.DefaultExecutorFactory
+import org.gradle.messaging.concurrent.StoppableExecutor
+import org.gradle.internal.Stoppable
+
+import java.util.concurrent.LinkedBlockingQueue
+
+/**
+ * Models an expected series of state checkpoints (which may have attached actions).
+ * <p>
+ * This works by watching a daemon registry in a background thread, recording each state change.
+ * As the state changes, it is tested against the next checkpoint. If the state of the checkpoint matches,
+ * then the process repeats with the next checkpoint (until all checkpoints are passed).
+ *
+ * If there is no state change detected in a specified interval, an assertion error will be thrown.
+ *
+ * @see org.gradle.launcher.daemon.DaemonLifecycleSpec
+ */
+class DaemonsEventSequence implements Stoppable, Runnable {
+
+    private final int pollRegistryMs
+    private final int timeoutBetweenStateChangeMs
+    private Date runStartedAt
+    private Date lastStateChangeAt
+
+    private final DaemonRegistry registry
+
+    private final List<DaemonsStateCheckpoint> allCheckpoints
+    private final List<DaemonsStateCheckpoint> remainingCheckpoints
+
+    private DaemonsState lastDaemonsState = new DaemonsState(0, 0)
+    private final Map<Long, DaemonsState> pastStateChanges = new LinkedHashMap<Long, DaemonsState>() // processed changes
+    private final Queue<Holder> changeQueue = new LinkedBlockingQueue() // unprocessed changes
+
+    private final StoppableExecutor executor
+    private boolean stop = false
+
+    private AssertionError timeoutError
+
+    // wrapper object for the queue, to enable a null sentinel
+    private class Holder {
+        final value
+        Holder(value) {
+            this.value = value
+        }
+    }
+
+    DaemonsEventSequence(int pollRegistryMs, int timeoutBetweenStateChangeMs, DaemonRegistry registry, DaemonsStateCheckpoint... checkpoints) {
+        this.pollRegistryMs = pollRegistryMs
+        this.timeoutBetweenStateChangeMs = timeoutBetweenStateChangeMs
+        this.registry = registry
+        this.allCheckpoints = Arrays.asList(checkpoints).asImmutable()
+        this.remainingCheckpoints = new LinkedList(allCheckpoints)
+
+        this.executor = new DefaultExecutorFactory().create("DaemonsEventSequence Consumer")
+    }
+
+    void run() {
+        runStartedAt = new Date()
+        executor.execute {
+            try {
+                putOnChangeQueue(lastDaemonsState) // always start with no daemons
+                lastStateChangeAt = runStartedAt
+                while (!stop) {
+                    checkForDaemonsStateChange()
+                    sleep(pollRegistryMs)
+                }
+                putOnChangeQueue(null) // sentinel that no more is comming
+            } catch (Exception e) {
+                e.printStackTrace()
+            }
+        }
+
+        processChanges()
+    }
+
+    private checkForDaemonsStateChange() {
+        def busy = registry.busy.size()
+        def idle = registry.idle.size()
+
+        def currentState = new DaemonsState(busy, idle)
+        if (!lastDaemonsState.matches(currentState)) {
+            putOnChangeQueue(currentState)
+            lastDaemonsState = currentState
+            lastStateChangeAt = new Date()
+        }
+
+        if (lastStateChangeAt.time + timeoutBetweenStateChangeMs < new Date().time) {
+            def nextCheckpoint = remainingCheckpoints.first()
+
+            def timeoutMessage = "timeoutBetweenStateChangeMs of $timeoutBetweenStateChangeMs"
+            def checkpointMessage = "hit at checkpoint num $currentActionNum (expecting: $nextCheckpoint.expectedState)"
+            def changesMessage = "processed state changes: $pastStateChanges, queued state changes: $changeQueue"
+
+            timeoutError = new AssertionError("$timeoutMessage $checkpointMessage $changesMessage")
+            stop = true
+        }
+    }
+
+    private processChanges() {
+        while (!remainingCheckpoints.empty) {
+            def daemonsState = takeFromChangeQueue()
+            if (timeoutError) {
+                throw timeoutError
+            }
+
+            if (daemonsState == null) { return }
+
+            Long timeSinceStart = lastStateChangeAt == null ? 0 : lastStateChangeAt.time - runStartedAt.time
+            pastStateChanges[timeSinceStart] = daemonsState
+            def nextCheckpoint = remainingCheckpoints.first()
+
+            if (nextCheckpoint.test(daemonsState)) {
+                remainingCheckpoints.remove(0)
+            }
+        }
+
+        stop()
+    }
+
+    private putOnChangeQueue(value) {
+        changeQueue.put(new Holder(value))
+    }
+
+    private takeFromChangeQueue() {
+        changeQueue.take().value
+    }
+
+    void stop() {
+        stop = true
+        executor.stop()
+    }
+
+    int getCurrentActionNum() {
+        remainingCheckpoints.empty ? -1 : allCheckpoints.size() - remainingCheckpoints.size()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsState.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsState.groovy
new file mode 100644
index 0000000..7b933d5
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsState.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.daemon.testing
+
+/**
+ * Represents the number of given busy and idle daemons in a registry at a particular time.
+ */
+class DaemonsState {
+    final int busy
+    final int idle
+
+    DaemonsState(int busy, int idle) {
+        this.busy = Math.max(busy, 0)
+        this.idle = Math.max(idle, 0)
+    }
+
+    static getWildcard() {
+        new DaemonsState()
+    }
+
+    private DaemonsState() {
+        this.busy = -1
+        this.idle = -1
+    }
+
+    String toString() {
+        wildcard ? "DaemonsState{*}" : "DaemonsState{busy=$busy,idle=$idle}"
+    }
+
+    boolean matches(DaemonsState rhs) {
+        wildcard || (this.busy == rhs.busy && this.idle == rhs.idle)
+    }
+
+    boolean isWildcard() {
+        busy < 0
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsStateCheckpoint.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsStateCheckpoint.groovy
new file mode 100644
index 0000000..5f49680
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsStateCheckpoint.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.daemon.testing
+
+/**
+ * Represents a particular expected registry state, and optionally a series of
+ * actions to perform when the checkpoint is reached.
+ */
+class DaemonsStateCheckpoint {
+
+    final DaemonsState expectedState
+    private final Runnable[] actions
+
+    DaemonsStateCheckpoint(DaemonsState expectedState, Runnable... actions) {
+        this.expectedState = expectedState
+        this.actions = actions
+    }
+
+    String toString() {
+        "DaemonsStateCheckpoint{expectedState=$expectedState,actions=$actions}"
+    }
+
+    boolean test(DaemonsState actualState) {
+        if (expectedState.matches(actualState)) {
+            actions*.run()
+            true
+        } else {
+            false
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/BuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/BuildActionParameters.java
deleted file mode 100644
index 54fd1d2..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/BuildActionParameters.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2011 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.BuildClientMetaData;
-import org.gradle.initialization.BuildRequestMetaData;
-
-import java.io.Serializable;
-import java.util.Map;
-
-public interface BuildActionParameters extends Serializable {
-    BuildRequestMetaData getBuildRequestMetaData();
-
-    BuildClientMetaData getClientMetaData();
-
-    Map<String, String> getSystemProperties();
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java
deleted file mode 100644
index c53f1f8..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/CommandLineActionFactory.java
+++ /dev/null
@@ -1,222 +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.launcher;
-
-import org.gradle.BuildExceptionReporter;
-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;
-import org.gradle.initialization.*;
-import org.gradle.logging.LoggingConfiguration;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.LoggingServiceRegistry;
-import org.gradle.logging.StyledTextOutputFactory;
-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;
-
-/**
- * <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 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";
-
-    /**
-     * <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
-     * 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.
-     */
-    Action<ExecutionListener> convert(List<String> args) {
-        CommandLineParser parser = new CommandLineParser();
-
-        CommandLineConverter<StartParameter> startParameterConverter = createStartParameterConverter();
-        startParameterConverter.configure(parser);
-
-        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();
-
-        Action<ExecutionListener> action;
-        try {
-            ParsedCommandLine commandLine = parser.parse(args);
-            CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = loggingServices.get(CommandLineConverter.class);
-            loggingConfiguration = loggingConfigurationConverter.convert(commandLine);
-            action = createAction(parser, commandLine, startParameterConverter, loggingServices);
-        } catch (CommandLineArgumentException e) {
-            action = new CommandLineParseFailureAction(parser, e);
-        }
-
-        return new WithLoggingAction(loggingConfiguration, loggingServices,
-                new ExceptionReportingAction(action,
-                        new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), new StartParameter(), clientMetaData())));
-    }
-
-    private static GradleLauncherMetaData clientMetaData() {
-        return new GradleLauncherMetaData();
-    }
-
-    CommandLineConverter<StartParameter> createStartParameterConverter() {
-        return new DefaultCommandLineConverter();
-    }
-
-    ServiceRegistry createLoggingServices() {
-        return LoggingServiceRegistry.newCommandLineProcessLogging();
-    }
-
-    private Action<ExecutionListener> createAction(CommandLineParser parser, final ParsedCommandLine commandLine, CommandLineConverter<StartParameter> startParameterConverter, final ServiceRegistry loggingServices) {
-        if (commandLine.hasOption(HELP)) {
-            return new ActionAdapter(new ShowUsageAction(parser));
-        }
-        if (commandLine.hasOption(VERSION)) {
-            return new ActionAdapter(new ShowVersionAction());
-        }
-        if (commandLine.hasOption(GUI)) {
-            return new ActionAdapter(new ShowGuiAction());
-        }
-
-        StartParameter startParameter = new StartParameter();
-        startParameterConverter.convert(commandLine, startParameter);
-        DaemonConnector connector = new DaemonConnector(startParameter.getGradleUserHomeDir());
-        GradleLauncherMetaData clientMetaData = clientMetaData();
-        long startTime = ManagementFactory.getRuntimeMXBean().getStartTime();
-        DaemonClient client = new DaemonClient(connector, clientMetaData, loggingServices.get(OutputEventListener.class));
-
-        if (commandLine.hasOption(FOREGROUND)) {
-            return new ActionAdapter(new DaemonMain(loggingServices, connector));
-        }
-        if (commandLine.hasOption(STOP)) {
-            return new ActionAdapter(new StopDaemonAction(client));
-        }
-
-        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 ActionAdapter(
-                    new DaemonBuildAction(client, commandLine, new File(System.getProperty("user.dir")), clientMetaData, startTime, System.getProperties()));
-        }
-
-        return new RunBuildAction(startParameter, loggingServices, new DefaultBuildRequestMetaData(clientMetaData, startTime));
-    }
-
-    private static void showUsage(PrintStream out, CommandLineParser parser) {
-        out.println();
-        out.print("USAGE: ");
-        clientMetaData().describeCommand(out, "[option...]", "[task...]");
-        out.println();
-        out.println();
-        parser.printUsage(out);
-        out.println();
-    }
-
-    private static class CommandLineParseFailureAction implements Action<ExecutionListener> {
-        private final Exception e;
-        private final CommandLineParser parser;
-
-        public CommandLineParseFailureAction(CommandLineParser parser, Exception e) {
-            this.parser = parser;
-            this.e = e;
-        }
-
-        public void execute(ExecutionListener executionListener) {
-            System.err.println();
-            System.err.println(e.getMessage());
-            showUsage(System.err, parser);
-            executionListener.onFailure(e);
-        }
-    }
-
-    private static class ShowUsageAction implements Runnable {
-        private final CommandLineParser parser;
-
-        public ShowUsageAction(CommandLineParser parser) {
-            this.parser = parser;
-        }
-
-        public void run() {
-            showUsage(System.out, parser);
-        }
-    }
-
-    private static class ShowVersionAction implements Runnable {
-        public void run() {
-            System.out.println(GradleVersion.current().prettyPrint());
-        }
-    }
-
-    static class ShowGuiAction implements Runnable {
-        public void run() {
-            BlockingApplication.launchAndBlock();
-        }
-    }
-
-    static class ActionAdapter implements Action<ExecutionListener> {
-        private final Runnable action;
-
-        ActionAdapter(Runnable action) {
-            this.action = action;
-        }
-
-        public void execute(ExecutionListener executionListener) {
-            action.run();
-        }
-    }
-
-    static class WithLoggingAction implements Action<ExecutionListener> {
-        private final LoggingConfiguration loggingConfiguration;
-        private final ServiceRegistry loggingServices;
-        private final Action<ExecutionListener> action;
-
-        public WithLoggingAction(LoggingConfiguration loggingConfiguration, ServiceRegistry loggingServices, Action<ExecutionListener> action) {
-            this.loggingConfiguration = loggingConfiguration;
-            this.loggingServices = loggingServices;
-            this.action = action;
-        }
-
-        public void execute(ExecutionListener executionListener) {
-            LoggingManagerInternal loggingManager = loggingServices.getFactory(LoggingManagerInternal.class).create();
-            loggingManager.setLevel(loggingConfiguration.getLogLevel());
-            loggingManager.colorStdOutAndStdErr(loggingConfiguration.isColorOutput());
-            loggingManager.start();
-            action.execute(executionListener);
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonBuildAction.java
deleted file mode 100644
index d921956..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonBuildAction.java
+++ /dev/null
@@ -1,47 +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.launcher;
-
-import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.initialization.ParsedCommandLine;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DaemonBuildAction implements Runnable {
-    private final DaemonClient client;
-    private final ParsedCommandLine args;
-    private final File currentDir;
-    private final BuildClientMetaData clientMetaData;
-    private final long startTime;
-    private final Map<String, String> systemProperties;
-
-    public DaemonBuildAction(DaemonClient client, ParsedCommandLine args, File currentDir, BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties) {
-        this.client = client;
-        this.args = args;
-        this.currentDir = currentDir;
-        this.clientMetaData = clientMetaData;
-        this.startTime = startTime;
-        this.systemProperties = new HashMap<String, String>();
-        GUtil.addToMap(this.systemProperties, systemProperties);
-    }
-
-    public void run() {
-        client.execute(new ExecuteBuildAction(currentDir, args), new DefaultBuildActionParameters(clientMetaData, startTime, systemProperties));
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonClient.java
deleted file mode 100644
index 754e1da..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonClient.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2011 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.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.protocol.*;
-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 class DaemonClient implements GradleLauncherActionExecuter<BuildActionParameters> {
-    private static final Logger LOGGER = Logging.getLogger(DaemonClient.class);
-    private final DaemonConnector connector;
-    private final BuildClientMetaData clientMetaData;
-    private final OutputEventListener outputEventListener;
-
-    public DaemonClient(DaemonConnector connector, BuildClientMetaData clientMetaData, OutputEventListener outputEventListener) {
-        this.connector = connector;
-        this.clientMetaData = clientMetaData;
-        this.outputEventListener = outputEventListener;
-    }
-
-    /**
-     * Stops the daemon, if it is running.
-     */
-    public void stop() {
-        Connection<Object> connection = connector.maybeConnect();
-        if (connection == null) {
-            LOGGER.lifecycle("Gradle daemon is not running.");
-            return;
-        }
-        run(new Stop(clientMetaData), connection);
-        LOGGER.lifecycle("Gradle daemon stopped.");
-    }
-
-    /**
-     * Executes the given action in the daemon. The action and parameters must be serializable.
-     *
-     * @param action The action
-     * @throws ReportedException On failure, when the failure has already been logged/reported.
-     */
-    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
-        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();
-        Result result = (Result) run(new Build(action, parameters), connection);
-        return (T) result.getResult();
-    }
-
-    private CommandComplete run(Command command, Connection<Object> connection) {
-        try {
-            connection.dispatch(command);
-            while (true) {
-                Object object = connection.receive();
-                if (object instanceof CommandComplete) {
-                    CommandComplete commandComplete = (CommandComplete) object;
-                    if (commandComplete.getFailure() != null) {
-                        throw commandComplete.getFailure();
-                    }
-                    return commandComplete;
-                }
-                OutputEvent outputEvent = (OutputEvent) object;
-                outputEventListener.onOutput(outputEvent);
-            }
-        } finally {
-            connection.stop();
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonConnector.java
deleted file mode 100644
index eb16359..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonConnector.java
+++ /dev/null
@@ -1,283 +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.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.
-     */
-    public Connection<Object> maybeConnect() {
-        URI uri = loadDaemonAddress();
-        if (uri == null) {
-            return null;
-        }
-        try {
-            return new TcpOutgoingConnector(getClass().getClassLoader()).connect(uri);
-        } catch (ConnectException e) {
-            // Ignore
-            return null;
-        }
-    }
-
-    private URI loadDaemonAddress() {
-        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);
-                return new URI(dataInputStream.readUTF());
-            } finally {
-                // Also releases the lock
-                inputStream.close();
-            }
-        } catch (FileNotFoundException e) {
-            // Ignore
-            return null;
-        } catch (EOFException e) {
-            // Daemon has created empty file, but not yet locked it or written anything to it.
-            // Or has crashed while writing the registry file.
-            return null;
-        } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-    }
-
-    /**
-     * Connects to the daemon, starting it if required.
-     *
-     * @return The connection. Never returns null.
-     */
-    public Connection<Object> connect() {
-        Connection<Object> connection = maybeConnect();
-        if (connection != null) {
-            return connection;
-        }
-
-        LOGGER.info("Starting Gradle daemon");
-        startDaemon();
-        Date expiry = new Date(System.currentTimeMillis() + 30000L);
-        do {
-            connection = maybeConnect();
-            if (connection != null) {
-                return connection;
-            }
-            try {
-                Thread.sleep(200L);
-            } catch (InterruptedException e) {
-                throw UncheckedException.asUncheckedException(e);
-            }
-        } while (System.currentTimeMillis() < expiry.getTime());
-
-        throw new GradleException("Timeout waiting to connect to Gradle daemon.");
-    }
-
-    private void startDaemon() {
-        try {
-            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();
-        } catch (IOException e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
-    }
-
-    /**
-     * 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();
-                }
-            }
-        });
-
-        storeDaemonAddress(uri);
-
-        boolean stopped = finished.awaitStop();
-        if (!stopped) {
-            LOGGER.lifecycle("Time-out waiting for requests. Stopping.");
-        }
-        new CompositeStoppable(incomingConnector, executorFactory).stop();
-
-        getRegistryFile().delete();
-    }
-
-    private void storeDaemonAddress(URI uri) {
-        try {
-            File registryFile = getRegistryFile();
-            registryFile.getParentFile().mkdirs();
-            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);
-        }
-    }
-
-    private File getRegistryFile() {
-        return new File(userHomeDir, String.format("daemon/%s/registry.bin", GradleVersion.current().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() {
-            resetTimer();
-        }
-
-        /**
-         * 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 {
-                        if (running) {
-                            condition.await();
-                        } else {
-                            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 {
-                assert running;
-                running = false;
-                resetTimer();
-                condition.signalAll();
-            } finally {
-                lock.unlock();
-            }
-        }
-
-        private void resetTimer() {
-            expiry = System.currentTimeMillis() + THREE_HOURS;
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonMain.java
deleted file mode 100644
index cb8b034..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/DaemonMain.java
+++ /dev/null
@@ -1,149 +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.launcher;
-
-import org.gradle.BuildExceptionReporter;
-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.initialization.GradleLauncherFactory;
-import org.gradle.launcher.protocol.*;
-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.logging.internal.StreamBackedStandardOutputListener;
-import org.gradle.messaging.concurrent.Stoppable;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.UncheckedException;
-
-import java.io.*;
-import java.util.Arrays;
-import java.util.Properties;
-
-/**
- * The server portion of the build daemon. See {@link DaemonClient} 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) throws IOException {
-        StartParameter startParameter = new DefaultCommandLineConverter().convert(Arrays.asList(args));
-        DaemonConnector connector = new DaemonConnector(startParameter.getGradleUserHomeDir());
-        LoggingServiceRegistry loggingServices = LoggingServiceRegistry.newCommandLineProcessLogging();
-        addLogFileWriters(startParameter, loggingServices);
-        new DaemonMain(loggingServices, connector).run();
-    }
-
-    private static void addLogFileWriters(StartParameter startParameter, LoggingServiceRegistry loggingServices) throws IOException {
-        File stderrOut = new File(startParameter.getGradleUserHomeDir(), String.format("daemon/%s/daemon.err.log", GradleVersion.current().getVersion()));
-        stderrOut.getParentFile().mkdirs();
-        final Writer writer = new BufferedWriter(new FileWriter(stderrOut));
-        loggingServices.get(LoggingOutputInternal.class).addStandardErrorListener(new StreamBackedStandardOutputListener(writer));
-    }
-
-    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) {
-        CommandComplete result = null;
-        Throwable failure = null;
-        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 {
-                result = doRunWithLogging(connection, serverControl);
-            } finally {
-                loggingOutput.removeOutputEventListener(listener);
-            }
-        } catch (ReportedException e) {
-            failure = e;
-        } catch (Throwable throwable) {
-            LOGGER.error("Could not execute build.", throwable);
-            failure = throwable;
-        }
-        if (failure != null) {
-            result = new CommandComplete(UncheckedException.asUncheckedException(failure));
-        }
-        assert result != null;
-        connection.dispatch(result);
-    }
-
-    private CommandComplete doRunWithLogging(Connection<Object> connection, Stoppable serverControl) {
-        Command command = (Command) connection.receive();
-        try {
-            return doRunWithExceptionHandling(command, serverControl);
-        } catch (ReportedException e) {
-            throw e;
-        } catch (Throwable throwable) {
-            BuildExceptionReporter exceptionReporter = new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), new StartParameter(), command.getClientMetaData());
-            exceptionReporter.reportException(throwable);
-            throw new ReportedException(throwable);
-        }
-    }
-
-    private CommandComplete doRunWithExceptionHandling(Command command, Stoppable serverControl) {
-        LOGGER.info("Executing {}", command);
-        if (command instanceof Stop) {
-            LOGGER.lifecycle("Stopping");
-            serverControl.stop();
-            return new CommandComplete(null);
-        }
-
-        return build((Build) command);
-    }
-
-    private Result build(Build build) {
-        Properties originalSystemProperties = new Properties();
-        originalSystemProperties.putAll(System.getProperties());
-        Properties clientSystemProperties = new Properties();
-        clientSystemProperties.putAll(build.getParameters().getSystemProperties());
-        System.setProperties(clientSystemProperties);
-        try {
-            DefaultGradleLauncherActionExecuter executer = new DefaultGradleLauncherActionExecuter(launcherFactory, loggingServices);
-            Object result = executer.execute(build.getAction(), build.getParameters());
-            return new Result(result);
-        } finally {
-            System.setProperties(originalSystemProperties);
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/DefaultBuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/DefaultBuildActionParameters.java
deleted file mode 100644
index aa1ee35..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/DefaultBuildActionParameters.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2011 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.BuildClientMetaData;
-import org.gradle.initialization.BuildRequestMetaData;
-import org.gradle.initialization.DefaultBuildRequestMetaData;
-import org.gradle.util.GUtil;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DefaultBuildActionParameters implements BuildActionParameters, Serializable {
-    private final BuildClientMetaData clientMetaData;
-    private final long startTime;
-    private final Map<String, String> systemProperties;
-
-    public DefaultBuildActionParameters(BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties) {
-        this.clientMetaData = clientMetaData;
-        this.startTime = startTime;
-        this.systemProperties = new HashMap<String, String>();
-        GUtil.addToMap(this.systemProperties, systemProperties);
-    }
-
-    public BuildRequestMetaData getBuildRequestMetaData() {
-        return new DefaultBuildRequestMetaData(clientMetaData, startTime);
-    }
-
-    public BuildClientMetaData getClientMetaData() {
-        return clientMetaData;
-    }
-
-    public Map<String, String> getSystemProperties() {
-        return systemProperties;
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/DefaultGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/DefaultGradleLauncherActionExecuter.java
deleted file mode 100644
index 41a13ad..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/DefaultGradleLauncherActionExecuter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2011 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.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.gradle.api.internal.project.ServiceRegistry;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.initialization.GradleLauncherFactory;
-import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.logging.StyledTextOutputFactory;
-
-public class DefaultGradleLauncherActionExecuter implements GradleLauncherActionExecuter<BuildActionParameters> {
-    private final ServiceRegistry loggingServices;
-    private final GradleLauncherFactory gradleLauncherFactory;
-
-    public DefaultGradleLauncherActionExecuter(GradleLauncherFactory gradleLauncherFactory, ServiceRegistry loggingServices) {
-        this.gradleLauncherFactory = gradleLauncherFactory;
-        this.loggingServices = loggingServices;
-    }
-
-    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
-        StartParameter startParameter = new StartParameter();
-        if (action instanceof InitializationAware) {
-            InitializationAware initializationAware = (InitializationAware) action;
-            initializationAware.configureStartParameter(startParameter);
-        }
-
-        LoggingManagerInternal loggingManager = loggingServices.getFactory(LoggingManagerInternal.class).create();
-        loggingManager.setLevel(startParameter.getLogLevel());
-        loggingManager.start();
-        try {
-            GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter, parameters.getBuildRequestMetaData());
-            BuildResult buildResult = action.run(gradleLauncher);
-            Throwable failure = buildResult.getFailure();
-            if (failure != null) {
-                throw new ReportedException(failure);
-            }
-            return action.getResult();
-        } catch (ReportedException e) {
-            throw e;
-        } catch (Throwable throwable) {
-            BuildExceptionReporter exceptionReporter = new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), new StartParameter(), parameters.getClientMetaData());
-            exceptionReporter.reportException(throwable);
-            throw new ReportedException(throwable);
-        } finally {
-            loggingManager.stop();
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/ExceptionReportingAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/ExceptionReportingAction.java
deleted file mode 100644
index 3754161..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/ExceptionReportingAction.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2011 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.api.Action;
-
-public class ExceptionReportingAction implements Action<ExecutionListener> {
-    private final Action<ExecutionListener> action;
-    private final BuildExceptionReporter reporter;
-
-    public ExceptionReportingAction(Action<ExecutionListener> action, BuildExceptionReporter reporter) {
-        this.action = action;
-        this.reporter = reporter;
-    }
-
-    public void execute(ExecutionListener executionListener) {
-        try {
-            action.execute(executionListener);
-        } catch (ReportedException e) {
-            executionListener.onFailure(e.getCause());
-        } catch (Throwable t) {
-            reporter.reportException(t);
-            executionListener.onFailure(t);
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/ExecuteBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/ExecuteBuildAction.java
deleted file mode 100644
index b272292..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/ExecuteBuildAction.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2011 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.StartParameter;
-import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.initialization.ParsedCommandLine;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class ExecuteBuildAction implements GradleLauncherAction<Void>, InitializationAware, Serializable {
-    private final File currentDir;
-    private final ParsedCommandLine args;
-
-    public ExecuteBuildAction(File currentDir, ParsedCommandLine args) {
-        this.currentDir = currentDir;
-        this.args = args;
-    }
-
-    public void configureStartParameter(StartParameter startParameter) {
-        DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
-        startParameter.setCurrentDir(currentDir);
-        converter.convert(args, startParameter);
-    }
-
-    public BuildResult run(GradleLauncher launcher) {
-        return launcher.run();
-    }
-
-    public Void getResult() {
-        return null;
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/ExecutionListener.java b/subprojects/launcher/src/main/java/org/gradle/launcher/ExecutionListener.java
deleted file mode 100644
index e2e8ab1..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/ExecutionListener.java
+++ /dev/null
@@ -1,32 +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.launcher;
-
-/**
- * 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/launcher/src/main/java/org/gradle/launcher/GradleDaemon.java b/subprojects/launcher/src/main/java/org/gradle/launcher/GradleDaemon.java
deleted file mode 100644
index 840bbf4..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleDaemon.java
+++ /dev/null
@@ -1,22 +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.launcher;
-
-public class GradleDaemon {
-    public static void main(String[] args) {
-        new ProcessBootstrap().run("org.gradle.launcher.DaemonMain", args);
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/GradleLauncherActionExecuter.java
deleted file mode 100644
index 14026e2..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleLauncherActionExecuter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2011 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.GradleLauncherAction;
-
-public interface GradleLauncherActionExecuter<P> {
-    /**
-     * Executes the given action, and returns the result. The given action may also implement {@link InitializationAware <T>}.
-     *
-     * @param action The action
-     * @param <T> The result type
-     * @return The result.
-     */
-    <T> T execute(GradleLauncherAction<T> action, P actionParameters);
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/IncomingConnectionHandler.java b/subprojects/launcher/src/main/java/org/gradle/launcher/IncomingConnectionHandler.java
deleted file mode 100644
index a030c73..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/IncomingConnectionHandler.java
+++ /dev/null
@@ -1,23 +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.launcher;
-
-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/launcher/src/main/java/org/gradle/launcher/InitializationAware.java b/subprojects/launcher/src/main/java/org/gradle/launcher/InitializationAware.java
deleted file mode 100644
index 0c81795..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/InitializationAware.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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.StartParameter;
-
-public interface InitializationAware {
-    void configureStartParameter(StartParameter startParameter);
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java b/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
index f381091..2967ae1 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
@@ -15,11 +15,9 @@
  */
 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 org.gradle.launcher.cli.CommandLineActionFactory;
+import org.gradle.launcher.exec.EntryPoint;
+import org.gradle.launcher.exec.ExecutionListener;
 
 import java.util.Arrays;
 
@@ -28,16 +26,17 @@ import java.util.Arrays;
  *
  * @author Hans Dockter
  */
-public class Main {
-    private final String[] args;
+public class Main extends EntryPoint {
 
+    private final String[] args;
+    
     public Main(String[] args) {
         this.args = args;
     }
 
     public static void main(String[] args) {
         try {
-            new Main(args).execute();
+            new Main(args).run();
             System.exit(0);
         } catch (Throwable throwable) {
             throwable.printStackTrace();
@@ -45,43 +44,11 @@ public class Main {
         }
     }
 
-    public void execute() {
-        BuildCompleter buildCompleter = createBuildCompleter();
-        try {
-            // 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) {
-            BuildExceptionReporter exceptionReporter = new BuildExceptionReporter(new StreamingStyledTextOutputFactory(System.err), new StartParameter(), new GradleLauncherMetaData());
-            exceptionReporter.reportException(e);
-            buildCompleter.onFailure(e);
-        }
-        buildCompleter.exit();
+    protected void doAction(ExecutionListener listener) {
+        createActionFactory().convert(Arrays.asList(args)).execute(listener);
     }
 
     CommandLineActionFactory createActionFactory() {
         return new CommandLineActionFactory();
     }
-
-    BuildCompleter createBuildCompleter() {
-        return new ProcessExitExecutionListener();
-    }
-
-    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/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java b/subprojects/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
index ab25e42..cb5c70a 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
@@ -16,14 +16,18 @@
 package org.gradle.launcher;
 
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathProvider;
 import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.util.ClassLoaderFactory;
+import org.gradle.util.ClassPath;
+import org.gradle.util.DefaultClassLoaderFactory;
+import org.gradle.util.MutableURLClassLoader;
 
 import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
 
 public class ProcessBootstrap {
-    void run(String mainClassName, String[] args) {
+    public void run(String mainClassName, String[] args) {
         try {
             runNoExit(mainClassName, args);
             System.exit(0);
@@ -34,14 +38,14 @@ public class ProcessBootstrap {
     }
 
     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);
+        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(new DefaultModuleRegistry()));
+        ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory();
+        ClassPath antClasspath = classPathRegistry.getClassPath("ANT");
+        ClassPath runtimeClasspath = classPathRegistry.getClassPath("GRADLE_RUNTIME");
+        ClassLoader antClassLoader = classLoaderFactory.createIsolatedClassLoader(antClasspath);
+        ClassLoader runtimeClassLoader = new MutableURLClassLoader(antClassLoader, runtimeClasspath);
         Thread.currentThread().setContextClassLoader(runtimeClassLoader);
-        Class mainClass = runtimeClassLoader.loadClass(mainClassName);
+        Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName);
         Method mainMethod = mainClass.getMethod("main", String[].class);
         mainMethod.invoke(null, new Object[]{args});
     }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/ReportedException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/ReportedException.java
deleted file mode 100644
index 9060079..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/ReportedException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 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;
-
-/**
- * Wraps an exception which has already been logged, and should not be logged again.
- */
-public class ReportedException extends RuntimeException {
-    public ReportedException(Throwable throwable) {
-        super(throwable);
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/RunBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/RunBuildAction.java
deleted file mode 100644
index 5fee3a7..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/RunBuildAction.java
+++ /dev/null
@@ -1,51 +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.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/launcher/src/main/java/org/gradle/launcher/StopDaemonAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/StopDaemonAction.java
deleted file mode 100644
index ab7d788..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/StopDaemonAction.java
+++ /dev/null
@@ -1,28 +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.launcher;
-
-public class StopDaemonAction implements Runnable {
-    private final DaemonClient client;
-
-    public StopDaemonAction(DaemonClient client) {
-        this.client = client;
-    }
-
-    public void run() {
-        client.stop();
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java
new file mode 100644
index 0000000..8d8e5be
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.cli;
+
+import org.gradle.api.Action;
+import org.gradle.launcher.exec.ExecutionListener;
+
+class ActionAdapter implements Action<ExecutionListener> {
+    private final Runnable action;
+
+    ActionAdapter(Runnable action) {
+        this.action = action;
+    }
+
+    public void execute(ExecutionListener executionListener) {
+        action.run();
+    }
+
+    public String toString() {
+        return String.format("ActionAdapter[runnable=%s]", action);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
new file mode 100644
index 0000000..4aa4622
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2012 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.cli;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.initialization.DefaultBuildRequestMetaData;
+import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.bootstrap.ForegroundDaemonMain;
+import org.gradle.launcher.daemon.client.DaemonClient;
+import org.gradle.launcher.daemon.client.DaemonClientServices;
+import org.gradle.launcher.daemon.client.SingleUseDaemonClientServices;
+import org.gradle.launcher.daemon.client.StopDaemonClientServices;
+import org.gradle.launcher.daemon.configuration.CurrentProcess;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.configuration.ForegroundDaemonConfiguration;
+import org.gradle.launcher.exec.ExecutionListener;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.util.Map;
+
+class BuildActionsFactory implements CommandLineAction {
+    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";
+    private final ServiceRegistry loggingServices;
+    private final CommandLineConverter<StartParameter> startParameterConverter;
+
+    BuildActionsFactory(ServiceRegistry loggingServices) {
+        this.loggingServices = loggingServices;
+        this.startParameterConverter = new DefaultCommandLineConverter();
+    }
+
+    BuildActionsFactory(ServiceRegistry loggingServices, CommandLineConverter<StartParameter> commandLineConverter) {
+        this.loggingServices = loggingServices;
+        this.startParameterConverter = commandLineConverter;
+    }
+
+    public void configureCommandLineParser(CommandLineParser parser) {
+        startParameterConverter.configure(parser);
+
+        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.");
+        parser.option(NO_DAEMON).hasDescription("Do not use the Gradle daemon to run the build.");
+        parser.option(STOP).hasDescription("Stops the Gradle daemon if it is running.");
+    }
+
+    public Action<ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
+        StartParameter startParameter = new StartParameter();
+        startParameterConverter.convert(commandLine, startParameter);
+        DaemonParameters daemonParameters = constructDaemonParameters(startParameter);
+        if (commandLine.hasOption(STOP)) {
+            return stopAllDaemons(daemonParameters, loggingServices);
+        }
+        if (commandLine.hasOption(FOREGROUND)) {
+            ForegroundDaemonConfiguration conf = new ForegroundDaemonConfiguration(
+                    daemonParameters.getUid(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout());
+            return new ActionAdapter(new ForegroundDaemonMain(conf));
+        }
+        if (useDaemon(commandLine, daemonParameters)) {
+            return runBuildWithDaemon(startParameter, daemonParameters, loggingServices);
+        }
+        if (canUseCurrentProcess(daemonParameters)) {
+            return runBuildInProcess(loggingServices, startParameter);
+        }
+        return runBuildInSingleUseDaemon(startParameter, daemonParameters, loggingServices);
+    }
+
+    private DaemonParameters constructDaemonParameters(StartParameter startParameter) {
+        Map<String, String> mergedSystemProperties = startParameter.getMergedSystemProperties();
+        DaemonParameters daemonParameters = new DaemonParameters();
+        daemonParameters.configureFromBuildDir(startParameter.getCurrentDir(), startParameter.isSearchUpwards());
+        daemonParameters.configureFromGradleUserHome(startParameter.getGradleUserHomeDir());
+        daemonParameters.configureFromSystemProperties(mergedSystemProperties);
+        return daemonParameters;
+    }
+
+    private Action<ExecutionListener> stopAllDaemons(DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+        DaemonClientServices clientServices = new StopDaemonClientServices(loggingServices, daemonParameters, System.in);
+        DaemonClient stopClient = clientServices.get(DaemonClient.class);
+        return new ActionAdapter(new StopDaemonAction(stopClient));
+    }
+
+    private boolean useDaemon(ParsedCommandLine commandLine, DaemonParameters daemonParameters) {
+        boolean useDaemon = daemonParameters.isEnabled();
+        useDaemon = useDaemon || commandLine.hasOption(DAEMON);
+        useDaemon = useDaemon && !commandLine.hasOption(NO_DAEMON);
+        return useDaemon;
+    }
+
+    private Action<ExecutionListener> runBuildWithDaemon(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+        // Create a client that will match based on the daemon startup parameters.
+        DaemonClientServices clientServices = new DaemonClientServices(loggingServices, daemonParameters, System.in);
+        DaemonClient client = clientServices.get(DaemonClient.class);
+        return daemonBuildAction(startParameter, daemonParameters, client);
+    }
+
+    private boolean canUseCurrentProcess(DaemonParameters requiredBuildParameters) {
+        CurrentProcess currentProcess = new CurrentProcess();
+        return currentProcess.configureForBuild(requiredBuildParameters);
+    }
+
+    private Action<ExecutionListener> runBuildInProcess(ServiceRegistry loggingServices, StartParameter startParameter) {
+        return new RunBuildAction(startParameter, loggingServices, new DefaultBuildRequestMetaData(clientMetaData(), getBuildStartTime()));
+    }
+
+    private Action<ExecutionListener> runBuildInSingleUseDaemon(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+        //(SF) this is a workaround until this story is completed. I'm hardcoding setting the idle timeout to be max X mins.
+        //this way we avoid potential runaway daemons that steal resources on linux and break builds on windows.
+        //We might leave that in if we decide it's a good idea for an extra safety net.
+        int maxTimeout = 2 * 60 * 1000;
+        if (daemonParameters.getIdleTimeout() > maxTimeout) {
+            daemonParameters.setIdleTimeout(maxTimeout);
+        }
+        //end of workaround.
+
+        // Create a client that will not match any existing daemons, so it will always startup a new one
+        DaemonClientServices clientServices = new SingleUseDaemonClientServices(loggingServices, daemonParameters, System.in);
+        DaemonClient client = clientServices.get(DaemonClient.class);
+        return daemonBuildAction(startParameter, daemonParameters, client);
+    }
+
+    private Action<ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, DaemonClient client) {
+        return new ActionAdapter(
+                new DaemonBuildAction(client, startParameter, getWorkingDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
+    }
+
+    private long getBuildStartTime() {
+        return ManagementFactory.getRuntimeMXBean().getStartTime();
+    }
+
+    private File getWorkingDir() {
+        return new File(System.getProperty("user.dir"));
+    }
+
+    private GradleLauncherMetaData clientMetaData() {
+        return new GradleLauncherMetaData();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
new file mode 100644
index 0000000..803d1d9
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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.cli;
+
+import org.gradle.api.Action;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.launcher.exec.ExecutionListener;
+
+public interface CommandLineAction {
+    /**
+     * Configures the given parser with the options used by this action.
+     */
+    void configureCommandLineParser(CommandLineParser parser);
+
+    /**
+     * Creates an executable action from the given command-line args. Returns null if this action was not selected by the given
+     * command-line args.
+     */
+    Action<ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine);
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
new file mode 100644
index 0000000..ee13a06
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
@@ -0,0 +1,214 @@
+/*
+ * 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.cli;
+
+import org.gradle.BuildExceptionReporter;
+import org.gradle.api.Action;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.cli.CommandLineConverter;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.exec.ExceptionReportingAction;
+import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.logging.LoggingConfiguration;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.util.GradleVersion;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * <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 VERSION = "v";
+
+    /**
+     * <p>Converts the given command-line arguments to an {@link Action} which performs the action requested by the
+     * command-line args.
+     *
+     * @param args The command-line arguments.
+     * @return The action to execute.
+     */
+    public Action<ExecutionListener> convert(List<String> args) {
+        ServiceRegistry loggingServices = createLoggingServices();
+
+        LoggingConfiguration loggingConfiguration = new LoggingConfiguration();
+
+        return new ExceptionReportingAction(
+                new WithLogging(loggingServices, args, loggingConfiguration,
+                        new ParseAndBuildAction(loggingServices, args)),
+                new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData()));
+    }
+
+    protected void createActionFactories(ServiceRegistry loggingServices, Collection<CommandLineAction> actions) {
+        actions.add(new GuiActionsFactory());
+        actions.add(new BuildActionsFactory(loggingServices));
+    }
+
+    private static GradleLauncherMetaData clientMetaData() {
+        return new GradleLauncherMetaData();
+    }
+
+    public ServiceRegistry createLoggingServices() {
+        return LoggingServiceRegistry.newCommandLineProcessLogging();
+    }
+
+    private static void showUsage(PrintStream out, CommandLineParser parser) {
+        out.println();
+        out.print("USAGE: ");
+        clientMetaData().describeCommand(out, "[option...]", "[task...]");
+        out.println();
+        out.println();
+        parser.printUsage(out);
+        out.println();
+    }
+
+    private static class BuiltInActions implements CommandLineAction {
+        public void configureCommandLineParser(CommandLineParser parser) {
+            parser.option(HELP, "?", "help").hasDescription("Shows this help message.");
+            parser.option(VERSION, "version").hasDescription("Print version info.");
+        }
+
+        public Action<ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
+            if (commandLine.hasOption(HELP)) {
+                return new ActionAdapter(new ShowUsageAction(parser));
+            }
+            if (commandLine.hasOption(VERSION)) {
+                return new ActionAdapter(new ShowVersionAction());
+            }
+            return null;
+        }
+    }
+
+    private static class CommandLineParseFailureAction implements Action<ExecutionListener> {
+        private final Exception e;
+        private final CommandLineParser parser;
+
+        public CommandLineParseFailureAction(CommandLineParser parser, Exception e) {
+            this.parser = parser;
+            this.e = e;
+        }
+
+        public void execute(ExecutionListener executionListener) {
+            System.err.println();
+            System.err.println(e.getMessage());
+            showUsage(System.err, parser);
+            executionListener.onFailure(e);
+        }
+    }
+
+    private static class ShowUsageAction implements Runnable {
+        private final CommandLineParser parser;
+
+        public ShowUsageAction(CommandLineParser parser) {
+            this.parser = parser;
+        }
+
+        public void run() {
+            showUsage(System.out, parser);
+        }
+    }
+
+    private static class ShowVersionAction implements Runnable {
+        public void run() {
+            System.out.println(GradleVersion.current().prettyPrint());
+        }
+    }
+
+    private static class WithLogging implements Action<ExecutionListener> {
+        private final ServiceRegistry loggingServices;
+        private final List<String> args;
+        private final LoggingConfiguration loggingConfiguration;
+        private final Action<ExecutionListener> action;
+
+        WithLogging(ServiceRegistry loggingServices, List<String> args, LoggingConfiguration loggingConfiguration, Action<ExecutionListener> action) {
+            this.loggingServices = loggingServices;
+            this.args = args;
+            this.loggingConfiguration = loggingConfiguration;
+            this.action = action;
+        }
+
+        public void execute(ExecutionListener executionListener) {
+            CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = (CommandLineConverter<LoggingConfiguration>)loggingServices.get(CommandLineConverter.class);
+            CommandLineParser parser = new CommandLineParser();
+            loggingConfigurationConverter.configure(parser);
+            parser.allowUnknownOptions();
+            parser.allowMixedSubcommandsAndOptions();
+            try {
+                ParsedCommandLine parsedCommandLine = parser.parse(args);
+                loggingConfigurationConverter.convert(parsedCommandLine, loggingConfiguration);
+            } catch (CommandLineArgumentException e) {
+                // Ignore
+            }
+
+            LoggingManagerInternal loggingManager = loggingServices.getFactory(LoggingManagerInternal.class).create();
+            loggingManager.setLevel(loggingConfiguration.getLogLevel());
+            loggingManager.colorStdOutAndStdErr(loggingConfiguration.isColorOutput());
+            loggingManager.start();
+
+            action.execute(executionListener);
+        }
+    }
+
+    private class ParseAndBuildAction implements Action<ExecutionListener> {
+        private final ServiceRegistry loggingServices;
+        private final List<String> args;
+
+        private ParseAndBuildAction(ServiceRegistry loggingServices, List<String> args) {
+            this.loggingServices = loggingServices;
+            this.args = args;
+        }
+
+        public void execute(ExecutionListener executionListener) {
+            List<CommandLineAction> actions = new ArrayList<CommandLineAction>();
+            actions.add(new BuiltInActions());
+            createActionFactories(loggingServices, actions);
+
+            CommandLineParser parser = new CommandLineParser();
+            for (CommandLineAction action : actions) {
+                action.configureCommandLineParser(parser);
+            }
+
+            Action<ExecutionListener> action;
+            try {
+                ParsedCommandLine commandLine = parser.parse(args);
+                action = createAction(actions, parser, commandLine);
+            } catch (CommandLineArgumentException e) {
+                action = new CommandLineParseFailureAction(parser, e);
+            }
+
+            action.execute(executionListener);
+        }
+
+        private Action<ExecutionListener> createAction(Iterable<CommandLineAction> factories, CommandLineParser parser, ParsedCommandLine commandLine) {
+            for (CommandLineAction factory : factories) {
+                Action<ExecutionListener> action = factory.createAction(parser, commandLine);
+                if (action != null) {
+                    return action;
+                }
+            }
+            throw new UnsupportedOperationException("No action factory for specified command-line arguments.");
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/DaemonBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/DaemonBuildAction.java
new file mode 100644
index 0000000..769c662
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/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.cli;
+
+import org.gradle.StartParameter;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DaemonBuildAction implements Runnable {
+    private final GradleLauncherActionExecuter<BuildActionParameters> executer;
+    private final StartParameter startParameter;
+    private final File currentDir;
+    private final BuildClientMetaData clientMetaData;
+    private final long startTime;
+    private final Map<String, String> systemProperties;
+    private final Map<String, String> envVariables;
+
+    public DaemonBuildAction(GradleLauncherActionExecuter<BuildActionParameters> executer, StartParameter startParameter, File currentDir, BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables) {
+        this.executer = executer;
+        this.startParameter = startParameter;
+        this.currentDir = currentDir;
+        this.clientMetaData = clientMetaData;
+        this.startTime = startTime;
+        this.systemProperties = new HashMap<String, String>();
+        GUtil.addToMap(this.systemProperties, systemProperties);
+        this.envVariables = envVariables;
+    }
+
+    public void run() {
+        executer.execute(new ExecuteBuildAction(startParameter), new DefaultBuildActionParameters(clientMetaData, startTime, systemProperties, envVariables, currentDir));
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java
new file mode 100644
index 0000000..cd49211
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExecuteBuildAction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.cli;
+
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.launcher.exec.InitializationAware;
+
+import java.io.Serializable;
+
+public class ExecuteBuildAction implements GradleLauncherAction<Void>, InitializationAware, Serializable {
+    private final StartParameter startParameter;
+
+    public ExecuteBuildAction(StartParameter startParameter) {
+        this.startParameter = startParameter;
+    }
+
+    public StartParameter configureStartParameter() {
+        return startParameter;
+    }
+
+    public BuildResult run(GradleLauncher launcher) {
+        return launcher.run();
+    }
+
+    public Void getResult() {
+        return null;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
new file mode 100644
index 0000000..81b5689
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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.cli;
+
+import org.gradle.api.Action;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication;
+import org.gradle.launcher.exec.ExecutionListener;
+
+class GuiActionsFactory implements CommandLineAction {
+    private static final String GUI = "gui";
+
+    public void configureCommandLineParser(CommandLineParser parser) {
+        parser.option(GUI).hasDescription("Launches the Gradle GUI.");
+    }
+
+    public Action<ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
+        if (commandLine.hasOption(GUI)) {
+            return new ActionAdapter(new ShowGuiAction());
+        }
+        return null;
+    }
+
+    static class ShowGuiAction implements Runnable {
+        public void run() {
+            BlockingApplication.launchAndBlock();
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
new file mode 100644
index 0000000..049507d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.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.cli;
+
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.initialization.BuildRequestMetaData;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.StartParameter;
+import org.gradle.api.Action;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.launcher.exec.ExecutionListener;
+
+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/launcher/src/main/java/org/gradle/launcher/cli/StopDaemonAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/StopDaemonAction.java
new file mode 100644
index 0000000..f5a90bb
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/StopDaemonAction.java
@@ -0,0 +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.launcher.cli;
+
+import org.gradle.launcher.daemon.client.DaemonClient;
+
+public class StopDaemonAction implements Runnable {
+    private final DaemonClient client;
+
+    public StopDaemonAction(DaemonClient client) {
+        this.client = client;
+    }
+
+    public void run() {
+        client.stop();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
new file mode 100644
index 0000000..427f7c2
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
@@ -0,0 +1,174 @@
+/*
+ * 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.daemon.bootstrap;
+
+import com.google.common.io.Files;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.configuration.DaemonServerConfiguration;
+import org.gradle.launcher.daemon.configuration.DefaultDaemonServerConfiguration;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.launcher.daemon.server.Daemon;
+import org.gradle.launcher.daemon.server.DaemonServices;
+import org.gradle.launcher.daemon.server.DaemonStoppedException;
+import org.gradle.launcher.exec.EntryPoint;
+import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.internal.OutputEventRenderer;
+
+import java.io.*;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The entry point for a daemon process.
+ *
+ * If the daemon hits the specified idle timeout the process will exit with 0. If the daemon encounters
+ * an internal error or is explicitly stopped (which can be via receiving a stop command, or unexpected client disconnection)
+ * the process will exit with 1.
+ */
+public class DaemonMain extends EntryPoint {
+
+    private static final Logger LOGGER = Logging.getLogger(DaemonMain.class);
+
+    private final DaemonServerConfiguration configuration;
+
+    public static void main(String[] args) {
+        //The first argument is not really used but it is very useful in diagnosing, i.e. running 'jps -m'
+        if (args.length < 4) {
+            invalidArgs("Following arguments are required: <gradle-version> <daemon-dir> <timeout-millis> <daemonUid> <optional startup jvm opts>");
+        }
+        File daemonBaseDir = new File(args[1]);
+
+        int idleTimeoutMs = 0;
+        try {
+            idleTimeoutMs = Integer.parseInt(args[2]);
+        } catch (NumberFormatException e) {
+            invalidArgs("Second argument must be a whole number (i.e. daemon idle timeout in ms)");
+        }
+
+        String daemonUid = args[3];
+
+        List<String> startupOpts = new LinkedList<String>();
+        for (int i = 4; i < args.length; i++) {
+            startupOpts.add(args[i]);
+        }
+        LOGGER.debug("Assuming the daemon was started with following jvm opts: {}", startupOpts);
+
+        DaemonServerConfiguration parameters = new DefaultDaemonServerConfiguration(
+                daemonUid, daemonBaseDir, idleTimeoutMs, startupOpts);
+        DaemonMain daemonMain = new DaemonMain(parameters);
+
+        daemonMain.run();
+    }
+
+    private static void invalidArgs(String message) {
+        System.out.println("USAGE: <gradle version> <path to registry base dir> <idle timeout in milliseconds>");
+        System.out.println(message);
+        System.exit(1);
+    }
+
+    public DaemonMain(DaemonServerConfiguration configuration) {
+        this.configuration = configuration;
+    }
+
+    protected void doAction(ExecutionListener listener) {
+        LoggingServiceRegistry loggingRegistry = LoggingServiceRegistry.newChildProcessLogging();
+        LoggingManagerInternal loggingManager = loggingRegistry.getFactory(LoggingManagerInternal.class).create();
+        DaemonServices daemonServices = new DaemonServices(configuration, loggingRegistry, loggingManager);
+        File daemonLog = daemonServices.getDaemonLogFile();
+        final DaemonContext daemonContext = daemonServices.get(DaemonContext.class);
+
+        initialiseLogging(loggingRegistry.get(OutputEventRenderer.class), loggingManager, daemonLog);
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            public void run() {
+                LOGGER.info("Daemon[pid = {}] process has finished.", daemonContext.getPid());
+            }
+        });
+
+        Daemon daemon = startDaemon(daemonServices);
+        try {
+            daemon.awaitIdleTimeout(configuration.getIdleTimeout());
+            LOGGER.info("Daemon hit idle timeout (" + configuration.getIdleTimeout() + "ms), stopping...");
+            daemon.stop();
+        } catch (DaemonStoppedException e) {
+            LOGGER.debug("Daemon stopping due to the stop request");
+            listener.onFailure(e);
+        }
+    }
+
+    protected void initialiseLogging(OutputEventRenderer renderer, LoggingManagerInternal loggingManager, File daemonLog) {
+        //create log file
+        PrintStream result;
+        try {
+            Files.createParentDirs(daemonLog);
+            result = new PrintStream(new FileOutputStream(daemonLog), true);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to create daemon log file", e);
+        }
+        final PrintStream log = result;
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            public void run() {
+                //just in case we have a bug related to logging,
+                //printing some exit info directly to file:
+                log.println(DaemonMessages.DAEMON_VM_SHUTTING_DOWN);
+            }
+        });
+
+        //close all streams and redirect IO
+        redirectOutputsAndInput(log);
+
+        //after redirecting we need to add the new std out/err to the renderer singleton
+        //so that logging gets its way to the daemon log:
+        renderer.addStandardOutputAndError();
+
+        //Making the daemon infrastructure log with DEBUG. This is only for the infrastructure!
+        //Each build request carries it's own log level and it is used during the execution of the build (see LogToClient)
+        loggingManager.setLevel(LogLevel.DEBUG);
+
+        loggingManager.start();
+    }
+
+    protected Daemon startDaemon(DaemonServices daemonServices) {
+        Daemon daemon = daemonServices.get(Daemon.class);
+        daemon.start();
+        return daemon;
+    }
+
+    private static void redirectOutputsAndInput(OutputStream log) {
+        PrintStream originalOut = System.out;
+        PrintStream originalErr = System.err;
+        //InputStream originalIn = System.in;
+
+        PrintStream printStream = new PrintStream(log, true);
+
+        System.setOut(printStream);
+        System.setErr(printStream);
+        System.setIn(new ByteArrayInputStream(new byte[0]));
+
+        originalOut.println(DaemonMessages.PROCESS_STARTED);
+        originalOut.close();
+        originalErr.close();
+
+        //TODO - make this work on windows
+        //originalIn.close();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java
new file mode 100644
index 0000000..20958f8
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java
@@ -0,0 +1,45 @@
+/*
+ * 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.launcher.daemon.bootstrap;
+
+import org.gradle.launcher.daemon.configuration.DaemonServerConfiguration;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.daemon.server.Daemon;
+import org.gradle.launcher.daemon.server.DaemonServices;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.internal.OutputEventRenderer;
+
+import java.io.File;
+
+public class ForegroundDaemonMain extends DaemonMain {
+
+    public ForegroundDaemonMain(DaemonServerConfiguration configuration) {
+        super(configuration);
+    }
+
+    @Override
+    protected void initialiseLogging(OutputEventRenderer renderer, LoggingManagerInternal loggingManager, File daemonLog) {
+        // Don't redirect IO for foreground daemon
+        loggingManager.start();
+    }
+
+    @Override
+    protected Daemon startDaemon(DaemonServices daemonServices) {
+        Daemon daemon = super.startDaemon(daemonServices);
+        daemonServices.get(DaemonRegistry.class).markIdle(daemon.getAddress());
+        return daemon;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java
new file mode 100644
index 0000000..81d3993
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java
@@ -0,0 +1,24 @@
+/*
+ * 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.daemon.bootstrap;
+
+import org.gradle.launcher.ProcessBootstrap;
+
+public class GradleDaemon {
+    public static void main(String[] args) {
+        new ProcessBootstrap().run("org.gradle.launcher.daemon.bootstrap.DaemonMain", args);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java
new file mode 100644
index 0000000..489568e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.specs.Spec;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.internal.UncheckedException;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.launcher.daemon.protocol.*;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.util.GFileUtils;
+
+import java.io.InputStream;
+
+/**
+ * The client piece of the build daemon.
+ * <p>
+ * Immediately upon forming a connection, the daemon may send {@link OutputEvent} messages back to the client and may do so
+ * for as long as the connection is open.
+ * <p>
+ * The client is expected to send exactly one {@link Build} message as the first message it sends to the daemon. The daemon 
+ * may either return {@link DaemonBusy} or {@link BuildStarted}. If the former is received, the client should not send any more
+ * messages to this daemon. If the latter is received, the client can assume the daemon is performing the build. The client may then
+ * send zero to many {@link ForwardInput} messages. If the client's stdin stream is closed before the connection to the
+ * daemon is terminated, the client must send a {@link CloseInput} command to instruct the daemon that no more input is to be
+ * expected.
+ * <p>
+ * After receiving the {@link Result} message (after a {@link BuildStarted} mesage), the client must send a {@link CloseInput}
+ * command if it has not already done so due to the stdin stream being closed. At this point the client is expected to 
+ * terminate the connection with the daemon.
+ * <p>
+ * If the daemon returns a {@code null} message before returning a {@link Result} object, it has terminated unexpectedly for some reason.
+ */
+public class DaemonClient implements GradleLauncherActionExecuter<BuildActionParameters> {
+    private static final Logger LOGGER = Logging.getLogger(DaemonClient.class);
+    protected final DaemonConnector connector;
+    protected final BuildClientMetaData clientMetaData;
+    private final OutputEventListener outputEventListener;
+    private final Spec<DaemonContext> compatibilitySpec;
+    private final InputStream buildStandardInput;
+
+    //TODO SF - outputEventListener and buildStandardInput are per-build settings
+    //so down the road we should refactor the code accordingly and potentially attach them to BuildActionParameters
+    public DaemonClient(DaemonConnector connector, BuildClientMetaData clientMetaData, OutputEventListener outputEventListener,
+                        Spec<DaemonContext> compatibilitySpec, InputStream buildStandardInput) {
+        this.connector = connector;
+        this.clientMetaData = clientMetaData;
+        this.outputEventListener = outputEventListener;
+        this.compatibilitySpec = compatibilitySpec;
+        this.buildStandardInput = buildStandardInput;
+    }
+
+    /**
+     * Stops all daemons, if any is running.
+     */
+    public void stop() {
+        DaemonConnection connection = connector.maybeConnect(compatibilitySpec);
+        if (connection == null) {
+            LOGGER.lifecycle(DaemonMessages.NO_DAEMONS_RUNNING);
+            return;
+        }
+
+        LOGGER.lifecycle("Stopping daemon(s).");
+        //iterate and stop all daemons
+        while (connection != null) {
+            new StopDispatcher().dispatch(clientMetaData, connection.getConnection());
+            LOGGER.lifecycle("Gradle daemon stopped.");
+            connection = connector.maybeConnect(compatibilitySpec);
+        }
+    }
+
+    /**
+     * Executes the given action in the daemon. The action and parameters must be serializable.
+     *
+     * @param action The action
+     * @throws org.gradle.launcher.exec.ReportedException On failure, when the failure has already been logged/reported.
+     */
+    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
+        Build build = new Build(action, parameters);
+        int saneNumberOfAttempts = 100; //is it sane enough?
+        for (int i = 1; i < saneNumberOfAttempts; i++) {
+            DaemonConnection daemonConnection = connector.connect(compatibilitySpec);
+            Connection<Object> connection = daemonConnection.getConnection();
+
+            try {
+                return (T) executeBuild(build, connection);
+            } catch (DaemonInitialConnectException e) {
+                LOGGER.info(e.getMessage() + " Trying a different daemon...", e.getCause());
+            }
+        }
+        throw new NoUsableDaemonFoundException("Unable to find a usable idle daemon. I have connected to "
+                + saneNumberOfAttempts + " different daemons but I could not use any of them to run build: " + build + ".");
+    }
+
+    protected Object executeBuild(Build build, Connection<Object> connection) throws DaemonInitialConnectException {
+        Object firstResult;
+        try {
+            LOGGER.info("Connected to the daemon. Dispatching {} request.", build);
+            connection.dispatch(build);
+            firstResult = connection.receive();
+        } catch (Exception e) {
+            throw new DaemonInitialConnectException("Exception when attempted to send and receive first result from the daemon.", e);
+        }
+
+        if (firstResult instanceof BuildStarted) {
+            DaemonDiagnostics diagnostics = ((BuildStarted) firstResult).getDiagnostics();
+            return monitorBuild(build, diagnostics, connection).getValue();
+        } else if (firstResult instanceof Failure) {
+            // Could potentially distinguish between CommandFailure and DaemonFailure here.
+            throw UncheckedException.throwAsUncheckedException(((Failure) firstResult).getValue());
+        } else if (firstResult instanceof DaemonBusy) {
+            throw new DaemonInitialConnectException("The daemon we connected to was busy.");
+        } else if (firstResult == null) {
+            throw new DaemonInitialConnectException("The first result from the daemon was empty. Most likely the process died immediately after connection.");
+        } else {
+            throw invalidResponse(firstResult, build);
+        }
+    }
+
+    private Result monitorBuild(Build build, DaemonDiagnostics diagnostics, Connection<Object> connection) {
+        DaemonClientInputForwarder inputForwarder = new DaemonClientInputForwarder(buildStandardInput, build.getClientMetaData(), connection);
+        try {
+            inputForwarder.start();
+            int objectsReceived = 0;
+
+            while (true) {
+                Object object = connection.receive();
+                LOGGER.trace("Received object #{}, type: {}", objectsReceived++, object == null ? null : object.getClass().getName());
+
+                if (object == null) {
+                    return handleDaemonDisappearance(build, diagnostics);
+                } else if (object instanceof Failure) {
+                    // Could potentially distinguish between CommandFailure and DaemonFailure here.
+                    throw UncheckedException.throwAsUncheckedException(((Failure) object).getValue());
+                } else if (object instanceof OutputEvent) {
+                    outputEventListener.onOutput((OutputEvent) object);
+                } else if (object instanceof Result) {
+                    return (Result) object;
+                } else {
+                    throw invalidResponse(object, build);
+                }
+            }
+        } finally {
+            inputForwarder.stop();
+            connection.stop();
+        }
+    }
+
+    private Result handleDaemonDisappearance(Build build, DaemonDiagnostics diagnostics) {
+        //we can try sending something to the daemon and try out if he is really dead or use jps
+        //if he's really dead we should deregister it if it is not already deregistered.
+        //if the daemon is not dead we might continue receiving from him (and try to find the bug in messaging infrastructure)
+        int daemonLogLines = 20;
+        LOGGER.error("The message received from the daemon indicates that the daemon has disappeared."
+                + "\nDaemon pid: " + diagnostics.getPid()
+                + "\nDaemon log: " + diagnostics.getDaemonLog()
+                + "\nBuild request sent: " + build
+                + "\nAttempting to read last " + daemonLogLines + " lines from the daemon log...");
+
+        try {
+            String tail = GFileUtils.tail(diagnostics.getDaemonLog(), daemonLogLines);
+            LOGGER.error("Last " + daemonLogLines + " lines from " + diagnostics.getDaemonLog().getName() + ":"
+                    + "\n----------\n" + tail + "----------\n");
+        } catch (GFileUtils.TailReadingException e) {
+            LOGGER.error("Unable to read from the daemon log file because of: " + e);
+            LOGGER.debug("Problem reading the daemon log file.", e);
+        }
+
+        throw new DaemonDisappearedException();
+    }
+
+    private IllegalStateException invalidResponse(Object response, Build command) {
+        return new IllegalStateException(String.format(
+                "Received invalid response from the daemon: '%s' is a result of a type we don't have a strategy to handle."
+                        + "Earlier, '%s' request was sent to the daemon.", response, command));
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
new file mode 100644
index 0000000..2e6c980
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import org.gradle.api.Action;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.Stoppable;
+import org.gradle.launcher.daemon.protocol.IoCommand;
+import org.gradle.launcher.daemon.protocol.ForwardInput;
+import org.gradle.launcher.daemon.protocol.CloseInput;
+import org.gradle.messaging.remote.internal.InputForwarder;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+
+import java.io.InputStream;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Eagerly consumes from an input stream, sending line by line ForwardInput
+ * commands over the connection and finishing with a CloseInput command.»
+ */
+public class DaemonClientInputForwarder implements Stoppable {
+
+    private static final Logger LOGGER = Logging.getLogger(DaemonClientInputForwarder.class);
+
+    public static final int DEFAULT_BUFFER_SIZE = 1024;
+
+    private final Lock lifecycleLock = new ReentrantLock();
+    private boolean started;
+
+    private final InputStream inputStream;
+    private final BuildClientMetaData clientMetadata;
+    private final Dispatch<? super IoCommand> dispatch;
+    private final ExecutorFactory executorFactory;
+    private final int bufferSize;
+
+    private InputForwarder forwarder;
+
+    public DaemonClientInputForwarder(InputStream inputStream, BuildClientMetaData clientMetadata, Dispatch<? super IoCommand> dispatch) {
+        this(inputStream, clientMetadata, dispatch, new DefaultExecutorFactory(), DEFAULT_BUFFER_SIZE);
+    }
+
+    public DaemonClientInputForwarder(InputStream inputStream, BuildClientMetaData clientMetadata, Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, int bufferSize) {
+        this.inputStream = inputStream;
+        this.clientMetadata = clientMetadata;
+        this.dispatch = dispatch;
+        this.executorFactory = executorFactory;
+        this.bufferSize = bufferSize;
+    }
+
+    public DaemonClientInputForwarder start() {
+        lifecycleLock.lock();
+        try {
+            if (started) {
+                throw new IllegalStateException("DaemonClientInputForwarder already started");
+            }
+
+            Action<String> dispatcher = new Action<String>() {
+                public void execute(String input) {
+                    if (LOGGER.isDebugEnabled()) {
+                        LOGGER.debug("Forwarding input to daemon: '{}'", input.replace("\n", "\\n"));
+                    }                    
+                    dispatch.dispatch(new ForwardInput(clientMetadata, input.getBytes()));
+                }
+            };
+
+            Runnable onFinish = new Runnable() {
+                public void run() {
+                    CloseInput message = new CloseInput(clientMetadata);
+                    LOGGER.debug("Dispatching close input message: {}", message);
+                    dispatch.dispatch(message);
+                }
+            };
+
+            forwarder = new InputForwarder(inputStream, dispatcher, onFinish, executorFactory, bufferSize).start();
+            started = true;
+            return this;
+        } finally {
+            lifecycleLock.unlock();
+        }
+    }
+
+    public void stop() {
+        lifecycleLock.lock();
+        try {
+            if (started) {
+                LOGGER.debug("input forwarder stop requested");
+                try {
+                    forwarder.stop();
+                } finally {
+                    forwarder = null;
+                    started = false;
+                }
+            }
+        } finally {
+            lifecycleLock.unlock();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
new file mode 100644
index 0000000..fdc60f5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.context.DaemonContextBuilder;
+import org.gradle.launcher.daemon.logging.DaemonGreeter;
+import org.gradle.launcher.daemon.registry.DaemonDir;
+import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+
+import java.io.InputStream;
+
+/**
+ * Takes care of instantiating and wiring together the services required by the daemon client.
+ */
+public class DaemonClientServices extends DaemonClientServicesSupport {
+    private final DaemonParameters daemonParameters;
+
+    public DaemonClientServices(ServiceRegistry loggingServices, DaemonParameters daemonParameters, InputStream buildStandardInput) {
+        super(loggingServices, buildStandardInput);
+        this.daemonParameters = daemonParameters;
+        add(new DaemonRegistryServices(daemonParameters.getBaseDir()));
+    }
+
+    public DaemonStarter createDaemonStarter() {
+        return new DefaultDaemonStarter(get(DaemonDir.class), daemonParameters, get(DaemonGreeter.class));
+    }
+
+    protected DaemonGreeter createDaemonGreeter() {
+        return new DaemonGreeter(get(DocumentationRegistry.class));
+    }
+
+    protected void configureDaemonContextBuilder(DaemonContextBuilder builder) {
+        builder.setDaemonRegistryDir(get(DaemonDir.class).getBaseDir());
+        builder.useDaemonParameters(daemonParameters);
+    }
+
+    public DaemonParameters getDaemonParameters() {
+        return daemonParameters;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
new file mode 100644
index 0000000..78eb821
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.context.DaemonCompatibilitySpec;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.context.DaemonContextBuilder;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
+import org.gradle.messaging.remote.internal.OutgoingConnector;
+import org.gradle.messaging.remote.internal.inet.TcpOutgoingConnector;
+
+import java.io.InputStream;
+
+/**
+ * Some support wiring for daemon clients.
+ * 
+ * @see DaemonClientServices
+ * @see EmbeddedDaemonClientServices
+ */
+abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry {
+
+    private final ServiceRegistry loggingServices;
+    private final InputStream buildStandardInput;
+
+    public DaemonClientServicesSupport(ServiceRegistry loggingServices, InputStream buildStandardInput) {
+        this.loggingServices = loggingServices;
+        this.buildStandardInput = buildStandardInput;
+        add(new NativeServices());
+    }
+
+    public ServiceRegistry getLoggingServices() {
+        return loggingServices;
+    }
+
+    protected InputStream getBuildStandardInput() {
+        return buildStandardInput;
+    }
+
+    protected DaemonClient createDaemonClient() {
+        DaemonCompatibilitySpec matchingContextSpec = new DaemonCompatibilitySpec(get(DaemonContext.class));
+        return new DaemonClient(get(DaemonConnector.class), get(BuildClientMetaData.class), get(OutputEventListener.class), matchingContextSpec, buildStandardInput);
+    }
+
+    protected DaemonContext createDaemonContext() {
+        DaemonContextBuilder builder = new DaemonContextBuilder(get(ProcessEnvironment.class));
+        configureDaemonContextBuilder(builder);
+        return builder.create();
+    }
+
+    // subclass hook, allowing us to fake the context for testing
+    protected void configureDaemonContextBuilder(DaemonContextBuilder builder) {
+        
+    }
+
+    protected OutputEventListener createOutputEventListener() {
+        return getLoggingServices().get(OutputEventListener.class);
+    }
+
+    protected BuildClientMetaData createBuildClientMetaData() {
+        return new GradleLauncherMetaData();
+    }
+
+    protected OutgoingConnector<Object> createOutgoingConnector() {
+        return new TcpOutgoingConnector<Object>(new DefaultMessageSerializer<Object>(getClass().getClassLoader()));
+    }
+
+    protected DaemonConnector createDaemonConnector() {
+        return new DefaultDaemonConnector(get(DaemonRegistry.class), get(OutgoingConnector.class), get(DaemonStarter.class));
+    }
+
+    protected DocumentationRegistry createDocumentationRegistry() {
+        return new DocumentationRegistry(get(GradleDistributionLocator.class));
+    }
+
+    protected DefaultModuleRegistry createModuleRegistry() {
+        return new DefaultModuleRegistry();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java
new file mode 100644
index 0000000..a3dc2bb
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.messaging.remote.internal.Connection;
+
+/**
+ * A simple wrapper for the connection to a daemon plus its password.
+ */
+public class DaemonConnection {
+
+    private final String uid;
+    private final Connection<Object> connection;
+    private final String password;
+
+    public DaemonConnection(String uid, Connection<Object> connection, String password) {
+        this.uid = uid;
+        this.connection = connection;
+        this.password = password;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public Connection<Object> getConnection() {
+        return this.connection;
+    }
+
+    public String getPassword() {
+        return this.password;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java
new file mode 100644
index 0000000..d847b2f
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.launcher.daemon.context.DaemonContext;
+
+/**
+ * A daemon connector establishes a connection to either an already running daemon, or a newly started daemon.
+ */
+public interface DaemonConnector {
+
+    /**
+     * Attempts to connect to a daemon that matches the given constraint.
+     *
+     * @return A connection to a matching daemon, or null if none running.
+     */
+    public DaemonConnection maybeConnect(Spec<? super DaemonContext> constraint);
+
+    /**
+     * Connects to a daemon that matches the given constraint, starting one if required.
+     *
+     * @return A connection to a matching daemon. Never returns null.
+     */
+    public DaemonConnection connect(Spec<? super DaemonContext> constraint);
+
+    public DaemonConnection createConnection();
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonDisappearedException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonDisappearedException.java
new file mode 100644
index 0000000..0650906
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonDisappearedException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.GradleException;
+
+/**
+ * Thrown when a daemon disconnects unexpectedly while a client is interacting with it.
+ */
+public class DaemonDisappearedException extends GradleException {
+
+    public static final String MESSAGE = "Gradle build daemon disappeared unexpectedly (it may have been stopped, killed or may have crashed)";
+
+    public DaemonDisappearedException() {
+        super(MESSAGE);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonInitialConnectException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonInitialConnectException.java
new file mode 100644
index 0000000..e3ac62e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonInitialConnectException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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.daemon.client;
+
+import org.gradle.api.GradleException;
+
+class DaemonInitialConnectException extends GradleException {
+    public DaemonInitialConnectException(String message) {
+        super(message);
+    }
+
+    public DaemonInitialConnectException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java
new file mode 100644
index 0000000..6fd7b96
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+public interface DaemonStarter {
+    String startDaemon();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
new file mode 100644
index 0000000..b06e17a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.UncheckedException;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.registry.DaemonInfo;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.messaging.remote.internal.ConnectException;
+import org.gradle.messaging.remote.internal.OutgoingConnector;
+
+import java.util.List;
+
+/**
+ * Provides the mechanics of connecting to a daemon, starting one via a given runnable if no suitable daemons are already available.
+ */
+public class DefaultDaemonConnector implements DaemonConnector {
+    private static final Logger LOGGER = Logging.getLogger(DefaultDaemonConnector.class);
+    public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
+    private final DaemonRegistry daemonRegistry;
+    private final OutgoingConnector<Object> connector;
+    private final DaemonStarter daemonStarter;
+    private long connectTimeout = DefaultDaemonConnector.DEFAULT_CONNECT_TIMEOUT;
+
+    public DefaultDaemonConnector(DaemonRegistry daemonRegistry, OutgoingConnector<Object> connector, DaemonStarter daemonStarter) {
+        this.daemonRegistry = daemonRegistry;
+        this.connector = connector;
+        this.daemonStarter = daemonStarter;
+    }
+
+    public void setConnectTimeout(long connectTimeout) {
+        this.connectTimeout = connectTimeout;
+    }
+
+    public long getConnectTimeout() {
+        return connectTimeout;
+    }
+
+    public DaemonRegistry getDaemonRegistry() {
+        return daemonRegistry;
+    }
+
+    public DaemonConnection maybeConnect(Spec<? super DaemonContext> constraint) {
+        return findConnection(daemonRegistry.getAll(), constraint);
+    }
+
+    public DaemonConnection connect(Spec<? super DaemonContext> constraint) {
+        DaemonConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
+        if (connection != null) {
+            return connection;
+        }
+
+        return createConnection();
+    }
+
+    private DaemonConnection findConnection(List<DaemonInfo> daemonInfos, Spec<? super DaemonContext> constraint) {
+        for (DaemonInfo daemonInfo : daemonInfos) {
+            if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
+                LOGGER.debug("Found daemon (address: {}, idle: {}) however it's context does not match the desired criteria.\n"
+                        + "  Wanted: {}.\n"
+                        + "  Found:  {}.\n"
+                        + "  Looking for a different daemon...", daemonInfo.getAddress(), daemonInfo.isIdle(), constraint, daemonInfo.getContext());
+                continue;
+            }
+
+            try {
+                return connectToDaemon(daemonInfo);
+            } catch (ConnectException e) {
+                //this means the daemon died without removing its address from the registry
+                //we can safely remove this address now
+                LOGGER.debug("We cannot connect to the daemon at " + daemonInfo.getAddress() + " due to " + e + ". "
+                        + "We will not remove this daemon from the registry because the connection issue may have been temporary.");
+                //TODO it might be good to store in the registry the number of failed attempts to connect to the deamon
+                //if the number is high we may decide to remove the daemon from the registry
+                //daemonRegistry.remove(address);
+            }
+        }
+        return null;
+    }
+
+    public DaemonConnection createConnection() {
+        LOGGER.info("Starting Gradle daemon");
+        final String uid = daemonStarter.startDaemon();
+        LOGGER.debug("Started Gradle Daemon with UID = {}", uid);
+        long expiry = System.currentTimeMillis() + connectTimeout;
+        do {
+            try {
+                Thread.sleep(200L);
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+            DaemonConnection daemonConnection = connectToDaemonWithId(uid);
+            if (daemonConnection != null) {
+                return daemonConnection;
+            }
+        } while (System.currentTimeMillis() < expiry);
+
+        throw new GradleException("Timeout waiting to connect to Gradle daemon.");
+    }
+
+    private DaemonConnection connectToDaemonWithId(String uid) throws ConnectException {
+        // Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that nobody else will grab it.
+        for (DaemonInfo daemonInfo : daemonRegistry.getBusy()) {
+            if (daemonInfo.getContext().getUid().equals(uid)) {
+                try {
+                    // TODO:DAZ We should verify the connection using the original daemon constraint
+                    return connectToDaemon(daemonInfo);
+                } catch (ConnectException e) {
+                    // this means the daemon died without removing its address from the registry
+                    // since we have never successfully connected we assume the daemon is dead and remove this address now
+                    daemonRegistry.remove(daemonInfo.getAddress());
+                    throw new GradleException("The forked daemon process died before we could connect");
+                }
+            }
+        }
+        return null;
+    }
+
+    private DaemonConnection connectToDaemon(DaemonInfo daemonInfo) {
+        return new DaemonConnection(daemonInfo.getContext().getUid(), connector.connect(daemonInfo.getAddress()), daemonInfo.getPassword());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
new file mode 100755
index 0000000..bd18970
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.bootstrap.GradleDaemon;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.logging.DaemonGreeter;
+import org.gradle.launcher.daemon.registry.DaemonDir;
+import org.gradle.process.internal.ProcessParentingInitializer;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultDaemonStarter implements DaemonStarter {
+
+    private static final Logger LOGGER = Logging.getLogger(DefaultDaemonStarter.class);
+
+    private final DaemonDir daemonDir;
+    private final DaemonParameters daemonParameters;
+    private DaemonGreeter daemonGreeter;
+
+    public DefaultDaemonStarter(DaemonDir daemonDir, DaemonParameters daemonParameters, DaemonGreeter daemonGreeter) {
+        this.daemonDir = daemonDir;
+        this.daemonParameters = daemonParameters;
+        this.daemonGreeter = daemonGreeter;
+    }
+
+    public String startDaemon() {
+        DefaultModuleRegistry registry = new DefaultModuleRegistry();
+        Set<File> bootstrapClasspath = new LinkedHashSet<File>();
+        bootstrapClasspath.addAll(registry.getModule("gradle-launcher").getImplementationClasspath().getAsFiles());
+        if (registry.getGradleHome() == null) {
+            // Running from the classpath - chuck in everything we can find
+            bootstrapClasspath.addAll(registry.getFullClasspath());
+        }
+        if (bootstrapClasspath.isEmpty()) {
+            throw new IllegalStateException("Unable to construct a bootstrap classpath when starting the daemon");
+        }
+
+        List<String> daemonArgs = new ArrayList<String>();
+        daemonArgs.add(daemonParameters.getEffectiveJavaExecutable());
+
+        List<String> daemonOpts = daemonParameters.getEffectiveJvmArgs();
+        LOGGER.debug("Using daemon opts: {}", daemonOpts);
+        daemonArgs.addAll(daemonOpts);
+        //Useful for debugging purposes - simply uncomment and connect to debug
+//        daemonArgs.add("-Xdebug");
+//        daemonArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006");
+        daemonArgs.add("-cp");
+        daemonArgs.add(GUtil.join(bootstrapClasspath, File.pathSeparator));
+        daemonArgs.add(GradleDaemon.class.getName());
+        daemonArgs.add(GradleVersion.current().getVersion());
+        daemonArgs.add(daemonDir.getBaseDir().getAbsolutePath());
+        daemonArgs.add(String.valueOf(daemonParameters.getIdleTimeout()));
+        daemonArgs.add(daemonParameters.getUid());
+
+        //all remaining arguments are daemon startup jvm opts.
+        //we need to pass them as *program* arguments to avoid problems with getInputArguments().
+        daemonArgs.addAll(daemonOpts);
+
+        startProcess(daemonArgs, daemonDir.getVersionedDir());
+
+        return daemonParameters.getUid();
+    }
+
+    private void startProcess(List<String> args, File workingDir) {
+        LOGGER.info("Starting daemon process: workingDir = {}, daemonArgs: {}", workingDir, args);
+        try {
+            workingDir.mkdirs();
+            ProcessParentingInitializer.intitialize();
+            Process process = new ProcessBuilder(args).redirectErrorStream(true).directory(workingDir).start();
+            daemonGreeter.verifyGreetingReceived(process);
+
+            process.getOutputStream().close();
+            process.getErrorStream().close();
+            process.getInputStream().close();
+        } catch (GradleException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new GradleException("Could not start Gradle daemon.", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java
new file mode 100644
index 0000000..19d43f3
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.context.DaemonContextBuilder;
+import org.gradle.launcher.daemon.registry.DaemonDir;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry;
+import org.gradle.launcher.daemon.server.Daemon;
+import org.gradle.launcher.daemon.server.DaemonServerConnector;
+import org.gradle.launcher.daemon.server.DaemonTcpServerConnector;
+import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter;
+import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+
+import java.io.File;
+import java.util.UUID;
+
+/**
+ * Wires together the embedded daemon client.
+ */
+public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
+
+    private final boolean displayOutput;
+
+    public EmbeddedDaemonClientServices() {
+        this(LoggingServiceRegistry.newCommandLineProcessLogging(), false);
+    }
+
+    private class EmbeddedDaemonFactory implements Factory<Daemon> {
+        public Daemon create() {
+            return new Daemon(
+                get(DaemonServerConnector.class),
+                get(DaemonRegistry.class),
+                get(DaemonContext.class),
+                "password",
+                get(DaemonCommandExecuter.class),
+                get(ExecutorFactory.class)
+            );
+        }
+    }
+
+    protected DaemonCommandExecuter createDaemonCommandExecuter() {
+        LoggingManagerInternal mgr = getLoggingServices().getFactory(LoggingManagerInternal.class).create();
+        return new DefaultDaemonCommandExecuter(new DefaultGradleLauncherFactory(getLoggingServices()),
+                get(ExecutorFactory.class), get(ProcessEnvironment.class), mgr, new File("dummy"));
+    }
+
+    public EmbeddedDaemonClientServices(ServiceRegistry loggingServices, boolean displayOutput) {
+        super(loggingServices, System.in);
+        this.displayOutput = displayOutput;
+        add(EmbeddedDaemonFactory.class, new EmbeddedDaemonFactory());
+    }
+
+    protected DaemonRegistry createDaemonRegistry() {
+        return new EmbeddedDaemonRegistry();
+    }
+
+    protected OutputEventListener createOutputEventListener() {
+        if (displayOutput) {
+            return super.createOutputEventListener();
+        } else {
+            return new OutputEventListener() { public void onOutput(OutputEvent event) {} };
+        }
+    }
+
+    @Override
+    protected void configureDaemonContextBuilder(DaemonContextBuilder builder) {
+        builder.setUid(UUID.randomUUID().toString());
+        builder.setDaemonRegistryDir(new DaemonDir(new DaemonParameters().getBaseDir()).getRegistry());
+    }
+
+    protected ExecutorFactory createExecutorFactory() {
+        return new DefaultExecutorFactory();
+    }
+
+    protected DaemonServerConnector createDaemonServerConnector() {
+        return new DaemonTcpServerConnector();
+    }
+
+    protected DaemonStarter createDaemonStarter() {
+        return new EmbeddedDaemonStarter((EmbeddedDaemonRegistry)get(DaemonRegistry.class), getFactory(Daemon.class));
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java
new file mode 100644
index 0000000..2994c1e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.internal.Factory;
+import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry;
+import org.gradle.launcher.daemon.server.Daemon;
+
+class EmbeddedDaemonStarter implements DaemonStarter {
+
+    private final EmbeddedDaemonRegistry daemonRegistry;
+    private final Factory<Daemon> daemonFactory;
+
+    public EmbeddedDaemonStarter(EmbeddedDaemonRegistry daemonRegistry, Factory<Daemon> daemonFactory) {
+        this.daemonRegistry = daemonRegistry;
+        this.daemonFactory = daemonFactory;
+    }
+
+    public String startDaemon() {
+        Daemon daemon = daemonFactory.create();
+        daemonRegistry.startDaemon(daemon);
+        return daemon.getUid();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java
new file mode 100644
index 0000000..078d774
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/NoUsableDaemonFoundException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.launcher.daemon.client;
+
+import org.gradle.api.GradleException;
+
+/**
+* by Szczepan Faber, created at: 2/24/12
+*/
+public class NoUsableDaemonFoundException extends GradleException {
+    public NoUsableDaemonFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java
new file mode 100644
index 0000000..3db92ba
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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.daemon.client;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.launcher.daemon.protocol.BuildAndStop;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.messaging.remote.internal.Connection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+public class SingleUseDaemonClient extends DaemonClient {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SingleUseDaemonClient.class);
+
+    public SingleUseDaemonClient(DaemonConnector connector, BuildClientMetaData clientMetaData, OutputEventListener outputEventListener, Spec<DaemonContext> compatibilitySpec, InputStream buildStandardInput) {
+        super(connector, clientMetaData, outputEventListener, compatibilitySpec, buildStandardInput);
+    }
+
+    @Override
+    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
+        LOGGER.warn("Note: in order to honour the org.gradle.jvmargs and/or org.gradle.java.home values specified for this build, it is necessary to fork a new JVM.");
+        LOGGER.warn("This forked JVM is effectively a single-use daemon process. In order to avoid the slowdown associated with this extra process, you might want to consider running Gradle with --daemon.");
+        Build build = new BuildAndStop(action, parameters);
+
+        DaemonConnection daemonConnection = connector.createConnection();
+        Connection<Object> connection = daemonConnection.getConnection();
+
+        return (T) executeBuild(build, connection);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java
new file mode 100644
index 0000000..f55dceb
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.daemon.client;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.logging.internal.OutputEventListener;
+
+import java.io.InputStream;
+
+public class SingleUseDaemonClientServices extends DaemonClientServices {
+    public SingleUseDaemonClientServices(ServiceRegistry loggingServices, DaemonParameters daemonParameters, InputStream buildStandardInput) {
+        super(loggingServices, daemonParameters, buildStandardInput);
+    }
+
+    @Override
+    protected DaemonClient createDaemonClient() {
+        Spec<DaemonContext> matchNone = Specs.satisfyNone();
+        return new SingleUseDaemonClient(get(DaemonConnector.class), get(BuildClientMetaData.class), get(OutputEventListener.class), matchNone, getBuildStandardInput());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
new file mode 100644
index 0000000..3aedc43
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.daemon.client;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.logging.internal.OutputEventListener;
+
+import java.io.InputStream;
+
+public class StopDaemonClientServices extends DaemonClientServices {
+    public StopDaemonClientServices(ServiceRegistry loggingServices, DaemonParameters daemonParameters, InputStream buildStandardInput) {
+        super(loggingServices, daemonParameters, buildStandardInput);
+    }
+
+    @Override
+    protected DaemonClient createDaemonClient() {
+        Spec<DaemonContext> matchAll = Specs.satisfyAll();
+        return new DaemonClient(get(DaemonConnector.class), get(BuildClientMetaData.class), get(OutputEventListener.class), matchAll, getBuildStandardInput());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java
new file mode 100644
index 0000000..e5a9766
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.daemon.client;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.launcher.daemon.protocol.Stop;
+import org.gradle.messaging.remote.internal.Connection;
+
+/**
+ * @author: Szczepan Faber, created at: 9/13/11
+ */
+public class StopDispatcher {
+
+    private static final Logger LOGGER = Logging.getLogger(StopDispatcher.class);
+
+    public void dispatch(BuildClientMetaData clientMetaData, Connection<Object> connection) {
+        //At the moment if we cannot communicate with the daemon we assume it is stopped and print a message to the user
+        try {
+            try {
+                connection.dispatch(new Stop(clientMetaData));
+            } catch (Exception e) {
+                LOGGER.lifecycle("Unable to send the Stop command to one of the daemons. The daemon has already stopped or crashed.");
+                LOGGER.debug("Unable to send Stop.", e);
+                return;
+            }
+            try {
+                connection.receive();
+            } catch (Exception e) {
+                LOGGER.lifecycle("The daemon didn't reply to Stop command. It is already stopped or crashed.");
+                LOGGER.debug("Unable to receive reply.", e);
+            }
+        } finally {
+            connection.stop();
+        }
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/CurrentProcess.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/CurrentProcess.java
new file mode 100644
index 0000000..f5bbc5e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/CurrentProcess.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.daemon.configuration;
+
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.process.internal.JvmOptions;
+import org.gradle.internal.jvm.Jvm;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.util.Properties;
+
+public class CurrentProcess {
+    private final File javaHome;
+    private final JvmOptions effectiveJvmOptions;
+
+    public CurrentProcess() {
+        this(Jvm.current().getJavaHome(), inferJvmOptions());
+    }
+
+    public CurrentProcess(File javaHome, JvmOptions effectiveJvmOptions) {
+        this.javaHome = javaHome;
+        this.effectiveJvmOptions = effectiveJvmOptions;
+    }
+
+    public JvmOptions getJvmOptions() {
+        return effectiveJvmOptions;
+    }
+
+    public File getJavaHome() {
+        return javaHome;
+    }
+
+    /**
+     * Attempts to configure the current process to run with the required build parameters.
+     * @return True if the current process could be configured, false otherwise.
+     */
+    public boolean configureForBuild(DaemonParameters requiredBuildParameters) {
+        boolean javaHomeMatch = getJavaHome().equals(requiredBuildParameters.getEffectiveJavaHome());
+
+        JvmOptions optionsWithNoArgsSet = new JvmOptions(new IdentityFileResolver());
+        boolean noImmutableJvmArgsRequired = requiredBuildParameters.isUsingDefaultJvmArgs()
+                || requiredBuildParameters.getEffectiveJvmArgs().equals(optionsWithNoArgsSet.getAllImmutableJvmArgs());
+
+        if (javaHomeMatch && noImmutableJvmArgsRequired) {
+            // Set the system properties and use this process
+            Properties properties = new Properties();
+            properties.putAll(requiredBuildParameters.getEffectiveSystemProperties());
+            System.setProperties(properties);
+            return true;
+        }
+        return false;
+    }
+
+    private static JvmOptions inferJvmOptions() {
+        // Try to infer the effective jvm options for the currently running process.
+        // We only care about 'managed' jvm args, anything else is unimportant to the running build
+        JvmOptions jvmOptions = new JvmOptions(new IdentityFileResolver());
+        jvmOptions.setAllJvmArgs(ManagementFactory.getRuntimeMXBean().getInputArguments());
+        return jvmOptions;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
new file mode 100644
index 0000000..d84f73b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2011 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.daemon.configuration;
+
+import org.gradle.StartParameter;
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.initialization.layout.BuildLayout;
+import org.gradle.initialization.layout.BuildLayoutFactory;
+import org.gradle.internal.jvm.JavaHomeException;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.process.internal.JvmOptions;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.*;
+
+import static java.util.Arrays.asList;
+import static org.gradle.util.GFileUtils.canonicalise;
+
+public class DaemonParameters {
+    public static final String IDLE_TIMEOUT_SYS_PROPERTY = "org.gradle.daemon.idletimeout";
+    public static final String BASE_DIR_SYS_PROPERTY = "org.gradle.daemon.registry.base";
+    public static final String JVM_ARGS_SYS_PROPERTY = "org.gradle.jvmargs";
+    public static final String JAVA_HOME_SYS_PROPERTY = "org.gradle.java.home";
+    public static final String DAEMON_SYS_PROPERTY = "org.gradle.daemon";
+    static final int DEFAULT_IDLE_TIMEOUT = 3 * 60 * 60 * 1000;
+    private final String uid;
+    private File baseDir = new File(StartParameter.DEFAULT_GRADLE_USER_HOME, "daemon");
+    private int idleTimeout = DEFAULT_IDLE_TIMEOUT;
+    private final JvmOptions jvmOptions = new JvmOptions(new IdentityFileResolver());
+    private boolean usingDefaultJvmArgs = true;
+    private boolean enabled;
+    private File javaHome;
+
+    public DaemonParameters(String uid) {
+        this.uid = uid;
+        jvmOptions.setAllJvmArgs(getDefaultJvmArgs());
+    }
+
+    public DaemonParameters() {
+        this(UUID.randomUUID().toString());
+    }
+
+    List<String> getDefaultJvmArgs() {
+        return new LinkedList<String>(asList("-Xmx1024m", "-XX:MaxPermSize=256m", "-XX:+HeapDumpOnOutOfMemoryError"));
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public File getBaseDir() {
+        return baseDir;
+    }
+
+    public void setBaseDir(File baseDir) {
+        this.baseDir = GFileUtils.canonicalise(baseDir);
+    }
+
+    public int getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    public void setIdleTimeout(int idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public List<String> getEffectiveJvmArgs() {
+        return jvmOptions.getAllImmutableJvmArgs();
+    }
+
+    public List<String> getAllJvmArgs() {
+        return jvmOptions.getAllJvmArgs();
+    }
+
+    public boolean isUsingDefaultJvmArgs() {
+        return usingDefaultJvmArgs;
+    }
+
+    public File getEffectiveJavaHome() {
+        if (javaHome == null) {
+            return canonicalise(Jvm.current().getJavaHome());
+        }
+        return javaHome;
+    }
+    
+    public String getEffectiveJavaExecutable() {
+        if (javaHome == null) {
+            return Jvm.current().getJavaExecutable().getAbsolutePath();
+        }
+        return Jvm.forHome(javaHome).getJavaExecutable().getAbsolutePath();
+    }
+
+    public void setJavaHome(File javaHome) {
+        this.javaHome = javaHome;
+    }
+
+    public Map<String, String> getSystemProperties() {
+        Map<String, String> systemProperties = new HashMap<String, String>();
+        GUtil.addToMap(systemProperties, jvmOptions.getSystemProperties());
+        return systemProperties;
+    }
+
+    public Map<String, String> getEffectiveSystemProperties() {
+        Map<String, String> systemProperties = new HashMap<String, String>();
+        GUtil.addToMap(systemProperties, jvmOptions.getSystemProperties());
+        GUtil.addToMap(systemProperties, System.getProperties());
+        return systemProperties;
+    }
+
+    public void setJvmArgs(Iterable<String> jvmArgs) {
+        usingDefaultJvmArgs = false;
+        jvmOptions.setAllJvmArgs(jvmArgs);
+    }
+
+    public void configureFromGradleUserHome(File gradleUserHomeDir) {
+        setBaseDir(new File(gradleUserHomeDir, "daemon"));
+        maybeConfigureFrom(new File(gradleUserHomeDir, Project.GRADLE_PROPERTIES));
+    }
+
+    public void configureFromSystemProperties(Map<?, ?> properties) {
+        Object propertyValue = properties.get(BASE_DIR_SYS_PROPERTY);
+        if (propertyValue != null) {
+            setBaseDir(new File(propertyValue.toString()));
+        }
+        configureFrom(properties);
+    }
+
+    public void configureFromBuildDir(File currentDir, boolean searchUpwards) {
+        BuildLayoutFactory factory = new BuildLayoutFactory();
+        BuildLayout layout = factory.getLayoutFor(currentDir, searchUpwards);
+        maybeConfigureFrom(new File(layout.getRootDirectory(), Project.GRADLE_PROPERTIES));
+    }
+
+    private void maybeConfigureFrom(File propertiesFile) {
+        if (!propertiesFile.isFile()) {
+            return;
+        }
+
+        Properties properties = new Properties();
+        try {
+            FileInputStream inputStream = new FileInputStream(propertiesFile);
+            try {
+                properties.load(inputStream);
+            } finally {
+                inputStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        configureFrom(properties);
+    }
+
+    void configureFrom(Map<?, ?> properties) {
+        Object propertyValue = properties.get(IDLE_TIMEOUT_SYS_PROPERTY);
+        if (propertyValue != null) {
+            try {
+                idleTimeout = Integer.parseInt(propertyValue.toString());
+            } catch (NumberFormatException e) {
+                throw new GradleException(String.format("Unable to parse %s property. The value should be an int but is: %s", IDLE_TIMEOUT_SYS_PROPERTY, propertyValue));
+            }
+        }
+        propertyValue = properties.get(JVM_ARGS_SYS_PROPERTY);
+        if (propertyValue != null) {
+            setJvmArgs(JvmOptions.fromString(propertyValue.toString()));
+        }
+        propertyValue = properties.get(DAEMON_SYS_PROPERTY);
+        if (propertyValue != null) {
+            enabled = propertyValue.toString().equalsIgnoreCase("true");
+        }
+
+        propertyValue = properties.get(JAVA_HOME_SYS_PROPERTY);
+        if (propertyValue != null) {
+            javaHome = new File(propertyValue.toString());
+            if (!javaHome.isDirectory()) {
+                throw new GradleException(String.format("Java home supplied via '%s' is invalid. Dir does not exist: %s", JAVA_HOME_SYS_PROPERTY, propertyValue));
+            }
+            try {
+                Jvm.forHome(javaHome);
+            } catch (JavaHomeException e) {
+                throw new GradleException(String.format("Java home supplied via '%s' seems to be invalid: %s", JAVA_HOME_SYS_PROPERTY, propertyValue));
+            }
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java
new file mode 100644
index 0000000..a91bcec
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonServerConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * 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.launcher.daemon.configuration;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 2/21/12
+ */
+public interface DaemonServerConfiguration {
+
+    File getBaseDir();
+
+    int getIdleTimeout();
+
+    String getUid();
+
+    List<String> getJvmOptions();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java
new file mode 100644
index 0000000..8e0f898
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DefaultDaemonServerConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * 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.launcher.daemon.configuration;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 2/21/12
+ */
+public class DefaultDaemonServerConfiguration implements DaemonServerConfiguration {
+
+    private final String daemonUid;
+    private final File daemonBaseDir;
+    private final int idleTimeoutMs;
+    private final List<String> jvmOptions;
+
+    public DefaultDaemonServerConfiguration(String daemonUid, File daemonBaseDir, int idleTimeoutMs, List<String> jvmOptions) {
+        this.daemonUid = daemonUid;
+        this.daemonBaseDir = daemonBaseDir;
+        this.idleTimeoutMs = idleTimeoutMs;
+        this.jvmOptions = jvmOptions;
+    }
+
+    public File getBaseDir() {
+        return daemonBaseDir;
+    }
+
+    public int getIdleTimeout() {
+        return idleTimeoutMs;
+    }
+
+    public String getUid() {
+        return daemonUid;
+    }
+
+    public List<String> getJvmOptions() {
+        return jvmOptions;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java
new file mode 100644
index 0000000..d5fd87f
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/ForegroundDaemonConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * 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.launcher.daemon.configuration;
+
+import java.io.File;
+
+/**
+ * by Szczepan Faber, created at: 2/21/12
+ */
+public class ForegroundDaemonConfiguration extends DefaultDaemonServerConfiguration {
+    public ForegroundDaemonConfiguration(String daemonUid, File daemonBaseDir, int idleTimeoutMs) {
+        // Foreground daemon cannot be 'told' what's his startup options as the client sits in the same process so we will infer the jvm opts from the inputArguments()
+        // Simplification, we will make the foreground daemon interested only in managed jvm args
+        super(daemonUid, daemonBaseDir, idleTimeoutMs, new CurrentProcess().getJvmOptions().getManagedJvmArgs());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java
new file mode 100644
index 0000000..6b78591
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.daemon.context;
+
+import org.gradle.api.specs.Spec;
+
+public class DaemonCompatibilitySpec implements Spec<DaemonContext> {
+
+    private final DaemonContext desiredContext;
+    
+    public DaemonCompatibilitySpec(DaemonContext desiredContext) {
+        this.desiredContext = desiredContext;
+    }
+
+    public boolean isSatisfiedBy(DaemonContext potentialContext) {
+        return potentialContext.getJavaHome().equals(desiredContext.getJavaHome())
+                && potentialContext.getDaemonOpts().containsAll(desiredContext.getDaemonOpts())
+                && potentialContext.getDaemonOpts().size() == desiredContext.getDaemonOpts().size();
+    }
+
+    @Override
+    public String toString() {
+        return desiredContext.toString();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContext.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContext.java
new file mode 100644
index 0000000..a6830e9
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContext.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.daemon.context;
+
+import java.io.Serializable;
+import java.io.File;
+import java.util.List;
+
+/**
+ * A value object that describes a daemons environment/context.
+ * <p>
+ * This is used by clients to determine whether or not a daemon meets its requirements
+ * such as JDK version, special system properties etc.
+ * <p>
+ * Instances must be serializable because they are shared via the DaemonRegistry, which is permitted
+ * to use serialization to communicate across VM boundaries. Implementations are not required to be,
+ * but should also be immutable.
+ *
+ * @see DaemonContextBuilder
+ * @see DaemonCompatibilitySpec
+ */
+public interface DaemonContext extends Serializable {
+
+    /**
+     * The unique identifier for this daemon.
+     */
+    String getUid();
+
+    /**
+     * The JAVA_HOME in use, as the canonical file.
+     */
+    File getJavaHome();
+
+    /**
+     * The directory that should be used for daemon storage (not including the gradle version number).
+     */
+    File getDaemonRegistryDir();
+
+    /**
+     * The process id of the daemon.
+     */
+    Long getPid();
+
+    /**
+     * The daemon's idle timeout in milliseconds.
+     */
+    Integer getIdleTimeout();
+
+    /**
+     * Returns the JVM options that the daemon was started with.
+     *
+     * @return the JVM options that the daemon was started with
+     */
+    List<String> getDaemonOpts();
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java
new file mode 100644
index 0000000..6a70eaa
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonContextBuilder.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 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.daemon.context;
+
+import com.google.common.collect.Lists;
+import org.gradle.internal.Factory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.internal.jvm.Jvm;
+
+import java.io.File;
+import java.util.List;
+
+import static org.gradle.util.GFileUtils.canonicalise;
+
+/**
+ * Builds a daemon context, reflecting the current environment.
+ * <p>
+ * The builder itself has properties for different context values, that allow you to override
+ * what would be set based on the environment. This is primarily to aid in testing.
+ */
+public class DaemonContextBuilder implements Factory<DaemonContext> {
+
+    private String uid;
+    private File javaHome;
+    private File daemonRegistryDir;
+    private Long pid;
+    private Integer idleTimeout;
+    private List<String> daemonOpts = Lists.newArrayList();
+
+    public DaemonContextBuilder(ProcessEnvironment processEnvironment) {
+        javaHome = canonicalise(Jvm.current().getJavaHome());
+        pid = processEnvironment.maybeGetPid();
+    }
+
+    public File getJavaHome() {
+        return javaHome;
+    }
+
+    public void setJavaHome(File javaHome) {
+        this.javaHome = javaHome;
+    }
+
+    public File getDaemonRegistryDir() {
+        return this.daemonRegistryDir = daemonRegistryDir;
+    }
+
+    public void setDaemonRegistryDir(File daemonRegistryDir) {
+        this.daemonRegistryDir = daemonRegistryDir;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public void setUid(String uid) {
+        this.uid = uid;
+    }
+
+    public Long getPid() {
+        return pid;
+    }
+    
+    public void setPid(Long pid) {
+        this.pid = pid;
+    }
+
+    public Integer getIdleTimeout() {
+        return idleTimeout;
+    }
+    
+    public void setIdleTimeout(Integer idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public List<String> getDaemonOpts() {
+        return daemonOpts;
+    }
+
+    public void setDaemonOpts(List<String> daemonOpts) {
+        this.daemonOpts = daemonOpts;
+    }
+
+    public void useDaemonParameters(DaemonParameters daemonParameters) {
+        setJavaHome(daemonParameters.getEffectiveJavaHome());
+        setDaemonOpts(daemonParameters.getEffectiveJvmArgs());
+    }
+
+    /**
+     * Creates a new daemon context, based on the current state of this builder.
+     */
+    public DaemonContext create() {
+        if (daemonRegistryDir == null) {
+            throw new IllegalStateException("Registry dir must be specified.");
+        }
+        return new DefaultDaemonContext(uid, javaHome, daemonRegistryDir, pid, idleTimeout, daemonOpts);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DefaultDaemonContext.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DefaultDaemonContext.java
new file mode 100644
index 0000000..3abda08
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DefaultDaemonContext.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 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.daemon.context;
+
+import com.google.common.base.Joiner;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Keep in mind that this is a serialized value object.
+ *
+ * @see DaemonContextBuilder
+ */
+public class DefaultDaemonContext implements DaemonContext {
+
+    private final String uid;
+    private final File javaHome;
+    private final File daemonRegistryDir;
+    private final Long pid;
+    private final Integer idleTimeout;
+    private final List<String> daemonOpts;
+
+    public DefaultDaemonContext(String uid, File javaHome, File daemonRegistryDir, Long pid, Integer idleTimeout, List<String> daemonOpts) {
+        this.uid = uid;
+        this.javaHome = javaHome;
+        this.daemonRegistryDir = daemonRegistryDir;
+        this.pid = pid;
+        this.idleTimeout = idleTimeout;
+        this.daemonOpts = daemonOpts;
+    }
+
+    public String toString() {
+        return String.format("DefaultDaemonContext[uid=%s,javaHome=%s,daemonRegistryDir=%s,pid=%s,idleTimeout=%s,daemonOpts=%s]",
+                uid, javaHome, daemonRegistryDir, pid, idleTimeout, Joiner.on(',').join(daemonOpts));
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public File getJavaHome() {
+        return javaHome;
+    }
+
+    public File getDaemonRegistryDir() {
+        return daemonRegistryDir;
+    }
+
+    public Long getPid() {
+        return pid;
+    }
+
+    public Integer getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    public List<String> getDaemonOpts() {
+        return daemonOpts;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java
new file mode 100644
index 0000000..afa515b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java
@@ -0,0 +1,44 @@
+/*
+ * 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.launcher.daemon.diagnostics;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Contains some daemon diagnostics information useful for the client.
+ * <p>
+ * by Szczepan Faber, created at: 2/28/12
+ */
+public class DaemonDiagnostics implements Serializable {
+
+    private final Long pid;
+    private final File daemonLog;
+
+    public DaemonDiagnostics(File daemonLog, Long pid) {
+        this.daemonLog = daemonLog;
+        this.pid = pid;
+    }
+
+    public Long getPid() {
+        return pid;
+    }
+
+    public File getDaemonLog() {
+        return daemonLog;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonGreeter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonGreeter.java
new file mode 100644
index 0000000..759beb5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonGreeter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.daemon.logging;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.DocumentationRegistry;
+
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 1/19/12
+ */
+public class DaemonGreeter {
+    private final DocumentationRegistry documentationRegistry;
+
+    public DaemonGreeter(DocumentationRegistry documentationRegistry) {
+        this.documentationRegistry = documentationRegistry;
+    }
+
+    public void verifyGreetingReceived(Process process) {
+        List<String> lines;
+        try {
+            lines = IOUtils.readLines(process.getInputStream());
+        } catch (Exception e) {
+            throw new GradleException("Unable to get a greeting message from the daemon process."
+                    + " Most likely the daemon process cannot be started.", e);
+        }
+
+        String lastMessage = lines.get(lines.size() - 1);
+        if (!lastMessage.equals(DaemonMessages.PROCESS_STARTED)) {
+            // consider waiting a bit for the exit value
+            // if exit value not provided warn that the daemon didn't exit
+            int exitValue;
+            try {
+                exitValue = process.exitValue();
+            } catch (IllegalThreadStateException e) {
+                throw new GradleException(
+                    DaemonMessages.UNABLE_TO_START_DAEMON + " However, it appears the process hasn't exited yet."
+                    + "\n" + processOutput(lines));
+            }
+            throw new GradleException(DaemonMessages.UNABLE_TO_START_DAEMON + " The exit value was: " + exitValue + "."
+                    + "\n" + processOutput(lines));
+        }
+    }
+
+    private String processOutput(List<String> lines) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("This problem might be caused by incorrect configuration of the daemon.\n");
+        sb.append("For example, an unrecognized jvm option is used.\n");
+        sb.append("Please refer to the user guide chapter on the daemon at ");
+        sb.append(documentationRegistry.getDocumentationFor("gradle_daemon"));
+        sb.append("\n");
+        sb.append("Please read below process output to find out more:\n");
+        sb.append("-----------------------\n");
+
+        for (String line : lines) {
+            sb.append(line).append("\n");
+        }
+        return sb.toString();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java
new file mode 100644
index 0000000..c86ee2a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.daemon.logging;
+
+/**
+ * by Szczepan Faber, created at: 1/19/12
+ */
+public class DaemonMessages {
+    
+    public final static String PROCESS_STARTED = "Daemon server started.";
+    public final static String STARTED_RELAYING_LOGS = "The client will now receive all logging from the daemon (pid: ";
+    public final static String UNABLE_TO_START_DAEMON = "Unable to start the daemon process.";
+    public final static String STARTED_EXECUTING_COMMAND = "Starting executing command: ";
+    public final static String FINISHED_EXECUTING_COMMAND = "Finishing executing command: ";
+    public final static String FINISHED_BUILD = "The daemon has finished executing the build.";
+    public final static String NO_DAEMONS_RUNNING = "No Gradle daemons are running.";
+    public final static String ABOUT_TO_START_RELAYING_LOGS = "About to start relaying all logs to the client via the connection.";
+    public final static String DAEMON_VM_SHUTTING_DOWN = "Daemon vm is shutting down... The daemon has exited normally or was terminated in response to a user interrupt.";
+    public final static String REMOVING_PRESENCE_DUE_TO_STOP = "Stop requested. Daemon is removing its presence from the registry...";
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
new file mode 100644
index 0000000..a24a4b6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
@@ -0,0 +1,80 @@
+/*
+ * 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.daemon.protocol;
+
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.InitializationAware;
+
+public class Build extends Command {
+    private final GradleLauncherAction<?> action;
+    private final BuildActionParameters parameters;
+
+    private transient StartParameter startParameter;
+        
+    public Build(GradleLauncherAction<?> action, BuildActionParameters parameters) {
+        super(parameters.getClientMetaData());
+        this.action = action;
+        this.parameters = parameters;
+    }
+
+    public GradleLauncherAction<?> getAction() {
+        return action;
+    }
+
+    public BuildActionParameters getParameters() {
+        return parameters;
+    }
+    
+    public StartParameter getStartParameter() {
+        if (startParameter == null) {
+            if (action instanceof InitializationAware) {
+                InitializationAware initializationAware = (InitializationAware) action;
+                startParameter = initializationAware.configureStartParameter();
+            } else {
+                startParameter = new StartParameter();
+            }
+        }
+        
+        return startParameter;
+    }
+    
+    public GradleLauncher createGradleLauncher(GradleLauncherFactory launcherFactory) {
+        return launcherFactory.newInstance(getStartParameter(), parameters.getBuildRequestMetaData());
+    }
+    
+    public Object run(GradleLauncherFactory launcherFactory) {
+        return run(createGradleLauncher(launcherFactory));
+    }
+    
+    public Object run(GradleLauncher launcher) {
+        BuildResult buildResult = action.run(launcher);
+        buildResult.rethrowFailure();
+        return action.getResult();
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{"
+                + "id=" + getIdentifier()
+                + ", currentDir=" + parameters.getCurrentDir()
+                + '}';
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
new file mode 100644
index 0000000..633725c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.daemon.protocol;
+
+import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.launcher.exec.BuildActionParameters;
+
+public class BuildAndStop extends Build {
+    public BuildAndStop(GradleLauncherAction<?> action, BuildActionParameters parameters) {
+        super(action, parameters);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildStarted.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildStarted.java
new file mode 100644
index 0000000..02621fa
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildStarted.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+
+import java.io.Serializable;
+
+/**
+ * Returned when the daemon starts a build command, signifying that it has begun processing it.
+ */
+public class BuildStarted implements Serializable {
+
+    private final DaemonDiagnostics diagnostics;
+
+    public BuildStarted(DaemonDiagnostics diagnostics) {
+        this.diagnostics = diagnostics;
+    }
+
+    public DaemonDiagnostics getDiagnostics() {
+        return diagnostics;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java
new file mode 100644
index 0000000..d8bad4f
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+import org.gradle.initialization.BuildClientMetaData;
+
+public class CloseInput extends IoCommand {
+
+    public CloseInput(BuildClientMetaData clientMetaData) {
+        super(clientMetaData);
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.java
new file mode 100644
index 0000000..4e8da51
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.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.daemon.protocol;
+
+import org.gradle.initialization.BuildClientMetaData;
+
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Command implements Serializable {
+
+    private static final AtomicInteger SEQUENCER = new AtomicInteger(1);
+
+    private final BuildClientMetaData clientMetaData;
+    private final String identifier;
+
+    public Command(BuildClientMetaData clientMetaData) {
+        this.clientMetaData = clientMetaData;
+        //unique only within the process but this should be enough
+        this.identifier = System.currentTimeMillis() + "-" + SEQUENCER.getAndIncrement();
+    }
+
+    public BuildClientMetaData getClientMetaData() {
+        return clientMetaData;
+    }
+
+    /**
+     * @return an id that is guaranteed to be unique in the same process
+     */
+    public String getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s[id=%s]", getClass().getSimpleName(), identifier);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CommandFailure.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CommandFailure.java
new file mode 100644
index 0000000..e889a33
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CommandFailure.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+/**
+ * Signifies that the daemon was able to run the command as expected, but the command itself did not complete successfully.
+ * <p>
+ * This is different to {@link DaemonFailure} which signifies that the daemon itself encountered some internal error
+ * trying to execute the command it received.
+ */
+public class CommandFailure extends Failure {
+
+    public CommandFailure(Throwable value) {
+        super(value);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonBusy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonBusy.java
new file mode 100644
index 0000000..01faa9e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonBusy.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+/**
+ * Returned when the daemon is busy running a different command. The command that the
+ * daemon is running is the value of this result.
+ */
+public class DaemonBusy extends Result<Command> {
+
+    public DaemonBusy(Command value) {
+        super(value);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonFailure.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonFailure.java
new file mode 100644
index 0000000..04619a7
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/DaemonFailure.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+/**
+ * Signifies that the daemon infrastructure encountered an internal error in trying to
+ * execute the command it received.
+ * <p>
+ * This is different to {@link CommandFailure}, which signifies that the daemon did its job successfully
+ * but the command itself failed.
+ */
+public class DaemonFailure extends Failure {
+    public DaemonFailure(Throwable value) {
+        super(value);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Failure.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Failure.java
new file mode 100644
index 0000000..ee4f0ff
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Failure.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+/**
+ * The base of all kinds of failure results.
+ * <p>
+ * The “value” of this result will be an exception that represents the failure. It may not be {@code null}.
+ */
+abstract public class Failure extends Result<Throwable> {
+    
+    public Failure(Throwable value) {
+        super(assertNotNull(value));
+    }
+    
+    private static Throwable assertNotNull(Throwable value) {
+        if (value == null) {
+            throw new IllegalArgumentException("The value parameter of a failure cannot be null");
+        }
+        
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java
new file mode 100644
index 0000000..27bfade
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+import org.gradle.initialization.BuildClientMetaData;
+
+public class ForwardInput extends IoCommand {
+    
+    private final byte[] bytes;
+    
+    public ForwardInput(BuildClientMetaData clientMetaData, byte[] bytes) {
+        super(clientMetaData);
+        this.bytes = bytes;
+    }
+    
+    public byte[] getBytes() {
+        return bytes;
+    }
+    
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java
new file mode 100644
index 0000000..b176a4d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+import org.gradle.initialization.BuildClientMetaData;
+
+abstract public class IoCommand extends Command {
+
+    public IoCommand(BuildClientMetaData clientMetaData) {
+        super(clientMetaData);
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Result.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Result.java
new file mode 100644
index 0000000..61220ae
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Result.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+import java.io.Serializable;
+
+/**
+ * The supertype of all objects sent from the daemon server back to the client.
+ * <p>
+ * Specific subclass types carry extra context, e.g. whether it was a failure or successful result.
+ * <p>
+ * The meaning of the value parameter is specific to each concrete subclass. The validity of {@code null}
+ * is also to be defined by each subclass. This implementation does allow null values.
+ */
+abstract public class Result<T> implements Serializable {
+
+    private final T value;
+
+    public Result(T value) {
+        this.value = value;
+    }
+
+    public T getValue() {
+        return value;
+    }
+    
+    @Override
+    public String toString() {
+        return String.format("DaemonCommandResult[type=%s, value=%s]", getClass().getSimpleName(), getValue());
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java
new file mode 100644
index 0000000..4282ca8
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java
@@ -0,0 +1,24 @@
+/*
+ * 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.daemon.protocol;
+
+import org.gradle.initialization.BuildClientMetaData;
+
+public class Stop extends Command {
+    public Stop(BuildClientMetaData clientMetaData) {
+        super(clientMetaData);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Success.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Success.java
new file mode 100644
index 0000000..dd4abc1
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Success.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.daemon.protocol;
+
+/**
+ * Signifies that the command completed successfully.
+ * <p>
+ * The value of the result is whatever the command produced on the server.
+ * Its meaning depends on the original command.
+ * <p>
+ * This result type does permit {@code null} values. If it is an error for a certain
+ * command to produce a {@code null} value that must be handled externally to this class.
+ */
+public class Success extends Result<Object> {
+
+    public Success(Object value) {
+        super(value);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java
new file mode 100644
index 0000000..3da23f9
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonDir.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+
+public class DaemonDir {
+    private final File baseDir;
+    private final File versionedDir;
+    private final File registryFile;
+
+    public DaemonDir(File baseDir) {
+        this.baseDir = baseDir;
+        this.versionedDir = new File(baseDir, String.format("%s", GradleVersion.current().getVersion()));
+        this.registryFile = new File(versionedDir, "registry.bin");
+        this.versionedDir.mkdirs();
+    }
+
+    public File getBaseDir() {
+        return baseDir;
+    }
+    
+    public File getVersionedDir() {
+        return versionedDir;
+    }
+
+    public File getRegistry() {
+        return registryFile;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonInfo.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonInfo.java
new file mode 100644
index 0000000..9d822a5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonInfo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.launcher.daemon.context.DaemonContext;
+
+import java.io.Serializable;
+
+/**
+ * Provides information about a daemon that is potentially available to do some work.
+ */
+public class DaemonInfo implements Serializable {
+
+    private final Address address;
+    private final DaemonContext context;
+    private final String password;
+    private boolean idle = true;
+
+    public DaemonInfo(Address address, DaemonContext context, String password) {
+        this.address = address;
+        this.context = context;
+        this.password = password;
+    }
+
+    public DaemonInfo setIdle(boolean idle) {
+        this.idle = idle;
+        return this;
+    }
+
+    public Address getAddress() {
+        return address;
+    }
+
+    public DaemonContext getContext() {
+        return context;
+    }
+
+    public boolean isIdle() {
+        return idle;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("DaemonInfo{address=%s, idle=%s, context=%s}", address, idle, context);
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java
new file mode 100644
index 0000000..1b1061a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.launcher.daemon.context.DaemonContext;
+
+import java.util.List;
+
+/**
+ * Provides access to existing daemons.
+ */
+public interface DaemonRegistry {
+
+    List<DaemonInfo> getAll();
+    List<DaemonInfo> getIdle();
+    List<DaemonInfo> getBusy();
+    
+    void store(Address address, DaemonContext daemonContext, String password);
+    void remove(Address address);
+    void markBusy(Address address);
+    void markIdle(Address address);
+
+    static class EmptyRegistryException extends RuntimeException {
+        public EmptyRegistryException(String message) {
+            super(message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java
new file mode 100644
index 0000000..8955cbb
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryContent.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.messaging.remote.Address;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author: Szczepan Faber, created at: 8/29/11
+ */
+public class DaemonRegistryContent implements Serializable {
+
+    private Map<Address, DaemonInfo> infosMap = new HashMap<Address, DaemonInfo>();
+
+    /**
+     * returns all statuses. May be empty.
+     */
+    public List<DaemonInfo> getInfos() {
+        return new LinkedList<DaemonInfo>(infosMap.values());
+    }
+
+    /**
+     * Gets the status for given address. May return null.
+     */
+    public DaemonInfo getInfo(Address address) {
+        return infosMap.get(address);
+    }
+
+    /**
+     * Removes the status
+     */
+    public void removeInfo(Address address) {
+        infosMap.remove(address);
+    }
+
+    /**
+     * sets the daemonInfo for given address
+     */
+    public void setStatus(Address address, DaemonInfo daemonInfo) {
+        infosMap.put(address, daemonInfo);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryServices.java
new file mode 100644
index 0000000..4fd9a79
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistryServices.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.cache.Cache;
+import org.gradle.api.internal.cache.CacheAccessSerializer;
+import org.gradle.api.internal.cache.MapBackedCache;
+import org.gradle.cache.internal.DefaultFileLockManager;
+import org.gradle.cache.internal.DefaultProcessMetaDataProvider;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.DefaultServiceRegistry;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Takes care of instantiating and wiring together the services required for a daemon registry.
+ */
+public class DaemonRegistryServices extends DefaultServiceRegistry {
+    private final File daemonBaseDir;
+    private final Cache<File, DaemonRegistry> daemonRegistryCache;
+
+    private static final Map<File, DaemonRegistry> REGISTRY_STORAGE = new HashMap<File, DaemonRegistry>();
+    private static final Cache<File, DaemonRegistry> REGISTRY_CACHE = new CacheAccessSerializer<File, DaemonRegistry>(
+            new MapBackedCache<File, DaemonRegistry>(REGISTRY_STORAGE)
+    );
+
+    public DaemonRegistryServices(File daemonBaseDir) {
+        this(daemonBaseDir, REGISTRY_CACHE);
+        add(new NativeServices());
+    }
+
+    protected DaemonRegistryServices(File daemonBaseDir, Cache<File, DaemonRegistry> daemonRegistryCache) {
+        this.daemonBaseDir = daemonBaseDir;
+        this.daemonRegistryCache = daemonRegistryCache;
+    }
+
+    protected DaemonDir createDaemonDir() {
+        return new DaemonDir(daemonBaseDir);
+    }
+
+    protected FileLockManager createFileLockManager() {
+        return new DefaultFileLockManager(new DefaultProcessMetaDataProvider(get(ProcessEnvironment.class)));
+    }
+
+    protected DaemonRegistry createDaemonRegistry() {
+        final File daemonRegistryFile = get(DaemonDir.class).getRegistry();
+        return daemonRegistryCache.get(daemonRegistryFile, new Factory<DaemonRegistry>() {
+            public DaemonRegistry create() {
+                return new PersistentDaemonRegistry(daemonRegistryFile, get(FileLockManager.class));
+            }
+        });
+    }
+    
+    protected Properties createProperties() {
+        return System.getProperties();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java
new file mode 100644
index 0000000..a919511
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.launcher.daemon.server.Daemon;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.messaging.remote.Address;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A daemon registry for daemons running in the same JVM.
+ * <p>
+ * This implementation is thread safe in that its getAll(), getIdle() and getBusy() methods are expected to be called from “consumer” threads,
+ * while the newEntry() method is expected to be called by “producer” threads.
+ * <p>
+ * The collections returned by the consumer methods do not return live collections so may not reflect the precise state of the registry
+ * by the time they are returned to the caller. Clients must therefore be prepared for this and expect connection failures, either through
+ * the endpoint disappearing or becoming busy between asking for idle daemons and trying to connect.
+ */
+public class EmbeddedDaemonRegistry implements DaemonRegistry, Stoppable {
+
+    private final Map<Address, DaemonInfo> daemonInfos = new ConcurrentHashMap<Address, DaemonInfo>();
+    private final List<Daemon> daemons = new ArrayList<Daemon>();
+    private final Lock daemonsLock = new ReentrantLock();
+
+    private final Spec<DaemonInfo> allSpec = new Spec<DaemonInfo>() {
+        public boolean isSatisfiedBy(DaemonInfo entry) {
+            return true;
+        }
+    };
+
+    @SuppressWarnings("unchecked")
+    private final Spec<DaemonInfo> idleSpec = Specs.<DaemonInfo>and(allSpec, new Spec<DaemonInfo>() {
+        public boolean isSatisfiedBy(DaemonInfo daemonInfo) {
+            return daemonInfo.isIdle();
+        }
+    });
+
+    @SuppressWarnings("unchecked")
+    private final Spec<DaemonInfo> busySpec = Specs.<DaemonInfo>and(allSpec, new Spec<DaemonInfo>() {
+        public boolean isSatisfiedBy(DaemonInfo daemonInfo) {
+            return !daemonInfo.isIdle();
+        }
+    });
+
+    public List<DaemonInfo> getAll() {
+        return daemonInfosOfEntriesMatching(allSpec);
+    }
+
+    public List<DaemonInfo> getIdle() {
+        return daemonInfosOfEntriesMatching(idleSpec);
+    }
+
+    public List<DaemonInfo> getBusy() {
+        return daemonInfosOfEntriesMatching(busySpec);
+    }
+
+    public void store(Address address, DaemonContext daemonContext, String password) {
+        daemonInfos.put(address, new DaemonInfo(address, daemonContext, password));
+    }
+
+    public void remove(Address address) {
+        daemonInfos.remove(address);
+    }
+
+    public void markBusy(Address address) {
+        synchronized (daemonInfos) {
+            daemonInfos.get(address).setIdle(false);
+        }
+    }
+
+    public void markIdle(Address address) {
+        synchronized (daemonInfos) {
+            daemonInfos.get(address).setIdle(true);
+        }
+    }
+
+    private List<DaemonInfo> daemonInfosOfEntriesMatching(Spec<DaemonInfo> spec) {
+        List<DaemonInfo> matches = new ArrayList<DaemonInfo>();
+        for (DaemonInfo daemonInfo : daemonInfos.values()) {
+            if (spec.isSatisfiedBy(daemonInfo)) {
+                matches.add(daemonInfo);
+            }
+        }
+
+        return matches;
+    }
+
+    /**
+     * Returns all daemons started in this registry since construction or most recent stopDaemons().
+     * <p>
+     * The returned daemons are not guaranteed to be running as they may have been stopped individually.
+     */
+    public List<Daemon> getDaemons() {
+        daemonsLock.lock();
+        try {
+            return new ArrayList<Daemon>(daemons);
+        } finally {
+            daemonsLock.unlock();
+        }
+    }
+    
+    public void startDaemon(Daemon daemon) {
+        daemonsLock.lock();
+        try {
+            daemons.add(daemon);
+        } finally {
+            daemonsLock.unlock();
+        }
+
+        daemon.start();
+    }
+
+    public void stop() {
+        List<Daemon> daemonsToStop;
+        
+        daemonsLock.lock();
+        try {
+            daemonsToStop = new ArrayList<Daemon>(daemons);
+            daemons.clear();
+        } finally {
+            daemonsLock.unlock();
+        }
+        
+        new CompositeStoppable(daemonsToStop).stop();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
new file mode 100644
index 0000000..2139df4
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2011 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.daemon.registry;
+
+import org.gradle.cache.DefaultSerializer;
+import org.gradle.cache.PersistentStateCache;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.cache.internal.OnDemandFileAccess;
+import org.gradle.cache.internal.SimpleStateCache;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.messaging.remote.Address;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Access to daemon registry files. Useful also for testing.
+ *
+ * @author: Szczepan Faber, created at: 8/18/11
+ */
+public class PersistentDaemonRegistry implements DaemonRegistry {
+    private final SimpleStateCache<DaemonRegistryContent> cache;
+    private final Lock lock = new ReentrantLock();
+    private final File registryFile;
+
+    public PersistentDaemonRegistry(File registryFile, FileLockManager fileLockManager) {
+        this.registryFile = registryFile;
+        cache = new SimpleStateCache<DaemonRegistryContent>(
+                registryFile,
+                new OnDemandFileAccess(
+                        registryFile,
+                        "daemon addresses registry",
+                        fileLockManager),
+                new DefaultSerializer<DaemonRegistryContent>());
+    }
+
+    public List<DaemonInfo> getAll() {
+        lock.lock();
+        try {
+            DaemonRegistryContent content = cache.get();
+            if (content == null) {
+                //when no daemon process has started yet
+                return new LinkedList<DaemonInfo>();
+            }
+            return content.getInfos();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public List<DaemonInfo> getIdle() {
+        lock.lock();
+        try {
+            List<DaemonInfo> out = new LinkedList<DaemonInfo>();
+            List<DaemonInfo> all = getAll();
+            for (DaemonInfo d : all) {
+                if (d.isIdle()) {
+                    out.add(d);
+                }
+            }
+            return out;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public List<DaemonInfo> getBusy() {
+        lock.lock();
+        try {
+            List<DaemonInfo> out = new LinkedList<DaemonInfo>();
+            List<DaemonInfo> all = getAll();
+            for (DaemonInfo d : all) {
+                if (!d.isIdle()) {
+                    out.add(d);
+                }
+            }
+            return out;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void remove(final Address address) {
+        lock.lock();
+        try {
+            cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
+                public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
+                    assertCacheNotEmpty(oldValue);
+                    oldValue.removeInfo(address);
+                    return oldValue;
+                }
+            });
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void markBusy(final Address address) {
+        lock.lock();
+        try {
+            cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
+                public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
+                    assertCacheNotEmpty(oldValue);
+                    DaemonInfo daemonInfo = oldValue.getInfo(address);
+                    daemonInfo.setIdle(false);
+                    return oldValue;
+                }
+            });
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void markIdle(final Address address) {
+        lock.lock();
+        try {
+            cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
+                public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
+                    assertCacheNotEmpty(oldValue);
+                    oldValue.getInfo(address).setIdle(true);
+                    return oldValue;
+                }
+            });
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public synchronized void store(final Address address, final DaemonContext daemonContext, final String password) {
+        lock.lock();
+        try {
+            cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
+                public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
+                    if (oldValue == null) {
+                        //it means the registry didn't exist yet
+                        oldValue = new DaemonRegistryContent();
+                    }
+                    DaemonInfo daemonInfo = new DaemonInfo(address, daemonContext, password).setIdle(true);
+                    oldValue.setStatus(address, daemonInfo);
+                    return oldValue;
+                }
+            });
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public String toString() {
+        return String.format("PersistentDaemonRegistry[file=%s]", registryFile);
+    }
+
+    private void assertCacheNotEmpty(Object value) {
+        if (value == null) {
+            throw new EmptyRegistryException("Registry is empty!");
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java
new file mode 100644
index 0000000..e7b0bf6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java
@@ -0,0 +1,239 @@
+/*
+ * 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.daemon.server;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Stoppable;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.launcher.daemon.protocol.Command;
+import org.gradle.launcher.daemon.protocol.DaemonFailure;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.Connection;
+
+import java.util.Date;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A long-lived build server that accepts commands via a communication channel.
+ * <p>
+ * Daemon instances are single use and have a start/stop debug. They are also threadsafe.
+ * <p>
+ * See {@link org.gradle.launcher.daemon.client.DaemonClient} for a description of the daemon communication protocol.
+ */
+public class Daemon implements Runnable, Stoppable {
+
+    private static final Logger LOGGER = Logging.getLogger(Daemon.class);
+
+    private final DaemonServerConnector connector;
+    private final DaemonRegistry daemonRegistry;
+    private final DaemonContext daemonContext;
+    private final DaemonCommandExecuter commandExecuter;
+    private final String password;
+
+    private DaemonStateCoordinator stateCoordinator;
+
+    private final StoppableExecutor handlersExecutor;
+
+    private final Lock lifecyleLock = new ReentrantLock();
+
+    private Address connectorAddress;
+    private DomainRegistryUpdater registryUpdater;
+
+    /**
+     * Creates a new daemon instance.
+     * 
+     * @param connector The provider of server connections for this daemon
+     * @param daemonRegistry The registry that this daemon should advertise itself in
+     */
+    public Daemon(DaemonServerConnector connector, DaemonRegistry daemonRegistry, DaemonContext daemonContext, String password, DaemonCommandExecuter commandExecuter, ExecutorFactory executorFactory) {
+        this.connector = connector;
+        this.daemonRegistry = daemonRegistry;
+        this.daemonContext = daemonContext;
+        this.password = password;
+        this.commandExecuter = commandExecuter;
+        handlersExecutor = executorFactory.create("Daemon Connection Handler");
+    }
+
+    public String getUid() {
+        return daemonContext.getUid();
+    }
+
+    public Address getAddress() {
+        return connectorAddress;
+    }
+
+    /**
+     * Starts the daemon, receiving connections asynchronously (i.e. returns immediately).
+     * 
+     * @throws IllegalStateException if this daemon is already running, or has already been stopped.
+     */
+    public void start() {
+        LOGGER.info("start() called on daemon - {}", daemonContext);
+        lifecyleLock.lock();
+        try {
+            if (stateCoordinator != null) {
+                throw new IllegalStateException("cannot start daemon as it is already running");
+            }
+            
+
+            // Get ready to accept connections, but we are assuming that no connections will be established
+            // because we have not yet advertised that we are open for business by entering our address into
+            // the registry, which happens a little further down in this method.
+            connectorAddress = connector.start(new IncomingConnectionHandler() {
+                public void handle(final Connection<Object> connection) {
+
+                    //we're spinning a thread to do work to avoid blocking the connection
+                    //This means that the Daemon potentially can do multiple things but we only allows a single build at a time
+                    handlersExecutor.execute(new Runnable() {
+                        private Command command;
+                        public void run() {
+                            try {
+                                command = (Command) connection.receive();
+                                LOGGER.info("Daemon (pid: {}) received command: {}.", daemonContext.getPid(), command);
+                            } catch (Throwable e) {
+                                String message = String.format("Unable to receive command from connection: '%s'", connection);
+                                LOGGER.warn(message + ". Dispatching the failure to the daemon client...", e);
+                                connection.dispatch(new DaemonFailure(new RuntimeException(message, e)));
+                                //TODO SF exception handling / send typed exception / refactor / unit test and apply the same for below
+                                return;
+                            }
+
+                            try {
+                                LOGGER.debug(DaemonMessages.STARTED_EXECUTING_COMMAND + command + " with connection: " + connection + ".");
+                                commandExecuter.executeCommand(connection, command, daemonContext, stateCoordinator);
+                            } catch (Throwable e) {
+                                String message = String.format("Uncaught exception when executing command: '%s' from connection: '%s'.", command, connection);
+                                LOGGER.warn(message + ". Dispatching the failure to the daemon client...", e);
+                                connection.dispatch(new DaemonFailure(new RuntimeException(message, e)));
+                            } finally {
+                                LOGGER.debug(DaemonMessages.FINISHED_EXECUTING_COMMAND + command);
+                            }
+                        }
+                    });
+                }
+            });
+
+            registryUpdater = new DomainRegistryUpdater(daemonRegistry, daemonContext, password, connectorAddress);
+            
+            Runnable onStart = new Runnable() {
+                public void run() {
+                    LOGGER.debug("Daemon starting at: " + new Date() + ", with address: " + connectorAddress);
+                    registryUpdater.onStart();
+                }
+            };
+            
+            Runnable onStartCommand = new Runnable() {
+                public void run() {
+                    registryUpdater.onStartActivity();
+                }
+            };
+
+            Runnable onFinishCommand = new Runnable() {
+                public void run() {
+                    registryUpdater.onCompleteActivity();
+                }
+            };
+            
+            Runnable onStop = new Runnable() {
+                public void run() {
+                    LOGGER.info("Daemon is stopping accepting new connections...");
+                    connector.stop(); // will block until any running commands are finished
+                }
+            };
+
+            Runnable onStopRequested = new Runnable() {
+                public void run() {
+                    LOGGER.info(DaemonMessages.REMOVING_PRESENCE_DUE_TO_STOP);
+                    registryUpdater.onStop();
+                }
+            };
+
+            stateCoordinator = new DaemonStateCoordinator(onStart, onStartCommand, onFinishCommand, onStop, onStopRequested);
+
+            // ready, set, go
+            stateCoordinator.start();
+        } finally {
+            lifecyleLock.unlock();
+        }
+    }
+
+    /**
+     * Stops the daemon, blocking until any current requests/connections have been satisfied.
+     * <p>
+     * This is the semantically the same as sending the daemon the Stop command.
+     * <p>
+     * This method does not quite conform to the semantics of the Stoppable contract in that it will NOT
+     * wait for any executing builds to stop before returning. This is by design as we currently have no way of
+     * gracefully stopping a build process and blocking until it's done would not allow us to tear down the jvm
+     * like we need to. This may change in the future if we create a way to interrupt a build.
+     * <p>
+     * What will happen though is that the daemon will immediately disconnect from any clients and remove itself
+     * from the registry.
+     */
+    public void stop() {
+        LOGGER.debug("stop() called on daemon");
+        lifecyleLock.lock();
+        try {
+            stateCoordinator.stop();
+        } finally {
+            lifecyleLock.unlock();
+        }
+    }
+
+    /**
+     * Blocks until this daemon is stopped by something else (i.e. does not ask it to stop)
+     */
+    public void awaitStop() {
+        LOGGER.debug("awaitStop() called on daemon");
+        stateCoordinator.awaitStop();
+    }
+
+    /**
+     * Waits until the daemon is either stopped, or has been idle for the given number of milliseconds.
+     *
+     * @return true if it was stopped, false if it hit the given idle timeout.
+     */
+    public boolean awaitStopOrIdleTimeout(int idleTimeout) {
+        LOGGER.debug("awaitStopOrIdleTimeout({}) called on daemon", idleTimeout);
+        return stateCoordinator.awaitStopOrIdleTimeout(idleTimeout);
+    }
+
+    /**
+     * Waits for the daemon to be idle for the specified number of milliseconds.
+     * 
+     * @throws DaemonStoppedException if the daemon is explicitly stopped instead of idling out.
+     */
+    public void awaitIdleTimeout(int idleTimeout) throws DaemonStoppedException {
+        LOGGER.debug("awaitIdleTimeout({}) called on daemon", idleTimeout);
+        stateCoordinator.awaitIdleTimeout(idleTimeout);
+    }
+
+    /**
+     * Starts the daemon, blocking until it is stopped (either by Stop command or by another thread calling stop())
+     */
+    public void run() {
+        start();
+        awaitStop();
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServerConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServerConnector.java
new file mode 100644
index 0000000..282c2fd
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServerConnector.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.daemon.server;
+
+import org.gradle.internal.Stoppable;
+import org.gradle.messaging.remote.Address;
+
+/**
+ * Opens a server connection for clients to connect to to communicate with a daemon.
+ * <p>
+ * A server connector should only be used by one daemon, and has a single use lifecycle.
+ * Implementations must be threadsafe so that start/stop can be called from different threads.
+ */
+public interface DaemonServerConnector extends Stoppable {
+
+    /**
+     * Starts accepting connections, passing any established connections to the given handler.
+     * <p>
+     * When this method returns, the daemon will be ready to accept connections.
+     *
+     * @return the address that clients can use to connect
+     * @throws IllegalStateException if this method has previously been called on this object, or if the stop method has previously been called on this object.
+     */
+    Address start(final IncomingConnectionHandler handler);
+
+    /**
+     * Stops accepting new connections, and blocks until all active connections close.
+     */
+    public void stop();
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java
new file mode 100644
index 0000000..4526c0e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 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.daemon.server;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.configuration.DaemonServerConfiguration;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.context.DaemonContextBuilder;
+import org.gradle.launcher.daemon.registry.DaemonDir;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
+import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+
+import java.io.File;
+import java.util.UUID;
+
+/**
+ * Takes care of instantiating and wiring together the services required by the daemon server.
+ */
+public class DaemonServices extends DefaultServiceRegistry {
+    private final DaemonServerConfiguration configuration;
+    private final ServiceRegistry loggingServices;
+    private final LoggingManagerInternal loggingManager;
+    private final static Logger LOGGER = Logging.getLogger(DaemonServices.class);
+
+    public DaemonServices(DaemonServerConfiguration configuration, ServiceRegistry loggingServices,
+                          LoggingManagerInternal loggingManager) {
+        this.configuration = configuration;
+        this.loggingServices = loggingServices;
+        this.loggingManager = loggingManager;
+
+        add(new NativeServices());
+        add(new DaemonRegistryServices(configuration.getBaseDir()));
+    }
+
+    protected ExecutorFactory createExecutorFactory() {
+        return new DefaultExecutorFactory();
+    }
+
+    protected DaemonContext createDaemonContext() {
+        DaemonContextBuilder builder = new DaemonContextBuilder(get(ProcessEnvironment.class));
+        builder.setDaemonRegistryDir(configuration.getBaseDir());
+        builder.setIdleTimeout(configuration.getIdleTimeout());
+        builder.setUid(configuration.getUid());
+
+        LOGGER.debug("Creating daemon context with opts: {}", configuration.getJvmOptions());
+        
+        builder.setDaemonOpts(configuration.getJvmOptions());
+
+        return builder.create();
+    }
+    
+    public File getDaemonLogFile() {
+        final DaemonContext daemonContext = get(DaemonContext.class);
+        final Long pid = daemonContext.getPid();
+        String fileName = String.format("daemon-%s.out.log", pid == null ? UUID.randomUUID() : pid);
+        return new File(get(DaemonDir.class).getVersionedDir(), fileName);
+    }
+
+    protected Daemon createDaemon() {
+        return new Daemon(
+                new DaemonTcpServerConnector(),
+                get(DaemonRegistry.class),
+                get(DaemonContext.class),
+                "password",
+                new DefaultDaemonCommandExecuter(
+                        new DefaultGradleLauncherFactory(loggingServices),
+                        get(ExecutorFactory.class),
+                        get(ProcessEnvironment.class),
+                        loggingManager,
+                        getDaemonLogFile()),
+                get(ExecutorFactory.class));
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java
new file mode 100644
index 0000000..d5f86b5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2011 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.daemon.server;
+
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.launcher.daemon.server.exec.DaemonCommandExecution;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+
+import java.util.Date;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A tool for synchronising the state amongst different threads.
+ *
+ * This class has no knowledge of the Daemon's internals and is designed to be used internally by the
+ * daemon to coordinate itself and allow worker threads to control the daemon's busy/idle status.
+ *
+ * This is not exposed to clients of the daemon.
+ */
+public class DaemonStateCoordinator implements Stoppable {
+
+    private static final org.gradle.api.logging.Logger LOGGER = Logging.getLogger(DaemonStateCoordinator.class);
+
+    private final Lock lock = new ReentrantLock();
+    Condition condition = lock.newCondition();
+
+    private boolean stoppingOrStopped;
+    private boolean stopped;
+    private long lastActivityAt = -1;
+    private DaemonCommandExecution currentCommandExecution;
+
+    private final Runnable onStart;
+    private final Runnable onStartCommand;
+    private final Runnable onFinishCommand;
+    private final Runnable onStop;
+    private final Runnable onStopRequested;
+
+    public DaemonStateCoordinator(Runnable onStart, Runnable onStartCommand, Runnable onFinishCommand, Runnable onStop, Runnable onStopRequested) {
+        this.onStart = onStart;
+        this.onStartCommand = onStartCommand;
+        this.onFinishCommand = onFinishCommand;
+        this.onStop = onStop;
+        this.onStopRequested = onStopRequested;
+    }
+
+    /**
+     * Waits until stopped.
+     */
+    public void awaitStop() {
+        lock.lock();
+        try {
+            while (!isStopped()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called once when the daemon is up and ready for connections.
+     */
+    public void start() {
+        lock.lock();
+        try {
+            updateActivityTimestamp();
+            onStart.run();
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Waits until stopped, or timeout.
+     *
+     * @return true if stopped, false if timeout
+     */
+    public boolean awaitStopOrIdleTimeout(int timeout) {
+        lock.lock();
+        try {
+            LOGGER.debug("waiting for daemon to stop or be idle for {}ms", timeout);
+            while (true) {
+                if (isStopped()) {
+                    return true;
+                } else if (hasBeenIdleFor(timeout)) {
+                    return false;
+                }
+            
+                try {
+                    if (!isStarted()) {
+                        LOGGER.debug("waiting for daemon to stop or idle timeout, daemon has not yet started, sleeping until then");
+                        condition.await();
+                    } else if (isBusy()) {
+                        LOGGER.debug("waiting for daemon to stop or idle timeout, daemon is busy, sleeping until it finishes");
+                        condition.await();
+                    } else if (isIdle()) {
+                        Date waitUntil = new Date(lastActivityAt + timeout);
+                        LOGGER.debug("waiting for daemon to stop or idle timeout, daemon is idle, sleeping until daemon state change or idle timeout at {}", waitUntil);
+                        condition.awaitUntil(waitUntil);
+                    } else {
+                        throw new IllegalStateException("waiting for daemon to stop or idle timeout, daemon has started but is not busy or idle, this shouldn't happen");
+                    }
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void awaitIdleTimeout(int timeout) throws DaemonStoppedException {
+        if (awaitStopOrIdleTimeout(timeout)) {
+            throw new DaemonStoppedException(currentCommandExecution);
+        }
+    }
+
+    /**
+     * Perform a stop, but wait until the daemon is idle to cut any open connections.
+     *
+     * The daemon will be removed from the registry upon calling this regardless of whether it is busy or not.
+     * If it is idle, this method will block until the daemon fully stops.
+     *
+     * If the daemon is busy, this method will return after removing the daemon from the registry but before the
+     * daemon is fully stopped. In this case, the daemon will stop as soon as {@link #onFinishCommand()} is called.
+     */
+    public void stopAsSoonAsIdle() {
+        lock.lock();
+        try {
+            LOGGER.debug("Stop as soon as idle requested. The daemon is busy: " + isBusy());
+            if (isBusy()) {
+                onStopRequested.run();
+                stoppingOrStopped = true;
+            } else {
+               stop();
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Forcibly stops the daemon, even if it is busy.
+     *
+     * If the daemon is busy and the client is waiting for a response, it may receive “null” from the daemon
+     * as the connection may be closed by this method before the result is sent back.
+     *
+     * @see #stopAsSoonAsIdle()
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            LOGGER.debug("Stop requested. The daemon is running a build: " + isBusy());
+            if (!isStoppingOrStopped()) {
+                onStopRequested.run();
+            }
+            stoppingOrStopped = true;
+            onStop.run();
+            stopped = true;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    Runnable asyncStop = new Runnable() {
+        public void run() {
+            new DefaultExecutorFactory().create("Daemon Async Stop Request").execute(new Runnable() {
+                public void run() {
+                    stop();
+                }
+            });
+        }
+    };
+
+    /**
+     * @return returns false if the daemon was already requested to stop
+     */
+    public boolean requestStop() {
+        lock.lock();
+        try {
+            if (stoppingOrStopped) {
+                return false;
+            }
+            stoppingOrStopped = true;
+            onStopRequested.run(); //blocking
+            asyncStop.run(); //not blocking
+            return true;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called when the execution of a command begins.
+     * <p>
+     * If the daemon is busy (i.e. already executing a command), this method will return the existing
+     * execution which the caller should be prepared for without considering the given execution to be in progress.
+     * If the daemon is idle the return value will be {@code null} and the given execution will be considered in progress.
+     */
+    public DaemonCommandExecution onStartCommand(DaemonCommandExecution execution) {
+        lock.lock();
+        try {
+            if (currentCommandExecution != null) { // daemon is busy
+                /*
+                    This is not particularly abnormal as daemon can become busy between a particular client connecting to it and then
+                    sending a command. The UpdateDaemonStateAndHandleBusyDaemon command action will send back a DaemonBusy result
+                    to the client which will then just try another daemon, making this a non-error condition.
+                */
+                LOGGER.debug("onStartCommand({}) called while currentCommandExecution = {}", execution, currentCommandExecution);
+                return currentCommandExecution;
+            } else {
+                LOGGER.debug("onStartCommand({}) called after {} mins of idle", execution, getIdleMinutes());
+                currentCommandExecution = execution;
+                updateActivityTimestamp();
+                onStartCommand.run();
+                condition.signalAll();
+                return null;
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Called when the execution of a command is complete (or at least the daemon is available for new commands).
+     * <p>
+     * If the daemon is currently idle, this method will return {@code null}. If it is busy, it will return what was the
+     * current execution which will considered to be complete (putting the daemon back in idle state).
+     * <p>
+     * If {@link #stopAsSoonAsIdle()} was previously called, this method will block while the daemon {@link #stop() stops}
+     */
+    public DaemonCommandExecution onFinishCommand() {
+        lock.lock();
+        try {
+            DaemonCommandExecution execution = currentCommandExecution;
+            if (execution == null) {
+                LOGGER.warn("onFinishCommand() called while currentCommandExecution is null");
+            } else {
+                LOGGER.debug("onFinishCommand() called while execution = {}", execution);
+                currentCommandExecution = null;
+                updateActivityTimestamp();
+                if (isStoppingOrStopped()) {
+                    stop();
+                } else {
+                    onFinishCommand.run();
+                    condition.signalAll();
+                }
+            }
+
+            return execution;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void updateActivityTimestamp() {
+        long now = System.currentTimeMillis();
+        LOGGER.debug("updating lastActivityAt to {}", now);
+        lastActivityAt = now;
+    }
+
+    /**
+     * The current command execution, or {@code null} if the daemon is idle.
+     */
+    public DaemonCommandExecution getCurrentCommandExecution() {
+        lock.lock();
+        try {
+            return currentCommandExecution;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Has the daemon started accepting connections.
+     */
+    public boolean isStarted() {
+        return lastActivityAt != -1;
+    }
+
+    public double getIdleMinutes() {
+        lock.lock();
+        try {
+            if (isStarted()) {
+                return (System.currentTimeMillis() - lastActivityAt) / 1000 / 60;
+            } else {
+                return -1;
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public boolean hasBeenIdleFor(int milliseconds) {
+        if (!isStarted()) {
+            return false;
+        } else {
+            return lastActivityAt < (System.currentTimeMillis() - milliseconds);
+        }
+    }
+
+    public boolean isStopped() {
+        return stopped;
+    }
+
+    public boolean isStoppingOrStopped() {
+        return stoppingOrStopped;
+    }
+
+    public boolean isIdle() {
+        return isRunning() && currentCommandExecution == null;
+    }
+
+    public boolean isBusy() {
+        return isRunning() && !isIdle();
+    }
+    
+    public boolean isRunning() {
+        return isStarted() && !isStopped();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStoppedException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStoppedException.java
new file mode 100644
index 0000000..1853c28
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStoppedException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.daemon.server;
+
+import org.gradle.launcher.daemon.server.exec.DaemonCommandExecution;
+
+public class DaemonStoppedException extends Exception {
+
+    private final DaemonCommandExecution executionWhenStopped;
+
+    public DaemonStoppedException(DaemonCommandExecution executionWhenStopped) {
+        super(toMessage(executionWhenStopped));
+        this.executionWhenStopped = executionWhenStopped;
+    }
+
+    private static String toMessage(DaemonCommandExecution executionWhenStopped) {
+        if (executionWhenStopped == null) {
+            return "daemon explicitly stopped while idle";
+        } else {
+            return String.format("daemon explicitly stopped while busy, execution when stopped = %s", executionWhenStopped);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
new file mode 100644
index 0000000..04bc8fd
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 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.daemon.server;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
+import org.gradle.messaging.remote.internal.SynchronizedDispatch;
+import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
+import org.gradle.messaging.remote.internal.inet.TcpIncomingConnector;
+import org.gradle.util.UUIDGenerator;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Opens a TCP connection for clients to connect to to communicate with a daemon.
+ */
+public class DaemonTcpServerConnector implements DaemonServerConnector {
+
+    private static final Logger LOGGER = Logging.getLogger(DaemonServerConnector.class);
+
+    final private TcpIncomingConnector<Object> incomingConnector;
+
+    private boolean started;
+    private boolean stopped;
+    private final Lock lifecycleLock = new ReentrantLock();
+
+    public DaemonTcpServerConnector() {
+        this.incomingConnector = new TcpIncomingConnector<Object>(
+                new DefaultExecutorFactory(),
+                new DefaultMessageSerializer<Object>(getClass().getClassLoader()),
+                new InetAddressFactory(),
+                new UUIDGenerator()
+        );
+    }
+
+    public Address start(final IncomingConnectionHandler handler) {
+        lifecycleLock.lock();
+        try {
+            if (stopped) {
+                throw new IllegalStateException("server connector cannot be started as it is either stopping or has been stopped");
+            }
+            if (started) {
+                throw new IllegalStateException("server connector cannot be started as it has already been started");
+            }
+
+            // Hold the lock until we actually start accepting connections for the case when stop is called from another
+            // thread while we are in the middle here.
+
+            LOGGER.lifecycle(DaemonMessages.PROCESS_STARTED);
+
+            Action<ConnectEvent<Connection<Object>>> connectEvent = new Action<ConnectEvent<Connection<Object>>>() {
+                public void execute(ConnectEvent<Connection<Object>> connectionConnectEvent) {
+                    handler.handle(new SynchronizedDispatch<Object>(connectionConnectEvent.getConnection()));
+                }
+            };
+
+            Address address = incomingConnector.accept(connectEvent, false);
+            started = true;
+            return address;
+        } finally {
+            lifecycleLock.unlock();
+        }
+    }
+
+    public void stop() {
+        lifecycleLock.lock();
+        try { // can't imagine what would go wrong here, but try/finally just in case
+            stopped = true;
+        } finally {
+            lifecycleLock.unlock();
+        }
+
+        incomingConnector.stop();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DomainRegistryUpdater.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DomainRegistryUpdater.java
new file mode 100644
index 0000000..d10190a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DomainRegistryUpdater.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.daemon.server;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.registry.DaemonRegistry;
+import org.gradle.messaging.remote.Address;
+
+/**
+* @author: Szczepan Faber, created at: 9/12/11
+*/
+class DomainRegistryUpdater {
+
+    private static final Logger LOGGER = Logging.getLogger(DomainRegistryUpdater.class);
+
+    private final DaemonRegistry daemonRegistry;
+    private final DaemonContext daemonContext;
+    private final String password;
+    private final Address connectorAddress;
+
+    public DomainRegistryUpdater(DaemonRegistry daemonRegistry, DaemonContext daemonContext, String password, Address connectorAddress) {
+        this.daemonRegistry = daemonRegistry;
+        this.daemonContext = daemonContext;
+        this.password = password;
+        this.connectorAddress = connectorAddress;
+    }
+
+    public void onStartActivity() {
+        LOGGER.info("Marking the daemon as busy, address: " + connectorAddress);
+        try {
+            daemonRegistry.markBusy(connectorAddress);
+        } catch (DaemonRegistry.EmptyRegistryException e) {
+            LOGGER.warn("Cannot mark daemon as busy because the registry is empty.");
+        }
+    }
+
+    public void onCompleteActivity() {
+        LOGGER.info("Marking the daemon as idle, address: " + connectorAddress);
+        try {
+            daemonRegistry.markIdle(connectorAddress);
+        } catch (DaemonRegistry.EmptyRegistryException e) {
+            LOGGER.warn("Cannot mark daemon as idle because the registry is empty.");
+        }
+    }
+
+    public void onStart() {
+        LOGGER.info("Advertising the daemon address to the clients: {}", connectorAddress);
+        LOGGER.debug("Advertised daemon context: {}", daemonContext);
+        daemonRegistry.store(connectorAddress, daemonContext, password);
+        daemonRegistry.markBusy(connectorAddress);
+    }
+
+    public void onStop() {
+        LOGGER.debug("Removing our presence to clients, eg. removing this address from the registry: " + connectorAddress);
+        try {
+            daemonRegistry.remove(connectorAddress);
+        } catch (DaemonRegistry.EmptyRegistryException e) {
+            LOGGER.warn("Cannot remove daemon from the registry because the registry is empty.");
+        }
+        LOGGER.debug("Address removed from registry.");
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/IncomingConnectionHandler.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/IncomingConnectionHandler.java
new file mode 100644
index 0000000..80b2659
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/IncomingConnectionHandler.java
@@ -0,0 +1,22 @@
+/*
+ * 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.daemon.server;
+
+import org.gradle.messaging.remote.internal.Connection;
+
+public interface IncomingConnectionHandler {
+    void handle(Connection<Object> connection);
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/BuildCommandOnly.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/BuildCommandOnly.java
new file mode 100644
index 0000000..3e9518e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/BuildCommandOnly.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.launcher.daemon.protocol.Command;
+
+/**
+ * Superclass template for actions that only work for Build.
+ * 
+ * If an action of this type receives a command that is not Build it will throw an exception.
+ */
+abstract public class BuildCommandOnly implements DaemonCommandAction {
+
+    public void execute(DaemonCommandExecution execution) {
+        Command command = execution.getCommand();
+        if (!(command instanceof Build)) {
+            throw new IllegalStateException(String.format("{} command action received a command that isn't Build (command is {}), this shouldn't happen", this.getClass(), command.getClass()));
+        }
+
+        doBuild(execution, (Build)command);
+    }
+
+    /**
+     * Note that the build param is the same object as execution.getCommand(), just “pre casted”.
+     */
+    protected void doBuild(DaemonCommandExecution execution, Build build) {}
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/CatchAndForwardDaemonFailure.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/CatchAndForwardDaemonFailure.java
new file mode 100644
index 0000000..897fbeb
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/CatchAndForwardDaemonFailure.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.protocol.DaemonFailure;
+
+/**
+ * Wraps the rest of the command execution in a try catch in order to forward any internal
+ * errors back to the client as DaemonFailure results.
+ */
+public class CatchAndForwardDaemonFailure implements DaemonCommandAction {
+
+    private static final Logger LOGGER = Logging.getLogger(CatchAndForwardDaemonFailure.class);
+
+    public void execute(DaemonCommandExecution execution) {
+        try {
+            execution.proceed();
+        } catch (Throwable e) {
+            LOGGER.error(String.format("Daemon failure during execution of %s - ", execution.getCommand()), e);
+            execution.getConnection().dispatch(new DaemonFailure(e));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandAction.java
new file mode 100644
index 0000000..a023e98
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandAction.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+/**
+ * An action that operations as part of a command execution.
+ * <p>
+ * Implementations must be multiple use and threadsafe.
+ */
+public interface DaemonCommandAction {
+
+    /**
+     * Executes this action.
+     * <p>
+     * The execution object is a kind of continuation and also carries the “result” of the action.
+     * For example, if an exception arises as part of actioning the command, the exception should be
+     * set on the execution object and not thrown. The implication of this is that any exceptions
+     * thrown by DaemonCommandAction implementations are programming errors in the implementation
+     * and not something like a build failure if the daemon command is to run a build.
+     */
+    void execute(DaemonCommandExecution execution);
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java
new file mode 100644
index 0000000..a15f558
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.launcher.daemon.protocol.Command;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
+import org.gradle.launcher.daemon.context.DaemonContext;
+
+/**
+ * An object capable of responding to commands sent to a daemon.
+ * <p>
+ * Daemons use implementations of this interface to do the heavy lifting of
+ * actual performing commands.
+ */
+public interface DaemonCommandExecuter {
+
+    /**
+     * Handle the given command, and communicate as necessary with the client over the given connection.
+     * <p>
+     * If an error occurs during the action of the command that is to be reasonably expected 
+     * (e.g. a failure in actually running the build for a Build command), the exception should be
+     * reported to the client and <b>NOT</b> thrown from this method.
+     * <p>
+     * The {@code command} param may be {@code null}, which means the client disconnected before sending a command.
+     */
+    void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateCoordinator daemonStateCoordinator);
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
new file mode 100644
index 0000000..596e21e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.protocol.Command;
+import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
+import org.gradle.messaging.remote.internal.DisconnectAwareConnection;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A continuation style object used to model the execution of a command.
+ * <p>
+ * Facilitates processing “chains”, making it easier to break up processing logic into discrete {@link DaemonCommandAction actions}.
+ * <p>
+ * The given actions will be executed in the order given to the constructor, and should use the {@link #proceed()} method to allow
+ * the next action to run. If an action does not call {@code proceed()}, it will be the last action that executes.
+ */
+public class DaemonCommandExecution {
+
+    final private DisconnectAwareConnection<Object> connection;
+    final private Command command;
+    final private DaemonContext daemonContext;
+    final private DaemonStateCoordinator daemonStateCoordinator;
+    final private List<DaemonCommandAction> actions;
+
+    private Throwable exception;
+    private Object result;
+    private final List<Runnable> finalizers = new LinkedList<Runnable>();
+
+    public DaemonCommandExecution(DisconnectAwareConnection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateCoordinator daemonStateCoordinator, List<DaemonCommandAction> actions) {
+        this.connection = connection;
+        this.command = command;
+        this.daemonContext = daemonContext;
+        this.daemonStateCoordinator = daemonStateCoordinator;
+
+        this.actions = new LinkedList<DaemonCommandAction>(actions);
+    }
+
+    public DisconnectAwareConnection<Object> getConnection() {
+        return connection;
+    }
+
+    /**
+     * The command to execute.
+     * <p>
+     * If the client disconnects before sending a command, this <b>will</b> be {@code null}.
+     */
+    public Command getCommand() {
+        return command;
+    }
+
+    public DaemonContext getDaemonContext() {
+        return daemonContext;
+    }
+
+    public DaemonStateCoordinator getDaemonStateCoordinator() {
+        return daemonStateCoordinator;
+    }
+
+    /**
+     * Sets what is to be considered the result of executing the command.
+     * <p>
+     * This may be called multiple times to do things like wrap the result in another type.
+     */
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    /**
+     * The currently nominated result for the execution.
+     * <p>
+     * If {@link #getException()} returns non null, the actual “result” of executing the command should be considered
+     * to be that exception and not what is returned by this method.
+     * <p>
+     * May be null if no action has set the result yet.
+     */
+    public Object getResult() {
+        return this.result;
+    }
+
+    /**
+     * If an exception happens in actioning the command that is to be expected (e.g. a build failure or error in the build)
+     */
+    public void setException(Throwable exception) {
+        this.exception = exception;
+    }
+
+    /**
+     * The currently nominated error that occurred during executing the commmand.
+     */
+    public Throwable getException() {
+        return this.exception;
+    }
+
+    /**
+     * Continues (or starts) execution.
+     * <p>
+     * Each action should call this method if it determines that execution should continue.
+     *
+     * @return true if execution did occur, false if this execution has already occurred.
+     */
+    public boolean proceed() {
+        if (actions.isEmpty()) {
+            return false;
+        } else {
+            actions.remove(0).execute(this);
+            return true;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("DaemonCommandExecution[command = %s, connection = %s]", command, connection);
+    }
+
+    public void addFinalizer(Runnable runnable) {
+        assert runnable != null;
+        finalizers.add(runnable);
+    }
+
+    public void executeFinalizers() {
+        for (Runnable finalizer : finalizers) {
+            finalizer.run();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java
new file mode 100644
index 0000000..5c7e7f2
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.protocol.Command;
+import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.DisconnectAwareConnectionDecorator;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The default implementation of how to execute commands that the daemon receives.
+ */
+public class DefaultDaemonCommandExecuter implements DaemonCommandExecuter {
+
+    private final ExecutorFactory executorFactory;
+    private final LoggingManagerInternal loggingManager;
+    private final GradleLauncherFactory launcherFactory;
+    private final ProcessEnvironment processEnvironment;
+    private final File daemonLog;
+
+    public DefaultDaemonCommandExecuter(GradleLauncherFactory launcherFactory, ExecutorFactory executorFactory,
+                                        ProcessEnvironment processEnvironment, LoggingManagerInternal loggingManager, File daemonLog) {
+        this.executorFactory = executorFactory;
+        this.processEnvironment = processEnvironment;
+        this.daemonLog = daemonLog;
+        this.loggingManager = loggingManager;
+        this.launcherFactory = launcherFactory;
+    }
+
+    public void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateCoordinator daemonStateCoordinator) {
+        new DaemonCommandExecution(
+            new DisconnectAwareConnectionDecorator<Object>(connection, executorFactory.create("DefaultDaemonCommandExecuter > DisconnectAwareConnectionDecorator")),
+            command,
+            daemonContext,
+            daemonStateCoordinator,
+            createActions(daemonContext)
+        ).proceed();
+    }
+
+    protected List<DaemonCommandAction> createActions(DaemonContext daemonContext) {
+        DaemonDiagnostics daemonDiagnostics = new DaemonDiagnostics(daemonLog, daemonContext.getPid());
+        return new LinkedList<DaemonCommandAction>(Arrays.asList(
+            new StopConnectionAfterExecution(),
+            new HandleClientDisconnectBeforeSendingCommand(),
+            new CatchAndForwardDaemonFailure(),
+            new HandleStop(),
+            new StartBuildOrRespondWithBusy(daemonDiagnostics),
+            new EstablishBuildEnvironment(processEnvironment),
+            new LogToClient(loggingManager, daemonDiagnostics), // from this point down, logging is sent back to the client
+            new ForwardClientInput(executorFactory),
+            new ReturnResult(),
+            new StartStopIfBuildAndStop(),
+            new ResetDeprecationLogger(),
+            new WatchForDisconnection(),
+            new ExecuteBuild(launcherFactory)
+        ));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java
new file mode 100644
index 0000000..9e35888
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Aims to make the local environment the same as the client's environment.
+ */
+public class EstablishBuildEnvironment extends BuildCommandOnly {
+    private final ProcessEnvironment processEnvironment;
+    private final static Logger LOGGER = Logging.getLogger(EstablishBuildEnvironment.class);
+
+    public EstablishBuildEnvironment(ProcessEnvironment processEnvironment) {
+        this.processEnvironment = processEnvironment;
+    }
+
+    protected void doBuild(DaemonCommandExecution execution, Build build) {
+        Properties originalSystemProperties = new Properties();
+        originalSystemProperties.putAll(System.getProperties());
+        File currentDir = GFileUtils.canonicalise(new File("."));
+
+        Properties clientSystemProperties = new Properties();
+        clientSystemProperties.putAll(build.getParameters().getSystemProperties());
+
+        //Let's ignore client's java.home
+        //We want to honour the java.home configured when the daemon process was started
+        //It does not make sense to update this property per job anyway as we have a daemon per java home
+        clientSystemProperties.put("java.home", originalSystemProperties.get("java.home"));
+
+        System.setProperties(clientSystemProperties);
+
+        Map<String, String> originalEnv = System.getenv();
+        LOGGER.debug("Configuring env variables: {}", build.getParameters().getEnvVariables());
+        processEnvironment.maybeSetEnvironment(build.getParameters().getEnvVariables());
+
+        processEnvironment.maybeSetProcessDir(build.getParameters().getCurrentDir());
+
+        try {
+            execution.proceed();
+        } finally {
+            System.setProperties(originalSystemProperties);
+            processEnvironment.maybeSetEnvironment(originalEnv);
+            processEnvironment.maybeSetProcessDir(currentDir);
+        }
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
new file mode 100644
index 0000000..6cf77d3
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.launcher.exec.ReportedException;
+
+/**
+ * Actually executes the build.
+ * 
+ * Typically the last action in the pipeline.
+ */
+public class ExecuteBuild extends BuildCommandOnly {
+
+    private static final Logger LOGGER = Logging.getLogger(ExecuteBuild.class);
+    
+    final private GradleLauncherFactory launcherFactory;
+
+    public ExecuteBuild(GradleLauncherFactory launcherFactory) {
+        this.launcherFactory = launcherFactory;
+    }
+
+    protected void doBuild(DaemonCommandExecution execution, Build build) {
+        LOGGER.info("Executing build with daemon context: {}", execution.getDaemonContext());
+        
+        try {
+            execution.setResult(build.run(launcherFactory));
+        } catch (GradleException e) {
+            /*
+                We have to wrap in a ReportedException so the other side doesn't re-log this exception, because it's already
+                been logged by the GradleLauncher infrastructure, and that logging has been shipped over to the other side.
+                
+                This doesn't seem right. Perhaps we should assume on the client side that all “build failures” (opposed to daemon infrastructure failures)
+                have already been logged and do away with this wrapper.
+            */
+            execution.setException(new ReportedException(e));
+        } finally {
+            LOGGER.debug(DaemonMessages.FINISHED_BUILD);
+        }
+
+        execution.proceed(); // ExecuteBuild should be the last action, but in case we want to decorate the result in the future
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java
new file mode 100644
index 0000000..6572d15
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.UncheckedException;
+import org.gradle.launcher.daemon.protocol.CloseInput;
+import org.gradle.launcher.daemon.protocol.ForwardInput;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.AsyncReceive;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.util.StdinSwapper;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Listens for ForwardInput commands during the execution and sends that to a piped input stream
+ * that we install.
+ */
+public class ForwardClientInput implements DaemonCommandAction {
+    private static final Logger LOGGER = Logging.getLogger(ForwardClientInput.class);
+    private final ExecutorFactory executorFactory;
+
+    public ForwardClientInput(ExecutorFactory executorFactory) {
+        this.executorFactory = executorFactory;
+    }
+
+    public void execute(final DaemonCommandExecution execution) {
+        final PipedOutputStream inputSource = new PipedOutputStream();
+        final PipedInputStream replacementStdin;
+        try {
+            replacementStdin = new PipedInputStream(inputSource);
+        } catch (IOException e) {
+            throw new GradleException("unable to wire client stdin to daemon stdin", e);
+        }
+
+        final CountDownLatch inputOrConnectionClosedLatch = new CountDownLatch(1);
+        final Runnable countDownInputOrConnectionClosedLatch = new Runnable() {
+            public void run() {
+                inputOrConnectionClosedLatch.countDown();
+            }
+        };
+
+        Dispatch<Object> dispatcher = new Dispatch<Object>() {
+            public void dispatch(Object command) {
+                if (command instanceof ForwardInput) {
+                    try {
+                        ForwardInput forwardedInput = (ForwardInput)command;
+                        LOGGER.debug("Putting forwarded input '{}' on daemon's stdin.", new String(forwardedInput.getBytes()).replace("\n", "\\n"));
+                        inputSource.write(forwardedInput.getBytes());
+
+                    } catch (Exception e) {
+                        LOGGER.warn("Received exception trying to forward client input.", e);
+                    }
+                } else if (command instanceof CloseInput) {
+                    try {
+                        LOGGER.info("Closing daemons standard input as requested by received command: {} ...", command);
+                        inputSource.close();
+                    } catch (Throwable e) {
+                        LOGGER.warn("Problem closing output stream connected to replacement stdin", e);
+                    } finally {
+                        LOGGER.info("The daemon will no longer process any standard input.");
+                        countDownInputOrConnectionClosedLatch.run();
+                    }
+                } else {
+                    LOGGER.warn("While listening for IOCommands, received unexpected command: {}.", command);
+                }
+            }
+        };
+
+        StoppableExecutor inputReceiverExecuter = executorFactory.create("daemon client input forwarder");
+        final AsyncReceive<Object> inputReceiver = new AsyncReceive<Object>(inputReceiverExecuter, dispatcher, countDownInputOrConnectionClosedLatch);
+        inputReceiver.receiveFrom(execution.getConnection());
+
+        execution.addFinalizer(new Runnable() {
+            public void run() {
+                // means we are going to sit here until the client disconnects, which we are expecting it to
+                // very soon because we are assuming we've just sent back the build result. We do this here
+                // in case the client tries to send input in between us sending back the result and it closing the connection.
+                try {
+                    LOGGER.debug("Waiting until the client disconnects so that we may no longer consume input...");
+                    inputOrConnectionClosedLatch.await();
+                } catch (InterruptedException e) {
+                    LOGGER.debug("Interrupted while waiting for client to disconnect.");
+                    throw UncheckedException.throwAsUncheckedException(e);
+                } finally {
+                    inputReceiver.stop();
+                    LOGGER.debug("The input receiver has been stopped.");
+                }
+            }
+        });
+
+        try {
+            new StdinSwapper().swap(replacementStdin, new Callable<Void>() {
+                public Void call() {
+                    execution.proceed();
+                    return null;
+                }
+            });
+            replacementStdin.close();
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleClientDisconnectBeforeSendingCommand.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleClientDisconnectBeforeSendingCommand.java
new file mode 100644
index 0000000..c35c70c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleClientDisconnectBeforeSendingCommand.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+public class HandleClientDisconnectBeforeSendingCommand implements DaemonCommandAction {
+    public void execute(DaemonCommandExecution execution) {
+        if (execution.getCommand() != null) {
+            execution.proceed();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java
new file mode 100644
index 0000000..ea6596a
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.launcher.daemon.protocol.Stop;
+
+/**
+ * If the command is a Stop, asks the daemon to stop asynchronously and does not proceed with execution.
+ */
+public class HandleStop implements DaemonCommandAction {
+    public void execute(DaemonCommandExecution execution) {
+        if (execution.getCommand() instanceof Stop) {
+            /*
+                When the daemon was started through the DaemonMain entry point, this will cause the entire
+                JVM to exit with code 1 (which is what we want) because the call to awaitIdleTimeout() in 
+                DaemonMain#doAction will throw a DaemonStoppedException. Note that at this point we will also 
+                immediately tear down the client connection and remove the daemon from the registry.
+            */
+            execution.getDaemonStateCoordinator().requestStop();
+        } else {
+            execution.proceed();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
new file mode 100644
index 0000000..6dd39ea
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.logging.internal.OutputEventListener;
+
+class LogToClient extends BuildCommandOnly {
+
+    private static final Logger LOGGER = Logging.getLogger(LogToClient.class);
+
+    private final LoggingManagerInternal loggingManager;
+    private final DaemonDiagnostics diagnostics;
+
+    public LogToClient(LoggingManagerInternal loggingManager, DaemonDiagnostics diagnostics) {
+        this.loggingManager = loggingManager;
+        this.diagnostics = diagnostics;
+    }
+
+    protected void doBuild(final DaemonCommandExecution execution, Build build) {
+        final LogLevel buildLogLevel = build.getStartParameter().getLogLevel();
+        OutputEventListener listener = new OutputEventListener() {
+            public void onOutput(OutputEvent event) {
+                try {
+                    if (event.getLogLevel().compareTo(buildLogLevel) >= 0) {
+                        execution.getConnection().dispatch(event);
+                    }
+                } catch (Exception e) {
+                    //Ignore. It means the client has disconnected so no point sending him any log output.
+                    //we should be checking if client still listens elsewhere anyway.
+                }
+            }
+        };
+
+        LOGGER.info(DaemonMessages.ABOUT_TO_START_RELAYING_LOGS);
+        loggingManager.addOutputEventListener(listener);
+        LOGGER.info(DaemonMessages.STARTED_RELAYING_LOGS + diagnostics.getPid() + "). The daemon log file: " + diagnostics.getDaemonLog());
+
+        try {
+            execution.proceed();
+        } finally {
+            loggingManager.removeOutputEventListener(listener);
+        }
+    } 
+}
+
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ResetDeprecationLogger.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ResetDeprecationLogger.java
new file mode 100644
index 0000000..deba7a6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ResetDeprecationLogger.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.util.DeprecationLogger;
+
+public class ResetDeprecationLogger implements DaemonCommandAction {
+    public void execute(DaemonCommandExecution execution) {
+        DeprecationLogger.reset();
+        execution.proceed();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ReturnResult.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ReturnResult.java
new file mode 100644
index 0000000..afd2ca4
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ReturnResult.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.protocol.CommandFailure;
+import org.gradle.launcher.daemon.protocol.Result;
+import org.gradle.launcher.daemon.protocol.Success;
+
+/**
+ * Handles sending the result of the execution back to the client.
+ *
+ * Likely to be the first thing in the pipeline.
+ */
+public class ReturnResult implements DaemonCommandAction {
+
+    private static final Logger LOGGER = Logging.getLogger(ReturnResult.class);
+
+    public void execute(DaemonCommandExecution execution) {
+        execution.proceed();
+
+        Result result;
+        Throwable commandException = execution.getException();
+        if (commandException != null) {
+            result = new CommandFailure(commandException);
+        } else {
+            result = new Success(execution.getResult());
+        }
+
+        LOGGER.debug("Daemon is dispatching the build result: {}", result);
+        execution.getConnection().dispatch(result);
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java
new file mode 100644
index 0000000..4edb5fe
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.launcher.daemon.protocol.BuildStarted;
+import org.gradle.launcher.daemon.protocol.DaemonBusy;
+import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
+
+/**
+ * Updates the daemon idle/busy status, sending a DaemonBusy result back to the client if the daemon is busy.
+ */
+public class StartBuildOrRespondWithBusy extends BuildCommandOnly {
+    
+    private static final Logger LOGGER = Logging.getLogger(StartBuildOrRespondWithBusy.class);
+    private final DaemonDiagnostics diagnostics;
+
+    public StartBuildOrRespondWithBusy(DaemonDiagnostics diagnostics) {
+        this.diagnostics = diagnostics;
+    }
+
+    protected void doBuild(DaemonCommandExecution execution, Build build) {
+        DaemonStateCoordinator stateCoordinator = execution.getDaemonStateCoordinator();
+
+        DaemonCommandExecution existingExecution = stateCoordinator.onStartCommand(execution);
+        if (existingExecution != null) {
+            LOGGER.info("Daemon will not handle the request: {} because is busy executing: {}. Dispatching 'Busy' response...", build, existingExecution);
+            execution.getConnection().dispatch(new DaemonBusy(existingExecution.getCommand()));
+        } else {
+            try {
+                LOGGER.info("Daemon is about to start building: " + build + ". Dispatching build started information...");
+                execution.getConnection().dispatch(new BuildStarted(diagnostics));
+                execution.proceed();
+            } finally {
+                stateCoordinator.onFinishCommand();
+            }
+        }
+    }
+    
+    
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java
new file mode 100644
index 0000000..bb52ac1
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.protocol.BuildAndStop;
+
+public class StartStopIfBuildAndStop implements DaemonCommandAction {
+
+    private static final Logger LOGGER = Logging.getLogger(StopConnectionAfterExecution.class);
+    
+    public void execute(DaemonCommandExecution execution) {
+        execution.proceed();
+
+        if (execution.getCommand() instanceof BuildAndStop) {
+            LOGGER.debug("Requesting daemon stop after processing {}", execution.getCommand());
+            
+            // This will cause the daemon to be removed from the registry, but no close the connection
+            // to the client until we've sent back the result.
+            execution.getDaemonStateCoordinator().stopAsSoonAsIdle();
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopConnectionAfterExecution.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopConnectionAfterExecution.java
new file mode 100644
index 0000000..06f31f5
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StopConnectionAfterExecution.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+public class StopConnectionAfterExecution implements DaemonCommandAction {
+    
+    private static final Logger LOGGER = Logging.getLogger(StopConnectionAfterExecution.class);
+
+    public void execute(DaemonCommandExecution execution) {
+        try {
+            execution.proceed();
+            //TODO SF this needs to be refactored down the road with consideration around exception handling
+            //for now, we'll just execute all finalizers here
+            LOGGER.debug("Execution completed. Running finalizers...");
+            execution.executeFinalizers();
+            LOGGER.debug("Finalizers execution complete.");
+        } finally {
+            LOGGER.debug("Stopping connection: {}", execution.getConnection());
+            execution.getConnection().stop();
+            LOGGER.debug("Connection stopped.");
+        }
+    }
+
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java
new file mode 100644
index 0000000..c5ed48c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.daemon.server.exec;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.messaging.remote.internal.DisconnectAwareConnection;
+
+public class WatchForDisconnection implements DaemonCommandAction {
+
+    private static final Logger LOGGER = Logging.getLogger(WatchForDisconnection.class);
+
+    public void execute(final DaemonCommandExecution execution) {
+        DisconnectAwareConnection connection = execution.getConnection();
+
+        // Watch for the client disconnecting before we call stop()
+        connection.onDisconnect(new Runnable() {
+            public void run() {
+                LOGGER.warn("client disconnection detected, stopping the daemon");
+                
+                /*
+                    When the daemon was started through the DaemonMain entry point, this will cause the entire
+                    JVM to exit with code 1 (which is what we want) because the call to awaitIdleTimeout() in 
+                    DaemonMain#doAction will throw a DaemonStoppedException. Note that at this point we will also 
+                    immediately remove the daemon from the registry.
+                */
+                execution.getDaemonStateCoordinator().requestStop();
+            }
+        });
+
+        try {
+            execution.proceed();
+        } finally {
+            // TODO - Do we need to remove the disconnect handler here?
+            // I think we should because if the client disconnects after we run the build we may as well stay up
+            connection.onDisconnect(null);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
new file mode 100644
index 0000000..9b82e32
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.exec;
+
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.BuildRequestMetaData;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Map;
+
+public interface BuildActionParameters extends Serializable {
+    BuildRequestMetaData getBuildRequestMetaData();
+
+    BuildClientMetaData getClientMetaData();
+
+    Map<String, String> getSystemProperties();
+
+    Map<String, String> getEnvVariables();
+
+    File getCurrentDir();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
new file mode 100644
index 0000000..de06dea
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.exec;
+
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.initialization.BuildRequestMetaData;
+import org.gradle.initialization.DefaultBuildRequestMetaData;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultBuildActionParameters implements BuildActionParameters, Serializable {
+    private final BuildClientMetaData clientMetaData;
+    private final long startTime;
+    private final File currentDir;
+    private final Map<String, String> systemProperties;
+    private final Map<String, String> envVariables;
+
+    public DefaultBuildActionParameters(BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables, File currentDir) {
+        this.clientMetaData = clientMetaData;
+        this.startTime = startTime;
+        this.currentDir = currentDir;
+        assert systemProperties != null;
+        assert envVariables != null;
+        this.systemProperties = new HashMap<String, String>();
+        GUtil.addToMap(this.systemProperties, systemProperties);
+        this.envVariables = new HashMap<String, String>(envVariables);
+    }
+
+    public BuildRequestMetaData getBuildRequestMetaData() {
+        return new DefaultBuildRequestMetaData(clientMetaData, startTime);
+    }
+
+    public BuildClientMetaData getClientMetaData() {
+        return clientMetaData;
+    }
+
+    public Map<String, String> getSystemProperties() {
+        return systemProperties;
+    }
+
+    public Map<String, String> getEnvVariables() {
+        return envVariables;
+    }
+
+    public File getCurrentDir() {
+        return currentDir;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultBuildActionParameters{"
+                + "clientMetaData=" + clientMetaData
+                + ", startTime=" + startTime
+                + ", currentDir=" + currentDir
+                + ", systemProperties size=" + systemProperties.size()
+                + ", envVariables size=" + envVariables.size()
+                + '}';
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/EntryPoint.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/EntryPoint.java
new file mode 100644
index 0000000..4e7738d
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/EntryPoint.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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.exec;
+
+import org.gradle.BuildExceptionReporter;
+import org.gradle.api.Action;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.logging.LoggingConfiguration;
+import org.gradle.logging.internal.StreamingStyledTextOutputFactory;
+
+/**
+ * An entry point is the point at which execution will never return from.
+ * <p>
+ * It's purpose is to consistently apply our completion logic of forcing the JVM
+ * to exit at a certain point instead of waiting for all threads to die, and to provide
+ * some consistent unhandled exception catching.
+ * <p>
+ * Entry points may be nested, as is the case when a foreground daemon is started.
+ * <p>
+ * The createCompleter() and createErrorHandler() are not really intended to be overridden
+ * by subclasses as they define our entry point behaviour, but they are protected to enable
+ * testing as it's difficult to test something that will call System.exit().
+ */
+abstract public class EntryPoint implements Runnable {
+
+    /**
+     * Unless the createCompleter() method is overridden, the JVM will exit before returning from this method.
+     */
+    public void run() {
+        RecordingExecutionListener listener = new RecordingExecutionListener();
+        try {
+            doAction(listener);
+        } catch (Throwable e) {
+            createErrorHandler().execute(e);
+            listener.onFailure(e);
+        }
+
+        Throwable failure = listener.getFailure();
+        ExecutionCompleter completer = createCompleter();
+        if (failure == null) {
+            completer.complete();
+        } else {
+            completer.completeWithFailure(failure);
+        }
+    }
+
+    protected ExecutionCompleter createCompleter() {
+        return new ProcessCompleter();
+    }
+
+    protected Action<Throwable> createErrorHandler() {
+        return new BuildExceptionReporter(new StreamingStyledTextOutputFactory(System.err), new LoggingConfiguration(), new GradleLauncherMetaData());
+    }
+
+    protected abstract void doAction(ExecutionListener listener);
+
+    private static class RecordingExecutionListener implements ExecutionListener {
+        private Throwable failure;
+
+        public void onFailure(Throwable failure) {
+            this.failure = failure;
+        }
+
+        public Throwable getFailure() {
+            return failure;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExceptionReportingAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExceptionReportingAction.java
new file mode 100644
index 0000000..65fa96f
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExceptionReportingAction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.exec;
+
+import org.gradle.api.Action;
+
+public class ExceptionReportingAction implements Action<ExecutionListener> {
+    private final Action<ExecutionListener> action;
+    private final Action<Throwable> reporter;
+
+    public ExceptionReportingAction(Action<ExecutionListener> action, Action<Throwable> reporter) {
+        this.action = action;
+        this.reporter = reporter;
+    }
+
+    public void execute(ExecutionListener executionListener) {
+        try {
+            action.execute(executionListener);
+        } catch (ReportedException e) {
+            executionListener.onFailure(e.getCause());
+        } catch (Throwable t) {
+            reporter.execute(t);
+            executionListener.onFailure(t);
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionCompleter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionCompleter.java
new file mode 100644
index 0000000..3bb1db1
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionCompleter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2011 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.exec;
+
+public interface ExecutionCompleter {
+    void complete();
+    void completeWithFailure(Throwable t);
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionListener.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionListener.java
new file mode 100644
index 0000000..89f6617
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionListener.java
@@ -0,0 +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.launcher.exec;
+
+/**
+ * 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/launcher/src/main/java/org/gradle/launcher/exec/GradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/GradleLauncherActionExecuter.java
new file mode 100644
index 0000000..99ade21
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/GradleLauncherActionExecuter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.exec;
+
+import org.gradle.initialization.GradleLauncherAction;
+
+public interface GradleLauncherActionExecuter<P> {
+    /**
+     * Executes the given action, and returns the result. The given action may also implement {@link InitializationAware <T>}.
+     *
+     * @param action The action
+     * @param <T> The result type
+     * @return The result.
+     */
+    <T> T execute(GradleLauncherAction<T> action, P actionParameters);
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InitializationAware.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InitializationAware.java
new file mode 100644
index 0000000..87fdb09
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InitializationAware.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.exec;
+
+import org.gradle.StartParameter;
+
+public interface InitializationAware {
+    StartParameter configureStartParameter();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ProcessCompleter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ProcessCompleter.java
new file mode 100644
index 0000000..9ffb564
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ProcessCompleter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.exec;
+
+public class ProcessCompleter implements ExecutionCompleter {
+    public void complete() {
+        System.exit(0);
+    }
+
+    public void completeWithFailure(Throwable t) {
+        System.exit(1);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ReportedException.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ReportedException.java
new file mode 100644
index 0000000..a1872de
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ReportedException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.exec;
+
+/**
+ * Wraps an exception which has already been logged, and should not be logged again.
+ */
+public class ReportedException extends RuntimeException {
+    public ReportedException(Throwable throwable) {
+        super(throwable);
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Build.java b/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Build.java
deleted file mode 100644
index 7a99eb7..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Build.java
+++ /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.launcher.protocol;
-
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.BuildActionParameters;
-
-public class Build extends Command {
-    private final GradleLauncherAction<?> action;
-    private final BuildActionParameters parameters;
-
-    public Build(GradleLauncherAction<?> action, BuildActionParameters parameters) {
-        super(parameters.getClientMetaData());
-        this.action = action;
-        this.parameters = parameters;
-    }
-
-    public GradleLauncherAction<?> getAction() {
-        return action;
-    }
-
-    public BuildActionParameters getParameters() {
-        return parameters;
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Command.java b/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Command.java
deleted file mode 100644
index 44e702e..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Command.java
+++ /dev/null
@@ -1,37 +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.launcher.protocol;
-
-import org.gradle.initialization.BuildClientMetaData;
-
-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/launcher/src/main/java/org/gradle/launcher/protocol/CommandComplete.java b/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/CommandComplete.java
deleted file mode 100644
index a9ccdab..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/CommandComplete.java
+++ /dev/null
@@ -1,30 +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.launcher.protocol;
-
-import java.io.Serializable;
-
-public class CommandComplete implements Serializable {
-    private final RuntimeException failure;
-
-    public CommandComplete(RuntimeException failure) {
-        this.failure = failure;
-    }
-
-    public RuntimeException getFailure() {
-        return failure;
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Result.java b/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Result.java
deleted file mode 100644
index daf9335..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Result.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2011 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;
-
-public class Result extends CommandComplete {
-    private final Object result;
-
-    public Result(Object result) {
-        super(null);
-        this.result = result;
-    }
-
-    public Object getResult() {
-        return result;
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Stop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Stop.java
deleted file mode 100644
index b0879c2..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/protocol/Stop.java
+++ /dev/null
@@ -1,24 +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.launcher.protocol;
-
-import org.gradle.initialization.BuildClientMetaData;
-
-public class Stop extends Command {
-    public Stop(BuildClientMetaData clientMetaData) {
-        super(clientMetaData);
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
index a922281..00f294b 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
@@ -18,26 +18,42 @@ package org.gradle.tooling.internal.provider;
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.InitializationAware;
+import org.gradle.launcher.exec.InitializationAware;
+import org.gradle.logging.ShowStacktrace;
+import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
 
 import java.io.File;
 import java.io.Serializable;
+import java.util.List;
 
 class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, InitializationAware, Serializable {
-    private final GradleLauncherAction<T> action;
-    private final File projectDirectory;
-    private final File gradleUserHomeDir;
-    private final Boolean searchUpwards;
-
-    ConfiguringBuildAction(File gradleUserHomeDir, File projectDirectory, Boolean searchUpwards, GradleLauncherAction<T> action) {
-        this.gradleUserHomeDir = gradleUserHomeDir;
-        this.projectDirectory = projectDirectory;
-        this.searchUpwards = searchUpwards;
+    private LogLevel buildLogLevel;
+    private List<String> arguments;
+    private List<String> tasks;
+    private GradleLauncherAction<T> action;
+    private File projectDirectory;
+    private File gradleUserHomeDir;
+    private Boolean searchUpwards;
+
+    public ConfiguringBuildAction() {}
+
+    public ConfiguringBuildAction(ProviderOperationParameters parameters, GradleLauncherAction<T> action) {
+        this.gradleUserHomeDir = parameters.getGradleUserHomeDir();
+        this.projectDirectory = parameters.getProjectDir();
+        this.searchUpwards = parameters.isSearchUpwards();
+        this.buildLogLevel = parameters.getBuildLogLevel();
+        this.arguments = parameters.getArguments();
+        this.tasks = parameters.getTasks();
         this.action = action;
     }
 
-    public void configureStartParameter(StartParameter startParameter) {
+    public StartParameter configureStartParameter() {
+        StartParameter startParameter = new StartParameter();
         startParameter.setProjectDir(projectDirectory);
         if (gradleUserHomeDir != null) {
             startParameter.setGradleUserHomeDir(gradleUserHomeDir);
@@ -45,11 +61,33 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
         if (searchUpwards != null) {
             startParameter.setSearchUpwards(searchUpwards);
         }
-        startParameter.setShowStacktrace(StartParameter.ShowStacktrace.ALWAYS);
-        if (action instanceof InitializationAware) {
-            InitializationAware initializationAware = (InitializationAware) action;
-            initializationAware.configureStartParameter(startParameter);
+
+        if (tasks != null) {
+            startParameter.setTaskNames(tasks);
+        }
+
+        if (arguments != null) {
+            DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
+            try {
+                converter.convert(arguments, startParameter);
+            } catch (CommandLineArgumentException e) {
+                throw new InternalUnsupportedBuildArgumentException(
+                    "Problem with provided build arguments: " + arguments + ". "
+                    + "\n" + e.getMessage()
+                    + "\nEither it is not a valid build option or it is not supported in the target Gradle version."
+                    + "\nNot all of the Gradle command line options are supported build arguments."
+                    + "\nExamples of supported build arguments: '--info', '-u', '-p'."
+                    + "\nExamples of unsupported build options: '--daemon', '-?', '-v'."
+                    + "\nPlease find more information in the javadoc for the BuildLauncher class.", e);
+            }
+        }
+
+        if (buildLogLevel != null) {
+            startParameter.setLogLevel(buildLogLevel);
         }
+
+        startParameter.setShowStacktrace(ShowStacktrace.ALWAYS);
+        return startParameter;
     }
 
     public BuildResult run(GradleLauncher launcher) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
index 911d52c..215cb9a 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
@@ -17,19 +17,29 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.*;
+import org.gradle.launcher.daemon.client.DaemonClient;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.launcher.exec.ReportedException;
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
 
-public class DaemonGradleLauncherActionExecuter implements GradleLauncherActionExecuter<BuildOperationParametersVersion1> {
+import java.io.File;
+
+public class DaemonGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
     private final DaemonClient client;
+    private final DaemonParameters parameters;
 
-    public DaemonGradleLauncherActionExecuter(DaemonClient client) {
+    public DaemonGradleLauncherActionExecuter(DaemonClient client, DaemonParameters parameters) {
         this.client = client;
+        this.parameters = parameters;
     }
 
-    public <T> T execute(GradleLauncherAction<T> action, BuildOperationParametersVersion1 actionParameters) {
-        BuildActionParameters parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), actionParameters.getStartTime(), System.getProperties());
+    public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
+        BuildActionParameters parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), actionParameters.getStartTime(),
+                this.parameters.getEffectiveSystemProperties(), System.getenv(), new File(System.getProperty("user.dir")));
         try {
             return client.execute(action, parameters);
         } catch (ReportedException e) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
index 4defcd8..bd6bf05 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
@@ -15,37 +15,47 @@
  */
 package org.gradle.tooling.internal.provider;
 
-import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
-import org.gradle.api.internal.project.ServiceRegistry;
-import org.gradle.configuration.GradleLauncherMetaData;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.api.logging.LogLevel;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.initialization.GradleLauncherFactory;
-import org.gradle.launcher.DaemonClient;
-import org.gradle.launcher.DaemonConnector;
-import org.gradle.launcher.GradleLauncherActionExecuter;
+import org.gradle.internal.Factory;
+import org.gradle.launcher.daemon.client.DaemonClient;
+import org.gradle.launcher.daemon.client.DaemonClientServices;
+import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
-import org.gradle.logging.internal.OutputEventListener;
+import org.gradle.logging.internal.OutputEventRenderer;
+import org.gradle.logging.internal.slf4j.SimpleSlf4jLoggingConfigurer;
+import org.gradle.tooling.internal.build.DefaultBuildEnvironment;
 import org.gradle.tooling.internal.protocol.*;
+import org.gradle.tooling.internal.provider.input.AdaptedOperationParameters;
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
 import org.gradle.util.GUtil;
 import org.gradle.util.GradleVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.util.List;
 
-public class DefaultConnection implements ConnectionVersion4 {
+public class DefaultConnection implements InternalConnection {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnection.class);
-    private final ServiceRegistry loggingServices;
-    private final GradleLauncherFactory gradleLauncherFactory;
+    private final EmbeddedExecuterSupport embeddedExecuterSupport;
+    private final SimpleSlf4jLoggingConfigurer loggingConfigurer = new SimpleSlf4jLoggingConfigurer();
 
     public DefaultConnection() {
-        LOGGER.debug("Using tooling API provider version {}.", GradleVersion.current().getVersion());
-        loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
-        gradleLauncherFactory = new DefaultGradleLauncherFactory(loggingServices);
-        GradleLauncher.injectCustomFactory(gradleLauncherFactory);
+        LOGGER.debug("Provider implementation created.");
+        //embedded use of the tooling api is not supported publicly so we don't care about its thread safety
+        //we can keep still keep this state:
+        embeddedExecuterSupport = new EmbeddedExecuterSupport();
+        LOGGER.debug("Embedded executer support created.");
+    }
+
+    public void configureLogging(boolean verboseLogging) {
+        LogLevel providerLogLevel = verboseLogging? LogLevel.DEBUG : LogLevel.INFO;
+        LOGGER.debug("Configuring logging to level: {}", providerLogLevel);
+        loggingConfigurer.configure(providerLogLevel);
     }
 
     public ConnectionMetaDataVersion1 getMetaData() {
@@ -63,30 +73,84 @@ public class DefaultConnection implements ConnectionVersion4 {
     public void stop() {
     }
 
-    public void executeBuild(final BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) {
-        run(new ExecuteBuildAction(buildParameters.getTasks()), operationParameters);
+    public void executeBuild(final BuildParametersVersion1 buildParameters,
+                             BuildOperationParametersVersion1 operationParameters) {
+        logTargetVersion();
+        AdaptedOperationParameters adaptedParams = new AdaptedOperationParameters(operationParameters, buildParameters);
+        run(new ExecuteBuildAction(), adaptedParams);
+    }
+
+    private void logTargetVersion() {
+        LOGGER.info("Tooling API uses target gradle version:" + " {}.", GradleVersion.current().getVersion());
+    }
+
+    @Deprecated //getTheModel method has much convenient interface, e.g. avoids locking to building only models of a specific type
+    public ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 parameters) {
+        return getTheModel(type, parameters);
     }
 
-    public ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) {
-        GradleLauncherAction<ProjectVersion3> action = new DelegatingBuildModelAction(type);
-        return run(action, operationParameters);
+    public <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 parameters) {
+        logTargetVersion();
+        ProviderOperationParameters adaptedParameters = new AdaptedOperationParameters(parameters);
+        if (type == InternalBuildEnvironment.class) {
+
+            //we don't really need to launch gradle to acquire information needed for BuildEnvironment
+            DaemonParameters daemonParameters = init(adaptedParameters);
+            DefaultBuildEnvironment out = new DefaultBuildEnvironment(
+                GradleVersion.current().getVersion(),
+                daemonParameters.getEffectiveJavaHome(),
+                daemonParameters.getEffectiveJvmArgs());
+
+            return type.cast(out);
+        }
+        DelegatingBuildModelAction<T> action = new DelegatingBuildModelAction<T>(type);
+        return run(action, adaptedParameters);
     }
 
-    private <T> T run(GradleLauncherAction<T> action, BuildOperationParametersVersion1 operationParameters) {
-        GradleLauncherActionExecuter<BuildOperationParametersVersion1> executer = createExecuter(operationParameters);
-        ConfiguringBuildAction<T> configuringAction = new ConfiguringBuildAction<T>(operationParameters.getGradleUserHomeDir(), operationParameters.getProjectDir(), operationParameters.isSearchUpwards(), action);
+    private <T> T run(GradleLauncherAction<T> action, ProviderOperationParameters operationParameters) {
+        GradleLauncherActionExecuter<ProviderOperationParameters> executer = createExecuter(operationParameters);
+        ConfiguringBuildAction<T> configuringAction = new ConfiguringBuildAction<T>(operationParameters, action);
         return executer.execute(configuringAction, operationParameters);
     }
 
-    private GradleLauncherActionExecuter<BuildOperationParametersVersion1> createExecuter(BuildOperationParametersVersion1 operationParameters) {
-        GradleLauncherActionExecuter<BuildOperationParametersVersion1> executer;
+    private GradleLauncherActionExecuter<ProviderOperationParameters> createExecuter(ProviderOperationParameters operationParameters) {
         if (Boolean.TRUE.equals(operationParameters.isEmbedded())) {
-            executer = new EmbeddedGradleLauncherActionExecuter(gradleLauncherFactory);
+            return embeddedExecuterSupport.getExecuter();
         } else {
-            File gradleUserHomeDir = GUtil.elvis(operationParameters.getGradleUserHomeDir(), StartParameter.DEFAULT_GRADLE_USER_HOME);
-            DaemonClient client = new DaemonClient(new DaemonConnector(gradleUserHomeDir), new GradleLauncherMetaData(), loggingServices.get(OutputEventListener.class));
-            executer = new DaemonGradleLauncherActionExecuter(client);
+            LoggingServiceRegistry loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
+
+            loggingServices.get(OutputEventRenderer.class).configure(operationParameters.getBuildLogLevel());
+
+            DaemonParameters daemonParams = init(operationParameters);
+            DaemonClientServices clientServices = new DaemonClientServices(loggingServices, daemonParams, operationParameters.getStandardInput());
+            DaemonClient client = clientServices.get(DaemonClient.class);
+
+            GradleLauncherActionExecuter<ProviderOperationParameters> executer = new DaemonGradleLauncherActionExecuter(client, clientServices.getDaemonParameters());
+
+            Factory<LoggingManagerInternal> loggingManagerFactory = clientServices.getLoggingServices().getFactory(LoggingManagerInternal.class);
+            return new LoggingBridgingGradleLauncherActionExecuter(executer, loggingManagerFactory);
+        }
+    }
+
+    private DaemonParameters init(ProviderOperationParameters operationParameters) {
+        File gradleUserHomeDir = GUtil.elvis(operationParameters.getGradleUserHomeDir(), StartParameter.DEFAULT_GRADLE_USER_HOME);
+        DaemonParameters daemonParams = new DaemonParameters();
+
+        boolean searchUpwards = operationParameters.isSearchUpwards() != null ? operationParameters.isSearchUpwards() : true;
+        daemonParams.configureFromBuildDir(operationParameters.getProjectDir(), searchUpwards);
+        daemonParams.configureFromGradleUserHome(gradleUserHomeDir);
+        daemonParams.configureFromSystemProperties(System.getProperties());
+
+        //override the params with the explicit settings provided by the tooling api
+        List<String> defaultJvmArgs = daemonParams.getAllJvmArgs();
+        daemonParams.setJvmArgs(operationParameters.getJvmArguments(defaultJvmArgs));
+        File defaultJavaHome = daemonParams.getEffectiveJavaHome();
+        daemonParams.setJavaHome(operationParameters.getJavaHome(defaultJavaHome));
+
+        if (operationParameters.getDaemonMaxIdleTimeValue() != null && operationParameters.getDaemonMaxIdleTimeUnits() != null) {
+            int idleTimeout = (int) operationParameters.getDaemonMaxIdleTimeUnits().toMillis(operationParameters.getDaemonMaxIdleTimeValue());
+            daemonParams.setIdleTimeout(idleTimeout);
         }
-        return new LoggingBridgingGradleLauncherActionExecuter(executer, loggingServices.getFactory(LoggingManagerInternal.class));
+        return daemonParams;
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DelegatingBuildModelAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DelegatingBuildModelAction.java
index 88e1605..f331e82 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DelegatingBuildModelAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DelegatingBuildModelAction.java
@@ -17,23 +17,23 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
-import org.gradle.initialization.ClassLoaderFactory;
+import org.gradle.initialization.ClassLoaderRegistry;
 import org.gradle.initialization.DefaultGradleLauncher;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
 
-class DelegatingBuildModelAction implements GradleLauncherAction<ProjectVersion3>, Serializable {
-    private transient GradleLauncherAction<ProjectVersion3> action;
-    private final Class<? extends ProjectVersion3> type;
+class DelegatingBuildModelAction<T> implements GradleLauncherAction<T>, Serializable {
+    private transient GradleLauncherAction<T> action;
+    private final Class<? extends T> type;
 
-    public DelegatingBuildModelAction(Class<? extends ProjectVersion3> type) {
+    public DelegatingBuildModelAction(Class<T> type) {
         this.type = type;
     }
 
-    public ProjectVersion3 getResult() {
+    public T getResult() {
         return action.getResult();
     }
 
@@ -42,13 +42,15 @@ class DelegatingBuildModelAction implements GradleLauncherAction<ProjectVersion3
         return action.run(launcher);
     }
 
+    @SuppressWarnings("unchecked")
     private void loadAction(DefaultGradleLauncher launcher) {
-        DefaultGradleLauncher gradleLauncher = launcher;
-        ClassLoaderFactory classLoaderFactory = gradleLauncher.getGradle().getServices().get(ClassLoaderFactory.class);
+        ClassLoaderRegistry classLoaderRegistry = launcher.getGradle().getServices().get(ClassLoaderRegistry.class);
         try {
-            action = (GradleLauncherAction<ProjectVersion3>) classLoaderFactory.getRootClassLoader().loadClass("org.gradle.tooling.internal.provider.BuildModelAction").getConstructor(Class.class).newInstance(type);
+            action = (GradleLauncherAction<T>) classLoaderRegistry.getRootClassLoader().loadClass("org.gradle.tooling.internal.provider.BuildModelAction").getConstructor(Class.class).newInstance(type);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.unwrapAndRethrow(e);
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
new file mode 100644
index 0000000..6bedc88
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider;
+
+import org.gradle.internal.Factory;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+
+/**
+ * by Szczepan Faber, created at: 12/6/11
+ */
+public class EmbeddedExecuterSupport {
+
+    private DefaultGradleLauncherFactory gradleLauncherFactory;
+    private LoggingServiceRegistry embeddedLogging;
+
+    public EmbeddedExecuterSupport() {
+        embeddedLogging = LoggingServiceRegistry.newEmbeddableLogging();
+        gradleLauncherFactory = new DefaultGradleLauncherFactory(embeddedLogging);
+    }
+
+    public LoggingBridgingGradleLauncherActionExecuter getExecuter() {
+        EmbeddedGradleLauncherActionExecuter executer = new EmbeddedGradleLauncherActionExecuter(gradleLauncherFactory);
+        Factory<LoggingManagerInternal> loggingManagerFactory = embeddedLogging.getFactory(LoggingManagerInternal.class);
+        return new LoggingBridgingGradleLauncherActionExecuter(executer, loggingManagerFactory);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java
index f73945a..3727df2 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java
@@ -20,26 +20,28 @@ import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
 import org.gradle.initialization.GradleLauncherAction;
 import org.gradle.initialization.GradleLauncherFactory;
-import org.gradle.launcher.GradleLauncherActionExecuter;
-import org.gradle.launcher.InitializationAware;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.launcher.exec.InitializationAware;
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
 
 /**
  * A {@link GradleLauncherActionExecuter} which executes an action locally.
  */
-public class EmbeddedGradleLauncherActionExecuter implements GradleLauncherActionExecuter<BuildOperationParametersVersion1> {
+public class EmbeddedGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
     private final GradleLauncherFactory gradleLauncherFactory;
 
     public EmbeddedGradleLauncherActionExecuter(GradleLauncherFactory gradleLauncherFactory) {
         this.gradleLauncherFactory = gradleLauncherFactory;
     }
 
-    public <T> T execute(GradleLauncherAction<T> action, BuildOperationParametersVersion1 actionParameters) {
-        StartParameter startParameter = new StartParameter();
+    public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
+        StartParameter startParameter;
         if (action instanceof InitializationAware) {
             InitializationAware initializationAware = (InitializationAware) action;
-            initializationAware.configureStartParameter(startParameter);
+            startParameter = initializationAware.configureStartParameter();
+        } else {
+            startParameter = new StartParameter();
         }
         GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter);
         BuildResult result = action.run(gradleLauncher);
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.java
index a568cef..eeb2558 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ExecuteBuildAction.java
@@ -17,29 +17,17 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.InitializationAware;
 
 import java.io.Serializable;
-import java.util.List;
 
-class ExecuteBuildAction implements GradleLauncherAction<Void>, InitializationAware, Serializable {
-    private final List<String> tasks;
-
-    public ExecuteBuildAction(List<String> tasks) {
-        this.tasks = tasks;
-    }
+public class ExecuteBuildAction implements GradleLauncherAction<Void>, Serializable {
 
     public Void getResult() {
         return null;
     }
 
-    public void configureStartParameter(StartParameter startParameter) {
-        startParameter.setTaskNames(tasks);
-    }
-
     public BuildResult run(GradleLauncher gradleLauncher) {
         return gradleLauncher.run();
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuter.java
index 985bfdc..30a9378 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuter.java
@@ -15,32 +15,29 @@
  */
 package org.gradle.tooling.internal.provider;
 
-import org.gradle.api.internal.Factory;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.GradleLauncherActionExecuter;
+import org.gradle.internal.Factory;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.internal.*;
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
 import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
 
 /**
- * A {@link org.gradle.launcher.GradleLauncherActionExecuter} which routes Gradle logging to those listeners specified in the {@link BuildOperationParametersVersion1} provided with a tooling api build
+ * A {@link org.gradle.launcher.exec.GradleLauncherActionExecuter} which routes Gradle logging to those listeners specified in the {@link ProviderOperationParameters} provided with a tooling api build
  * request.
  */
-public class LoggingBridgingGradleLauncherActionExecuter implements GradleLauncherActionExecuter<BuildOperationParametersVersion1> {
+public class LoggingBridgingGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
     private final Factory<LoggingManagerInternal> loggingManagerFactory;
-    private final GradleLauncherActionExecuter<BuildOperationParametersVersion1> executer;
+    private final GradleLauncherActionExecuter<ProviderOperationParameters> executer;
 
-    public LoggingBridgingGradleLauncherActionExecuter(GradleLauncherActionExecuter<BuildOperationParametersVersion1> executer, Factory<LoggingManagerInternal> loggingManagerFactory) {
+    public LoggingBridgingGradleLauncherActionExecuter(GradleLauncherActionExecuter<ProviderOperationParameters> executer, Factory<LoggingManagerInternal> loggingManagerFactory) {
         this.executer = executer;
         this.loggingManagerFactory = loggingManagerFactory;
     }
 
-    public <T> T execute(GradleLauncherAction<T> action, BuildOperationParametersVersion1 actionParameters) {
+    public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
         LoggingManagerInternal loggingManager = loggingManagerFactory.create();
-        if (!Boolean.TRUE.equals(actionParameters.isEmbedded())) {
-            loggingManager.disableStandardOutputCapture();
-        }
         if (actionParameters.getStandardOutput() != null) {
             loggingManager.addStandardOutputListener(new StreamBackedStandardOutputListener(actionParameters.getStandardOutput()));
         }
@@ -50,7 +47,7 @@ public class LoggingBridgingGradleLauncherActionExecuter implements GradleLaunch
         ProgressListenerVersion1 progressListener = actionParameters.getProgressListener();
         OutputEventListenerAdapter listener = new OutputEventListenerAdapter(progressListener);
         loggingManager.addOutputEventListener(listener);
-
+        loggingManager.setLevel(actionParameters.getBuildLogLevel());
         loggingManager.start();
         try {
             return executer.execute(action, actionParameters);
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/AdaptedOperationParameters.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/AdaptedOperationParameters.java
new file mode 100644
index 0000000..b7fdb39
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/AdaptedOperationParameters.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider.input;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.logging.LoggingConfiguration;
+import org.gradle.logging.internal.LoggingCommandLineConverter;
+import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+import org.gradle.tooling.internal.reflect.CompatibleIntrospector;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * by Szczepan Faber, created at: 12/20/11
+ */
+public class AdaptedOperationParameters implements ProviderOperationParameters {
+
+    private final BuildOperationParametersVersion1 delegate;
+    private final List<String> tasks;
+    
+    CompatibleIntrospector introspector;
+
+    public AdaptedOperationParameters(BuildOperationParametersVersion1 operationParameters) {
+        this(operationParameters, Arrays.<String>asList());
+    }
+
+    public AdaptedOperationParameters(BuildOperationParametersVersion1 operationParameters, BuildParametersVersion1 buildParameters) {
+        this(operationParameters, buildParameters.getTasks());
+    }
+
+    private AdaptedOperationParameters(BuildOperationParametersVersion1 delegate, List<String> tasks) {
+        this.delegate = delegate;
+        this.introspector = new CompatibleIntrospector(delegate);
+        this.tasks = new LinkedList<String>(tasks);
+    }
+
+    public InputStream getStandardInput() {
+        //Tooling api means embedded use. We don't want to consume standard input if we don't own the process.
+        //Hence we use a dummy input stream by default
+        ByteArrayInputStream safeDummy = new ByteArrayInputStream(new byte[0]);
+        return maybeGet(safeDummy, "getStandardInput");
+    }
+
+    public LogLevel getBuildLogLevel() {
+        LoggingCommandLineConverter converter = new LoggingCommandLineConverter();
+        CommandLineParser parser = new CommandLineParser().allowUnknownOptions();
+        converter.configure(parser);
+        ParsedCommandLine parsedCommandLine = parser.parse(getArguments());
+        //configure verbosely only if arguments do not specify any log level.
+        if (getVerboseLogging() && !parsedCommandLine.hasAnyOption(converter.getLogLevelOptions())) {
+            return LogLevel.DEBUG;
+        }
+
+        LoggingConfiguration loggingConfiguration = converter.convert(parsedCommandLine);
+        return loggingConfiguration.getLogLevel();
+    }
+
+    public boolean getVerboseLogging() {
+        return introspector.getSafely(false, "getVerboseLogging");
+    }
+
+    public File getJavaHome(File defaultJavaHome) {
+        return maybeGet(defaultJavaHome, "getJavaHome");
+    }
+
+    public List<String> getJvmArguments(List<String> defaultJvmArgs) {
+        return maybeGet(defaultJvmArgs, "getJvmArguments");
+    }
+
+    private <T> T maybeGet(T defaultValue, String methodName) {
+        T out = introspector.getSafely(defaultValue, methodName);
+        if (out == null) {
+            return defaultValue;
+        }
+        return out;
+    }
+
+    public File getProjectDir() {
+        return delegate.getProjectDir();
+    }
+
+    public Boolean isSearchUpwards() {
+        return delegate.isSearchUpwards();
+    }
+
+    public File getGradleUserHomeDir() {
+        return delegate.getGradleUserHomeDir();
+    }
+
+    public Boolean isEmbedded() {
+        return delegate.isEmbedded();
+    }
+
+    public Integer getDaemonMaxIdleTimeValue() {
+        return delegate.getDaemonMaxIdleTimeValue();
+    }
+
+    public TimeUnit getDaemonMaxIdleTimeUnits() {
+        return delegate.getDaemonMaxIdleTimeUnits();
+    }
+
+    public long getStartTime() {
+        return delegate.getStartTime();
+    }
+
+    public OutputStream getStandardOutput() {
+        return delegate.getStandardOutput();
+    }
+
+    public OutputStream getStandardError() {
+        return delegate.getStandardError();
+    }
+
+    public ProgressListenerVersion1 getProgressListener() {
+        return delegate.getProgressListener();
+    }
+
+    public List<String> getArguments() {
+        return maybeGet(Arrays.<String>asList(), "getArguments");
+    }
+    
+    public List<String> getTasks() {
+        return tasks;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/ProviderOperationParameters.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/ProviderOperationParameters.java
new file mode 100644
index 0000000..aa36b67
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/input/ProviderOperationParameters.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.tooling.internal.provider.input;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Defines what information is needed on the provider side regarding the build operation.
+ * <p>
+ * by Szczepan Faber, created at: 12/20/11
+ */
+public interface ProviderOperationParameters {
+
+    LogLevel getBuildLogLevel();
+
+    InputStream getStandardInput();
+
+    File getJavaHome(File defaultJavaHome);
+
+    List<String> getJvmArguments(List<String> defaultJvmArgs);
+
+    long getStartTime();
+
+    File getGradleUserHomeDir();
+
+    File getProjectDir();
+
+    Boolean isSearchUpwards();
+
+    Boolean isEmbedded();
+
+    OutputStream getStandardOutput();
+
+    OutputStream getStandardError();
+
+    Integer getDaemonMaxIdleTimeValue();
+
+    ProgressListenerVersion1 getProgressListener();
+
+    TimeUnit getDaemonMaxIdleTimeUnits();
+
+    List<String> getArguments();
+
+    List<String> getTasks();
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy
deleted file mode 100644
index 7b392e7..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/CommandLineActionFactoryTest.groovy
+++ /dev/null
@@ -1,227 +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.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.initialization.GradleLauncherFactory
-
-class CommandLineActionFactoryTest extends Specification {
-    @Rule
-    public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr();
-    @Rule
-    public final SetSystemProperties sysProperties = new SetSystemProperties();
-    final ExecutionListener buildCompleter = Mock()
-    final CommandLineConverter<StartParameter> startParameterConverter = Mock()
-    final GradleLauncherFactory gradleLauncherFactory = Mock()
-    final GradleLauncher gradleLauncher = Mock()
-    final BuildResult buildResult = Mock()
-    final ServiceRegistry loggingServices = Mock()
-    final CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = Mock()
-    final LoggingManagerInternal loggingManager = Mock()
-    final CommandLineActionFactory factory = new CommandLineActionFactory() {
-        @Override
-        ServiceRegistry createLoggingServices() {
-            return loggingServices
-        }
-
-        @Override
-        CommandLineConverter<StartParameter> createStartParameterConverter() {
-            return startParameterConverter
-        }
-    }
-
-    def setup() {
-        _ * loggingServices.get(CommandLineConverter) >> loggingConfigurationConverter
-        _ * loggingConfigurationConverter.convert(!null) >> new LoggingConfiguration()
-        Factory<LoggingManagerInternal> loggingManagerFactory = Mock()
-        _ * loggingServices.getFactory(LoggingManagerInternal) >> loggingManagerFactory
-        _ * loggingManagerFactory.create() >> loggingManager
-    }
-
-    def reportsCommandLineParseFailure() {
-        def failure = new CommandLineArgumentException('<broken>')
-
-        when:
-        def action = factory.convert([])
-
-        then:
-        1 * startParameterConverter.configure(!null) >> { args -> args[0].option('some-build-option') }
-        1 * startParameterConverter.convert(!null, !null) >> { throw failure }
-
-        when:
-        action.execute(buildCompleter)
-
-        then:
-        1 * loggingManager.start()
-        outputs.stdErr.contains('<broken>')
-        outputs.stdErr.contains('USAGE: gradle [option...] [task...]')
-        outputs.stdErr.contains('--help')
-        outputs.stdErr.contains('--some-build-option')
-        1 * buildCompleter.onFailure(failure)
-    }
-
-    def displaysUsageMessage() {
-        when:
-        def action = factory.convert([option])
-        action.execute(buildCompleter)
-
-        then:
-        _ * startParameterConverter.configure(!null) >> { args -> args[0].option('some-build-option') }
-        1 * loggingManager.start()
-        outputs.stdOut.contains('USAGE: gradle [option...] [task...]')
-        outputs.stdOut.contains('--help')
-        outputs.stdOut.contains('--some-build-option')
-
-        where:
-        option << ['-h', '-?', '--help']
-    }
-
-    def usesSystemPropertyForGradleAppName() {
-        System.setProperty("org.gradle.appname", "gradle-app");
-
-        when:
-        def action = factory.convert(['-?'])
-        action.execute(buildCompleter)
-
-        then:
-        outputs.stdOut.contains('USAGE: gradle-app [option...] [task...]')
-    }
-
-    def displaysVersionMessage() {
-        when:
-        def action = factory.convert([option])
-        action.execute(buildCompleter)
-
-        then:
-        1 * loggingManager.start()
-        outputs.stdOut.contains(GradleVersion.current().prettyPrint())
-
-        where:
-        option << ['-v', '--version']
-    }
-
-    def launchesGUI() {
-        when:
-        def action = factory.convert(['--gui'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action instanceof ExceptionReportingAction
-        action.action.action instanceof ActionAdapter
-        action.action.action.action instanceof ShowGuiAction
-    }
-
-    def executesBuild() {
-        when:
-        def action = factory.convert(['args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action instanceof ExceptionReportingAction
-        action.action.action instanceof RunBuildAction
-    }
-
-    def executesBuildUsingDaemon() {
-        when:
-        def action = factory.convert(['--daemon', 'args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action instanceof ExceptionReportingAction
-        action.action.action instanceof ActionAdapter
-        action.action.action.action instanceof DaemonBuildAction
-    }
-
-    def executesBuildUsingDaemonWhenSystemPropertyIsSetToTrue() {
-        when:
-        System.properties['org.gradle.daemon'] = 'false'
-        def action = factory.convert(['args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action.action instanceof RunBuildAction
-
-        when:
-        System.properties['org.gradle.daemon'] = 'true'
-        action = factory.convert(['args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action.action.action instanceof DaemonBuildAction
-    }
-
-    def doesNotUseDaemonWhenNoDaemonOptionPresent() {
-        when:
-        def action = factory.convert(['--no-daemon', 'args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action.action instanceof RunBuildAction
-    }
-
-    def daemonOptionTakesPrecedenceOverSystemProperty() {
-        when:
-        System.properties['org.gradle.daemon'] = 'false'
-        def action = factory.convert(['--daemon', 'args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action.action.action instanceof DaemonBuildAction
-
-        when:
-        System.properties['org.gradle.daemon'] = 'true'
-        action = factory.convert(['--no-daemon', 'args'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action.action instanceof RunBuildAction
-    }
-    
-    def stopsDaemon() {
-        when:
-        def action = factory.convert(['--stop'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action instanceof ExceptionReportingAction
-        action.action.action instanceof ActionAdapter
-        action.action.action.action instanceof StopDaemonAction
-    }
-
-    def runsDaemonInForeground() {
-        when:
-        def action = factory.convert(['--foreground'])
-
-        then:
-        action instanceof WithLoggingAction
-        action.action instanceof ExceptionReportingAction
-        action.action.action instanceof ActionAdapter
-        action.action.action.action instanceof DaemonMain
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/DaemonBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/DaemonBuildActionTest.groovy
deleted file mode 100644
index 68221c5..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/DaemonBuildActionTest.groovy
+++ /dev/null
@@ -1,47 +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.launcher
-
-import org.gradle.initialization.BuildClientMetaData
-import org.gradle.initialization.ParsedCommandLine
-import spock.lang.Specification
-
-class DaemonBuildActionTest extends Specification {
-    final DaemonClient client = Mock()
-    final ParsedCommandLine commandLine = Mock()
-    final BuildClientMetaData clientMetaData = Mock()
-    final File currentDir = new File('current-dir')
-    final long startTime = 90
-    final Map<String, String> systemProperties = [key: 'value']
-    final DaemonBuildAction action = new DaemonBuildAction(client, commandLine, currentDir, clientMetaData, startTime, systemProperties)
-
-    def runsBuildUsingDaemon() {
-        when:
-        action.run()
-
-        then:
-        1 * client.execute({!null}, {!null}) >> { args ->
-            ExecuteBuildAction action = args[0]
-            assert action.currentDir == currentDir
-            assert action.args == commandLine
-            BuildActionParameters build = args[1]
-            assert build.clientMetaData == clientMetaData
-            assert build.startTime == startTime
-            assert build.systemProperties == systemProperties
-        }
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/DaemonClientTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/DaemonClientTest.groovy
deleted file mode 100644
index 2fba724..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/DaemonClientTest.groovy
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2011 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.BuildClientMetaData
-import org.gradle.initialization.GradleLauncherAction
-import org.gradle.launcher.protocol.Build
-import org.gradle.launcher.protocol.CommandComplete
-import org.gradle.launcher.protocol.Result
-import org.gradle.launcher.protocol.Stop
-import org.gradle.logging.internal.OutputEventListener
-import org.gradle.messaging.remote.internal.Connection
-import spock.lang.Specification
-
-class DaemonClientTest extends Specification {
-    final DaemonConnector connector = Mock()
-    final Connection<Object> connection = Mock()
-    final BuildClientMetaData metaData = Mock()
-    final OutputEventListener outputEventListener = Mock()
-    final DaemonClient client = new DaemonClient(connector, metaData, outputEventListener)
-
-    def stopsTheDaemonWhenRunning() {
-        when:
-        client.stop()
-
-        then:
-        1 * connector.maybeConnect() >> connection
-        1 * connection.dispatch({it instanceof Stop})
-        1 * connection.receive() >> new CommandComplete(null)
-        1 * connection.stop()
-        0 * _._
-    }
-
-    def stopsTheDaemonWhenNotRunning() {
-        when:
-        client.stop()
-
-        then:
-        1 * connector.maybeConnect() >> null
-        0 * _._
-    }
-
-    def rethrowsFailureToStopDaemon() {
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        client.stop()
-
-        then:
-        RuntimeException e = thrown()
-        e == failure
-        1 * connector.maybeConnect() >> connection
-        1 * connection.dispatch({it instanceof Stop})
-        1 * connection.receive() >> new CommandComplete(failure)
-        1 * connection.stop()
-        0 * _._
-    }
-
-    def executesAction() {
-        GradleLauncherAction<String> action = Mock()
-        BuildActionParameters parameters = Mock()
-
-        when:
-        def result = client.execute(action, parameters)
-
-        then:
-        result == '[result]'
-        1 * connector.connect() >> connection
-        1 * connection.dispatch({it instanceof Build})
-        1 * connection.receive() >> new Result('[result]')
-        1 * connection.stop()
-    }
-
-    def rethrowsFailureToExecuteAction() {
-        GradleLauncherAction<String> action = Mock()
-        BuildActionParameters parameters = Mock()
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        client.execute(action, parameters)
-
-        then:
-        RuntimeException e = thrown()
-        e == failure
-        1 * connector.connect() >> connection
-        1 * connection.dispatch({it instanceof Build})
-        1 * connection.receive() >> new CommandComplete(failure)
-        1 * connection.stop()
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/ExceptionReportingActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/ExceptionReportingActionTest.groovy
deleted file mode 100644
index e360bf2..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/ExceptionReportingActionTest.groovy
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 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.api.Action
-import spock.lang.Specification
-
-class ExceptionReportingActionTest extends Specification {
-    final Action<ExecutionListener> target = Mock()
-    final ExecutionListener listener = Mock()
-    final BuildExceptionReporter reporter = Mock()
-    final ExceptionReportingAction action = new ExceptionReportingAction(target, reporter)
-
-    def executesAction() {
-        when:
-        action.execute(listener)
-
-        then:
-        1 * target.execute(listener)
-        0 * _._
-    }
-
-    def reportsExceptionThrownByAction() {
-        def failure = new RuntimeException()
-
-        when:
-        action.execute(listener)
-
-        then:
-        1 * target.execute(listener) >> { throw failure }
-        1 * reporter.reportException(failure)
-        1 * listener.onFailure(failure)
-        0 * _._
-    }
-
-    def doesNotReportAlreadyReportedExceptionThrownByAction() {
-        def cause = new RuntimeException()
-        def failure = new ReportedException(cause)
-
-        when:
-        action.execute(listener)
-
-        then:
-        1 * target.execute(listener) >> { throw failure }
-        1 * listener.onFailure(cause)
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
index d1daceb..c4173e0 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
@@ -19,66 +19,84 @@ import spock.lang.Specification
 import org.junit.Rule
 import org.gradle.util.RedirectStdOutAndErr
 import org.gradle.api.Action
+import org.gradle.launcher.cli.CommandLineActionFactory
+import org.gradle.launcher.exec.ExecutionListener
+import org.gradle.launcher.exec.ExecutionCompleter
 
 class MainTest extends Specification {
+    
     @Rule final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
-    final Main.BuildCompleter completer = Mock()
-    final CommandLineActionFactory factory = Mock()
+    
+    Action actionImpl 
+    
+    void action(Closure closure) {
+        actionImpl = closure as Action
+    }
+    
+    def actionFactoryImpl
+    
+    void actionFactory(Closure closure) {
+        actionFactoryImpl = new CommandLineActionFactory() { Action<ExecutionListener> convert(List args) { closure(args) } }
+    }
+    boolean completedSuccessfully
+    boolean completedWithFailure
+    Throwable failure
+        
     final String[] args = ['arg']
+    
     final Main main = new Main(args) {
-        @Override
-        Main.BuildCompleter createBuildCompleter() {
-            completer
+        protected ExecutionCompleter createCompleter() {
+            [complete: { completedSuccessfully = true }, completeWithFailure: { completedWithFailure = true; failure = it }] as ExecutionCompleter
         }
 
-        @Override
-        CommandLineActionFactory createActionFactory() {
-            factory
+        protected CommandLineActionFactory createActionFactory() {
+            actionFactoryImpl
         }
     }
+    
 
+    def setup() {
+        actionFactory { actionImpl }
+    }
+    
     def createsAndExecutesCommandLineAction() {
-        Action<ExecutionListener> action = Mock()
-
+        given:
+        action {}
+            
         when:
-        main.execute()
+        main.run()
 
         then:
-        1 * factory.convert(args) >> action
-        1 * action.execute(completer)
-        1 * completer.exit()
-        0 * _._
+        completedSuccessfully
     }
 
     def reportsActionExecutionFailure() {
-        Action<ExecutionListener> action = Mock()
-        RuntimeException failure = new RuntimeException('broken')
+        given:
+        def thrownFailure = new RuntimeException('broken')
+        action { throw thrownFailure }
 
         when:
-        main.execute()
+        main.run()
 
         then:
-        1 * factory.convert(args) >> action
-        1 * action.execute(completer) >> { throw failure }
         outputs.stdErr.contains('internal error')
         outputs.stdErr.contains('java.lang.RuntimeException: broken')
-        1 * completer.onFailure(failure)
-        1 * completer.exit()
-        0 * _._
+        completedWithFailure
+        failure == thrownFailure
     }
 
     def reportsActionCreationFailure() {
-        RuntimeException failure = new RuntimeException('broken')
+        given:
+        def thrownFailure = new RuntimeException('broken')
+        actionFactory { throw thrownFailure }
 
         when:
-        main.execute()
+        main.run()
 
         then:
-        1 * factory.convert(args) >> { throw failure }
         outputs.stdErr.contains('internal error')
         outputs.stdErr.contains('java.lang.RuntimeException: broken')
-        1 * completer.onFailure(failure)
-        1 * completer.exit()
-        0 * _._
+        completedWithFailure
+        failure == thrownFailure
     }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/RunBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/RunBuildActionTest.groovy
deleted file mode 100644
index f3bafee..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/RunBuildActionTest.groovy
+++ /dev/null
@@ -1,66 +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.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/launcher/src/test/groovy/org/gradle/launcher/StopDaemonActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/StopDaemonActionTest.groovy
deleted file mode 100644
index 151d8d1..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/StopDaemonActionTest.groovy
+++ /dev/null
@@ -1,32 +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.launcher
-
-import spock.lang.Specification
-
-class StopDaemonActionTest extends Specification {
-    final DaemonClient client = Mock()
-    final StopDaemonAction action = new StopDaemonAction(client)
-
-    def executesStopCommand() {
-        when:
-        action.run()
-
-        then:
-        1 * client.stop()
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
new file mode 100644
index 0000000..ef0e940
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2012 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.cli
+
+import org.gradle.StartParameter
+import org.gradle.cli.CommandLineConverter
+import org.gradle.cli.CommandLineParser
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.launcher.daemon.bootstrap.DaemonMain
+import org.gradle.launcher.daemon.configuration.DaemonParameters
+import org.gradle.logging.internal.OutputEventListener
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class BuildActionsFactoryTest extends Specification {
+    @Rule
+    public final SetSystemProperties sysProperties = new SetSystemProperties();
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder();
+    final CommandLineConverter<StartParameter> startParameterConverter = Mock()
+    final ServiceRegistry loggingServices = Mock()
+    final BuildActionsFactory factory = new BuildActionsFactory(loggingServices, startParameterConverter)
+
+    def setup() {        
+        _ * loggingServices.get(OutputEventListener) >> Mock(OutputEventListener)
+    }
+
+    def executesBuild() {
+        when:
+        def action = convert('args')
+
+        then:
+        isInProcess(action)
+    }
+
+    def executesBuildUsingDaemon() {
+        when:
+        def action = convert('--daemon', 'args')
+
+        then:
+        isDaemon action
+    }
+
+    def executesBuildUsingDaemonWhenSystemPropertyIsSetToTrue() {
+        when:
+        System.properties['org.gradle.daemon'] = 'false'
+        def action = convert('args')
+
+        then:
+        isInProcess action
+
+        when:
+        System.properties['org.gradle.daemon'] = 'true'
+        action = convert('args')
+
+        then:
+        isDaemon action
+    }
+
+    def doesNotUseDaemonWhenNoDaemonOptionPresent() {
+        when:
+        def action = convert('--no-daemon', 'args')
+
+        then:
+        isInProcess action
+    }
+
+    def daemonOptionTakesPrecedenceOverSystemProperty() {
+        when:
+        System.properties['org.gradle.daemon'] = 'false'
+        def action = convert('--daemon', 'args')
+
+        then:
+        isDaemon action
+
+        when:
+        System.properties['org.gradle.daemon'] = 'true'
+        action = convert('--no-daemon', 'args')
+
+        then:
+        isInProcess action
+    }
+
+    def stopsDaemon() {
+        when:
+        def action = convert('--stop')
+
+        then:
+        action instanceof ActionAdapter
+        action.action instanceof StopDaemonAction
+    }
+
+    def runsDaemonInForeground() {
+        when:
+        def action = convert('--foreground')
+
+        then:
+        action instanceof ActionAdapter
+        action.action instanceof DaemonMain
+    }
+
+    def executesBuildWithSingleUseDaemonIfJavaHomeIsNotCurrent() {
+        when:
+        def javaHome = tmpDir.createDir("javahome")
+        javaHome.createFile(OperatingSystem.current().getExecutableName("bin/java"))
+
+        System.properties['org.gradle.java.home'] = javaHome.canonicalPath
+        def action = convert()
+
+        then:
+        isSingleUseDaemon action
+    }
+
+    def "daemon setting in precedence is system prop, user home then project directory"() {
+        given:
+        def userHome = tmpDir.createDir("user_home")
+        userHome.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((DaemonParameters.DAEMON_SYS_PROPERTY): 'false').store(outstr, "HEADER")
+        }
+        def projectDir = tmpDir.createDir("project_dir")
+        projectDir.createFile("settings.gradle")
+        projectDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((DaemonParameters.DAEMON_SYS_PROPERTY): 'true').store(outstr, "HEADER")
+        }
+
+        when:
+        def action = convert()
+
+        then:
+        startParameterConverter.convert(!null, !null) >> { args, startParam ->
+            startParam.currentDir = projectDir
+        }
+
+        and:
+        isDaemon action
+
+        when:
+        action = convert()
+
+        then:
+        startParameterConverter.convert(!null, !null) >> { args, startParam ->
+            startParam.gradleUserHomeDir = userHome
+            startParam.currentDir = projectDir
+        }
+
+        and:
+        isInProcess action
+
+        when:
+        System.properties['org.gradle.daemon'] = 'true'
+        action = convert()
+
+        then:
+        startParameterConverter.convert(!null, !null) >> { args, startParam ->
+            startParam.gradleUserHomeDir = userHome
+            startParam.currentDir = projectDir
+        }
+
+        and:
+        isDaemon action
+    }
+
+    def convert(String... args) {
+        def CommandLineParser parser = new CommandLineParser()
+        factory.configureCommandLineParser(parser)
+        def cl = parser.parse(args)
+        return factory.createAction(parser, cl)
+    }
+
+    def isDaemon(def action) {
+        assert action instanceof ActionAdapter
+        action.action instanceof DaemonBuildAction
+    }
+
+    def isInProcess(def action) {
+        action instanceof RunBuildAction
+    }
+
+    def isSingleUseDaemon(def action) {
+        assert action instanceof ActionAdapter
+        action.action instanceof DaemonBuildAction
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
new file mode 100644
index 0000000..3f0eae5
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
@@ -0,0 +1,230 @@
+/*
+ * 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.cli
+
+import org.gradle.api.Action
+import org.gradle.cli.CommandLineArgumentException
+import org.gradle.cli.CommandLineConverter
+import org.gradle.cli.CommandLineParser
+import org.gradle.internal.Factory
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.launcher.exec.ExecutionListener
+import org.gradle.logging.internal.OutputEventListener
+import org.gradle.logging.internal.StreamingStyledTextOutput
+import org.gradle.util.GradleVersion
+import org.gradle.util.RedirectStdOutAndErr
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.logging.*
+
+class CommandLineActionFactoryTest extends Specification {
+    @Rule
+    public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr();
+    @Rule
+    public final SetSystemProperties sysProperties = new SetSystemProperties();
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder();
+    final ExecutionListener executionListener = Mock()
+    final ServiceRegistry loggingServices = Mock()
+    final CommandLineConverter<LoggingConfiguration> loggingConfigurationConverter = Mock()
+    final LoggingManagerInternal loggingManager = Mock()
+    final CommandLineAction actionFactory1 = Mock()
+    final CommandLineAction actionFactory2 = Mock()
+    final CommandLineActionFactory factory = new CommandLineActionFactory() {
+        @Override
+        ServiceRegistry createLoggingServices() {
+            return loggingServices
+        }
+
+        @Override
+        protected void createActionFactories(ServiceRegistry loggingServices, Collection<CommandLineAction> actions) {
+            actions.add(actionFactory1)
+            actions.add(actionFactory2)
+        }
+    }
+
+    def setup() {
+        ProgressLoggerFactory progressLoggerFactory = Mock()
+        _ * loggingServices.get(ProgressLoggerFactory) >> progressLoggerFactory
+        _ * loggingServices.get(CommandLineConverter) >> loggingConfigurationConverter
+        _ * loggingServices.get(OutputEventListener) >> Mock(OutputEventListener)
+        Factory<LoggingManagerInternal> loggingManagerFactory = Mock()
+        _ * loggingServices.getFactory(LoggingManagerInternal) >> loggingManagerFactory
+        _ * loggingManagerFactory.create() >> loggingManager
+        StyledTextOutputFactory textOutputFactory = Mock()
+        _ * loggingServices.get(StyledTextOutputFactory) >> textOutputFactory
+        StyledTextOutput textOutput = new StreamingStyledTextOutput(outputs.stdErrPrintStream)
+        _ * textOutputFactory.create(_, _) >> textOutput
+    }
+
+    def "delegates to each action factory to configure the command-line parser and create the action"() {
+        Action<ExecutionListener> rawAction = Mock()
+
+        when:
+        def action = factory.convert(["--some-option"])
+
+        then:
+        action
+
+        when:
+        action.execute(executionListener)
+
+        then:
+        1 * actionFactory1.configureCommandLineParser(!null) >> { CommandLineParser parser -> parser.option("some-option") }
+        1 * actionFactory2.configureCommandLineParser(!null)
+        1 * actionFactory1.createAction(!null, !null) >> rawAction
+        1 * rawAction.execute(executionListener)
+    }
+
+    def "configures logging before parsing command-line"() {
+        Action<ExecutionListener> rawAction = Mock()
+
+        when:
+        def action = factory.convert([])
+
+        then:
+        action
+
+        when:
+        action.execute(executionListener)
+
+        then:
+        1 * loggingManager.start()
+
+        and:
+        1 * actionFactory1.configureCommandLineParser(!null)
+        1 * actionFactory2.configureCommandLineParser(!null)
+        1 * actionFactory1.createAction(!null, !null) >> rawAction
+    }
+
+    def "reports command-line parse failure"() {
+        when:
+        def action = factory.convert(['--broken'])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdErr.contains('--broken')
+        outputs.stdErr.contains('USAGE: gradle [option...] [task...]')
+        outputs.stdErr.contains('--help')
+        outputs.stdErr.contains('--some-option')
+
+        and:
+        1 * actionFactory1.configureCommandLineParser(!null) >> {CommandLineParser parser -> parser.option('some-option')}
+        1 * executionListener.onFailure({it instanceof CommandLineArgumentException})
+        0 * executionListener._
+    }
+
+    def "reports failure to build action due to command-line parse failure"() {
+        def failure = new CommandLineArgumentException("<broken>")
+
+        when:
+        def action = factory.convert(['--some-option'])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdErr.contains('<broken>')
+        outputs.stdErr.contains('USAGE: gradle [option...] [task...]')
+        outputs.stdErr.contains('--help')
+        outputs.stdErr.contains('--some-option')
+
+        and:
+        1 * actionFactory1.configureCommandLineParser(!null) >> {CommandLineParser parser -> parser.option('some-option')}
+        1 * actionFactory1.createAction(!null, !null) >> { throw failure }
+        1 * executionListener.onFailure(failure)
+        0 * executionListener._
+    }
+
+    def "ignores failure to parse logging configuration"() {
+        when:
+        def action = factory.convert(["--logging=broken"])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdErr.contains('--logging')
+        outputs.stdErr.contains('USAGE: gradle [option...] [task...]')
+        outputs.stdErr.contains('--help')
+        outputs.stdErr.contains('--some-option')
+
+        and:
+        1 * loggingConfigurationConverter.configure(!null) >> {CommandLineParser parser -> parser.option("logging").hasArgument()}
+        1 * actionFactory1.configureCommandLineParser(!null) >> {CommandLineParser parser -> parser.option('some-option')}
+        1 * executionListener.onFailure({it instanceof CommandLineArgumentException})
+        0 * executionListener._
+    }
+
+    def "reports other failure to build action"() {
+        def failure = new RuntimeException("<broken>")
+
+        when:
+        def action = factory.convert([])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdErr.contains('<broken>')
+
+        and:
+        1 * actionFactory1.createAction(!null, !null) >> { throw failure }
+        1 * executionListener.onFailure(failure)
+        0 * executionListener._
+    }
+
+    def "displays usage message"() {
+        when:
+        def action = factory.convert([option])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdOut.contains('USAGE: gradle [option...] [task...]')
+        outputs.stdOut.contains('--help')
+        outputs.stdOut.contains('--some-option')
+
+        and:
+        1 * actionFactory1.configureCommandLineParser(!null) >> {CommandLineParser parser -> parser.option('some-option')}
+        0 * executionListener._
+
+        where:
+        option << ['-h', '-?', '--help']
+    }
+
+    def "uses system property for application name"() {
+        System.setProperty("org.gradle.appname", "gradle-app");
+
+        when:
+        def action = factory.convert(['-?'])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdOut.contains('USAGE: gradle-app [option...] [task...]')
+    }
+
+    def "displays version message"() {
+        when:
+        def action = factory.convert([option])
+        action.execute(executionListener)
+
+        then:
+        outputs.stdOut.contains(GradleVersion.current().prettyPrint())
+
+        and:
+        1 * loggingManager.start()
+        0 * executionListener._
+
+        where:
+        option << ['-v', '--version']
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/DaemonBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/DaemonBuildActionTest.groovy
new file mode 100644
index 0000000..224f024
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/DaemonBuildActionTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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.cli
+
+import org.gradle.StartParameter
+import org.gradle.initialization.BuildClientMetaData
+import org.gradle.launcher.exec.BuildActionParameters
+import org.gradle.launcher.exec.GradleLauncherActionExecuter
+import spock.lang.Specification
+
+class DaemonBuildActionTest extends Specification {
+    final GradleLauncherActionExecuter<BuildActionParameters> client = Mock()
+    final StartParameter startParameter = Mock()
+    final BuildClientMetaData clientMetaData = Mock()
+    final File currentDir = new File('current-dir')
+    final long startTime = 90
+    final Map<String, String> systemProperties = [key: 'value']
+    final Map<String, String> envVariables = [key2: 'value2']
+    final DaemonBuildAction action = new DaemonBuildAction(client, startParameter, currentDir, clientMetaData, startTime, systemProperties, envVariables)
+
+    def runsBuildUsingDaemon() {
+        when:
+        action.run()
+
+        then:
+        1 * client.execute({!null}, {!null}) >> { args ->
+            ExecuteBuildAction action = args[0]
+            assert action.startParameter == startParameter
+            BuildActionParameters build = args[1]
+            assert build.clientMetaData == clientMetaData
+            assert build.startTime == startTime
+            assert build.systemProperties == systemProperties
+        }
+        0 * _._
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/GuiActionsFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/GuiActionsFactoryTest.groovy
new file mode 100644
index 0000000..322bc61
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/GuiActionsFactoryTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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.cli
+
+import spock.lang.Specification
+import org.gradle.cli.CommandLineParser
+
+class GuiActionsFactoryTest extends Specification {
+    final GuiActionsFactory factory = new GuiActionsFactory()
+    final CommandLineParser parser = new CommandLineParser()
+
+    def "creates GUI action"() {
+        when:
+        factory.configureCommandLineParser(parser)
+        def cl = parser.parse("--gui")
+        def action = factory.createAction(parser, cl)
+
+        then:
+        action instanceof ActionAdapter
+        action.action instanceof GuiActionsFactory.ShowGuiAction
+    }
+
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
new file mode 100644
index 0000000..b995da0
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
@@ -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.launcher.cli
+
+import spock.lang.Specification
+import org.gradle.StartParameter
+import org.gradle.internal.service.ServiceRegistry
+import org.gradle.initialization.GradleLauncherFactory
+import org.gradle.GradleLauncher
+import org.gradle.BuildResult
+import org.gradle.initialization.BuildRequestMetaData
+import org.gradle.launcher.exec.ExecutionListener
+
+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/launcher/src/test/groovy/org/gradle/launcher/cli/StopDaemonActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/StopDaemonActionTest.groovy
new file mode 100644
index 0000000..5816783
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/StopDaemonActionTest.groovy
@@ -0,0 +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.launcher.cli
+
+import spock.lang.Specification
+import org.gradle.launcher.daemon.client.DaemonClient
+
+class StopDaemonActionTest extends Specification {
+    final DaemonClient client = Mock()
+    final StopDaemonAction action = new StopDaemonAction(client)
+
+    def executesStopCommand() {
+        when:
+        action.run()
+
+        then:
+        1 * client.stop()
+        0 * _._
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
new file mode 100644
index 0000000..f08ef7c
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.daemon
+
+import org.gradle.configuration.GradleLauncherMetaData
+import org.gradle.launcher.daemon.client.DaemonClient
+import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
+import org.gradle.launcher.exec.DefaultBuildActionParameters
+import org.gradle.tooling.internal.provider.ConfiguringBuildAction
+import org.gradle.tooling.internal.provider.ExecuteBuildAction
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * Exercises the basic mechanics using an embedded daemon.
+ * 
+ * @todo Test stdio (what should println do in the daemon threads?)
+ * @todo launching multiple embedded daemons with the same registry
+ */
+class EmbeddedDaemonSmokeTest extends Specification {
+
+    @Rule TemporaryFolder temp
+
+    def daemonClientServices = new EmbeddedDaemonClientServices()
+    
+    def "run build"() {
+        given:
+        def action = new ConfiguringBuildAction(projectDirectory: temp.dir, searchUpwards: false, tasks: ['echo'],
+                gradleUserHomeDir: temp.createDir("user-home"), action: new ExecuteBuildAction())
+        def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), new Date().time, System.properties, System.getenv(), temp.dir)
+        
+        and:
+        def outputFile = temp.file("output.txt")
+        
+        expect:
+        !outputFile.exists()
+        
+        and:
+        temp.file("build.gradle") << """
+            task echo << {
+                file("output.txt").write "Hello!"
+            }
+        """
+        
+        when:
+        daemonClientServices.get(DaemonClient).execute(action, parameters)
+        
+        then:
+        outputFile.exists() && outputFile.text == "Hello!"
+    }
+    
+    def cleanup() {
+        daemonClientServices?.close()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy
new file mode 100644
index 0000000..936d4eb
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2011 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.daemon.client
+
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import org.gradle.initialization.BuildClientMetaData
+import org.gradle.launcher.daemon.protocol.CloseInput
+import org.gradle.launcher.daemon.protocol.ForwardInput
+import org.gradle.messaging.dispatch.Dispatch
+import org.gradle.util.ConcurrentSpecification
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class DaemonClientInputForwarderTest extends ConcurrentSpecification {
+
+    def bufferSize = 1024
+
+    def source = new PipedOutputStream()
+    def inputStream = new PipedInputStream(source)
+
+    def received = new LinkedBlockingQueue()
+    def dispatch = { received << it } as Dispatch
+    
+    def receivedCommand() {
+        received.poll(5, TimeUnit.SECONDS)
+    }
+
+    def receive(expected) {
+        def receivedCommand = receivedCommand()
+        assert receivedCommand instanceof ForwardInput
+        assert receivedCommand.bytes == expected.bytes
+        true
+    }
+
+    boolean receiveClosed() {
+        receivedCommand() instanceof CloseInput
+    }
+    
+    def forwarder
+
+    def createForwarder() {
+        forwarder = new DaemonClientInputForwarder(inputStream, [:] as BuildClientMetaData, dispatch, executorFactory, bufferSize)
+        forwarder.start()
+    }
+    
+    def setup() {
+        createForwarder()
+    }
+    
+    def closeInput() {
+        source.close()
+    }
+    
+    def "input is forwarded until forwarder is stopped"() {
+        when:
+        source << toPlatformLineSeparators("abc\ndef\njkl\n")
+
+        then:
+        receive toPlatformLineSeparators("abc\n")
+        receive toPlatformLineSeparators("def\n")
+        receive toPlatformLineSeparators("jkl\n")
+
+        when:
+        forwarder.stop()
+
+        then:
+        receiveClosed()
+    }
+    
+    def "close input is sent when the underlying input stream is closed"() {
+        when:
+        source << toPlatformLineSeparators("abc\ndef\n")
+        closeInput()
+
+        then:
+        receive toPlatformLineSeparators("abc\n")
+        receive toPlatformLineSeparators("def\n")
+
+        and:
+        receiveClosed()
+    }
+        
+    def "stream being closed without sending anything just sends close input command"() {
+        when:
+        forwarder.stop()
+        
+        then:
+        receiveClosed()
+    }
+    
+    def "one partial line when input stream closed gets forwarded"() {
+        when:
+        source << "abc"
+        closeInput()
+
+        then:
+        receive "abc"
+
+        and:
+        receiveClosed()
+    }
+
+    def "one partial line when forwarder stopped gets forwarded"() {
+        when:
+        source << "abc"
+        // Semantics of DaemonClientInputForwarder.stop() mean we can't know when the input has been consumed
+        // so, let's just guess
+        sleep(1000)
+        forwarder.stop()
+
+        then:
+        receive "abc"
+
+        and:
+        receiveClosed()
+    }
+    
+    def cleanup() {
+        source.close()
+        inputStream.close()
+        forwarder.stop()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
new file mode 100644
index 0000000..bc3887f
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientServicesTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.daemon.client
+
+import org.gradle.launcher.daemon.configuration.DaemonParameters
+import org.gradle.launcher.daemon.registry.DaemonRegistry
+import org.gradle.launcher.daemon.registry.PersistentDaemonRegistry
+import org.gradle.logging.LoggingServiceRegistry
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DaemonClientServicesTest extends Specification {
+    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    final DaemonParameters parameters = new DaemonParameters(baseDir: tmp.testDir)
+    final DaemonClientServices services = new DaemonClientServices(LoggingServiceRegistry.newEmbeddableLogging(), parameters, System.in)
+
+    def "makes a DaemonRegistry available"() {
+        expect:
+        services.get(DaemonRegistry.class) instanceof PersistentDaemonRegistry
+    }
+
+    def "makes a DaemonConnector available"() {
+        expect:
+        services.get(DaemonConnector.class) instanceof DefaultDaemonConnector
+    }
+
+    def "makes a DaemonClient available"() {
+        expect:
+        services.get(DaemonClient) != null
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy
new file mode 100644
index 0000000..6c05271
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 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.daemon.client
+
+import org.gradle.api.specs.Spec
+import org.gradle.initialization.BuildClientMetaData
+import org.gradle.initialization.GradleLauncherAction
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.exec.BuildActionParameters
+import org.gradle.logging.internal.OutputEventListener
+import org.gradle.messaging.remote.internal.Connection
+import spock.lang.Specification
+import org.gradle.launcher.daemon.protocol.*
+
+class DaemonClientTest extends Specification {
+    final DaemonConnector connector = Mock()
+    final DaemonConnection daemonConnection = Mock()
+    final Connection<Object> connection = Mock()
+    final BuildClientMetaData metaData = Mock()
+    final OutputEventListener outputEventListener = Mock()
+    final Spec<DaemonContext> compatibilitySpec = Mock()
+    final DaemonClient client = new DaemonClient(connector, metaData, outputEventListener, compatibilitySpec, System.in)
+
+    def setup() {
+        daemonConnection.getConnection() >> connection
+    }
+
+    def stopsTheDaemonWhenRunning() {
+        when:
+        client.stop()
+
+        then:
+        2 * connector.maybeConnect(compatibilitySpec) >>> [daemonConnection, null]
+        1 * connection.dispatch({it instanceof Stop})
+        1 * connection.receive() >> new Success(null)
+        1 * connection.stop()
+        daemonConnection.getConnection() >> connection // why do I need this? Why doesn't the interaction specified in setup cover me?
+        0 * _
+    }
+
+    def stopsTheDaemonWhenNotRunning() {
+        when:
+        client.stop()
+
+        then:
+        1 * connector.maybeConnect(compatibilitySpec) >> null
+        0 * _
+    }
+
+    def "stops all compatible daemons"() {
+        when:
+        client.stop()
+
+        then:
+        3 * connector.maybeConnect(compatibilitySpec) >>> [daemonConnection, daemonConnection, null]
+        2 * connection.dispatch({it instanceof Stop})
+        2 * connection.receive() >> new Success(null)
+    }
+
+    def executesAction() {
+        when:
+        def result = client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+
+        then:
+        result == '[result]'
+        1 * connector.connect(compatibilitySpec) >> daemonConnection
+        1 * connection.dispatch({it instanceof Build})
+        2 * connection.receive() >>> [Mock(BuildStarted), new Success('[result]')]
+        1 * connection.stop()
+    }
+
+    def rethrowsFailureToExecuteAction() {
+        GradleLauncherAction<String> action = Mock()
+        BuildActionParameters parameters = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * connector.connect(compatibilitySpec) >> daemonConnection
+        1 * connection.dispatch({it instanceof Build})
+        2 * connection.receive() >>> [Mock(BuildStarted), new CommandFailure(failure)]
+        1 * connection.stop()
+    }
+    
+    def "tries to find a different daemon if getting the first result from the daemon fails"() {
+        when:
+        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+
+        then:
+        2 * connector.connect(compatibilitySpec) >> daemonConnection
+        connection.dispatch({it instanceof Build}) >> { throw new RuntimeException("Boo!")} >> { /* success */ }
+        2 * connection.receive() >>> [Mock(BuildStarted), new Success('')]
+    }
+
+    def "tries to find a different daemon if the daemon is busy"() {
+        when:
+        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+
+        then:
+        2 * connector.connect(compatibilitySpec) >> daemonConnection
+        connection.receive() >>> [Mock(DaemonBusy), Mock(BuildStarted), new Success('')]
+    }
+
+    def "tries to find a different daemon if the first result is null"() {
+        when:
+        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+
+        then:
+        3 * connector.connect(compatibilitySpec) >> daemonConnection
+        //first busy, then null, then build started...
+        connection.receive() >>> [Mock(DaemonBusy), null, Mock(BuildStarted), new Success('')]
+    }
+
+    def "does not loop forever finding usable daemons"() {
+        given:
+        connector.connect(compatibilitySpec) >> daemonConnection
+        connection.receive() >> Mock(DaemonBusy)
+        
+        when:
+        client.execute(Mock(GradleLauncherAction), Mock(BuildActionParameters))
+
+        then:
+        thrown(NoUsableDaemonFoundException)
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
new file mode 100644
index 0000000..bcc7e0f
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2011 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.daemon.client
+
+import org.gradle.api.specs.Spec
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.context.DefaultDaemonContext
+import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry
+import org.gradle.messaging.remote.Address
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.messaging.remote.internal.OutgoingConnector
+import spock.lang.Specification
+
+class DefaultDaemonConnectorTest extends Specification {
+
+    def javaHome = new File("tmp")
+    def connectTimeoutSecs = 1
+    def daemonCounter = 0
+
+    def createOutgoingConnector() {
+        new OutgoingConnector() {
+            Connection connect(Address address) {
+                def connection = [:] as Connection
+                // unsure why I can't add this as property in the map-mock above
+                connection.metaClass.num = address.num
+                connection
+            }
+        }
+    }
+
+    def createAddress(int i) {
+        new Address() {
+            int getNum() { i }
+
+            String getDisplayName() { getNum() }
+        }
+    }
+
+    def createConnector() {
+        def connector = new DefaultDaemonConnector(
+                new EmbeddedDaemonRegistry(),
+                createOutgoingConnector(),
+                { startBusyDaemon() } as DaemonStarter
+        )
+        connector.connectTimeout = connectTimeoutSecs * 1000
+        connector
+    }
+
+    def startBusyDaemon() {
+        def daemonNum = daemonCounter++
+        DaemonContext context = new DefaultDaemonContext(daemonNum.toString(), javaHome, javaHome, daemonNum, 1000, [])
+        def address = createAddress(daemonNum)
+        registry.store(address, context, "password")
+        registry.markBusy(address)
+        return daemonNum.toString()
+    }
+
+    def startIdleDaemon() {
+        def daemonNum = daemonCounter++
+        DaemonContext context = new DefaultDaemonContext(daemonNum.toString(), javaHome, javaHome, daemonNum, 1000, [])
+        def address = createAddress(daemonNum)
+        registry.store(address, context, "password")
+    }
+
+    def theConnector
+
+    def getConnector() {
+        if (theConnector == null) {
+            theConnector = createConnector()
+        }
+        theConnector
+    }
+
+    def getRegistry() {
+        connector.daemonRegistry
+    }
+
+    def getNumAllDaemons() {
+        registry.all.size()
+    }
+
+    def "maybeConnect() returns connection to any daemon that matches spec"() {
+        given:
+        startIdleDaemon()
+        startIdleDaemon()
+        
+        expect:
+        def connection = connector.maybeConnect({it.pid < 12} as Spec)
+        connection && connection.connection.num < 12
+    }
+
+    def "maybeConnect() returns null when no daemon matches spec"() {
+        given:
+        startIdleDaemon()
+        startIdleDaemon()
+
+        expect:
+        connector.maybeConnect({it.pid == 12} as Spec) == null
+    }
+
+    def "maybeConnect() ignores daemons that do not match spec"() {
+        given:
+        startIdleDaemon()
+        startIdleDaemon()
+
+        expect:
+        def connection = connector.maybeConnect({it.pid == 1} as Spec)
+        connection && connection.connection.num == 1
+    }
+
+    def "connect() returns connection to any existing daemon that matches spec"() {
+        given:
+        startIdleDaemon()
+        startIdleDaemon()
+
+        expect:
+        def connection = connector.connect({it.pid < 12} as Spec)
+        connection && connection.connection.num < 12
+
+        and:
+        numAllDaemons == 2
+    }
+
+    def "connect() starts a new daemon when no daemon matches spec"() {
+        given:
+        startIdleDaemon()
+
+        expect:
+        def connection = connector.connect({it.pid > 0} as Spec)
+        connection && connection.connection.num > 0
+
+        and:
+        numAllDaemons == 2
+    }
+
+    def "connect() will not use existing connection if it fails the compatibility spec"() {
+        given:
+        startIdleDaemon()
+
+        expect:
+        def connection = connector.connect({it.pid != 0} as Spec)
+        connection && connection.connection.num != 0
+
+        and:
+        numAllDaemons == 2
+    }
+
+    def "connect() will use daemon started by connector even if it fails compatibility spec"() {
+        when:
+        def connection = connector.connect({false} as Spec)
+        connection && connection.connection.num == 0
+
+        then:
+        numAllDaemons == 1
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
new file mode 100644
index 0000000..8786b61
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/CurrentProcessTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 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.daemon.configuration;
+
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.process.internal.JvmOptions
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+public class CurrentProcessTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final SetSystemProperties systemPropertiesSet = new SetSystemProperties()
+    private FileResolver fileResolver = Mock()
+    private def currentJavaHome = tmpDir.file('java_home')
+    private JvmOptions currentJvmOptions = new JvmOptions(fileResolver)
+    private DaemonParameters parameters = new DaemonParameters()
+
+    def "can only run build with identical java home"() {
+        when:
+        CurrentProcess currentProcess = new CurrentProcess(currentJavaHome, currentJvmOptions)
+
+        then:
+        currentProcess.configureForBuild(buildParameters(currentJavaHome))
+        !currentProcess.configureForBuild(buildParameters(tmpDir.file('other')))
+    }
+
+    def "can only run build when no immutable jvm arguments specified"() {
+        when:
+        currentJvmOptions.setAllJvmArgs(["-Xmx100m", "-XX:SomethingElse", "-Dfoo=bar", "-Dbaz"])
+        CurrentProcess currentProcess = new CurrentProcess(currentJavaHome, currentJvmOptions)
+
+        then:
+        currentProcess.configureForBuild(buildParameters([]))
+        currentProcess.configureForBuild(buildParameters(['-Dfoo=bar']))
+
+        and:
+        !currentProcess.configureForBuild(buildParameters(["-Xms10m"]))
+        !currentProcess.configureForBuild(buildParameters(["-XX:SomethingElse"]))
+        !currentProcess.configureForBuild(buildParameters(["-Xmx100m", "-XX:SomethingElse", "-Dfoo=bar", "-Dbaz"]))
+        !currentProcess.configureForBuild(buildParameters(['-Dfile.encoding=UTF8']))
+    }
+
+    def "sets all mutable system properties before running build"() {
+        when:
+        CurrentProcess currentProcess = new CurrentProcess(tmpDir.file('java_home'), currentJvmOptions)
+
+        then:
+        currentProcess.configureForBuild(buildParameters(["-Dfoo=bar", "-Dbaz"]))
+
+        and:
+        System.getProperty('foo') == 'bar'
+        System.getProperty('baz') != null
+    }
+
+    private DaemonParameters buildParameters(Iterable<String> jvmArgs) {
+        return buildParameters(currentJavaHome, jvmArgs)
+    }
+
+    private DaemonParameters buildParameters(File javaHome, Iterable<String> jvmArgs = []) {
+        parameters.setJavaHome(javaHome)
+        parameters.setJvmArgs(jvmArgs)
+        return parameters
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
new file mode 100644
index 0000000..e1c88e0
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2011 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.daemon.configuration
+
+import org.gradle.StartParameter
+import org.gradle.api.GradleException
+import org.gradle.internal.jvm.Jvm
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DaemonParametersTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    final DaemonParameters parameters = new DaemonParameters()
+
+    def "has reasonable default values"() {
+        expect:
+        !parameters.enabled
+        parameters.idleTimeout == DaemonParameters.DEFAULT_IDLE_TIMEOUT
+        def baseDir = new File(StartParameter.DEFAULT_GRADLE_USER_HOME, "daemon")
+        parameters.baseDir == baseDir
+        parameters.systemProperties.isEmpty()
+        // Not that reasonable
+        parameters.effectiveJvmArgs.containsAll(parameters.defaultJvmArgs)
+        parameters.effectiveJvmArgs.size() == parameters.defaultJvmArgs.size() + 1 // + 1 because effective JVM args contains -Dfile.encoding
+    }
+
+    def "determines base dir from user home dir"() {
+        def userHome = new File("some-dir")
+
+        when:
+        parameters.configureFromGradleUserHome(userHome)
+
+        then:
+        parameters.baseDir == new File(userHome, "daemon").canonicalFile
+    }
+
+    def "can configure base directory using system property"() {
+        when:
+        parameters.configureFromSystemProperties((DaemonParameters.BASE_DIR_SYS_PROPERTY): 'some-dir')
+
+        then:
+        parameters.baseDir == new File('some-dir').canonicalFile
+    }
+
+    def "can configure idle timeout using system property"() {
+        when:
+        parameters.configureFromSystemProperties((DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY): '4000')
+
+        then:
+        parameters.idleTimeout == 4000
+    }
+
+    def "nice message for invalid idle timeout"() {
+        when:
+        parameters.configureFromSystemProperties((DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY): 'asdf')
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.daemon.idletimeout'
+        ex.message.contains 'asdf'
+    }
+
+    def "uses default idle timeout if prop not set"() {
+        when:
+        parameters.configureFromSystemProperties(abc: 'def')
+
+        then:
+        parameters.idleTimeout == DaemonParameters.DEFAULT_IDLE_TIMEOUT
+    }
+
+    def "configuring jvmargs replaces the defaults"() {
+        when:
+        parameters.configureFrom([(DaemonParameters.JVM_ARGS_SYS_PROPERTY) : "-Xmx17m"])
+
+        then:
+        parameters.effectiveJvmArgs.each { assert !parameters.defaultJvmArgs.contains(it) }
+    }
+
+    def "can configure jvm args using system property"() {
+        when:
+        parameters.configureFromSystemProperties((DaemonParameters.JVM_ARGS_SYS_PROPERTY):  '-Xmx1024m -Dprop=value')
+
+        then:
+        parameters.effectiveJvmArgs.contains('-Xmx1024m')
+        !parameters.effectiveJvmArgs.contains('-Dprop=value')
+
+        parameters.systemProperties == [prop: 'value']
+    }
+
+    def "can configure jvm args using gradle.properties in root directory"() {
+        given:
+        tmpDir.createFile("settings.gradle")
+        tmpDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((DaemonParameters.JVM_ARGS_SYS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
+        }
+
+        when:
+        parameters.configureFromBuildDir(tmpDir.dir, true)
+
+        then:
+        parameters.effectiveJvmArgs.contains('-Xmx1024m')
+        !parameters.effectiveJvmArgs.contains('-Dprop=value')
+
+        parameters.systemProperties == [prop: 'value']
+    }
+
+    def "can configure idle timeout using gradle.properties in root directory"() {
+        given:
+        tmpDir.createFile("settings.gradle")
+        tmpDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY): '1450').store(outstr, "HEADER")
+        }
+
+        when:
+        parameters.configureFromBuildDir(tmpDir.dir, true)
+
+        then:
+        parameters.idleTimeout == 1450
+    }
+
+    def "can configure parameters using gradle.properties in user home directory"() {
+        given:
+        tmpDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((DaemonParameters.JVM_ARGS_SYS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
+        }
+
+        when:
+        parameters.configureFromGradleUserHome(tmpDir.dir)
+
+        then:
+        parameters.effectiveJvmArgs.contains('-Xmx1024m')
+        !parameters.effectiveJvmArgs.contains('-Dprop=value')
+
+        parameters.systemProperties == [prop: 'value']
+    }
+
+    def "can enable daemon using system property"() {
+        when:
+        parameters.configureFromSystemProperties((DaemonParameters.DAEMON_SYS_PROPERTY):  'true')
+
+        then:
+        parameters.enabled
+    }
+
+    def "can disable daemon using system property"() {
+        when:
+        parameters.configureFromSystemProperties((DaemonParameters.DAEMON_SYS_PROPERTY):  'no way')
+
+        then:
+        !parameters.enabled
+    }
+
+    def "can enable daemon using gradle.properties in root directory"() {
+        given:
+        tmpDir.createFile("settings.gradle")
+        tmpDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((DaemonParameters.DAEMON_SYS_PROPERTY): 'true').store(outstr, "HEADER")
+        }
+
+        when:
+        parameters.configureFromBuildDir(tmpDir.dir, true)
+
+        then:
+        parameters.enabled
+    }
+
+    def "can configure java home"() {
+        File jdk = Jvm.current().getJavaHome()
+
+        when:
+        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : jdk.toString()])
+
+        then:
+        parameters.effectiveJavaHome == jdk.canonicalFile
+    }
+
+    def "nice message for dummy java home"() {
+        when:
+        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : "/invalid/path"])
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.java.home'
+        ex.message.contains '/invalid/path'
+    }
+
+    def "nice message for invalid java home"() {
+        def dummyDir = tmpDir.createDir("foobar")
+        when:
+        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : dummyDir.absolutePath])
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.java.home'
+        ex.message.contains 'foobar'
+    }
+
+    def "supports 'empty' system properties"() {
+        when:
+        parameters.configureFrom([(DaemonParameters.JVM_ARGS_SYS_PROPERTY) : "-Dfoo= -Dbar"])
+
+        then:
+        parameters.getSystemProperties() == [foo: '', bar: '']
+    }
+
+    def "knows if not using default jvm args"() {
+        given:
+        assert parameters.usingDefaultJvmArgs
+
+        when:
+        parameters.setJvmArgs(["-Dfoo= -Dbar"])
+
+        then:
+        !parameters.usingDefaultJvmArgs
+    }
+    
+    def "knows if using default jvm args"() {
+        when:
+        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : Jvm.current().getJavaHome()])
+
+        then:
+        parameters.usingDefaultJvmArgs
+    }
+
+    def "knows if not using default jvm args when configured"() {
+        given:
+        assert parameters.usingDefaultJvmArgs
+
+        when:
+        parameters.configureFrom([(DaemonParameters.JVM_ARGS_SYS_PROPERTY) : "-Dfoo= -Dbar"])
+
+        then:
+        !parameters.usingDefaultJvmArgs
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
new file mode 100644
index 0000000..7daef13
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2011 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.daemon.context
+
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.util.ConfigureUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DaemonCompatibilitySpecSpec extends Specification {
+
+    @Rule TemporaryFolder tmp = new TemporaryFolder()
+
+    def clientConfigure = {}
+    def serverConfigure = {}
+
+    def client(Closure c) {
+        clientConfigure = c
+    }
+
+    def server(Closure c) {
+        serverConfigure = c
+    }
+
+    def createContext(Closure config) {
+        def builder = new DaemonContextBuilder({ 12L } as ProcessEnvironment)
+        builder.daemonRegistryDir = new File("dir")
+        ConfigureUtil.configure(config, builder).create()
+    }
+
+    def getClientContext() {
+        createContext(clientConfigure)
+    }
+
+    def getServerContext() {
+        createContext(serverConfigure)
+    }
+
+    boolean isCompatible() {
+        new DaemonCompatibilitySpec(clientContext).isSatisfiedBy(serverContext)
+    }
+
+    def "default contexts are compatible"() {
+        expect:
+        compatible
+    }
+
+    def "contexts with different java homes are incompatible"() {
+        client { javaHome = tmp.createDir("a") }
+        server { javaHome = tmp.createDir("b") }
+
+        expect:
+        !compatible
+    }
+
+    def "contexts with same daemon opts are compatible"() {
+        client { daemonOpts = ["-Xmx256m", "-Dfoo=foo"] }
+        server { daemonOpts = ["-Xmx256m", "-Dfoo=foo"] }
+
+        expect:
+        compatible
+    }
+
+    def "contexts with same daemon opts but different order are compatible"() {
+        client { daemonOpts = ["-Xmx256m", "-Dfoo=foo"] }
+        server { daemonOpts = ["-Dfoo=foo", "-Xmx256m"] }
+
+        expect:
+        compatible
+    }
+
+    def "contexts with different quantity of opts are not compatible"() {
+        client { daemonOpts = ["-Xmx256m", "-Dfoo=foo"] }
+        server { daemonOpts = ["-Xmx256m"] }
+
+        expect:
+        !compatible
+    }
+
+    def "contexts with different daemon opts are incompatible"() {
+        client { daemonOpts = ["-Xmx256m", "-Dfoo=foo"] }
+        server { daemonOpts = ["-Xmx256m", "-Dfoo=bar"] }
+
+        expect:
+        !compatible
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
new file mode 100644
index 0000000..a42f247
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.daemon.registry
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DaemonRegistryServicesTest extends Specification {
+    @Rule TemporaryFolder tmp = new TemporaryFolder()
+
+    def registry(baseDir) {
+        new DaemonRegistryServices(tmp.createDir(baseDir))
+    }
+
+    def "same daemon registry instance is used for same daemon registry file across service instances"() {
+        expect:
+        registry("a").get(DaemonRegistry).is(registry("a").get(DaemonRegistry))
+        !registry("a").get(DaemonRegistry).is(registry("b").get(DaemonRegistry))
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DomainRegistryUpdaterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DomainRegistryUpdaterTest.groovy
new file mode 100644
index 0000000..31e64cc
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DomainRegistryUpdaterTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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.daemon.registry
+
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.registry.DaemonRegistry.EmptyRegistryException
+import org.gradle.launcher.daemon.server.DomainRegistryUpdater
+import org.gradle.messaging.remote.Address
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 9/12/11
+ */
+public class DomainRegistryUpdaterTest extends Specification {
+
+    final DaemonRegistry registry = Mock()
+    final Address address = Mock()
+    final DaemonContext context = Mock()
+    final updater = new DomainRegistryUpdater(registry, context, "password", address)
+
+    def "marks idle"() {
+        when:
+        updater.onCompleteActivity()
+
+        then:
+        1 * registry.markIdle(address)
+    }
+
+    def "ignores empty cache on marking idle"() {
+        given:
+        1 * registry.markIdle(address) >> { throw new EmptyRegistryException("") }
+
+        when:
+        updater.onCompleteActivity()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "marks busy"() {
+        when:
+        updater.onStartActivity()
+
+        then:
+        1 * registry.markBusy(address)
+    }
+
+    def "ignores empty cache on marking busy"() {
+        given:
+        1 * registry.markBusy(address) >> { throw new EmptyRegistryException("") }
+
+        when:
+        updater.onStartActivity()
+
+        then:
+        noExceptionThrown()
+    }
+
+     def "ignores empty cache on stopping"() {
+        given:
+        1 * registry.remove(address) >> { throw new EmptyRegistryException("") }
+
+        when:
+        updater.onStop()
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistrySpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistrySpec.groovy
new file mode 100644
index 0000000..507ff79
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistrySpec.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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.daemon.registry
+
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.messaging.remote.Address
+import spock.lang.Specification
+
+class EmbeddedDaemonRegistrySpec extends Specification {
+
+    @Delegate EmbeddedDaemonRegistry registry = new EmbeddedDaemonRegistry()
+
+    def context = [:] as DaemonContext
+    
+    def address(value) {
+        [key: value] as Address
+    }
+
+    def "initially empty"() {
+        expect:
+        all.empty
+        idle.empty
+        busy.empty
+    }
+
+    def "lifecycle"() {
+        given:
+        store(address(10), context, "password")
+        store(address(20), context, "password")
+
+        expect:
+        all.size() == 2
+        idle.size() == 2
+        busy.empty
+
+        when:
+        markBusy(address(10))
+
+        then:
+        all.size() == 2
+        idle.size() == 1
+        busy.size() == 1
+
+        when:
+        markBusy(address(20))
+
+        then:
+        all.size() == 2
+        idle.empty
+        busy.size() == 2
+
+        when:
+        markIdle(address(10))
+        markIdle(address(20))
+
+        then:
+        all.size() == 2
+        idle.size() == 2
+        busy.empty
+
+        when:
+        remove(address(10))
+        remove(address(20))
+
+        then:
+        all.empty
+        idle.empty
+        busy.empty
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
new file mode 100644
index 0000000..844c446
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2011 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.daemon.server
+
+import org.gradle.BuildResult
+import org.gradle.GradleLauncher
+import org.gradle.configuration.GradleLauncherMetaData
+import org.gradle.initialization.DefaultGradleLauncherFactory
+import org.gradle.initialization.GradleLauncherAction
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.launcher.daemon.client.DaemonClient
+import org.gradle.launcher.daemon.client.EmbeddedDaemonClientServices
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.server.exec.DaemonCommandAction
+import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter
+import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter
+import org.gradle.launcher.daemon.server.exec.ForwardClientInput
+import org.gradle.launcher.exec.DefaultBuildActionParameters
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.messaging.concurrent.ExecutorFactory
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/21/11
+ */
+class DaemonServerExceptionHandlingTest extends Specification {
+
+    @Rule TemporaryFolder temp = new TemporaryFolder()
+    def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), 0, new HashMap(System.properties), [:], temp.dir)
+
+    static class DummyLauncherAction implements GradleLauncherAction, Serializable {
+        Object someState
+        Object getResult() { null }
+        BuildResult run(GradleLauncher launcher) { null }
+    }
+
+    def "sends back failure when the daemon cannot receive the first command"() {
+        given:
+        def client = new EmbeddedDaemonClientServices().get(DaemonClient)
+
+        def clz = new GroovyClassLoader().parseClass("class Foo implements Serializable {}")
+        def unloadableClass = clz.newInstance()
+        //the action contains some state that cannot be deserialized on the daemon side
+        //this a real-world scenario, the tooling api can ask the daemon to build model
+        //that does not exist with given daemon version
+        def action = new DummyLauncherAction(someState: unloadableClass)
+
+        when:
+        client.execute(action, parameters)
+
+        then:
+        def ex = thrown(Exception)
+        ex.message.contains("Unable to receive command from connection")
+    }
+
+    EmbeddedDaemonClientServices servicesWith(Closure configureDeamonActions) {
+        //we need to override some methods to inject a failure action into the sequence
+        def services = new EmbeddedDaemonClientServices() {
+            DaemonCommandExecuter createDaemonCommandExecuter() {
+                return new DefaultDaemonCommandExecuter(new DefaultGradleLauncherFactory(loggingServices), get(ExecutorFactory),
+                        get(ProcessEnvironment), loggingServices.getFactory(LoggingManagerInternal.class).create(), new File("dummy")) {
+                    List<DaemonCommandAction> createActions(DaemonContext daemonContext) {
+                        def actions = new LinkedList(super.createActions(daemonContext));
+                        configureDeamonActions(actions);
+                        return actions
+                    }
+                }
+            }
+        }
+        services
+    }
+
+    def "sends back any Throwable when daemon failure happens after listening for input"() {
+        given:
+        def services = servicesWith { daemonActions ->
+            def throwsError = { throw new Error("boo!") } as DaemonCommandAction
+            //we need to inject the failing action in an appropriate place in the sequence
+            //that is after the ForwardClientInput
+            daemonActions.add(daemonActions.findIndexOf { it instanceof ForwardClientInput } + 1, throwsError)
+        }
+
+        when:
+        services.get(DaemonClient).execute(new DummyLauncherAction(), parameters)
+
+        then:
+        def ex = thrown(Throwable)
+        ex.message.contains('boo!')
+    }
+
+    def "reports any Throwable that might happen before client receives the output"() {
+        given:
+        //we need to override some methods to inject a failure action into the sequence
+        def services = servicesWith { daemonActions ->
+            def oome = { throw new OutOfMemoryError("Buy more ram, dude!") } as DaemonCommandAction
+            daemonActions.add(0, oome)
+        }
+
+        when:
+        services.get(DaemonClient).execute(new DummyLauncherAction(), parameters)
+
+        then:
+        def ex = thrown(RuntimeException)
+        ex.cause.message.contains 'Buy more ram'
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
new file mode 100644
index 0000000..42a0d6a
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServicesTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.daemon.server
+
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.launcher.daemon.configuration.DefaultDaemonServerConfiguration
+import org.gradle.launcher.daemon.registry.DaemonDir
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.logging.LoggingServiceRegistry
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import static java.util.Arrays.asList
+
+class DaemonServicesTest extends Specification {
+
+    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    final DaemonServices services = new DaemonServices(new DefaultDaemonServerConfiguration("uid", tmp.testDir, 100, asList()),
+            LoggingServiceRegistry.newEmbeddableLogging(), Mock(LoggingManagerInternal))
+
+    def "makes a DaemonDir available"() {
+        expect:
+        services.get(DaemonDir.class) != null
+    }
+
+    def "makes a ProcessEnvironment available"() {
+        expect:
+        services.get(ProcessEnvironment.class) != null
+    }
+
+    def "makes a Daemon available"() {
+        expect:
+        services.get(Daemon.class) != null
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonStateCoordinatorTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonStateCoordinatorTest.groovy
new file mode 100644
index 0000000..496e2f5
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonStateCoordinatorTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * 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.launcher.daemon.server
+
+import java.util.concurrent.locks.Condition
+import spock.lang.Specification
+import org.gradle.launcher.daemon.server.exec.DaemonCommandExecution
+
+/**
+ * by Szczepan Faber, created at: 2/6/12
+ */
+class DaemonStateCoordinatorTest extends Specification {
+
+    def coordinator = new DaemonStateCoordinator(Mock(Runnable), Mock(Runnable), Mock(Runnable), Mock(Runnable), Mock(Runnable))
+
+    def "requesting stop lifecycle"() {
+        coordinator.asyncStop = Mock(Runnable)
+
+        expect:
+        !coordinator.stopped
+
+        when: "requested first time"
+        def passOne = coordinator.requestStop()
+
+        then: "retruns true and schedules stopping"
+        passOne == true
+        1 * coordinator.asyncStop.run()
+        1 * coordinator.onStopRequested.run()
+        coordinator.stoppingOrStopped
+        !coordinator.stopped
+
+        when: "requested again"
+        def passTwo = coordinator.requestStop()
+
+        then: "only returns false"
+        passTwo == false
+        0 * coordinator.asyncStop.run()
+        0 * coordinator.onStopRequested.run()
+        coordinator.stoppingOrStopped
+        !coordinator.stopped
+    }
+
+    def "stopping lifecycle"() {
+        coordinator.condition = Mock(Condition)
+
+        expect:
+        !coordinator.stopped
+
+        when: "stopped first time"
+        coordinator.stop()
+
+        then: "stops"
+        1 * coordinator.onStop.run()
+        1 * coordinator.onStopRequested.run()
+        1 * coordinator.condition.signalAll()
+        coordinator.stopped
+
+        when: "requested again"
+        coordinator.stop()
+
+        then:
+        0 * coordinator.onStopRequested.run()
+        1 * coordinator.onStop.run()
+        1 * coordinator.condition.signalAll()
+        coordinator.stopped
+    }
+
+    def "stopAsSoonAsIdle when idle"() {
+        given:
+        coordinator.start()
+
+        expect:
+        coordinator.idle
+
+        when:
+        coordinator.stopAsSoonAsIdle()
+
+        then:
+        coordinator.stopped
+        coordinator.stoppingOrStopped
+
+        and:
+        1 * coordinator.onStopRequested.run()
+        1 * coordinator.onStop.run()
+    }
+
+    def "stopAsSoonAsIdle when busy"() {
+        given:
+        coordinator.start()
+        coordinator.onStartCommand(Mock(DaemonCommandExecution))
+
+        expect:
+        coordinator.busy
+
+        when:
+        coordinator.stopAsSoonAsIdle()
+
+        then:
+        !coordinator.stopped
+        coordinator.stoppingOrStopped
+
+        and:
+        1 * coordinator.onStopRequested.run()
+
+        when:
+        coordinator.onFinishCommand()
+
+        then:
+        coordinator.stopped
+        1 * coordinator.onStop.run()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/StopDispatcherTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/StopDispatcherTest.groovy
new file mode 100644
index 0000000..1d15c1b
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/StopDispatcherTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.daemon.server
+
+import org.gradle.initialization.BuildClientMetaData
+import org.gradle.messaging.remote.internal.Connection
+import spock.lang.Specification
+import org.gradle.launcher.daemon.client.StopDispatcher
+
+/**
+ * @author: Szczepan Faber, created at: 9/13/11
+ */
+public class StopDispatcherTest extends Specification {
+
+    def dispatcher = new StopDispatcher()
+    def connection = Mock(Connection)
+    def meta = Mock(BuildClientMetaData)
+
+    def "ignores failed dispatch and does not receive"() {
+        given:
+        connection.dispatch(_) >> { throw new RuntimeException("Cannot dispatch") }
+
+        when:
+        dispatcher.dispatch(meta, connection)
+
+        then:
+        0 * connection.receive()
+        noExceptionThrown()
+    }
+
+    def "ignores failed receive"() {
+        given:
+        connection.receive() >> { throw new RuntimeException("Cannot dispatch") }
+
+        when:
+        dispatcher.dispatch(meta, connection)
+
+        then:
+        1 * connection.dispatch(_)
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
new file mode 100644
index 0000000..724a0e1
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.exec;
+
+
+import org.gradle.configuration.GradleLauncherMetaData
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 9/6/11
+ */
+public class DefaultBuildActionParametersTest extends Specification {
+
+    def "is serializable"() {
+        given:
+        def params = new DefaultBuildActionParameters(new GradleLauncherMetaData(), System.currentTimeMillis(), System.properties, System.getenv(), new File("."))
+        ObjectOutputStream out = new ObjectOutputStream(new ByteArrayOutputStream());
+
+        when:
+        out.writeObject(params);
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/EntryPointTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/EntryPointTest.groovy
new file mode 100644
index 0000000..8058ff3
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/EntryPointTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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.exec
+
+import spock.lang.Specification
+import org.gradle.api.Action
+
+class EntryPointTest extends Specification {
+    final Action<ExecutionListener> action = Mock()
+    final ProcessCompleter completer = Mock()
+    final EntryPoint entryPoint = new EntryPoint() {
+        @Override
+        protected ExecutionCompleter createCompleter() {
+            return completer
+        }
+
+        @Override
+        protected void doAction(ExecutionListener listener) {
+            action.execute(listener)
+        }
+    }
+
+    def "exits with success when action executes successfully"() {
+        when:
+        entryPoint.run()
+
+        then:
+        1 * action.execute(!null)
+        1 * completer.complete()
+        0 * _._
+    }
+
+    def "exits with failure when action reports a failure"() {
+        def failure = new RuntimeException()
+
+        when:
+        entryPoint.run()
+
+        then:
+        1 * action.execute(!null) >> { ExecutionListener listener -> listener.onFailure(failure) }
+        1 * completer.completeWithFailure(failure)
+        0 * _._
+    }
+
+    def "exits with failure when action throws exception"() {
+        def failure = new RuntimeException()
+
+        when:
+        entryPoint.run()
+
+        then:
+        1 * action.execute(!null) >> { throw failure }
+        1 * completer.completeWithFailure(failure)
+        0 * _._
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ExceptionReportingActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ExceptionReportingActionTest.groovy
new file mode 100644
index 0000000..7229ac3
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ExceptionReportingActionTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 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.exec
+
+import org.gradle.api.Action
+import spock.lang.Specification
+
+class ExceptionReportingActionTest extends Specification {
+    final Action<ExecutionListener> target = Mock()
+    final ExecutionListener listener = Mock()
+    final Action<Throwable> reporter = Mock()
+    final ExceptionReportingAction action = new ExceptionReportingAction(target, reporter)
+
+    def executesAction() {
+        when:
+        action.execute(listener)
+
+        then:
+        1 * target.execute(listener)
+        0 * _._
+    }
+
+    def reportsExceptionThrownByAction() {
+        def failure = new RuntimeException()
+
+        when:
+        action.execute(listener)
+
+        then:
+        1 * target.execute(listener) >> { throw failure }
+        1 * reporter.execute(failure)
+        1 * listener.onFailure(failure)
+        0 * _._
+    }
+
+    def doesNotReportAlreadyReportedExceptionThrownByAction() {
+        def cause = new RuntimeException()
+        def failure = new ReportedException(cause)
+
+        when:
+        action.execute(listener)
+
+        then:
+        1 * target.execute(listener) >> { throw failure }
+        1 * listener.onFailure(cause)
+        0 * _._
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
new file mode 100644
index 0000000..08023de
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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.tooling.internal.provider
+
+import org.gradle.StartParameter
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/6/12
+ */
+class ConfiguringBuildActionTest extends Specification {
+    @Rule TemporaryFolder temp
+
+    def "allows configuring the start parameter with build arguments"() {
+        when:
+        def action = new ConfiguringBuildAction(arguments: ['-PextraProperty=foo', '-m'])
+        def start = action.configureStartParameter()
+
+        then:
+        start.projectProperties['extraProperty'] == 'foo'
+        start.dryRun
+    }
+
+    def "can overwrite project dir via build arguments"() {
+        given:
+        def projectDir = temp.createDir('projectDir')
+
+        when:
+        def action = new ConfiguringBuildAction(projectDirectory: projectDir, arguments: ['-p', 'otherDir'])
+        def start = action.configureStartParameter()
+
+        then:
+        start.projectDir == new File(projectDir, "otherDir")
+    }
+
+    def "can overwrite gradle user home via build arguments"() {
+        given:
+        def dotGradle = temp.createDir('.gradle')
+        def projectDir = temp.createDir('projectDir')
+
+        when:
+        def action = new ConfiguringBuildAction(gradleUserHomeDir: dotGradle, projectDirectory: projectDir, 
+                arguments: ['-g', 'otherDir'])
+        def start = action.configureStartParameter()
+
+        then:
+        start.gradleUserHomeDir == new File(projectDir, "otherDir")
+    }
+
+    def "can overwrite searchUpwards via build arguments"() {
+        when:
+        def action = new ConfiguringBuildAction(arguments: ['-u'])
+        def start = action.configureStartParameter()
+
+        then:
+        !start.isSearchUpwards()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy
index ecfbcfa..dab3a9d 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuterTest.groovy
@@ -15,18 +15,20 @@
  */
 package org.gradle.tooling.internal.provider
 
-import org.gradle.launcher.DaemonClient
-import spock.lang.Specification
-import org.gradle.launcher.ReportedException
-import org.gradle.tooling.internal.protocol.BuildExceptionVersion1
 import org.gradle.initialization.GradleLauncherAction
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1
+import org.gradle.launcher.daemon.client.DaemonClient
+import org.gradle.launcher.daemon.configuration.DaemonParameters
+import org.gradle.launcher.exec.ReportedException
+import org.gradle.tooling.internal.protocol.BuildExceptionVersion1
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters
+import spock.lang.Specification
 
 class DaemonGradleLauncherActionExecuterTest extends Specification {
     final DaemonClient client = Mock()
     final GradleLauncherAction<String> action = Mock()
-    final BuildOperationParametersVersion1 parameters = Mock()
-    final DaemonGradleLauncherActionExecuter executer = new DaemonGradleLauncherActionExecuter(client)
+    final ProviderOperationParameters parameters = Mock()
+    final DaemonParameters daemonParameters = Mock()
+    final DaemonGradleLauncherActionExecuter executer = new DaemonGradleLauncherActionExecuter(client, daemonParameters)
 
     def unpacksReportedException() {
         def failure = new RuntimeException()
@@ -38,5 +40,6 @@ class DaemonGradleLauncherActionExecuterTest extends Specification {
         BuildExceptionVersion1 e = thrown()
         e.cause == failure
         1 * client.execute(action, !null) >> { throw new ReportedException(failure) }
+        _ * daemonParameters.effectiveSystemProperties >> [:]
     }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy
index 22d0ca1..d4c1c4f 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy
@@ -19,13 +19,14 @@ import org.gradle.BuildResult
 import org.gradle.GradleLauncher
 import org.gradle.initialization.GradleLauncherAction
 import org.gradle.initialization.GradleLauncherFactory
-import org.gradle.launcher.InitializationAware
+import org.gradle.launcher.exec.InitializationAware
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters
 import spock.lang.Specification
+import org.gradle.StartParameter
 
 class EmbeddedGradleLauncherActionExecuterTest extends Specification {
-    final BuildOperationParametersVersion1 parameters = Mock()
+    final ProviderOperationParameters parameters = Mock()
     final GradleLauncherFactory gradleLauncherFactory = Mock()
     final GradleLauncher gradleLauncher = Mock()
     final BuildResult buildResult = Mock()
@@ -46,14 +47,15 @@ class EmbeddedGradleLauncherActionExecuterTest extends Specification {
 
     def actionCanConfigureStartParameters() {
         TestAction action = Mock()
+        def StartParameter startParameter = new StartParameter()
 
         when:
         def result = executer.execute(action, parameters)
 
         then:
         result == 'result'
-        1 * gradleLauncherFactory.newInstance(!null) >> gradleLauncher
-        1 * action.configureStartParameter(!null)
+        1 * action.configureStartParameter() >> startParameter
+        1 * gradleLauncherFactory.newInstance(startParameter) >> gradleLauncher
         1 * action.run(gradleLauncher) >> buildResult
         1 * action.result >> 'result'
     }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy
index ea73457..b960551 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ExecuteBuildActionTest.groovy
@@ -15,24 +15,12 @@
  */
 package org.gradle.tooling.internal.provider
 
-import spock.lang.Specification
-import org.gradle.StartParameter
-import org.gradle.GradleLauncher
 import org.gradle.BuildResult
+import org.gradle.GradleLauncher
+import spock.lang.Specification
 
 class ExecuteBuildActionTest extends Specification {
-    final ExecuteBuildAction action = new ExecuteBuildAction(['a', 'b'])
-
-    def setsTaskNamesOnStartParameter() {
-        StartParameter startParameter = Mock()
-
-        when:
-        action.configureStartParameter(startParameter)
-
-        then:
-        1 * startParameter.setTaskNames(['a', 'b'])
-        0 * _._
-    }
+    final ExecuteBuildAction action = new ExecuteBuildAction()
 
     def runsBuild() {
         GradleLauncher launcher = Mock()
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
index 5938aa4..e26ba45 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
@@ -15,20 +15,23 @@
  */
 package org.gradle.tooling.internal.provider
 
-import spock.lang.Specification
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1
-import org.gradle.launcher.GradleLauncherActionExecuter
-import org.gradle.logging.LoggingManagerInternal
+import org.gradle.api.logging.LogLevel
 import org.gradle.initialization.GradleLauncherAction
-import org.gradle.api.internal.Factory
+import org.gradle.internal.Factory
+import org.gradle.launcher.exec.GradleLauncherActionExecuter
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.tooling.internal.provider.input.ProviderOperationParameters
+import spock.lang.Specification
 
 class LoggingBridgingGradleLauncherActionExecuterTest extends Specification {
-    final GradleLauncherActionExecuter<BuildOperationParametersVersion1> target = Mock()
+    final GradleLauncherActionExecuter<ProviderOperationParameters> target = Mock()
     final Factory<LoggingManagerInternal> loggingManagerFactory = Mock()
     final LoggingManagerInternal loggingManager = Mock()
     final GradleLauncherAction<String> action = Mock()
-    final BuildOperationParametersVersion1 parameters = Mock()
-    final LoggingBridgingGradleLauncherActionExecuter executer = new LoggingBridgingGradleLauncherActionExecuter(target, loggingManagerFactory)
+    final ProviderOperationParameters parameters = Mock()
+
+    //declared type-lessly to work around groovy eclipse plugin bug
+    final executer = new LoggingBridgingGradleLauncherActionExecuter(target, loggingManagerFactory)
 
     def configuresLoggingWhileActionIsExecuting() {
         when:
@@ -56,4 +59,16 @@ class LoggingBridgingGradleLauncherActionExecuterTest extends Specification {
         1 * target.execute(action, parameters) >> {throw failure}
         1 * loggingManager.stop()
     }
+
+    def "sets log level accordingly"() {
+        given:
+        loggingManagerFactory.create() >> loggingManager
+
+        when:
+        parameters.getBuildLogLevel() >> LogLevel.QUIET
+        executer.execute(action, parameters)
+        
+        then:
+        1 * loggingManager.setLevel(LogLevel.QUIET)
+    }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/input/AdaptedOperationParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/input/AdaptedOperationParametersTest.groovy
new file mode 100644
index 0000000..7961c90
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/input/AdaptedOperationParametersTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * 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.tooling.internal.provider.input
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/6/12
+ */
+class AdaptedOperationParametersTest extends Specification {
+
+    interface BuildOperationParametersStub extends BuildOperationParametersVersion1 {
+        List<String> getArguments()
+    } 
+    
+    def delegate = Mock(BuildOperationParametersStub)
+    def params = new AdaptedOperationParameters(delegate)
+
+    def "configures build log level to debug if verbose logging requested"() {
+        given:
+        delegate.getVerboseLogging() >> true
+
+        when:
+        def level = params.getBuildLogLevel()
+
+        then:
+        level == LogLevel.DEBUG
+    }
+
+    def "uses log level from the arguments if verbose logging not configured"() {
+        given:
+        delegate.getArguments() >> ['--info']
+        delegate.getVerboseLogging() >> false
+
+        when:
+        def level = params.getBuildLogLevel()
+
+        then:
+        level == LogLevel.INFO
+    }
+
+    def "uses lifecycle log level if verbose logging not configured"() {
+        given:
+        delegate.getArguments() >> []
+        delegate.getVerboseLogging() >> false
+
+        when:
+        def level = params.getBuildLogLevel()
+
+        then:
+        //depends on implementation of the CommandLineConverter and the global default
+        //but if feels important to validate it
+        level == LogLevel.LIFECYCLE
+    }
+}
diff --git a/subprojects/maven/maven.gradle b/subprojects/maven/maven.gradle
index e853ad2..4a8fa81 100644
--- a/subprojects/maven/maven.gradle
+++ b/subprojects/maven/maven.gradle
@@ -13,13 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
+    compile project(':coreImpl')
     compile project(':plugins')
     compile libraries.slf4j_api
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    compile libraries.maven_ant_tasks
+    compile "org.sonatype.pmaven:pmaven-common:0.8-20100325 at jar"
+    compile "org.sonatype.pmaven:pmaven-groovy:0.8-20100325 at jar"
+    compile "org.codehaus.plexus:plexus-component-annotations:1.5.2 at jar"
 }
+
+useTestFixtures()
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployment.java
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
new file mode 100644
index 0000000..4b94173
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
@@ -0,0 +1,225 @@
+/*
+ * 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.artifacts.maven;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.ConfigurationContainer;
+
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Is used for generating a Maven pom file and customizing the generation.
+ * To learn about the Maven pom see: <a href="http://maven.apache.org/pom.html">http://maven.apache.org/pom.html</a>
+ *
+ * @author Hans Dockter
+ */
+public interface MavenPom {
+    /**
+     * Returns the scope mappings used for generating this pom.
+     */
+    Conf2ScopeMappingContainer getScopeMappings();
+
+    /**
+     * Provides a builder for the Maven pom for adding or modifying properties of the Maven {@link #getModel()}.
+     * The syntax is exactly the same as used by polyglot Maven. For example:
+     *
+     * <pre>
+     * pom.project {
+     *    inceptionYear '2008'
+     *    licenses {
+     *       license {
+     *          name 'The Apache Software License, Version 2.0'
+     *          url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+     *          distribution 'repo'
+     *       }
+     *    }
+     * }
+     * </pre>
+     *
+     * @param pom
+     * @return this
+     */
+    MavenPom project(Closure pom);
+
+    /**
+     * Returns the group id for this POM.
+     *
+     * @see org.apache.maven.model.Model#setGroupId(String)
+     */
+    String getGroupId();
+
+    /**
+     * Sets the group id for this POM.
+     *
+     * @see org.apache.maven.model.Model#getGroupId
+     * @return this
+     */
+    MavenPom setGroupId(String groupId);
+
+    /**
+     * Returns the artifact id for this POM.
+     * 
+     * @see org.apache.maven.model.Model#getArtifactId()
+     */
+    String getArtifactId();
+
+    /**
+     * Sets the artifact id for this POM.
+     *
+     * @see org.apache.maven.model.Model#setArtifactId(String)
+     * @return this
+     */
+    MavenPom setArtifactId(String artifactId);
+
+    /**
+     * Returns the version for this POM.
+     *
+     * @see org.apache.maven.model.Model#getVersion()
+     */
+    String getVersion();
+
+    /**
+     * Sets the version for this POM.
+     *
+     * @see org.apache.maven.model.Model#setVersion(String)
+     * @return this
+     */
+    MavenPom setVersion(String version);
+
+    /**
+     * Returns the packaging for this POM.
+     *
+     * @see org.apache.maven.model.Model#getPackaging()
+     */
+    String getPackaging();
+
+    /**
+     * Sets the packaging for this POM.
+     *
+     * @see org.apache.maven.model.Model#setPackaging(String)
+     * @return this
+     */
+    MavenPom setPackaging(String packaging);
+
+    /**
+     * Sets the dependencies for this POM.
+     *
+     * @see org.apache.maven.model.Model#setDependencies(java.util.List)
+     * @return this
+     */
+    MavenPom setDependencies(List<?> dependencies);
+
+    /**
+     * Returns the dependencies for this POM.
+     * 
+     * @see org.apache.maven.model.Model#getDependencies()
+     */
+    List<?> getDependencies();
+
+    /**
+     * Returns the underlying native Maven {@link org.apache.maven.model.Model} object. The MavenPom object
+     * delegates all the configuration information to this object. There Gradle MavenPom objects provides
+     * delegation methods just for setting the groupId, artifactId, version and packaging. For all other
+     * elements, either use the model object or {@link #project(groovy.lang.Closure)}.
+     *
+     * @return the underlying native Maven object
+     */
+    Object getModel();
+
+    /**
+     * Sets the underlying native Maven {@link org.apache.maven.model.Model} object.
+     *
+     * @param model
+     * @return this
+     * @see #getModel() 
+     */
+    MavenPom setModel(Object model);
+
+    /**
+     * Writes the {@link #getEffectivePom()} xml to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions.
+     *
+     * @param writer The writer to write the pom xml.
+     * @return this
+     */
+    MavenPom writeTo(Writer writer);
+
+    /**
+     * Writes the {@link #getEffectivePom()} xml to a file while applying the {@link #withXml(org.gradle.api.Action)} actions.
+     * The path is resolved as defined by {@link org.gradle.api.Project#files(Object...)}
+     * The file will be encoded as UTF-8.
+     *
+     * @param path The path of the file to write the pom xml into.
+     * @return this
+     */
+    MavenPom writeTo(Object path);
+
+    /**
+     * <p>Adds a closure to be called when the pom has been configured. The pom is passed to the closure as a
+     * parameter.</p>
+     *
+     * @param closure The closure to execute when the pom has been configured.
+     * @return this
+     */
+    MavenPom whenConfigured(Closure closure);
+
+    /**
+     * <p>Adds an action to be called when the pom has been configured. The pom is passed to the action as a
+     * parameter.</p>
+     *
+     * @param action The action to execute when the pom has been configured.
+     * @return this
+     */
+    MavenPom whenConfigured(Action<MavenPom> action);
+
+    /**
+     * <p>Adds a closure to be called when the POM XML has been created. The XML is passed to the closure as a
+     * parameter in form of a {@link org.gradle.api.XmlProvider}. The action can modify the XML.</p>
+     *
+     * @param closure The closure to execute when the POM XML has been created.
+     * @return this
+     */
+    MavenPom withXml(Closure closure);
+
+    /**
+     * <p>Adds an action to be called when the POM XML has been created. The XML is passed to the action as a
+     * parameter in form of a {@link org.gradle.api.XmlProvider}. The action can modify the XML.</p>
+     *
+     * @param action The action to execute when the POM XML has been created.
+     * @return this
+     */
+    MavenPom withXml(Action<XmlProvider> action);
+
+    /**
+     * Returns the configuration container used for mapping configurations to maven scopes.
+     */
+    ConfigurationContainer getConfigurations();
+
+    /**
+     * Sets the configuration container used for mapping configurations to maven scopes.
+     * @return this
+     */
+    MavenPom setConfigurations(ConfigurationContainer configurations);
+
+    /**
+     * Returns a pom with the generated dependencies and the {@link #whenConfigured(org.gradle.api.Action)} actions applied.
+     *
+     * @return the effective pom
+     */
+    MavenPom getEffectivePom();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
new file mode 100644
index 0000000..0cd890a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
@@ -0,0 +1,51 @@
+/*
+ * 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.artifacts.maven;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+
+/**
+ * An {@link ArtifactRepository} which can be used to publish artifacts to Maven repositories.
+ *
+ * @author Hans Dockter
+ */
+public interface MavenResolver extends ArtifactRepository, 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.
+     */
+    Object 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/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
new file mode 100644
index 0000000..263e57e
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
@@ -0,0 +1,136 @@
+/*
+ * 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.artifacts.maven;
+
+import groovy.lang.Closure;
+import org.gradle.api.publication.maven.internal.PomFilter;
+
+/**
+ * Manages a set of {@link MavenPom} instances and their associated {@link PublishFilter} instances.
+ *
+ * @author Hans Dockter
+ */
+public interface PomFilterContainer {
+    String DEFAULT_ARTIFACT_POM_NAME = "default";
+
+    /**
+     * Returns the default filter being used. .
+     *
+     * @see #setFilter(org.gradle.api.artifacts.maven.PublishFilter)
+     */
+    PublishFilter getFilter();
+
+    /**
+     * <p>Sets the default filter to be used. This filter is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
+     * If at least one custom filter has been added the default filter is not used any longer.</p>
+     * <p>The default for this property is {@link PublishFilter#ALWAYS_ACCEPT}.
+     * If there is only one artifact you are fine with this filter. If there is more than one artifact, deployment will lead to
+     * an exception, if you don't specify a filter that selects the artifact to deploy. If you want to deploy more than one artiact you have
+     * to use the (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)} method.</p>
+     *
+     * @param defaultFilter
+     * @see #getFilter()
+     */
+    void setFilter(PublishFilter defaultFilter);
+
+    /**
+     * Returns the pom property of the custom filter.
+     * The pom property can be used to customize the pom generation. By default the properties of such a pom object are all null.
+     * Null means that Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
+     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use those instead.
+     *
+     * @return The Maven Pom
+     */
+    MavenPom getPom();
+
+    /**
+     * <p>Sets the default pom to be used. This pom is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
+     * If at least one custom filter has been added the default pom is not used any longer.</p>
+     * <p>Usually you don't need to set this property as the default value provides you a pom object you might use for configuration.
+     * By default the properties of such a pom object are all null.
+     * If they are null, Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
+     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use this instead.</p>
+     *
+     * @param defaultPom
+     */
+    void setPom(MavenPom defaultPom);
+
+    /**
+     * If you want to deploy more than one artifact you need to define filters to select each of those artifacts. The method
+     * returns a pom object associated with this filter, that allows you to customize the pom generation for the artifact selected
+     * by the filter.
+     *
+     * @param name The name of the filter
+     * @param publishFilter The filter to use
+     * @return The pom associated with the filter
+     */
+    MavenPom addFilter(String name, PublishFilter publishFilter);
+
+    /**
+     * Adds a publish filter.
+     *
+     * @param name   The name of the filter
+     * @param filter The filter
+     * @return The Maven pom associated with the closure
+     * @see PublishFilter
+     * @see PomFilterContainer#addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)
+     */
+    MavenPom addFilter(String name, Closure filter);
+
+    /**
+     * Returns a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
+     *
+     * @param name The name of the filter
+     */
+    PublishFilter filter(String name);
+
+    /**
+     * Sets the default publish filter.
+     *
+     * @param filter The filter to be set
+     * @see PublishFilter
+     * @see PomFilterContainer#setFilter(org.gradle.api.artifacts.maven.PublishFilter)
+     */
+    void filter(Closure filter);
+
+    /**
+     * Returns the pom associated with a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
+     *
+     * @param name The name of the filter.
+     */
+    MavenPom pom(String name);
+
+    /**
+     * Configures a pom by a closure. The closure statements are delegated to the pom object associated with the given name.
+     *
+     * @param name
+     * @param configureClosure
+     * @return The pom object associated with the given name.
+     * @see PomFilterContainer#pom(String)
+     */
+    MavenPom pom(String name, Closure configureClosure);
+
+    /**
+     * Configures the default pom by a closure. The closure statements are delegated to the default pom.
+     *
+     * @param configureClosure
+     * @return The default pom.
+     * @see PomFilterContainer#getPom()
+     */
+    MavenPom pom(Closure configureClosure);
+
+    Iterable<PomFilter> getActivePomFilters();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java b/subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java
rename to subprojects/maven/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
index de81654..e186f11 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
@@ -21,17 +21,17 @@ import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.ResolverContainer;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.publication.maven.internal.DefaultDeployerFactory;
+import org.gradle.api.publication.maven.internal.DefaultMavenFactory;
+import org.gradle.api.publication.maven.internal.DefaultMavenRepositoryHandlerConvention;
+import org.gradle.api.publication.maven.internal.MavenFactory;
 import org.gradle.api.tasks.Upload;
-import org.gradle.util.GUtil;
-import org.gradle.util.WrapUtil;
-
-import java.util.Map;
+import org.gradle.logging.LoggingManagerInternal;
 
 /**
  * <p>A {@link org.gradle.api.Plugin} which allows project artifacts to be deployed to a Maven repository, or installed
@@ -39,7 +39,7 @@ import java.util.Map;
  *
  * @author Hans Dockter
  */
-public class MavenPlugin implements Plugin<Project> {
+public class MavenPlugin implements Plugin<ProjectInternal> {
     public static final int COMPILE_PRIORITY = 300;
     public static final int RUNTIME_PRIORITY = 200;
     public static final int TEST_COMPILE_PRIORITY = 150;
@@ -50,78 +50,72 @@ public class MavenPlugin implements Plugin<Project> {
 
     public static final String INSTALL_TASK_NAME = "install";
 
-    public void apply(final Project project) {
-        setConventionMapping(project);
-        addConventionObject(project);
+    public void apply(final ProjectInternal project) {
+        project.getPlugins().apply(BasePlugin.class);
+
+        DefaultMavenFactory mavenFactory = new DefaultMavenFactory();
+        final MavenPluginConvention pluginConvention = addConventionObject(project, mavenFactory);
+        final DefaultDeployerFactory deployerFactory = new DefaultDeployerFactory(
+                mavenFactory,
+                project.getServices().getFactory(LoggingManagerInternal.class),
+                project.getFileResolver(),
+                pluginConvention,
+                project.getConfigurations(),
+                pluginConvention.getConf2ScopeMappings());
+
+        project.getTasks().withType(Upload.class, new Action<Upload>() {
+            public void execute(Upload upload) {
+                RepositoryHandler repositories = upload.getRepositories();
+                DefaultRepositoryHandler handler = (DefaultRepositoryHandler) repositories;
+                DefaultMavenRepositoryHandlerConvention repositoryConvention = new DefaultMavenRepositoryHandlerConvention(handler, deployerFactory);
+                new DslObject(repositories).getConvention().getPlugins().put("maven", repositoryConvention);
+            }
+        });
         PluginContainer plugins = project.getPlugins();
         plugins.withType(JavaPlugin.class, new Action<JavaPlugin>() {
             public void execute(JavaPlugin javaPlugin) {
-                configureJavaScopeMappings(project.getRepositories(), project.getConfigurations());
+                configureJavaScopeMappings(project.getConfigurations(), pluginConvention.getConf2ScopeMappings());
                 configureInstall(project);
             }
         });
         plugins.withType(WarPlugin.class, new Action<WarPlugin>() {
             public void execute(WarPlugin warPlugin) {
-                configureWarScopeMappings(project.getRepositories(), project.getConfigurations());
+                configureWarScopeMappings(project.getConfigurations(), pluginConvention.getConf2ScopeMappings());
             }
         });
     }
 
-    private void setConventionMapping(final Project project) {
-        Map mapping = GUtil.map(
-                "mavenPomDir", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return convention.getPlugin(MavenPluginConvention.class).getPomDir();
-                    }
-                },
-                "configurationContainer", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return project.getConfigurations();
-                    }
-                },
-                "fileResolver", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return ((ProjectInternal) project).getFileResolver();
-                    }
-                },
-                "mavenScopeMappings", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return convention.getPlugin(MavenPluginConvention.class).getConf2ScopeMappings();
-                    }
-                });
-        ((IConventionAware) project.getRepositories()).getConventionMapping().map(mapping);
-    }
-
-    private void addConventionObject(Project project) {
-        MavenPluginConvention mavenConvention = new MavenPluginConvention((ProjectInternal) project);
+    private MavenPluginConvention addConventionObject(ProjectInternal project, MavenFactory mavenFactory) {
+        MavenPluginConvention mavenConvention = new MavenPluginConvention(project, mavenFactory);
         Convention convention = project.getConvention();
         convention.getPlugins().put("maven", mavenConvention);
+        return mavenConvention;
     }
 
-    private void configureJavaScopeMappings(ResolverContainer resolverFactory, ConfigurationContainer configurations) {
-        resolverFactory.getMavenScopeMappings().addMapping(COMPILE_PRIORITY, configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME),
+    private void configureJavaScopeMappings(ConfigurationContainer configurations, Conf2ScopeMappingContainer mavenScopeMappings) {
+        mavenScopeMappings.addMapping(COMPILE_PRIORITY, configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME),
                 Conf2ScopeMappingContainer.COMPILE);
-        resolverFactory.getMavenScopeMappings().addMapping(RUNTIME_PRIORITY, configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME),
+        mavenScopeMappings.addMapping(RUNTIME_PRIORITY, configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME),
                 Conf2ScopeMappingContainer.RUNTIME);
-        resolverFactory.getMavenScopeMappings().addMapping(TEST_COMPILE_PRIORITY, configurations.getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME),
+        mavenScopeMappings.addMapping(TEST_COMPILE_PRIORITY, configurations.getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME),
                 Conf2ScopeMappingContainer.TEST);
-        resolverFactory.getMavenScopeMappings().addMapping(TEST_RUNTIME_PRIORITY, configurations.getByName(JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME),
+        mavenScopeMappings.addMapping(TEST_RUNTIME_PRIORITY, configurations.getByName(JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME),
                 Conf2ScopeMappingContainer.TEST);
     }
 
-    private void configureWarScopeMappings(ResolverContainer resolverContainer, ConfigurationContainer configurations) {
-        resolverContainer.getMavenScopeMappings().addMapping(PROVIDED_COMPILE_PRIORITY, configurations.getByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME),
+    private void configureWarScopeMappings(ConfigurationContainer configurations, Conf2ScopeMappingContainer mavenScopeMappings) {
+        mavenScopeMappings.addMapping(PROVIDED_COMPILE_PRIORITY, configurations.getByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME),
                 Conf2ScopeMappingContainer.PROVIDED);
-        resolverContainer.getMavenScopeMappings().addMapping(PROVIDED_RUNTIME_PRIORITY, configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME),
+        mavenScopeMappings.addMapping(PROVIDED_RUNTIME_PRIORITY, configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME),
                 Conf2ScopeMappingContainer.PROVIDED);
     }
 
     private void configureInstall(Project project) {
         Upload installUpload = project.getTasks().add(INSTALL_TASK_NAME, Upload.class);
         Configuration configuration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
-        installUpload.dependsOn(configuration.getBuildArtifacts());
         installUpload.setConfiguration(configuration);
-        installUpload.getRepositories().mavenInstaller(WrapUtil.toMap("name", RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME));
+        MavenRepositoryHandlerConvention repositories = new DslObject(installUpload.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
+        repositories.mavenInstaller();
         installUpload.setDescription("Does a maven install of the archives artifacts into the local .m2 cache.");
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
index d41b608..c24fc11 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
@@ -17,9 +17,13 @@ package org.gradle.api.plugins;
 
 import groovy.lang.Closure;
 import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.maven.*;
-import org.gradle.api.internal.Factory;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.publication.maven.internal.MavenFactory;
+import org.gradle.api.publication.maven.internal.MavenPomMetaInfoProvider;
+import org.gradle.internal.Factory;
 import org.gradle.util.ConfigureUtil;
 
 import java.io.File;
@@ -30,31 +34,18 @@ import java.util.Collections;
  * 
  * @author Hans Dockter
  */
-public class MavenPluginConvention {
+public class MavenPluginConvention implements MavenPomMetaInfoProvider {
     private final ProjectInternal project;
     private final MavenFactory mavenFactory;
     private Conf2ScopeMappingContainer conf2ScopeMappings;
-    private String pomDirName = "poms";
+    private Object pomDir;
 
-    public MavenPluginConvention(ProjectInternal project) {
+    public MavenPluginConvention(ProjectInternal project, MavenFactory mavenFactory) {
         this.project = project;
-        mavenFactory = project.getServices().get(MavenFactory.class);
+        this.mavenFactory = mavenFactory;
         conf2ScopeMappings = mavenFactory.createConf2ScopeMappingContainer(Collections.<Configuration, Conf2ScopeMapping>emptyMap());
     }
 
-    /**
-     * Returns the name of the directory to generate Maven POMs into, relative to the build directory.
-     */
-    public String getPomDirName() {
-        return pomDirName;
-    }
-
-    /**
-     * Sets the name of the directory to generate Maven POMs into, relative to the build directory.
-     */
-    public void setPomDirName(String pomDirName) {
-        this.pomDirName = pomDirName;
-    }
 
     /**
      * Returns the set of rules for how to map Gradle dependencies to Maven scopes.
@@ -72,8 +63,20 @@ public class MavenPluginConvention {
     /**
      * Returns the directory to generate Maven POMs into.
      */
-    public File getPomDir() {
-        return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve(pomDirName);
+    public File getMavenPomDir() {
+        if (pomDir == null) {
+            return new File(project.getBuildDir(), "poms");
+        }
+        return project.getFileResolver().resolve(pomDir);
+    }
+
+    /**
+     * Sets the directory to generate Maven POMs into.
+     *
+     * @param pomDir The new POM directory. Evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     */
+    public void setMavenPomDir(Object pomDir) {
+        this.pomDir = pomDir;
     }
 
     /**
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
new file mode 100644
index 0000000..7604963
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2011 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.plugins;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+
+import java.util.Map;
+
+/**
+ * Allows maven repositories for publishing artifacts to be defined. The maven plugin mixes-in this interface to the {@link org.gradle.api.artifacts.dsl.RepositoryHandler} associated with each
+ * task of type {@link org.gradle.api.tasks.Upload}.
+ */
+public interface MavenRepositoryHandlerConvention {
+    String DEFAULT_MAVEN_DEPLOYER_NAME = "mavenDeployer";
+    String DEFAULT_MAVEN_INSTALLER_NAME = "mavenInstaller";
+
+    GroovyMavenDeployer mavenDeployer();
+
+    GroovyMavenDeployer mavenDeployer(Closure configureClosure);
+
+    /**
+     * Adds a repository for publishing to a Maven repository. This repository can not be used for reading from a Maven
+     * repository.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository. The default is <em>mavenDeployer-{SOME_ID}</em>.
+     * The name is used in the console output,
+     * to point to information related to a particular repository. A name must be unique amongst a repository group.
+     * </td></tr>
+     * </table>
+     *
+     * @param args The argument to create the repository
+     * @return The added repository
+     * @see #mavenDeployer(java.util.Map, groovy.lang.Closure)
+     */
+    GroovyMavenDeployer mavenDeployer(Map<String, ?> args);
+
+    /**
+     * Behaves the same way as {@link #mavenDeployer(java.util.Map)}. Additionally a closure can be passed to
+     * further configure the added repository.
+     *
+     * @param args The argument to create the repository
+     * @param configureClosure
+     * @return The added repository
+     */
+    GroovyMavenDeployer mavenDeployer(Map<String, ?> args, Closure configureClosure);
+
+    MavenResolver mavenInstaller();
+
+    MavenResolver mavenInstaller(Closure configureClosure);
+
+    /**
+     * Adds a repository for installing to a local Maven cache. This repository can not be used for reading.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository. The default is <em>mavenInstaller-{SOME_ID}</em>.
+     * The name is used in the console output,
+     * to point to information related to a particular repository. A name must be unique amongst a repository group.
+     * </td></tr>
+     * </table>
+     *
+     * @param args The argument to create the repository
+     * @return The added repository
+     * @see #mavenInstaller(java.util.Map, groovy.lang.Closure) (java.util.Map, groovy.lang.Closure)
+     */
+    MavenResolver mavenInstaller(Map<String, ?> args);
+
+    /**
+     * Behaves the same way as {@link #mavenInstaller(java.util.Map)}. Additionally a closure can be passed to further configure the added repository.
+     *
+     * @param args The argument to create the repository
+     * @return The added repository
+     */
+    MavenResolver mavenInstaller(Map<String, ?> args, Closure configureClosure);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/InstallPublications.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/InstallPublications.groovy
new file mode 100644
index 0000000..73774c1
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/InstallPublications.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.publication
+
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.publication.maven.internal.ant.DefaultMavenPublisher
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.internal.file.TemporaryFileProvider
+
+/**
+ * @author: Szczepan Faber, created at: 6/16/11
+ */
+class InstallPublications extends ConventionTask {
+
+    Publications publications
+
+    @TaskAction
+    void publish() {
+        DefaultMavenPublisher publisher = new DefaultMavenPublisher(services.get(TemporaryFileProvider))
+        publisher.install(publications.maven)
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublicationPlugin.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublicationPlugin.groovy
new file mode 100644
index 0000000..f0b82e0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublicationPlugin.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.publication
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.MavenPlugin
+import org.gradle.api.publication.maven.internal.modelbuilder.MavenPublicationBuilder
+
+/**
+ * This is only temporary plugin :) When we're happy with what it does we can move that to the core dsl?
+ *
+ * @author: Szczepan Faber, created at: 6/16/11
+ */
+class PublicationPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        def newPublications = project.extensions.create("publications", Publications)
+
+        project.plugins.withType(MavenPlugin) {
+            newPublications.maven = new MavenPublicationBuilder().build(project)
+            project.task("publishArchives", dependsOn: 'assemble', type: PublishPublications.class) {
+                publications = newPublications
+            }
+            project.task("installArchives", dependsOn: 'assemble', type: InstallPublications.class) {
+                publications = newPublications
+            }
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/Publications.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/Publications.groovy
new file mode 100644
index 0000000..f0087a5
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/Publications.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.publication
+
+import org.gradle.api.publication.maven.MavenPublication
+import org.gradle.util.ConfigureUtil
+
+/**
+ * @author: Szczepan Faber, created at: 6/16/11
+ */
+class Publications {
+    void maven(Closure c) {
+        ConfigureUtil.configure(c, getMaven())
+    }
+
+    MavenPublication maven
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublishPublications.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublishPublications.groovy
new file mode 100644
index 0000000..04f7ba1
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/PublishPublications.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.publication
+
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.publication.maven.internal.ant.DefaultMavenPublisher
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.internal.file.TemporaryFileProvider
+
+/**
+ * @author: Szczepan Faber, created at: 6/16/11
+ */
+class PublishPublications extends ConventionTask {
+
+    Publications publications
+
+    @TaskAction
+    void publish() {
+        DefaultMavenPublisher publisher = new DefaultMavenPublisher(services.get(TemporaryFileProvider))
+        publisher.deploy(publications.maven, publications.maven.repository)
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenArtifact.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenArtifact.groovy
new file mode 100644
index 0000000..3290109
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenArtifact.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.publication.maven
+
+interface MavenArtifact {
+    String getClassifier()
+    String getExtension()
+    File getFile()
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenDependency.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenDependency.groovy
new file mode 100644
index 0000000..56ee2ff
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenDependency.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.publication.maven
+
+interface MavenDependency {
+    String getGroupId()
+    String getArtifactId()
+    String getVersion()
+    String getClassifier()
+    MavenScope getScope()
+    boolean isOptional()
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPomCustomizer.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPomCustomizer.groovy
new file mode 100644
index 0000000..4255b05
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPomCustomizer.groovy
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.publication.maven
+
+interface MavenPomCustomizer {
+    void call(Closure pomBuilder)
+    void whenConfigured(Closure modelTransformer)
+    void withXml(Closure xmlTransformer)
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublication.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublication.groovy
new file mode 100644
index 0000000..5529998
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublication.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.publication.maven
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+
+interface MavenPublication {
+    MavenArtifactRepository getRepository()
+    String getModelVersion()
+    String getGroupId()
+    String getArtifactId()
+    String getVersion()
+    String getPackaging()
+    String getDescription()
+    MavenArtifact getMainArtifact()
+    List<MavenArtifact> getSubArtifacts()
+    List<MavenDependency> getDependencies()
+    MavenPomCustomizer getPom()
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublisher.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublisher.groovy
new file mode 100644
index 0000000..970c2ce
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenPublisher.groovy
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.publication.maven
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+
+public interface MavenPublisher {
+    void install(MavenPublication publication)
+    void deploy(MavenPublication publication, MavenArtifactRepository repository)
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenScope.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenScope.groovy
new file mode 100644
index 0000000..5cb9ac5
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/MavenScope.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.publication.maven
+
+enum MavenScope {
+    COMPILE,
+    RUNTIME,
+    TEST,
+    PROVIDED,
+    SYSTEM
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPom.java
new file mode 100644
index 0000000..710a6f4
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPom.java
@@ -0,0 +1,41 @@
+/*
+ * 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.publication.maven.internal;
+
+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;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPom {
+    /**
+     * @return The main artifact, may be null.
+     */
+    PublishArtifact getArtifact();
+
+    MavenPom getPom();
+
+    void addArtifact(Artifact artifact, File src);
+
+    Set<PublishArtifact> getAttachedArtifacts();
+
+    PublishArtifact writePom(File pomFile);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java
new file mode 100644
index 0000000..92a11fe
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomContainer.java
@@ -0,0 +1,31 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.maven.MavenDeployment;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPomContainer {
+    void addArtifact(Artifact artifact, File src);
+
+    Set<MavenDeployment> createDeployableFilesInfos();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java
new file mode 100644
index 0000000..c3761ac
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ArtifactPomFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPomFactory {
+    ArtifactPom createArtifactPom(MavenPom pom);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java
new file mode 100644
index 0000000..4c631ef
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainer.java
@@ -0,0 +1,137 @@
+/*
+ * 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.publication.maven.internal;
+
+import groovy.lang.Closure;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+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.internal.Factory;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.WrapUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class BasePomFilterContainer implements PomFilterContainer {
+    private Map<String, PomFilter> pomFilters = new HashMap<String, PomFilter>();
+
+    private PomFilter defaultPomFilter;
+
+    private Factory<MavenPom> mavenPomFactory;
+
+    public BasePomFilterContainer(Factory<MavenPom> mavenPomFactory) {
+        this.mavenPomFactory = mavenPomFactory;
+    }
+
+    public PublishFilter getFilter() {
+        return getDefaultPomFilter().getFilter();
+    }
+
+    public void setFilter(PublishFilter defaultFilter) {
+        getDefaultPomFilter().setFilter(defaultFilter);
+    }
+
+    public MavenPom getPom() {
+        return getDefaultPomFilter().getPomTemplate();
+    }
+
+    public void setPom(MavenPom defaultPom) {
+        getDefaultPomFilter().setPomTemplate(defaultPom);
+    }
+
+    public void filter(Closure filter) {
+        setFilter(toFilter(filter));
+    }
+
+    public MavenPom addFilter(String name, Closure filter) {
+        return addFilter(name, toFilter(filter));
+    }
+
+    private PublishFilter toFilter(final Closure filter) {
+        return (PublishFilter) DefaultGroovyMethods.asType(filter, PublishFilter.class);
+    }
+
+    public MavenPom pom(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, getPom());
+    }
+
+    public MavenPom pom(String name, Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, pom(name));
+    }
+
+    public MavenPom addFilter(String name, PublishFilter publishFilter) {
+        if (name == null || publishFilter == null) {
+            throw new InvalidUserDataException("Name and Filter must not be null.");
+        }
+        MavenPom pom = mavenPomFactory.create();
+        pomFilters.put(name, new DefaultPomFilter(name, pom, publishFilter));
+        return pom;
+    }
+
+    public PublishFilter filter(String name) {
+        if (name == null) {
+            throw new InvalidUserDataException("Name must not be null.");
+        }
+        return pomFilters.get(name).getFilter();
+    }
+
+    public MavenPom pom(String name) {
+        if (name == null) {
+            throw new InvalidUserDataException("Name must not be null.");
+        }
+        return pomFilters.get(name).getPomTemplate();
+    }
+
+    public Iterable<PomFilter> getActivePomFilters() {
+        Iterable<PomFilter> activeArtifactPoms;
+        if (pomFilters.size() == 0 && getDefaultPomFilter() != null) {
+            activeArtifactPoms = WrapUtil.toSet(getDefaultPomFilter());
+        } else {
+            activeArtifactPoms = pomFilters.values();
+        }
+        return activeArtifactPoms;
+    }
+
+    public Factory<MavenPom> getMavenPomFactory() {
+        return mavenPomFactory;
+    }
+
+    public PomFilter getDefaultPomFilter() {
+        if (defaultPomFilter == null) {
+            defaultPomFilter = new DefaultPomFilter(PomFilterContainer.DEFAULT_ARTIFACT_POM_NAME, mavenPomFactory.create(),
+                PublishFilter.ALWAYS_ACCEPT);
+        }
+        return defaultPomFilter;
+    }
+
+    public void setDefaultPomFilter(PomFilter defaultPomFilter) {
+        this.defaultPomFilter = defaultPomFilter;
+    }
+
+    public Map<String, PomFilter> getPomFilters() {
+        return pomFilters;
+    }
+
+    protected BasePomFilterContainer newInstance() {
+        return new BasePomFilterContainer(mavenPomFactory);
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomModelBuilder.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomModelBuilder.java
new file mode 100644
index 0000000..395033a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/CustomModelBuilder.java
@@ -0,0 +1,82 @@
+/*
+ * 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.publication.maven.internal;
+
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.model.Model;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
+import org.gradle.api.internal.artifacts.PlexusLoggerAdapter;
+import org.slf4j.LoggerFactory;
+import org.sonatype.maven.polyglot.execute.ExecuteManager;
+import org.sonatype.maven.polyglot.execute.ExecuteManagerImpl;
+import org.sonatype.maven.polyglot.groovy.builder.ModelBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+/**
+ * This is a slightly modified version as shipped with polyglot Maven.
+ */
+public class CustomModelBuilder extends ModelBuilder {
+
+    public CustomModelBuilder(Model model) {
+        ExecuteManager executeManager = new ExecuteManagerImpl();
+        setProp(executeManager.getClass(), executeManager, "log",
+                new PlexusLoggerAdapter(LoggerFactory.getLogger(ExecuteManagerImpl.class)));
+        setProp(ModelBuilder.class, this, "executeManager", executeManager);
+        setProp(ModelBuilder.class, this, "log",
+                new PlexusLoggerAdapter(LoggerFactory.getLogger(ModelBuilder.class)));
+        try {
+            initialize();
+        } catch (InitializationException e) {
+            throw new RuntimeException(e);
+        }
+        Map factories = (Map) getProp(FactoryBuilderSupport.class, this, "factories");
+        factories.remove("project");
+        ModelFactory modelFactory = new ModelFactory(model);
+        registerFactory(modelFactory.getName(), null, modelFactory);
+    }
+
+    public static void setProp(Class c, Object obj, String fieldName, Object value) {
+        try {
+            Field f = c.getDeclaredField(fieldName);
+            f.setAccessible(true); // solution
+            f.set(obj, value); // IllegalAccessException
+            // production code should handle these exceptions more gracefully
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+           throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+           throw new RuntimeException(e);
+        }
+    }
+
+    public static Object getProp(Class c, Object obj, String fieldName) {
+        try {
+            Field f = c.getDeclaredField(fieldName);
+            f.setAccessible(true); // solution
+            return f.get(obj); // IllegalAccessException
+            // production code should handle these exceptions more gracefully
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java
new file mode 100644
index 0000000..6ed01b0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPom.java
@@ -0,0 +1,212 @@
+/*
+ * 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.publication.maven.internal;
+
+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.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.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 PublishArtifact artifact;
+
+    private final Set<PublishArtifact> classifiers = new HashSet<PublishArtifact>();
+
+    public DefaultArtifactPom(MavenPom pom) {
+        this.pom = pom;
+    }
+
+    public MavenPom getPom() {
+        return pom;
+    }
+
+    public PublishArtifact getArtifact() {
+        return artifact;
+    }
+
+    public Set<PublishArtifact> getAttachedArtifacts() {
+        return Collections.unmodifiableSet(classifiers);
+    }
+
+    public PublishArtifact writePom(final File pomFile) {
+        getPom().writeTo(pomFile);
+        return new PomArtifact(pomFile);
+    }
+
+    public void addArtifact(Artifact artifact, File src) {
+        throwExceptionIfArtifactOrSrcIsNull(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) {
+            // 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 = publishArtifact;
+        this.artifacts.put(artifactKey, publishArtifact);
+        assignArtifactValuesToPom(artifact, pom, true);
+    }
+
+    private void addArtifact(PublishArtifact artifact) {
+        classifiers.add(artifact);
+        artifacts.put(new ArtifactKey(artifact), artifact);
+    }
+
+    private String getClassifier(Artifact artifact) {
+        return artifact.getExtraAttribute(Dependency.CLASSIFIER);
+    }
+
+    private void assignArtifactValuesToPom(Artifact artifact, MavenPom pom, boolean setType) {
+        if (pom.getGroupId().equals(MavenProject.EMPTY_PROJECT_GROUP_ID)) {
+            pom.setGroupId(artifact.getModuleRevisionId().getOrganisation());
+        }
+        if (pom.getArtifactId().equals(MavenProject.EMPTY_PROJECT_ARTIFACT_ID)) {
+            pom.setArtifactId(artifact.getName());
+        }
+        if (pom.getVersion().equals(MavenProject.EMPTY_PROJECT_VERSION)) {
+            pom.setVersion(artifact.getModuleRevisionId().getRevision());
+        }
+        if (setType) {
+            pom.setPackaging(artifact.getType());
+        }
+    }
+
+    private void throwExceptionIfArtifactOrSrcIsNull(Artifact artifact, File src) {
+        if (artifact == null) {
+            throw new InvalidUserDataException("Artifact must not be null.");
+        }
+        if (src == null) {
+            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/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java
new file mode 100644
index 0000000..66b6383
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainer.java
@@ -0,0 +1,74 @@
+/*
+ * 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.publication.maven.internal;
+
+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.MavenDeployment;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPomContainer implements ArtifactPomContainer {
+    private Map<String, ArtifactPom> artifactPoms = new HashMap<String, ArtifactPom>();
+    private final MavenPomMetaInfoProvider pomMetaInfoProvider;
+    private PomFilterContainer pomFilterContainer;
+    private ArtifactPomFactory artifactPomFactory;
+
+    public DefaultArtifactPomContainer(MavenPomMetaInfoProvider pomMetaInfoProvider, PomFilterContainer pomFilterContainer,
+                                       ArtifactPomFactory artifactPomFactory) {
+        this.pomMetaInfoProvider = pomMetaInfoProvider;
+        this.pomFilterContainer = pomFilterContainer;
+        this.artifactPomFactory = artifactPomFactory;
+    }
+
+    public void addArtifact(Artifact artifact, File src) {
+        if (artifact == null || src == null) {
+            throw new InvalidUserDataException("Artifact or source file must not be null!");
+        }
+        for (PomFilter activePomFilter : pomFilterContainer.getActivePomFilters()) {
+            if (activePomFilter.getFilter().accept(artifact, src)) {
+                if (artifactPoms.get(activePomFilter.getName()) == null) {
+                    artifactPoms.put(activePomFilter.getName(), artifactPomFactory.createArtifactPom(activePomFilter.getPomTemplate()));
+                }
+                artifactPoms.get(activePomFilter.getName()).addArtifact(artifact, src); 
+            }
+        }
+    }
+
+    public Set<MavenDeployment> createDeployableFilesInfos() {
+        Set<MavenDeployment> mavenDeployments = new HashSet<MavenDeployment>();
+        for (String activeArtifactPomName : artifactPoms.keySet()) {
+            ArtifactPom activeArtifactPom = artifactPoms.get(activeArtifactPomName);
+            File pomFile = createPomFile(activeArtifactPomName);
+            PublishArtifact pomArtifact = activeArtifactPom.writePom(pomFile);
+            mavenDeployments.add(new DefaultMavenDeployment(pomArtifact, activeArtifactPom.getArtifact(), activeArtifactPom.getAttachedArtifacts()));
+        }
+        return mavenDeployments;
+    }
+
+    private File createPomFile(String artifactPomName) {
+        return new File(pomMetaInfoProvider.getMavenPomDir(), "pom-" + artifactPomName + ".xml");
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java
new file mode 100644
index 0000000..b9eb510
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPomFactory implements ArtifactPomFactory {
+    public ArtifactPom createArtifactPom(MavenPom pom) {
+        return new DefaultArtifactPom(pom);
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java
new file mode 100644
index 0000000..c766fd1
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainer.java
@@ -0,0 +1,115 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.util.WrapUtil;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConf2ScopeMappingContainer implements Conf2ScopeMappingContainer {
+    private Map<Configuration, Conf2ScopeMapping> mappings = new HashMap<Configuration, Conf2ScopeMapping>();
+
+    private boolean skipUnmappedConfs = true;
+
+    public DefaultConf2ScopeMappingContainer() {
+    }
+
+    public DefaultConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings) {
+        this.mappings.putAll(mappings);
+    }
+
+    public Conf2ScopeMapping getMapping(Collection<Configuration> configurations) {
+        Set<Conf2ScopeMapping> result = getMappingsWithHighestPriority(configurations);
+        if (result.size() > 1) {
+            throw new InvalidUserDataException(
+                    "The configuration to scope mapping is not unique. The following configurations "
+                            + "have the same priority: " + result);
+        }
+        return result.size() == 0 ? null : result.iterator().next();
+    }
+
+    private Set<Conf2ScopeMapping> getMappingsWithHighestPriority(Collection<Configuration> configurations) {
+        Integer lastPriority = null;
+        Set<Conf2ScopeMapping> result = new HashSet<Conf2ScopeMapping>();
+        for (Conf2ScopeMapping conf2ScopeMapping : getMappingsForConfigurations(configurations)) {
+            Integer thisPriority = conf2ScopeMapping.getPriority();
+            if (lastPriority != null && lastPriority.equals(thisPriority)) {
+                result.add(conf2ScopeMapping);
+            } else if (lastPriority == null || (thisPriority != null && lastPriority < thisPriority)) {
+                lastPriority = thisPriority;
+                result = WrapUtil.toSet(conf2ScopeMapping);
+            }
+        }
+        return result;
+    }
+
+    private List<Conf2ScopeMapping> getMappingsForConfigurations(Collection<Configuration> configurations) {
+        List<Conf2ScopeMapping> existingMappings = new ArrayList<Conf2ScopeMapping>();
+        for (Configuration configuration : configurations) {
+            if (mappings.get(configuration) != null) {
+                existingMappings.add(mappings.get(configuration));
+            } else {
+                existingMappings.add(new Conf2ScopeMapping(null, configuration, null));
+            }
+        }
+        return existingMappings;
+    }
+
+    public Conf2ScopeMappingContainer addMapping(int priority, Configuration configuration, String scope) {
+        mappings.put(configuration, new Conf2ScopeMapping(priority, configuration, scope));
+        return this;
+    }
+
+    public Map<Configuration, Conf2ScopeMapping> getMappings() {
+        return mappings;
+    }
+
+    public boolean isSkipUnmappedConfs() {
+        return skipUnmappedConfs;
+    }
+
+    public void setSkipUnmappedConfs(boolean skipUnmappedConfs) {
+        this.skipUnmappedConfs = skipUnmappedConfs;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultConf2ScopeMappingContainer that = (DefaultConf2ScopeMappingContainer) o;
+
+        if (!mappings.equals(that.mappings)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public int hashCode() {
+        return mappings.hashCode();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java
new file mode 100644
index 0000000..d3ad057
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultDeployerFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.*;
+import org.gradle.internal.Factory;
+import org.gradle.api.publication.maven.internal.ant.BaseMavenInstaller;
+import org.gradle.api.publication.maven.internal.ant.DefaultGroovyMavenDeployer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.logging.LoggingManagerInternal;
+
+public class DefaultDeployerFactory implements DeployerFactory {
+    private final Factory<LoggingManagerInternal> loggingManagerFactory;
+    private final MavenFactory mavenFactory;
+    private final FileResolver fileResolver;
+    private final MavenPomMetaInfoProvider pomMetaInfoProvider;
+    private final ConfigurationContainer configurationContainer;
+    private final Conf2ScopeMappingContainer scopeMapping;
+
+    public DefaultDeployerFactory(MavenFactory mavenFactory, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMapping) {
+        this.mavenFactory = mavenFactory;
+        this.loggingManagerFactory = loggingManagerFactory;
+        this.fileResolver = fileResolver;
+        this.pomMetaInfoProvider = pomMetaInfoProvider;
+        this.configurationContainer = configurationContainer;
+        this.scopeMapping = scopeMapping;
+    }
+
+    public GroovyMavenDeployer createMavenDeployer() {
+        PomFilterContainer pomFilterContainer = createPomFilterContainer(
+                mavenFactory.createMavenPomFactory(configurationContainer, scopeMapping, fileResolver));
+        return new DefaultGroovyMavenDeployer(pomFilterContainer, createArtifactPomContainer(
+                pomMetaInfoProvider, pomFilterContainer, createArtifactPomFactory()), loggingManagerFactory.create());
+    }
+
+    public MavenResolver createMavenInstaller() {
+        PomFilterContainer pomFilterContainer = createPomFilterContainer(
+                mavenFactory.createMavenPomFactory(configurationContainer, scopeMapping, fileResolver));
+        return new BaseMavenInstaller(pomFilterContainer, createArtifactPomContainer(pomMetaInfoProvider,
+                pomFilterContainer, createArtifactPomFactory()), loggingManagerFactory.create());
+    }
+
+    private PomFilterContainer createPomFilterContainer(Factory<MavenPom> mavenPomFactory) {
+        return new BasePomFilterContainer(mavenPomFactory);
+    }
+
+    private ArtifactPomFactory createArtifactPomFactory() {
+        return new DefaultArtifactPomFactory();
+    }
+
+    private ArtifactPomContainer createArtifactPomContainer(MavenPomMetaInfoProvider pomMetaInfoProvider, PomFilterContainer filterContainer,
+                                                            ArtifactPomFactory pomFactory) {
+        return new DefaultArtifactPomContainer(pomMetaInfoProvider, filterContainer, pomFactory);
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenDeployment.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenDeployment.java
new file mode 100644
index 0000000..09ec43d
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/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.publication.maven.internal;
+
+import com.google.common.collect.Sets;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.MavenDeployment;
+
+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 = Sets.newLinkedHashSet(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/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenFactory.java
new file mode 100644
index 0000000..3496e47
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.*;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.publication.maven.internal.ant.DefaultExcludeRuleConverter;
+import org.gradle.api.publication.maven.internal.ant.DefaultPomDependenciesConverter;
+
+import java.util.Map;
+
+public class DefaultMavenFactory implements MavenFactory {
+
+    public Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, FileResolver fileResolver) {
+        return new DefaultMavenPomFactory(configurationContainer, conf2ScopeMappingContainer, createPomDependenciesConverter(), fileResolver);
+    }
+
+    public Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Map<Configuration, Conf2ScopeMapping> mappings, FileResolver fileResolver) {
+        return new DefaultMavenPomFactory(configurationContainer, createConf2ScopeMappingContainer(mappings), createPomDependenciesConverter(), fileResolver);
+    }
+
+    private PomDependenciesConverter createPomDependenciesConverter() {
+        return new DefaultPomDependenciesConverter(new DefaultExcludeRuleConverter());
+    }
+
+    public Conf2ScopeMappingContainer createConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings) {
+        return new DefaultConf2ScopeMappingContainer(mappings);
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
new file mode 100644
index 0000000..55f529d
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
@@ -0,0 +1,251 @@
+/*
+ * 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.publication.maven.internal;
+
+import groovy.lang.Closure;
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.XmlTransformer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.listener.ActionBroadcast;
+
+import java.io.*;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultMavenPom implements MavenPom {
+    private PomDependenciesConverter pomDependenciesConverter;
+    private FileResolver fileResolver;
+    private MavenProject mavenProject = new MavenProject();
+    private Conf2ScopeMappingContainer scopeMappings;
+    private ActionBroadcast<MavenPom> whenConfiguredActions = new ActionBroadcast<MavenPom>();
+    private XmlTransformer withXmlActions = new XmlTransformer();
+    private ConfigurationContainer configurations;
+
+    public DefaultMavenPom(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMappings, PomDependenciesConverter pomDependenciesConverter,
+                           FileResolver fileResolver) {
+        this.configurations = configurationContainer;
+        this.scopeMappings = scopeMappings;
+        this.pomDependenciesConverter = pomDependenciesConverter;
+        this.fileResolver = fileResolver;
+        mavenProject.setModelVersion("4.0.0");
+    }
+
+    public Conf2ScopeMappingContainer getScopeMappings() {
+        return scopeMappings;
+    }
+
+    public ConfigurationContainer getConfigurations() {
+        return configurations;
+    }
+
+    public DefaultMavenPom setConfigurations(ConfigurationContainer configurations) {
+        this.configurations = configurations;
+        return this;
+    }
+    
+    public DefaultMavenPom setGroupId(String groupId) {
+        getModel().setGroupId(groupId);
+        return this;
+    }
+
+    public String getGroupId() {
+        return getModel().getGroupId();
+    }
+
+    public DefaultMavenPom setArtifactId(String artifactId) {
+        getModel().setArtifactId(artifactId);
+        return this;
+    }
+
+    public String getArtifactId() {
+        return getModel().getArtifactId();
+    }
+
+    @SuppressWarnings("unchecked")
+    public DefaultMavenPom setDependencies(List<?> dependencies) {
+        getModel().setDependencies((List<Dependency>) dependencies);
+        return this;
+    }
+
+    public List<Dependency> getDependencies() {
+        return getModel().getDependencies();
+    }
+
+    public DefaultMavenPom setName(String name) {
+        getModel().setName(name);
+        return this;
+    }
+
+    public String getName() {
+        return getModel().getName();
+    }
+
+    public DefaultMavenPom setVersion(String version) {
+        getModel().setVersion(version);
+        return this;
+    }
+
+    public String getVersion() {
+        return getModel().getVersion();
+    }
+
+    public String getPackaging() {
+        return getModel().getPackaging();
+    }
+
+    public DefaultMavenPom setPackaging(String packaging) {
+        getModel().setPackaging(packaging);
+        return this;
+    }
+
+    public DefaultMavenPom project(Closure cl) {
+        CustomModelBuilder pomBuilder = new CustomModelBuilder(getModel());
+        InvokerHelper.invokeMethod(pomBuilder, "project", cl);
+        return this;
+    }
+
+    public Model getModel() {
+        return mavenProject.getModel();
+    }
+
+    public DefaultMavenPom setModel(Object model) {
+        this.mavenProject = new MavenProject((Model) model);
+        return this;
+    }
+
+    public MavenProject getMavenProject() {
+        return mavenProject;
+    }
+
+    public DefaultMavenPom setMavenProject(MavenProject mavenProject) {
+        this.mavenProject = mavenProject;
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<Dependency> getGeneratedDependencies() {
+        if (configurations == null) {
+            return Collections.emptyList();
+        }
+        return (List<Dependency>) pomDependenciesConverter.convert(getScopeMappings(), configurations);
+    }
+
+    public DefaultMavenPom getEffectivePom() {
+        DefaultMavenPom effectivePom = new DefaultMavenPom(null, this.scopeMappings, pomDependenciesConverter, fileResolver);
+        try {
+            effectivePom.setMavenProject((MavenProject) mavenProject.clone());
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+        effectivePom.getDependencies().addAll(getGeneratedDependencies());
+        effectivePom.withXmlActions = withXmlActions;
+        whenConfiguredActions.execute(effectivePom);
+        return effectivePom;
+    }
+
+    public PomDependenciesConverter getPomDependenciesConverter() {
+        return pomDependenciesConverter;
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public DefaultMavenPom setFileResolver(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+        return this;
+    }
+
+    public DefaultMavenPom writeTo(final Writer pomWriter) {
+        getEffectivePom().writeNonEffectivePom(pomWriter);
+        return this;
+    }
+
+    public DefaultMavenPom writeTo(Object path) {
+        OutputStream stream = null;
+
+        try {
+            File file = fileResolver.resolve(path);
+            if (file.getParentFile() != null) {
+                file.getParentFile().mkdirs();
+            }
+            stream = new FileOutputStream(file);
+            getEffectivePom().writeNonEffectivePom(stream);
+            return this;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            IOUtils.closeQuietly(stream);
+        }
+    }
+
+    private void writeNonEffectivePom(final Writer pomWriter) {
+        try {
+            final StringWriter stringWriter = new StringWriter();
+            mavenProject.writeModel(stringWriter);
+            withXmlActions.transform(stringWriter.toString(), pomWriter);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            IOUtils.closeQuietly(pomWriter);
+        }
+    }
+
+    private void writeNonEffectivePom(OutputStream stream) {
+        try {
+            final StringWriter stringWriter = new StringWriter();
+            mavenProject.writeModel(stringWriter);
+            withXmlActions.transform(stringWriter.toString(), stream);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            IOUtils.closeQuietly(stream);
+        }
+    }
+
+    public DefaultMavenPom whenConfigured(final Closure closure) {
+        whenConfiguredActions.add(closure);
+        return this;
+    }
+
+    public DefaultMavenPom whenConfigured(final Action<MavenPom> action) {
+        whenConfiguredActions.add(action);
+        return this;
+    }
+
+    public DefaultMavenPom withXml(final Closure closure) {
+        withXmlActions.addAction(closure);
+        return this;
+    }
+
+    public DefaultMavenPom withXml(final Action<XmlProvider> action) {
+        withXmlActions.addAction(action);
+        return this;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java
new file mode 100644
index 0000000..4df4826
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.file.FileResolver;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultMavenPomFactory implements Factory<MavenPom> {
+    private ConfigurationContainer configurationContainer;
+    private Conf2ScopeMappingContainer conf2ScopeMappingContainer;
+    private PomDependenciesConverter pomDependenciesConverter;
+    private FileResolver fileResolver;
+
+
+    public DefaultMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, PomDependenciesConverter pomDependenciesConverter,
+                                  FileResolver fileResolver) {
+        this.configurationContainer = configurationContainer;
+        this.conf2ScopeMappingContainer = conf2ScopeMappingContainer;
+        this.pomDependenciesConverter = pomDependenciesConverter;
+        this.fileResolver = fileResolver;
+    }
+
+    public MavenPom create() {
+        return new DefaultMavenPom(configurationContainer,
+                new DefaultConf2ScopeMappingContainer(conf2ScopeMappingContainer.getMappings()), pomDependenciesConverter, fileResolver);
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
new file mode 100644
index 0000000..3f1e012
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConvention.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 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.publication.maven.internal;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.plugins.MavenRepositoryHandlerConvention;
+
+import java.util.Map;
+
+public class DefaultMavenRepositoryHandlerConvention implements MavenRepositoryHandlerConvention {
+    private final DefaultRepositoryHandler container;
+    private final DeployerFactory deployerFactory;
+
+    public DefaultMavenRepositoryHandlerConvention(DefaultRepositoryHandler container, DeployerFactory deployerFactory) {
+        this.container = container;
+        this.deployerFactory = deployerFactory;
+    }
+
+    public GroovyMavenDeployer mavenDeployer() {
+        return container.addRepository(createMavenDeployer(), DEFAULT_MAVEN_DEPLOYER_NAME);
+    }
+
+    public GroovyMavenDeployer mavenDeployer(Closure configureClosure) {
+        return container.addRepository(createMavenDeployer(), configureClosure, DEFAULT_MAVEN_DEPLOYER_NAME);
+    }
+
+    public GroovyMavenDeployer mavenDeployer(Map<String, ?> args) {
+        return container.addRepository(createMavenDeployer(), args, DEFAULT_MAVEN_DEPLOYER_NAME);
+    }
+
+    public GroovyMavenDeployer mavenDeployer(Map<String, ?> args, Closure configureClosure) {
+        return container.addRepository(createMavenDeployer(), args, configureClosure, DEFAULT_MAVEN_DEPLOYER_NAME);
+    }
+
+    private GroovyMavenDeployer createMavenDeployer() {
+        return deployerFactory.createMavenDeployer();
+    }
+
+    public MavenResolver mavenInstaller() {
+        return container.addRepository(createMavenInstaller(), DEFAULT_MAVEN_INSTALLER_NAME);
+    }
+
+    public MavenResolver mavenInstaller(Closure configureClosure) {
+        return container.addRepository(createMavenInstaller(), configureClosure, DEFAULT_MAVEN_INSTALLER_NAME);
+    }
+
+    public MavenResolver mavenInstaller(Map<String, ?> args) {
+        return container.addRepository(createMavenInstaller(), args, DEFAULT_MAVEN_INSTALLER_NAME);
+    }
+
+    public MavenResolver mavenInstaller(Map<String, ?> args, Closure configureClosure) {
+        return container.addRepository(createMavenInstaller(), args, configureClosure, DEFAULT_MAVEN_INSTALLER_NAME);
+    }
+
+    private MavenResolver createMavenInstaller() {
+        return deployerFactory.createMavenInstaller();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java
new file mode 100644
index 0000000..3f44c82
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPomFilter implements PomFilter {
+    private String name;
+
+    private MavenPom pom;
+
+    private PublishFilter filter;
+
+    public DefaultPomFilter(String name, MavenPom pom, PublishFilter filter) {
+        this.name = name;
+        this.pom = pom;
+        this.filter = filter;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public MavenPom getPomTemplate() {
+        return pom;
+    }
+
+    public void setPomTemplate(MavenPom pom) {
+        this.pom = pom;
+    }
+
+    public PublishFilter getFilter() {
+        return filter;
+    }
+
+    public void setFilter(PublishFilter filter) {
+        this.filter = filter;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DeployerFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DeployerFactory.java
new file mode 100644
index 0000000..573942f
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DeployerFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+
+public interface DeployerFactory {
+    GroovyMavenDeployer createMavenDeployer();
+
+    MavenResolver createMavenInstaller();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java
new file mode 100644
index 0000000..cc5743d
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ExcludeRuleConverter.java
@@ -0,0 +1,26 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.ExcludeRule;
+
+
+/**
+ * @author Hans Dockter
+ */
+public interface ExcludeRuleConverter {
+    Object convert(ExcludeRule excludeRule);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenFactory.java
new file mode 100644
index 0000000..99affcd
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.util.Map;
+
+/**
+ * Factory for various types related to Maven dependency management.
+ */
+public interface MavenFactory {
+    Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Map<Configuration, Conf2ScopeMapping> mappings, FileResolver fileResolver);
+
+    Factory<MavenPom> createMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, FileResolver fileResolver);
+
+    Conf2ScopeMappingContainer createConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings);
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPomMetaInfoProvider.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPomMetaInfoProvider.java
new file mode 100644
index 0000000..b997871
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPomMetaInfoProvider.java
@@ -0,0 +1,22 @@
+/*
+ * 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.publication.maven.internal;
+
+import java.io.File;
+
+public interface MavenPomMetaInfoProvider {
+    File getMavenPomDir();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPublicationPomGenerator.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPublicationPomGenerator.groovy
new file mode 100644
index 0000000..dc32108
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/MavenPublicationPomGenerator.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.publication.maven.internal
+
+import org.gradle.api.publication.maven.MavenPublication
+import org.apache.maven.model.Model
+import org.apache.maven.model.Dependency
+import org.gradle.api.publication.maven.MavenDependency
+
+class MavenPublicationPomGenerator {
+    private final MavenPublication publication
+
+    private final Model model = new Model()
+
+    MavenPublicationPomGenerator(MavenPublication publication) {
+        this.publication = publication
+    }
+
+    Model generatePom() {
+        generateMainAttributes()
+        generateDependencies()
+        model
+    }
+
+    private void generateMainAttributes() {
+        model.modelVersion = publication.modelVersion
+        model.groupId = publication.groupId
+        model.artifactId = publication.artifactId
+        model.version = publication.version
+        model.packaging = publication.packaging
+        model.description = publication.description
+    }
+
+    private void generateDependencies() {
+        model.dependencies = publication.dependencies.collect { MavenDependency mavenDep ->
+            def dependency = new Dependency()
+            dependency.groupId = mavenDep.groupId
+            dependency.artifactId = mavenDep.artifactId
+            dependency.version = mavenDep.version
+            dependency.classifier = mavenDep.classifier
+            dependency.scope = mavenDep.scope.name().toLowerCase()
+            dependency.optional = mavenDep.optional
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ModelFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ModelFactory.java
new file mode 100644
index 0000000..06d6fed
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ModelFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.publication.maven.internal;
+
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.model.Model;
+import org.sonatype.maven.polyglot.groovy.builder.factory.NamedFactory;
+
+import java.util.Map;
+
+/**
+ * This is a slightly modified version as shipped with polyglot Maven.
+ */
+public class ModelFactory extends NamedFactory {
+    private Model model;
+
+    public ModelFactory(Model model) {
+        super("project");
+        this.model = model;
+    }
+
+    public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attrs) throws InstantiationException, IllegalAccessException {
+        return model;
+    }
+
+    @Override
+    public void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) {
+        Model model = (Model)node;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java
new file mode 100644
index 0000000..735fb9a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomDependenciesConverter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PomDependenciesConverter {
+    public List<?> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java
new file mode 100644
index 0000000..84f8010
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/PomFilter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PomFilter {
+    String getName();
+
+    PublishFilter getFilter();
+
+    void setFilter(PublishFilter filter);
+
+    MavenPom getPomTemplate();
+
+    void setPomTemplate(MavenPom pom);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
new file mode 100644
index 0000000..1c7e262
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
@@ -0,0 +1,299 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import groovy.lang.Closure;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.ModuleEntry;
+import org.apache.ivy.core.search.OrganisationEntry;
+import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.maven.artifact.ant.AttachedArtifact;
+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.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.NoOpRepositoryCacheManager;
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
+import org.gradle.api.publication.maven.internal.PomFilter;
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.listener.ActionBroadcast;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.AntUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractMavenResolver implements MavenResolver, DependencyResolver, ArtifactRepositoryInternal {
+
+    private String name;
+    
+    private ArtifactPomContainer artifactPomContainer;
+
+    private PomFilterContainer pomFilterContainer;
+
+    private Settings settings;
+
+    private LoggingManagerInternal loggingManager;
+
+    private final ActionBroadcast<MavenDeployment> beforeDeploymentActions = new ActionBroadcast<MavenDeployment>();
+
+    protected MavenSettingsSupplier mavenSettingsSupplier = new EmptyMavenSettingsSupplier();
+
+    public AbstractMavenResolver(PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        this.pomFilterContainer = pomFilterContainer;
+        this.artifactPomContainer = artifactPomContainer;
+        this.loggingManager = loggingManager;
+    }
+
+    protected abstract InstallDeployTaskSupport createPreConfiguredTask(Project project);
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public DependencyResolver createResolver() {
+        return this;
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public boolean exists(Artifact artifact) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public void reportFailure() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public void reportFailure(Artifact art) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry module) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public Namespace getNamespace() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public void dumpSettings() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        if (isIgnorable(artifact)) {
+            return;
+        }
+        getArtifactPomContainer().addArtifact(artifact, src);
+    }
+
+    private boolean isIgnorable(Artifact artifact) {
+        return artifact.getType().equals("ivy");
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+        // do nothing
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        // do nothing
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        InstallDeployTaskSupport installDeployTaskSupport = createPreConfiguredTask(AntUtil.createProject());
+        Set<MavenDeployment> mavenDeployments = getArtifactPomContainer().createDeployableFilesInfos();
+        mavenSettingsSupplier.supply(installDeployTaskSupport);
+        for (MavenDeployment mavenDeployment : mavenDeployments) {
+            beforeDeploymentActions.execute(mavenDeployment);
+            addPomAndArtifact(installDeployTaskSupport, mavenDeployment);
+            execute(installDeployTaskSupport);
+        }
+        mavenSettingsSupplier.done();
+        settings = ((CustomInstallDeployTaskSupport) installDeployTaskSupport).getSettings();
+    }
+
+    private void execute(InstallDeployTaskSupport deployTask) {
+        loggingManager.captureStandardOutput(LogLevel.INFO).start();
+        try {
+            deployTask.execute();
+        } finally {
+            loggingManager.stop();
+        }
+    }
+
+    private void addPomAndArtifact(InstallDeployTaskSupport installOrDeployTask, MavenDeployment mavenDeployment) {
+        Pom pom = new Pom();
+        pom.setProject(installOrDeployTask.getProject());
+        pom.setFile(mavenDeployment.getPomArtifact().getFile());
+        installOrDeployTask.addPom(pom);
+        if (mavenDeployment.getMainArtifact() != null) {
+            installOrDeployTask.setFile(mavenDeployment.getMainArtifact().getFile());
+        }
+        for (PublishArtifact classifierArtifact : mavenDeployment.getAttachedArtifacts()) {
+            AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
+            attachedArtifact.setClassifier(classifierArtifact.getClassifier());
+            attachedArtifact.setFile(classifierArtifact.getFile());
+            attachedArtifact.setType(classifierArtifact.getType());
+        }
+    }
+
+    public void setSettings(ResolverSettings settings) {
+        // do nothing
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        return new NoOpRepositoryCacheManager(getName());
+    }
+
+    public ArtifactPomContainer getArtifactPomContainer() {
+        return artifactPomContainer;
+    }
+
+    public void setArtifactPomContainer(ArtifactPomContainer artifactPomContainer) {
+        this.artifactPomContainer = artifactPomContainer;
+    }
+
+    public Settings getSettings() {
+        return settings;
+    }
+
+    public PublishFilter getFilter() {
+        return pomFilterContainer.getFilter();
+    }
+
+    public void setFilter(PublishFilter defaultFilter) {
+        pomFilterContainer.setFilter(defaultFilter);
+    }
+
+    public MavenPom getPom() {
+        return pomFilterContainer.getPom();
+    }
+
+    public void setPom(MavenPom defaultPom) {
+        pomFilterContainer.setPom(defaultPom);
+    }
+
+    public MavenPom addFilter(String name, PublishFilter publishFilter) {
+        return pomFilterContainer.addFilter(name, publishFilter);
+    }
+
+    public MavenPom addFilter(String name, Closure filter) {
+        return pomFilterContainer.addFilter(name, filter);
+    }
+
+    public void filter(Closure filter) {
+        pomFilterContainer.filter(filter);
+    }
+
+    public PublishFilter filter(String name) {
+        return pomFilterContainer.filter(name);
+    }
+
+    public MavenPom pom(String name) {
+        return pomFilterContainer.pom(name);
+    }
+
+    public MavenPom pom(Closure configureClosure) {
+        return pomFilterContainer.pom(configureClosure);
+    }
+
+    public MavenPom pom(String name, Closure configureClosure) {
+        return pomFilterContainer.pom(name, configureClosure);
+    }
+
+    public Iterable<PomFilter> getActivePomFilters() {
+        return pomFilterContainer.getActivePomFilters();
+    }
+
+    public PomFilterContainer getPomFilterContainer() {
+        return pomFilterContainer;
+    }
+
+    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/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java
new file mode 100644
index 0000000..bac0525
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployer.java
@@ -0,0 +1,129 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.DeployTask;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.tools.ant.Project;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.MavenDeployer;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.internal.Factory;
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
+import org.gradle.logging.LoggingManagerInternal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDeployer {
+    private RemoteRepository remoteRepository;
+
+    private RemoteRepository remoteSnapshotRepository;
+
+    private Factory<CustomDeployTask> deployTaskFactory = new DefaultDeployTaskFactory();
+
+    private Configuration configuration;
+
+    // todo remove this property once configuration can handle normal file system dependencies
+    private List<File> protocolProviderJars = new ArrayList<File>();
+
+    private boolean uniqueVersion = true;
+
+    public BaseMavenDeployer(PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        super(pomFilterContainer, artifactPomContainer, loggingManager);
+    }
+
+    protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
+        CustomDeployTask deployTask = deployTaskFactory.create();
+        deployTask.setProject(project);
+        deployTask.setUniqueVersion(isUniqueVersion());
+        addProtocolProvider(deployTask);
+        addRemoteRepositories(deployTask);
+        return deployTask;
+    }
+
+    private void addProtocolProvider(CustomDeployTask deployTask) {
+        PlexusContainer plexusContainer = deployTask.getContainer();
+        for (File wagonProviderJar : getJars()) {
+            try {
+                plexusContainer.addJarResource(wagonProviderJar);
+            } catch (PlexusContainerException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private List<File> getJars() {
+        return configuration != null ? new ArrayList<File>(configuration.resolve()) : protocolProviderJars;
+    }
+
+    private void addRemoteRepositories(DeployTask deployTask) {
+        deployTask.addRemoteRepository(remoteRepository);
+        deployTask.addRemoteSnapshotRepository(remoteSnapshotRepository);
+    }
+
+    public RemoteRepository getRepository() {
+        return remoteRepository;
+    }
+
+    public void setRepository(Object remoteRepository) {
+        this.remoteRepository = (RemoteRepository) remoteRepository;
+    }
+
+    public RemoteRepository getSnapshotRepository() {
+        return remoteSnapshotRepository;
+    }
+
+    public void setSnapshotRepository(Object remoteSnapshotRepository) {
+        this.remoteSnapshotRepository = (RemoteRepository) remoteSnapshotRepository;
+    }
+
+    public Factory<CustomDeployTask> getDeployTaskFactory() {
+        return deployTaskFactory;
+    }
+
+    public void setDeployTaskFactory(Factory<CustomDeployTask> deployTaskFactory) {
+        this.deployTaskFactory = deployTaskFactory;
+    }
+
+    public void addProtocolProviderJars(Collection<File> jars) {
+        protocolProviderJars.addAll(jars);
+    }
+
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    public boolean isUniqueVersion() {
+        return uniqueVersion;
+    }
+
+    public void setUniqueVersion(boolean uniqueVersion) {
+        this.uniqueVersion = uniqueVersion;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java
new file mode 100644
index 0000000..df93aac
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstaller.java
@@ -0,0 +1,50 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.InstallTask;
+import org.apache.tools.ant.Project;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.internal.Factory;
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
+import org.gradle.logging.LoggingManagerInternal;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseMavenInstaller extends AbstractMavenResolver {
+    private Factory<CustomInstallTask> installTaskFactory = new DefaultInstallTaskFactory();
+
+    public BaseMavenInstaller(PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        super(pomFilterContainer, artifactPomContainer, loggingManager);
+        mavenSettingsSupplier = new MaybeUserMavenSettingsSupplier();
+    }
+
+    protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
+        InstallTask installTask = installTaskFactory.create();
+        installTask.setProject(project);
+        return installTask;
+    }
+
+    public Factory<CustomInstallTask> getInstallTaskFactory() {
+        return installTaskFactory;
+    }
+
+    public void setInstallTaskFactory(Factory<CustomInstallTask> installTaskFactory) {
+        this.installTaskFactory = installTaskFactory;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
new file mode 100644
index 0000000..78164ac
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
@@ -0,0 +1,44 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.DeployTask;
+import org.apache.maven.settings.Settings;
+import org.codehaus.plexus.PlexusContainer;
+
+/**
+ * We could also use reflection to get hold of the container property. But this would make it harder
+ * to use a Mock for this class.
+ *
+ * @author Hans Dockter
+ */
+public class CustomDeployTask extends DeployTask implements CustomInstallDeployTaskSupport {
+    @Override
+    public synchronized Settings getSettings() {
+        return super.getSettings();
+    }
+    
+    @Override
+    public synchronized PlexusContainer getContainer() {
+        return super.getContainer();
+    }
+
+    @Override
+    public void doExecute() {
+        LoggingHelper.injectLogger(getContainer(), getProject());
+        super.doExecute();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
new file mode 100644
index 0000000..072bd4f
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
@@ -0,0 +1,29 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.settings.Settings;
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.tools.ant.Project;
+
+/**
+ * @author Hans Dockter
+ */
+public interface CustomInstallDeployTaskSupport {
+    Settings getSettings();
+    Project getProject();
+    AttachedArtifact createAttach();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
new file mode 100644
index 0000000..c3c2bef
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
@@ -0,0 +1,37 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.InstallTask;
+import org.apache.maven.settings.Settings;
+
+/**
+ * @author Hans Dockter
+ */
+public class CustomInstallTask extends InstallTask implements CustomInstallDeployTaskSupport {
+    @Override
+    public synchronized Settings getSettings() {
+        return super.getSettings();   
+    }
+
+    @Override
+    public void doExecute() {
+        LoggingHelper.injectLogger(getContainer(), getProject());
+        super.doExecute();
+    }
+
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactory.java
new file mode 100644
index 0000000..6ae4016
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.gradle.internal.Factory;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDeployTaskFactory implements Factory<CustomDeployTask> {
+    public CustomDeployTask create() {
+        return new CustomDeployTask();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java
new file mode 100644
index 0000000..93160b5
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.publication.maven.internal.ExcludeRuleConverter;
+
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
+    public Exclusion convert(ExcludeRule excludeRule) {
+        if (isConvertable(excludeRule)) {
+            Exclusion exclusion = new Exclusion();
+            exclusion.setGroupId(excludeRule.getGroup());
+            exclusion.setArtifactId(excludeRule.getModule());
+            return exclusion;
+        }
+        return null;
+    }
+
+    private boolean isConvertable(ExcludeRule excludeRule) {
+        return excludeRule.getGroup()!=null && excludeRule.getModule()!=null;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy
new file mode 100644
index 0000000..3fafaa0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployer.groovy
@@ -0,0 +1,51 @@
+/*
+ * 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.publication.maven.internal.ant
+
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer
+import org.gradle.api.artifacts.maven.PomFilterContainer
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer
+
+import org.gradle.logging.LoggingManagerInternal
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultGroovyMavenDeployer extends BaseMavenDeployer implements GroovyMavenDeployer, PomFilterContainer {
+    public static final String REPOSITORY_BUILDER = "repository"
+    public static final String SNAPSHOT_REPOSITORY_BUILDER = 'snapshotRepository'
+    
+    private RepositoryBuilder repositoryBuilder = new RepositoryBuilder()
+
+    DefaultGroovyMavenDeployer(PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        super(pomFilterContainer, artifactPomContainer, loggingManager)
+    }
+    
+    def methodMissing(String name, args) {
+        if (name == REPOSITORY_BUILDER || name == SNAPSHOT_REPOSITORY_BUILDER) {
+            Object repository = InvokerHelper.invokeMethod(repositoryBuilder, REPOSITORY_BUILDER, args)
+            if (name == REPOSITORY_BUILDER) {
+                setRepository(repository)
+            } else {
+                setSnapshotRepository(repository)
+            }
+            return repository;
+        } else {
+            throw new MissingMethodException(name, this.class, args)
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultInstallTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultInstallTaskFactory.java
new file mode 100644
index 0000000..ade162e
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultInstallTaskFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.gradle.internal.Factory;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultInstallTaskFactory implements Factory<CustomInstallTask> {
+    public CustomInstallTask create() {
+        return new CustomInstallTask();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisher.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisher.groovy
new file mode 100644
index 0000000..5aa1d6f
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisher.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.ant
+
+import org.apache.tools.ant.Project
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.api.publication.maven.MavenPublication
+import org.gradle.api.publication.maven.MavenPublisher
+import org.apache.maven.artifact.ant.*
+
+class DefaultMavenPublisher implements MavenPublisher {
+    private final File localRepoDir
+    private final TemporaryFileProvider temporaryFileProvider
+
+    DefaultMavenPublisher(TemporaryFileProvider temporaryFileProvider) {
+        this(null, temporaryFileProvider)
+    }
+
+    DefaultMavenPublisher(File localRepoDir, TemporaryFileProvider temporaryFileProvider) {
+        this.localRepoDir = localRepoDir
+        this.temporaryFileProvider = temporaryFileProvider
+    }
+
+    void install(MavenPublication publication) {
+        def task = new InstallTask()
+        if (localRepoDir) {
+            def repository = new LocalRepository()
+            repository.path = localRepoDir
+            task.addLocalRepository(repository)
+        }
+        execute(publication, task)
+    }
+
+    void deploy(MavenPublication publication, MavenArtifactRepository repository) {
+        def task = new DeployTask()
+        task.addRemoteRepository(new RemoteRepository())
+        task.remoteRepository.url = repository.url
+        execute(publication, task)
+    }
+
+    private def execute(MavenPublication publication, InstallDeployTaskSupport task) {
+        Project project = new Project()
+        task.setProject(project)
+
+        File pomFile = temporaryFileProvider.newTemporaryFile("${publication.artifactId}.pom")
+        pomFile.text = """
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>$publication.groupId</groupId>
+  <artifactId>$publication.artifactId</artifactId>
+  <packaging>jar</packaging>
+  <version>$publication.version</version>
+</project>
+"""
+
+        Pom pom = new Pom();
+        pom.project = task.project;
+        pom.file = pomFile
+        task.addPom(pom);
+
+        if (publication.mainArtifact.classifier) {
+            AttachedArtifact mainArtifact = task.createAttach()
+            mainArtifact.classifier = publication.mainArtifact.classifier
+            mainArtifact.file = publication.mainArtifact.file
+            mainArtifact.type = publication.mainArtifact.extension
+        } else {
+            task.file = publication.mainArtifact.file
+        }
+
+        publication.subArtifacts.each { artifact ->
+            AttachedArtifact attachedArtifact = task.createAttach()
+            attachedArtifact.classifier = artifact.classifier
+            attachedArtifact.file = artifact.file
+            attachedArtifact.type = artifact.extension
+        }
+
+        task.execute()
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
new file mode 100644
index 0000000..6025fe7
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
@@ -0,0 +1,149 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.DependencyArtifact;
+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.publication.maven.internal.ExcludeRuleConverter;
+import org.gradle.api.publication.maven.internal.PomDependenciesConverter;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPomDependenciesConverter implements PomDependenciesConverter {
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public DefaultPomDependenciesConverter(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    public List<org.apache.maven.model.Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> 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), dependencyToConfigurations.get(dependency));
+            } else {
+                addFromArtifactDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency), dependencyToConfigurations.get(dependency));
+            }
+        }
+        return mavenDependencies;
+    }
+    
+    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));
+            if (!useScope(conf2ScopeMappingContainer, conf2ScopeDependencyMapping)) {
+                continue;
+            }
+            dependencyToScope.put(findDependency(dependency, conf2ScopeDependencyMapping.getConfiguration()),
+                    conf2ScopeDependencyMapping.getScope());
+        }
+        return dependencyToScope;
+    }
+
+    private ModuleDependency findDependency(ModuleDependency dependency, Configuration configuration) {
+        for (ModuleDependency configurationDependency : configuration.getDependencies().withType(ModuleDependency.class)) {
+            if (dependency.equals(configurationDependency)) {
+                return configurationDependency;
+            }
+        }
+        throw new GradleException("Dependency could not be found. We should never get here!");
+    }
+
+    private boolean useScope(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Conf2ScopeMapping conf2ScopeMapping) {
+        return conf2ScopeMapping.getScope() != null || !conf2ScopeMappingContainer.isSkipUnmappedConfs();
+    }
+
+    private Map<ModuleDependency, Set<Configuration>> createDependencyToConfigurationsMap(Set<Configuration> configurations) {
+        Map<ModuleDependency, Set<Configuration>> dependencySetMap = new HashMap<ModuleDependency, Set<Configuration>>();
+        for (Configuration configuration : configurations) {
+            for (ModuleDependency dependency : configuration.getDependencies().withType(ModuleDependency.class)) {
+                if (dependencySetMap.get(dependency) == null) {
+                    dependencySetMap.put(dependency, new HashSet<Configuration>());
+                }
+                dependencySetMap.get(dependency).add(configuration);
+            }
+        }
+        return dependencySetMap;
+    }
+
+    private void addFromArtifactDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope, 
+            Set<Configuration> configurations) {
+        for (DependencyArtifact artifact : dependency.getArtifacts()) {
+            mavenDependencies.add(createMavenDependencyFromArtifactDescriptor(dependency, artifact, scope, configurations));
+        }
+    }
+
+    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,
+            Set<Configuration> configurations) {
+        return createMavenDependency(dependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier(), configurations);
+    }
+
+    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,
+            Set<Configuration> configurations) {
+        Dependency mavenDependency =  new Dependency();
+        mavenDependency.setGroupId(dependency.getGroup());
+        mavenDependency.setArtifactId(name);
+        mavenDependency.setVersion(dependency.getVersion());
+        mavenDependency.setType(type);
+        mavenDependency.setScope(scope);
+        mavenDependency.setOptional(false);
+        mavenDependency.setClassifier(classifier);
+        mavenDependency.setExclusions(getExclusions(dependency, configurations));
+        return mavenDependency;
+    }
+
+    private List<Exclusion> getExclusions(ModuleDependency dependency, Set<Configuration> configurations) {
+        List<Exclusion> mavenExclusions = new ArrayList<Exclusion>();
+        Set<ExcludeRule> excludeRules = new HashSet<ExcludeRule>(dependency.getExcludeRules());
+        for (Configuration configuration : configurations) {
+            excludeRules.addAll(configuration.getExcludeRules());
+        }
+        for (ExcludeRule excludeRule : excludeRules) {
+            Exclusion mavenExclusion = (Exclusion) excludeRuleConverter.convert(excludeRule);
+            if (mavenExclusion != null) {
+                mavenExclusions.add(mavenExclusion);
+            }
+        }
+        return mavenExclusions;
+    }
+
+    public ExcludeRuleConverter getExcludeRuleConverter() {
+        return excludeRuleConverter;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java
new file mode 100644
index 0000000..2f7b1ac
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplier.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.ant;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Szczepan Faber, created at: 3/29/11
+ */
+public class EmptyMavenSettingsSupplier implements MavenSettingsSupplier {
+
+    private final TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
+    private File settingsXml;
+
+    public void supply(InstallDeployTaskSupport installDeployTaskSupport) {
+        try {
+            settingsXml = temporaryFileProvider.createTemporaryFile("gradle_empty_settings", ".xml");
+            FileUtils.writeStringToFile(settingsXml, "<settings/>");
+            settingsXml.deleteOnExit();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        installDeployTaskSupport.setSettingsFile(settingsXml);
+    }
+
+    public void done() {
+        if (settingsXml != null) {
+            settingsXml.delete();
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java
new file mode 100644
index 0000000..759a5b0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/LoggingHelper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.AntDownloadMonitor;
+import org.apache.maven.artifact.manager.DefaultWagonManager;
+import org.apache.maven.artifact.manager.WagonManager;
+import org.apache.tools.ant.Project;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+
+import java.lang.reflect.Field;
+
+/**
+ * @author Hans Dockter
+ */
+public class LoggingHelper {
+    public static void injectLogger(PlexusContainer container, Project project) {
+        try {
+            WagonManager wagonManager = (WagonManager) container.lookup(WagonManager.ROLE);
+            Field field = DefaultWagonManager.class.getDeclaredField("downloadMonitor");
+            field.setAccessible(true);
+            AntDownloadMonitor antDownloadMonitor = (AntDownloadMonitor) field.get(wagonManager);
+            antDownloadMonitor.setProject(project);
+        } catch (ComponentLookupException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java
new file mode 100644
index 0000000..8efe218
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MavenSettingsSupplier.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+
+/**
+ * @author Szczepan Faber, created at: 3/29/11
+ */
+public interface MavenSettingsSupplier {
+    void done();
+    void supply(InstallDeployTaskSupport installDeployTaskSupport);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java
new file mode 100644
index 0000000..01fef7e
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplier.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenFileLocations;
+import org.gradle.api.internal.artifacts.mvnsettings.MavenFileLocations;
+
+import java.io.File;
+
+/**
+ * @author Szczepan Faber, created at: 3/29/11
+ */
+public class MaybeUserMavenSettingsSupplier implements MavenSettingsSupplier {
+
+    MavenSettingsSupplier emptySettingsSupplier = new EmptyMavenSettingsSupplier();
+    MavenFileLocations mavenFileLocations = new DefaultMavenFileLocations();
+
+    public void supply(InstallDeployTaskSupport installDeployTaskSupport) {
+        File userSettings = mavenFileLocations.getUserSettingsFile();
+        if (userSettings.exists()) {
+            installDeployTaskSupport.setSettingsFile(userSettings);
+            return;
+        }
+
+        emptySettingsSupplier.supply(installDeployTaskSupport);
+    }
+
+    public void done() {
+        emptySettingsSupplier.done();
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java
new file mode 100644
index 0000000..110fc10
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryBuilder.java
@@ -0,0 +1,35 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.Proxy;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.artifact.ant.RepositoryPolicy;
+
+/**
+ * @author Hans Dockter
+ */
+public class RepositoryBuilder extends FactoryBuilderSupport {
+    public RepositoryBuilder() {
+        registerFactory("repository", new RepositoryFactory(RemoteRepository.class));
+        registerBeanFactory("authentication", Authentication.class);
+        registerBeanFactory("proxy", Proxy.class);
+        registerBeanFactory("snapshots", RepositoryPolicy.class);
+        registerBeanFactory("releases", RepositoryPolicy.class);
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.java
new file mode 100644
index 0000000..7bf1586
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/RepositoryFactory.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.publication.maven.internal.ant;
+
+import groovy.swing.factory.BeanFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.Proxy;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.artifact.ant.RepositoryPolicy;
+
+/**
+ * @author Hans Dockter
+ */
+public class RepositoryFactory extends BeanFactory {
+    public RepositoryFactory(Class klass) {
+        super(klass);
+    }
+
+    public RepositoryFactory(Class klass, boolean leaf) {
+        super(klass, leaf);
+    }
+
+    public void setChild(FactoryBuilderSupport builder, Object parent, Object child) {
+        if (child instanceof Authentication) {
+            getRepository(parent).addAuthentication((Authentication) child);
+        } else if (child instanceof Proxy) {
+            getRepository(parent).addProxy((Proxy) child);
+        } else if (child instanceof RepositoryPolicy) {
+            if (builder.getCurrentName().equals("snapshots")) {
+                getRepository(parent).addSnapshots((RepositoryPolicy) child);
+            } else {
+                getRepository(parent).addReleases((RepositoryPolicy) child);
+            }
+        }
+    }
+
+    private RemoteRepository getRepository(Object parent) {
+        return (RemoteRepository) parent;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenArtifact.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenArtifact.groovy
new file mode 100644
index 0000000..2328a86
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenArtifact.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.model
+
+import org.gradle.api.publication.maven.MavenArtifact
+
+class DefaultMavenArtifact implements MavenArtifact {
+    String classifier
+    String extension
+    File file
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenDependency.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenDependency.groovy
new file mode 100644
index 0000000..29d19be
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenDependency.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.model
+
+import org.gradle.api.artifacts.ExternalDependency
+import org.gradle.api.publication.maven.MavenDependency
+import org.gradle.api.publication.maven.MavenScope
+
+/**
+ * @author: Szczepan Faber, created at: 6/14/11
+ */
+class DefaultMavenDependency implements MavenDependency {
+
+    DefaultMavenDependency() {}
+
+    DefaultMavenDependency(ExternalDependency externalDependency, MavenScope scope) {
+        this.artifactId = externalDependency.name
+        this.groupId = externalDependency.group
+        this.version = externalDependency.version
+        //Taking the classifier from the first artifact if exists
+        //This is huge simplification however I don't yet understand why would I have more than 1 artifact in a dependency
+        if (!externalDependency.artifacts.empty) {
+            this.classifier = externalDependency.artifacts.iterator().next().classifier
+        } else {
+            this.classifier = null
+        }
+
+        this.scope = scope
+        this.optional = false
+    }
+
+    String groupId
+    String artifactId
+    String version
+    String classifier
+    MavenScope scope
+    boolean optional
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenPublication.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenPublication.groovy
new file mode 100644
index 0000000..28ad7a2
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/model/DefaultMavenPublication.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.model
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+import org.gradle.api.internal.artifacts.repositories.DefaultMavenArtifactRepository
+import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials
+import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.api.publication.maven.MavenArtifact
+import org.gradle.api.publication.maven.MavenDependency
+import org.gradle.api.publication.maven.MavenPomCustomizer
+import org.gradle.api.publication.maven.MavenPublication
+import org.gradle.util.ConfigureUtil
+
+class DefaultMavenPublication implements MavenPublication {
+    String modelVersion
+    String groupId
+    String artifactId
+    String version
+    String packaging
+    String description
+    MavenArtifact mainArtifact
+    List<MavenArtifact> subArtifacts = []
+    List<MavenDependency> dependencies = []
+    MavenPomCustomizer pom
+    MavenArtifactRepository repository = new DefaultMavenArtifactRepository(new IdentityFileResolver(), new DefaultPasswordCredentials(), null, null, null)
+
+    void repository(Closure c) {
+        ConfigureUtil.configure(c, getRepository())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/DependenciesConverter.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/DependenciesConverter.groovy
new file mode 100644
index 0000000..df9c874
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/DependenciesConverter.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.modelbuilder
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.ExternalDependency
+import org.gradle.api.publication.maven.MavenDependency
+import org.gradle.api.publication.maven.MavenScope
+import org.gradle.api.publication.maven.internal.model.DefaultMavenDependency
+
+/**
+ * @author: Szczepan Faber, created at: 6/21/11
+ */
+class DependenciesConverter {
+    List<MavenDependency> convert(Project project) {
+        //First fundamental question is should we reuse Conf2ScopeMappingContainer / PomDependenciesConverter ? How far?
+
+        //should project dependencies be transformed into entries in the pom?
+        //how to approach the case when the ExternalDependency has multiple artifcts? Don't know when it happens, though
+        //we could check if war plugin was applied and deal with providedCompile and providedRuntime?
+        //should we make sure that there are no duplicate entries e.g. the same library in both compile scope and test scope
+
+        //0. I absolutely hate it but the goal today is not to make the DSL perfect but to have working pre-population of the model
+        //1. It suffers from the fundamental convention mapping issue - non mutable collections
+        //2. It is hard to reconfigure by the user (Imagine the user typing all this code what I did below if he needs to put a dependency from a different configuration)
+        //3. I don't want to pass Configurations to the maven model. We went down that path with ide plugins and it bites us hard. We need the DependencySet!
+        def out = new LinkedList()
+        project.configurations['compile'].dependencies.withType(ExternalDependency).each {
+            out << new DefaultMavenDependency(it, MavenScope.COMPILE);
+        }
+
+        project.configurations['testCompile'].dependencies.withType(ExternalDependency).each {
+            out << new DefaultMavenDependency(it, MavenScope.TEST);
+        }
+
+        project.configurations['runtime'].dependencies.withType(ExternalDependency).each {
+            out << new DefaultMavenDependency(it, MavenScope.RUNTIME);
+        }
+
+        project.configurations['testRuntime'].dependencies.withType(ExternalDependency).each {
+            out << new DefaultMavenDependency(it, MavenScope.TEST);
+        }
+        return out
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy
new file mode 100644
index 0000000..5a20a48
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy
@@ -0,0 +1,80 @@
+/*
+ * 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.publication.maven.internal.modelbuilder
+
+import org.gradle.api.Project
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.publication.maven.MavenPublication
+import org.gradle.api.publication.maven.internal.model.DefaultMavenArtifact
+import org.gradle.api.publication.maven.internal.model.DefaultMavenPublication
+import org.gradle.api.tasks.bundling.Jar
+
+/**
+ * @author: Szczepan Faber, created at: 5/13/11
+ */
+class MavenPublicationBuilder {
+
+    MavenPublication build(Project project) {
+        DefaultMavenPublication publication = project.services.get(Instantiator).newInstance(DefaultMavenPublication)
+        publication.mainArtifact = project.services.get(Instantiator).newInstance(DefaultMavenArtifact)
+        //@Peter, I was prolific with comments because I wasn't sure I'll be able to pair soon. Get rid of comments if you like.
+
+        //basic values can be easily extracted from the project:
+        publication.conventionMapping.description = { project.description }
+        publication.conventionMapping.groupId = { project.group.toString()? project.group.toString() : 'unspecified.group'}
+        publication.conventionMapping.version = { project.version.toString() }
+        publication.modelVersion = '4.0.0'
+
+        project.plugins.withType(JavaPlugin) {
+            publication.packaging = 'jar'
+            //I like the simple way of setting the packaging above. There're other theoretical ways of getting the packaging, but they're ugly:
+            //or: we can extract packaging from maven installer but that's difficult as the code is complex
+            //or: publication.conventionMapping.groupId = { project.convention.plugins.maven.pom().groupId }
+            //or: publication.packaging = project.convention.plugins.maven.pom().packaging
+
+            withTask(project, 'jar', Jar) { Jar jar ->
+                //It makes more sense to me to get the artifact info from the 'jar' section of the build because 'jar' task is the way for a user to declaratively configure version/baseName.
+                publication.conventionMapping.artifactId = { jar.baseName }
+                publication.conventionMapping.version = { jar.version }
+
+                //We can also get this info from the project... se below
+                //or: publication.conventionMapping.artifactId = { project.convention.plugins.base.archivesBaseName }
+                //or: publication.conventionMapping.version = { project.version.toString() }
+
+                //again it feels natural to get it from the 'jar' section of the build.
+                publication.mainArtifact.conventionMapping.classifier = { project.jar.classifier }
+                publication.mainArtifact.conventionMapping.extension = { project.jar.extension }
+                publication.mainArtifact.conventionMapping.file = { project.jar.archivePath }
+            }
+
+            publication.conventionMapping.dependencies = {
+                new DependenciesConverter().convert(project)
+            }
+        }
+
+        return publication
+    }
+
+    void withTask(Project project, String taskName, Class taskType, Closure configureTask) {
+        project.tasks.withType(taskType) {
+            if (it.name == taskName) {
+                configureTask(it)
+            }
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/CustomModelBuilder.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/CustomModelBuilder.java
new file mode 100644
index 0000000..e5fe98c
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/CustomModelBuilder.java
@@ -0,0 +1,82 @@
+/*
+ * 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.publication.maven.internal.pombuilder;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import groovy.util.FactoryBuilderSupport;
+
+import org.apache.maven.model.Model;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
+import org.slf4j.LoggerFactory;
+import org.sonatype.maven.polyglot.execute.ExecuteManager;
+import org.sonatype.maven.polyglot.execute.ExecuteManagerImpl;
+import org.sonatype.maven.polyglot.groovy.builder.ModelBuilder;
+
+/**
+* This is a slightly modified version as shipped with polyglot Maven.
+*/
+public class CustomModelBuilder extends ModelBuilder {
+
+    public CustomModelBuilder(Model model) {
+        ExecuteManager executeManager = new ExecuteManagerImpl();
+        setProp(executeManager.getClass(), executeManager, "log",
+                new PlexusLoggerAdapter(LoggerFactory.getLogger(ExecuteManagerImpl.class)));
+        setProp(ModelBuilder.class, this, "executeManager", executeManager);
+        setProp(ModelBuilder.class, this, "log",
+                new PlexusLoggerAdapter(LoggerFactory.getLogger(ModelBuilder.class)));
+        try {
+            initialize();
+        } catch (InitializationException e) {
+            throw new RuntimeException(e);
+        }
+        Map factories = (Map) getProp(FactoryBuilderSupport.class, this, "factories");
+        factories.remove("project");
+        ModelFactory modelFactory = new ModelFactory(model);
+        registerFactory(modelFactory.getName(), null, modelFactory);
+    }
+
+    public static void setProp(Class c, Object obj, String fieldName, Object value) {
+        try {
+            Field f = c.getDeclaredField(fieldName);
+            f.setAccessible(true); // solution
+            f.set(obj, value); // IllegalAccessException
+            // production code should handle these exceptions more gracefully
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Object getProp(Class c, Object obj, String fieldName) {
+        try {
+            Field f = c.getDeclaredField(fieldName);
+            f.setAccessible(true); // solution
+            return f.get(obj); // IllegalAccessException
+            // production code should handle these exceptions more gracefully
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/ModelFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/ModelFactory.java
new file mode 100644
index 0000000..f1935f8
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/ModelFactory.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.publication.maven.internal.pombuilder;
+
+import java.util.Map;
+
+import groovy.util.FactoryBuilderSupport;
+
+import org.apache.maven.model.Model;
+import org.sonatype.maven.polyglot.groovy.builder.factory.NamedFactory;
+
+/**
+ * This is a slightly modified version as shipped with polyglot Maven.
+ */
+public class ModelFactory extends NamedFactory {
+    private Model model;
+
+    public ModelFactory(Model model) {
+        super("project");
+        this.model = model;
+    }
+
+    public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attrs) throws InstantiationException, IllegalAccessException {
+        return model;
+    }
+
+    @Override
+    public void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) {
+        Model model = (Model) node;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/PlexusLoggerAdapter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/PlexusLoggerAdapter.java
new file mode 100644
index 0000000..a0543fe
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/pombuilder/PlexusLoggerAdapter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.publication.maven.internal.pombuilder;
+
+import org.codehaus.plexus.logging.Logger;
+
+/**
+ * @author Hans Dockter
+ */
+public class PlexusLoggerAdapter implements Logger {
+    org.slf4j.Logger logger;
+
+    public PlexusLoggerAdapter(org.slf4j.Logger logger) {
+        this.logger = logger;
+    }
+
+    public void debug(String s) {
+        logger.debug(s);
+    }
+
+    public void debug(String s, Throwable throwable) {
+        logger.debug(s, throwable);
+    }
+
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    public void info(String s) {
+        logger.info(s);
+    }
+
+    public void info(String s, Throwable throwable) {
+        logger.info(s, throwable);
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public void warn(String s) {
+        logger.warn(s);
+    }
+
+    public void warn(String s, Throwable throwable) {
+        logger.warn(s, throwable);
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
+    }
+
+    public void error(String s) {
+        logger.error(s);
+    }
+
+    public void error(String s, Throwable throwable) {
+        logger.error(s, throwable);
+    }
+
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public void fatalError(String s) {
+        logger.error(s);
+    }
+
+    public void fatalError(String s, Throwable throwable) {
+        logger.error(s, throwable);
+    }
+
+    public boolean isFatalErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public Logger getChildLogger(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getThreshold() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setThreshold(int threshold) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getName() {
+        return logger.getName();
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
new file mode 100644
index 0000000..43cbe4c
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.artifacts.maven;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JUnit4.class)
+public class Conf2ScopeMappingTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private Conf2ScopeMapping conf2ScopeMapping;
+    private static final String TEST_SCOPE = "somescope";
+    private static final Integer TEST_PRIORITY = 10;
+    private final Configuration configuration = context.mock(Configuration.class);
+
+    @Before
+    public void setUp() {
+        conf2ScopeMapping = new Conf2ScopeMapping(TEST_PRIORITY, configuration, TEST_SCOPE);
+    }
+
+    @Test
+    public void init() {
+        assertEquals(TEST_PRIORITY, conf2ScopeMapping.getPriority());
+        assertEquals(configuration, conf2ScopeMapping.getConfiguration());
+        assertEquals(TEST_SCOPE, conf2ScopeMapping.getScope());
+    }
+
+    @Test
+    public void equality() {
+        assertTrue(conf2ScopeMapping.equals(new Conf2ScopeMapping(TEST_PRIORITY, configuration, TEST_SCOPE)));
+        assertFalse(conf2ScopeMapping.equals(new Conf2ScopeMapping(TEST_PRIORITY + 10, configuration, TEST_SCOPE)));
+    }
+
+    @Test
+    public void hashcode() {
+        assertEquals(conf2ScopeMapping.hashCode(), new Conf2ScopeMapping(TEST_PRIORITY, configuration, TEST_SCOPE).hashCode());
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
index 114a4ca..1e9a54d 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
@@ -20,13 +20,16 @@ import org.gradle.util.HelperUtil
 import org.gradle.api.artifacts.maven.MavenPom
 
 import spock.lang.Specification
+import org.gradle.api.publication.maven.internal.MavenFactory
+import org.gradle.api.publication.maven.internal.DefaultMavenFactory
 
 /**
  * @author Hans Dockter
  */
 class MavenPluginConventionTest extends Specification {
     DefaultProject project = HelperUtil.createRootProject()
-    MavenPluginConvention mavenPluginConvention = new MavenPluginConvention(project)
+    MavenFactory mavenFactory = new DefaultMavenFactory()
+    MavenPluginConvention mavenPluginConvention = new MavenPluginConvention(project, mavenFactory)
 
     def pomShouldCreateMavenPom() {
         project.group = 'someGroup'
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
index 07f7568..fe8cc04 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
@@ -19,16 +19,21 @@ import org.gradle.api.Task;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.tasks.Upload;
 import org.gradle.util.HelperUtil;
+import org.hamcrest.Matchers;
+
+import java.io.File;
+import java.util.Set;
+
 import static org.gradle.util.WrapUtil.toSet;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-import java.util.Set;
-
 /**
  * @author Hans Dockter
  */
@@ -37,6 +42,22 @@ public class MavenPluginTest {
     private final MavenPlugin mavenPlugin = new MavenPlugin();
 
     @org.junit.Test
+    public void addsConventionToProject() {
+        mavenPlugin.apply(project);
+
+        assertThat(project.getConvention().getPlugin(MavenPluginConvention.class), Matchers.<MavenPluginConvention>notNullValue());
+    }
+    
+    @org.junit.Test
+    public void defaultConventionValues() {
+        mavenPlugin.apply(project);
+
+        MavenPluginConvention convention = project.getConvention().getPlugin(MavenPluginConvention.class);
+        assertThat(convention.getMavenPomDir(), equalTo(new File(project.getBuildDir(), "poms")));
+        assertThat(convention.getConf2ScopeMappings(), notNullValue());
+    }
+
+    @org.junit.Test
     public void applyWithWarPlugin() {
         project.getPlugins().apply(WarPlugin.class);
         mavenPlugin.apply(project);
@@ -51,7 +72,7 @@ public class MavenPluginTest {
     }
 
     private void assertHasConfigurationAndMapping(DefaultProject project, String configurationName, String scope, int priority) {
-        Conf2ScopeMappingContainer scopeMappingContainer = project.getRepositories().getMavenScopeMappings();
+        Conf2ScopeMappingContainer scopeMappingContainer = project.getConvention().getPlugin(MavenPluginConvention.class).getConf2ScopeMappings();
         ConfigurationContainer configurationContainer = project.getConfigurations();
         Conf2ScopeMapping mapping = scopeMappingContainer.getMappings().get(configurationContainer.getByName(configurationName));
         assertThat(mapping.getScope(), equalTo(scope));
@@ -77,6 +98,29 @@ public class MavenPluginTest {
     }
 
     @org.junit.Test
+    public void addsAndConfiguresAnInstallTask() {
+        project.getPlugins().apply(JavaPlugin.class);
+        mavenPlugin.apply(project);
+
+        Upload task = project.getTasks().withType(Upload.class).getByName(MavenPlugin.INSTALL_TASK_NAME);
+        assertThat(task.getRepositories().get(0), instanceOf(MavenResolver.class));
+    }
+
+    @org.junit.Test
+    public void addsConventionMappingToTheRepositoryContainerOfEachUploadTask() {
+        project.getPlugins().apply(JavaPlugin.class);
+        mavenPlugin.apply(project);
+
+        Upload task = project.getTasks().withType(Upload.class).getByName(MavenPlugin.INSTALL_TASK_NAME);
+        MavenRepositoryHandlerConvention convention = new DslObject(task.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
+        assertThat(convention, notNullValue());
+
+        task = project.getTasks().add("customUpload", Upload.class);
+        convention = new DslObject(task.getRepositories()).getConvention().getPlugin(MavenRepositoryHandlerConvention.class);
+        assertThat(convention, notNullValue());
+    }
+
+    @org.junit.Test
     public void applyWithoutWarPlugin() {
         mavenPlugin.apply(project);
         assertThat(project.getConfigurations().findByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME),
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainerTest.java
new file mode 100644
index 0000000..46241cf
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/BasePomFilterContainerTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.publication.maven.internal;
+
+import com.google.common.collect.Sets;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.internal.Factory;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class BasePomFilterContainerTest {
+    private static final String TEST_NAME = "testName";
+    
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private BasePomFilterContainer pomFilterContainer;
+    @SuppressWarnings("unchecked")
+    protected Factory<MavenPom> mavenPomFactoryMock = context.mock(Factory.class);
+    protected MavenPom pomMock;
+    protected PomFilter pomFilterMock;
+    protected PublishFilter publishFilterMock;
+
+
+    protected BasePomFilterContainer createPomFilterContainer() {
+        return new BasePomFilterContainer(mavenPomFactoryMock);
+    }
+
+
+    @Before
+    public void setUp() {
+        pomFilterMock = context.mock(PomFilter.class);
+        pomMock = context.mock(MavenPom.class);
+        publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {
+            {
+                allowing(mavenPomFactoryMock).create();
+                will(returnValue(pomMock));
+            }
+        });
+        pomFilterContainer = createPomFilterContainer();
+        pomFilterContainer.setDefaultPomFilter(pomFilterMock);
+    }
+
+    @Test
+    public void init() {
+        pomFilterContainer = new BasePomFilterContainer(mavenPomFactoryMock);
+        assertNotNull(pomFilterContainer.getPom());
+        assertSame(PublishFilter.ALWAYS_ACCEPT, pomFilterContainer.getFilter());
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getFilterWithNullName() {
+        pomFilterContainer.filter((String) null);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getPomWithNullName() {
+        pomFilterContainer.pom((String) null);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addFilterWithNullName() {
+        pomFilterContainer.addFilter(null, PublishFilter.ALWAYS_ACCEPT);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addFilterWithNullFilter() {
+        pomFilterContainer.addFilter("somename", (PublishFilter) null);
+    }
+
+    @Test
+    public void getFilter() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
+        }});
+        assertSame(publishFilterMock, pomFilterContainer.getFilter());
+    }
+
+    @Test
+    public void setFilter() {
+        context.checking(new Expectations() {{
+            one(pomFilterMock).setFilter(publishFilterMock);
+        }});
+        pomFilterContainer.setFilter(publishFilterMock);
+    }
+
+    @Test
+    public void getPom() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterMock).getPomTemplate(); will(returnValue(pomMock));
+        }});
+        assertSame(pomMock, pomFilterContainer.getPom());
+    }
+
+    @Test
+    public void setPom() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterMock).setPomTemplate(pomMock);
+        }});
+        pomFilterContainer.setPom(pomMock);
+    }
+
+
+    @Test
+    public void addFilter() {
+        MavenPom pom = pomFilterContainer.addFilter(TEST_NAME, publishFilterMock);
+        assertSame(pom, pomMock);
+        assertSame(pomMock, pomFilterContainer.pom(TEST_NAME));
+        assertSame(publishFilterMock, pomFilterContainer.filter(TEST_NAME));
+    }
+
+    @Test
+    public void getActivePomFiltersWithDefault() {
+        Iterator<PomFilter> pomFilterIterator = pomFilterContainer.getActivePomFilters().iterator();
+        assertSame(pomFilterMock, pomFilterIterator.next());
+        assertFalse(pomFilterIterator.hasNext());
+    }
+
+    @Test
+    public void getActivePomFiltersWithAdditionalFilters() {
+        PublishFilter filter1 = context.mock(PublishFilter.class, "filter1");
+        PublishFilter filter2 = context.mock(PublishFilter.class, "filter2");
+        String testName1 = "name1";
+        String testName2 = "name2";
+        pomFilterContainer.addFilter(testName1, filter1);
+        pomFilterContainer.addFilter(testName2, filter2);
+        Set<PomFilter> actualActiveFilters = Sets.newLinkedHashSet(pomFilterContainer.getActivePomFilters());
+        assertEquals(2, actualActiveFilters.size());
+        checkIfInSet(testName1, filter1, actualActiveFilters);
+        checkIfInSet(testName2, filter2, actualActiveFilters);
+    }
+
+    private void checkIfInSet(String expectedName, PublishFilter expectedPublishFilter, Set<PomFilter> filters) {
+        for (PomFilter pomFilter : filters) {
+            if (areEqualPomFilter(expectedName, expectedPublishFilter, pomFilter)) {
+                return;
+            }
+        }
+        fail("Not in Set");
+    }
+
+    private boolean areEqualPomFilter(String expectedName, PublishFilter expectedPublishFilter, PomFilter pomFilter) {
+        if (!expectedName.equals(pomFilter.getName())) {
+            return false;
+        }
+        if (!(expectedPublishFilter == pomFilter.getFilter())) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainerTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomContainerTest.groovy
new file mode 100644
index 0000000..48a9417
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/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.publication.maven.internal
+
+import spock.lang.Specification
+
+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/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
new file mode 100644
index 0000000..906cdf7
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultArtifactPomTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.publication.maven.internal;
+
+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.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPomTest {
+    private DefaultArtifactPom artifactPom;
+    private MavenPom testPom;
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp() {
+        testPom = new DefaultMavenPom(context.mock(ConfigurationContainer.class), context.mock(Conf2ScopeMappingContainer.class),
+                context.mock(PomDependenciesConverter.class), context.mock(FileResolver.class));
+        artifactPom = new DefaultArtifactPom(testPom);
+    }
+
+    @Test
+    public void pomWithMainArtifact() {
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
+
+        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");
+
+        artifactPom.addArtifact(mainArtifact, mainFile);
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+
+        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));
+
+        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 pomWithClassifierArtifactsOnly() {
+        File classifierFile = new File("someClassifierFile");
+        Artifact classifierArtifact = createTestArtifact("someName", "javadoc", "zip");
+
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+
+        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("someName", "javadoc");
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addMainArtifactTwiceShouldThrowInvalidUserDataEx() {
+        Artifact mainArtifact = createTestArtifact("someName", null, "mainPackaging");
+        File mainFile = new File("someFile");
+        artifactPom.addArtifact(mainArtifact, mainFile);
+        artifactPom.addArtifact(mainArtifact, mainFile);
+    }
+
+    @Test
+    public void cannotAddMultipleArtifactsWithTheSameTypeAndClassifier() {
+
+        // No classifier
+        Artifact mainArtifact = createTestArtifact("someName", null);
+        artifactPom.addArtifact(mainArtifact, new File("someFile"));
+
+        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"));
+    }
+
+    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."));
+        }
+    }
+
+    @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) {
+        return createTestArtifact(name, classifier, "jar");
+    }
+
+    private Artifact createTestArtifact(String name, String classifier, String type) {
+        Map<String, String> extraAttributes = new HashMap<String, String>();
+        if (classifier != null) {
+            extraAttributes.put(Dependency.CLASSIFIER, classifier);
+        }
+        return new DefaultArtifact(ModuleRevisionId.newInstance("org", name, "1.0"), null, name, type, type, extraAttributes);
+    }
+
+    @Test
+    public void writePom() {
+        final MavenPom mavenPomMock = context.mock(MavenPom.class);
+        DefaultArtifactPom artifactPom = new DefaultArtifactPom(mavenPomMock);
+        final File somePomFile = new File(tmpDir.getDir(), "someDir/somePath");
+        context.checking(new Expectations() {{
+            allowing(mavenPomMock).getArtifactId();
+            will(returnValue("artifactId"));
+            one(mavenPomMock).writeTo(with(any(FileOutputStream.class)));
+        }});
+
+        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));
+    }
+
+    private <T> T singleItem(Iterable<? extends T> collection) {
+        List<T> elements = newArrayList(collection);
+        assertThat(elements.size(), equalTo(1));
+        return elements.get(0);
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java
new file mode 100644
index 0000000..d27e17b
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultConf2ScopeMappingContainerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JUnit4.class)
+public class DefaultConf2ScopeMappingContainerTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private DefaultConf2ScopeMappingContainer conf2ScopeMappingContainer;
+    private final Configuration testConf1 = context.mock(Configuration.class);
+    private final Configuration testConf2 = context.mock(Configuration.class);
+    private final Configuration testConf3 = context.mock(Configuration.class);
+    private static final String TEST_SCOPE_1 = "test";
+    private static final String TEST_SCOPE_2 = "test2";
+    private static final int TEST_PRIORITY_1 = 10;
+    private static final int TEST_PRIORITY_2 = 20;
+
+    @Before
+    public void setUp() {
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer();
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, testConf1, TEST_SCOPE_1);
+    }
+
+    @Test
+    public void init() {
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer();
+        assertTrue(conf2ScopeMappingContainer.isSkipUnmappedConfs());
+        assertEquals(0, conf2ScopeMappingContainer.getMappings().size());
+        Map<Configuration, Conf2ScopeMapping> testMappings = createTestMappings();
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer(testMappings);
+        assertNotSame(testMappings, conf2ScopeMappingContainer.getMappings());
+        assertEquals(testMappings, conf2ScopeMappingContainer.getMappings());
+    }
+
+    @Test
+    public void equalsAndHashCode() {
+        Map<Configuration, Conf2ScopeMapping> testMappings = createTestMappings();
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer(testMappings);
+        assertTrue(conf2ScopeMappingContainer.equals(new DefaultConf2ScopeMappingContainer(testMappings)));
+        assertEquals(conf2ScopeMappingContainer.hashCode(), new DefaultConf2ScopeMappingContainer(testMappings).hashCode());
+        conf2ScopeMappingContainer.addMapping(10, context.mock(Configuration.class), "scope");
+        assertFalse(conf2ScopeMappingContainer.equals(new DefaultConf2ScopeMappingContainer(testMappings)));
+    }
+
+    private Map<Configuration, Conf2ScopeMapping> createTestMappings() {
+        Map<Configuration, Conf2ScopeMapping> testMappings = new HashMap<Configuration, Conf2ScopeMapping>() {{
+            Configuration configuration = context.mock(Configuration.class);
+            put(configuration, new Conf2ScopeMapping(10, configuration, "scope"));
+        }};
+        return testMappings;
+    }
+
+    @Test
+    public void addGetMapping() {
+        assertEquals(new Conf2ScopeMapping(TEST_PRIORITY_1, testConf1, TEST_SCOPE_1),
+                conf2ScopeMappingContainer.getMapping(asList(testConf1)));
+    }
+
+    @Test
+    public void singleMappedConfiguration() {
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(testConf1)), equalTo(
+                new Conf2ScopeMapping(TEST_PRIORITY_1, testConf1, TEST_SCOPE_1)));
+    }
+
+    @Test
+    public void unmappedConfiguration() {
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(testConf2)), equalTo(
+                new Conf2ScopeMapping(null, testConf2, null)));
+    }
+
+    @Test
+    public void mappedConfigurationAndUnmappedConfiguration() {
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(testConf1, testConf2)), equalTo(
+                new Conf2ScopeMapping(TEST_PRIORITY_1, testConf1, TEST_SCOPE_1)));
+    }
+
+    @Test
+    public void mappingWithDifferentPrioritiesDifferentConfsDifferentScopes() {
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_2, testConf2, TEST_SCOPE_2);
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(testConf1, testConf2)), equalTo(
+                new Conf2ScopeMapping(TEST_PRIORITY_2, testConf2, TEST_SCOPE_2)));
+    }
+    
+    @Test(expected = InvalidUserDataException.class)
+    public void mappingWithSamePrioritiesDifferentConfsSameScope() {
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, testConf2, TEST_SCOPE_1);
+        conf2ScopeMappingContainer.getMapping(asList(testConf1, testConf2));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void mappingWithSamePrioritiesDifferentConfsDifferentScopes() {
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, testConf2, TEST_SCOPE_1);
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, testConf3, TEST_SCOPE_2);
+        conf2ScopeMappingContainer.getMapping(asList(testConf1, testConf2, testConf3));
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy
new file mode 100644
index 0000000..406909a
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomFactoryTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.publication.maven.internal;
+
+
+import org.gradle.api.artifacts.ConfigurationContainer
+
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultMavenPomFactoryTest extends Specification {
+    def createMavenPom() {
+        DefaultConf2ScopeMappingContainer scopeMappings = new DefaultConf2ScopeMappingContainer();
+        PomDependenciesConverter pomDependenciesConverter = Mock(PomDependenciesConverter);
+        ConfigurationContainer configurationContainer = Mock(ConfigurationContainer); 
+        FileResolver fileResolver = Mock(FileResolver); 
+        DefaultMavenPomFactory mavenPomFactory = new DefaultMavenPomFactory(configurationContainer, scopeMappings,
+                pomDependenciesConverter, fileResolver);
+        DefaultMavenPom mavenPom = (DefaultMavenPom) mavenPomFactory.create();
+
+        expect:
+        !scopeMappings.is(mavenPom.scopeMappings)
+        scopeMappings == mavenPom.scopeMappings
+        mavenPom.mavenProject != null
+        mavenPom.pomDependenciesConverter.is(pomDependenciesConverter)
+        mavenPom.configurations.is(configurationContainer)
+        mavenPom.fileResolver == fileResolver
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy
new file mode 100644
index 0000000..29ca9b2
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPomTest.groovy
@@ -0,0 +1,184 @@
+/*
+ * 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.publication.maven.internal
+
+import org.apache.commons.lang.builder.EqualsBuilder
+import org.apache.maven.model.Dependency
+import org.apache.maven.model.Model
+import org.gradle.api.Action
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.gradle.util.TextUtil
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultMavenPomTest extends Specification {
+    static final String EXPECTED_PACKAGING = "something";
+    static final String EXPECTED_GROUP_ID = "someGroup";
+    static final String EXPECTED_ARTIFACT_ID = "artifactId";
+    static final String EXPECTED_VERSION = "version";
+
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder()
+
+    Conf2ScopeMappingContainer conf2ScopeMappingContainer = Mock()
+    PomDependenciesConverter pomDependenciesConverterStub = Mock()
+    ConfigurationContainer configurationContainerStub = Mock()
+    FileResolver fileResolver = Mock()
+    DefaultMavenPom mavenPom = new DefaultMavenPom(configurationContainerStub, conf2ScopeMappingContainer, pomDependenciesConverterStub,
+            fileResolver)
+
+    void setup() {
+        mavenPom.packaging = EXPECTED_PACKAGING
+        mavenPom.groupId = EXPECTED_GROUP_ID
+        mavenPom.artifactId = EXPECTED_ARTIFACT_ID
+        mavenPom.version = EXPECTED_VERSION
+    }
+
+    def init() {
+        expect:
+        mavenPom.scopeMappings.is(conf2ScopeMappingContainer)
+        mavenPom.configurations.is(configurationContainerStub)
+        mavenPom.fileResolver.is(fileResolver)
+        mavenPom.mavenProject.modelVersion == "4.0.0"
+    }
+
+    def setModel() {
+        def newModel = new Model()
+
+        when:
+        mavenPom.model = newModel
+
+        then:
+        mavenPom.model.is(newModel)
+    }
+
+    def effectivePomShouldHaveGeneratedDependencies() {
+        List generatedDependencies = [new Dependency(groupId: 'someGroup')]
+        List manuallyAddedDependencies = [new Dependency()]
+        pomDependenciesConverterStub.convert(conf2ScopeMappingContainer, configurationContainerStub) >> generatedDependencies
+
+        when:
+        mavenPom.dependencies = manuallyAddedDependencies.clone()
+
+        then:
+        EqualsBuilder.reflectionEquals(mavenPom.getEffectivePom().getMavenProject().getDependencies(), manuallyAddedDependencies + generatedDependencies)
+
+        when:
+        mavenPom.dependencies = []
+
+        then:
+        mavenPom.getEffectivePom().getMavenProject().getDependencies() == generatedDependencies
+    }
+
+    def configureActionsShouldBeAppliedAgainstEffectivePom() {
+        mavenPom.configurations = null
+        when:
+        mavenPom.whenConfigured(new Action() {
+            void execute(def mavenPom) {
+                mavenPom.mavenProject.inceptionYear = '1999'
+            }
+        })
+
+        then:
+        mavenPom.effectivePom.mavenProject.inceptionYear == '1999'
+        mavenPom.mavenProject.inceptionYear == null
+    }
+
+
+    def writeShouldUseEffectivePom() {
+        List generatedDependencies = [new Dependency(groupId: 'someGroup')]
+        pomDependenciesConverterStub.convert(conf2ScopeMappingContainer, configurationContainerStub) >> generatedDependencies
+
+        when:
+        StringWriter pomWriter = new StringWriter()
+        mavenPom.writeTo pomWriter
+
+        then:
+        pomWriter.toString().contains('someGroup')
+    }
+
+    def effectivePomWithNullConfigurationsShouldWork() {
+        when:
+        mavenPom.configurations = null
+
+        then:
+        mavenPom.getEffectivePom().getMavenProject().getDependencies() == []
+    }
+
+    void projectBuilder() {
+        mavenPom.mavenProject.inceptionYear = '2007'
+        mavenPom.mavenProject.description = 'some description'
+        mavenPom.project {
+            inceptionYear '2008'
+            licenses {
+                license {
+                    name 'The Apache Software License, Version 2.0'
+                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    distribution 'repo'
+                }
+            }
+        }
+
+        expect:
+        mavenPom.mavenProject.modelVersion == "4.0.0"
+        mavenPom.version == EXPECTED_VERSION
+        mavenPom.mavenProject.description == 'some description'
+        mavenPom.mavenProject.inceptionYear == '2008'
+        mavenPom.mavenProject.licenses.size() == 1
+        mavenPom.mavenProject.licenses[0].name == 'The Apache Software License, Version 2.0'
+        mavenPom.mavenProject.licenses[0].url == 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+        mavenPom.mavenProject.licenses[0].distribution == 'repo'
+    }
+
+    void writeToShouldApplyXmlActions() {
+        mavenPom.configurations = null
+        StringWriter pomWriter = new StringWriter()
+
+        when:
+        mavenPom.withXml {xmlProvider ->
+            xmlProvider.asString().append('someAppendix')
+        }
+        mavenPom.writeTo(pomWriter);
+
+        then:
+        pomWriter.toString().endsWith("someAppendix")
+    }
+
+    void writeToWritesCorrectPom() {
+        mavenPom.configurations = null
+        TestFile pomFile = tmpDir.file('someNonexistingDir').file('someFile')
+        fileResolver.resolve('file') >> pomFile
+
+        when:
+        mavenPom.writeTo('file');
+
+        then:
+        pomFile.text == TextUtil.toPlatformLineSeparators('''<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>someGroup</groupId>
+  <artifactId>artifactId</artifactId>
+  <version>version</version>
+  <packaging>something</packaging>
+</project>
+''')
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
new file mode 100644
index 0000000..648683a
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultMavenRepositoryHandlerConventionTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.publication.maven.internal
+
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer
+import org.gradle.api.artifacts.maven.MavenResolver
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler
+
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+
+class DefaultMavenRepositoryHandlerConventionTest extends Specification {
+    final DefaultRepositoryHandler container = Mock()
+    final FileResolver fileResolver = Mock()
+    final ConfigurationContainer configurationContainer = Mock()
+    final Conf2ScopeMappingContainer conf2ScopeMappingContainer = Mock()
+    final DeployerFactory factory = Mock()
+    final MavenPomMetaInfoProvider metaInfoProvider = Mock()
+    final DefaultMavenRepositoryHandlerConvention convention = new DefaultMavenRepositoryHandlerConvention(container, factory)
+
+    public void mavenDeployerWithoutName() {
+        GroovyMavenDeployer deployer = Mock()
+
+        when:
+        def result = convention.mavenDeployer()
+
+        then:
+        result == deployer
+        1 * factory.createMavenDeployer() >> deployer
+        1 * container.addRepository(deployer, "mavenDeployer") >> deployer
+    }
+
+    public void mavenDeployerWithArgs() {
+        GroovyMavenDeployer deployer = Mock()
+
+        when:
+        def result = convention.mavenDeployer(name: 'someName')
+
+        then:
+        result == deployer
+        1 * factory.createMavenDeployer() >> deployer
+        1 * container.addRepository(deployer, [name: 'someName'], "mavenDeployer") >> deployer
+    }
+
+    public void mavenDeployerWithArgsAndClosure() {
+        GroovyMavenDeployer deployer = Mock()
+        def cl = {
+            name = 'other'
+        }
+
+        when:
+        def result = convention.mavenDeployer(name: 'someName', cl)
+
+        then:
+        result == deployer
+        1 * factory.createMavenDeployer() >> deployer
+        1 * container.addRepository(deployer, [name: 'someName'], cl, "mavenDeployer") >> deployer
+    }
+
+    public void mavenDeployerWithClosure() {
+        GroovyMavenDeployer deployer = Mock()
+        def cl = {
+            name = 'other'
+        }
+
+        when:
+        def result = convention.mavenDeployer(cl)
+
+        then:
+        result == deployer
+        1 * factory.createMavenDeployer() >> deployer
+        1 * container.addRepository(deployer, cl, "mavenDeployer") >> deployer
+    }
+
+    public void mavenInstallerWithoutName() {
+        MavenResolver installer = Mock()
+
+        when:
+        def result = convention.mavenInstaller()
+
+        then:
+        result == installer
+        1 * factory.createMavenInstaller() >> installer
+        1 * container.addRepository(installer, "mavenInstaller") >> installer
+    }
+
+    public void mavenInstallerWithArgs() {
+        MavenResolver installer = Mock()
+
+        when:
+        def result = convention.mavenInstaller(name: 'name')
+
+        then:
+        result == installer
+        1 * factory.createMavenInstaller() >> installer
+        1 * container.addRepository(installer, [name: 'name'], "mavenInstaller") >> installer
+    }
+
+    public void mavenInstallerWithNameAndClosure() {
+        MavenResolver installer = Mock()
+        def cl = { name = 'other' }
+
+        when:
+        def result = convention.mavenInstaller(name: 'name', cl)
+
+        then:
+        result == installer
+        1 * factory.createMavenInstaller() >> installer
+        1 * container.addRepository(installer, [name: 'name'], cl, "mavenInstaller") >> installer
+    }
+
+    public void mavenInstallerWithClosure() {
+        MavenResolver installer = Mock()
+        def cl = { name = 'other' }
+
+        when:
+        def result = convention.mavenInstaller(cl)
+
+        then:
+        result == installer
+        1 * factory.createMavenInstaller() >> installer
+        1 * container.addRepository(installer, cl, "mavenInstaller") >> installer
+    }
+
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java
new file mode 100644
index 0000000..b99e204
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/DefaultPomFilterTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.publication.maven.internal;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultPomFilterTest {
+    private static final String TEST_NAME = "TEST_NAME";
+
+    private DefaultPomFilter pomFilter;
+    private MavenPom mavenPomMock;
+
+    private PublishFilter publishFilterMock;
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp() {
+        mavenPomMock = context.mock(MavenPom.class);
+        publishFilterMock = context.mock(PublishFilter.class);
+        pomFilter = new DefaultPomFilter(TEST_NAME, mavenPomMock, publishFilterMock);
+    }
+
+    @Test
+    public void testGetName() {
+        assertEquals(TEST_NAME, pomFilter.getName());
+        assertSame(mavenPomMock, pomFilter.getPomTemplate());
+        assertSame(publishFilterMock, pomFilter.getFilter());
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
new file mode 100644
index 0000000..90efcbd
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+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.apache.maven.artifact.ant.AttachedArtifact;
+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.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.maven.MavenDeployment;
+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.DefaultPublishArtifact;
+import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
+import org.gradle.api.publication.maven.internal.DefaultMavenDeployment;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.AntUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractMavenResolverTest {
+    public static final String TEST_NAME = "name";
+    private static final Artifact TEST_IVY_ARTIFACT = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null);
+    private static final File TEST_IVY_FILE = new File("somepom.xml");
+    private static final File TEST_JAR_FILE = new File("somejar.jar");
+    private static final Artifact TEST_ARTIFACT = new DefaultArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null, TEST_NAME, "jar", "jar");
+    protected ArtifactPomContainer artifactPomContainerMock;
+    protected PomFilterContainer pomFilterContainerMock;
+    protected LoggingManagerInternal loggingManagerMock;
+
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {
+        {
+            setImposteriser(ClassImposteriser.INSTANCE);
+        }
+    };
+    protected MavenPom pomMock;
+
+    protected Settings mavenSettingsMock;
+
+    protected abstract AbstractMavenResolver getMavenResolver();
+
+    protected abstract InstallDeployTaskSupport getInstallDeployTask();
+
+    protected abstract PomFilterContainer createPomFilterContainerMock();
+
+    @Before
+    public void setUp() {
+        pomFilterContainerMock = createPomFilterContainerMock();
+        artifactPomContainerMock = context.mock(ArtifactPomContainer.class);
+        pomMock = context.mock(MavenPom.class);
+        mavenSettingsMock = context.mock(Settings.class);
+        loggingManagerMock = context.mock(LoggingManagerInternal.class);
+    }
+
+    @Test
+    public void deployOrInstall() throws IOException, PlexusContainerException {
+        getMavenResolver().mavenSettingsSupplier = context.mock(MavenSettingsSupplier.class);
+
+        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();
+        @SuppressWarnings("unchecked")
+        final Action<MavenDeployment> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {
+            {
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getSettings();
+                will(returnValue(mavenSettingsMock));
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getProject();
+                will(returnValue(AntUtil.createProject()));
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).createAttach();
+                will(returnValue(attachedArtifact));
+                one(artifactPomContainerMock).addArtifact(TEST_ARTIFACT, TEST_JAR_FILE);
+                allowing(artifactPomContainerMock).createDeployableFilesInfos();
+                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(testDefaultMavenDeployments, attachedArtifact, classifierArtifact);
+        assertSame(mavenSettingsMock, getMavenResolver().getSettings());
+    }
+
+    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 {
+        context.checking(new Expectations() {
+            {
+                one(getInstallDeployTask()).setProject(with(any(Project.class)));
+                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();
+                    one(getInstallDeployTask()).execute();
+                    one(loggingManagerMock).stop();
+                    will(returnValue(loggingManagerMock));
+                }
+                one(getMavenResolver().mavenSettingsSupplier).supply(getInstallDeployTask());
+                one(getMavenResolver().mavenSettingsSupplier).done();
+            }
+        });
+        getMavenResolver().commitPublishTransaction();
+        assertThat(attachedArtifact.getFile(), equalTo(classifierArtifact.getFile()));
+        assertThat(attachedArtifact.getType(), equalTo(classifierArtifact.getType()));
+        assertThat(attachedArtifact.getClassifier(), equalTo(classifierArtifact.getClassifier()));
+    }
+
+    private static Matcher<Pom> pomMatcher(final File expectedPomFile, final Project expectedAntProject) {
+        return new BaseMatcher<Pom>() {
+            public void describeTo(Description description) {
+                description.appendText("matching pom");
+            }
+
+            public boolean matches(Object actual) {
+                Pom actualPom = (Pom) actual;
+                return actualPom.getFile().equals(expectedPomFile) && actualPom.getProject().equals(expectedAntProject);
+            }
+        };
+    }
+
+    @Test
+    public void setFilter() {
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).setFilter(publishFilterMock);
+        }});
+        getMavenResolver().setFilter(publishFilterMock);
+    }
+
+    @Test
+    public void getFilter() {
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            allowing(pomFilterContainerMock).getFilter();
+            will(returnValue(publishFilterMock));
+        }});
+        assertSame(publishFilterMock, getMavenResolver().getFilter());
+    }
+
+    @Test
+    public void setPom() {
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).setPom(pomMock);
+        }});
+        getMavenResolver().setPom(pomMock);
+    }
+
+    @Test
+    public void getPom() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterContainerMock).getPom();
+            will(returnValue(pomMock));
+        }});
+        assertSame(pomMock, getMavenResolver().getPom());
+    }
+
+    @Test
+    public void addFilter() {
+        final String testName = "somename";
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).addFilter(testName, publishFilterMock);
+        }});
+        getMavenResolver().addFilter(testName, publishFilterMock);
+    }
+
+    @Test
+    public void filter() {
+        final String testName = "somename";
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).filter(testName);
+            will(returnValue(publishFilterMock));
+        }});
+        assertSame(publishFilterMock, getMavenResolver().filter(testName));
+    }
+
+    @Test
+    public void pom() {
+        final String testName = "somename";
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).pom(testName);
+            will(returnValue(pomMock));
+        }});
+        assertSame(pomMock, getMavenResolver().pom(testName));
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployerTest.java
new file mode 100644
index 0000000..1c0a32b
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenDeployerTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+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.PomFilterContainer;
+import org.gradle.internal.Factory;
+import org.gradle.api.publication.maven.internal.DefaultMavenDeployment;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class BaseMavenDeployerTest extends AbstractMavenResolverTest {
+
+    private BaseMavenDeployer mavenDeployer = createMavenDeployer();
+
+    @SuppressWarnings("unchecked")
+    private Factory<CustomDeployTask> deployTaskFactoryMock = context.mock(Factory.class);
+    private CustomDeployTask deployTaskMock = context.mock(CustomDeployTask.class);
+
+    private PlexusContainer plexusContainerMock = context.mock(PlexusContainer.class);
+    private RemoteRepository testRepository = new RemoteRepository();
+    private RemoteRepository testSnapshotRepository = new RemoteRepository();
+
+    private Configuration configurationStub = context.mock(Configuration.class);
+
+    protected BaseMavenDeployer createMavenDeployer() {
+        return new BaseMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
+    }
+
+    protected AbstractMavenResolver getMavenResolver() {
+        return mavenDeployer;
+    }
+
+    protected InstallDeployTaskSupport getInstallDeployTask() {
+        return deployTaskMock;
+    }
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        return context.mock(PomFilterContainer.class);
+    }
+
+    public void setUp() {
+        super.setUp();
+        mavenDeployer = createMavenDeployer();
+        mavenDeployer.setDeployTaskFactory(deployTaskFactoryMock);
+        mavenDeployer.setRepository(testRepository);
+        mavenDeployer.setSnapshotRepository(testSnapshotRepository);
+        mavenDeployer.setConfiguration(configurationStub);
+        mavenDeployer.setUniqueVersion(false);
+    }
+
+    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();
+                will(returnValue(protocolJars));
+                allowing(deployTaskFactoryMock).create();
+                will(returnValue(getInstallDeployTask()));
+                allowing(deployTaskMock).getContainer();
+                will(returnValue(plexusContainerMock));
+                for (File protocolProviderJar : protocolJars) {
+                    one(plexusContainerMock).addJarResource(protocolProviderJar);
+                }
+                one(deployTaskMock).setUniqueVersion(mavenDeployer.isUniqueVersion());
+                one(deployTaskMock).addRemoteRepository(testRepository);
+                one(deployTaskMock).addRemoteSnapshotRepository(testSnapshotRepository);
+        }});
+        super.checkTransaction(defaultMavenDeployments, attachedArtifact, classifierArtifact);
+    }
+
+    @Test
+    public void init() {
+        mavenDeployer = new BaseMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
+        assertTrue(mavenDeployer.isUniqueVersion());
+    }
+
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstallerTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstallerTest.java
new file mode 100644
index 0000000..4db25cb
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/BaseMavenInstallerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+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.PomFilterContainer;
+import org.gradle.internal.Factory;
+import org.gradle.api.publication.maven.internal.DefaultMavenDeployment;
+import org.jmock.Expectations;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseMavenInstallerTest extends AbstractMavenResolverTest {
+    private BaseMavenInstaller mavenInstaller;
+
+    @SuppressWarnings("unchecked")
+    private Factory<CustomInstallTask> installTaskFactoryMock = context.mock(Factory.class);
+    private CustomInstallTask installTaskMock;
+
+    protected BaseMavenInstaller createMavenInstaller() {
+        return new BaseMavenInstaller(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
+    }
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        return context.mock(PomFilterContainer.class);
+    }
+
+    protected AbstractMavenResolver getMavenResolver() {
+        return mavenInstaller;
+    }
+
+    protected InstallDeployTaskSupport getInstallDeployTask() {
+        return installTaskMock;
+    }
+
+    public void setUp() {
+        super.setUp();
+        installTaskMock = context.mock(CustomInstallTask.class);
+        mavenInstaller = createMavenInstaller();
+        mavenInstaller.setInstallTaskFactory(installTaskFactoryMock);
+    }
+
+    protected void checkTransaction(final Set<DefaultMavenDeployment> deployableUnits, AttachedArtifact attachedArtifact, PublishArtifact classifierArtifact) throws IOException, PlexusContainerException {
+        context.checking(new Expectations() {
+            {
+                allowing(installTaskFactoryMock).create();
+                will(returnValue(getInstallDeployTask()));
+            }
+        });
+        super.checkTransaction(deployableUnits, attachedArtifact, classifierArtifact);
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactoryTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactoryTest.java
new file mode 100644
index 0000000..fc35ae1
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultDeployTaskFactoryTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDeployTaskFactoryTest {
+    @Test
+    public void create() {
+        assertTrue(new DefaultDeployTaskFactory().create() instanceof CustomDeployTask);
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java
new file mode 100644
index 0000000..fe1f57b
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultExcludeRuleConverterTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.junit.Before;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverterTest {
+    private static final String TEST_ORG = "org";
+    private static final String TEST_MODULE = "module";
+
+    private DefaultExcludeRuleConverter excludeRuleConverter;
+
+    @Before
+    public void setUp() {
+        excludeRuleConverter = new DefaultExcludeRuleConverter();   
+    }
+    
+    @Test
+    public void convertableRule() {
+        DefaultExcludeRule excludeRule = new DefaultExcludeRule(TEST_ORG, TEST_MODULE);
+        Exclusion mavenExclude = excludeRuleConverter.convert(excludeRule);
+        assertEquals(TEST_ORG, mavenExclude.getGroupId());
+        assertEquals(TEST_MODULE, mavenExclude.getArtifactId());
+    }
+    
+    @Test
+    public void unconvertableRules() {
+        checkForNull(new DefaultExcludeRule(TEST_ORG, null));
+        checkForNull(new DefaultExcludeRule(null, TEST_MODULE));
+    }
+
+    private void checkForNull(DefaultExcludeRule excludeRule) {
+        assertNull(excludeRuleConverter.convert(excludeRule));
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy
new file mode 100644
index 0000000..bf9d6a8
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyMavenDeployerTest.groovy
@@ -0,0 +1,116 @@
+/*
+ * 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.publication.maven.internal.ant
+
+import org.gradle.api.artifacts.maven.PomFilterContainer
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (org.jmock.integration.junit4.JMock.class)
+class DefaultGroovyMavenDeployerTest extends BaseMavenDeployerTest {
+    private DefaultGroovyMavenDeployer groovyMavenDeployer;
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        context.mock(PomFilterContainer.class);
+    }
+
+    protected BaseMavenDeployer createMavenDeployer() {
+        groovyMavenDeployer = new DefaultGroovyMavenDeployer(pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock)
+    }
+
+    @Before
+    void setUp() {
+        super.setUp();
+    }
+
+    @Test
+    void repositoryBuilder() {
+        checkRepositoryBuilder(DefaultGroovyMavenDeployer.REPOSITORY_BUILDER)
+    }
+
+    @Test
+    void snapshotRepositoryBuilder() {
+        checkRepositoryBuilder(DefaultGroovyMavenDeployer.SNAPSHOT_REPOSITORY_BUILDER)
+    }
+
+
+    void checkRepositoryBuilder(String repositoryName) {
+        String testUrl = 'testUrl'
+        String testProxyHost = 'hans'
+        String testUserName = 'userId'
+        String testSnapshotUpdatePolicy = 'always'
+        String testReleaseUpdatePolicy = 'never'
+        groovyMavenDeployer."$repositoryName"(url: testUrl) {
+            authentication(userName: testUserName)
+            proxy(host: testProxyHost)
+            releases(updatePolicy: testReleaseUpdatePolicy)
+            snapshots(updatePolicy: testSnapshotUpdatePolicy)
+        }
+        assertEquals(testUrl, groovyMavenDeployer."$repositoryName".url)
+        assertEquals(testUserName, groovyMavenDeployer."$repositoryName".authentication.userName)
+        assertEquals(testProxyHost, groovyMavenDeployer."$repositoryName".proxy.host)
+        assertEquals(testReleaseUpdatePolicy, groovyMavenDeployer."$repositoryName".releases.updatePolicy)
+        assertEquals(testSnapshotUpdatePolicy, groovyMavenDeployer."$repositoryName".snapshots.updatePolicy)
+    }
+
+    @Test
+    void filter() {
+        Closure testClosure = {}
+        context.checking {
+            one(pomFilterContainerMock).filter(testClosure)
+        }
+        groovyMavenDeployer.filter(testClosure)
+    }
+
+    @Test
+    void pom() {
+        Closure testClosure = {}
+        context.checking {
+            one(pomFilterContainerMock).pom(testClosure)
+        }
+        groovyMavenDeployer.pom(testClosure)
+    }
+
+    @Test
+    void pomWithName() {
+        Closure testClosure = {}
+        String testName = 'somename'
+        context.checking {
+            one(pomFilterContainerMock).pom(testName, testClosure)
+        }
+        groovyMavenDeployer.pom(testName, testClosure)
+    }
+
+    @Test
+    void addFilter() {
+        Closure testClosure = {}
+        String testName = 'somename'
+        context.checking {
+            one(pomFilterContainerMock).addFilter(testName, testClosure)
+        }
+        groovyMavenDeployer.addFilter(testName, testClosure)
+    }
+}
+
+
+
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy
new file mode 100644
index 0000000..be6ce92
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultGroovyPomFilterContainerTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * 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.publication.maven.internal.ant
+
+import java.lang.reflect.Proxy
+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.publication.maven.internal.BasePomFilterContainer
+import org.gradle.api.publication.maven.internal.BasePomFilterContainerTest
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
+import org.hamcrest.Factory
+import org.hamcrest.Matcher
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.assertSame
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock)
+class DefaultGroovyPomFilterContainerTest extends BasePomFilterContainerTest {
+    static final String TEST_NAME = "somename"
+    PomFilterContainer groovyPomFilterContainer
+
+    @Before
+    public void setUp() {
+        super.setUp()
+    }
+
+    protected BasePomFilterContainer createPomFilterContainer() {
+        return groovyPomFilterContainer = new BasePomFilterContainer(mavenPomFactoryMock);
+    }
+
+    @Test
+    public void addFilterWithClosure() {
+        Closure closureFilter = {}
+        MavenPom pom = groovyPomFilterContainer.addFilter(TEST_NAME, closureFilter)
+        assertSame(pomMock, pom);
+        assertSame(pomMock, groovyPomFilterContainer.pom(TEST_NAME));
+        assertSame(closureFilter, getClosureFromProxy(groovyPomFilterContainer.filter(TEST_NAME)));
+    }
+
+    private Closure getClosureFromProxy(PublishFilter filter) {
+        Proxy.getInvocationHandler(filter).delegate
+    }
+
+    @Test
+    public void filterWithClosure() {
+        Closure closureFilter = {}
+        context.checking {
+            one(pomFilterMock).setFilter(withParam(FilterMatcher.equalsFilter(closureFilter)))
+        }
+        groovyPomFilterContainer.filter(closureFilter)
+    }
+
+    @Test
+    public void defaultPomWithClosure() {
+        String testGroup = "testGroup"
+        context.checking {
+            one(pomFilterMock).getPomTemplate(); will(returnValue(pomMock))
+            one(pomMock).setGroupId(testGroup);
+        }
+        groovyPomFilterContainer.pom {
+            groupId = testGroup
+        }
+    }
+
+    @Test
+    public void pomWithClosure() {
+        groovyPomFilterContainer.addFilter(TEST_NAME, {})
+        String testGroup = "testGroup"
+        context.checking {
+            one(pomMock).setGroupId(testGroup);
+        }
+        groovyPomFilterContainer.pom(TEST_NAME) {
+            groupId = testGroup
+        }
+    }
+}
+
+public class FilterMatcher extends BaseMatcher {
+    Closure filter
+
+    public void describeTo(Description description) {
+        description.appendText("matching filter");
+    }
+
+    public boolean matches(Object actual) {
+        return getClosureFromProxy(actual) == filter;
+    }
+
+    private Closure getClosureFromProxy(PublishFilter filter) {
+        Proxy.getInvocationHandler(filter).delegate
+    }
+
+
+    @Factory
+    public static Matcher<PublishFilter> equalsFilter(Closure filter) {
+        return new FilterMatcher(filter: filter);
+    }
+
+}
+
+
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy
new file mode 100644
index 0000000..47cfc83
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy
@@ -0,0 +1,152 @@
+/*
+ * 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.publication.maven.internal.ant
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+import org.gradle.api.internal.file.DefaultTemporaryFileProvider
+import org.gradle.api.internal.file.FileSource
+import org.gradle.api.publication.maven.internal.model.DefaultMavenArtifact
+import org.gradle.api.publication.maven.internal.model.DefaultMavenPublication
+import org.gradle.util.Resources
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 5/12/11
+ */
+class DefaultMavenPublisherTest extends Specification {
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+    @Rule Resources resources = new Resources()
+
+    def publisher = new DefaultMavenPublisher(dir.file("local-repository"), new DefaultTemporaryFileProvider({dir.createDir("tmp")} as FileSource))
+    
+    def "installs artifact"() {
+        def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1")
+        def artifact = new DefaultMavenArtifact(classifier: "", extension: "jar", file: sampleJar())
+        publication.mainArtifact = artifact
+
+        when:
+        publisher.install(publication)
+
+        then:
+        def installed = new File("$dir.testDir/local-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1.jar")
+        installed.exists()
+        installed.bytes == sampleJar().bytes
+    }
+
+    def "installs artifact with classifier"() {
+        def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1")
+        def artifact = new DefaultMavenArtifact(classifier: "jdk15", extension: "jar", file: sampleJar())
+        publication.mainArtifact = artifact
+
+        when:
+        publisher.install(publication)
+
+        then:
+        def installed = new File("$dir.testDir/local-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1-jdk15.jar")
+        installed.exists()
+        installed.bytes == sampleJar().bytes
+    }
+
+    def "deploys artifact"() {
+        def fakeRemoteRepo = repo(new File("$dir.testDir/remote-repository"))
+
+        def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1")
+        def artifact = new DefaultMavenArtifact(classifier: "", extension: "jar", file: sampleJar())
+        publication.mainArtifact = artifact
+
+        when:
+        publisher.deploy(publication, fakeRemoteRepo)
+
+        then:
+        def deployed = new File("$dir.testDir/remote-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1.jar")
+        deployed.exists()
+        deployed.bytes == sampleJar().bytes
+    }
+
+    def "deploys snapshot along with maven stuff"() {
+        def fakeRemoteRepo = repo(new File("$dir.testDir/remote-repository"))
+
+        def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1-SNAPSHOT")
+        def artifact = new DefaultMavenArtifact(classifier: "", extension: "jar", file: sampleJar())
+        publication.mainArtifact = artifact
+
+        when:
+        publisher.deploy(publication, fakeRemoteRepo)
+
+        then:
+        def deployedDir = new File("$dir.testDir/remote-repository/gradleware/test/fooArtifact/1.1-SNAPSHOT")
+        def files = deployedDir.list() as List
+        ['maven-metadata.xml', 'maven-metadata.xml.md5', 'maven-metadata.xml.sha1'].each {
+            assert files.contains(it)
+        }
+        assert files.any { it =~ /fooArtifact-1.1-.*\.jar/ }
+        assert files.any { it =~ /fooArtifact-1.1-.*\.jar.sha1/ }
+        assert files.any { it =~ /fooArtifact-1.1-.*\.jar.md5/ }
+    }
+
+    def "deploys artifact with classifier"() {
+        def fakeRemoteRepo = repo(new File("$dir.testDir/remote-repository"))
+
+        def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1")
+        def artifact = new DefaultMavenArtifact(classifier: "jdk15", extension: "jar", file: sampleJar())
+        publication.mainArtifact = artifact
+
+        when:
+        publisher.deploy(publication, fakeRemoteRepo)
+
+        then:
+        def deployed = new File("$dir.testDir/remote-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1-jdk15.jar")
+        deployed.exists()
+        deployed.bytes == sampleJar().bytes
+    }
+
+    def "deals with multiple artifacts"() {
+        def fakeRemoteRepo = repo(new File("$dir.testDir/remote-repository"))
+        def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1")
+        def artifact = new DefaultMavenArtifact(classifier: "", extension: "jar", file: sampleJar())
+        publication.mainArtifact = artifact
+        publication.subArtifacts << new DefaultMavenArtifact(classifier: "jdk15", extension: "jar", file: sampleJar())
+
+        when:
+        publisher.install(publication)
+        publisher.deploy(publication, fakeRemoteRepo)
+
+        then:
+        new File("$dir.testDir/local-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1.jar").exists()
+        new File("$dir.testDir/local-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1-jdk15.jar").exists()
+        new File("$dir.testDir/remote-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1.jar").exists()
+        new File("$dir.testDir/remote-repository/gradleware/test/fooArtifact/1.1/fooArtifact-1.1-jdk15.jar").exists()
+    }
+
+    TestFile sampleJar() {
+        return dir.dir.createZip("sample.jar")
+    }
+
+    String dir() {
+        return dir.testDir
+    }
+    
+    MavenArtifactRepository repo(File dir) {
+        MavenArtifactRepository repo = Mock()
+        _ * repo.url >> dir.toURI()
+        return repo
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java
new file mode 100644
index 0000000..6afac33
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverterTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.publication.maven.internal.ant;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.artifacts.*;
+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.api.publication.maven.internal.ExcludeRuleConverter;
+import org.gradle.util.JUnit4GroovyMockery;
+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.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static java.util.Arrays.asList;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultPomDependenciesConverterTest {
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+    
+    private DefaultPomDependenciesConverter dependenciesConverter;
+    private Conf2ScopeMappingContainer conf2ScopeMappingContainerMock = context.mock(Conf2ScopeMappingContainer.class);
+    private ExcludeRuleConverter excludeRuleConverterMock = context.mock(ExcludeRuleConverter.class);
+
+    private ModuleDependency dependency1;
+    private ModuleDependency dependency2;
+    private ModuleDependency dependency31;
+    private ModuleDependency dependency32;
+    private Configuration compileConfStub;
+    private Configuration testCompileConfStub;
+
+    @Before
+    public void setUp() {
+        setUpCommonDependenciesAndConfigurations();
+        dependenciesConverter = new DefaultPomDependenciesConverter(excludeRuleConverterMock);
+    }
+
+    private void setUpCommonDependenciesAndConfigurations() {
+        dependency1 = createDependency("org1", "name1", "rev1");
+        dependency2 = createDependency("org2", "name2", "rev2");
+        dependency2.addArtifact(new DefaultDependencyArtifact("name2", null, null, null, null));
+        dependency31 = createDependency("org3", "name3", "rev3");
+        dependency32 = createDependency("org3", "name3", "rev3");
+        dependency32.addArtifact(new DefaultDependencyArtifact("artifactName32", "type32", "ext", "classifier32", null));
+        compileConfStub = createNamedConfigurationStubWithDependencies("compile", dependency1, dependency31);
+        testCompileConfStub = createNamedConfigurationStubWithDependencies("testCompile", dependency2, dependency32);
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(testCompileConfStub, compileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(compileConfStub, testCompileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(testCompileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(compileConfStub)); will(returnValue(createMapping(compileConfStub, "compile")));
+        }});
+    }
+
+    private Conf2ScopeMapping createMapping(Configuration configuration, String scope) {
+        return new Conf2ScopeMapping(10, configuration, scope);
+    }
+
+    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);
+        final DependencySet dependencySet = context.mock(DependencySet.class);
+
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getName();
+            will(returnValue(confName));
+            allowing(configurationStub).getDependencies();
+            will(returnValue(dependencySet));
+            allowing(dependencySet).withType(ModuleDependency.class);
+            will(returnValue(toDomainObjectSet(ModuleDependency.class, dependencies)));
+            allowing(configurationStub).getExcludeRules();
+            will(returnValue(excludeRules));
+        }});
+        return configurationStub;
+    }
+
+    private ModuleDependency createDependency(final String group, final String name, final String version) {
+        return new DefaultExternalModuleDependency(group, name, version);
+    }
+
+    @Test
+    public void init() {
+        assertSame(excludeRuleConverterMock, dependenciesConverter.getExcludeRuleConverter());
+    }
+
+    @Test
+    public void convert() {
+        Set<Configuration> configurations = toSet(compileConfStub, testCompileConfStub);
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(false));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, configurations);
+        assertEquals(4, actualMavenDependencies.size());
+        checkCommonMavenDependencies(actualMavenDependencies);
+    }
+
+    @Test
+    public void convertWithUnMappedConfAndSkipTrue() {
+        final Dependency dependency4 = createDependency("org4", "name4", "rev4");
+        final Configuration unmappedConfigurationStub = createNamedConfigurationStubWithDependencies("unmappedConf");
+        context.checking(new Expectations() {{
+            allowing(unmappedConfigurationStub).getDependencies();
+            will(returnValue(toSet(dependency4)));
+        }});
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(true));
+            allowing(conf2ScopeMappingContainerMock).getMapping(asList(unmappedConfigurationStub)); will(returnValue(null));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(
+                compileConfStub, testCompileConfStub, unmappedConfigurationStub));
+        assertEquals(4, actualMavenDependencies.size());
+        checkCommonMavenDependencies(actualMavenDependencies);
+    }
+
+    @Test
+    public void convertWithUnMappedConfAndSkipFalse() {
+        final ModuleDependency dependency4 = createDependency("org4", "name4", "rev4");
+        final Configuration unmappedConfigurationStub = createNamedConfigurationStubWithDependencies("unmappedConf", dependency4);
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(false));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(unmappedConfigurationStub)); will(returnValue(new Conf2ScopeMapping(null, unmappedConfigurationStub, null)));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(
+                compileConfStub, testCompileConfStub, unmappedConfigurationStub));
+        assertEquals(5, actualMavenDependencies.size());
+        checkCommonMavenDependencies(actualMavenDependencies);
+        assertTrue(hasDependency(actualMavenDependencies, "org4", "name4", "rev4", null, null, null, false));
+    }
+
+    private void checkCommonMavenDependencies(List<org.apache.maven.model.Dependency> actualMavenDependencies) {
+        assertTrue(hasDependency(actualMavenDependencies, "org1", "name1", "rev1", null, "compile", null, false));
+        assertTrue(hasDependency(actualMavenDependencies, "org2", "name2", "rev2", null, "test", null, false));
+        assertTrue(hasDependency(actualMavenDependencies, "org3", "name3", "rev3", null, "test", null, false));
+        assertTrue(hasDependency(actualMavenDependencies, "org3", "artifactName32", "rev3", "type32", "test", "classifier32", false));
+    }
+
+    private boolean hasDependency(List<org.apache.maven.model.Dependency> mavenDependencies,
+                                  String group, String artifactId, String version, String type, String scope,
+                                  String classifier, boolean optional) {
+        org.apache.maven.model.Dependency expectedDependency = new org.apache.maven.model.Dependency();
+        expectedDependency.setGroupId(group);
+        expectedDependency.setArtifactId(artifactId);
+        expectedDependency.setVersion(version);
+        expectedDependency.setType(type);
+        expectedDependency.setScope(scope);
+        expectedDependency.setClassifier(classifier);
+        expectedDependency.setOptional(optional);
+        for (org.apache.maven.model.Dependency mavenDependency : mavenDependencies) {
+            if (equals(mavenDependency, expectedDependency)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean equals(org.apache.maven.model.Dependency lhs, org.apache.maven.model.Dependency rhs) {
+        if (!lhs.getGroupId().equals(lhs.getGroupId())) {
+            return false;
+        }
+        if (!lhs.getArtifactId().equals(lhs.getArtifactId())) {
+            return false;
+        }
+        if (!lhs.getVersion().equals(lhs.getVersion())) {
+            return false;
+        }
+        if (lhs.getType() != null ? !lhs.getType().equals(lhs.getType()) : rhs.getType() != null) {
+            return false;
+        }
+        if (lhs.getScope() != null ? !lhs.getScope().equals(lhs.getScope()) : rhs.getScope() != null) {
+            return false;
+        }
+        if (!lhs.isOptional() == lhs.isOptional()) {
+            return false;
+        }
+        if (lhs.getClassifier() != null ? !lhs.getClassifier().equals(rhs.getClassifier()) : rhs.getClassifier() != null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Test
+    public void convertWithConvertableDependencyExcludes() {
+        final Configuration someConfigurationStub = createNamedConfigurationStubWithDependencies("someConfiguration", dependency1);
+        final Exclusion mavenExclude = new Exclusion();
+        mavenExclude.setGroupId("a");
+        mavenExclude.setArtifactId("b");
+        dependency1.exclude(toMap(ExcludeRule.GROUP_KEY, "value"));
+        context.checking(new Expectations() {{
+           allowing(conf2ScopeMappingContainerMock).getMapping(toSet(someConfigurationStub)); will(returnValue(createMapping(compileConfStub, "compile")));
+           allowing(excludeRuleConverterMock).convert(dependency1.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()));
+    }
+    
+    @Test
+    public void convertWithConvertableConfigurationExcludes() {
+        final Configuration someConfigurationStub = createNamedConfigurationStubWithDependencies("someConfiguration", 
+                WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule("value", null)), 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/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy
new file mode 100644
index 0000000..eafd010
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/EmptyMavenSettingsSupplierTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.ant
+
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport
+import spock.lang.Specification
+
+/**
+ * @author Szczepan Faber, created at: 3/29/11
+ */
+class EmptyMavenSettingsSupplierTest extends Specification {
+
+    def EmptyMavenSettingsSupplier supplier = new EmptyMavenSettingsSupplier()
+    InstallDeployTaskSupport support = Mock()
+
+    def "supplies empty settings"() {
+        when:
+        supplier.supply(support)
+
+        then:
+        supplier.settingsXml.text == '<settings/>'
+//        1 * support.setSettingsFile(supplier.settingsXml) //not sure why it doesn't work
+    }
+
+    def "deletes file when done"() {
+        when:
+        supplier.supply(support)
+        supplier.done()
+
+        then:
+        !supplier.settingsXml.exists()
+    }
+
+    def "done operation must be safe"() {
+        when:
+        supplier.done()
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy
new file mode 100644
index 0000000..9906e0a
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/MaybeUserMavenSettingsSupplierTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.publication.maven.internal.ant
+
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport
+import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenFileLocations
+
+import spock.lang.Specification
+
+/**
+ * @author Szczepan Faber, created at: 3/29/11
+ */
+class MaybeUserMavenSettingsSupplierTest extends Specification {
+
+    InstallDeployTaskSupport support = Mock()
+    def supplier = new MaybeUserMavenSettingsSupplier()
+
+    def "supplies empty settings when user settings not found"() {
+        given:
+        supplier.emptySettingsSupplier = Mock(EmptyMavenSettingsSupplier)
+        supplier.mavenFileLocations = Mock(DefaultMavenFileLocations)
+
+        supplier.mavenFileLocations.getUserSettingsFile() >> { new File('does not exist') }
+
+        when:
+        supplier.supply(support)
+        supplier.done()
+
+        then:
+        1 * supplier.emptySettingsSupplier.supply(support)
+        1 * supplier.emptySettingsSupplier.done()
+    }
+
+    def "supplies user settings when file exists"() {
+        given:
+        supplier.emptySettingsSupplier = Mock(EmptyMavenSettingsSupplier)
+        supplier.mavenFileLocations = Mock(DefaultMavenFileLocations)
+
+        def concreteFile = File.createTempFile('I exist', ', really')
+        concreteFile.deleteOnExit()
+        supplier.mavenFileLocations.getUserSettingsFile() >> { concreteFile }
+
+        when:
+        supplier.supply(support)
+        supplier.done()
+
+        then:
+        1 * support.setSettingsFile(concreteFile)
+        0 * supplier.emptySettingsSupplier.supply(support)
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilderTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilderTest.groovy
new file mode 100644
index 0000000..4fdc7dc
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilderTest.groovy
@@ -0,0 +1,209 @@
+/*
+ * 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.publication.maven.internal.modelbuilder
+
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.publication.maven.MavenPublication
+import org.gradle.api.publication.maven.MavenScope
+import org.gradle.util.HelperUtil
+import spock.lang.Ignore
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 5/13/11
+ */
+class MavenPublicationBuilderTest extends Specification {
+
+    DefaultProject project = HelperUtil.createRootProject()
+
+    //building the publication early, before the plugins and configurations are applied
+    //to make sure that the conventionMappings are tied correctly and lazily evaluated
+    MavenPublication publication = new MavenPublicationBuilder().build(project)
+
+    def "populates model with basic information"() {
+        when:
+        project.apply(plugin: 'java')
+        project.apply(plugin: 'maven')
+
+        project.description = 'some test project'
+        project.group = 'com.gradleware'
+
+        project.jar {
+            version = 1.8
+            baseName = 'someJar'
+        }
+
+        then:
+        publication.artifactId == 'someJar'
+        publication.version == '1.8'
+
+        publication.description == 'some test project'
+        publication.groupId == 'com.gradleware'
+
+        publication.packaging == 'jar'
+        publication.modelVersion == '4.0.0'
+    }
+
+    def "honors archivesBaseName"() {
+        when:
+        project.apply(plugin: 'java')
+        project.apply(plugin: 'maven')
+
+        project.archivesBaseName = 'foobar'
+
+        then:
+        publication.artifactId == 'foobar'
+        publication.mainArtifact.file.name == 'foobar.jar'
+    }
+
+    @Ignore
+    //I don't think we want to support that...
+    //the idea should be that the new publication dsl works when you configure the installation/deployment using the new DSL, not the old one
+    def "populates model with info from installer configuration"() {
+        when:
+        project.apply(plugin: 'java')
+        project.apply(plugin: 'maven')
+
+        project.install {
+            repositories.mavenInstaller.pom.project {
+                groupId 'com.gradleware2'
+            }
+        }
+
+        then:
+        publication.groupId == 'com.gradleware2'
+    }
+
+    def "populates model with main artifact"() {
+        when:
+        project.apply(plugin: 'java')
+        project.jar {
+            classifier = 'jdk15'
+            extension  = 'rambo'
+        }
+
+        then:
+        publication.mainArtifact != null
+        publication.mainArtifact.classifier == 'jdk15'
+        publication.mainArtifact.extension == 'rambo'
+
+        publication.mainArtifact.file != null
+        publication.mainArtifact.file == project.jar.archivePath
+    }
+
+    def "populates model with compile dependencies"() {
+        when:
+        project.apply(plugin: 'java')
+        project.repositories {
+            mavenCentral()
+        }
+        project.dependencies {
+           compile 'commons-lang:commons-lang:2.6'
+           testCompile 'org.mockito:mockito-all:1.8.5'
+        }
+
+        then:
+        publication.dependencies.size() == 2
+
+        publication.dependencies[0].artifactId == 'commons-lang'
+        publication.dependencies[0].groupId == 'commons-lang'
+        publication.dependencies[0].classifier == null
+        publication.dependencies[0].optional == false
+        publication.dependencies[0].version == '2.6'
+        publication.dependencies[0].scope == MavenScope.COMPILE
+
+        publication.dependencies[1].artifactId == 'mockito-all'
+        publication.dependencies[1].groupId == 'org.mockito'
+        publication.dependencies[1].classifier == null
+        publication.dependencies[1].optional == false
+        publication.dependencies[1].version == '1.8.5'
+        publication.dependencies[1].scope == MavenScope.TEST
+    }
+
+    def "populates model with runtime dependencies"() {
+        when:
+        project.apply(plugin: 'java')
+        project.repositories {
+            mavenCentral()
+        }
+        project.dependencies {
+           runtime 'commons-lang:commons-lang:2.6'
+           testRuntime 'org.mockito:mockito-all:1.8.5'
+        }
+
+        then:
+        publication.dependencies.size() == 2
+
+        publication.dependencies[0].artifactId == 'commons-lang'
+        publication.dependencies[0].groupId == 'commons-lang'
+        publication.dependencies[0].classifier == null
+        publication.dependencies[0].optional == false
+        publication.dependencies[0].version == '2.6'
+        publication.dependencies[0].scope == MavenScope.RUNTIME
+
+        publication.dependencies[1].artifactId == 'mockito-all'
+        publication.dependencies[1].groupId == 'org.mockito'
+        publication.dependencies[1].classifier == null
+        publication.dependencies[1].optional == false
+        publication.dependencies[1].version == '1.8.5'
+        publication.dependencies[1].scope == MavenScope.TEST
+    }
+
+    def "populates model with dependency with a classifier"() {
+        when:
+        project.apply(plugin: 'java')
+        project.dependencies {
+           testCompile 'org.foo:bar:1.0:testUtil'
+        }
+
+        then:
+        publication.dependencies.size() == 1
+
+        publication.dependencies[0].artifactId == 'bar'
+        publication.dependencies[0].groupId == 'org.foo'
+        publication.dependencies[0].classifier == 'testUtil'
+        publication.dependencies[0].optional == false
+        publication.dependencies[0].version == '1.0'
+        publication.dependencies[0].scope == MavenScope.TEST
+    }
+
+    def "does not break when java plugin not applied and has reasonable defaults"() {
+        expect:
+        !publication.artifactId
+        !publication.dependencies
+        !publication.description
+        publication.groupId
+        publication.mainArtifact
+        publication.modelVersion
+        !publication.packaging
+        !publication.pom
+        publication.properties
+        !publication.subArtifacts
+        publication.version
+    }
+
+    def "does not break when file dependencies are configured"() {
+        when:
+        project.apply(plugin: 'java')
+        project.dependencies {
+           compile project.files('sample.jar')
+        }
+
+        then:
+        publication.dependencies.size() == 0
+    }
+}
diff --git a/subprojects/native/native.gradle b/subprojects/native/native.gradle
new file mode 100755
index 0000000..1778ca8
--- /dev/null
+++ b/subprojects/native/native.gradle
@@ -0,0 +1,29 @@
+/*
+    This project contains various native operating system integration utilities.
+*/
+apply from: "$rootDir/gradle/classycle.gradle"
+
+dependencies {
+    groovy libraries.groovy
+
+    compile project(':baseServices')
+    compile libraries.commons_io
+    compile libraries.slf4j_api
+    compile libraries.jna
+    compile module('org.jruby.ext.posix:jna-posix:1.0.3') {
+        dependency libraries.jna
+    }
+    compile module('org.fusesource.jansi:jansi:1.2.1') {
+        dependency libraries.jna
+    }
+    compile libraries.guava
+    compile libraries.jcip
+}
+
+if (!Jvm.current().isJava7()) {
+    sourceSets.main.java.exclude '**/jdk7/**'
+    sourceSets.test.groovy.exclude '**/jdk7/**'
+}
+
+useTestFixtures()
+
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NativeIntegrationException.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NativeIntegrationException.java
new file mode 100644
index 0000000..b5296ef
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NativeIntegrationException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform;
+
+public class NativeIntegrationException extends RuntimeException {
+    public NativeIntegrationException(String message) {
+        super(message);
+    }
+
+    public NativeIntegrationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NativeIntegrationUnavailableException.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NativeIntegrationUnavailableException.java
new file mode 100644
index 0000000..1990a2d
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NativeIntegrationUnavailableException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform;
+
+/**
+ * Thrown when the native integration for the current platform is not available for some reason (eg unsupported operating system, cannot load native library, etc).
+ */
+public class NativeIntegrationUnavailableException extends NativeIntegrationException {
+    public NativeIntegrationUnavailableException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NoOpTerminalDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NoOpTerminalDetector.java
new file mode 100644
index 0000000..e1f8944
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/NoOpTerminalDetector.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform;
+
+import java.io.FileDescriptor;
+
+public class NoOpTerminalDetector implements TerminalDetector {
+    public boolean isTerminal(FileDescriptor fileDescriptor) {
+        return false;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ProcessEnvironment.java
new file mode 100644
index 0000000..650e93c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ProcessEnvironment.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Provides access to information about the current process.
+ *
+ * <p>Implementations are not thread-safe.</p>
+ */
+public interface ProcessEnvironment {
+    /**
+     * Sets the environment of this process, if possible.
+     *
+     * @param source The environment
+     * @return true if environment changed, false if not possible.
+     */
+    public boolean maybeSetEnvironment(Map<String, String> source);
+
+    /**
+     * Removes the given environment variable.
+     *
+     * @param name The name of the environment variable.
+     * @throws NativeIntegrationException If the environment variable cannot be removed.
+     */
+    void removeEnvironmentVariable(String name) throws NativeIntegrationException;
+
+    /**
+     * Removes the given environment variable, if possible.
+     *
+     * @param name The name of the environment variable.
+     * @return true if removed, false if not possible.
+     */
+    boolean maybeRemoveEnvironmentVariable(String name);
+
+    /**
+     * Sets the given environment variable.
+     *
+     * @param name The name
+     * @param value The value. Can be null, which removes the environment variable.
+     * @throws NativeIntegrationException If the environment variable cannot be set.
+     */
+    void setEnvironmentVariable(String name, String value) throws NativeIntegrationException;
+
+    /**
+     * Sets the given environment variable, if possible.
+     *
+     * @param name The name
+     * @param value The value
+     * @return true if set, false if not possible.
+     */
+    boolean maybeSetEnvironmentVariable(String name, String value);
+
+    /**
+     * Returns the working directory of the current process.
+     *
+     * @throws NativeIntegrationException If the process directory is not available.
+     */
+    File getProcessDir() throws NativeIntegrationException;
+
+    /**
+     * Sets the process working directory.
+     *
+     * @param processDir The directory.
+     * @throws NativeIntegrationException If process directory cannot be set.
+     */
+    void setProcessDir(File processDir) throws NativeIntegrationException;
+
+    /**
+     * Sets the process working directory, if possible
+     *
+     * @param processDir The directory.
+     * @return true if the directory can be set, false if not possible.
+     */
+    boolean maybeSetProcessDir(File processDir);
+
+    /**
+     * Returns the OS level PID for the current process.
+     *
+     * @throws NativeIntegrationException If the pid is not available.
+     */
+    Long getPid() throws NativeIntegrationException;
+
+    /**
+     * Returns the OS level PID for the current process, or null if not available.
+     */
+    Long maybeGetPid();
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java
new file mode 100644
index 0000000..b853340
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/ReflectiveEnvironment.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform;
+
+import org.gradle.internal.os.OperatingSystem;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+/**
+ * Uses reflection to update private environment state
+ *
+ * @author: Szczepan Faber, created at: 9/7/11
+ */
+public class ReflectiveEnvironment {
+
+    public void unsetenv(String name) {
+        Map<String, String> map = getEnv();
+        map.remove(name);
+        if (OperatingSystem.current().isWindows()) {
+            Map<String, String> env2 = getWindowsEnv();
+            env2.remove(name);
+        }
+    }
+
+    public void setenv(String name, String value) {
+        Map<String, String> map = getEnv();
+        map.put(name, value);
+        if (OperatingSystem.current().isWindows()) {
+            Map<String, String> env2 = getWindowsEnv();
+            env2.put(name, value);
+        }
+    }
+
+    /**
+     * Windows keeps an extra map with case insensitive keys. The map is used when the user calls {@link System#getenv(String)}
+     */
+    private Map<String, String> getWindowsEnv() {
+        try {
+            Class<?> sc = Class.forName("java.lang.ProcessEnvironment");
+            Field caseinsensitive = sc.getDeclaredField("theCaseInsensitiveEnvironment");
+            caseinsensitive.setAccessible(true);
+            @SuppressWarnings("unchecked")
+            Map<String, String> result = (Map<String, String>)caseinsensitive.get(null);
+            return result;
+        } catch (Exception e) {
+            throw new NativeIntegrationException("Unable to get mutable windows case insensitive environment map", e);
+        }
+    }
+
+    private Map<String, String> getEnv() {
+        try {
+            Map<String, String> theUnmodifiableEnvironment = System.getenv();
+            Class<?> cu = theUnmodifiableEnvironment.getClass();
+            Field m = cu.getDeclaredField("m");
+            m.setAccessible(true);
+            @SuppressWarnings("unchecked")
+            Map<String, String> result = (Map<String, String>)m.get(theUnmodifiableEnvironment);
+            return result;
+        } catch (Exception e) {
+            throw new NativeIntegrationException("Unable to get mutable environment map", e);
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/TerminalDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/TerminalDetector.java
new file mode 100644
index 0000000..719d875
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/TerminalDetector.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform;
+
+import java.io.FileDescriptor;
+
+public interface TerminalDetector {
+    boolean isTerminal(FileDescriptor fileDescriptor);
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/WindowsTerminalDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/WindowsTerminalDetector.java
new file mode 100755
index 0000000..e9ba420
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/WindowsTerminalDetector.java
@@ -0,0 +1,36 @@
+/*
+ * 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.internal.nativeplatform;
+
+import org.fusesource.jansi.WindowsAnsiOutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class WindowsTerminalDetector implements TerminalDetector {
+    public boolean isTerminal(FileDescriptor fileDescriptor) {
+        // Use Jansi's detection mechanism
+        try {
+            new WindowsAnsiOutputStream(new ByteArrayOutputStream());
+            return true;
+        } catch (IOException ignore) {
+            // Not attached to a console
+            return false;
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandler.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandler.java
new file mode 100644
index 0000000..e059430
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem;
+
+import org.jruby.ext.posix.POSIX;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ComposableFilePermissionHandler implements FilePermissionHandler {
+    private final Chmod chmod;
+    private final POSIX posix;
+
+    public ComposableFilePermissionHandler(Chmod chmod, POSIX posix) {
+        this.chmod = chmod;
+        this.posix = posix;
+    }
+
+    public int getUnixMode(File f) throws IOException {
+        return posix.stat(f.getAbsolutePath()).mode() & 0777;
+    }
+
+    public void chmod(File f, int mode) throws IOException {
+        chmod.chmod(f, mode);
+    }
+
+    public static interface Chmod {
+        public void chmod(File f, int mode) throws IOException;
+
+    }
+}
\ No newline at end of file
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackFileStat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackFileStat.java
new file mode 100644
index 0000000..43d79c6
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackFileStat.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem;
+
+import org.jruby.ext.posix.FileStat;
+
+import java.io.File;
+
+public class FallbackFileStat implements FileStat {
+
+    private final File file;
+
+    public FallbackFileStat(String path) {
+        this.file = new File(path);
+    }
+
+    public long atime() {
+        throw new UnsupportedOperationException("Operation atime() is not supported.");
+    }
+
+    public long blocks() {
+        throw new UnsupportedOperationException("Operation blocks is not supported.");
+    }
+
+    public long blockSize() {
+        throw new UnsupportedOperationException("Operation blockSize() is not supported.");
+    }
+
+    public long ctime() {
+        throw new UnsupportedOperationException("Operation ctime() is not supported.");
+    }
+
+    public long dev() {
+        throw new UnsupportedOperationException("Operation dev() is not supported.");
+    }
+
+    public String ftype() {
+        throw new UnsupportedOperationException("Operation ftype() is not supported.");
+    }
+
+    public int gid() {
+        throw new UnsupportedOperationException("Operation gid() is not supported.");
+    }
+
+    public boolean groupMember(int gid) {
+        throw new UnsupportedOperationException("Operation groupMember() is not supported.");
+    }
+
+    public long ino() {
+        throw new UnsupportedOperationException("Operation ino() is not supported.");
+    }
+
+    public boolean isBlockDev() {
+        throw new UnsupportedOperationException("Operation isBlockDev() is not supported.");
+    }
+
+    public boolean isCharDev() {
+        throw new UnsupportedOperationException("Operation isCharDev() is not supported.");
+    }
+
+    public boolean isDirectory() {
+        return file.isDirectory();
+    }
+
+    public boolean isEmpty() {
+        throw new UnsupportedOperationException("Operation isEmpty() is not supported.");
+    }
+
+    public boolean isExecutable() {
+        throw new UnsupportedOperationException("Operation isExecutable() is not supported.");
+    }
+
+    public boolean isExecutableReal() {
+        throw new UnsupportedOperationException("Operation isExecutableReal() is not supported.");
+    }
+
+    public boolean isFifo() {
+        throw new UnsupportedOperationException("Operation isFifo() is not supported.");
+    }
+
+    public boolean isFile() {
+        return file.isFile();
+    }
+
+    public boolean isGroupOwned() {
+        throw new UnsupportedOperationException("Operation isGroupOwned() is not supported.");
+    }
+
+    public boolean isIdentical(FileStat other) {
+        throw new UnsupportedOperationException("Operation isIdentical() is not supported.");
+    }
+
+    public boolean isNamedPipe() {
+        throw new UnsupportedOperationException("Operation isNamedPipe() is not supported.");
+    }
+
+    public boolean isOwned() {
+        throw new UnsupportedOperationException("Operation isOwned() is not supported.");
+    }
+
+    public boolean isROwned() {
+        throw new UnsupportedOperationException("Operation isROwned() is not supported.");
+    }
+
+    public boolean isReadable() {
+        throw new UnsupportedOperationException("Operation isReadable() is not supported.");
+    }
+
+    public boolean isReadableReal() {
+        throw new UnsupportedOperationException("Operation isReadableReal() is not supported.");
+    }
+
+    public boolean isWritable() {
+        throw new UnsupportedOperationException("Operation isWritable() is not supported.");
+    }
+
+    public boolean isWritableReal() {
+        throw new UnsupportedOperationException("Operation isWritableReal() is not supported.");
+    }
+
+    public boolean isSetgid() {
+        throw new UnsupportedOperationException("Operation isSetgid() is not supported.");
+    }
+
+    public boolean isSetuid() {
+        throw new UnsupportedOperationException("Operation isSetuid() is not supported.");
+    }
+
+    public boolean isSocket() {
+        throw new UnsupportedOperationException("Operation isSocket() is not supported.");
+    }
+
+    public boolean isSticky() {
+        throw new UnsupportedOperationException("Operation isSticky() is not supported.");
+    }
+
+    public boolean isSymlink() {
+        throw new UnsupportedOperationException("Operation isSymlink() is not supported.");
+    }
+
+    public int major(long dev) {
+        throw new UnsupportedOperationException("Operation major() is not supported.");
+    }
+
+    public int minor(long dev) {
+        throw new UnsupportedOperationException("Operation minor() is not supported.");
+    }
+
+    public int mode() {
+        if (isDirectory()) {
+            return FileSystem.DEFAULT_DIR_MODE;
+        } else {
+            return FileSystem.DEFAULT_FILE_MODE;
+        }
+    }
+
+    public long mtime() {
+        throw new UnsupportedOperationException("Operation mtime() is not supported.");
+    }
+
+    public int nlink() {
+        throw new UnsupportedOperationException("Operation nlink() is not supported.");
+    }
+
+    public long rdev() {
+        throw new UnsupportedOperationException("Operation rdev() is not supported.");
+    }
+
+    public long st_size() {
+        throw new UnsupportedOperationException("Operation st_size() is not supported.");
+    }
+
+    public int uid() {
+        throw new UnsupportedOperationException("Operation uid() is not supported.");
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIX.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIX.java
new file mode 100644
index 0000000..45bd2cf
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIX.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem;
+
+import org.jruby.ext.posix.FileStat;
+import org.jruby.ext.posix.Group;
+import org.jruby.ext.posix.POSIX;
+import org.jruby.ext.posix.Passwd;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class FallbackPOSIX implements POSIX {
+
+    static final int ENOTSUP = 1;
+
+    public int chmod(String filename, int mode) {
+        return 0;
+    }
+
+    public int chown(String filename, int user, int group) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int fork() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public FileStat fstat(FileDescriptor descriptor) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getegid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int geteuid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int seteuid(int euid) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getgid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public String getlogin() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getpgid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getpgid(int pid) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getpgrp() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getpid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getppid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getpriority(int which, int who) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public Passwd getpwent() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public Passwd getpwuid(int which) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public Passwd getpwnam(String which) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public Group getgrgid(int which) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public Group getgrnam(String which) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public Group getgrent() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int endgrent() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setgrent() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int endpwent() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setpwent() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int getuid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public boolean isatty(FileDescriptor descriptor) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int kill(int pid, int signal) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int lchmod(String filename, int mode) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int lchown(String filename, int user, int group) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int link(String oldpath, String newpath) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public FileStat lstat(String path) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int mkdir(String path, int mode) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public String readlink(String path) throws IOException {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setsid() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setgid(int gid) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setegid(int egid) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setpgid(int pid, int pgid) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setpgrp(int pid, int pgrp) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setpriority(int which, int who, int prio) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int setuid(int uid) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public FileStat stat(String path) {
+        return new FallbackFileStat(path);
+    }
+
+    public int symlink(String oldpath, String newpath) {
+        return ENOTSUP;
+    }
+
+    public int umask(int mask) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int utimes(String path, long[] atimeval, long[] mtimeval) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int waitpid(int pid, int[] status, int flags) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int wait(int[] status) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public int errno() {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+
+    public void errno(int value) {
+        throw new UnsupportedOperationException("This operation is not supported.");
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandler.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandler.java
new file mode 100644
index 0000000..2f35a2c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandler.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface FilePermissionHandler {
+    public int getUnixMode(File f) throws IOException;
+    public void chmod(File f, int mode) throws IOException;
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactory.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactory.java
new file mode 100644
index 0000000..99e40a5
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactory.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem;
+
+import com.sun.jna.LastErrorException;
+import com.sun.jna.Native;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.nativeplatform.jna.LibC;
+import org.gradle.internal.os.OperatingSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FilePermissionHandlerFactory {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FilePermissionHandlerFactory.class);
+
+    public static FilePermissionHandler createDefaultFilePermissionHandler() {
+        if (Jvm.current().isJava7() && !OperatingSystem.current().isWindows()) {
+            try {
+                String jdkFilePermissionclass = "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler";
+                FilePermissionHandler jdk7FilePermissionHandler =
+                        (FilePermissionHandler) FilePermissionHandler.class.getClassLoader().loadClass(jdkFilePermissionclass).newInstance();
+                return jdk7FilePermissionHandler;
+            } catch (Exception e) {
+                LOGGER.warn("Unable to load Jdk7FilePermissionHandler", e);
+            }
+        }
+        ComposableFilePermissionHandler.Chmod chmod = createChmod();
+        return new ComposableFilePermissionHandler(chmod, PosixUtil.current());
+    }
+
+    static ComposableFilePermissionHandler.Chmod createChmod() {
+        try {
+            LibC libc = (LibC) Native.loadLibrary("c", LibC.class);
+            return new LibcChmod(libc);
+        } catch (LinkageError e) {
+            return new EmptyChmod();
+        }
+    }
+
+    private static class LibcChmod implements ComposableFilePermissionHandler.Chmod {
+        private final LibC libc;
+
+        public LibcChmod(LibC libc) {
+            this.libc = libc;
+        }
+
+        public void chmod(File f, int mode) throws IOException {
+            try{
+                libc.chmod(f.getAbsolutePath(), mode);
+            }catch(LastErrorException exception){
+                throw new IOException(String.format("Failed to set file permissions %s on file %s. errno: %d", mode, f.getName(), exception.getErrorCode()));
+            }
+        }
+    }
+
+    private static class EmptyChmod implements ComposableFilePermissionHandler.Chmod {
+        public void chmod(File f, int mode) throws IOException {
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystem.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystem.java
new file mode 100755
index 0000000..448168f
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystem.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A file system accessible to Gradle.
+ */
+public interface FileSystem {
+    /**
+     * Default Unix permissions for directories, {@code 755}.
+     */
+    public static final int DEFAULT_DIR_MODE = 0755;
+
+    /**
+     * Default Unix permissions for files, {@code 644}.
+     */
+    public static final int DEFAULT_FILE_MODE = 0644;
+
+    /**
+     * Tells whether the file system is case sensitive.
+     *
+     * @return <tt>true</tt> if the file system is case sensitive, <tt>false</tt> otherwise
+     */
+    boolean isCaseSensitive();
+
+    /**
+     * Tells if the file system can create symbolic links. If the answer cannot be determined accurately,
+     * <tt>false</tt> is returned.
+     *
+     * @return <tt>true</tt> if the file system can create symbolic links, <tt>false</tt> otherwise
+     */
+    boolean canCreateSymbolicLink();
+
+    /**
+     * Creates a symbolic link to a target file.
+     *
+     *
+     * @param link the link to be created
+     * @param target the file to link to
+     * @exception java.io.IOException if the operation fails
+     */
+    void createSymbolicLink(File link, File target) throws IOException;
+
+    /**
+     * Tries to create a symbolic link to a target file.
+     *
+     * @param link the link to be created
+     * @param target the file to link to
+     * @return <tt>true</tt> if the operation was successful, <tt>false</tt> otherwise
+     */
+    boolean tryCreateSymbolicLink(File link, File target);
+
+    /**
+     * Returns the Unix permissions for a provided file. Some file systems may not
+     * support Unix permissions, in which case sensible default values are returned
+     * instead.
+     *
+     * @param file the file to read permissions from
+     * @throws java.io.FileNotFoundException if {@code file} doesn't exist
+     * @throws IOException if the permissions can't be read
+     * @return the file's Unix permissions, e.g. 0755
+     * @see #DEFAULT_DIR_MODE
+     * @see #DEFAULT_FILE_MODE
+     */
+    int getUnixMode(File file) throws IOException;
+
+    /**
+     * Changes the Unix permissions of a provided file. Implementations that don't
+     * support Unix permissions may choose to ignore this request.
+     *
+     * @param file the file to change permissions on
+     * @param mode the permissions, e.g. 0755
+     * @throws java.io.FileNotFoundException if {@code file} doesn't exist
+     * @throws IOException if the permissions can't be changed
+     */
+    void chmod(File file, int mode) throws IOException;
+}
+
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java
new file mode 100644
index 0000000..ee158b1
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.filesystem;
+
+public abstract class FileSystems {
+    public static FileSystem getDefault() {
+        return DefaultFileSystem.INSTANCE;
+    }
+
+    private static class DefaultFileSystem {
+        static final FileSystem INSTANCE = new GenericFileSystem(FilePermissionHandlerFactory.createDefaultFilePermissionHandler());
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
new file mode 100644
index 0000000..236630c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.filesystem;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.UUID;
+
+class GenericFileSystem implements FileSystem {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GenericFileSystem.class);
+
+    final boolean caseSensitive;
+    final boolean canCreateSymbolicLink;
+
+    final FilePermissionHandler filePermissionHandler;
+
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    public boolean canCreateSymbolicLink() {
+        return canCreateSymbolicLink;
+    }
+
+    public void createSymbolicLink(File link, File target) throws IOException {
+        int returnCode = doCreateSymbolicLink(link, target);
+        if (returnCode != 0) {
+            throw new IOException("Failed to create symbolic link " + link
+                    + " pointing to " + target + ". Return code is: " + returnCode);
+        }
+    }
+
+    public boolean tryCreateSymbolicLink(File link, File target) {
+        return doCreateSymbolicLink(link, target) == 0;
+    }
+
+    public int getUnixMode(File f) throws IOException {
+        assertFileExists(f);
+        return filePermissionHandler.getUnixMode(f);
+    }
+
+    public void chmod(File f, int mode) throws IOException {
+        assertFileExists(f);
+        filePermissionHandler.chmod(f, mode);
+    }
+
+    private int doCreateSymbolicLink(File link, File target) {
+        link.getParentFile().mkdirs();
+        try {
+            return PosixUtil.current().symlink(target.getPath(), link.getPath());
+        } catch (UnsatisfiedLinkError e) {
+            // Assume symlink() is not available
+            return 1;
+        }
+    }
+
+    protected final void assertFileExists(File f) throws FileNotFoundException {
+        if (!f.exists()) {
+            throw new FileNotFoundException(f + " does not exist");
+        }
+    }
+
+    GenericFileSystem(FilePermissionHandler handler) {
+        String content = generateUniqueContent();
+        File file = null;
+        try {
+            checkJavaIoTmpDirExists();
+            file = createFile(content);
+            caseSensitive = probeCaseSensitive(file, content);
+            canCreateSymbolicLink = probeCanCreateSymbolicLink(file, content);
+            filePermissionHandler = handler;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            FileUtils.deleteQuietly(file);
+        }
+    }
+
+    private String generateUniqueContent() {
+        return UUID.randomUUID().toString();
+    }
+
+    private File createFile(String content) throws IOException {
+        File file = File.createTempFile("gradle_fs_probing", null, null);
+        Files.write(content, file, Charsets.UTF_8);
+        return file;
+    }
+
+    private boolean probeCaseSensitive(File file, String content) {
+        try {
+            File upperCased = new File(file.getPath().toUpperCase());
+            return !hasContent(upperCased, content);
+        } catch (IOException e) {
+            // not fully accurate but a sensible fallback
+            // see http://stackoverflow.com/questions/1288102/how-do-i-detect-whether-the-file-system-is-case-sensitive
+            boolean result = !new File("foo").equals(new File("FOO"));
+            LOGGER.info("Failed to determine if file system is case sensitive. Best guess is '{}'.", result);
+            return result;
+        }
+    }
+
+    private boolean probeCanCreateSymbolicLink(File file, String content) {
+        File link = null;
+        try {
+            link = generateUniqueTempFileName();
+            int returnCode = doCreateSymbolicLink(link, file);
+            return returnCode == 0 && hasContent(link, content);
+        } catch (IOException e) {
+            LOGGER.info("Failed to determine if file system can create symbolic links. Assuming it can't.");
+            return false;
+        } finally {
+            FileUtils.deleteQuietly(link);
+        }
+    }
+
+    private boolean hasContent(File file, String content) throws IOException {
+        return file.exists() && Files.readFirstLine(file, Charsets.UTF_8).equals(content);
+    }
+
+    private File generateUniqueTempFileName() throws IOException {
+        return new File(System.getProperty("java.io.tmpdir"), "gradle_unique_file_name" + UUID.randomUUID().toString());
+    }
+
+    private void checkJavaIoTmpDirExists() throws IOException {
+        File dir = new File(System.getProperty("java.io.tmpdir"));
+        if (!dir.exists()) {
+            throw new IOException("java.io.tmpdir is set to a directory that doesn't exist: " + dir);
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java
new file mode 100644
index 0000000..907ea1c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java
@@ -0,0 +1,83 @@
+/*
+ * 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.internal.nativeplatform.filesystem;
+
+import org.jruby.ext.posix.*;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+public class PosixUtil {
+    private static final POSIX POSIX = FallbackAwarePosixFactory.getPOSIX();
+
+    public static POSIX current() {
+        return POSIX;
+    }
+
+    private static class FallbackAwarePosixFactory{
+        public static POSIX getPOSIX() {
+            POSIX posix = POSIXFactory.getPOSIX(new POSIXHandlerImpl(), true);
+            if(posix instanceof JavaPOSIX || posix instanceof WindowsPOSIX){
+                return new FallbackPOSIX();
+            }
+            return posix;
+        }
+    }
+
+    private static class POSIXHandlerImpl implements POSIXHandler {
+        public void error(POSIX.ERRORS error, String message) {
+            throw new UnsupportedOperationException(error + " - " + message);
+        }
+
+        public void unimplementedError(String message) {
+            throw new UnsupportedOperationException(message);
+        }
+
+        public void warn(WARNING_ID warningId, String message, Object... objects) {
+        }
+
+        public boolean isVerbose() {
+            return false;
+        }
+
+        public File getCurrentWorkingDirectory() {
+            throw new UnsupportedOperationException();
+        }
+
+        public String[] getEnv() {
+            throw new UnsupportedOperationException();
+        }
+
+        public InputStream getInputStream() {
+            return System.in;
+        }
+
+        public PrintStream getOutputStream() {
+            return System.out;
+        }
+
+        public int getPID() {
+            throw new UnsupportedOperationException();
+        }
+
+        public PrintStream getErrorStream() {
+            return System.err;
+        }
+    }
+
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverter.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverter.java
new file mode 100644
index 0000000..ba8c510
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem.jdk7;
+
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.EnumSet;
+import java.util.Set;
+
+import static java.nio.file.attribute.PosixFilePermission.*;
+
+public class PosixFilePermissionConverter {
+
+    static Set<PosixFilePermission> convertToPermissionsSet(int mode) {
+        Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class);
+
+        if (isSet(mode, 0400)) {
+            result.add(OWNER_READ);
+        }
+        if (isSet(mode, 0200)) {
+            result.add(OWNER_WRITE);
+        }
+        if (isSet(mode, 0100)) {
+            result.add(OWNER_EXECUTE);
+        }
+
+        if (isSet(mode, 040)) {
+            result.add(GROUP_READ);
+        }
+        if (isSet(mode, 020)) {
+            result.add(GROUP_WRITE);
+        }
+        if (isSet(mode, 010)) {
+            result.add(GROUP_EXECUTE);
+        }
+        if (isSet(mode, 04)) {
+            result.add(OTHERS_READ);
+        }
+        if (isSet(mode, 02)) {
+            result.add(OTHERS_WRITE);
+        }
+        if (isSet(mode, 01)) {
+            result.add(OTHERS_EXECUTE);
+        }
+        return result;
+    }
+
+    private static boolean isSet(int mode, int testbit) {
+        return (mode & testbit) == testbit;
+    }
+
+    public static int convertToInt(Set<PosixFilePermission> permissions) {
+        int result = 0;
+        if (permissions.contains(OWNER_READ)) {
+            result = result | 0400;
+        }
+        if (permissions.contains(OWNER_WRITE)) {
+            result = result | 0200;
+        }
+        if (permissions.contains(OWNER_EXECUTE)) {
+            result = result | 0100;
+        }
+        if (permissions.contains(GROUP_READ)) {
+            result = result | 040;
+        }
+        if (permissions.contains(GROUP_WRITE)) {
+            result = result | 020;
+        }
+        if (permissions.contains(GROUP_EXECUTE)) {
+            result = result | 010;
+        }
+        if (permissions.contains(OTHERS_READ)) {
+            result = result | 04;
+        }
+        if (permissions.contains(OTHERS_WRITE)) {
+            result = result | 02;
+        }
+        if (permissions.contains(OTHERS_EXECUTE)) {
+            result = result | 01;
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java
new file mode 100644
index 0000000..454a045
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem.jdk7;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+
+
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.filesystem.FilePermissionHandler;
+
+import static org.gradle.internal.nativeplatform.filesystem.jdk7.PosixFilePermissionConverter.convertToInt;
+import static org.gradle.internal.nativeplatform.filesystem.jdk7.PosixFilePermissionConverter.convertToPermissionsSet;
+
+public class PosixJdk7FilePermissionHandler implements FilePermissionHandler {
+
+    public int getUnixMode(File file) {
+        try {
+            final PosixFileAttributes posixFileAttributes = Files.readAttributes(file.toPath(), PosixFileAttributes.class);
+            return convertToInt(posixFileAttributes.permissions());
+        }catch (Exception e) {
+            throw new NativeIntegrationException(String.format("Failed to read File permissions for %s", file.getAbsolutePath()), e);
+        }
+    }
+
+    public void chmod(File f, int mode) throws IOException {
+        PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(f.toPath(), PosixFileAttributeView.class);
+        fileAttributeView.setPermissions(convertToPermissionsSet(mode));
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
new file mode 100644
index 0000000..39e7427
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.ReflectiveEnvironment;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.Map;
+
+public abstract class AbstractProcessEnvironment implements ProcessEnvironment {
+    //for updates to private JDK caches of the environment state
+    private final ReflectiveEnvironment reflectiveEnvironment = new ReflectiveEnvironment();
+
+    public boolean maybeSetEnvironment(Map<String, String> source) {
+        Map<String, String> currentEnv = System.getenv();
+        Iterable<String> names = new LinkedList<String>(currentEnv.keySet());
+        for (String name : names) {
+            removeEnvironmentVariable(name);
+        }
+        for (String key : source.keySet()) {
+            setEnvironmentVariable(key, source.get(key));
+        }
+        return true;
+    }
+
+    public void removeEnvironmentVariable(String name) throws NativeIntegrationException {
+        removeNativeEnvironmentVariable(name);
+        reflectiveEnvironment.unsetenv(name);
+    }
+
+    protected abstract void removeNativeEnvironmentVariable(String name);
+
+    public boolean maybeRemoveEnvironmentVariable(String name) {
+        removeEnvironmentVariable(name);
+        return true;
+    }
+
+    public void setEnvironmentVariable(String name, String value) throws NativeIntegrationException {
+        if (value == null) {
+            removeEnvironmentVariable(name);
+            return;
+        }
+
+        setNativeEnvironmentVariable(name, value);
+        reflectiveEnvironment.setenv(name, value);
+    }
+
+    protected abstract void setNativeEnvironmentVariable(String name, String value);
+
+    public boolean maybeSetEnvironmentVariable(String name, String value) {
+        setEnvironmentVariable(name, value);
+        return true;
+    }
+
+    public void setProcessDir(File processDir) throws NativeIntegrationException {
+        if (!processDir.exists()) {
+            return;
+        }
+
+        setNativeProcessDir(processDir);
+        System.setProperty("user.dir", processDir.getAbsolutePath());
+    }
+
+    protected abstract void setNativeProcessDir(File processDir);
+
+    public boolean maybeSetProcessDir(File processDir) {
+        setProcessDir(processDir);
+        return true;
+    }
+
+    public Long maybeGetPid() {
+        return getPid();
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/JnaBootPathConfigurer.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/JnaBootPathConfigurer.java
new file mode 100644
index 0000000..036c43a
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/JnaBootPathConfigurer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.NativeIntegrationUnavailableException;
+import org.gradle.internal.os.OperatingSystem;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author: Szczepan Faber, created at: 9/12/11
+ */
+public class JnaBootPathConfigurer {
+    private final File storageDir;
+
+    /**
+     * Attempts to find the jna library and copies it to a specified folder.
+     * The copy operation happens only once. Sets the jna-related system property.
+     *
+     * This hackery is to prevent JNA from creating a shared lib in the tmp dir, as it does not clean things up.
+     *
+     * @param storageDir - where to store the jna library
+     */
+    public JnaBootPathConfigurer(File storageDir) {
+        this.storageDir = storageDir;
+    }
+
+    public void configure() throws NativeIntegrationUnavailableException {
+        File tmpDir = new File(storageDir, "jna");
+        tmpDir.mkdirs();
+        String jnaLibName = OperatingSystem.current().isMacOsX() ? "libjnidispatch.jnilib" : System.mapLibraryName("jnidispatch");
+        File libFile = new File(tmpDir, jnaLibName);
+        if (!libFile.exists()) {
+            String resourceName = "/com/sun/jna/" + OperatingSystem.current().getNativePrefix() + "/" + jnaLibName;
+            try {
+                InputStream lib = getClass().getResourceAsStream(resourceName);
+                if (lib == null) {
+                    throw new NativeIntegrationUnavailableException(String.format("Could not locate JNA native library resource '%s'.", resourceName));
+                }
+                try {
+                    FileOutputStream outputStream = new FileOutputStream(libFile);
+                    try {
+                        IOUtils.copy(lib, outputStream);
+                    } finally {
+                        outputStream.close();
+                    }
+                } finally {
+                    lib.close();
+                }
+            } catch (IOException e) {
+                throw new NativeIntegrationException(String.format("Could not create JNA native library '%s'.", libFile), e);
+            }
+        }
+        System.setProperty("jna.boot.library.path", tmpDir.getAbsolutePath());
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/Kernel32.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/Kernel32.java
new file mode 100755
index 0000000..36c32e3
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/Kernel32.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.PointerType;
+import com.sun.jna.win32.W32APIOptions;
+
+/**
+* Windows' Kernel32
+*/
+public interface Kernel32 extends Library {
+
+    //CHECKSTYLE:OFF
+
+    Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
+
+    int STD_INPUT_HANDLE = -10;
+    int STD_OUTPUT_HANDLE = -11;
+    int STD_ERROR_HANDLE = -12;
+    int HANDLE_FLAG_INHERIT = 1;
+    int ERROR_INVALID_HANDLE = 6;
+    int ERROR_INVALID_PARAMETER = 87;
+    HANDLE INVALID_HANDLE_VALUE = new HANDLE(new Pointer(-1));
+
+    int GetLastError();
+
+    boolean SetEnvironmentVariable(String lpName, String lpValue);
+
+    HANDLE GetStdHandle(int stdHandle);
+
+    boolean SetHandleInformation(HANDLE handle, int dwMask, int dwFlags);
+
+    boolean SetCurrentDirectory(String lpPathName);
+
+    int GetCurrentDirectory(int nBufferLength, char[] lpBuffer);
+
+    int GetCurrentProcessId();
+
+    class HANDLE extends PointerType {
+        public HANDLE() {
+        }
+
+        public HANDLE(Pointer p) {
+            super(p);
+        }
+    }
+
+    //CHECKSTYLE:ON
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
new file mode 100644
index 0000000..68ff7da
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import com.sun.jna.LastErrorException;
+import com.sun.jna.Library;
+
+public interface LibC extends Library {
+    //CHECKSTYLE:OFF
+    public int setenv(String name, String value, int overwrite) throws LastErrorException;
+    public int unsetenv(String name) throws LastErrorException;
+    public String getcwd(byte[] out, int size) throws LastErrorException;
+    public int chdir(String dirAbsolutePath) throws LastErrorException;
+    public int getpid();
+    public int isatty(int fdes);
+    public int chmod(String filename, int mode) throws LastErrorException;
+    //CHECKSTYLE:ON
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
new file mode 100644
index 0000000..ea88351
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import com.sun.jna.LastErrorException;
+import com.sun.jna.Native;
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+
+import java.io.File;
+
+/**
+ * Uses JNA to drive the POSIX API provided by libc
+ */
+public class LibCBackedProcessEnvironment extends AbstractProcessEnvironment {
+    private static final int LOTS_OF_CHARS = 2048;
+    private final LibC libc;
+
+    public LibCBackedProcessEnvironment(LibC libc) {
+        this.libc = libc;
+    }
+
+    public void setNativeEnvironmentVariable(String name, String value) {
+        try {
+            libc.setenv(name, value, 1);
+        } catch (LastErrorException lastErrorException) {
+            throw new NativeIntegrationException(String.format("Could not set environment variable '%s'. errno: %d", name, lastErrorException.getErrorCode()));
+        }
+    }
+
+    public void removeNativeEnvironmentVariable(String name) {
+        try {
+            libc.unsetenv(name);
+        } catch (LastErrorException lastErrorException) {
+            throw new NativeIntegrationException(String.format("Could not unset environment variable '%s'. errno: %d", name, lastErrorException.getErrorCode()));
+        }
+    }
+
+    public void setNativeProcessDir(File dir) {
+        try {
+            libc.chdir(dir.getAbsolutePath());
+        } catch (LastErrorException lastErrorException) {
+            throw new NativeIntegrationException(String.format("Could not set process working directory to '%s'. errno: %d", dir, lastErrorException.getErrorCode()));
+        }
+    }
+
+    public File getProcessDir() {
+        byte[] out = new byte[LOTS_OF_CHARS];
+        try {
+            libc.getcwd(out, LOTS_OF_CHARS);
+        } catch (LastErrorException lastErrorException) {
+            throw new NativeIntegrationException(String.format("Could not get process working directory. errno: %d", lastErrorException.getErrorCode()));
+        }
+        return new File(Native.toString(out));
+    }
+
+    public Long getPid() {
+        return Long.valueOf(libc.getpid());
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedTerminalDetector.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedTerminalDetector.java
new file mode 100644
index 0000000..d15eab8
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedTerminalDetector.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.nativeplatform.TerminalDetector;
+
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+
+public class LibCBackedTerminalDetector implements TerminalDetector {
+    private final LibC libC;
+
+    public LibCBackedTerminalDetector(LibC libC) {
+        this.libC = libC;
+    }
+
+    public boolean isTerminal(FileDescriptor fileDescriptor) {
+        int osFileDesc;
+        try {
+            Field fdField = FileDescriptor.class.getDeclaredField("fd");
+            fdField.setAccessible(true);
+            osFileDesc = fdField.getInt(fileDescriptor);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        // Determine if we're connected to a terminal
+        if (libC.isatty(osFileDesc) == 0) {
+            return false;
+        }
+
+        // Dumb terminal doesn't support ANSI control codes. Should really be using termcap database.
+        String term = System.getenv("TERM");
+        if (term != null && term.equals("dumb")) {
+            return false;
+        }
+
+        // Assume a terminal
+        return true;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/UnsupportedEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/UnsupportedEnvironment.java
new file mode 100644
index 0000000..f758f38
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/UnsupportedEnvironment.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.NativeIntegrationUnavailableException;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.os.OperatingSystem;
+
+import java.io.File;
+import java.util.Map;
+
+public class UnsupportedEnvironment implements ProcessEnvironment {
+    public boolean maybeSetEnvironment(Map<String, String> source) {
+        return false;
+    }
+
+    public void removeEnvironmentVariable(String name) throws NativeIntegrationException {
+        throw notSupported();
+    }
+
+    public boolean maybeRemoveEnvironmentVariable(String name) {
+        return false;
+    }
+
+    public void setEnvironmentVariable(String name, String value) throws NativeIntegrationException {
+        throw notSupported();
+    }
+
+    public boolean maybeSetEnvironmentVariable(String name, String value) {
+        return false;
+    }
+
+    public File getProcessDir() throws NativeIntegrationException {
+        throw notSupported();
+    }
+
+    public void setProcessDir(File processDir) throws NativeIntegrationException {
+        throw notSupported();
+    }
+
+    public boolean maybeSetProcessDir(File processDir) {
+        return false;
+    }
+
+    public Long getPid() throws NativeIntegrationException {
+        throw notSupported();
+    }
+
+    public Long maybeGetPid() {
+        return null;
+    }
+
+    private NativeIntegrationException notSupported() {
+        return new NativeIntegrationUnavailableException("We don't support this operating system: " + OperatingSystem.current());
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsHandlesManipulator.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsHandlesManipulator.java
new file mode 100644
index 0000000..17960dc
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsHandlesManipulator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import static org.gradle.internal.nativeplatform.jna.Kernel32.*;
+
+/**
+ * Uses jna to make the stream handles 'uninheritable'. 
+ * This way we can achieve a fully detached process on windows.
+ * Without that, if the process was spawning child processes on windows (for example gradle build daemon) it waited until the child has completed.
+ * This is undesired because sometimes the child process is a long-running process but the parent process is a short-running process.
+ */
+public class WindowsHandlesManipulator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(WindowsHandlesManipulator.class);
+
+    /**
+     * Makes the standard streams handles 'uninheritable' for the current process.
+     *
+     * @throws ProcessInitializationException when the operation fails
+     */
+    public void uninheritStandardStreams() throws ProcessInitializationException {
+        Kernel32 kernel32 = INSTANCE;
+
+        try {
+            uninheritStream(kernel32, Kernel32.STD_INPUT_HANDLE);
+            uninheritStream(kernel32, Kernel32.STD_OUTPUT_HANDLE);
+            uninheritStream(kernel32, Kernel32.STD_ERROR_HANDLE);
+        } catch (Exception e) {
+            throw new ProcessInitializationException("Failed to configure the standard stream handles to be 'uninheritable'.", e);
+        }
+    }
+
+    private void uninheritStream(Kernel32 kernel32, int stdInputHandle) throws IOException {
+        HANDLE streamHandle = kernel32.GetStdHandle(stdInputHandle);
+        if (streamHandle == null) {
+            // We're not attached to a stdio (eg Desktop application). Ignore.
+            return;
+        }
+        makeUninheritable(kernel32, streamHandle);
+    }
+
+    private void makeUninheritable(Kernel32 kernel32, HANDLE streamHandle) throws IOException {
+        if (streamHandle.equals(INVALID_HANDLE_VALUE)) {
+            throw new IOException("Invalid handle. Errno: " + kernel32.GetLastError());
+        }
+        boolean ok = kernel32.SetHandleInformation(streamHandle, HANDLE_FLAG_INHERIT, 0);
+        if (!ok) {
+            int setHandleError = kernel32.GetLastError();
+            if (setHandleError == ERROR_INVALID_PARAMETER) {
+                // Didn't get a valid handle: ignore.
+                LOGGER.debug("Invalid parameter attempting to uninherit stream - child process may remain attached.");
+                return;
+            }
+            if (setHandleError == ERROR_INVALID_HANDLE) {
+                LOGGER.debug("Invalid handle attempting to uninherit stream - child process may remain attached.");
+                return;
+            }
+            throw new IOException("Could not set flag on handle. Errno: " + kernel32.GetLastError());
+        }
+    }
+
+    public static class ProcessInitializationException extends RuntimeException {
+        public ProcessInitializationException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
new file mode 100755
index 0000000..87162dc
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna;
+
+import com.sun.jna.Native;
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+
+import java.io.File;
+
+/**
+ * Uses JNA to drive the functions provided by kernel32.dll
+ */
+public class WindowsProcessEnvironment extends AbstractProcessEnvironment {
+    private static final int LOTS_OF_CHARS = 2048;
+    private final Kernel32 kernel32 = Kernel32.INSTANCE;
+
+    public void setNativeEnvironmentVariable(String name, String value) {
+        boolean retval = kernel32.SetEnvironmentVariable(name, value == null ? null : value);
+        if (!retval) {
+            throw new NativeIntegrationException(String.format("Could not set environment variable '%s'. errno: %d", name, kernel32.GetLastError()));
+        }
+    }
+
+    public void removeNativeEnvironmentVariable(String name) {
+        setNativeEnvironmentVariable(name, null);
+    }
+
+    public void setNativeProcessDir(File dir) {
+        boolean retval = kernel32.SetCurrentDirectory(dir.getAbsolutePath());
+        if (!retval) {
+            throw new NativeIntegrationException(String.format("Could not set process working directory to '%s'. errno: %d", dir, kernel32.GetLastError()));
+        }
+    }
+
+    public File getProcessDir() {
+        char[] out = new char[LOTS_OF_CHARS];
+        int retval = kernel32.GetCurrentDirectory(out.length, out);
+        if (retval == 0) {
+            throw new NativeIntegrationException(String.format("Could not get process working directory. errno: %d", kernel32.GetLastError()));
+        }
+        return new File(Native.toString(out));
+    }
+
+    public Long getPid() {
+        return Long.valueOf(kernel32.GetCurrentProcessId());
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
new file mode 100755
index 0000000..9124feb
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.services;
+
+import com.sun.jna.Native;
+import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.TerminalDetector;
+import org.gradle.internal.nativeplatform.WindowsTerminalDetector;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.jna.*;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.service.DefaultServiceRegistry;
+
+/**
+ * Provides various native platform integration services.
+ */
+public class NativeServices extends DefaultServiceRegistry {
+    protected OperatingSystem createOperatingSystem() {
+        return OperatingSystem.current();
+    }
+
+    protected FileSystem createFileSystem() {
+        return FileSystems.getDefault();
+    }
+
+    protected ProcessEnvironment createProcessEnvironment() {
+        try {
+            if (OperatingSystem.current().isUnix()) {
+                return new LibCBackedProcessEnvironment(get(LibC.class));
+            } else if (OperatingSystem.current().isWindows()) {
+                return new WindowsProcessEnvironment();
+            } else {
+                return new UnsupportedEnvironment();
+            }
+        } catch (LinkageError e) {
+            // Thrown when jna cannot initialize the native stuff
+            return new UnsupportedEnvironment();
+        }
+    }
+
+    protected TerminalDetector createTerminalDetector() {
+        try {
+            if (get(OperatingSystem.class).isWindows()) {
+                return new WindowsTerminalDetector();
+            }
+            return new LibCBackedTerminalDetector(get(LibC.class));
+        } catch (LinkageError e) {
+            // Thrown when jna cannot initialize the native stuff
+            return new NoOpTerminalDetector();
+        }
+    }
+    
+    protected LibC createLibC() {
+        return (LibC) Native.loadLibrary("c", LibC.class);
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
new file mode 100644
index 0000000..d6d6fac
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.filesystem
+
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+class CommonFileSystemTest extends Specification {
+    @Rule TemporaryFolder tmpDir
+
+    def fs = FileSystems.default
+    
+    def "unix permissions cannot be read on non existing file"() {
+        when:
+        fs.getUnixMode(tmpDir.file("someFile"))
+
+        then:
+        thrown(FileNotFoundException)
+    }
+
+    def "unix permissions cannot be set on non existing file"() {
+        when:
+        fs.chmod(tmpDir.file("someFile"), 0644)
+
+        then:
+        thrown(FileNotFoundException)
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "unix permissions on files can be changed and read"() {
+        def f = tmpDir.createFile("someFile")
+
+        when:
+        fs.chmod(f, mode)
+
+        then:
+        fs.getUnixMode(f) == mode
+        f.mode == mode
+
+        where:
+        mode << [0644, 0600, 0751]
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "unix permissions on directories can be changed and read"() {
+        def d = tmpDir.createDir("someDir")
+
+        when:
+        fs.chmod(d, mode)
+
+        then:
+        fs.getUnixMode(d) == mode
+        d.mode == mode
+
+        where:
+        mode << [0755, 0700, 0722]
+    }
+
+    @Requires(TestPrecondition.NO_FILE_PERMISSIONS)
+    def "unix permissions have default values on unsupported platforms"() {
+        expect:
+        fs.getUnixMode(tmpDir.createFile("someFile")) == FileSystem.DEFAULT_FILE_MODE
+        fs.getUnixMode(tmpDir.createDir("someDir")) == FileSystem.DEFAULT_DIR_MODE
+    }
+
+    @Requires(TestPrecondition.NO_FILE_PERMISSIONS)
+    def "setting unix permissions does nothing on unsupported platforms"() {
+        expect:
+        fs.chmod(tmpDir.createFile("someFile"), 0644)
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "can create symlink on platforms that support symlinks"() {
+        def target = tmpDir.createFile("target.txt")
+        def link = tmpDir.file("link.txt")
+
+        when:
+        fs.createSymbolicLink(link, target)
+
+        then:
+        link.exists()
+        link.readLink() == target.absolutePath
+    }
+
+    @Requires(TestPrecondition.NO_SYMLINKS)
+    def "cannot create symlinks on platforms that do not support symlinks"() {
+        def target = tmpDir.createFile("target.txt")
+        def link = tmpDir.file("link.txt")
+
+        when:
+        fs.createSymbolicLink(link, target)
+
+        then:
+        thrown(IOException)
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandlerTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandlerTest.groovy
new file mode 100644
index 0000000..49c0774
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandlerTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem
+
+import org.jruby.ext.posix.FileStat
+import org.jruby.ext.posix.POSIX
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+class ComposableFilePermissionHandlerTest extends Specification {
+    final ComposableFilePermissionHandler.Chmod chmod = Mock()
+    final POSIX posix = Mock()
+    final ComposableFilePermissionHandler handler = new ComposableFilePermissionHandler(chmod, posix)
+
+    @Rule TemporaryFolder temporaryFolder;
+    def "chmod calls are delegated to Chmod"(){
+        setup:
+        def file = temporaryFolder.createFile("testfile");
+        when:
+        handler.chmod(file, 0744);
+
+        then:
+        1 * chmod.chmod(file, 0744)
+    }
+
+    def "getUnixMode calls are delegated to POSIX"(){
+        setup:
+        FileStat stat = Mock()
+        def file = temporaryFolder.createFile("testfile");
+        posix.stat(file.getAbsolutePath()) >> stat
+        stat.mode() >> 0754
+
+        expect:
+        handler.getUnixMode(file) == 0754
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackFileStatTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackFileStatTest.groovy
new file mode 100644
index 0000000..42e4648
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackFileStatTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+class FallbackFileStatTest extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder;
+
+    def "mode() returns FileSystem.DEFAULT_FILE_MODE for files"() {
+        setup:
+        def testfile = temporaryFolder.createFile("testFile")
+        FallbackFileStat fallbackFileStat = new FallbackFileStat(testfile.absolutePath)
+        expect:
+        FileSystem.DEFAULT_FILE_MODE == fallbackFileStat.mode()
+    }
+
+    def "mode() returns FileSystem.DEFAULT_DIR_MODE for directories"() {
+        setup:
+        def testfolder = temporaryFolder.createDir()
+        FallbackFileStat fallbackFileStat = new FallbackFileStat(testfolder.absolutePath)
+        expect:
+        FileSystem.DEFAULT_DIR_MODE == fallbackFileStat.mode()
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIXTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIXTest.groovy
new file mode 100644
index 0000000..fe496d8
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIXTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem
+
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.util.TemporaryFolder
+import org.jruby.ext.posix.POSIX
+
+class FallbackPOSIXTest extends Specification {
+
+    FallbackPOSIX posix = new FallbackPOSIX()
+
+    @Rule TemporaryFolder tempFolder;
+
+    def "returns 0 on chmod calls"() {
+        setup:
+        def testFile = tempFolder.createFile("testFile");
+        expect:
+        0 == posix.chmod(testFile.absolutePath, mode)
+        where:
+        mode << [644, 755, 777];
+    }
+
+    def "stat() returns instance of FallbackStat"() {
+        setup:
+        def testFile = tempFolder.createDir();
+        when:
+        def stat = posix.stat(testFile.absolutePath)
+        then:
+        stat instanceof FallbackFileStat
+    }
+
+    def "returns errno code 1 (ENOTSUP) for symlink calls"() {
+        expect:
+        1 == posix.symlink("/old/path", "new/path")
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnJdk7Test.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnJdk7Test.groovy
new file mode 100644
index 0000000..531289e
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnJdk7Test.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+ at Requires(TestPrecondition.JDK7)
+class FilePermissionHandlerFactoryOnJdk7Test extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "createChmod creates EmptyChmod instance on Windows OS"() {
+        when:
+        def chmod = FilePermissionHandlerFactory.createChmod()
+        then:
+        chmod instanceof FilePermissionHandlerFactory.EmptyChmod
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Windows OS"() {
+        def File file = temporaryFolder.createFile("testFile")
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        when:
+        handler.chmod(file, mode);
+        then:
+        644 == handler.getUnixMode(file);
+        where:
+        mode << [0722, 0644, 0744, 0755]
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "createDefaultFilePermissionHandler creates Jdk7PosixFilePermissionHandler on JDK7 with Posix Fs"() {
+        when:
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        then:
+        handler.getClass().name == "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler"
+    }
+
+    @Requires(TestPrecondition.UNKNOWN_OS)
+    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Unknown OS"() {
+        setup:
+        def File file = temporaryFolder.createFile("testFile")
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        def originalMode = handler.getUnixMode(file);
+        when:
+        handler.chmod(file, mode);
+        then:
+        originalMode == handler.getUnixMode(file);
+        where:
+        mode << [0722, 0644, 0744, 0755]
+    }
+
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnNonJdk7Test.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnNonJdk7Test.groovy
new file mode 100644
index 0000000..a0c45b2
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnNonJdk7Test.groovy
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem;
+
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+ at Requires(TestPrecondition.NOT_JDK7)
+public class FilePermissionHandlerFactoryOnNonJdk7Test extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "createChmod creates EmptyChmod instance on Windows OS"() {
+        when:
+        def chmod = FilePermissionHandlerFactory.createChmod()
+        then:
+        chmod instanceof FilePermissionHandlerFactory.EmptyChmod
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Windows OS"() {
+        when:
+        def chmod = FilePermissionHandlerFactory.createChmod()
+        then:
+        chmod instanceof FilePermissionHandlerFactory.EmptyChmod
+    }
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "createDefaultFilePermissionHandler creates ComposeableFilePermissionHandler if not on Windows OS"() {
+        when:
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        then:
+        handler instanceof ComposableFilePermissionHandler
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with enabled Chmod on Unknown OS"() {
+        setup:
+        def file = temporaryFolder.createFile("testFile")
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        when:
+        handler.chmod(file, mode);
+        then:
+        mode == handler.getUnixMode(file);
+        where:
+        mode << [0722, 0644, 0744, 0755]
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "Throws IOException for failed chmod calls"() {
+        setup:
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        def notExistingFile = new File(temporaryFolder.createDir(), "nonexisting.file");
+        when:
+        handler.chmod(notExistingFile, 622);
+        then:
+        def e = thrown(IOException)
+        e.message == "Failed to set file permissions 622 on file nonexisting.file. errno: 2"
+    }
+
+    @Requires(TestPrecondition.UNKNOWN_OS)
+    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Unknown OS"() {
+        setup:
+        def file = temporaryFolder.createFile("testFile")
+
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        def originalMode = handler.getUnixMode(file);
+        when:
+        handler.chmod(file, mode);
+        then:
+        originalMode == handler.getUnixMode(file);
+        where:
+        mode << [0722, 0644, 0744, 0755]
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy
new file mode 100644
index 0000000..aaf201e
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LinuxFileSystemTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.filesystem
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+
+ at Requires(TestPrecondition.LINUX)
+class LinuxFileSystemTest extends Specification {
+    def fs = FileSystems.default
+
+    def "is case sensitive"() {
+        expect:
+        fs.caseSensitive
+    }
+
+    def "can create symbolic link"() {
+        expect:
+        fs.canCreateSymbolicLink()
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy
new file mode 100644
index 0000000..045a7bc
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/MacOsFileSystemTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.filesystem
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+
+ at Requires(TestPrecondition.MAC_OS_X)
+class MacOsFileSystemTest extends Specification {
+    def fs = FileSystems.default
+
+    def "is not case sensitive"() {
+        expect:
+        !fs.caseSensitive
+    }
+
+    def "can create symbolic link"() {
+        expect:
+        fs.canCreateSymbolicLink()
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/PosixUtilTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/PosixUtilTest.groovy
new file mode 100644
index 0000000..496f6da
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/PosixUtilTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem
+
+import spock.lang.Specification
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition;
+
+public class PosixUtilTest extends Specification {
+
+    @Requires(TestPrecondition.UNKNOWN_OS)
+    def "PosixUtil.current returns FallbackPOSIX on Unknown OS"() {
+        expect:
+        PosixUtil.current() instanceof FallbackPOSIX
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "PosixUtil.current returns FallbackPOSIX on WindowsOS"() {
+        expect:
+        PosixUtil.current() instanceof FallbackPOSIX
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "PosixUtil.current returns no FallbackPOSIX on Macosx and Unix"() {
+        expect:
+        !(PosixUtil.current() instanceof FallbackPOSIX)
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy
new file mode 100644
index 0000000..981a514
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/WindowsFileSystemTest.groovy
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+
+ at Requires(TestPrecondition.WINDOWS)
+class WindowsFileSystemTest extends Specification {
+    def fs = FileSystems.default
+
+    def "windows file system is case insensitive"() {
+        expect:
+        !fs.caseSensitive
+    }
+
+    def "windows file system cannot create symbolic link"() {
+        expect:
+        !fs.canCreateSymbolicLink()
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy
new file mode 100644
index 0000000..11de586
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixFilePermissionConverterTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem.jdk7
+
+import java.nio.file.attribute.PosixFilePermission
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+import static java.nio.file.attribute.PosixFilePermission.*
+
+ at Requires(TestPrecondition.JDK7)
+class PosixFilePermissionConverterTest extends Specification {
+    def "converts Set<PosixFilePermission to int representation"() {
+
+        expect:
+        PosixFilePermissionConverter.convertToInt(perms) == intValue
+
+        where:
+        perms                                                                |       intValue
+        EnumSet.noneOf(PosixFilePermission)                                  |       0
+        EnumSet.allOf(PosixFilePermission)                                   |       0777
+        EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)                   |       0700
+        EnumSet.of(OWNER_READ, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE)       |       0470
+        EnumSet.of(OWNER_READ, GROUP_READ, OTHERS_READ)                      |       0444
+        EnumSet.of(OWNER_READ, OWNER_WRITE, GROUP_READ, OTHERS_READ)         |       0644
+        EnumSet.of(OWNER_READ, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE)       |       0560
+    }
+
+
+    def "converts int representation to Set<PosixFilePermission)String representation"() {
+        expect:
+        perms == PosixFilePermissionConverter.convertToPermissionsSet(intValue)
+
+        where:
+        perms                                                                |       intValue
+        EnumSet.noneOf(PosixFilePermission)                                  |       0
+        EnumSet.allOf(PosixFilePermission)                                   |       0777
+        EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)                   |       0700
+        EnumSet.of(OWNER_READ, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE)       |       0470
+        EnumSet.of(OWNER_READ, GROUP_READ, OTHERS_READ)                      |       0444
+        EnumSet.of(OWNER_READ, OWNER_WRITE, GROUP_READ, OTHERS_READ)         |       0644
+        EnumSet.of(OWNER_READ, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE)       |       0560
+
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
new file mode 100644
index 0000000..e401c89
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.filesystem.jdk7
+
+import spock.lang.Specification
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.internal.nativeplatform.filesystem.FilePermissionHandlerFactory
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+
+class PosixJdk7FilePermissionHandlerTest extends Specification {
+
+    @Rule TemporaryFolder temporaryFolder
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "test chmod on non windows platforms with JDK7"() {
+        setup:
+        def file = temporaryFolder.createFile("testFile")
+        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        when:
+        handler.chmod(file, mode);
+        then:
+        mode == handler.getUnixMode(file);
+        where:
+        mode << [0722, 0644, 0744, 0755]
+    }
+
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironmentTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironmentTest.groovy
new file mode 100644
index 0000000..69abfdd
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironmentTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.internal.nativeplatform.jna
+
+import org.gradle.internal.nativeplatform.NativeIntegrationException
+import org.gradle.internal.nativeplatform.services.NativeServices
+import spock.lang.Specification
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class LibCBackedProcessEnvironmentTest extends Specification {
+
+    NativeServices registry = new NativeServices();
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS) //MACOSX & UNIX
+    def "setNativeEnvironmentVariable throws NativeEnvironmentException for NULL value on env name with errno code"() {
+        setup:
+        LibCBackedProcessEnvironment processEnvironment = new LibCBackedProcessEnvironment(registry.get(LibC.class))
+        when:
+        processEnvironment.setNativeEnvironmentVariable(null, "TEST_ENV_VAR");
+        then:
+        def e = thrown(NativeIntegrationException);
+        e.message == "Could not set environment variable 'null'. errno: 22"
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
new file mode 100644
index 0000000..ec7968d
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.jna
+
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.util.Requires
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProcessEnvironmentTest extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule final SetSystemProperties systemProperties = new SetSystemProperties()
+    final ProcessEnvironment env = new NativeServices().get(ProcessEnvironment)
+
+    @Requires(TestPrecondition.SET_ENV_VARIABLE)
+    def "can set and remove environment variable"() {
+        when:
+        env.setEnvironmentVariable("TEST_ENV_1", "value")
+        env.maybeSetEnvironmentVariable("TEST_ENV_2", "value")
+
+        then:
+        System.getenv("TEST_ENV_1") == "value"
+        System.getenv("TEST_ENV_2") == "value"
+
+        when:
+        env.removeEnvironmentVariable("TEST_ENV_1")
+        env.maybeRemoveEnvironmentVariable("TEST_ENV_2")
+
+        then:
+        System.getenv("TEST_ENV_1") == null
+        System.getenv("TEST_ENV_2") == null
+    }
+
+    @Requires(TestPrecondition.WORKING_DIR)
+    def "can get working directory of current process"() {
+        expect:
+        env.processDir.canonicalFile == new File('.').canonicalFile
+    }
+
+    @Requires(TestPrecondition.WORKING_DIR)
+    def "can get set working directory of current process"() {
+        File originalDir = new File(System.getProperty("user.dir"))
+
+        when:
+        env.setProcessDir(tmpDir.dir)
+
+        then:
+        env.processDir.canonicalFile == tmpDir.dir
+        new File(".").canonicalFile == tmpDir.dir
+
+        cleanup:
+        System.setProperty("user.dir", originalDir.absolutePath)
+        env.setProcessDir(originalDir)
+    }
+
+    @Requires(TestPrecondition.PROCESS_ID)
+    def "can get pid of current process"() {
+        expect:
+        env.pid != null
+        env.maybeGetPid() == env.pid
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/services/NativeServicesTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/services/NativeServicesTest.groovy
new file mode 100755
index 0000000..93dd007
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/services/NativeServicesTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.internal.nativeplatform.services
+
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.TerminalDetector
+import org.gradle.internal.nativeplatform.filesystem.FileSystem
+import org.gradle.internal.os.OperatingSystem
+import spock.lang.Specification
+
+class NativeServicesTest extends Specification {
+    final NativeServices services = new NativeServices()
+    
+    def "makes a ProcessEnvironment available"() {
+        expect:
+        services.get(ProcessEnvironment) != null
+    }
+
+    def "makes an OperatingSystem available"() {
+        expect:
+        services.get(OperatingSystem) != null
+    }
+
+    def "makes a FileSystem available"() {
+        expect:
+        services.get(FileSystem) != null
+    }
+
+    def "makes a TerminalDetector available"() {
+        expect:
+        services.get(TerminalDetector) != null
+    }
+}
diff --git a/subprojects/open-api/open-api.gradle b/subprojects/open-api/open-api.gradle
index 88c9c91..39be9ad 100644
--- a/subprojects/open-api/open-api.gradle
+++ b/subprojects/open-api/open-api.gradle
@@ -1,13 +1,12 @@
-apply plugin: 'groovy'
-apply from: "$rootDir/gradle/integTest.gradle"
-
 dependencies {
-    groovy libraries.groovy_depends
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    groovy libraries.groovy
 
     integTestCompile libraries.slf4j_api
-    integTestCompile project(path: ':core', configuration: 'integTestFixtures')
-    integTestRuntime project(path: ':core', configuration: 'integTestFixturesRuntime')
+    integTestCompile libraries.commons_lang
 }
+
+useTestFixtures()
+
+integTestTasks.all {
+    jvmArgs '-XX:MaxPermSize=256m'
+}
\ No newline at end of file
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/BlockingRequestObserver.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/BlockingRequestObserver.java
index 8f9b051..547674a 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/BlockingRequestObserver.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/BlockingRequestObserver.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.integtests.openapi;
 
+import org.gradle.internal.UncheckedException;
 import org.gradle.openapi.external.foundation.RequestObserverVersion1;
 import org.gradle.openapi.external.foundation.RequestVersion1;
-import org.gradle.util.UncheckedException;
 
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
@@ -128,7 +128,7 @@ public class BlockingRequestObserver implements RequestObserverVersion1 {
                 throw failure;
             }
         } catch(Throwable t) {
-            throw UncheckedException.asUncheckedException(t);
+            throw UncheckedException.throwAsUncheckedException(t);
         } finally {
             lock.unlock();
         }
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
index 4785598..7af2f2a 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
@@ -15,65 +15,51 @@
  */
 package org.gradle.integtests.openapi
 
-import org.gradle.api.internal.AbstractClassPathProvider
 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.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.util.ClasspathUtil
+import org.gradle.util.DefaultClassLoaderFactory
+import org.gradle.util.TestPrecondition
+import org.gradle.util.Requires
 import org.junit.Assert
 import org.junit.Rule
-import org.junit.Test
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
-class CrossVersionCompatibilityIntegrationTest {
+ at Requires(TestPrecondition.SWING)
+class CrossVersionCompatibilityIntegrationTest extends CrossVersionIntegrationSpec {
     private final Logger logger = LoggerFactory.getLogger(CrossVersionCompatibilityIntegrationTest)
-    @Rule public final GradleDistribution dist = new GradleDistribution()
     @Rule public final TestResources resources = new TestResources()
 
-    private final BasicGradleDistribution gradle09rc3 = dist.previousVersion('0.9-rc-3')
-    private final BasicGradleDistribution gradle09 = dist.previousVersion('0.9')
-    private final BasicGradleDistribution gradle091 = dist.previousVersion('0.9.1')
-    private final BasicGradleDistribution gradle092 = dist.previousVersion('0.9.2')
-    private final BasicGradleDistribution gradle10Milestone1 = dist.previousVersion('1.0-milestone-1')
-    private final BasicGradleDistribution gradle10Milestone2 = dist.previousVersion('1.0-milestone-2')
-
-    @Test
     public void canUseOpenApiFromCurrentVersionToBuildUsingAnOlderVersion() {
-        [gradle09rc3, gradle09, gradle091, gradle092, gradle10Milestone1, gradle10Milestone2].each {
-            checkCanBuildUsing(dist, it)
-        }
+        expect:
+        checkCanBuildUsing(current, previous)
     }
 
-    @Test
     public void canUseOpenApiFromOlderVersionToBuildUsingCurrentVersion() {
-        [gradle09rc3, gradle09, gradle091, gradle092, gradle10Milestone1, gradle10Milestone2].each {
-            checkCanBuildUsing(it, dist)
-        }
+        expect:
+        checkCanBuildUsing(previous, current)
     }
 
-    def checkCanBuildUsing(BasicGradleDistribution openApiVersion, BasicGradleDistribution buildVersion) {
-        try {
-            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/ }
-            logger.info('Using Open API classpath {}', classpath)
-            def classloader = new URLClassLoader(classpath.collect { it.toURI().toURL() } as URL[], ClassLoader.systemClassLoader.parent)
-            def builder = classloader.loadClass(CrossVersionBuilder.class.name).newInstance()
-            builder.targetGradleHomeDir = buildVersion.gradleHomeDir
-            builder.currentDir = dist.testDir
-            builder.version = buildVersion.version
-            builder.build()
-        } catch (Throwable throwable) {
-            throw new RuntimeException("Failed to build using $buildVersion via the open API of $openApiVersion", throwable)
+    void checkCanBuildUsing(BasicGradleDistribution openApiVersion, BasicGradleDistribution buildVersion) {
+        if (!buildVersion.openApiSupported) {
+            System.out.println("skipping $buildVersion as it does not support the open API.")
+            return
+        }
+        if (!openApiVersion.openApiSupported) {
+            System.out.println("skipping $openApiVersion as it does not support the open API.")
+            return
         }
+        def testClasses = ClasspathUtil.getClasspathForClass(CrossVersionBuilder.class)
+        def junitJar = ClasspathUtil.getClasspathForClass(Assert.class)
+        def classpath = [testClasses, junitJar] + openApiVersion.gradleHomeDir.file('lib').listFiles().findAll { it.name =~ /gradle-open-api.*\.jar/ }
+        logger.info('Using Open API classpath {}', classpath)
+        def classloader = new DefaultClassLoaderFactory().createIsolatedClassLoader(classpath.collect { it.toURI() })
+        def builder = classloader.loadClass(CrossVersionBuilder.class.name).newInstance()
+        builder.targetGradleHomeDir = buildVersion.gradleHomeDir
+        builder.currentDir = testDir
+        builder.version = buildVersion.version
+        builder.build()
     }
 }
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/ExtraTestCommandLineOptionsListener.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/ExtraTestCommandLineOptionsListener.java
new file mode 100644
index 0000000..1f086df
--- /dev/null
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/ExtraTestCommandLineOptionsListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.openapi;
+
+import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
+
+import java.io.File;
+
+public class ExtraTestCommandLineOptionsListener implements CommandLineArgumentAlteringListenerVersion1 {
+    private final File gradleUserHomeDir;
+
+    public ExtraTestCommandLineOptionsListener(File gradleUserHomeDir) {
+        this.gradleUserHomeDir = gradleUserHomeDir;
+    }
+
+    public String getAdditionalCommandLineArguments(String commandLineArguments) {
+        return String.format("--no-search-upward --gradle-user-home \'%s\'", gradleUserHomeDir);
+    }
+}
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
index 416b039..b0a45b6 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
@@ -25,6 +25,7 @@ import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.apache.commons.lang.builder.ReflectionToStringBuilder
 
 class GradleRunnerTest {
 
@@ -88,32 +89,32 @@ class GradleRunnerTest {
     }
 
     if( totalWaitTime > maximumWaitTime ) {
-      throw new AssertionFailedError( "Waited " + totalWaitTime + " seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\nCurrent project directory: '" + interaction.getWorkingDirectory() + "'\nOutput:\n" + interaction.output.toString() )
+      throw new AssertionFailedError( "Waited $totalWaitTime seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\n. Interaction: $interaction" )
     }
 
     //now make sure we were notified of things correctly:
 
     //it should have fired a message that execution has started
-    Assert.assertTrue( "Execution did not report started", interaction.executionStarted )
+    Assert.assertTrue( "Execution did not report started. Interaction: $interaction", interaction.executionStarted )
     
     //it should have finished
-    Assert.assertTrue( "Execution did not report finished", interaction.executionFinished )
+    Assert.assertTrue( "Execution did not report finished. Interaction: $interaction", interaction.executionFinished )
 
     //it should have been successful
-    Assert.assertTrue( "Did not execute command successfully", interaction.wasSuccessful )
+    Assert.assertTrue( "Did not execute command successfully. Interaction: $interaction", interaction.wasSuccessful )
 
     //we should have output
-    Assert.assertTrue( "Missing output", interaction.output.length() > 0 )
+    Assert.assertTrue( "Missing output. Interaction: $interaction", interaction.output.length() > 0 )
 
     //we should have a message when we finished (basically the full output)
-    Assert.assertTrue( "Missing finish message", interaction.finishMessage != null )
+    Assert.assertTrue( "Missing finish message. Interaction: $interaction", interaction.finishMessage != null )
 
     //there should have been multiple tasks to execute
-    Assert.assertTrue( "Not enough tasks executed. Expected multiple. Found " + interaction.numberOfTasksToExecute, interaction.numberOfTasksToExecute > 1 )
+    Assert.assertTrue( "Not enough tasks executed. Expected multiple. Found $interaction.numberOfTasksToExecute. Interaction: $interaction", interaction.numberOfTasksToExecute > 1 )
 
     //we should have been notified that tasks started and completed (we're not interested in tracking how many times or specific tasks as that might change too often with releases of gradle.
-    Assert.assertTrue( "No tasks reported started", interaction.taskStarted )
-    Assert.assertTrue( "No tasks reported completed", interaction.taskCompleted )
+    Assert.assertTrue( "No tasks reported started. Interaction: $interaction", interaction.taskStarted )
+    Assert.assertTrue( "No tasks reported completed. Interaction: $interaction", interaction.taskCompleted )
   }
 
   /**
@@ -149,22 +150,22 @@ class GradleRunnerTest {
     }
 
     if( totalWaitTime > maximumWaitTime ) {
-      throw new AssertionFailedError( "Waited " + totalWaitTime + " seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\nCurrent project directory: '" + interaction.getWorkingDirectory() + "'\nOutput:\n" + interaction.output.toString() )
+      throw new AssertionFailedError( "Waited $totalWaitTime seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\nInteraction: $interaction")
     }
 
     //make sure we tried to kill the task
-    Assert.assertTrue( "Did not attempt to kill execution", interaction.killedTask )
+    Assert.assertTrue( "Did not attempt to kill execution. Interaction: $interaction", interaction.killedTask )
 
     //now make sure we were notified of things correctly:
 
     //it should NOT have been successful
-    Assert.assertFalse( "Erroneously executed successfully (was not killed)", interaction.wasSuccessful )
+    Assert.assertFalse( "Erroneously executed successfully (was not killed). Interaction: $interaction", interaction.wasSuccessful )
 
     //it should have fired a message that execution has started
-    Assert.assertTrue( "Execution did not report started", interaction.executionStarted )
+    Assert.assertTrue( "Execution did not report started. Interaction: $interaction", interaction.executionStarted )
 
     //it should have finished
-    Assert.assertTrue( "Execution did not report finished", interaction.executionFinished )
+    Assert.assertTrue( "Execution did not report finished. Interaction: $interaction", interaction.executionFinished )
   }
 }
 
@@ -193,7 +194,7 @@ class GradleRunnerTest {
 
     GradleRunnerInteractionVersion1.LogLevel getLogLevel() { return GradleRunnerInteractionVersion1.LogLevel.Lifecycle }
 
-    GradleRunnerInteractionVersion1.StackTraceLevel getStackTraceLevel() { return GradleRunnerInteractionVersion1.StackTraceLevel.InternalExceptions }
+    GradleRunnerInteractionVersion1.StackTraceLevel getStackTraceLevel() { return GradleRunnerInteractionVersion1.StackTraceLevel.AlwaysFull }
 
     void reportExecutionStarted() { executionStarted = true }
     void reportNumberOfTasksToExecute(int size) { numberOfTasksToExecute = size }
@@ -211,6 +212,10 @@ class GradleRunnerTest {
     }
 
     File getCustomGradleExecutable() { return null; }
+
+    String toString() {
+      return ReflectionToStringBuilder.toString(this);
+    }
   }
 
 
@@ -256,4 +261,8 @@ class GradleRunnerTest {
       this.executionFinished = true;
       this.wasSuccessful = wasSuccessful
     }
+
+    String toString() {
+      return ReflectionToStringBuilder.toString(this);
+    }
   }
\ No newline at end of file
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
index 239ca5b..c6787b1 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
@@ -17,11 +17,10 @@ package org.gradle.integtests.openapi;
 
 import org.gradle.integtests.fixtures.GradleDistribution;
 import org.gradle.integtests.fixtures.RuleHelper;
-import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
+import org.gradle.internal.UncheckedException;
 import org.gradle.openapi.external.ui.DualPaneUIVersion1;
 import org.gradle.openapi.external.ui.SinglePaneUIVersion1;
 import org.gradle.openapi.external.ui.UIFactory;
-import org.gradle.util.UncheckedException;
 import org.junit.Assert;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
@@ -29,7 +28,6 @@ import org.junit.runners.model.Statement;
 
 import javax.swing.*;
 import java.awt.*;
-import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -63,7 +61,7 @@ public class OpenApiFixture implements MethodRule {
         try {
             singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         //make sure we got something
@@ -81,7 +79,7 @@ public class OpenApiFixture implements MethodRule {
         try {
             dualPane = UIFactory.createDualPaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         //make sure we got something
@@ -122,7 +120,7 @@ public class OpenApiFixture implements MethodRule {
                 }
             });
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
     }
 
@@ -149,15 +147,4 @@ public class OpenApiFixture implements MethodRule {
         return frame;
     }
 
-    private static class ExtraTestCommandLineOptionsListener implements CommandLineArgumentAlteringListenerVersion1 {
-        private final File gradleUserHomeDir;
-
-        public ExtraTestCommandLineOptionsListener(File gradleUserHomeDir) {
-            this.gradleUserHomeDir = gradleUserHomeDir;
-        }
-
-        public String getAdditionalCommandLineArguments(String commandLineArguments) {
-            return String.format("--no-search-upward --gradle-user-home \'%s\'", gradleUserHomeDir);
-        }
-    }
 }
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
index 629f056..e3a8160 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
@@ -21,7 +21,7 @@ import java.awt.event.HierarchyListener
 import java.util.concurrent.TimeUnit
 import javax.swing.JFrame
 import javax.swing.JLabel
-import junit.framework.AssertionFailedError
+
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.openapi.external.ExternalUtility
@@ -31,21 +31,29 @@ import org.gradle.openapi.external.foundation.RequestVersion1
 import org.gradle.openapi.external.foundation.TaskVersion1
 import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1
 import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1
-import org.gradle.util.OperatingSystem
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.openapi.external.ui.*
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.util.PreconditionVerifier
+
 import org.junit.Assert
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.openapi.external.ui.*
+import org.junit.ClassRule
+
 import static org.hamcrest.Matchers.*
 
 /**
  * This tests numerous aspects of the Open API UI. This is how the Idea plugin extracts the UI from
  * Gradle.
  */
-public class OpenApiUiTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final TestResources resources = new TestResources('testproject')
-    @Rule public final OpenApiFixture openApi = new OpenApiFixture()
+ at Requires(TestPrecondition.SWING)
+class OpenApiUiTest {
+    @Rule public GradleDistribution dist = new GradleDistribution()
+    @Rule public TestResources resources = new TestResources('testproject')
+    @Rule public OpenApiFixture openApi = new OpenApiFixture()
+    @ClassRule public static PreconditionVerifier verifier = new PreconditionVerifier()
 
     /**
      This tests to see if we can call the UIFactory to create a single pane UI.
@@ -54,7 +62,7 @@ public class OpenApiUiTest {
      to make sure the basics are working.
      */
     @Test
-    public void testSinglePaneBasic() {
+    void testSinglePaneBasic() {
         SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
 
         //make sure we got something
@@ -67,551 +75,538 @@ public class OpenApiUiTest {
         Assert.assertNotNull(singlePane.getComponent())
     }
 
-  /**
-   * This tests to see if we can call the UIFactory to create a dual pane UI.
-      This is only testing that extracting the UI returns something without giving
-      errors and that it has its dual components. This is just a good general-case test
-      to make sure the basics are working.
-   */
+    /**
+     * This tests to see if we can call the UIFactory to create a dual pane UI.
+     This is only testing that extracting the UI returns something without giving
+     errors and that it has its dual components. This is just a good general-case test
+     to make sure the basics are working.
+     */
     @Test
-    public void testDualPaneBasic()
-    {
+    void testDualPaneBasic() {
         DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
 
         //make sure we got something
-        Assert.assertNotNull( dualPane )
+        Assert.assertNotNull(dualPane)
 
         //tell it we're about to show it, so it'll create a component
         dualPane.aboutToShow();
 
         //make sure we now have the main component
-        Assert.assertNotNull( dualPane.getMainComponent() )
+        Assert.assertNotNull(dualPane.getMainComponent())
 
         //and the output component
-        Assert.assertNotNull( dualPane.getOutputPanel() )
+        Assert.assertNotNull(dualPane.getOutputPanel())
     }
 
     /**
-    * This verifies that favorites are working for some basics. We're going to test this with both
+     * This verifies that favorites are working for some basics. We're going to test this with both
      * the single and dual pane UIs (they actually use the same editor then for other tests we'll
      * assume they're same).
-    */
+     */
     @Test
-    public void testFavoritesBasic()
-    {
-      SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-      checkFavoritesBasic( singlePane )
+    void testFavoritesBasic() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        checkFavoritesBasic(singlePane)
 
-      DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
-      checkFavoritesBasic( dualPane )
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+        checkFavoritesBasic(dualPane)
     }
 
     /**
-    * This verifies that we favorites are basically working based on the given UI. We're going to add one, then
-    * do some 'gets' to find the just-added favorite.
-    */
-    private void checkFavoritesBasic( BasicGradleUIVersion1 basicGradleUI )
-    {
-      FavoritesEditorVersion1 editor = basicGradleUI.getFavoritesEditor()
+     * This verifies that we favorites are basically working based on the given UI. We're going to add one, then
+     * do some 'gets' to find the just-added favorite.
+     */
+    private void checkFavoritesBasic(BasicGradleUIVersion1 basicGradleUI) {
+        FavoritesEditorVersion1 editor = basicGradleUI.getFavoritesEditor()
 
-      //there should be no favorites as of yet
-      Assert.assertTrue( editor.getFavoriteTasks().isEmpty() )
+        //there should be no favorites as of yet
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty())
 
-      //add one (doesn't really matter what it is)
-      def fullCommandLine = "-t -S"
-      def displayName = "Task List With Stack trace"
-      FavoriteTaskVersion1 favorite = editor.addFavorite( fullCommandLine, displayName, true )
+        //add one (doesn't really matter what it is)
+        def fullCommandLine = "-t -S"
+        def displayName = "Task List With Stack trace"
+        FavoriteTaskVersion1 favorite = editor.addFavorite(fullCommandLine, displayName, true)
 
-      //make sure something was added
-      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+        //make sure something was added
+        Assert.assertEquals(1, editor.getFavoriteTasks().size())
 
-      //get the newly-added favorite by command line.
-      FavoriteTaskVersion1 matchingFavorite1 = editor.getFavorite( fullCommandLine )
-      Assert.assertEquals( favorite, matchingFavorite1 )
+        //get the newly-added favorite by command line.
+        FavoriteTaskVersion1 matchingFavorite1 = editor.getFavorite(fullCommandLine)
+        Assert.assertEquals(favorite, matchingFavorite1)
 
-      //get the newly-added favorite by displayName.
-      FavoriteTaskVersion1 matchingFavorite2 = editor.getFavoriteByDisplayName( displayName )
-      Assert.assertEquals( favorite, matchingFavorite2 )
+        //get the newly-added favorite by displayName.
+        FavoriteTaskVersion1 matchingFavorite2 = editor.getFavoriteByDisplayName(displayName)
+        Assert.assertEquals(favorite, matchingFavorite2)
 
-      Assert.assertTrue( matchingFavorite2.alwaysShowOutput() )
+        Assert.assertTrue(matchingFavorite2.alwaysShowOutput())
     }
 
     /**
-    * This verifies that we can edit favorites. We're going to add a favorite then edit its
+     * This verifies that we can edit favorites. We're going to add a favorite then edit its
      * command line, display name, and 'show output' setting.
-    */
+     */
     @Test
-    public void testEditingFavorites()
-    {
-      SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-      FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
-
-      def originalFullCommandLine = "-t -S"
-      def originalDisplayName = "Task List With Stack trace"
-      FavoriteTaskVersion1 addedFavorite = editor.addFavorite(originalFullCommandLine, originalDisplayName, true)
-
-      //make sure we can find the just-added favorite
-      Assert.assertNotNull( editor.getFavorite( originalFullCommandLine ) )
-      Assert.assertNotNull( editor.getFavoriteByDisplayName( originalDisplayName ) )
-
-      String newFullCommandLine = "-t -S -d"
-      String newDisplayName = "new task list"
-      String error = editor.editFavorite( addedFavorite, newFullCommandLine, newDisplayName, false )
-      Assert.assertNull( error )  //we should get no error
-
-      //now we shouldn't be able to find the favorite using the original values. This is part of verifying the values were in fact changed.
-      Assert.assertNull( editor.getFavorite( originalFullCommandLine ) )
-      Assert.assertNull( editor.getFavoriteByDisplayName( originalDisplayName ) )
-
-      //make sure we can find it using the new values. This is part of verifying the values were in fact changed.
-      Assert.assertNotNull( editor.getFavorite( newFullCommandLine ) )
-      Assert.assertNotNull( editor.getFavoriteByDisplayName( newDisplayName ) )
-
-      //there should just be 1 favorite
-      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+    void testEditingFavorites() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
+
+        def originalFullCommandLine = "-t -S"
+        def originalDisplayName = "Task List With Stack trace"
+        FavoriteTaskVersion1 addedFavorite = editor.addFavorite(originalFullCommandLine, originalDisplayName, true)
+
+        //make sure we can find the just-added favorite
+        Assert.assertNotNull(editor.getFavorite(originalFullCommandLine))
+        Assert.assertNotNull(editor.getFavoriteByDisplayName(originalDisplayName))
+
+        String newFullCommandLine = "-t -S -d"
+        String newDisplayName = "new task list"
+        String error = editor.editFavorite(addedFavorite, newFullCommandLine, newDisplayName, false)
+        Assert.assertNull(error)  //we should get no error
+
+        //now we shouldn't be able to find the favorite using the original values. This is part of verifying the values were in fact changed.
+        Assert.assertNull(editor.getFavorite(originalFullCommandLine))
+        Assert.assertNull(editor.getFavoriteByDisplayName(originalDisplayName))
+
+        //make sure we can find it using the new values. This is part of verifying the values were in fact changed.
+        Assert.assertNotNull(editor.getFavorite(newFullCommandLine))
+        Assert.assertNotNull(editor.getFavoriteByDisplayName(newDisplayName))
+
+        //there should just be 1 favorite
+        Assert.assertEquals(1, editor.getFavoriteTasks().size())
     }
 
     /**
-    * This verifies that we can remove favorites. We're going to add some favorites then remove them
+     * This verifies that we can remove favorites. We're going to add some favorites then remove them
      * verifying that they've gone.
-    */
+     */
     @Test
-    public void testRemovingFavorites()
-    {
-      SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-      FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
+    void testRemovingFavorites() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
 
-      //there should be no favorites as of yet
-      Assert.assertTrue( editor.getFavoriteTasks().isEmpty() )
+        //there should be no favorites as of yet
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty())
 
-      //add one (doesn't really matter what it is)
-      String command1 = "build"
-      FavoriteTaskVersion1 favorite1 = editor.addFavorite( command1, command1, true )
+        //add one (doesn't really matter what it is)
+        String command1 = "build"
+        FavoriteTaskVersion1 favorite1 = editor.addFavorite(command1, command1, true)
 
-      //make sure it was added
-      Assert.assertNotNull( editor.getFavorite( command1 ) )
-      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+        //make sure it was added
+        Assert.assertNotNull(editor.getFavorite(command1))
+        Assert.assertEquals(1, editor.getFavoriteTasks().size())
 
-      //add another one
-      String command2 = "build -xtest"
-      FavoriteTaskVersion1 favorite2 = editor.addFavorite( command2, command2, true )
+        //add another one
+        String command2 = "build -xtest"
+        FavoriteTaskVersion1 favorite2 = editor.addFavorite(command2, command2, true)
 
-      //make sure it was added
-      Assert.assertNotNull( editor.getFavorite( command2 ) )
-      Assert.assertEquals( 2, editor.getFavoriteTasks().size() )
+        //make sure it was added
+        Assert.assertNotNull(editor.getFavorite(command2))
+        Assert.assertEquals(2, editor.getFavoriteTasks().size())
 
-      String command3 = "clean"
-      FavoriteTaskVersion1 favorite3 = editor.addFavorite( command3, command3, true )
+        String command3 = "clean"
+        FavoriteTaskVersion1 favorite3 = editor.addFavorite(command3, command3, true)
 
-      //make sure it was added
-      Assert.assertNotNull( editor.getFavorite( command3 ) )
-      Assert.assertEquals( 3, editor.getFavoriteTasks().size() )
+        //make sure it was added
+        Assert.assertNotNull(editor.getFavorite(command3))
+        Assert.assertEquals(3, editor.getFavoriteTasks().size())
 
-      String command4 = "docs"
-      FavoriteTaskVersion1 favorite4 = editor.addFavorite( command4, command4, true )
+        String command4 = "docs"
+        FavoriteTaskVersion1 favorite4 = editor.addFavorite(command4, command4, true)
 
-      //make sure it was added
-      Assert.assertNotNull( editor.getFavorite( command4 ) )
-      Assert.assertEquals( 4, editor.getFavoriteTasks().size() )
+        //make sure it was added
+        Assert.assertNotNull(editor.getFavorite(command4))
+        Assert.assertEquals(4, editor.getFavoriteTasks().size())
 
-      //now remove one of them
-      List removed1 = [ favorite2 ]
-      editor.removeFavorites( removed1 )
+        //now remove one of them
+        List removed1 = [favorite2]
+        editor.removeFavorites(removed1)
 
-      //make sure it was removed
-      Assert.assertNull( editor.getFavorite( command2 ) )
-      Assert.assertEquals( 3, editor.getFavoriteTasks().size() )
+        //make sure it was removed
+        Assert.assertNull(editor.getFavorite(command2))
+        Assert.assertEquals(3, editor.getFavoriteTasks().size())
 
-      //now remove multiples
-      List removed2 = [ favorite1, favorite4 ]
-      editor.removeFavorites( removed2 )
+        //now remove multiples
+        List removed2 = [favorite1, favorite4]
+        editor.removeFavorites(removed2)
 
-      //make sure they were both removed
-      Assert.assertNull( editor.getFavorite( command1 ) )
-      Assert.assertNull( editor.getFavorite( command4 ) )
-      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+        //make sure they were both removed
+        Assert.assertNull(editor.getFavorite(command1))
+        Assert.assertNull(editor.getFavorite(command4))
+        Assert.assertEquals(1, editor.getFavoriteTasks().size())
     }
 
     /**
-   * This tests executing multiple favorites at once. We'll add two favorites, then execute both of them
-   * via GradleInterfaceVersion1.executeFavorites(). This should execute both as a single command
-   * concatenating them together.
-   */
+     * This tests executing multiple favorites at once. We'll add two favorites, then execute both of them
+     * via GradleInterfaceVersion1.executeFavorites(). This should execute both as a single command
+     * concatenating them together.
+     */
     @Test
-    public void testExecutingFavorites()
-    {
-      SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-      FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
+    void testExecutingFavorites() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
 
-      //this starts the execution queue
-      singlePane.aboutToShow()
+        //this starts the execution queue
+        singlePane.aboutToShow()
 
-      //there should be no favorites as of yet
-      Assert.assertTrue( editor.getFavoriteTasks().isEmpty() )
+        //there should be no favorites as of yet
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty())
 
-      //add one (doesn't really matter what it is)
-      String command1 = "build"
-      FavoriteTaskVersion1 favorite1 = editor.addFavorite( command1, command1, true )
+        //add one (doesn't really matter what it is)
+        String command1 = "build"
+        FavoriteTaskVersion1 favorite1 = editor.addFavorite(command1, command1, true)
 
-      //make sure it was added
-      Assert.assertNotNull( editor.getFavorite( command1 ) )
-      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+        //make sure it was added
+        Assert.assertNotNull(editor.getFavorite(command1))
+        Assert.assertEquals(1, editor.getFavoriteTasks().size())
 
-      //add another one
-      String command2 = "clean"
-      FavoriteTaskVersion1 favorite2 = editor.addFavorite( command2, command2, true )
+        //add another one
+        String command2 = "clean"
+        FavoriteTaskVersion1 favorite2 = editor.addFavorite(command2, command2, true)
 
-      //make sure it was added
-      Assert.assertNotNull( editor.getFavorite( command2 ) )
-      Assert.assertEquals( 2, editor.getFavoriteTasks().size() )
+        //make sure it was added
+        Assert.assertNotNull(editor.getFavorite(command2))
+        Assert.assertEquals(2, editor.getFavoriteTasks().size())
 
-      //add a request observer so we can observe when the command is finished. This allows us to
-      //see what was actually executed.
-      BlockingRequestObserver testRequestObserver = new BlockingRequestObserver( RequestVersion1.EXECUTION_TYPE )
-      ((GradleInterfaceVersion2)singlePane.getGradleInterfaceVersion1()).addRequestObserver( testRequestObserver )
+        //add a request observer so we can observe when the command is finished. This allows us to
+        //see what was actually executed.
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.EXECUTION_TYPE)
+        ((GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).addRequestObserver(testRequestObserver)
 
-      //now execute both favorites
-      List<FavoriteTaskVersion1> favorites = [ favorite1, favorite2 ]
-      RequestVersion1 request = ( (GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1() ).executeFavorites(favorites)
+        //now execute both favorites
+        List<FavoriteTaskVersion1> favorites = [favorite1, favorite2]
+        RequestVersion1 request = ((GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).executeFavorites(favorites)
 
-      Assert.assertNotNull( request )
+        Assert.assertNotNull(request)
 
-      //verify that the actual command that was executed is a concatenation of both favorites
-      Assert.assertThat( request.getFullCommandLine(), startsWith("build clean") )
+        //verify that the actual command that was executed is a concatenation of both favorites
+        Assert.assertThat(request.getFullCommandLine(), startsWith("build clean"))
     }
 
     /**
-    * This tests getting projects and tasks from gradle. It then goes through a series of tests
-    * related to projects and tasks (such as making sure their description is returned, their
-    * parent is returned, etc).
-    */
+     * This tests getting projects and tasks from gradle. It then goes through a series of tests
+     * related to projects and tasks (such as making sure their description is returned, their
+     * parent is returned, etc).
+     */
     @Test
-    public void testProjectsAndTasks()
-    {
-      DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
-      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+    void testProjectsAndTasks() {
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+        GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
 
-      //make sure our samples directory exists
-      Assert.assertTrue( gradleInterface.getCurrentDirectory().isDirectory() )
+        //make sure our samples directory exists
+        Assert.assertTrue(gradleInterface.getCurrentDirectory().isDirectory())
 
-      //add a request observer so we can observe when the command is finished. This allows us to
-      //see what was actually executed.
-      BlockingRequestObserver testRequestObserver = new BlockingRequestObserver( RequestVersion1.REFRESH_TYPE )
-      gradleInterface.addRequestObserver( testRequestObserver )
+        //add a request observer so we can observe when the command is finished. This allows us to
+        //see what was actually executed.
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.REFRESH_TYPE)
+        gradleInterface.addRequestObserver(testRequestObserver)
 
-      //this starts the execution queue
-      dualPane.aboutToShow()
+        //this starts the execution queue
+        dualPane.aboutToShow()
 
-      gradleInterface.refreshTaskTree()
+        gradleInterface.refreshTaskTree()
 
-      testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
 
-      Assert.assertEquals( "Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
+        Assert.assertEquals("Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
 
-      List<ProjectVersion1> rootProjects = gradleInterface.getRootProjects();
-      Assert.assertFalse( rootProjects.isEmpty() );   //do we have any root projects?
+        List<ProjectVersion1> rootProjects = gradleInterface.getRootProjects();
+        Assert.assertFalse(rootProjects.isEmpty());   //do we have any root projects?
 
-      ProjectVersion1 rootProject = rootProjects.get( 0 );
-      Assert.assertNotNull( rootProject );
-      Assert.assertThat( rootProject.getSubProjects().size(), equalTo(3))
+        ProjectVersion1 rootProject = rootProjects.get(0);
+        Assert.assertNotNull(rootProject);
+        Assert.assertThat(rootProject.getSubProjects().size(), equalTo(3))
 
-      //Quick check to make sure there are tasks on each of the sub projects.
-      //The exact task names will change over time, so I don't want to try
-      //to test for those. I'll just make sure there are several.
-      Iterator<ProjectVersion1> iterator = rootProjects.get(0).getSubProjects().iterator();
-      while( iterator.hasNext() )
-      {
-         ProjectVersion1 projectVersion1 = iterator.next();
-         Assert.assertTrue( projectVersion1.getTasks().size() > 4 );
-      }
+        //Quick check to make sure there are tasks on each of the sub projects.
+        //The exact task names will change over time, so I don't want to try
+        //to test for those. I'll just make sure there are several.
+        Iterator<ProjectVersion1> iterator = rootProjects.get(0).getSubProjects().iterator();
+        while (iterator.hasNext()) {
+            ProjectVersion1 projectVersion1 = iterator.next();
+            Assert.assertTrue(projectVersion1.getTasks().size() > 4);
+        }
 
-      //there should be a 'services' project
-      ProjectVersion1 servicesProject = rootProjects.get(0).getSubProject("services" );
-      Assert.assertNotNull( servicesProject );
+        //there should be a 'services' project
+        ProjectVersion1 servicesProject = rootProjects.get(0).getSubProject("services");
+        Assert.assertNotNull(servicesProject);
 
-      //and it contains a 'webservice' sub project
-      ProjectVersion1 webserviceProject = servicesProject.getSubProject("webservice");
-      Assert.assertNotNull( webserviceProject );
+        //and it contains a 'webservice' sub project
+        ProjectVersion1 webserviceProject = servicesProject.getSubProject("webservice");
+        Assert.assertNotNull(webserviceProject);
 
-      ProjectVersion1 apiProject = rootProjects.get(0).getSubProject("api");
-      Assert.assertNotNull( apiProject );
+        ProjectVersion1 apiProject = rootProjects.get(0).getSubProject("api");
+        Assert.assertNotNull(apiProject);
 
-      //verify the parent project is set correctly
-      Assert.assertEquals( servicesProject, webserviceProject.getParentProject() )
+        //verify the parent project is set correctly
+        Assert.assertEquals(servicesProject, webserviceProject.getParentProject())
 
-      //verify its full name is correct (this might should be prefixed with a colon)
-      Assert.assertEquals( "services:webservice", webserviceProject.getFullProjectName() )
+        //verify its full name is correct (this might should be prefixed with a colon)
+        Assert.assertEquals("services:webservice", webserviceProject.getFullProjectName())
 
-      //verify getSubProjectFromFullPath works
-      ProjectVersion1 foundProject = rootProject.getSubProjectFromFullPath("services:webservice")
-      Assert.assertNotNull( "Failed to find services:webservice", foundProject )
-      Assert.assertEquals( webserviceProject, foundProject )
+        //verify getSubProjectFromFullPath works
+        ProjectVersion1 foundProject = rootProject.getSubProjectFromFullPath("services:webservice")
+        Assert.assertNotNull("Failed to find services:webservice", foundProject)
+        Assert.assertEquals(webserviceProject, foundProject)
 
-      //verify that are multiple tasks here (we know their should be)
-      Assert.assertTrue( webserviceProject.getTasks().size() > 4 );
+        //verify that are multiple tasks here (we know their should be)
+        Assert.assertTrue(webserviceProject.getTasks().size() > 4);
 
-      //verify getTaskFromFullPath works
-      TaskVersion1 apiBuildTask = rootProject.getTaskFromFullPath(":api:build")
-      Assert.assertNotNull( "Failed to find :api:build", apiBuildTask )
+        //verify getTaskFromFullPath works
+        TaskVersion1 apiBuildTask = rootProject.getTaskFromFullPath(":api:build")
+        Assert.assertNotNull("Failed to find :api:build", apiBuildTask)
 
-      Assert.assertEquals( apiProject, apiBuildTask.getProject() )
+        Assert.assertEquals(apiProject, apiBuildTask.getProject())
 
-      //then make sure it has a description
-      Assert.assertNotNull( apiBuildTask.getDescription() )
+        //then make sure it has a description
+        Assert.assertNotNull(apiBuildTask.getDescription())
 
-      //and that its not marked as the default (we need a task to be the default so we can verify it returns true)
-      Assert.assertFalse( apiBuildTask.isDefault() )
+        //and that its not marked as the default (we need a task to be the default so we can verify it returns true)
+        Assert.assertFalse(apiBuildTask.isDefault())
 
-      //there are no default tasks here
-      Assert.assertTrue( apiProject.getDefaultTasks().isEmpty() )
+        //there are no default tasks here
+        Assert.assertTrue(apiProject.getDefaultTasks().isEmpty())
 
-      //this build task is a child of the api project. Should be the same task we got earlier
-      TaskVersion1 buildTask = apiProject.getTask("build")
-      Assert.assertNotNull( "Failed to find build task", buildTask )
-      Assert.assertEquals( apiBuildTask, buildTask )
+        //this build task is a child of the api project. Should be the same task we got earlier
+        TaskVersion1 buildTask = apiProject.getTask("build")
+        Assert.assertNotNull("Failed to find build task", buildTask)
+        Assert.assertEquals(apiBuildTask, buildTask)
     }
 
-   /**
-    * This verifies that the GradleInterfaceVersion1.refreshTaskTree that takes
-    * additional arguments works. We're not really interested in what those additional
-    * arguments are, just that it passes them along.
-    */
+    /**
+     * This verifies that the GradleInterfaceVersion1.refreshTaskTree that takes
+     * additional arguments works. We're not really interested in what those additional
+     * arguments are, just that it passes them along.
+     */
     @Test
-    public void testRefreshWithArguments()
-    {
-      DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
-      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+    void testRefreshWithArguments() {
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+        GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
 
-      //make sure our samples directory exists
-      if( !gradleInterface.getCurrentDirectory().exists() ) {
-        throw new AssertionFailedError('sample project missing. Expected it at: ' + gradleInterface.getCurrentDirectory())
-      }
+        //make sure our samples directory exists
+        if (!gradleInterface.getCurrentDirectory().exists()) {
+            throw new AssertionError('sample project missing. Expected it at: ' + gradleInterface.getCurrentDirectory())
+        }
 
-      //this starts the execution queue
-      dualPane.aboutToShow()
+        //this starts the execution queue
+        dualPane.aboutToShow()
 
-      //add a request observer so we can observe when the command is finished. This allows us to
-      //see what was actually executed.
-      BlockingRequestObserver testRequestObserver = new BlockingRequestObserver( RequestVersion1.REFRESH_TYPE )
-      gradleInterface.addRequestObserver( testRequestObserver )
+        //add a request observer so we can observe when the command is finished. This allows us to
+        //see what was actually executed.
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.REFRESH_TYPE)
+        gradleInterface.addRequestObserver(testRequestObserver)
 
-      RequestVersion1 request = gradleInterface.refreshTaskTree2("-xtest")
+        RequestVersion1 request = gradleInterface.refreshTaskTree2("-xtest")
 
-      //make sure that the actual request is the normal refresh request with our
-      //(this line is really what we're trying to test)
-      Assert.assertThat( request.getFullCommandLine(), startsWith("tasks -xtest") )
+        //make sure that the actual request is the normal refresh request with our
+        //(this line is really what we're trying to test)
+        Assert.assertThat(request.getFullCommandLine(), startsWith("tasks -xtest"))
 
-      testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
 
-      Assert.assertEquals( "Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
+        Assert.assertEquals("Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
 
-      Assert.assertEquals( "Not our request", request, testRequestObserver.request );
+        Assert.assertEquals("Not our request", request, testRequestObserver.request);
     }
 
     /**
-    * This verifies that you can add custom stuff to the setup tab. This is a UI test and is kinda tricky. We're going
-    * to use a HierarchyListener to see if our component is made visible. This will confirm if it was added or not
-    * because it must be added to be made visible. To do this, however, we'll need to actually show the UI. All we're
-    * really doing here, is adding a 'custom' component to the UI, then adding the UI to a frame, then showing the frame,
-    * so we can verify that our component was shown.
-    */
+     * This verifies that you can add custom stuff to the setup tab. This is a UI test and is kinda tricky. We're going
+     * to use a HierarchyListener to see if our component is made visible. This will confirm if it was added or not
+     * because it must be added to be made visible. To do this, however, we'll need to actually show the UI. All we're
+     * really doing here, is adding a 'custom' component to the UI, then adding the UI to a frame, then showing the frame,
+     * so we can verify that our component was shown.
+     */
     @Test
-    public void testAddingComponentToSetupTab()
-    {
-      if ( java.awt.GraphicsEnvironment.isHeadless() ) {
-        return;  // Can't run this test in headless mode!
-       }
-
-      JLabel label = new JLabel("Testing Testing 123")
-      TestVisibilityHierarchyListener hierarchyAdapter = new TestVisibilityHierarchyListener()
-      label.addHierarchyListener( hierarchyAdapter )
+    void testAddingComponentToSetupTab() {
+        if (java.awt.GraphicsEnvironment.isHeadless()) {
+            return;  // Can't run this test in headless mode!
+        }
 
-      SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        JLabel label = new JLabel("Testing Testing 123")
+        TestVisibilityHierarchyListener hierarchyAdapter = new TestVisibilityHierarchyListener()
+        label.addHierarchyListener(hierarchyAdapter)
 
-      //make sure we haven't been told the component was shown or hidden yet
-      Assert.assertFalse( hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( hierarchyAdapter.componentWasHidden )
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
 
-      singlePane.aboutToShow();
+        //make sure we haven't been told the component was shown or hidden yet
+        Assert.assertFalse(hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(hierarchyAdapter.componentWasHidden)
 
-      singlePane.setCustomPanelToSetupTab( label )
+        singlePane.aboutToShow();
 
-      //this still should not show the component (at this point, we're probably more testing that our hierarchyAdapter is working)
-      Assert.assertFalse( hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( hierarchyAdapter.componentWasHidden )
+        singlePane.setCustomPanelToSetupTab(label)
 
-      //now create a frame, place the UI in it, then show it briefly
-      JFrame frame = openApi.open(singlePane)
+        //this still should not show the component (at this point, we're probably more testing that our hierarchyAdapter is working)
+        Assert.assertFalse(hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(hierarchyAdapter.componentWasHidden)
 
-      //set the Setup tab as the current tab. This is required to actually show the component.
-      int setupTabIndex = singlePane.getGradleTabIndex( "Setup" );
-      Assert.assertTrue( "Failed to get index of setup tab", setupTabIndex != -1 )
-      singlePane.setCurrentGradleTab( setupTabIndex );
+        //now create a frame, place the UI in it, then show it briefly
+        JFrame frame = openApi.open(singlePane)
 
-      //still should not show the component (its not yet visible, but is about to be)
-      Assert.assertFalse( hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( hierarchyAdapter.componentWasHidden )
+        //set the Setup tab as the current tab. This is required to actually show the component.
+        int setupTabIndex = singlePane.getGradleTabIndex("Setup");
+        Assert.assertTrue("Failed to get index of setup tab", setupTabIndex != -1)
+        singlePane.setCurrentGradleTab(setupTabIndex);
 
+        //still should not show the component (its not yet visible, but is about to be)
+        Assert.assertFalse(hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(hierarchyAdapter.componentWasHidden)
 
-      //This shows and hides the UI, giving it time to actually show itself and empty the event dispatch
-      //queue. This is required for the setup tab to become current as well as show the custom component we added.
-      openApi.flushEventQueue( frame )
+        //This shows and hides the UI, giving it time to actually show itself and empty the event dispatch
+        //queue. This is required for the setup tab to become current as well as show the custom component we added.
+        openApi.flushEventQueue(frame)
 
-      Assert.assertEquals( "The setup tab was not selected", setupTabIndex, singlePane.getCurrentGradleTab() )
+        Assert.assertEquals("The setup tab was not selected", setupTabIndex, singlePane.getCurrentGradleTab())
 
-      //now the label should have been made visible then invisible
-      Assert.assertTrue( hierarchyAdapter.componentWasShown )
-      Assert.assertTrue( hierarchyAdapter.componentWasHidden )
+        //now the label should have been made visible then invisible
+        Assert.assertTrue(hierarchyAdapter.componentWasShown)
+        Assert.assertTrue(hierarchyAdapter.componentWasHidden)
     }
 
     /**
-    * This verifies that you can add a custom tab to the UI. This is a UI test and is kinda tricky. We're going
-    * to use a HierarchyListener to see if our tab component is made visible. This will confirm if it was added or not
-    * because it must be added to be made visible. To do this, however, we'll need to actually show the UI. All we're
-    * really doing here, is adding a tab to the UI, then adding the UI to a frame, then showing the frame so we can
-    * then verify that our tab was shown (actually using our tab's component).
-    */
+     * This verifies that you can add a custom tab to the UI. This is a UI test and is kinda tricky. We're going
+     * to use a HierarchyListener to see if our tab component is made visible. This will confirm if it was added or not
+     * because it must be added to be made visible. To do this, however, we'll need to actually show the UI. All we're
+     * really doing here, is adding a tab to the UI, then adding the UI to a frame, then showing the frame so we can
+     * then verify that our tab was shown (actually using our tab's component).
+     */
     @Test
-    public void testAddingCustomTab()
-    {
-      if ( java.awt.GraphicsEnvironment.isHeadless() ) {
-        return;  // Can't run this test in headless mode!
-      }
+    void testAddingCustomTab() {
+        if (java.awt.GraphicsEnvironment.isHeadless()) {
+            return;  // Can't run this test in headless mode!
+        }
 
-      TestTab testTab = new TestTab()
+        TestTab testTab = new TestTab()
 
-      SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
 
-      //make sure we haven't been told the component was shown or hidden yet
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+        //make sure we haven't been told the component was shown or hidden yet
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasHidden)
 
-      //make sure things are initialized properly. These should all be false
-      Assert.assertFalse( testTab.nameRetrieved );
-      Assert.assertFalse( testTab.informedAboutToShow );
-      Assert.assertFalse( testTab.componentCreated );
+        //make sure things are initialized properly. These should all be false
+        Assert.assertFalse(testTab.nameRetrieved);
+        Assert.assertFalse(testTab.informedAboutToShow);
+        Assert.assertFalse(testTab.componentCreated);
 
-      int originalCount = singlePane.getGradleTabCount();
+        int originalCount = singlePane.getGradleTabCount();
 
-      singlePane.addTab( 99, testTab ) //I don't really care about the index. It should accept a number that is too large and handle it appropriately.
+        singlePane.addTab(99, testTab) //I don't really care about the index. It should accept a number that is too large and handle it appropriately.
 
-      singlePane.aboutToShow()
+        singlePane.aboutToShow()
 
-      //this still should not show the component (at this point, we're probably more testing that our hierarchyAdapter is working)
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+        //this still should not show the component (at this point, we're probably more testing that our hierarchyAdapter is working)
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasHidden)
 
-      //now create a frame, place the UI in it, then show it briefly
-      JFrame frame = openApi.open( singlePane )
+        //now create a frame, place the UI in it, then show it briefly
+        JFrame frame = openApi.open(singlePane)
 
-      String testTabName = "Test Tab"
+        String testTabName = "Test Tab"
 
-      //set the test tab as the current tab. This is required to actually show the component.
-      int testTabIndex = singlePane.getGradleTabIndex( testTabName )
-      Assert.assertTrue( "Failed to get index of test tab", testTabIndex != -1 )
-      singlePane.setCurrentGradleTab( testTabIndex )
+        //set the test tab as the current tab. This is required to actually show the component.
+        int testTabIndex = singlePane.getGradleTabIndex(testTabName)
+        Assert.assertTrue("Failed to get index of test tab", testTabIndex != -1)
+        singlePane.setCurrentGradleTab(testTabIndex)
 
-      //just to test getGradleTabName, make sure it returns our tab name
-      Assert.assertEquals( testTabName, singlePane.getGradleTabName( testTabIndex ) )
+        //just to test getGradleTabName, make sure it returns our tab name
+        Assert.assertEquals(testTabName, singlePane.getGradleTabName(testTabIndex))
 
-      //to test getGradleTabCount, make sure the tab count went up by 1
-      Assert.assertEquals( originalCount + 1, singlePane.getGradleTabCount() )
+        //to test getGradleTabCount, make sure the tab count went up by 1
+        Assert.assertEquals(originalCount + 1, singlePane.getGradleTabCount())
 
-      //still should not show the component (its not yet visible, but is about to be)
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+        //still should not show the component (its not yet visible, but is about to be)
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasHidden)
 
-      //This shows and hides the UI, giving it time to actually show itself and empty the event dispatch
-      //queue. This is required for the test tab to become current.
-      openApi.flushEventQueue( frame )
+        //This shows and hides the UI, giving it time to actually show itself and empty the event dispatch
+        //queue. This is required for the test tab to become current.
+        openApi.flushEventQueue(frame)
 
-      Assert.assertEquals( "The test tab was not selected", testTabIndex, singlePane.getCurrentGradleTab() )
+        Assert.assertEquals("The test tab was not selected", testTabIndex, singlePane.getCurrentGradleTab())
 
-      //now the label should have been made visible then invisible
-      Assert.assertTrue( testTab.hierarchyAdapter.componentWasShown )
-      Assert.assertTrue( testTab.hierarchyAdapter.componentWasHidden )
+        //now the label should have been made visible then invisible
+        Assert.assertTrue(testTab.hierarchyAdapter.componentWasShown)
+        Assert.assertTrue(testTab.hierarchyAdapter.componentWasHidden)
 
-      //at the end, the name should have been queried, we should have been told we were about to shown, and the component should be created
-      Assert.assertTrue( testTab.nameRetrieved );
-      Assert.assertTrue( testTab.informedAboutToShow );
-      Assert.assertTrue( testTab.componentCreated );
+        //at the end, the name should have been queried, we should have been told we were about to shown, and the component should be created
+        Assert.assertTrue(testTab.nameRetrieved);
+        Assert.assertTrue(testTab.informedAboutToShow);
+        Assert.assertTrue(testTab.componentCreated);
 
-      //reset the test tab (resets the listener so we can remove the tab and verify that it no longer shows up, as well as some of our test variables)
-      testTab.reset()
-      singlePane.removeTab( testTab )
+        //reset the test tab (resets the listener so we can remove the tab and verify that it no longer shows up, as well as some of our test variables)
+        testTab.reset()
+        singlePane.removeTab(testTab)
 
-      //I'm going to set the current tab, but this shouldn't do anything because the tab was removed
-      singlePane.setCurrentGradleTab( testTabIndex );
+        //I'm going to set the current tab, but this shouldn't do anything because the tab was removed
+        singlePane.setCurrentGradleTab(testTabIndex);
 
-      //part of showing the UI is telling it its about to be shown. In this case, nothing should happen
-      //related to the test tab. It has been removed
-      singlePane.aboutToShow()
+        //part of showing the UI is telling it its about to be shown. In this case, nothing should happen
+        //related to the test tab. It has been removed
+        singlePane.aboutToShow()
 
-      //This shows and hides the UI, giving it time to actually show itself and empty the event
-      //dispatch queue. This is required for the test tab to become current (were it still present).
-      openApi.flushEventQueue( frame )
+        //This shows and hides the UI, giving it time to actually show itself and empty the event
+        //dispatch queue. This is required for the test tab to become current (were it still present).
+        openApi.flushEventQueue(frame)
 
-      //try to get the test tab
-      testTabIndex = singlePane.getGradleTabIndex( "Test Tab" );
-      Assert.assertTrue( "Erroneously got index of test tab. It was removed", testTabIndex == -1 )
+        //try to get the test tab
+        testTabIndex = singlePane.getGradleTabIndex("Test Tab");
+        Assert.assertTrue("Erroneously got index of test tab. It was removed", testTabIndex == -1)
 
-      //we've removed it, so it shouldn't have been polled about or informed of anything
-      Assert.assertFalse( testTab.nameRetrieved );
-      Assert.assertFalse( testTab.informedAboutToShow );
-      Assert.assertFalse( testTab.componentCreated );
+        //we've removed it, so it shouldn't have been polled about or informed of anything
+        Assert.assertFalse(testTab.nameRetrieved);
+        Assert.assertFalse(testTab.informedAboutToShow);
+        Assert.assertFalse(testTab.componentCreated);
 
-      //It was not shown after the reset, these should both be false
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
-      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+        //It was not shown after the reset, these should both be false
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasShown)
+        Assert.assertFalse(testTab.hierarchyAdapter.componentWasHidden)
     }
 
     /**
-    * We want to make sure the settings are working correctly here. This is the mechanism that
+     * We want to make sure the settings are working correctly here. This is the mechanism that
      * handles saving/restoring the values within the UI and can be stored in different ways
      * depending on how the UI integrated with its parent (its up to whoever implements
      * SettingsNodeVersion1). Here, to spot check that the basics are working, we'll create a
      * UI, set a value, close it, then recreate it using the same settings object. The values
      * should be saved upon close and then restored.
-    */
+     */
     @Test
-    public void testSettings()
-    {
-      TestSettingsNodeVersion1 settingsNode = new TestSettingsNodeVersion1();
+    void testSettings() {
+        TestSettingsNodeVersion1 settingsNode = new TestSettingsNodeVersion1();
 
-      TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), settingsNode );
+        TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), settingsNode);
         SinglePaneUIVersion1 singlePane = null;
         try {
-            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false );
+            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
-            throw new AssertionFailedError( "Failed to extract single pane: Caused by " + e.getMessage() )
+            throw new AssertionError("Failed to extract single pane: Caused by " + e.getMessage())
         }
 
-        File illegalDirectory = dist.testFile( "non-existant" ).createDir();
-        if( illegalDirectory.equals( singlePane.getCurrentDirectory() ) ) {
-          throw new AssertionFailedError( "Directory already set to 'test' directory. The test is not setup correctly." );
+        File illegalDirectory = dist.testFile("non-existant").createDir();
+        if (illegalDirectory.equals(singlePane.getCurrentDirectory())) {
+            throw new AssertionError("Directory already set to 'test' directory. The test is not setup correctly.");
         }
 
         //this is required to get the ball rolling
         singlePane.aboutToShow();
 
         //set the current directory after calling aboutToShow (otherwise, it'll stomp over us when it restores its default settings)
-        singlePane.setCurrentDirectory( illegalDirectory );
+        singlePane.setCurrentDirectory(illegalDirectory);
 
         //close the UI. This saves the current settings.
         singlePane.close();
 
         //now instantiate it again
-        testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), settingsNode );
+        testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1(new TestAlternateUIInteractionVersion1(), settingsNode);
         try {
-            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false );
+            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false);
         } catch (Exception e) {
-            throw new AssertionFailedError( "Failed to extract single pane (second time): Caused by " + e.getMessage() )
+            throw new AssertionError("Failed to extract single pane (second time): Caused by " + e.getMessage())
         }
 
         //this should restore the previous settings
         singlePane.aboutToShow();
 
-        Assert.assertEquals( illegalDirectory, singlePane.getCurrentDirectory() );
+        Assert.assertEquals(illegalDirectory, singlePane.getCurrentDirectory());
     }
 
     /**
@@ -625,287 +620,274 @@ public class OpenApiUiTest {
      * so I'm going to pass an argument that is illegal by itself -- meaning no tasks
      * are specified. Then I'll alter the command line by adding an actual task. Then
      * wait for it to complete and verify what was executed
-    */
+     */
     @Test
-    public void testCommandLineAlteringListener()
-    {
-      DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
-      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+    void testCommandLineAlteringListener() {
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+        GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
 
-      //this starts the execution queue. This also initiates a refresh that we'll ignore later.
-      dualPane.aboutToShow()
+        //this starts the execution queue. This also initiates a refresh that we'll ignore later.
+        dualPane.aboutToShow()
 
-      //add a request observer so we can observe when the command is finished. This allows us to
-      //see what was actually executed.
-      BlockingRequestObserver testRequestObserver = new BlockingRequestObserver( RequestVersion1.EXECUTION_TYPE )
-      gradleInterface.addRequestObserver( testRequestObserver )
+        //add a request observer so we can observe when the command is finished. This allows us to
+        //see what was actually executed.
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.EXECUTION_TYPE)
+        gradleInterface.addRequestObserver(testRequestObserver)
 
-      //now that we know that command is illegal by itself, try it again but the listener will append 'build'
-      //to the command line which makes it legal (again, we don't really care what we execute.
-      TestCommandLineArgumentAlteringListenerVersion1 commandLineArgumentAlteringListener = new TestCommandLineArgumentAlteringListenerVersion1("classes")
-      gradleInterface.addCommandLineArgumentAlteringListener( commandLineArgumentAlteringListener )
+        //now that we know that command is illegal by itself, try it again but the listener will append 'build'
+        //to the command line which makes it legal (again, we don't really care what we execute.
+        TestCommandLineArgumentAlteringListenerVersion1 commandLineArgumentAlteringListener = new TestCommandLineArgumentAlteringListenerVersion1("classes")
+        gradleInterface.addCommandLineArgumentAlteringListener(commandLineArgumentAlteringListener)
 
-      //execute this before we do our test. This is not legal by itself. It should fail. That means our
-      //test is setup correctly. For example: if someone adds a default task to this project, this will
-      //generate NO error and thus, our test will prove nothing. If you get a test failure here, you
-      //can try changing the command line to something that's illegal by itself (we don't care what).
-      RequestVersion1 request = gradleInterface.executeCommand2("-s", "test command")
+        //execute this before we do our test. This is not legal by itself. It should fail. That means our
+        //test is setup correctly. For example: if someone adds a default task to this project, this will
+        //generate NO error and thus, our test will prove nothing. If you get a test failure here, you
+        //can try changing the command line to something that's illegal by itself (we don't care what).
+        RequestVersion1 request = gradleInterface.executeCommand2("-s", "test command")
 
-      testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
 
-      Assert.assertThat( testRequestObserver.request.getFullCommandLine(), startsWith( "-s " ) )
-      Assert.assertThat( testRequestObserver.request.getFullCommandLine(), endsWith( " classes" ) )
+        Assert.assertThat(testRequestObserver.request.getFullCommandLine(), startsWith("-s "))
+        Assert.assertThat(testRequestObserver.request.getFullCommandLine(), endsWith(" classes"))
 
-      //make sure it completed execution correctly
-      Assert.assertEquals( "Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output , 0, testRequestObserver.result )
+        //make sure it completed execution correctly
+        Assert.assertEquals("Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output, 0, testRequestObserver.result)
 
-      //the request that was executed should be equal to our original command with our 'altered' command added to it
-      Assert.assertNotNull( "Missing 'execution completed' request", testRequestObserver.request )
+        //the request that was executed should be equal to our original command with our 'altered' command added to it
+        Assert.assertNotNull("Missing 'execution completed' request", testRequestObserver.request)
 
-      //just to be paranoid, let's make sure it was actually our request. If this fails, it probably represents something
-      //fundamentally flawed with the request or request wrapper mechanism.
-      Assert.assertEquals( request, testRequestObserver.request )
+        //just to be paranoid, let's make sure it was actually our request. If this fails, it probably represents something
+        //fundamentally flawed with the request or request wrapper mechanism.
+        Assert.assertEquals(request, testRequestObserver.request)
 
-      gradleInterface.removeRequestObserver( testRequestObserver )
-      gradleInterface.removeCommandLineArgumentAlteringListener( commandLineArgumentAlteringListener )
+        gradleInterface.removeRequestObserver(testRequestObserver)
+        gradleInterface.removeCommandLineArgumentAlteringListener(commandLineArgumentAlteringListener)
     }
 
-   /**
-    * This tests that getVersion returns the same thing as the jar's suffix.
-    * We'll get the gradle jar, then strip off its extension and verify that
-    * the jar's name ends with the version number.
-    */
-  @Test
-  public void testVersion()
-  {
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-    String version = ( (GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).getVersion()
-
-    Assert.assertNotNull( "null version number", version )
-
-    Assert.assertFalse( "Empty version number", version.trim().equals( "" ) )       //shouldn't be empty
-
-    File gradleJar = ExternalUtility.getGradleJar(dist.gradleHomeDir)
-
-    Assert.assertNotNull( "Missing gradle jar", gradleJar )                         //we should have a gradle jar
-
-    int indexOfExtension = gradleJar.getName().toLowerCase().lastIndexOf( ".jar" )  //get the index of its extension
-
-    Assert.assertTrue( "Has no '.jar' extension", indexOfExtension != -1 )          //it had better have an extension
-
-    String name = gradleJar.getName().substring( 0, indexOfExtension )              //get its name minus the extension
-
-    Assert.assertTrue( "Jar name doesn't end with version", name.endsWith( version ) )  //the name (minus extension) should end with the version
-  }
-
-  /**
-   * This is just a spot check that getGradleHomeDirectory works. Its based off
-   * of the gradle you're running.
-   */
-  @Test
-  public void testGradleHomeDirectory()
-  {
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-
-    Assert.assertEquals( dist.gradleHomeDir, singlePane.getGradleHomeDirectory() )
-  }
-
-
-  /**
-   * This is just a spot check that we can get an instance of the OutputUILord.
-   * Other tests cover its functionality more thoroughly. This is just to make sure
-   * its working when accessed via the Open API.
-   */
-  @Test
-  public void testOutputUILord()
-  {
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
-    Assert.assertNotNull( outputUILord )
-  }
-
-  /**
-   * This tests that you can correctly obtain the number of output tabs from a
-   * dual pane UI. This
-   */
-  @Test
-  public void testDualPaneOutputPaneNumber()
-  {
-    if ( java.awt.GraphicsEnvironment.isHeadless() ) {
-      return;  // Can't run this test in headless mode!
+    /**
+     * This tests that getVersion returns the same thing as the jar's suffix.
+     * We'll get the gradle jar, then strip off its extension and verify that
+     * the jar's name ends with the version number.
+     */
+    @Test
+    void testVersion() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        String version = ((GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).getVersion()
+
+        Assert.assertNotNull("null version number", version)
+
+        Assert.assertFalse("Empty version number", version.trim().equals(""))       //shouldn't be empty
+
+        File gradleJar = ExternalUtility.getGradleJar(dist.gradleHomeDir)
+
+        Assert.assertNotNull("Missing gradle jar", gradleJar)                         //we should have a gradle jar
+
+        int indexOfExtension = gradleJar.getName().toLowerCase().lastIndexOf(".jar")  //get the index of its extension
+
+        Assert.assertTrue("Has no '.jar' extension", indexOfExtension != -1)          //it had better have an extension
+
+        String jarName = gradleJar.getName().substring(0, indexOfExtension)              //get its name minus the extension
+
+        assert jarName.endsWith(version)  //the name (minus extension) should end with the version
     }
 
-    DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+    /**
+     * This is just a spot check that getGradleHomeDirectory works. Its based off
+     * of the gradle you're running.
+     */
+    @Test
+    void testGradleHomeDirectory() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
 
-    //now create a frame, place the UI in it, then show it briefly
-    JFrame frame = openApi.open(dualPane)
+        Assert.assertEquals(dist.gradleHomeDir, singlePane.getGradleHomeDirectory())
+    }
+
+    /**
+     * This is just a spot check that we can get an instance of the OutputUILord.
+     * Other tests cover its functionality more thoroughly. This is just to make sure
+     * its working when accessed via the Open API.
+     */
+    @Test
+    void testOutputUILord() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+        Assert.assertNotNull(outputUILord)
+    }
 
-    //make sure we got something
-    Assert.assertNotNull( dualPane )
+    /**
+     * This tests that you can correctly obtain the number of output tabs from a
+     * dual pane UI. This
+     */
+    @Test
+    void testDualPaneOutputPaneNumber() {
+        if (java.awt.GraphicsEnvironment.isHeadless()) {
+            return;  // Can't run this test in headless mode!
+        }
 
-    //tell it we're about to show it, so it'll create a component
-    dualPane.aboutToShow()
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
 
-    dualPane.refreshTaskTree()
+        //now create a frame, place the UI in it, then show it briefly
+        JFrame frame = openApi.open(dualPane)
 
-    openApi.flushEventQueue( frame )
+        //make sure we got something
+        Assert.assertNotNull(dualPane)
 
-    //there should be one opened output tab for the refresh
-    Assert.assertEquals( 1, dualPane.getNumberOfOpenedOutputTabs() )
+        //tell it we're about to show it, so it'll create a component
+        dualPane.aboutToShow()
 
-    dualPane.executeCommand( "build", "build" )
+        dualPane.refreshTaskTree()
 
-    openApi.flushEventQueue( frame )
+        openApi.flushEventQueue(frame)
 
-    //there should be 2 opened output tabs. One for refresh, one for build
-    Assert.assertEquals( 2, dualPane.getNumberOfOpenedOutputTabs() )
-  }
+        //there should be one opened output tab for the refresh
+        Assert.assertEquals(1, dualPane.getNumberOfOpenedOutputTabs())
 
-   /**
-  * This tests whether or not a the UI is considered busy. Its busy if its
-    * executing a command. To test this, we'll execute a command and verify
-    * we're busy. When it finishes, we'll verify we're not longer busy.
-    * We'll also check that canClose works properly. If we're busy, calling
-    * canClose should prompt the user to confirm closing and return their
-    * answer. If we're not busy, it should not prompt the user and return
-    * true.
-   */
-  @Test
-  public void testBusy()
-  {
-      DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
-      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+        dualPane.executeCommand("build", "build")
 
-      //this starts the execution queue. This also initiates a refresh that we'll ignore later.
-      dualPane.aboutToShow()
+        openApi.flushEventQueue(frame)
 
-      //add a request observer so we can observe when the command is finished.
-      BlockingRequestObserver testRequestObserver = new BlockingRequestObserver( RequestVersion1.EXECUTION_TYPE )
-      gradleInterface.addRequestObserver( testRequestObserver )
+        //there should be 2 opened output tabs. One for refresh, one for build
+        Assert.assertEquals(2, dualPane.getNumberOfOpenedOutputTabs())
+    }
 
-      gradleInterface.executeCommand("build", "test command")
+    /**
+     * This tests whether or not a the UI is considered busy. Its busy if its
+     * executing a command. To test this, we'll execute a command and verify
+     * we're busy. When it finishes, we'll verify we're not longer busy.
+     * We'll also check that canClose works properly. If we're busy, calling
+     * canClose should prompt the user to confirm closing and return their
+     * answer. If we're not busy, it should not prompt the user and return
+     * true.
+     */
+    @Test
+    void testBusy() {
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+        GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
 
-      //now that there's a real command in the queue, we should be considered busy
-      Assert.assertTrue( dualPane.isBusy() )
-      Assert.assertTrue( gradleInterface.isBusy() )
+        //this starts the execution queue. This also initiates a refresh that we'll ignore later.
+        dualPane.aboutToShow()
 
-      //we're busy, we shouldn't be able to close
-      TestCloseInteraction testCloseInteraction = new TestCloseInteraction( false )
-      Assert.assertFalse( dualPane.canClose( testCloseInteraction ) )
+        //add a request observer so we can observe when the command is finished.
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.EXECUTION_TYPE)
+        gradleInterface.addRequestObserver(testRequestObserver)
 
-      //since we just asked to close and we're busy, make sure we prompted the user
-      Assert.assertTrue( testCloseInteraction.wasPromptedToConfirmClose )
+        gradleInterface.executeCommand("build", "test command")
 
-      testRequestObserver.waitForRequestExecutionComplete(120, TimeUnit.SECONDS)
+        //now that there's a real command in the queue, we should be considered busy
+        Assert.assertTrue(dualPane.isBusy())
+        Assert.assertTrue(gradleInterface.isBusy())
 
-      Assert.assertThat( testRequestObserver.request.getFullCommandLine(), startsWith( "build" ) )
+        //we're busy, we shouldn't be able to close
+        TestCloseInteraction testCloseInteraction = new TestCloseInteraction(false)
+        Assert.assertFalse(dualPane.canClose(testCloseInteraction))
 
-      //make sure it completed execution correctly
-      Assert.assertEquals( "Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output , 0, testRequestObserver.result )
+        //since we just asked to close and we're busy, make sure we prompted the user
+        Assert.assertTrue(testCloseInteraction.wasPromptedToConfirmClose)
 
-      //make sure we're not longer considered busy
-      Assert.assertFalse( dualPane.isBusy() )
-      Assert.assertFalse( gradleInterface.isBusy() )
+        testRequestObserver.waitForRequestExecutionComplete(120, TimeUnit.SECONDS)
 
-      //make sure we can close now
-      testCloseInteraction = new TestCloseInteraction( false )
-      Assert.assertTrue( dualPane.canClose( testCloseInteraction ) )
+        Assert.assertThat(testRequestObserver.request.getFullCommandLine(), startsWith("build"))
 
-      //since we just asked to close and we're NOT busy, make sure we did NOT prompt the user
-      Assert.assertFalse( testCloseInteraction.wasPromptedToConfirmClose )
+        //make sure it completed execution correctly
+        Assert.assertEquals("Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output, 0, testRequestObserver.result)
 
-      gradleInterface.removeRequestObserver( testRequestObserver )
-  }
+        //make sure we're not longer considered busy
+        Assert.assertFalse(dualPane.isBusy())
+        Assert.assertFalse(gradleInterface.isBusy())
 
-   /**
-   * This tests that we can set a custom gradle executor.
-    */
+        //make sure we can close now
+        testCloseInteraction = new TestCloseInteraction(false)
+        Assert.assertTrue(dualPane.canClose(testCloseInteraction))
+
+        //since we just asked to close and we're NOT busy, make sure we did NOT prompt the user
+        Assert.assertFalse(testCloseInteraction.wasPromptedToConfirmClose)
+
+        gradleInterface.removeRequestObserver(testRequestObserver)
+    }
+
+    /**
+     * This tests that we can set a custom gradle executor.
+     */
     @Test
-    public void testSettingCustomGradleExecutor()
-    {
-       DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
-      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+    void testSettingCustomGradleExecutor() {
+        DualPaneUIVersion1 dualPane = openApi.createDualPaneUI()
+        GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
 
-      //it should be null by default
-      Assert.assertNull( gradleInterface.getCustomGradleExecutable() )
+        //it should be null by default
+        Assert.assertNull(gradleInterface.getCustomGradleExecutable())
 
-      //now let's set it to a custom gradle executable. Actually, we're not going to really get
-      //a custom one; we'll use the normal one. Why? Because a real custom one would probably
-      //become a pain for maintaining this test. Here, we're interested that the basics are working
-      //from an open-api standpoint.
-      File gradleExecutor = getCustomGradleExecutable()
+        //now let's set it to a custom gradle executable. Actually, we're not going to really get
+        //a custom one; we'll use the normal one. Why? Because a real custom one would probably
+        //become a pain for maintaining this test. Here, we're interested that the basics are working
+        //from an open-api standpoint.
+        File gradleExecutor = getCustomGradleExecutable()
 
-      gradleInterface.setCustomGradleExecutable( gradleExecutor )
+        gradleInterface.setCustomGradleExecutable(gradleExecutor)
 
-      //make sure it was set
-      Assert.assertEquals( gradleExecutor, gradleInterface.getCustomGradleExecutable() )
-      Assert.assertEquals( gradleExecutor, dualPane.getCustomGradleExecutable() ) //just another way to get it
+        //make sure it was set
+        Assert.assertEquals(gradleExecutor, gradleInterface.getCustomGradleExecutable())
+        Assert.assertEquals(gradleExecutor, dualPane.getCustomGradleExecutable()) //just another way to get it
 
-      //add a request observer so we can observe when the command is finished.
-      BlockingRequestObserver testRequestObserver = new BlockingRequestObserver( RequestVersion1.REFRESH_TYPE )
-      gradleInterface.addRequestObserver( testRequestObserver )
+        //add a request observer so we can observe when the command is finished.
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver(RequestVersion1.REFRESH_TYPE)
+        gradleInterface.addRequestObserver(testRequestObserver)
 
-      //this starts the execution queue
-      dualPane.aboutToShow()
+        //this starts the execution queue
+        dualPane.aboutToShow()
 
-      dualPane.refreshTaskTree()
+        dualPane.refreshTaskTree()
 
-      testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
 
-      //make sure it completed execution correctly
-      Assert.assertEquals( "Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output , 0, testRequestObserver.result )
+        //make sure it completed execution correctly
+        Assert.assertEquals("Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output, 0, testRequestObserver.result)
 
-      gradleInterface.removeRequestObserver( testRequestObserver )
+        gradleInterface.removeRequestObserver(testRequestObserver)
     }
 
-   /**
-    * This gets a gradle executable. That is, a way to launch gradle (shell script or batch file).
-    */
-    private File getCustomGradleExecutable()
-    {
-      //now let's set it to a custom gradle executable. We'll just point it to the regular
-      //gradle file (but it'll be the custom one.
-      String name = OperatingSystem.current().getScriptName("bin/gradle");
+    /**
+     * This gets a gradle executable. That is, a way to launch gradle (shell script or batch file).
+     */
+    private File getCustomGradleExecutable() {
+        //now let's set it to a custom gradle executable. We'll just point it to the regular
+        //gradle file (but it'll be the custom one.
+        String name = OperatingSystem.current().getScriptName("bin/gradle");
 
-      File gradleExecutor = new File( dist.getGradleHomeDir(), name )
+        File gradleExecutor = new File(dist.getGradleHomeDir(), name)
 
-      //make sure the executable exists
-      Assert.assertTrue( "Missing gradle executable at: " + gradleExecutor, gradleExecutor.exists() )
+        //make sure the executable exists
+        Assert.assertTrue("Missing gradle executable at: " + gradleExecutor, gradleExecutor.exists())
 
-      return gradleExecutor
+        return gradleExecutor
     }
 }
 
-  /**
-   * Inner class for tracking a component's visiblity has changed.
-   * A HierarchyListener is how Swing notifies you that a component's visibility has changed.
-   * We'll use it to track if the component was shown and then hidden.
-   */
-    private class TestVisibilityHierarchyListener implements HierarchyListener
-    {
-      private boolean componentWasShown = false;
-      private boolean componentWasHidden = false;
-
-       public void hierarchyChanged(HierarchyEvent e)
-       {
-         if((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED)!=0)
-         {
-            if( e.getComponent().isShowing() ) {
-              componentWasShown = true;
+/**
+ * Inner class for tracking a component's visiblity has changed.
+ * A HierarchyListener is how Swing notifies you that a component's visibility has changed.
+ * We'll use it to track if the component was shown and then hidden.
+ */
+private class TestVisibilityHierarchyListener implements HierarchyListener {
+    private boolean componentWasShown = false;
+    private boolean componentWasHidden = false;
+
+    void hierarchyChanged(HierarchyEvent e) {
+        if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
+            if (e.getComponent().isShowing()) {
+                componentWasShown = true;
             }
             else {
-              componentWasHidden = true;
+                componentWasHidden = true;
             }
-         }
-       }
-     }
+        }
+    }
+}
 
- /**
+/**
  * A class that manages a dummy gradle tab. It just consists of a label,
  *  but tracks that certain fields were called.
-  */
-  public class TestTab implements GradleTabVersion1
-  {
+ */
+class TestTab implements GradleTabVersion1 {
     private JLabel label = new JLabel("Testing Testing 123")
     private TestVisibilityHierarchyListener hierarchyAdapter = new TestVisibilityHierarchyListener()
     private boolean nameRetrieved
@@ -913,71 +895,69 @@ public class OpenApiUiTest {
     private boolean componentCreated
 
     def TestTab() {
-      label.addHierarchyListener( hierarchyAdapter )
+        label.addHierarchyListener(hierarchyAdapter)
     }
 
     private void reset() {
-      label.removeHierarchyListener( hierarchyAdapter )         //remove the existing listener
-      hierarchyAdapter = new TestVisibilityHierarchyListener()  //create a new one
-      label.addHierarchyListener( hierarchyAdapter )            //add it
+        label.removeHierarchyListener(hierarchyAdapter)         //remove the existing listener
+        hierarchyAdapter = new TestVisibilityHierarchyListener()  //create a new one
+        label.addHierarchyListener(hierarchyAdapter)            //add it
 
-      nameRetrieved = false
-      informedAboutToShow = false
-      componentCreated = false
+        nameRetrieved = false
+        informedAboutToShow = false
+        componentCreated = false
     }
 
     String getName() {
-      nameRetrieved = true;
-      return "Test Tab";
+        nameRetrieved = true;
+        return "Test Tab";
     }
 
     Component createComponent() {
-      componentCreated = true;
-      return label;
+        componentCreated = true;
+        return label;
     }
 
     void aboutToShow() {
-      informedAboutToShow = true;
+        informedAboutToShow = true;
     }
-  }
+}
 
-  /**
-   * Class that tracks whether we were prompted to confirm close. It also returns a specific
-   * value to that prompt.
-   */
-    private class TestCloseInteraction implements BasicGradleUIVersion1.CloseInteraction
-    {
-      boolean wasPromptedToConfirmClose
-      boolean promptResult
+/**
+ * Class that tracks whether we were prompted to confirm close. It also returns a specific
+ * value to that prompt.
+ */
+private class TestCloseInteraction implements BasicGradleUIVersion1.CloseInteraction {
+    boolean wasPromptedToConfirmClose
+    boolean promptResult
 
 
 
-      def TestCloseInteraction(promptResult) {
+    def TestCloseInteraction(promptResult) {
         this.promptResult = promptResult;
-      }
+    }
 
-      boolean promptUserToConfirmClosingWhileBusy() {
+    boolean promptUserToConfirmClosingWhileBusy() {
         wasPromptedToConfirmClose = true
         return promptResult
-      }
     }
+}
 
-  /**
-   * This appends a specified string to the command line when executing a command. 
-   */
-  private class TestCommandLineArgumentAlteringListenerVersion1 implements CommandLineArgumentAlteringListenerVersion1
-  {
+/**
+ * This appends a specified string to the command line when executing a command.
+ */
+private class TestCommandLineArgumentAlteringListenerVersion1 implements CommandLineArgumentAlteringListenerVersion1 {
     private final String additionalArguments;
 
     def TestCommandLineArgumentAlteringListenerVersion1(additionalArguments) {
-      this.additionalArguments = additionalArguments;
+        this.additionalArguments = additionalArguments;
     }
 
     String getAdditionalCommandLineArguments(String commandLineArguments) {
-      if( commandLineArguments.startsWith( "-s " ) ) {  //we're only interested in altering this one command
-        return additionalArguments;
-      }
+        if (commandLineArguments.startsWith("-s ")) {  //we're only interested in altering this one command
+            return additionalArguments;
+        }
 
-      return null;
+        return null;
     }
-  }
+}
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
index 85d6439..df857b5 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
@@ -15,17 +15,23 @@
  */
 package org.gradle.integtests.openapi;
 
-
 import java.awt.Font
 import java.util.concurrent.TimeUnit
 import javax.swing.UIManager
+
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.openapi.external.ui.OutputUILordVersion1
 import org.gradle.openapi.external.ui.SinglePaneUIVersion1
+import org.gradle.util.TestPrecondition
+import org.gradle.util.Requires
+import org.gradle.util.PreconditionVerifier
+
 import org.junit.Assert
 import org.junit.Rule
 import org.junit.Test
+import org.junit.ClassRule
+
 import static org.hamcrest.Matchers.startsWith
 
 /**
@@ -33,95 +39,90 @@ import static org.hamcrest.Matchers.startsWith
  *
  * @author mhunsicker
  */
-public class OutputUILordTest  {
-  @Rule public final GradleDistribution dist = new GradleDistribution()
-  @Rule public final OpenApiFixture openApi = new OpenApiFixture()
-  @Rule public final TestResources resources = new TestResources('testProject')
-
-  /**
-  * This verifies that you can add file extension to the output lord. This is for
-  * highlighting file links in the output. Here, we're just interested in whether
-  * or not the functions work via/exists in the Open API. The actual functionality
-  * is tested elsewhere.
-  */
-  @Test
-  public void testAddingFileExtension()
-  {
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
-
-    outputUILord.addFileExtension( '.txt', ':' )
-    List extensions = outputUILord.getFileExtensions()
-    Assert.assertTrue( extensions.contains( '.txt' ) )
-  }
-
-  /**
-  * This verifies that you can add prefixed file extensions to the output lord. This
-  * is for highlighting file links in the output. Here, we're just interested in whether
-  * or not the functions work via/exists in the Open API. The actual functionality is tested elsewhere.
-  */
-  @Test
-  public void testAddingPrefixedFileLink()
-  {
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
-
-    outputUILord.addPrefixedFileLink( "Error Text", "The error is:", ".txt", ":" )
-  }
-
-  /**
-  * This tests setting the font. There's not much here to do other than set it and then
-  * get it, making sure its the same. This isn't worried so much about the font itself as
-  * much as the open API doesn't have a problem with setting the font.
-  */
-  @Test
-  public void testFont()
-  {
-    if ( java.awt.GraphicsEnvironment.isHeadless() ) {
-       return;  // Can't run this test in headless mode!
+ at Requires(TestPrecondition.SWING)
+class OutputUILordTest {
+    @Rule public GradleDistribution dist = new GradleDistribution()
+    @Rule public OpenApiFixture openApi = new OpenApiFixture()
+    @Rule public TestResources resources = new TestResources('testProject')
+    @ClassRule public static PreconditionVerifier verifier = new PreconditionVerifier()
+
+    /**
+     * This verifies that you can add file extension to the output lord. This is for
+     * highlighting file links in the output. Here, we're just interested in whether
+     * or not the functions work via/exists in the Open API. The actual functionality
+     * is tested elsewhere.
+     */
+    @Test
+    void testAddingFileExtension() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+
+        outputUILord.addFileExtension('.txt', ':')
+        List extensions = outputUILord.getFileExtensions()
+        Assert.assertTrue(extensions.contains('.txt'))
     }
 
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
-    Font font = UIManager.getFont( "Button.font" )  //this specific font is not important
-
-    //make sure that the above font doesn't happen to be the default font for the output lord. If it
-    //is, this test will silently succeed even if it should fail.
-    Assert.assertNotSame( "Fonts are the same. This test is not setup correctly.", font, outputUILord.getOutputTextFont() )
+    /**
+     * This verifies that you can add prefixed file extensions to the output lord. This
+     * is for highlighting file links in the output. Here, we're just interested in whether
+     * or not the functions work via/exists in the Open API. The actual functionality is tested elsewhere.
+     */
+    @Test
+    void testAddingPrefixedFileLink() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+
+        outputUILord.addPrefixedFileLink("Error Text", "The error is:", ".txt", ":")
+    }
 
-    //now set the new font and then make sure it worked
-    outputUILord.setOutputTextFont( font )
-  }
+    /**
+     * This tests setting the font. There's not much here to do other than set it and then
+     * get it, making sure its the same. This isn't worried so much about the font itself as
+     * much as the open API doesn't have a problem with setting the font.
+     */
+    @Test
+    void testFont() {
+        if (java.awt.GraphicsEnvironment.isHeadless()) {
+            return;  // Can't run this test in headless mode!
+        }
+
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+        Font font = UIManager.getFont("Button.font")  //this specific font is not important
+
+        //make sure that the above font doesn't happen to be the default font for the output lord. If it
+        //is, this test will silently succeed even if it should fail.
+        Assert.assertNotSame("Fonts are the same. This test is not setup correctly.", font, outputUILord.getOutputTextFont())
+
+        //now set the new font and then make sure it worked
+        outputUILord.setOutputTextFont(font)
+    }
 
-  /**
-  *
-  */
-  @Test
-  public void testReExecute()
-  {
-    SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
-    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+    @Test
+    void testReExecute() {
+        SinglePaneUIVersion1 singlePane = openApi.createSinglePaneUI()
+        OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
 
-    //this starts the execution queue. This also initiates a refresh that we'll ignore later.
-    singlePane.aboutToShow()
+        //this starts the execution queue. This also initiates a refresh that we'll ignore later.
+        singlePane.aboutToShow()
 
-    BlockingRequestObserver testRequestObserver = new BlockingRequestObserver()
-    singlePane.getGradleInterfaceVersion1().addRequestObserver( testRequestObserver )
+        BlockingRequestObserver testRequestObserver = new BlockingRequestObserver()
+        singlePane.getGradleInterfaceVersion1().addRequestObserver(testRequestObserver)
 
-    //now execute a command
-    singlePane.executeCommand( "build", "test build")
+        //now execute a command
+        singlePane.executeCommand("build", "test build")
 
-    //wait for it to complete
-    testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
-    testRequestObserver.reset()
+        //wait for it to complete
+        testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        testRequestObserver.reset()
 
-    //now the single command we're trying to test
-    outputUILord.reExecuteLastCommand();
+        //now the single command we're trying to test
+        outputUILord.reExecuteLastCommand();
 
-    //wait again for it exit
-    testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
+        //wait again for it exit
+        testRequestObserver.waitForRequestExecutionComplete(80, TimeUnit.SECONDS)
 
-    //make sure it executed the correct request
-    Assert.assertThat( testRequestObserver.request.getFullCommandLine(), startsWith('build') )
-  }
+        //make sure it executed the correct request
+        Assert.assertThat(testRequestObserver.request.getFullCommandLine(), startsWith('build'))
+    }
 }
diff --git a/subprojects/open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/build.gradle b/subprojects/open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
index 20dc96c..357ceb0 100644
--- a/subprojects/open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
+++ b/subprojects/open-api/src/integTest/resources/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest/shared/build.gradle
@@ -1,3 +1,3 @@
 allprojects {
-    usePlugin 'java'
+    apply plugin: 'java'
 }
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
index 0d78555..b705c56 100644
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
+++ b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
@@ -40,7 +40,9 @@ public class GradleRunnerFactory {
      * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
      * @return a gradle runner
      * @author mhunsicker
+     * @deprecated Use the tooling API instead.
      */
+    @Deprecated
     public static GradleRunnerVersion1 createGradleRunner(ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo)
             throws Exception {
         //much of this function is exception handling so if we can't obtain it via the newer factory method, then
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
index f8469db..8d97a88 100644
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
+++ b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
@@ -43,7 +43,9 @@ public class UIFactory {
      * @param gradleHomeDirectory the root directory of a gradle installation
      * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
      * @return the UI object.
+     * @deprecated Use the tooling API instead.
      */
+    @Deprecated
     public static SinglePaneUIVersion1 createSinglePaneUI(ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo)
             throws Exception {
         //much of this function is exception handling so if we can't obtain it via the newer factory method, then
@@ -145,7 +147,9 @@ public class UIFactory {
      * @param showDebugInfo true to show some additional information that may be helpful diagnosing problems is this fails
      * @return the UI object.
      * @author mhunsicker
+     * @deprecated Use the tooling API instead.
      */
+    @Deprecated
     public static DualPaneUIVersion1 createDualPaneUI(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
             throws Exception {
         //much of this function is exception handling so if we can't obtain it via the newer factory method, then
@@ -179,7 +183,9 @@ public class UIFactory {
     /**
      * This function uses a factory to instantiate the UI. The factory is located with the version of gradle pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make
      * decisions about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
+     * @deprecated Use the tooling API instead.
      */
+    @Deprecated
     public static DualPaneUIVersion1 createDualPaneUIViaFactory(ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo)
             throws Exception {
         //load the class in gradle that wraps our return interface and handles versioning issues.
diff --git a/subprojects/osgi/osgi.gradle b/subprojects/osgi/osgi.gradle
index 8bb9536..57d7c1f 100644
--- a/subprojects/osgi/osgi.gradle
+++ b/subprojects/osgi/osgi.gradle
@@ -15,15 +15,13 @@
  */
 
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
     compile libraries.slf4j_api
 
-    compile 'biz.aQute:bndlib:1.15.0 at jar'
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    compile module('biz.aQute:bndlib:1.50.0')
 }
 
+useTestFixtures()
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
index fb963c5..2ad3486 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.plugins.osgi;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 
 /**
  * @author Hans Dockter
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
index b30a25c..fb4136e 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
@@ -17,13 +17,13 @@ package org.gradle.api.internal.plugins.osgi;
 
 import aQute.lib.osgi.Analyzer;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.Factory;
+import org.gradle.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.internal.UncheckedException;
 import org.gradle.util.GUtil;
-import org.gradle.util.UncheckedException;
 import org.gradle.util.WrapUtil;
 
 import java.io.File;
@@ -70,7 +70,7 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
                 effectiveManifest.attributes(ent.getValue(), ent.getKey());
             }
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
         return getEffectiveManifestInternal(effectiveManifest);
     }
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
index 04292d0..8da3158 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
@@ -36,7 +36,7 @@ public class OsgiPlugin implements Plugin<Project> {
         project.plugins.withType(JavaPlugin) {
             def osgiManifest = osgiConvention.osgiManifest {
                 from project.manifest
-                classesDir = project.sourceSets.main.classesDir
+                classesDir = project.sourceSets.main.output.classesDir
                 classpath = project.configurations.runtime
             }
             project.jar.manifest = osgiManifest
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
index d36e1ad..023d8a9 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
@@ -16,7 +16,6 @@
 package org.gradle.api.plugins.osgi;
 
 import groovy.lang.Closure;
-import org.gradle.api.Project;
 import org.gradle.api.internal.plugins.osgi.DefaultOsgiManifest;
 import org.gradle.api.internal.plugins.osgi.OsgiHelper;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -29,9 +28,9 @@ import org.gradle.util.ConfigureUtil;
  * @author Hans Dockter
  */
 public class OsgiPluginConvention {
-    private Project project;
+    private ProjectInternal project;
 
-    public OsgiPluginConvention(Project project) {
+    public OsgiPluginConvention(ProjectInternal project) {
         this.project = project;
     }
 
@@ -66,9 +65,9 @@ public class OsgiPluginConvention {
         return ConfigureUtil.configure(closure, createDefaultOsgiManifest(project));
     }
 
-    private OsgiManifest createDefaultOsgiManifest(Project project) {
+    private OsgiManifest createDefaultOsgiManifest(ProjectInternal project) {
         OsgiHelper osgiHelper = new OsgiHelper();
-        OsgiManifest osgiManifest = new DefaultOsgiManifest(((ProjectInternal) project).getFileResolver());
+        OsgiManifest osgiManifest = new DefaultOsgiManifest(project.getFileResolver());
         osgiManifest.setVersion(osgiHelper.getVersion((String) project.property("version")));
         osgiManifest.setName(project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName());
         osgiManifest.setSymbolicName(osgiHelper.getBundleSymbolicName(project));
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
index 60d085c..9487c0e 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
@@ -17,18 +17,18 @@ package org.gradle.api.internal.plugins.osgi;
 
 import aQute.lib.osgi.Analyzer;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.Factory;
+import org.gradle.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.JUnit4GroovyMockery;
 import org.gradle.util.WrapUtil;
 import org.hamcrest.Matchers;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,19 +49,17 @@ public class DefaultOsgiManifestTest {
     private static final String ARBITRARY_ATTRIBUTE = "Silly-Attribute";
     private static final String ANOTHER_ARBITRARY_ATTRIBUTE = "Serious-Attribute";
 
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
     private DefaultOsgiManifest osgiManifest;
-    private Factory<ContainedVersionAnalyzer> analyzerFactoryMock;
+    @SuppressWarnings("unchecked")
+    private Factory<ContainedVersionAnalyzer> analyzerFactoryMock = context.mock(Factory.class);
     private ContainedVersionAnalyzer analyzerMock;
 
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
     private FileResolver fileResolver = context.mock(FileResolver.class);
 
     @Before
     public void setUp() {
         osgiManifest = new DefaultOsgiManifest(fileResolver);
-        analyzerFactoryMock = context.mock(Factory.class);
         analyzerMock = context.mock(ContainedVersionAnalyzer.class);
         context.checking(new Expectations() {{
             allowing(analyzerFactoryMock).create();
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
index 794cde2..f1c9201 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
@@ -42,6 +42,6 @@ public class OsgiPluginTest extends Specification {
         OsgiManifest osgiManifest = project.jar.manifest
         osgiManifest.mergeSpecs[0].mergePaths[0] == project.manifest
         osgiManifest.classpath == project.configurations."$JavaPlugin.RUNTIME_CONFIGURATION_NAME"
-        osgiManifest.classesDir == project.sourceSets."$SourceSet.MAIN_SOURCE_SET_NAME".classesDir
+        osgiManifest.classesDir == project.sourceSets."$SourceSet.MAIN_SOURCE_SET_NAME".output.classesDir
     }
 }
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
new file mode 100644
index 0000000..a384bf8
--- /dev/null
+++ b/subprojects/performance/performance.gradle
@@ -0,0 +1,62 @@
+apply from: 'src/generator.groovy'
+
+configurations {
+    junit
+}
+
+dependencies {
+    junit 'junit:junit:4.10'
+    groovy libraries.groovy
+}
+
+useTestFixtures()
+
+task small(type: ProjectGeneratorTask, description: 'Generates a small project') {
+}
+
+task largeSrc(type: ProjectGeneratorTask, description: 'Generates a single project with lots of source files') {
+    sourceFiles = 50000
+    linesOfCodePerSourceFile = 20
+}
+
+task multi(type: ProjectGeneratorTask, description: 'Generates a multi-project build') {
+    projects = 25
+    sourceFiles = 100
+}
+
+task mixedSize(type: ProjectGeneratorTask) {
+    projects = 400
+    sourceFiles = 100
+    projects[1].sourceFiles = 20000
+}
+
+task multiGroovy(type: ProjectGeneratorTask, description: 'Generates a multi-project groovy build') {
+    projects = 25
+    groovyProject = true
+}
+
+def generators = tasks.withType(ProjectGeneratorTask)
+generators.all {
+    group = 'Project setup'
+    testDependencies = configurations.junit
+}
+task all(dependsOn: generators)
+
+task prepareSamples(dependsOn: [small, multi])
+
+tasks.integTest.dependsOn prepareSamples
+
+task performanceTest(dependsOn: tasks.integTest) {
+    description = "Runs the performance test (note that performanceTest is not a part of 'check' or 'test')"
+}
+
+tasks.integTest.testLogging.showStandardStreams = true
+
+gradle.taskGraph.whenReady {
+    if (!it.hasTask(':performance:performanceTest')) {
+        project.tasks.withType(Test) {
+            logger.info("Skipping $it because task 'performanceTest' was not requested.")
+            enabled = false
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/generator.groovy b/subprojects/performance/src/generator.groovy
new file mode 100644
index 0000000..09e0211
--- /dev/null
+++ b/subprojects/performance/src/generator.groovy
@@ -0,0 +1,167 @@
+import groovy.text.SimpleTemplateEngine
+import groovy.text.Template
+
+class TestProject {
+    final String name
+    final Object defaults
+    Integer sourceFiles
+    Integer testSourceFiles
+    Integer linesOfCodePerSourceFile
+
+    TestProject() {
+    }
+
+    TestProject(String name, Object defaults) {
+        this.name = name
+        this.defaults = defaults
+    }
+
+    int getSourceFiles() {
+        return sourceFiles ?: defaults.sourceFiles
+    }
+
+    int getTestSourceFiles() {
+        return testSourceFiles ?: defaults.testSourceFiles
+    }
+
+    int getLinesOfCodePerSourceFile() {
+        return linesOfCodePerSourceFile ?: defaults.linesOfCodePerSourceFile
+    }
+}
+
+class ProjectGeneratorTask extends DefaultTask {
+    @OutputDirectory
+    File destDir
+    boolean groovyProject
+    int sourceFiles = 1
+    Integer testSourceFiles
+    int linesOfCodePerSourceFile = 5
+    @InputFiles FileCollection testDependencies
+
+    final List<TestProject> projects = []
+    final SimpleTemplateEngine engine = new SimpleTemplateEngine()
+    final Map<File, Template> templates = [:]
+
+    def ProjectGeneratorTask() {
+        outputs.upToDateWhen { false }
+        setProjects(1)
+        destDir = project.file("${project.buildDir}/${name}")
+    }
+
+    int getTestSourceFiles() {
+        return testSourceFiles ?: sourceFiles
+    }
+
+    void setProjects(int projectCount) {
+        if (projects.size() > projectCount) {
+            projects.subList(projectCount, projects.size()).clear()
+        } else {
+            while (projects.size() < projectCount) {
+                def project = projects.empty ? new TestProject("root", this) : new TestProject("project${projects.size()}", this)
+                projects << project
+            }
+        }
+    }
+
+    @TaskAction
+    void generate() {
+        ant.delete(dir: destDir)
+        destDir.mkdirs()
+
+        generateRootProject()
+        subprojects.each {
+            generateSubProject(it)
+        }
+    }
+
+    List getSubprojectNames() {
+        return getSubprojects().collect { it.name }
+    }
+
+    TestProject getRootProject() {
+        return projects[0]
+    }
+
+    List<TestProject> getSubprojects() {
+        return projects.subList(1, projects.size())
+    }
+
+    def generateRootProject() {
+        generateProject rootProject, subprojects: subprojectNames, projectDir: destDir,
+                files: subprojectNames.empty ? [] : ['settings.gradle'],
+                includeSource: subprojectNames.empty
+
+        project.copy {
+            from testDependencies
+            into new File(getDestDir(), 'lib/test')
+        }
+    }
+
+    def generateSubProject(TestProject testProject) {
+        generateProject testProject, subprojects: [], projectDir: new File(destDir, testProject.name), files: [],
+                includeSource: true
+    }
+
+    def generateProject(Map args, TestProject testProject) {
+        File projectDir = args.projectDir
+        logger.lifecycle"Generating test project '$testProject.name' into $projectDir"
+
+        List files = args.files + [
+                'build.gradle',
+                'pom.xml',
+                'build.xml',
+        ]
+
+        Closure generate = {String name, String templateName, Map templateArgs ->
+            File destFile = new File(projectDir, name)
+            File srcTemplate = project.file("src/templates/$templateName")
+            destFile.parentFile.mkdirs()
+            destFile.withWriter {Writer writer ->
+                getTemplate(srcTemplate).make(templateArgs).writeTo(writer)
+            }
+        }
+
+        args += [projectName: testProject.name, groovyProject: groovyProject, propertyCount: (testProject.linesOfCodePerSourceFile.intdiv(7))]
+
+        files.each {String name ->
+            generate(name, name, args)
+        }
+
+        if (args.includeSource) {
+            testProject.sourceFiles.times {
+                String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
+                Map classArgs = args + [packageName: packageName, productionClassName: "Production${it + 1}"]
+                generate("src/main/java/${packageName.replace('.', '/')}/${classArgs.productionClassName}.java", 'Production.java', classArgs)
+            }
+            testProject.testSourceFiles.times {
+                String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
+                Map classArgs = args + [packageName: packageName, productionClassName: "Production${it + 1}", testClassName: "Test${it + 1}"]
+                generate("src/test/java/${packageName.replace('.', '/')}/${classArgs.testClassName}.java", 'Test.java', classArgs)
+            }
+            if (groovyProject) {
+                testProject.sourceFiles.times {
+                    String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
+                    Map classArgs = args + [packageName: packageName, productionClassName: "ProductionGroovy${it + 1}"]
+                    generate("src/main/groovy/${packageName.replace('.', '/')}/${classArgs.productionClassName}.groovy", 'Production.groovy', classArgs)
+                }
+                testProject.testSourceFiles.times {
+                    String packageName = "org.gradle.test.performance${(int) (it / 100) + 1}"
+                    Map classArgs = args + [packageName: packageName, productionClassName: "ProductionGroovy${it + 1}", testClassName: "TestGroovy${it + 1}"]
+                    generate("src/test/groovy/${packageName.replace('.', '/')}/${classArgs.testClassName}.groovy", 'Test.groovy', classArgs)
+                }
+            }
+        }
+    }
+
+    def getTemplate(File srcTemplate) {
+        def template = templates[srcTemplate]
+        if (!template) {
+            template = engine.createTemplate(srcTemplate)
+            templates[srcTemplate] = template
+        }
+        return template
+    }
+}
+
+//workaround for referring to task types defined in plugin scripts
+project.ext.set('ProjectGeneratorTask', ProjectGeneratorTask)
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
new file mode 100644
index 0000000..2a2d721
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/PerformanceTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.peformance
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.ReleasedVersions
+import org.gradle.peformance.fixture.PerformanceTestRunner
+import spock.lang.Specification
+import spock.lang.Unroll
+
+/**
+ * by Szczepan Faber, created at: 2/9/12
+ */
+class PerformanceTest extends Specification {
+
+    def current = new GradleDistribution()
+    def previous = new ReleasedVersions(current).last
+
+    @Unroll("Project '#testProject' ran #runs times. Current release is not slower than the previous one.")
+    def "speed"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject, runs: runs, warmUpRuns: 1, accuracyMs: accuracyMs).run()
+        result.assertCurrentReleaseIsNotSlower()
+
+        where:
+        testProject | runs | accuracyMs
+        "small"     | 10   | 500
+        "multi"     | 10   | 1000
+    }
+
+    @Unroll("Project '#testProject' with heap size: #heapSize. Current release does not require more memory than the previous one.")
+    def "memory"() {
+        expect:
+        def result = new PerformanceTestRunner(testProject: testProject, runs: 1, gradleOpts: [heapSize]).run()
+        result.assertEveryBuildSucceeds()
+
+        where:
+        testProject | heapSize
+        "small"     | '-Xmx19m' //fails with 16m
+        "multi"     | '-Xmx66m' //fails with 54m on my box, with 60m on ci
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
new file mode 100644
index 0000000..5f9e104
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/MeasuredOperation.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.peformance.fixture
+
+import org.gradle.util.Clock
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+public class MeasuredOperation {
+    long executionTime
+    Exception exception
+    String prettyTime
+    
+    String toString() {
+        prettyTime
+    }
+
+    static MeasuredOperation measure(Closure operation) {
+        def out = new MeasuredOperation()
+        def clock = new Clock()
+        clock.reset()
+        try {
+            operation()
+        } catch (Exception e) {
+            out.exception = e
+        }
+        //not very atomic... :)
+        out.prettyTime = clock.time
+        out.executionTime = clock.timeInMs
+        return out
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
new file mode 100644
index 0000000..5404790
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceResults.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.peformance.fixture
+
+import org.gradle.api.logging.Logging
+
+public class PerformanceResults {
+
+    int accuracyMs
+    String displayName
+
+    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
+
+    List<MeasuredOperation> previous = new LinkedList<MeasuredOperation>()
+    List<MeasuredOperation> current = new LinkedList<MeasuredOperation>()
+
+    def clear() {
+        previous.clear();
+        current.clear();
+    }
+
+    void addResult(MeasuredOperation previous, MeasuredOperation current) {
+        this.previous.add(previous)
+        this.current.add(current)
+    }
+
+    void assertEveryBuildSucceeds() {
+        LOGGER.info("Asserting all builds have succeeded...");
+        assert previous.size() == current.size()
+        def previousExceptions = previous.findAll { it.exception }.collect() { it.exception }
+        def currentExceptions  = previous.findAll { it.exception }.collect() { it.exception }
+        assert previousExceptions.isEmpty() & currentExceptions.isEmpty()
+    }
+
+    void assertCurrentReleaseIsNotSlower() {
+        assertEveryBuildSucceeds()
+        long averagePrevious = previous.collect { it.executionTime }.sum() / previous.size()
+        long averageCurrent  = current.collect { it.executionTime }.sum() / current.size()
+
+        LOGGER.info("\n---------------\nBuild duration stats. $displayName:\n"
+            + " -previous: $previous\n"
+            + " -current : $current\n---------------\n")
+
+        if (averageCurrent > averagePrevious) {
+            LOGGER.warn("Before applying any statistical tuning, the current release average build time is slower than the previous.")
+        }
+
+        assert (averageCurrent - accuracyMs) <= averagePrevious : """Looks like the current gradle is slower than latest release.
+  Previous release build times: ${previous}
+  Current gradle build times:   ${current}
+  Difference between average current and average previous: ${averageCurrent - averagePrevious} millis.
+  Currently configured accuracy treshold: $accuracyMs
+"""
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
new file mode 100644
index 0000000..4e109f6
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/PerformanceTestRunner.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.peformance.fixture
+
+import org.gradle.api.logging.Logging
+import org.gradle.integtests.fixtures.*
+
+public class PerformanceTestRunner {
+    
+    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
+
+    def current = new GradleDistribution()
+    def previous = new ReleasedVersions(current).last
+
+    String testProject
+    int runs
+    int warmUpRuns
+    int accuracyMs
+    List<String> gradleOpts
+
+    def results
+
+    PerformanceResults run() {
+        results = new PerformanceResults(accuracyMs: accuracyMs, displayName: "Results for test project '$testProject'")
+        LOGGER.lifecycle("Running performance tests for test project '{}', no. # runs: {}", testProject, runs)
+        warmUpRuns.times {
+            LOGGER.info("Executing warm-up run #${it+1}")
+            runOnce()
+        }
+        results.clear()
+        runs.times {
+            LOGGER.info("Executing test run #${it+1}")
+            runOnce()
+        }
+        results
+    }
+
+    void runOnce() {
+        def previousExecuter = executer(previous, testProject)
+        def previousResult = MeasuredOperation.measure {
+            previousExecuter.run()
+        }
+
+        def currentExecuter = executer(current, testProject)
+        def currentResult = MeasuredOperation.measure {
+            currentExecuter.run()
+        }
+
+        results.addResult(previousResult, currentResult)
+    }
+
+    GradleExecuter executer(BasicGradleDistribution dist, String testProjectName) {
+        def projectDir = new TestProjectLocator().findProjectDir(testProjectName)
+        def executer
+        if (dist instanceof GradleDistribution) {
+            executer = new GradleDistributionExecuter(GradleDistributionExecuter.Executer.forking, dist)
+        } else {
+            executer = dist.executer()
+        }
+        if (gradleOpts) {
+            executer.withGradleOpts(gradleOpts as String[])
+        }
+        return executer.withArguments('-u').inDirectory(projectDir).withTasks('clean', 'build')
+    }
+}
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy
new file mode 100644
index 0000000..c82f0c7
--- /dev/null
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/peformance/fixture/TestProjectLocator.groovy
@@ -0,0 +1,38 @@
+/*
+ * 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.peformance.fixture
+
+/**
+ * by Szczepan Faber, created at: 2/10/12
+ */
+class TestProjectLocator {
+
+    File findProjectDir(String name) {
+        def base = "subprojects/performance/build"
+        def locations = ["$base/$name", "../../$base/$name"]
+        def dirs = locations.collect { new File(it).absoluteFile }
+        for (File dir: dirs) {
+            if (dir.isDirectory()) {
+                return dir
+            }
+        }
+        def message = "Looks like the test project '$name' was not generated.\nI've tried to find it at:\n"
+        dirs.each { message += "  $it\n" }
+        message +="Please run 'gradlew performance:$name' to generate the test project."
+        assert false: message
+    }
+}
diff --git a/subprojects/performance/src/templates/Production.groovy b/subprojects/performance/src/templates/Production.groovy
new file mode 100644
index 0000000..e9f0386
--- /dev/null
+++ b/subprojects/performance/src/templates/Production.groovy
@@ -0,0 +1,13 @@
+package ${packageName};
+
+public class ${productionClassName} {
+    private final String property;
+
+    public ${productionClassName}(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/performance/src/templates/Production.java b/subprojects/performance/src/templates/Production.java
new file mode 100644
index 0000000..ba24a43
--- /dev/null
+++ b/subprojects/performance/src/templates/Production.java
@@ -0,0 +1,24 @@
+package ${packageName};
+
+public class ${productionClassName} {
+    private final String property;
+
+    public ${productionClassName}(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+<% propertyCount.times { %>
+    private String prop${it};
+
+    public String getProp${it}() {
+        return prop${it};
+    }
+
+    public void setProp${it}(String value) {
+        prop${it} = value;
+    }
+<% } %>
+}
diff --git a/subprojects/performance/src/templates/Test.groovy b/subprojects/performance/src/templates/Test.groovy
new file mode 100644
index 0000000..aeffc36
--- /dev/null
+++ b/subprojects/performance/src/templates/Test.groovy
@@ -0,0 +1,13 @@
+package ${packageName};
+
+
+import static org.junit.Assert.assertEquals
+
+public class ${testClassName} {
+    private final ${productionClassName} production = new ${productionClassName}("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/Test.java b/subprojects/performance/src/templates/Test.java
new file mode 100644
index 0000000..1918fb7
--- /dev/null
+++ b/subprojects/performance/src/templates/Test.java
@@ -0,0 +1,12 @@
+package ${packageName};
+
+import static org.junit.Assert.*;
+
+public class ${testClassName} {
+    private final ${productionClassName} production = new ${productionClassName}("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/build.gradle b/subprojects/performance/src/templates/build.gradle
new file mode 100644
index 0000000..db0df72
--- /dev/null
+++ b/subprojects/performance/src/templates/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+<% if (subprojects.empty ) { %>
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+repositories {
+    mavenCentral()
+//    mavenRepo(urls: 'http://snapshots.repository.codehaus.org/')
+    ivy {
+        name = 'jfrog'
+        artifactPattern('http://repo.jfrog.org/artifactory/gradle-plugins-snapshots/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]')
+//        artifactPattern('http://repo.jfrog.org/artifactory/gradle-ivy-local/[organisation]/[module]/ivy-[revision].xml')
+    }
+}
+
+dependencies {
+    compile 'commons-lang:commons-lang:2.5'
+    compile "commons-httpclient:commons-httpclient:3.0"
+    compile "commons-codec:commons-codec:1.2"
+    compile "org.slf4j:jcl-over-slf4j:1.6.4"
+    compile "org.codehaus.groovy:groovy:1.8.4"
+    compile "commons-codec:commons-codec:1.2"
+    testCompile 'junit:junit:4.8.2'
+    runtime 'com.esotericsoftware:kryo:1.03', 'com.esotericsoftware:minlog:1.2', 'com.googlecode:reflectasm:1.01'
+}
+
+test {
+    jvmArgs '-XX:MaxPermSize=512m', '-XX:+HeapDumpOnOutOfMemoryError'
+//    testReport = false
+}
+
+<% if (groovyProject) { %>
+apply plugin: 'groovy'
+dependencies {
+    groovy 'org.codehaus.groovy:groovy-all:1.8.4'
+}
+<% } %>
+
+<% } %>
diff --git a/subprojects/performance/src/templates/build.xml b/subprojects/performance/src/templates/build.xml
new file mode 100644
index 0000000..f5383b1
--- /dev/null
+++ b/subprojects/performance/src/templates/build.xml
@@ -0,0 +1,70 @@
+<project>
+    <property name="lib.dir" location="lib/test"/>
+
+<% if (!subprojects.empty ) {
+    ['clean', 'jar', 'build'].each { target ->
+%>
+    <target name="$target">
+        <% subprojects.each { subproject -> %>
+            <ant dir="$subproject" target="$target">
+                <property name="lib.dir" location="\${lib.dir}"/>
+            </ant>
+        <%  } %>
+    </target>
+<% }
+} else { %>
+
+    <property name="build.dir" location="ant-build"/>
+    <property name="src.dir" location="src/main/java"/>
+    <property name="test.src.dir" location="src/test/java"/>
+    <property name="classes.dir" location="\${build.dir}/classes"/>
+    <property name="test.classes.dir" location="\${build.dir}/test-classes"/>
+    <property name="test.reports.dir" location="\${build.dir}/test-reports"/>
+
+    <target name="clean">
+        <delete dir="\${build.dir}"/>
+    </target>
+
+    <target name="compile">
+        <mkdir dir="\${classes.dir}"/>
+        <javac srcdir="\${src.dir}" destdir="\${classes.dir}"/>
+    </target>
+
+    <target name="compileTest" depends="compile">
+        <mkdir dir="\${test.classes.dir}"/>
+        <javac srcdir="\${test.src.dir}" destdir="\${test.classes.dir}">
+            <classpath>
+                <path location="\${classes.dir}"/>
+                <fileset dir="\${lib.dir}"/>
+            </classpath>
+        </javac>
+    </target>
+
+    <target name="test" depends="compile, compileTest">
+        <mkdir dir="\${test.reports.dir}"/>
+        <junit>
+            <classpath>
+                <path location="\${test.classes.dir}"/>
+                <path location="\${classes.dir}"/>
+                <fileset dir="\${lib.dir}"/>
+            </classpath>
+            <batchtest todir="\${test.reports.dir}">
+                <fileset dir="\${test.classes.dir}" includes="**/*Test*.class"/>
+            </batchtest>
+            <formatter type="xml"/>
+        </junit>
+        <!--<junitreport toDir="\${build.dir}">-->
+            <!--<fileset dir="\${test.reports.dir}" includes="*.xml"/>-->
+            <!--<report todir="\${test.reports.dir}"/>-->
+        <!--</junitreport>-->
+    </target>
+
+    <target name="jar" depends="compile">
+        <jar destfile="\${build.dir}/production.jar">
+            <fileset dir="\${classes.dir}"/>
+        </jar>
+    </target>
+
+    <target name="build" depends="jar, test"/>
+<% } %>
+</project>
diff --git a/subprojects/performance/src/templates/pom.xml b/subprojects/performance/src/templates/pom.xml
new file mode 100644
index 0000000..bc00a1d
--- /dev/null
+++ b/subprojects/performance/src/templates/pom.xml
@@ -0,0 +1,58 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.gradle</groupId>
+    <artifactId>${projectName}</artifactId>
+<% if (!subprojects.empty ) { %>
+    <packaging>pom</packaging>
+    <modules>
+        <% subprojects.each { out.println "<module>$it</module>" } %>
+    </modules>
+<% } else { %>
+    <packaging>jar</packaging>
+<% } %>
+
+    <version>1.0-SNAPSHOT</version>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.8.1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <!--<version>2.5</version>-->
+                <!--<configuration>-->
+                    <!--<parallel>classes</parallel>-->
+                    <!--<threadCount>4</threadCount>-->
+                <!--</configuration>-->
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-report-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>test-report</id>
+                        <goals>
+                            <goal>report-only</goal>
+                        </goals>
+                        <phase>verify</phase>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/subprojects/performance/src/templates/settings.gradle b/subprojects/performance/src/templates/settings.gradle
new file mode 100644
index 0000000..529201d
--- /dev/null
+++ b/subprojects/performance/src/templates/settings.gradle
@@ -0,0 +1 @@
+<% subprojects.each { out.println "include '$it'"} %>
\ No newline at end of file
diff --git a/subprojects/plugins/plugins.gradle b/subprojects/plugins/plugins.gradle
index e61774e..5a4ee68 100644
--- a/subprojects/plugins/plugins.gradle
+++ b/subprojects/plugins/plugins.gradle
@@ -1,5 +1,3 @@
-import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
-
 /*
  * Copyright 2010 the original author or authors.
  *
@@ -16,59 +14,49 @@ import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDepend
  * limitations under the License.
  */
 
+apply from: "$rootDir/gradle/providedConfiguration.gradle"
+
 configurations {
-    reports
     testFixtures
 }
 
+if (!Jvm.current().java6Compatible) {
+    sourceSets.main.groovy.exclude '**/jdk6/**'
+}
+
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
+    compile project(':wrapper')
 
-    compile libraries.slf4j_api,
-            libraries.commons_lang,
-            libraries.asm_all,
-            libraries.junit,
-            libraries.ant,
-            'org.testng:testng:5.14.10'
-
-    reports 'css3-pie:css3-pie:1.0beta3'
-    testCompile libraries.xmlunit, 'net.sourceforge.nekohtml:nekohtml:1.9.14'
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
-    testFixtures sourceSets.test.classes
-}
-
-task reportResources << {
-    copy {
-        from(configurations.reports)
-        into "${sourceSets.main.classesDir}/org/gradle/api/internal/tasks/testing/junit/report"
-    }
-}
+    compile libraries.ant
+    compile libraries.asm
+    compile libraries.commons_io
+    compile libraries.commons_lang
+    compile libraries.junit
+    compile libraries.slf4j_api
+    compile 'org.testng:testng:6.3.1'
 
-classes.dependsOn reportResources
+    provided files(Jvm.current().toolsJar) // for SunJavaCompiler
 
-task ideResources(type: Copy) {
-    from(configurations.reports)
-    into "${ideDir}/resources/test/org/gradle/api/internal/tasks/testing/junit/report"
-}
-
-ide.dependsOn ideResources
+    runtime libraries.commons_cli
 
-ideaModule {
-    dependsOn ideResources
-    scopes.RUNTIME.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(new File(ideDir, "resources/test/")))))
+    testCompile libraries.nekohtml
 }
 
-eclipseClasspath {
-    dependsOn ideResources
-    plusConfigurations.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(new File(ideDir, "resources/test/")))))
+evaluationDependsOn(":wrapper")
+task wrapperJar(type: Copy) {
+    from project(":wrapper").executableJar.outputs.files
+    into generatedResourcesDir
 }
+sourceSets.main.output.dir generatedResourcesDir, builtBy: wrapperJar
 
 test {
     exclude 'org/gradle/api/internal/tasks/testing/junit/ATestClass*.*'
     exclude 'org/gradle/api/internal/tasks/testing/junit/ABroken*TestClass*.*'
     jvmArgs '-Xms128m', '-Xmx256m', '-XX:+HeapDumpOnOutOfMemoryError'
 }
+
+useTestFixtures()
+useTestFixtures(sourceSet: "testFixtures")
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
new file mode 100644
index 0000000..849a049
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BuildSrcPluginTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 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.plugins
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import spock.lang.Issue
+
+class BuildSrcPluginTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2001") // when using the daemon
+    def "can use plugin from buildSrc that changes"() {
+        given:
+        if (executer.type == GradleDistributionExecuter.Executer.daemon) {
+            distribution.requireIsolatedDaemons() // make sure we get the same daemon both times
+        }
+
+        buildFile << "apply plugin: 'test-plugin'"
+
+        file("buildSrc/settings.gradle") << "include 'testplugin'"
+        
+        file("buildSrc/build.gradle") << """
+            apply plugin: "groovy"
+            dependencies {
+                runtime project(":testplugin")
+            }
+        """
+                
+        file("buildSrc/testplugin/build.gradle") << """
+            apply plugin: "groovy"
+
+            dependencies {
+                groovy localGroovy()
+                compile gradleApi()
+            }
+        """
+
+        def pluginSource = file("buildSrc/testplugin/src/main/groovy/testplugin/TestPlugin.groovy") << """
+            package testplugin
+            import org.gradle.api.Plugin
+
+            class TestPlugin implements Plugin {
+                void apply(project) {
+                    project.task("echo").doFirst {
+                        println "hello"
+                    }
+                }
+            }
+        """
+
+
+        file("buildSrc/testplugin/src/main/resources/META-INF/gradle-plugins/test-plugin.properties") << """\
+            implementation-class=testplugin.TestPlugin
+        """
+
+        when:
+        succeeds "echo"
+
+        then:
+        output.contains "hello"
+
+        when:
+        pluginSource.write """
+            package testplugin
+            import org.gradle.api.Plugin
+
+            class TestPlugin implements Plugin {
+                void apply(project) {
+                    project.task("echo").doFirst {
+                        println "hello again"
+                    }
+                }
+            }
+        """
+
+        and:
+        succeeds "echo"
+
+        then:
+        output.contains "hello again"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
new file mode 100644
index 0000000..b585370
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * 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.reporting.internal
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class TaskReportContainerIntegTest extends AbstractIntegrationSpec {
+    
+    def task = ":createReports"
+    
+    def setup() {
+        buildFile << """
+            import org.gradle.api.reporting.*
+            import org.gradle.api.reporting.internal.*
+            
+            ext.value = "bar"
+
+            class TestTaskReportContainer extends TaskReportContainer<Report> {
+                TestTaskReportContainer(Task task) {
+                    super(Report, task)
+                    add(TaskGeneratedReport, "file1", Report.OutputType.FILE, task)
+                    add(TaskGeneratedReport, "file2", Report.OutputType.FILE, task)
+                    add(TaskGeneratedReport, "dir1", Report.OutputType.DIRECTORY, task)
+                    add(TaskGeneratedReport, "dir2", Report.OutputType.DIRECTORY, task)
+                }
+            }
+
+            class TestTask extends DefaultTask {
+                @Nested
+                TaskReportContainer reports = project.services.get(org.gradle.api.internal.Instantiator).newInstance(TestTaskReportContainer, this)
+
+                @TaskAction
+                def doStuff() {
+                    reports.enabled.each {
+                         if (it.outputType == Report.OutputType.FILE) {
+                             assert it.destination.parentFile.exists() && it.destination.parentFile.directory
+                             it.destination << project.value
+                         } else {
+                             assert it.destination.exists() && it.destination.directory
+                             new File(it.destination, "file1") << project.value
+                             new File(it.destination, "file2") << project.value
+                         }
+                    }
+                }
+            }
+
+            task createReports(type: TestTask) { task ->
+                inputs.property "foo", { project.value }
+                reports.all {
+                    it.enabled true
+                    destination it.outputType == Report.OutputType.DIRECTORY ? it.name : "\$it.name/file"
+                }
+            }
+        """ 
+    }
+    
+    def "task up to date when no reporting configuration change"() {
+        expect:
+        succeeds(task) && task in nonSkippedTasks
+
+        and:
+        succeeds(task) && task in skippedTasks
+    }
+
+    def "task not up to date when enabled set changes"() {
+        expect:
+        succeeds(task) && task in nonSkippedTasks
+
+        when:
+        buildFile << """
+            createReports.reports.file1.enabled false
+        """
+
+        then:
+        succeeds(task) && task in nonSkippedTasks
+    }
+
+    def "task not up to date when enabled set changes but output files stays the same"() {
+        given:
+        buildFile << """
+            createReports.reports.configure {
+                [dir1, dir2, file2]*.enabled false
+            }
+        """
+
+        expect:
+        succeeds(task) && task in nonSkippedTasks
+
+        and:
+        succeeds(task) && task in skippedTasks
+
+        when:
+        buildFile << """
+            createReports.reports.configure {
+                file1.enabled false
+                file2.enabled false
+                file2.destination file1.destination
+            }
+        """
+
+        then:
+        succeeds(task) && task in nonSkippedTasks
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/JavaExecIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/JavaExecIntegrationTest.groovy
new file mode 100644
index 0000000..68fafb1
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/JavaExecIntegrationTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class JavaExecIntegrationTest extends AbstractIntegrationSpec {
+
+    def setup() {
+        file("src", "main", "java").mkdirs()
+        
+        file("src", "main", "java", "Driver.java").write """
+            package driver;
+
+            import java.io.*;
+
+            public class Driver {
+                public static void main(String[] args) {
+                    try {
+                        FileWriter out = new FileWriter("out.txt");
+                        out.write(args[0]);
+                        out.close();
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        """
+
+
+        buildFile.write """
+            apply plugin: "java"
+
+            task run(type: JavaExec) {
+                classpath = project.files(compileJava)
+                main "driver.Driver"
+                args "1"
+            }
+        """
+    }
+    
+    def "java exec is not incremental by default"() {
+        when:
+        run "run"
+        
+        then:
+        ":run" in nonSkippedTasks
+        
+        when:
+        run "run"
+        
+        then:
+        ":run" in nonSkippedTasks
+    }
+
+    @Issue("GRADLE-1483")
+    def "when the user declares outputs it becomes incremental"() {
+        given:
+        buildFile << """
+            run.outputs.file "out.txt"
+        """
+
+        when:
+        run "run"
+
+        then:
+        ":run" in nonSkippedTasks
+
+        when:
+        run "run"
+
+        then:
+        ":run" in skippedTasks
+        
+        when:
+        file("out.txt").delete()
+        
+        and:
+        run "run"
+        
+        then:
+        ":run" in nonSkippedTasks
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..164ab2a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.groovy.compile
+
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at TargetVersions(['1.6.9', '1.7.10', '1.8.6'])
+class AntForkingGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
+
+    @Override
+    def String compilerConfiguration() {
+        '''
+    tasks.withType(GroovyCompile) {
+        groovyOptions.useAnt = true
+        groovyOptions.fork = true
+    }
+'''
+    }
+
+    @Override
+    String getCompilationFailureMessage() {
+        return "Forked groovyc returned error code: 1"
+    }
+
+    @Override
+    String getCompileErrorOutput() {
+        if (version.startsWith('1.8')) {
+            return output
+        }
+        return errorOutput
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..7014c78
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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.groovy.compile
+
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at TargetVersions(['1.6.9', '1.7.10', '1.8.6'])
+class AntInProcessGroovyCompilerIntegrationTest extends BasicGroovyCompilerIntegrationSpec {
+    def setup() {
+        executer.withForkingExecuter()
+    }
+
+    @Override
+    def String compilerConfiguration() {
+    '''
+    tasks.withType(GroovyCompile) {
+        groovyOptions.useAnt = true
+        groovyOptions.fork = false
+    }
+'''
+    }
+
+    @Override
+    String getCompilationFailureMessage() {
+        return "Compilation Failed"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
new file mode 100644
index 0000000..5019dea
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 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.groovy.compile
+
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+ at TargetVersions(['1.5.8', '1.6.9', '1.7.10', '1.8.6'])
+abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegrationSpec {
+    @Rule TestResources resources = new TestResources()
+
+    def setup() {
+        executer.withArguments("-i")
+    }
+
+    def "badCodeBreaksBuild"() {
+        when:
+        runAndFail("classes")
+
+        then:
+        compileErrorOutput.contains 'unable to resolve class Unknown1'
+        compileErrorOutput.contains 'unable to resolve class Unknown2'
+        failure.assertHasCause(compilationFailureMessage)
+    }
+
+    def "badJavaCodeBreaksBuild"() {
+        when:
+        runAndFail("classes")
+
+        then:
+        compileErrorOutput.contains 'illegal start of type'
+        failure.assertHasCause(compilationFailureMessage)
+    }
+
+    def "canCompileAgainstGroovyClassThatDependsOnExternalClass"() {
+        when:
+        run("test")
+
+        then:
+        noExceptionThrown()
+    }
+
+    @Override
+    protected ExecutionResult run(String... tasks) {
+        tweakBuildFile()
+        return super.run(tasks)
+    }
+
+    @Override
+    protected ExecutionFailure runAndFail(String... tasks) {
+        tweakBuildFile()
+        return super.runAndFail(tasks)
+    }
+
+    private void tweakBuildFile() {
+        buildFile << """
+dependencies { groovy 'org.codehaus.groovy:groovy:$version' }
+"""
+        buildFile << compilerConfiguration()
+
+        println "->> USING BUILD FILE: ${buildFile.text}"
+    }
+
+    abstract String compilerConfiguration()
+
+    String getCompilationFailureMessage() {
+        return "Compilation failed; see the compiler error output for details."
+    }
+
+    String getCompileErrorOutput() {
+        return errorOutput
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..ac93333
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.groovy.compile
+
+class DaemonGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
+
+    @Override
+    def String compilerConfiguration() {
+'''
+    tasks.withType(GroovyCompile) {
+        groovyOptions.useAnt = false
+        groovyOptions.fork = true
+    }
+'''
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
new file mode 100644
index 0000000..f8bdbd2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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.groovy.compile
+
+abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrationSpec {
+    def "canUseBuiltInAstTransform"() {
+        if (version.startsWith('1.5.')) {
+            return
+        }
+
+        when:
+        run("test")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "canUseThirdPartyAstTransform"() {
+        if (version.startsWith('1.5.')) {
+            return
+        }
+
+        when:
+        run("test")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "canUseAstTransformWrittenInGroovy"() {
+        if (version.startsWith('1.5.')) {
+            return
+        }
+
+        when:
+        run("test")
+
+        then:
+        noExceptionThrown()
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..d108739
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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.groovy.compile
+
+class InProcessGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
+
+    def String compilerConfiguration() {
+'''
+    tasks.withType(GroovyCompile) {
+        groovyOptions.useAnt = false
+        groovyOptions.fork = false
+    }
+'''
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
new file mode 100644
index 0000000..1a3aaf3
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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.groovy.compile
+
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.junit.Test
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+class IncrementalGroovyCompileIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void recompilesSourceWhenPropertiesChange() {
+        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava')
+
+        distribution.testFile('build.gradle').text += '''
+            compileGroovy.options.debug = false
+'''
+
+        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava')
+
+        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava', ':compileGroovy')
+    }
+
+    @Test
+    public void recompilesDependentClasses() {
+        executer.withTasks("classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('src/main/groovy/IPerson.groovy').assertIsFile().copyFrom(distribution.testFile('NewIPerson.groovy'))
+
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileGroovy'.");
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
new file mode 100644
index 0000000..129a4a4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.java
+
+import org.gradle.integtests.fixtures.*
+
+class JavaPluginGoodBehaviourTest extends WellBehavedPluginTest {
+    @Override
+    String getMainTask() {
+        return "build"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/AntForkingJavaCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/AntForkingJavaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..6714cbb
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/AntForkingJavaCompilerIntegrationTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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.java.compile
+
+class AntForkingJavaCompilerIntegrationTest extends BasicJavaCompilerIntegrationSpec {
+    def compilerConfiguration() {
+        '''
+compileJava.options.with {
+    useAnt = true
+    fork = true
+}
+'''
+    }
+
+    def logStatement() {
+        "Compiling with Ant javac task"
+    }
+
+    def getCompilerErrorOutput() {
+        return output
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/AntInProcessJavaCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/AntInProcessJavaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..b4c384f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/AntInProcessJavaCompilerIntegrationTest.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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.java.compile
+
+class AntInProcessJavaCompilerIntegrationTest extends BasicJavaCompilerIntegrationSpec {
+    def compilerConfiguration() {
+        '''
+compileJava.options.with {
+    useAnt = true
+    fork = false
+}
+'''
+    }
+
+    def logStatement() {
+        "Compiling with Ant javac task"
+    }
+
+    def getCompilerErrorOutput() {
+        return output
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
new file mode 100644
index 0000000..4e6f105
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2012 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.java.compile
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+abstract class BasicJavaCompilerIntegrationSpec extends AbstractIntegrationSpec {
+    def setup() {
+        executer.withArguments("-i")
+        buildFile << buildScript()
+        buildFile << compilerConfiguration()
+    }
+
+    def compileGoodCode() {
+        given:
+        goodCode()
+
+        expect:
+        succeeds("compileJava")
+        output.contains(logStatement())
+        !errorOutput
+        file("build/classes/main/compile/test/Person.class").exists()
+    }
+
+    def compileBadCodeBreaksTheBuild() {
+        given:
+        badCode()
+
+        expect:
+        fails("compileJava")
+        output.contains(logStatement())
+        compilerErrorOutput.contains("';' expected")
+        file("build/classes/main").assertHasDescendants()
+    }
+
+    def compileBadCodeWithoutFailing() {
+        given:
+        badCode()
+
+        and:
+        buildFile << 'compileJava.options.failOnError = false'
+
+        expect:
+        succeeds("compileJava")
+        output.contains(logStatement())
+        compilerErrorOutput.contains("';' expected")
+        file("build/classes/main").assertHasDescendants()
+    }
+
+    def compileWithSpecifiedEncoding() {
+        given:
+        goodCodeEncodedWith('ISO8859_7')
+
+        and:
+        buildFile << '''
+            apply plugin: 'application'
+            mainClassName = 'Main'
+            compileJava.options.encoding = \'ISO8859_7\'
+'''
+
+        expect:
+        succeeds("run")
+        output.contains(logStatement())
+        !errorOutput
+        file('encoded.out').getText("utf-8") == "\u03b1\u03b2\u03b3"
+    }
+
+    def compilesWithSpecifiedDebugSettings() {
+        given:
+        goodCode()
+
+        when:
+        run("compileJava")
+
+        then:
+        def fullDebug = classFile("build/classes/main/compile/test/Person.class")
+        fullDebug.debugIncludesSourceFile
+        fullDebug.debugIncludesLineNumbers
+        fullDebug.debugIncludesLocalVariables
+
+        when:
+        buildFile << """
+compileJava.options.debugOptions.debugLevel='lines'
+"""
+        run("compileJava")
+
+        then:
+        def linesOnly = classFile("build/classes/main/compile/test/Person.class")
+        !linesOnly.debugIncludesSourceFile
+        linesOnly.debugIncludesLineNumbers
+        !linesOnly.debugIncludesLocalVariables
+
+        when:
+        buildFile << """
+compileJava.options.debug = false
+"""
+        run("compileJava")
+
+        then:
+        def noDebug = classFile("build/classes/main/compile/test/Person.class")
+        !noDebug.debugIncludesSourceFile
+        !noDebug.debugIncludesLineNumbers
+        !noDebug.debugIncludesLocalVariables
+    }
+
+    def getCompilerErrorOutput() {
+        return errorOutput
+    }
+
+    def buildScript() {
+        '''
+apply plugin: "java"
+
+dependencies {
+    compile localGroovy()
+}
+'''
+    }
+
+    abstract compilerConfiguration()
+
+    abstract logStatement()
+
+    def goodCode() {
+        file("src/main/java/compile/test/Person.java") << '''
+package compile.test;
+
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Person {
+    String name;
+    int age;
+
+    void hello() {
+        List<Integer> vars = Arrays.asList(3, 1, 2);
+        DefaultGroovyMethods.max(vars);
+    }
+}'''
+        file("src/main/java/compile/test/Person2.java") << '''
+package compile.test;
+
+class Person2 extends Person {
+}
+'''
+    }
+
+    def goodCodeEncodedWith(String encoding) {
+        def code = '''
+import java.io.FileOutputStream;
+import java.io.File;
+import java.io.OutputStreamWriter;
+
+class Main {
+    public static void main(String[] args) throws Exception {
+        // Some lowercase greek letters
+        String content = "\u03b1\u03b2\u03b3";
+        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File("encoded.out")), "utf-8");
+        writer.write(content);
+        writer.close();
+    }
+}
+'''
+        def file = file("src/main/java/Main.java")
+        file.parentFile.mkdirs()
+        file.withWriter(encoding) { writer ->
+            writer.write(code)
+        }
+
+        // Verify some assumptions: that we've got the correct characters in there, and that we're not using the system encoding
+        assert code.contains(new String(Character.toChars(0x3b1)))
+        assert !Arrays.equals(code.bytes, file.bytes)
+    }
+
+    def badCode() {
+        file("src/main/java/compile/test/Person.java") << '''
+        package compile.fork;
+
+        public class Person {
+            String name;
+            int age;
+
+            void hello() {
+                return nothing
+            }
+        } '''
+    }
+
+    def classFile(String path) {
+        return new ClassFile(file(path))
+    }
+}
+
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/ClassFile.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/ClassFile.groovy
new file mode 100644
index 0000000..4b68225
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/ClassFile.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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.java.compile
+
+import org.objectweb.asm.commons.EmptyVisitor
+import org.objectweb.asm.Label
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.ClassReader
+
+class ClassFile {
+    final File file
+    boolean hasSourceFile
+    boolean hasLineNumbers
+    boolean hasLocalVars
+
+    ClassFile(File file) {
+        this.file = file
+        def methodVisitor = new EmptyVisitor() {
+            @Override
+            void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
+                hasLocalVars = true
+            }
+
+            @Override
+            void visitLineNumber(int line, Label start) {
+                hasLineNumbers = true
+            }
+        }
+        def visitor = new EmptyVisitor() {
+            @Override
+            MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+                return methodVisitor
+            }
+
+            @Override
+            void visitSource(String source, String debug) {
+                hasSourceFile = true
+            }
+        }
+        new ClassReader(file.bytes).accept(visitor, 0)
+    }
+
+    boolean getDebugIncludesSourceFile() {
+        return hasSourceFile
+    }
+
+    boolean getDebugIncludesLineNumbers() {
+        return hasLineNumbers
+    }
+
+    boolean getDebugIncludesLocalVariables() {
+        return hasLocalVars
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/CommandLineJavaCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/CommandLineJavaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..3826eeb
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/CommandLineJavaCompilerIntegrationTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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.java.compile
+
+import org.gradle.internal.jvm.Jvm
+
+import spock.lang.IgnoreIf
+import org.gradle.util.TextUtil
+
+ at IgnoreIf({ !Jvm.current().getExecutable("javac").exists() })
+class CommandLineJavaCompilerIntegrationTest extends JavaCompilerIntegrationSpec {
+    def compilerConfiguration() {
+        def executable = TextUtil.escapeString(Jvm.current().getExecutable("javac"))
+
+        """
+compileJava.options.with {
+    useAnt = false
+    fork = true
+    forkOptions.executable = "$executable"
+}
+"""
+    }
+
+    def logStatement() {
+        "Compiling with Java command line compiler"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/InProcessJavaCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/InProcessJavaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..c5daf67
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/InProcessJavaCompilerIntegrationTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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.java.compile
+
+class InProcessJavaCompilerIntegrationTest extends JavaCompilerIntegrationSpec {
+    def compilerConfiguration() {
+        '''
+compileJava.options.with {
+    useAnt = false
+    fork = false
+}
+'''
+    }
+
+    def logStatement() {
+        "Java compiler API"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
new file mode 100644
index 0000000..5f00d71
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * 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.java.compile
+
+import org.junit.Rule
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Test
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+class IncrementalJavaCompileIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void recompilesSourceWhenPropertiesChange() {
+        executer.withTasks('compileJava').run().assertTasksSkipped()
+
+        distribution.testFile('build.gradle').text += '''
+            sourceCompatibility = 1.4
+'''
+
+        executer.withTasks('compileJava').run().assertTasksSkipped()
+
+        distribution.testFile('build.gradle').text += '''
+            compileJava.options.debug = false
+'''
+
+        executer.withTasks('compileJava').run().assertTasksSkipped()
+
+        executer.withTasks('compileJava').run().assertTasksSkipped(':compileJava')
+    }
+
+    @Test
+    public void recompilesDependentClasses() {
+        executer.withTasks("classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
+        
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileJava'.");
+    }
+
+    @Test
+    public void recompilesDependentClassesAcrossProjectBoundaries() {
+        executer.withTasks("app:classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('lib/src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
+
+        ExecutionFailure failure = executer.withTasks("app:classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':app:compileJava'.");
+    }
+
+    @Test
+    public void recompilesDependentClassesWhenUsingAntDepend() {
+        distribution.testFile("build.gradle").writelns(
+                "apply plugin: 'java'",
+                "compileJava.options.depend()"
+        );
+        writeShortInterface();
+        writeTestClass();
+
+        executer.withTasks("classes").run();
+
+        // file system time stamp may not see change without this wait
+        Thread.sleep(1000L);
+
+        // Update interface, compile should fail because depend deletes old class
+        writeLongInterface();
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileJava'.");
+
+        // assert that dependency caching is on
+        distribution.testFile("build/dependency-cache/dependencies.txt").assertExists();
+    }
+
+    private void writeShortInterface() {
+        distribution.testFile("src/main/java/IPerson.java").writelns(
+                "interface IPerson {",
+                "    String getName();",
+                "}"
+        );
+    }
+
+    private void writeLongInterface() {
+        distribution.testFile("src/main/java/IPerson.java").writelns(
+                "interface IPerson {",
+                "    String getName();",
+                "    String getAddress();",
+                "}"
+        );
+    }
+
+    private void writeTestClass() {
+        distribution.testFile("src/main/java/Person.java").writelns(
+                "public class Person implements IPerson {",
+                "    private final String name = \"never changes\";",
+                "    public String getName() {",
+                "        return name;\n" +
+                "    }",
+                "}"
+        );
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
new file mode 100644
index 0000000..763b147
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/JavaCompilerIntegrationSpec.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 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.java.compile
+
+abstract class JavaCompilerIntegrationSpec extends BasicJavaCompilerIntegrationSpec {
+    def compileWithLongClasspath() {
+        given:
+        goodCode()
+
+        and:
+        buildFile << '''
+            dependencies {
+                compile files((1..999).collect { "$projectDir/lib/library${it}.jar" })
+            }
+        '''
+
+        expect:
+        succeeds("compileJava")
+        output.contains(logStatement())
+        !errorOutput
+        file("build/classes/main/compile/test/Person.class").exists()
+    }
+
+    def compileWithCustomHeapSettings() {
+        given:
+        goodCode()
+
+        and:
+        buildFile << '''
+            compileJava.options.forkOptions.with {
+                memoryInitialSize = '64m'
+                memoryMaximumSize = '128m'
+            }
+'''
+
+        expect:
+        succeeds("compileJava")
+        output.contains(logStatement())
+        !errorOutput
+        file("build/classes/main/compile/test/Person.class").exists()
+        // couldn't find a good way to verify that heap settings take effect
+    }
+
+    def listSourceFiles() {
+        given:
+        goodCode()
+
+        and:
+        buildFile << 'compileJava.options.listFiles = true'
+
+        expect:
+        succeeds("compileJava")
+        output.contains(new File("src/main/java/compile/test/Person.java").toString())
+        output.contains(new File("src/main/java/compile/test/Person2.java").toString())
+        output.contains(logStatement())
+        !errorOutput
+        file("build/classes/main/compile/test/Person.class").exists()
+        file("build/classes/main/compile/test/Person2.class").exists()
+    }
+
+    def nonJavaSourceFilesAreAutomaticallyExcluded() {
+        given:
+        goodCode()
+
+        and:
+        file('src/main/java/resource.txt').createFile()
+        buildFile << 'compileJava.source += files("src/main/java/resource.txt")'
+
+        expect:
+        succeeds("compileJava")
+        file("build/classes/main/compile/test/Person.class").exists()
+        file("build/classes/main/compile/test/Person2.class").exists()
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/daemon/DaemonJavaCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/daemon/DaemonJavaCompilerIntegrationTest.groovy
new file mode 100644
index 0000000..692f8d2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/daemon/DaemonJavaCompilerIntegrationTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.java.compile.daemon
+
+import org.gradle.java.compile.JavaCompilerIntegrationSpec
+
+class DaemonJavaCompilerIntegrationTest extends JavaCompilerIntegrationSpec {
+    def compilerConfiguration() {
+        '''
+compileJava.options.with {
+    useAnt = false
+    fork = true
+}
+'''
+    }
+
+    def logStatement() {
+        "in compiler daemon"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
new file mode 100644
index 0000000..4b74fac
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.javadoc
+
+import org.junit.Rule
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class JavadocIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TestResources testResources = new TestResources()
+
+    @Issue("GRADLE-1563")
+    def handlesTagsAndTaglets() {
+        when:
+        run("javadoc")
+
+        then:
+        def javadoc = testResources.dir.file("build/docs/javadoc/Person.html")
+        javadoc.text =~ /(?ms)This is the Person class.*Author.*author value.*Deprecated.*deprecated value.*Custom Tag.*custom tag value/
+        // we can't currently control the order between tags and taglets (limitation on our side)
+        javadoc.text =~ /(?ms)Custom Taglet.*custom taglet value/
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/InterruptedTestThreadIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/InterruptedTestThreadIntegrationTest.groovy
new file mode 100644
index 0000000..0e0cc8a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/InterruptedTestThreadIntegrationTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.testing
+
+import org.gradle.integtests.fixtures.*
+
+import spock.lang.*
+
+ at Issue("http://issues.gradle.org/browse/GRADLE-1948")
+class InterruptedTestThreadIntegrationTest extends AbstractIntegrationSpec {
+
+    @Timeout(30)
+    def "test interrupting its own thread does not kill test execution"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile "junit:junit:4.8.2" }
+        """
+        
+        and:
+        file("src/test/java/SomeTest.java") << """
+            import org.junit.*;
+
+            public class SomeTest {
+                @Test public void foo() {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        """
+        
+        when:
+        run "test"
+        
+        then:
+        ":test" in nonSkippedTasks
+    }  
+ 
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
new file mode 100644
index 0000000..4d6e785
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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.testing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+
+class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final TestResources resources = new TestResources()
+
+    def canRunTestsWithCustomSystemClassLoader() {
+        when:
+        run 'test'
+
+        then:
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.JUnitTest')
+        result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
+    }
+
+    def canRunTestsWithCustomSystemClassLoaderAndJavaAgent() {
+        when:
+        run 'test'
+
+        then:
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.JUnitTest')
+        result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
+    }
+
+    def canRunTestsWithCustomSecurityManager() {
+        when:
+        run 'test'
+
+        then:
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.JUnitTest')
+        result.testClass('org.gradle.JUnitTest').assertTestPassed('mySecurityManagerIsUsed')
+    }
+
+    def canRunTestsWithJMockitLoadedWithJavaAgent() {
+        when:
+        run 'test'
+
+        then:
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.JMockitTest')
+        result.testClass('org.gradle.JMockitTest').assertTestPassed('testOk')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
new file mode 100644
index 0000000..fe6465b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2011 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.testing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Issue
+
+ at Issue("GRADLE-1009")
+public class TestOutputListenerIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    def "can use standard output listener for tests"() {
+        given:
+        def test = file("src/test/java/SomeTest.java")
+        test << """
+import org.junit.*;
+
+public class SomeTest {
+    @Test public void showsOutputWhenPassing() {
+        System.out.println("out passing");
+        System.err.println("err passing");
+        Assert.assertTrue(true);
+    }
+
+    @Test public void showsOutputWhenFailing() {
+        System.out.println("out failing");
+        System.err.println("err failing");
+        Assert.assertTrue(false);
+    }
+}
+"""
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile "junit:junit:4.8.2" }
+
+test.addTestOutputListener(new VerboseOutputListener(logger: project.logger))
+
+def removeMe = new RemoveMeListener()
+test.addTestOutputListener(removeMe)
+test.removeTestOutputListener(removeMe)
+
+class VerboseOutputListener implements TestOutputListener {
+
+    def logger
+
+    public void onOutput(TestDescriptor descriptor, TestOutputEvent event) {
+        logger.lifecycle(descriptor.toString() + " " + event.destination + " " + event.message);
+    }
+}
+
+class RemoveMeListener implements TestOutputListener {
+    public void onOutput(TestDescriptor descriptor, TestOutputEvent event) {
+        println "remove me!"
+    }
+}
+"""
+
+        when:
+        def failure = executer.withTasks('test').runWithFailure()
+
+        then:
+        failure.output.contains('test showsOutputWhenPassing(SomeTest) StdOut out passing')
+        failure.output.contains('test showsOutputWhenFailing(SomeTest) StdOut out failing')
+        failure.output.contains('test showsOutputWhenPassing(SomeTest) StdErr err passing')
+        failure.output.contains('test showsOutputWhenFailing(SomeTest) StdErr err failing')
+
+        !failure.output.contains("remove me!")
+    }
+
+    @Test
+    def "can register output listener at gradle level and using onOutput method"() {
+        given:
+        def test = file("src/test/java/SomeTest.java")
+        test << """
+import org.junit.*;
+
+public class SomeTest {
+    @Test public void foo() {
+        System.out.println("message from foo");
+    }
+}
+"""
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile "junit:junit:4.8.2" }
+
+test.onOutput { descriptor, event ->
+    logger.lifecycle("first: " + event.message)
+}
+
+gradle.addListener(new VerboseOutputListener(logger: project.logger))
+
+class VerboseOutputListener implements TestOutputListener {
+
+    def logger
+
+    public void onOutput(TestDescriptor descriptor, TestOutputEvent event) {
+        logger.lifecycle("second: " + event.message);
+    }
+}
+"""
+
+        when:
+        def result = executer.withTasks('test').run()
+
+        then:
+        result.output.contains('first: message from foo')
+        result.output.contains('second: message from foo')
+    }
+
+    @Test
+    def "shows standard streams configured via closure"() {
+        given:
+        def test = file("src/test/java/SomeTest.java")
+        test << """
+import org.junit.*;
+
+public class SomeTest {
+    @Test public void foo() {
+        System.out.println("message from foo");
+    }
+}
+"""
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile "junit:junit:4.8.2" }
+
+test.testLogging {
+    showStandardStreams = true
+}
+"""
+
+        when:
+        def result = executer.withTasks('test').withArguments('-i').run()
+
+        then:
+        result.output.contains('message from foo')
+    }
+
+    @Test
+    def "shows standard stream also for testNG"() {
+        given:
+        def test = file("src/test/java/SomeTest.java")
+        test << """
+import org.testng.*;
+import org.testng.annotations.*;
+
+public class SomeTest {
+    @Test public void foo() {
+        System.out.println("output from foo");
+        System.err.println("error from foo");
+    }
+}
+"""
+
+        def buildFile = file('build.gradle')
+        buildFile << """
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+
+test {
+    useTestNG()
+    testLogging.showStandardStreams = true
+}
+"""
+        when: "run without '-i'"
+        def result = executer.setAllowExtraLogging(false).withTasks('test').run()
+        then:
+        !result.output.contains('output from foo')
+
+        when: "run with '-i'"
+        result = executer.withTasks('cleanTest', 'test').withArguments('-i').run()
+
+        then:
+        result.output.contains('output from foo')
+        result.error.contains('error from foo')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
new file mode 100755
index 0000000..ad05c10
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
@@ -0,0 +1,422 @@
+/*
+ * Copyright 2011 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.testing.junit
+
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import org.gradle.integtests.fixtures.*
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+public class JUnitIntegrationTest extends AbstractIntegrationTest {
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void executesTestsInCorrectEnvironment() {
+        executer.withTasks('build').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.OtherTest')
+
+        result.testClass('org.gradle.OkTest').assertTestPassed('ok')
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('This is test stdout'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('no EOL'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('class loaded'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('test constructed'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('stdout from another thread'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('This is test stderr'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('this is a warning'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('sys out from another test method'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('sys err from another test method'))
+
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('before class out'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('before class err'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('after class out'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('after class err'))
+
+        result.testClass('org.gradle.OtherTest').assertTestPassed('ok')
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('This is other stdout'))
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('other class loaded'))
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('other test constructed'))
+        result.testClass('org.gradle.OtherTest').assertStderr(containsString('This is other stderr'))
+        result.testClass('org.gradle.OtherTest').assertStderr(containsString('this is another warning'))
+    }
+
+    @Test
+    public void suitesOutputIsVisible() {
+        executer.withTasks('test').withArguments('-i').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.ASuite')
+
+        result.testClass('org.gradle.ASuite').assertStdout(containsString('suite class loaded'))
+        result.testClass('org.gradle.ASuite').assertStderr(containsString('This is test stderr'))
+        result.testClass('org.gradle.ASuite').assertStdout(containsString('sys out from another test method'))
+        result.testClass('org.gradle.ASuite').assertStderr(containsString('sys err from another test method'))
+        result.testClass('org.gradle.ASuite').assertStdout(containsString('This is other stdout'))
+        result.testClass('org.gradle.ASuite').assertStdout(containsString('before suite class out'))
+        result.testClass('org.gradle.ASuite').assertStderr(containsString('before suite class err'))
+        result.testClass('org.gradle.ASuite').assertStdout(containsString('after suite class out'))
+        result.testClass('org.gradle.ASuite').assertStderr(containsString('after suite class err'))
+    }
+
+    @Test
+    public void canRunMixOfJunit3And4Tests() {
+        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
+        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
+        executer.withTasks('check').run()
+
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest')
+        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
+        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
+        result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
+        result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
+        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
+        result.testClass('org.gradle.IgnoredTest').assertTestsExecuted()
+    }
+
+    @Test
+    public void canRunTestsUsingJUnit3() {
+        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
+        executer.withTasks('check').run()
+
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.Junit3Test')
+        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
+        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
+    }
+
+    @Test
+    public void canRunTestsUsingJUnit4_4() {
+        resources.maybeCopy('JUnitIntegrationTest/junit3Tests')
+        resources.maybeCopy('JUnitIntegrationTest/junit4Tests')
+        resources.maybeCopy('JUnitIntegrationTest/junit4_4Tests')
+        executer.withTasks('check').run()
+
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest')
+        result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
+        result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
+        result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
+        result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
+        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
+        result.testClass('org.gradle.IgnoredTest').assertTestsExecuted()
+    }
+
+    @Test
+    public void reportsAndBreaksBuildWhenTestFails() {
+        ExecutionFailure failure = executer.withTasks('build').runWithFailure();
+
+        failure.assertHasDescription("Execution failed for task ':test'.");
+        failure.assertThatCause(startsWith('There were failing tests.'));
+
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenTest FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenBefore FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenAfter FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenBeforeAndAfter FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenBeforeClass FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenAfterClass FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenConstructor FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.BrokenException FAILED');
+        assert containsLine(failure.getError(), 'Test org.gradle.Unloadable FAILED');
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted(
+                'org.gradle.ClassWithBrokenRunner',
+                'org.gradle.BrokenTest',
+                'org.gradle.BrokenBefore',
+                'org.gradle.BrokenAfter',
+                'org.gradle.BrokenBeforeClass',
+                'org.gradle.BrokenAfterClass',
+                'org.gradle.BrokenBeforeAndAfter',
+                'org.gradle.BrokenConstructor',
+                'org.gradle.BrokenException',
+                'org.gradle.Unloadable')
+        result.testClass('org.gradle.ClassWithBrokenRunner').assertTestFailed('initializationError', equalTo('java.lang.UnsupportedOperationException: broken'))
+        result.testClass('org.gradle.BrokenTest').assertTestFailed('failure', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenTest').assertTestFailed('broken', equalTo('java.lang.IllegalStateException'))
+        result.testClass('org.gradle.BrokenBeforeClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenAfterClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenBefore').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenBeforeAndAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: before failed'), equalTo('java.lang.AssertionError: after failed'))
+        result.testClass('org.gradle.BrokenConstructor').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenException').assertTestFailed('broken', startsWith('Could not determine failure message for exception of type org.gradle.BrokenException$BrokenRuntimeException: '))
+        result.testClass('org.gradle.Unloadable').assertTestFailed('initializationError', equalTo('java.lang.AssertionError: failed'))
+    }
+
+    @Test
+    public void canRunSingleTests() {
+        executer.withTasks('test').withArguments('-Dtest.single=Ok2').run()
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('Ok2')
+
+        executer.withTasks('cleanTest', 'test').withArguments('-Dtest.single=Ok').run()
+        result.assertTestClassesExecuted('Ok', 'Ok2')
+
+        def failure = executer.withTasks('test').withArguments('-Dtest.single=DoesNotMatchAClass').runWithFailure()
+        failure.assertHasCause('Could not find matching test for pattern: DoesNotMatchAClass')
+
+        failure = executer.withTasks('test').withArguments('-Dtest.single=NotATest').runWithFailure()
+        failure.assertHasCause('Could not find matching test for pattern: NotATest')
+    }
+
+    @Test
+    public void canUseTestSuperClassesFromAnotherProject() {
+        testDir.file('settings.gradle').write("include 'a', 'b'");
+        testDir.file('b/build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { compile 'junit:junit:4.7' }
+        '''
+        testDir.file('b/src/main/java/org/gradle/AbstractTest.java') << '''
+            package org.gradle;
+            public abstract class AbstractTest {
+                @org.junit.Test public void ok() { }
+            }
+        '''
+        TestFile buildFile = testDir.file('a/build.gradle');
+        buildFile << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile project(':b') }
+        '''
+        testDir.file('a/src/test/java/org/gradle/SomeTest.java') << '''
+            package org.gradle;
+            public class SomeTest extends AbstractTest {
+            }
+        '''
+
+        executer.withTasks('a:test').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir.file('a'))
+        result.assertTestClassesExecuted('org.gradle.SomeTest')
+        result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
+    }
+
+    @Test
+    public void canExcludeSuperClassesFromExecution() {
+        TestFile buildFile = testDir.file('build.gradle');
+        buildFile << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.7' }
+            test { exclude '**/BaseTest.*' }
+        '''
+        testDir.file('src/test/java/org/gradle/BaseTest.java') << '''
+            package org.gradle;
+            public class BaseTest {
+                @org.junit.Test public void ok() { }
+            }
+        '''
+        testDir.file('src/test/java/org/gradle/SomeTest.java') << '''
+            package org.gradle;
+            public class SomeTest extends BaseTest {
+            }
+        '''
+
+        executer.withTasks('test').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.SomeTest')
+        result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
+    }
+
+    @Test
+    public void detectsTestClasses() {
+        executer.withTasks('test').run()
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.EmptyRunWithSubclass', 'org.gradle.TestsOnInner', 'org.gradle.TestsOnInner$SomeInner')
+        result.testClass('org.gradle.EmptyRunWithSubclass').assertTestsExecuted('ok')
+        result.testClass('org.gradle.EmptyRunWithSubclass').assertTestPassed('ok')
+        result.testClass('org.gradle.TestsOnInner').assertTestPassed('ok')
+        result.testClass('org.gradle.TestsOnInner$SomeInner').assertTestPassed('ok')
+    }
+
+    @Test
+    public void runsAllTestsInTheSameForkedJvm() {
+        testDir.file('build.gradle').writelns(
+                "apply plugin: 'java'",
+                "repositories { mavenCentral() }",
+                "dependencies { compile 'junit:junit:4.7' }"
+        );
+        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
+                "package org.gradle;",
+                "public abstract class AbstractTest {",
+                "    @org.junit.Test public void ok() {",
+                "        long time = java.lang.management.ManagementFactory.getRuntimeMXBean().getStartTime();",
+                "        System.out.println(String.format(\"VM START TIME = %s\", time));",
+                "    }",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest extends AbstractTest {",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest2 extends AbstractTest {",
+                "}");
+
+        executer.withTasks('test').run();
+
+        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
+        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
+        results1.assertIsFile();
+        results2.assertIsFile();
+        assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), equalTo(results2.linesThat(containsString('VM START TIME =')).get(0)));
+    }
+
+    @Test
+    public void canSpecifyMaximumNumberOfTestClassesToExecuteInAForkedJvm() {
+        testDir.file('build.gradle').writelns(
+                "apply plugin: 'java'",
+                "repositories { mavenCentral() }",
+                "dependencies { compile 'junit:junit:4.7' }",
+                "test.forkEvery = 1"
+        );
+        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
+                "package org.gradle;",
+                "public abstract class AbstractTest {",
+                "    @org.junit.Test public void ok() {",
+                "        long time = java.lang.management.ManagementFactory.getRuntimeMXBean().getStartTime();",
+                "        System.out.println(String.format(\"VM START TIME = %s\", time));",
+                "    }",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest extends AbstractTest {",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest2 extends AbstractTest {",
+                "}");
+
+        executer.withTasks('test').run();
+
+        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
+        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
+        results1.assertIsFile();
+        results2.assertIsFile();
+        assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), not(equalTo(results2.linesThat(
+                containsString('VM START TIME =')).get(0))));
+    }
+
+    @Test
+    public void canListenForTestResults() {
+        testDir.file('src/main/java/AppException.java').writelns(
+                "public class AppException extends Exception { }"
+        );
+
+        testDir.file('src/test/java/SomeTest.java').writelns(
+                "public class SomeTest {",
+                "@org.junit.Test public void fail() { org.junit.Assert.fail(\"message\"); }",
+                "@org.junit.Test public void knownError() { throw new RuntimeException(\"message\"); }",
+                "@org.junit.Test public void unknownError() throws AppException { throw new AppException(); }",
+                "}"
+        );
+        testDir.file('src/test/java/SomeOtherTest.java').writelns(
+                "public class SomeOtherTest {",
+                "@org.junit.Test public void pass() { }",
+                "}"
+        );
+
+        testDir.file('build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.7' }
+            def listener = new TestListenerImpl()
+            test.addTestListener(listener)
+            test.ignoreFailures = true
+            class TestListenerImpl implements TestListener {
+                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
+                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name] [$result.resultType] [$result.testCount]" }
+                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
+                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.resultType] [$result.testCount] [$result.exception]" }
+            }
+        '''
+
+        ExecutionResult result = executer.withTasks("test").run();
+        assert containsLine(result.getOutput(), "START [tests] []");
+        assert containsLine(result.getOutput(), "FINISH [tests] [] [FAILURE] [4]");
+
+        assert containsLine(result.getOutput(), "START [test process 'Gradle Worker 1'] [Gradle Worker 1]");
+        assert containsLine(result.getOutput(), "FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1] [FAILURE] [4]");
+
+        assert containsLine(result.getOutput(), "START [test class SomeOtherTest] [SomeOtherTest]");
+        assert containsLine(result.getOutput(), "FINISH [test class SomeOtherTest] [SomeOtherTest] [SUCCESS] [1]");
+        assert containsLine(result.getOutput(), "START [test pass(SomeOtherTest)] [pass]");
+        assert containsLine(result.getOutput(), "FINISH [test pass(SomeOtherTest)] [pass] [SUCCESS] [1] [null]");
+
+        assert containsLine(result.getOutput(), "START [test class SomeTest] [SomeTest]");
+        assert containsLine(result.getOutput(), "FINISH [test class SomeTest] [SomeTest] [FAILURE] [3]");
+        assert containsLine(result.getOutput(), "START [test fail(SomeTest)] [fail]");
+        assert containsLine(result.getOutput(), "FINISH [test fail(SomeTest)] [fail] [FAILURE] [1] [java.lang.AssertionError: message]");
+        assert containsLine(result.getOutput(), "START [test knownError(SomeTest)] [knownError]");
+        assert containsLine(result.getOutput(), "FINISH [test knownError(SomeTest)] [knownError] [FAILURE] [1] [java.lang.RuntimeException: message]");
+        assert containsLine(result.getOutput(), "START [test unknownError(SomeTest)] [unknownError]");
+        assert containsLine(result.getOutput(), "FINISH [test unknownError(SomeTest)] [unknownError] [FAILURE] [1] [AppException: null]");
+    }
+
+    @Test
+    public void canListenForTestResultsWhenJUnit3IsUsed() {
+        testDir.file('src/test/java/SomeTest.java').writelns(
+                "public class SomeTest extends junit.framework.TestCase {",
+                "public void testPass() { }",
+                "public void testFail() { junit.framework.Assert.fail(\"message\"); }",
+                "public void testError() { throw new RuntimeException(\"message\"); }",
+                "}"
+        );
+
+        testDir.file('build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:3.8' }
+            def listener = new TestListenerImpl()
+            test.addTestListener(listener)
+            test.ignoreFailures = true
+            class TestListenerImpl implements TestListener {
+                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
+                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name]" }
+                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
+                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.exception]" }
+            }
+        '''
+
+        ExecutionResult result = executer.withTasks("test").run();
+        assert containsLine(result.getOutput(), "START [test class SomeTest] [SomeTest]");
+        assert containsLine(result.getOutput(), "FINISH [test class SomeTest] [SomeTest]");
+        assert containsLine(result.getOutput(), "START [test testPass(SomeTest)] [testPass]");
+        assert containsLine(result.getOutput(), "FINISH [test testPass(SomeTest)] [testPass] [null]");
+        assert containsLine(result.getOutput(), "START [test testFail(SomeTest)] [testFail]");
+        assert containsLine(result.getOutput(), "FINISH [test testFail(SomeTest)] [testFail] [junit.framework.AssertionFailedError: message]");
+        assert containsLine(result.getOutput(), "START [test testError(SomeTest)] [testError]");
+        assert containsLine(result.getOutput(), "FINISH [test testError(SomeTest)] [testError] [java.lang.RuntimeException: message]");
+    }
+
+    @Test
+    public void canHaveMultipleTestTaskInstances() {
+        executer.withTasks('check').run()
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.Test1', 'org.gradle.Test2')
+        result.testClass('org.gradle.Test1').assertTestPassed('ok')
+        result.testClass('org.gradle.Test2').assertTestPassed('ok')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
new file mode 100644
index 0000000..9f97603
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.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.testing.testng
+
+import org.junit.Rule
+import org.junit.Test
+import org.gradle.integtests.fixtures.*
+
+/**
+ * @author Tom Eyckmans
+ */
+public class SampleTestNGIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample()
+
+    @Test @UsesSample('testng/suitexmlbuilder')
+    public void suiteXmlBuilder() {
+        executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
+
+        def result = new TestNGExecutionResult(sample.dir)
+        result.assertTestClassesExecuted('org.gradle.testng.UserImplTest')
+        result.testClass('org.gradle.testng.UserImplTest').assertTestsExecuted('testOkFirstName')
+        result.testClass('org.gradle.testng.UserImplTest').assertTestPassed('testOkFirstName')
+    }
+
+    @Test @UsesSample('testng/java-jdk14-passing')
+    public void javaJdk14Passing() {
+        executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
+
+        def result = new TestNGExecutionResult(sample.dir)
+        result.assertTestClassesExecuted('org.gradle.OkTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+    }
+    
+    @Test @UsesSample('testng/java-jdk15-passing')
+    public void javaJdk15Passing() {
+        executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
+
+        def result = new TestNGExecutionResult(sample.dir)
+        result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.ConcreteTest', 'org.gradle.SuiteSetup', 'org.gradle.SuiteCleanup', 'org.gradle.TestSetup', 'org.gradle.TestCleanup')
+        result.testClass('org.gradle.OkTest').assertTestsExecuted('passingTest', 'expectedFailTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('expectedFailTest')
+        result.testClass('org.gradle.ConcreteTest').assertTestsExecuted('ok', 'alsoOk')
+        result.testClass('org.gradle.ConcreteTest').assertTestPassed('ok')
+        result.testClass('org.gradle.ConcreteTest').assertTestPassed('alsoOk')
+        result.testClass('org.gradle.SuiteSetup').assertConfigMethodPassed('setupSuite')
+        result.testClass('org.gradle.SuiteCleanup').assertConfigMethodPassed('cleanupSuite')
+        result.testClass('org.gradle.TestSetup').assertConfigMethodPassed('setupTest')
+        result.testClass('org.gradle.TestCleanup').assertConfigMethodPassed('cleanupTest')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationProject.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationProject.groovy
new file mode 100644
index 0000000..e9d9a53
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationProject.groovy
@@ -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.testing.testng
+
+import org.gradle.integtests.fixtures.TestNGExecutionResult
+
+/**
+ * @author Tom Eyckmans
+ */
+
+public class TestNGIntegrationProject {
+    String name
+    boolean expectFailure
+    Closure assertClosure
+
+    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, null, assertClosure)
+    }
+
+    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, nameSuffix, assertClosure)
+    }
+
+    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, null, assertClosure)
+    }
+
+    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, nameSuffix, assertClosure)
+    }
+
+    public TestNGIntegrationProject(String name, boolean expectFailure, String nameSuffix, assertClosure)
+    {
+        if ( nameSuffix == null ) {
+            this.name = name
+        } else {
+            this.name = name + nameSuffix
+        }
+        this.expectFailure = expectFailure
+        this.assertClosure = assertClosure
+    }
+
+    void doAssert(projectDir, result) {
+        if (assertClosure.maximumNumberOfParameters == 3) {
+            assertClosure(name, projectDir, new TestNGExecutionResult(projectDir))
+        } else {
+            assertClosure(name, projectDir, new TestNGExecutionResult(projectDir), result)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
new file mode 100644
index 0000000..37de851
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
@@ -0,0 +1,157 @@
+/*
+ * 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.testing.testng
+
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Issue
+import org.gradle.integtests.fixtures.*
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Tom Eyckmans
+ */
+class TestNGIntegrationTest {
+    @Rule public GradleDistribution dist = new GradleDistribution()
+    @Rule public GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public TestResources resources = new TestResources()
+
+    @Test
+    void executesTestsInCorrectEnvironment() {
+        ExecutionResult result = executer.withTasks('test').run();
+
+        assertThat(result.output, not(containsString('stdout')))
+        assertThat(result.error, not(containsString('stderr')))
+        assertThat(result.error, not(containsString('a warning')))
+
+        new TestNGExecutionResult(dist.testDir).testClass('org.gradle.OkTest').assertTestPassed('ok')
+    }
+
+    @Test
+    void canListenForTestResults() {
+        ExecutionResult result = executer.withTasks("test").run();
+
+        assert containsLine(result.getOutput(), "START [tests] []");
+        assert containsLine(result.getOutput(), "FINISH [tests] []");
+        assert containsLine(result.getOutput(), "START [test process 'Gradle Worker 1'] [Gradle Worker 1]");
+        assert containsLine(result.getOutput(), "FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1]");
+        assert containsLine(result.getOutput(), "START [test 'Gradle test'] [Gradle test]");
+        assert containsLine(result.getOutput(), "FINISH [test 'Gradle test'] [Gradle test]");
+        assert containsLine(result.getOutput(), "START [test method pass(SomeTest)] [pass]");
+        assert containsLine(result.getOutput(), "FINISH [test method pass(SomeTest)] [pass] [null]");
+        assert containsLine(result.getOutput(), "START [test method fail(SomeTest)] [fail]");
+        assert containsLine(result.getOutput(), "FINISH [test method fail(SomeTest)] [fail] [java.lang.AssertionError]");
+        assert containsLine(result.getOutput(), "START [test method knownError(SomeTest)] [knownError]");
+        assert containsLine(result.getOutput(), "FINISH [test method knownError(SomeTest)] [knownError] [java.lang.RuntimeException: message]");
+        assert containsLine(result.getOutput(), "START [test method unknownError(SomeTest)] [unknownError]");
+        assert containsLine(result.getOutput(), "FINISH [test method unknownError(SomeTest)] [unknownError] [AppException: null]");
+    }
+
+    @Test
+    void groovyJdk15Failing() {
+        executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
+
+        def result = new TestNGExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('org.gradle.BadTest')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+    }
+
+    @Test
+    void groovyJdk15Passing() {
+        executer.withTasks("test").run()
+
+        def result = new TestNGExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('org.gradle.OkTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+    }
+
+    @Test
+    void javaJdk14Failing() {
+        executer.withTasks("test").runWithFailure().assertThatCause(startsWith('There were failing tests'))
+
+        def result = new TestNGExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('org.gradle.BadTest')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+    }
+
+    @Issue("GRADLE-1822")
+    @Test
+    void javaJdk15Failing() {
+        doJavaJdk15Failing("5.14.10")
+        doJavaJdk15Failing("6.3.1")
+    }
+
+    private void doJavaJdk15Failing(String testNGVersion) {
+        def execution = executer.withTasks("test").withArguments("-PtestNGVersion=$testNGVersion").runWithFailure().assertThatCause(startsWith('There were failing tests'))
+
+        def result = new TestNGExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('org.gradle.BadTest', 'org.gradle.TestWithBrokenSetup', 'org.gradle.BrokenAfterSuite', 'org.gradle.TestWithBrokenMethodDependency')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+        result.testClass('org.gradle.TestWithBrokenSetup').assertConfigMethodFailed('setup')
+        result.testClass('org.gradle.BrokenAfterSuite').assertConfigMethodFailed('cleanup')
+        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('broken'))
+        result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestSkipped('okTest')
+        assertThat(execution.error, containsString('Test org.gradle.BadTest FAILED'))
+        assertThat(execution.error, containsString('Test org.gradle.TestWithBrokenSetup FAILED'))
+        assertThat(execution.error, containsString('Test org.gradle.BrokenAfterSuite FAILED'))
+        assertThat(execution.error, containsString('Test org.gradle.TestWithBrokenMethodDependency FAILED'))
+    }
+
+    @Issue("GRADLE-1532")
+    @Test
+    void supportsThreadPoolSize() {
+        dist.testDir.file('src/test/java/SomeTest.java') << """
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class SomeTest {
+	@Test(invocationCount = 2, threadPoolSize = 2)
+	public void someTest() {
+		Assert.assertTrue(true);
+	}
+}
+"""
+
+        dist.testDir.file("build.gradle") << """
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+    mavenLocal()
+}
+
+dependencies {
+	testCompile 'org.testng:testng:6.3.1'
+}
+
+test {
+ 	useTestNG()
+}
+
+"""
+        executer.withTasks("test").run()
+    }
+    
+    @Test
+    void supportsTestGroups() {
+        executer.withTasks("test").run()
+        def result = new TestNGExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('org.gradle.groups.SomeTest')
+        result.testClass('org.gradle.groups.SomeTest').assertTestsExecuted("databaseTest")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/build.gradle
new file mode 100644
index 0000000..2eda5b4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/src/main/groovy/BrokenClass.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/src/main/groovy/BrokenClass.groovy
new file mode 100644
index 0000000..6e73e68
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badCodeBreaksBuild/src/main/groovy/BrokenClass.groovy
@@ -0,0 +1,5 @@
+
+class BrokenClass {
+    Unknown1 field1
+    Unknown2 field2
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/build.gradle
new file mode 100644
index 0000000..2eda5b4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/BrokenClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/BrokenClass.java
new file mode 100644
index 0000000..d25f689
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/BrokenClass.java
@@ -0,0 +1,2 @@
+public class BrokenClass extends {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/OkClass.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/OkClass.groovy
new file mode 100644
index 0000000..4e4a0b2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/badJavaCodeBreaksBuild/src/main/groovy/OkClass.groovy
@@ -0,0 +1,4 @@
+
+class OkClass {
+    String field1
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle
new file mode 100644
index 0000000..2eda5b4
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/src/test/groovy/MyGroovyTestCase.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/src/test/groovy/MyGroovyTestCase.groovy
new file mode 100644
index 0000000..6c77192
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canCompileAgainstGroovyClassThatDependsOnExternalClass/src/test/groovy/MyGroovyTestCase.groovy
@@ -0,0 +1,6 @@
+// GroovyTestCase depends on external class junit.framework.TestCase
+class MyGroovyTestCase extends GroovyTestCase {
+    void testSomething() {
+        assert getName() == "testSomething"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/build.gradle
new file mode 100644
index 0000000..c10fb18
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/main/groovy/GroovyMagicField.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/main/groovy/GroovyMagicField.groovy
new file mode 100644
index 0000000..a7c6761
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/main/groovy/GroovyMagicField.groovy
@@ -0,0 +1,4 @@
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+ at GroovyASTTransformationClass(["GroovyMagicFieldTransform"])
+public @interface GroovyMagicField {}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/main/groovy/GroovyMagicFieldTransform.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/main/groovy/GroovyMagicFieldTransform.groovy
new file mode 100644
index 0000000..d10ea94
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/main/groovy/GroovyMagicFieldTransform.groovy
@@ -0,0 +1,17 @@
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.transform.ASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+
+import java.lang.reflect.Modifier
+
+ at GroovyASTTransformation
+class GroovyMagicFieldTransform implements ASTTransformation {
+    void visit(ASTNode[] nodes, SourceUnit source) {
+        def clazz = (ClassNode) nodes[1]
+        clazz.addField("magicField", Modifier.PUBLIC, ClassHelper.STRING_TYPE, new ConstantExpression("magicValue"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/test/groovy/GroovyMagicFieldTransformTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/test/groovy/GroovyMagicFieldTransformTest.groovy
new file mode 100644
index 0000000..fb31f34
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformWrittenInGroovy/src/test/groovy/GroovyMagicFieldTransformTest.groovy
@@ -0,0 +1,10 @@
+import org.junit.Test
+
+ at GroovyMagicField
+class GroovyMagicFieldTransformTest {
+    @Test
+    void transformHasBeenApplied() {
+        assert getClass().declaredFields.find { it.name == "magicField" }
+        assert magicField == "magicValue"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/build.gradle
new file mode 100644
index 0000000..c10fb18
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy
new file mode 100644
index 0000000..286a5c1
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy
@@ -0,0 +1,6 @@
+
+class TestDelegate {
+    def doStuff(String value) {
+        return "[$value]"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy
new file mode 100644
index 0000000..612394d
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy
@@ -0,0 +1,11 @@
+import org.junit.Test
+import TestDelegate
+
+class UseBuiltInTransformTest {
+    @Delegate final TestDelegate delegate = new TestDelegate()
+
+    @Test
+    void transformHasBeenApplied() {
+        assert doStuff("hi") == "[hi]"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/build.gradle
new file mode 100644
index 0000000..c10fb18
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicField.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicField.java
new file mode 100644
index 0000000..ca54596
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicField.java
@@ -0,0 +1,4 @@
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+ at GroovyASTTransformationClass({"MagicFieldTransform"})
+public @interface MagicField {}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicFieldTransform.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicFieldTransform.java
new file mode 100644
index 0000000..6cc1177
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicFieldTransform.java
@@ -0,0 +1,17 @@
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import java.lang.reflect.Modifier;
+
+ at GroovyASTTransformation
+public class MagicFieldTransform implements ASTTransformation {
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode clazz = (ClassNode) nodes[1];
+        clazz.addField("magicField", Modifier.PUBLIC, ClassHelper.STRING_TYPE, new ConstantExpression("magicValue"));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterface.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterface.java
new file mode 100644
index 0000000..2781d6b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterface.java
@@ -0,0 +1,4 @@
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+ at GroovyASTTransformationClass(classes = {MagicInterfaceTransform.class})
+public @interface MagicInterface {}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java
new file mode 100644
index 0000000..10aca0e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java
@@ -0,0 +1,17 @@
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import java.lang.reflect.Modifier;
+
+ at GroovyASTTransformation
+public class MagicInterfaceTransform implements ASTTransformation {
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode clazz = (ClassNode) nodes[1];
+        clazz.addInterface(new ClassNode(Marker.class));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/Marker.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/Marker.java
new file mode 100644
index 0000000..da996a2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/Marker.java
@@ -0,0 +1,2 @@
+public interface Marker {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/test/groovy/MagicFieldTransformTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/test/groovy/MagicFieldTransformTest.groovy
new file mode 100644
index 0000000..9457b72
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/test/groovy/MagicFieldTransformTest.groovy
@@ -0,0 +1,11 @@
+import org.junit.Test
+
+ at MagicField @MagicInterface
+class MagicFieldTransformTest {
+    @Test
+    void transformHasBeenApplied() {
+        assert getClass().declaredFields.find { it.name == "magicField" }
+        assert magicField == "magicValue"
+        assert this instanceof Marker
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java
similarity index 100%
copy from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java
copy to subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.groovy
new file mode 100644
index 0000000..9dec906
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.groovy
@@ -0,0 +1,3 @@
+class PersonImpl implements Person {
+    def String name
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
new file mode 100644
index 0000000..4b0357f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -0,0 +1,4 @@
+apply plugin: 'java'
+
+sourceCompatibility = 1.5
+compileJava.options.debug = true
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java b/subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/build.gradle
new file mode 100644
index 0000000..f99ff81
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/build.gradle
@@ -0,0 +1,23 @@
+import org.gradle.internal.jvm.Jvm
+
+apply plugin: "java"
+
+sourceSets {
+    taglet
+}
+
+dependencies {
+    tagletCompile files(Jvm.current().toolsJar)
+}
+
+javadoc {
+    dependsOn tagletClasses
+
+    options {
+        tags 'author'
+        tags 'deprecated'
+        tags 'customtag:t:"Custom Tag:"'
+        taglets 'CustomTaglet'
+        tagletPath sourceSets.taglet.output.classesDir
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/src/main/java/Person.java b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/src/main/java/Person.java
new file mode 100644
index 0000000..8b0ad15
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/src/main/java/Person.java
@@ -0,0 +1,11 @@
+/**
+ * This is the Person class.
+ *
+ * @author author value
+ * @version version value
+ * @deprecated deprecated value
+ * @customtag custom tag value
+ * @customtaglet custom taglet value
+ */
+public class Person {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/src/taglet/java/CustomTaglet.java b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/src/taglet/java/CustomTaglet.java
new file mode 100644
index 0000000..d825f56
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/javadoc/JavadocIntegrationTest/handlesTagsAndTaglets/src/taglet/java/CustomTaglet.java
@@ -0,0 +1,51 @@
+import com.sun.tools.doclets.Taglet;
+import com.sun.javadoc.*;
+
+import java.util.Map;
+
+public class CustomTaglet implements Taglet {
+    public boolean inField() {
+        return false;
+    }
+
+    public boolean inConstructor() {
+        return false;
+    }
+
+    public boolean inMethod() {
+        return false;
+    }
+
+    public boolean inOverview() {
+        return false;
+    }
+
+    public boolean inPackage() {
+        return false;
+    }
+
+    public boolean inType() {
+        return true;
+    }
+
+    public boolean isInlineTag() {
+        return false;
+    }
+
+    public String getName() {
+        return "customtaglet";
+    }
+
+    public String toString(Tag tag) {
+        return "<DT><B>Custom Taglet:</B></DT>\n<DD>" + tag.text() + "</DD>\n";
+    }
+
+    public String toString(Tag[] tags) {
+        return toString(tags[0]);
+    }
+
+    public static void register(Map tagletMap) {
+        CustomTaglet taglet = new CustomTaglet();
+        tagletMap.put(taglet.getName(), taglet);
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/build.gradle
new file mode 100644
index 0000000..46d17d3
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.7"
+}
+
+test {
+    systemProperties 'java.security.manager': 'org.gradle.MySecurityManager'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/src/test/java/org/gradle/JUnitTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/src/test/java/org/gradle/JUnitTest.java
new file mode 100644
index 0000000..fd6644b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/src/test/java/org/gradle/JUnitTest.java
@@ -0,0 +1,14 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
+import static org.junit.Assert.assertEquals;
+
+public class JUnitTest {
+    @Test
+    public void mySecurityManagerIsUsed() throws ClassNotFoundException {
+        assertTrue(System.getSecurityManager() instanceof MySecurityManager);
+        assertEquals(ClassLoader.getSystemClassLoader(), MySecurityManager.class.getClassLoader());
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/src/test/java/org/gradle/MySecurityManager.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/src/test/java/org/gradle/MySecurityManager.java
new file mode 100644
index 0000000..7507c21
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSecurityManager/src/test/java/org/gradle/MySecurityManager.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import java.security.Permission;
+
+import static org.junit.Assert.assertEquals;
+
+public class MySecurityManager extends SecurityManager {
+    public MySecurityManager() {
+        assertEquals(getClass().getName(), System.getProperty("java.security.manager"));
+    }
+
+    @Override
+    public void checkPermission(Permission permission) {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/build.gradle
new file mode 100644
index 0000000..b4e8810
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.7"
+}
+
+test {
+    systemProperties 'java.system.class.loader':'org.gradle.MySystemClassLoader'
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/src/test/java/org/gradle/JUnitTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/src/test/java/org/gradle/JUnitTest.java
new file mode 100644
index 0000000..85dea42
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/src/test/java/org/gradle/JUnitTest.java
@@ -0,0 +1,13 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
+
+public class JUnitTest {
+    @Test
+    public void mySystemClassLoaderIsUsed() throws ClassNotFoundException {
+        assertTrue(ClassLoader.getSystemClassLoader() instanceof MySystemClassLoader);
+        assertEquals(getClass().getClassLoader(), ClassLoader.getSystemClassLoader().getParent());
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/src/test/java/org/gradle/MySystemClassLoader.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/src/test/java/org/gradle/MySystemClassLoader.java
new file mode 100644
index 0000000..49be87a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoader/src/test/java/org/gradle/MySystemClassLoader.java
@@ -0,0 +1,13 @@
+package org.gradle;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import static junit.framework.Assert.*;
+
+public class MySystemClassLoader extends URLClassLoader {
+    public MySystemClassLoader(ClassLoader parent) {
+        super(new URL[0], parent);
+        // Should be constructed with the default system ClassLoader as parent
+        assertEquals(getClass().getClassLoader(), parent);
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/build.gradle
new file mode 100644
index 0000000..2d44108
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "junit:junit:4.7"
+}
+
+jar {
+    manifest {
+        attributes 'Premain-Class': 'org.gradle.MyAgent'
+    }
+}
+
+test {
+    dependsOn jar
+    systemProperties 'java.system.class.loader':'org.gradle.MySystemClassLoader'
+    jvmArgs "-javaagent:${jar.archivePath}"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/main/java/org/gradle/MyAgent.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/main/java/org/gradle/MyAgent.java
new file mode 100644
index 0000000..bbc4d13
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/main/java/org/gradle/MyAgent.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import java.lang.instrument.Instrumentation;
+
+import static junit.framework.Assert.assertTrue;
+
+public class MyAgent {
+    public static void premain(String args, Instrumentation instrumentation) {
+        System.setProperty("using.custom.agent", "true");
+
+        // This agent should be loaded via the custom system ClassLoader
+        assertTrue(ClassLoader.getSystemClassLoader() instanceof MySystemClassLoader);
+        assertTrue(MyAgent.class.getClassLoader() == ClassLoader.getSystemClassLoader());
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/main/java/org/gradle/MySystemClassLoader.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/main/java/org/gradle/MySystemClassLoader.java
new file mode 100644
index 0000000..adcdd5c
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/main/java/org/gradle/MySystemClassLoader.java
@@ -0,0 +1,38 @@
+package org.gradle;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * A custom ClassLoader that redefines the custom agent and the tests, to verify that they are
+ * loaded via the custom system ClassLoader.
+ */
+public class MySystemClassLoader extends URLClassLoader {
+    public MySystemClassLoader(ClassLoader parent) throws URISyntaxException, ClassNotFoundException {
+        super(new URL[0], parent);
+        // Should be constructed with the default system ClassLoader as parent
+        assertEquals(getClass().getClassLoader(), parent);
+        addClasspathFor("org.gradle.MyAgent");
+        addClasspathFor("org.gradle.JUnitTest");
+    }
+
+    private void addClasspathFor(String name) throws ClassNotFoundException {
+        URL codebase = getParent().loadClass(name).getProtectionDomain().getCodeSource().getLocation();
+        addURL(codebase);
+    }
+
+    @Override
+    public Class<?> loadClass(String className) throws ClassNotFoundException {
+        if (className.equals(getClass().getName())) {
+            return getClass();
+        }
+        try {
+            return findClass(className);
+        } catch (ClassNotFoundException e) {
+            return super.loadClass(className);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/test/java/org/gradle/JUnitTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/test/java/org/gradle/JUnitTest.java
new file mode 100644
index 0000000..9699dc8
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithCustomSystemClassLoaderAndJavaAgent/src/test/java/org/gradle/JUnitTest.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+public class JUnitTest {
+    @Test
+    public void mySystemClassLoaderIsUsed() throws ClassNotFoundException {
+        assertEquals("true", System.getProperty("using.custom.agent"));
+
+        // This test class should be loaded via the custom system ClassLoader
+        assertTrue(ClassLoader.getSystemClassLoader() instanceof MySystemClassLoader);
+        assertTrue(getClass().getClassLoader() == ClassLoader.getSystemClassLoader());
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithJMockitLoadedWithJavaAgent/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithJMockitLoadedWithJavaAgent/build.gradle
new file mode 100644
index 0000000..e844153
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithJMockitLoadedWithJavaAgent/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+configurations {
+    jmockit
+    testCompile.extendsFrom jmockit
+}
+
+dependencies {
+    jmockit "com.googlecode.jmockit:jmockit:0.999.13"
+    testCompile "junit:junit:4.7"
+}
+
+test {
+    jvmArgs "-javaagent:${configurations.jmockit.singleFile.absolutePath}"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithJMockitLoadedWithJavaAgent/src/test/java/org/gradle/JMockitTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithJMockitLoadedWithJavaAgent/src/test/java/org/gradle/JMockitTest.java
new file mode 100644
index 0000000..300911a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/TestEnvironmentIntegrationTest/canRunTestsWithJMockitLoadedWithJavaAgent/src/test/java/org/gradle/JMockitTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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;
+
+import java.util.*;
+import org.junit.*;
+import static org.junit.Assert.*;
+
+import mockit.*;
+
+public class JMockitTest {
+   @Mocked List testList;
+
+   @Test
+   public void testOk() throws Exception
+   {
+       new Expectations() {
+          {
+             testList.size(); result = 5;
+          }
+       };
+
+       assertEquals(testList.size(), 5);
+   }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test1.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test1.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test1.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test1.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test2.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test2.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test2.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canHaveMultipleTestTaskInstances/src/test/java/org/gradle/Test2.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
new file mode 100644
index 0000000..25f7b6c
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'junit:junit:4.8.2', 'ant:ant:1.6.1', 'ant:ant-launcher:1.6.1' }
+test {
+    systemProperties.testSysProperty = 'value'
+    systemProperties.projectDir = projectDir
+    systemProperties.expectedClassPath = sourceSets.test.runtimeClasspath.asPath
+    environment.TEST_ENV_VAR = 'value'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
new file mode 100644
index 0000000..8dfadc2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
@@ -0,0 +1,121 @@
+package org.gradle;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.*;
+
+public class OkTest {
+    static {
+        System.out.println("class loaded");
+    }
+
+    public OkTest() {
+        System.out.println("test constructed");
+    }
+
+    @org.junit.BeforeClass public static void init() {
+        System.out.println("before class out");
+        System.err.println("before class err");
+    }
+
+    @org.junit.AfterClass public static void end() {
+        System.out.println("after class out");
+        System.err.println("after class err");
+    }
+
+    @org.junit.Test
+    public void ok() throws Exception {
+        // check versions of dependencies
+        assertEquals("4.8.2", new org.junit.runner.JUnitCore().getVersion());
+        assertTrue(org.apache.tools.ant.Main.getAntVersion().contains("1.6.1"));
+
+        // check working dir
+        assertEquals(System.getProperty("projectDir"), System.getProperty("user.dir"));
+
+        // check sys properties
+        assertEquals("value", System.getProperty("testSysProperty"));
+
+        // check env vars
+        assertEquals("value", System.getenv("TEST_ENV_VAR"));
+
+        // check classloader and classpath
+        assertSame(ClassLoader.getSystemClassLoader(), getClass().getClassLoader());
+        assertSame(getClass().getClassLoader(), Thread.currentThread().getContextClassLoader());
+        assertEquals(System.getProperty("java.class.path"), System.getProperty("expectedClassPath"));
+        List<URL> expectedClassPath = buildExpectedClassPath(System.getProperty("expectedClassPath"));
+        List<URL> actualClassPath = buildActualClassPath();
+        assertEquals(expectedClassPath, actualClassPath);
+
+        // check Gradle and impl classes not visible
+        try {
+            getClass().getClassLoader().loadClass("org.gradle.api.Project");
+            fail();
+        } catch (ClassNotFoundException e) {
+        }
+        try {
+            getClass().getClassLoader().loadClass("org.slf4j.Logger");
+            fail();
+        } catch (ClassNotFoundException e) {
+        }
+
+        // check other environmental stuff
+        assertNull(System.getSecurityManager());
+
+        // check stdout and stderr and logging
+        System.out.println("This is test stdout");
+        System.out.print("no EOL");
+        System.out.println();
+        System.err.println("This is test stderr");
+        Logger.getLogger("test-logger").warning("this is a warning");
+
+        final PrintStream out = System.out;
+        // logging from a shutdown hook
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                out.println("stdout from a shutdown hook.");
+                Logger.getLogger("test-logger").info("info from a shutdown hook.");
+            }
+        });
+
+        // logging from another thread
+        Thread thread = new Thread() {
+            @Override
+            public void run() {
+                System.out.println("stdout from another thread");
+                Logger.getLogger("test-logger").info("info from another thread.");
+            }
+        };
+        thread.start();
+        thread.join();
+    }
+
+    private List<URL> buildActualClassPath() {
+        List<URL> urls = Arrays.asList(((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs());
+        return urls.subList(1, urls.size());
+    }
+
+    private List<URL> buildExpectedClassPath(String expectedClassPath) throws MalformedURLException {
+        String[] paths = expectedClassPath.split(Pattern.quote(File.pathSeparator));
+        List<URL> urls = new ArrayList<URL>();
+        for (String path : paths) {
+            urls.add(new File(path).toURI().toURL());
+        }
+        return urls;
+    }
+
+    @org.junit.Test
+    public void anotherOk() {
+        System.out.println("sys out from another test method");
+        System.err.println("sys err from another test method");
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit3Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit3Tests/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit3Tests/src/test/java/org/gradle/Junit3Test.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/src/test/java/org/gradle/Junit3Test.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit3Tests/src/test/java/org/gradle/Junit3Test.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit3Tests/src/test/java/org/gradle/Junit3Test.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4Tests/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/IgnoredTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/IgnoredTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/IgnoredTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/IgnoredTest.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4_4Tests/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4_4Tests/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/junit4_4Tests/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4_4Tests/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeAndAfter.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeAndAfter.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeAndAfter.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeAndAfter.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenRunner.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenRunner.java
new file mode 100644
index 0000000..4abefe8
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenRunner.java
@@ -0,0 +1,23 @@
+package org.gradle;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+
+public class BrokenRunner extends Runner {
+    private final Class<?> type;
+
+    public BrokenRunner(Class<?> type) {
+        this.type = type;
+    }
+
+    @Override
+    public Description getDescription() {
+        return Description.createSuiteDescription(type);
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        throw new UnsupportedOperationException("broken");
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/ClassWithBrokenRunner.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/ClassWithBrokenRunner.java
new file mode 100644
index 0000000..8984a63
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/ClassWithBrokenRunner.java
@@ -0,0 +1,7 @@
+package org.gradle;
+
+import org.junit.runner.RunWith;
+
+ at RunWith(BrokenRunner.class)
+public class ClassWithBrokenRunner {
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle
new file mode 100644
index 0000000..36f2504
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'junit:junit:4.8.2'}
+test {
+    include '**/ASuite.class'
+    exclude '**/*Test.class'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java
new file mode 100644
index 0000000..29702fd
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/ASuite.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+ at RunWith(Suite.class)
+ at Suite.SuiteClasses({OkTest.class, OtherTest.class })
+public class ASuite {
+    static {
+        System.out.println("suite class loaded");
+    }
+
+    @org.junit.BeforeClass public static void init() {
+        System.out.println("before suite class out");
+        System.err.println("before suite class err");
+    }
+
+    @org.junit.AfterClass public static void end() {
+        System.out.println("after suite class out");
+        System.err.println("after suite class err");
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/OkTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/OkTest.java
new file mode 100644
index 0000000..54c874a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/OkTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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;
+
+public class OkTest {
+
+    @org.junit.Test
+    public void ok() throws Exception {
+        System.err.println("This is test stderr");
+    }
+
+    @org.junit.Test
+    public void anotherOk() {
+        System.out.println("sys out from another test method");
+        System.err.println("sys err from another test method");
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/OtherTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/OtherTest.java
new file mode 100644
index 0000000..75891c2
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/suitesOutputIsVisible/src/test/java/org/gradle/OtherTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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;
+
+public class OtherTest {
+
+    @org.junit.Test
+    public void ok() throws Exception {
+        System.out.println("This is other stdout");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/build.gradle
new file mode 100644
index 0000000..5899a75
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+def listener = new TestListenerImpl()
+
+test {
+    useTestNG()
+    addTestListener(listener)
+    ignoreFailures = true
+}
+
+class TestListenerImpl implements TestListener {
+    void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
+
+    void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name]" }
+
+    void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
+
+    void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.exception]" }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/AppException.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/AppException.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/AppException.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/AppException.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/SomeTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/SomeTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/SomeTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/canListenForTestResults/src/test/java/SomeTest.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
new file mode 100644
index 0000000..4243da8
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'org.testng:testng:6.3.1' }
+test {
+    useTestNG()
+    systemProperties.testSysProperty = 'value'
+    systemProperties.testDir = projectDir
+    environment.TEST_ENV_VAR = 'value'
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
new file mode 100644
index 0000000..a3a56e6
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'groovy'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+	groovy "org.codehaus.groovy:groovy-all:1.8.4"
+    testCompile 'org.testng:testng:6.3.1'
+}
+
+test {
+   useTestNG() 
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/src/main/groovy/org/gradle/Ok.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/src/main/groovy/org/gradle/Ok.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/src/main/groovy/org/gradle/Ok.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/src/main/groovy/org/gradle/Ok.groovy
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/src/test/groovy/org/gradle/BadTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/src/test/groovy/org/gradle/BadTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Failing/src/test/groovy/org/gradle/BadTest.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Failing/src/test/groovy/org/gradle/BadTest.groovy
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
new file mode 100644
index 0000000..a3a56e6
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'groovy'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+	groovy "org.codehaus.groovy:groovy-all:1.8.4"
+    testCompile 'org.testng:testng:6.3.1'
+}
+
+test {
+   useTestNG() 
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/src/main/groovy/org/gradle/Ok.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/src/main/groovy/org/gradle/Ok.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/src/main/groovy/org/gradle/Ok.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/src/main/groovy/org/gradle/Ok.groovy
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/src/test/groovy/org/gradle/OkTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/src/test/groovy/org/gradle/OkTest.groovy
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/groovyJdk15Passing/src/test/groovy/org/gradle/OkTest.groovy
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/groovyJdk15Passing/src/test/groovy/org/gradle/OkTest.groovy
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk14Failing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk14Failing/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk14Failing/build.gradle
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk14Failing/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk14Failing/src/main/java/org/gradle/Ok.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk14Failing/src/main/java/org/gradle/Ok.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk14Failing/src/main/java/org/gradle/Ok.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk14Failing/src/main/java/org/gradle/Ok.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk14Failing/src/test/java/org/gradle/BadTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk14Failing/src/test/java/org/gradle/BadTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk14Failing/src/test/java/org/gradle/BadTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk14Failing/src/test/java/org/gradle/BadTest.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
new file mode 100644
index 0000000..0d80e2b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "org.testng:testng:$testNGVersion"
+}
+
+test {
+   useTestNG()
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/main/java/org/gradle/Ok.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/main/java/org/gradle/Ok.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/main/java/org/gradle/Ok.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/main/java/org/gradle/Ok.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BadTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BadTest.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BadTest.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BadTest.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BrokenAfterSuite.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BrokenAfterSuite.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BrokenAfterSuite.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/BrokenAfterSuite.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenMethodDependency.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenMethodDependency.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenMethodDependency.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenMethodDependency.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenSetup.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenSetup.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenSetup.java
rename to subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/javaJdk15Failing/src/test/java/org/gradle/TestWithBrokenSetup.java
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestGroups/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestGroups/build.gradle
new file mode 100644
index 0000000..8722feb
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestGroups/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "org.testng:testng:6.3.1"
+}
+
+test {
+    useTestNG {
+        includeGroups "database"
+        excludeGroups "slow"
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestGroups/src/test/java/org/gradle/groups/SomeTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestGroups/src/test/java/org/gradle/groups/SomeTest.java
new file mode 100644
index 0000000..850f257
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestGroups/src/test/java/org/gradle/groups/SomeTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.groups;
+
+import org.testng.annotations.Test;
+
+public class SomeTest {
+    @Test(groups = "web")
+    public void webTest() {}
+
+    @Test(groups = "database")
+    public void databaseTest() {}
+
+    @Test(groups = {"database", "slow"})
+    public void slowDatabaseTest() {}
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/DefaultArtifactPublicationSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/DefaultArtifactPublicationSet.java
new file mode 100644
index 0000000..4cd03e5
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/DefaultArtifactPublicationSet.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.artifacts.PublishArtifactSet;
+
+/**
+ * The policy for the artifacts should be published by default when none are explicitly declared.
+ */
+public class DefaultArtifactPublicationSet {
+    private final PublishArtifactSet artifacts;
+    private PublishArtifact defaultArtifact;
+
+    public DefaultArtifactPublicationSet(PublishArtifactSet artifacts) {
+        this.artifacts = artifacts;
+    }
+
+    public void addCandidate(PublishArtifact artifact) {
+        if (defaultArtifact == null) {
+            artifacts.add(artifact);
+            defaultArtifact = artifact;
+            return;
+        }
+
+        String thisType = artifact.getType();
+        String currentType = defaultArtifact.getType();
+        if (thisType.equals("ear")) {
+            replaceCurrent(artifact);
+        } else if (thisType.equals("war")) {
+            if (currentType.equals("jar")) {
+                replaceCurrent(artifact);
+            }
+        } else if (!thisType.equals("jar")) {
+            artifacts.add(artifact);
+        }
+    }
+
+    private void replaceCurrent(PublishArtifact artifact) {
+        artifacts.remove(defaultArtifact);
+        artifacts.add(artifact);
+        defaultArtifact = artifact;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
new file mode 100644
index 0000000..97ff114
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/StartScriptGenerator.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2011 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 groovy.text.SimpleTemplateEngine
+import org.gradle.util.TextUtil
+import org.gradle.util.AntUtil
+import org.apache.tools.ant.taskdefs.Chmod
+
+class StartScriptGenerator {
+    /**
+     * The display name of the application
+     */
+    String applicationName
+
+    /**
+     * The environment variable to use to provide additional options to the JVM
+     */
+    String optsEnvironmentVar
+
+    /**
+     * The environment variable to use to control exit value (windows only)
+     */
+    String exitEnvironmentVar
+
+    String mainClassName
+
+    /**
+     * The classpath, relative to the application home directory.
+     */
+    Iterable<String> classpath
+
+    /**
+     * The path of the script, relative to the application home directory.
+     */
+    String scriptRelPath
+
+    /**
+     * This system property to use to pass the script name to the application. May be null.
+     */
+    String appNameSystemProperty
+
+    private final engine = new SimpleTemplateEngine()
+
+    void generateUnixScript(File unixScript) {
+        String nativeOutput = generateUnixScriptContent()
+        writeToFile(nativeOutput, unixScript)
+        createExecutablePermission(unixScript)
+    }
+
+    String generateUnixScriptContent() {
+        def unixClassPath = classpath.collect { "\$APP_HOME/${it.replace('\\', '/')}" }.join(":")
+        def binding = [applicationName: applicationName,
+                optsEnvironmentVar: optsEnvironmentVar,
+                mainClassName: mainClassName,
+                appNameSystemProperty: appNameSystemProperty,
+                appHomeRelativePath: appHomeRelativePath,
+                classpath: unixClassPath]
+        return generateNativeOutput('unixStartScript.txt', binding, TextUtil.unixLineSeparator)
+    }
+
+    void generateWindowsScript(File windowsScript) {
+        String nativeOutput = generateWindowsScriptContent()
+        writeToFile(nativeOutput, windowsScript);
+    }
+
+    String generateWindowsScriptContent() {
+        def windowsClassPath = classpath.collect { "%APP_HOME%\\${it.replace('/', '\\')}" }.join(";")
+        def appHome = appHomeRelativePath.replace('/', '\\')
+        def binding = [applicationName: applicationName,
+                optsEnvironmentVar: optsEnvironmentVar,
+                exitEnvironmentVar: exitEnvironmentVar,
+                mainClassName: mainClassName,
+                appNameSystemProperty: appNameSystemProperty,
+                appHomeRelativePath: appHome,
+                classpath: windowsClassPath]
+        return generateNativeOutput('windowsStartScript.txt', binding, TextUtil.windowsLineSeparator)
+
+    }
+
+    private void createExecutablePermission(File unixScriptFile) {
+        Chmod chmod = new Chmod()
+        chmod.file = unixScriptFile
+        chmod.perm = "ugo+rx"
+        chmod.project = AntUtil.createProject()
+        chmod.execute()
+    }
+
+    void writeToFile(String scriptContent, File scriptFile) {
+        scriptFile.parentFile.mkdirs()
+        scriptFile.write(scriptContent)
+    }
+
+
+    private String generateNativeOutput(String templateName, Map binding, String lineSeparator) {
+        def stream = StartScriptGenerator.getResource(templateName)
+        def templateText = stream.text
+        def output = engine.createTemplate(templateText).make(binding)
+        def nativeOutput = TextUtil.convertLineSeparators(output as String, lineSeparator)
+        return nativeOutput;
+
+    }
+
+    private String getAppHomeRelativePath() {
+        def depth = scriptRelPath.count("/")
+        if (depth == 0) {
+            return ""
+        }
+        return (1..depth).collect {".."}.join("/")
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java
index 4a46e78..1d6219a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java
@@ -22,31 +22,26 @@ import org.gradle.api.file.FileTreeElement;
 import org.gradle.api.file.SourceDirectorySet;
 import org.gradle.api.internal.file.DefaultSourceDirectorySet;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetOutput;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.GUtil;
 
-import java.io.File;
-import java.util.concurrent.Callable;
-
 public class DefaultSourceSet implements SourceSet {
     private final String name;
-    private final FileResolver fileResolver;
-    private File classesDir;
     private FileCollection compileClasspath;
     private FileCollection runtimeClasspath;
     private final SourceDirectorySet javaSource;
     private final SourceDirectorySet allJavaSource;
     private final SourceDirectorySet resources;
-    private final DefaultConfigurableFileCollection classes;
     private final String displayName;
     private final SourceDirectorySet allSource;
 
-    public DefaultSourceSet(String name, FileResolver fileResolver, TaskResolver taskResolver) {
+    private DefaultSourceSetOutput output;
+
+    public DefaultSourceSet(String name, FileResolver fileResolver) {
         this.name = name;
-        this.fileResolver = fileResolver;
         displayName = GUtil.toWords(this.name);
 
         String javaSrcDisplayName = String.format("%s Java source", displayName);
@@ -70,13 +65,6 @@ public class DefaultSourceSet implements SourceSet {
         allSource = new DefaultSourceDirectorySet(allSourceDisplayName, fileResolver);
         allSource.source(resources);
         allSource.source(javaSource);
-
-        String classesDisplayName = String.format("%s classes", displayName);
-        classes = new DefaultConfigurableFileCollection(classesDisplayName, fileResolver, taskResolver, new Callable() {
-            public Object call() throws Exception {
-                return getClassesDir();
-            }
-        });
     }
 
     public String getName() {
@@ -122,20 +110,24 @@ public class DefaultSourceSet implements SourceSet {
         return name.equals(SourceSet.MAIN_SOURCE_SET_NAME) ? "" : GUtil.toCamelCase(name);
     }
 
-    public File getClassesDir() {
-        return classesDir;
+    public String getCompileConfigurationName() {
+        return StringUtils.uncapitalize(String.format("%sCompile", getTaskBaseName()));
+    }
+
+    public String getRuntimeConfigurationName() {
+        return StringUtils.uncapitalize(String.format("%sRuntime", getTaskBaseName()));
     }
 
-    public void setClassesDir(File classesDir) {
-        this.classesDir = fileResolver.resolve(classesDir);
+    public SourceSetOutput getOutput() {
+        return output;
     }
 
-    public FileCollection getClasses() {
-        return classes;
+    public void setClasses(DefaultSourceSetOutput classes) {
+        this.output = classes;
     }
 
     public SourceSet compiledBy(Object... taskPaths) {
-        classes.builtBy(taskPaths);
+        output.builtBy(taskPaths);
         return this;
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
old mode 100644
new mode 100755
index 0301001..3b73234
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
@@ -1,40 +1,54 @@
-/*
- * 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.tasks;
-
-import org.gradle.api.internal.AutoCreateDomainObjectContainer;
-import org.gradle.api.internal.ClassGenerator;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.tasks.SourceSet;
-import org.gradle.api.tasks.SourceSetContainer;
-
-public class DefaultSourceSetContainer extends AutoCreateDomainObjectContainer<SourceSet> implements SourceSetContainer {
-    private final FileResolver fileResolver;
-    private final TaskResolver taskResolver;
-    private final ClassGenerator generator;
-
-    public DefaultSourceSetContainer(FileResolver fileResolver, TaskResolver taskResolver, ClassGenerator classGenerator) {
-        super(SourceSet.class, classGenerator);
-        this.fileResolver = fileResolver;
-        this.taskResolver = taskResolver;
-        this.generator = classGenerator;
-    }
-
-    @Override
-    protected SourceSet create(String name) {
-        return generator.newInstance(DefaultSourceSet.class, name, fileResolver, taskResolver);
-    }
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.Namer;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
+
+public class DefaultSourceSetContainer extends AbstractNamedDomainObjectContainer<SourceSet> implements SourceSetContainer {
+    private final FileResolver fileResolver;
+    private final TaskResolver taskResolver;
+    private final Instantiator instantiator;
+
+    public DefaultSourceSetContainer(FileResolver fileResolver, TaskResolver taskResolver, Instantiator classGenerator) {
+        super(SourceSet.class, classGenerator, new Namer<SourceSet>() { public String determineName(SourceSet ss) { return ss.getName(); }});
+        this.fileResolver = fileResolver;
+        this.taskResolver = taskResolver;
+        this.instantiator = classGenerator;
+    }
+
+    @Override
+    protected SourceSet doCreate(String name) {
+        DefaultSourceSet sourceSet = instantiator.newInstance(DefaultSourceSet.class, name, fileResolver);
+        sourceSet.setClasses(instantiator.newInstance(DefaultSourceSetOutput.class, sourceSet.getDisplayName(), fileResolver, taskResolver));
+
+        return sourceSet;
+    }
+
+    public SourceSet add(String name) {
+        return create(name);
+    }
+
+    public SourceSet add(String name, Closure closure) {
+        return create(name, closure);
+    }
+
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java
new file mode 100644
index 0000000..722326e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetOutput.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.CompositeFileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.internal.file.collections.FileCollectionResolveContext;
+import org.gradle.api.tasks.SourceSetOutput;
+import org.gradle.util.DeprecationLogger;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * @author: Szczepan Faber, created at: 5/4/11
+ */
+public class DefaultSourceSetOutput extends CompositeFileCollection implements SourceSetOutput {
+    private DefaultConfigurableFileCollection outputDirectories;
+    private Object classesDir;
+    private Object resourcesDir;
+    private DefaultConfigurableFileCollection dirs;
+    private FileResolver fileResolver;
+
+    public DefaultSourceSetOutput(String sourceSetDisplayName, FileResolver fileResolver, TaskResolver taskResolver) {
+        this.fileResolver = fileResolver;
+        String displayName = String.format("%s output", sourceSetDisplayName);
+        outputDirectories = new DefaultConfigurableFileCollection(displayName, fileResolver, taskResolver, new Callable() {
+            public Object call() throws Exception {
+                return getClassesDir();
+            }
+        }, new Callable() {
+            public Object call() throws Exception {
+                return getResourcesDir();
+            }
+        });
+        dirs = new DefaultConfigurableFileCollection("dirs", fileResolver, taskResolver);
+    }
+
+    @Override
+    public void resolve(FileCollectionResolveContext context) {
+        context.add(outputDirectories);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return outputDirectories.getDisplayName();
+    }
+
+    public File getClassesDir() {
+        if (classesDir == null) {
+            return null;
+        }
+        return fileResolver.resolve(classesDir);
+    }
+
+    public void setClassesDir(Object classesDir) {
+        this.classesDir = classesDir;
+    }
+
+    public File getResourcesDir() {
+        if (resourcesDir == null) {
+            return null;
+        }
+        return fileResolver.resolve(resourcesDir);
+    }
+
+    public void setResourcesDir(Object resourcesDir) {
+       this.resourcesDir = resourcesDir;
+    }
+
+    public void builtBy(Object ... taskPaths) {
+        outputDirectories.builtBy(taskPaths);
+    }
+
+    public void dir(Object dir) {
+        this.dir(new HashMap<String, Object>(), dir);
+    }
+
+    public void dir(Map<String, Object> options, Object dir) {
+        this.dirs.from(dir);
+        this.outputDirectories.from(dir);
+
+        Object buildBy = options.get("buildBy");
+        if (buildBy != null) {
+            DeprecationLogger.nagUserOfReplacedNamedParameter("buildBy:", "builtBy:");
+            this.builtBy(buildBy);
+            this.dirs.builtBy(buildBy);
+        }
+
+        Object builtBy = options.get("builtBy");
+        if (builtBy != null) {
+            this.builtBy(builtBy);
+            this.dirs.builtBy(builtBy);
+        }
+    }
+
+    public FileCollection getDirs() {
+        return dirs;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
index f3b2799..e71588e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
@@ -15,29 +15,28 @@
  */
 package org.gradle.api.internal.tasks.compile
 
+import org.gradle.api.AntBuilder
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.Factory
+import org.gradle.internal.Factory
 
 class AntDependsStaleClassCleaner extends StaleClassCleaner {
     private final Factory<AntBuilder> antBuilderFactory
+
     File dependencyCacheDir
 
-    def AntDependsStaleClassCleaner(Factory<AntBuilder> antBuilderFactory) {
+    AntDependsStaleClassCleaner(Factory<AntBuilder> antBuilderFactory) {
         this.antBuilderFactory = antBuilderFactory;
     }
 
     void execute() {
-        Map dependArgs = [
-                destDir: destinationDir
-        ]
-
-        Map dependOptions = dependArgs + compileOptions.dependOptions.optionMap()
+        def dependArgs = [destDir: destinationDir]
+        def dependOptions = dependArgs + compileOptions.dependOptions.optionMap()
         if (compileOptions.dependOptions.useCache) {
             dependOptions['cache'] = dependencyCacheDir
         }
 
         def ant = antBuilderFactory.create()
-        ant.project.addTaskDefinition('gradleDepend', AntDepend.class)
+        ant.project.addTaskDefinition('gradleDepend', AntDepend)
         ant.gradleDepend(dependOptions) {
             source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
         }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy
index 7749b52..24d9360 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy
@@ -17,14 +17,12 @@
 package org.gradle.api.internal.tasks.compile
 
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.project.IsolatedAntBuilder
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
 import org.gradle.api.internal.ClassPathRegistry
-
+import org.gradle.api.internal.project.IsolatedAntBuilder
 import org.gradle.api.tasks.WorkResult
-import org.gradle.api.tasks.compile.GroovyCompileOptions
 import org.gradle.api.tasks.compile.CompileOptions
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 
 /**
  * Please note: includeAntRuntime=false is ignored if groovyc is used in non fork mode. In this case the runtime classpath is
@@ -33,19 +31,11 @@ import org.gradle.api.tasks.compile.CompileOptions
  *
  * @author Hans Dockter
  */
-class AntGroovyCompiler implements GroovyJavaJointCompiler {
+class AntGroovyCompiler implements org.gradle.api.internal.tasks.compile.Compiler<GroovyJavaJointCompileSpec> {
     private static Logger logger = LoggerFactory.getLogger(AntGroovyCompiler)
 
     private final IsolatedAntBuilder ant
     private final ClassPathRegistry classPathRegistry
-    FileCollection source
-    File destinationDir
-    Iterable<File> classpath
-    String sourceCompatibility
-    String targetCompatibility
-    GroovyCompileOptions groovyCompileOptions = new GroovyCompileOptions()
-    CompileOptions compileOptions = new CompileOptions()
-    Iterable<File> groovyClasspath
 
     List nonGroovycJavacOptions = ['verbose', 'deprecation', 'includeJavaRuntime', 'includeAntRuntime', 'optimize', 'fork', 'failonerror', 'listfiles', 'nowarn', 'depend']
 
@@ -54,19 +44,19 @@ class AntGroovyCompiler implements GroovyJavaJointCompiler {
         this.classPathRegistry = classPathRegistry;
     }
 
-    public WorkResult execute() {
+    WorkResult execute(GroovyJavaJointCompileSpec spec) {
         int numFilesCompiled;
 
         // Add in commons-cli, as the Groovy POM does not (for some versions of Groovy)
-        Collection antBuilderClasspath = (groovyClasspath as List) + classPathRegistry.getClassPathFiles("COMMONS_CLI")
+        Collection antBuilderClasspath = (spec.groovyClasspath as List) + classPathRegistry.getClassPath("COMMONS_CLI").asFiles
         
         ant.withGroovy(antBuilderClasspath).execute {
             taskdef(name: 'groovyc', classname: 'org.codehaus.groovy.ant.Groovyc')
-            def task = groovyc([includeAntRuntime: false, destdir: destinationDir, classpath: ((classpath as List) + antBuilderClasspath).join(File.pathSeparator)]
-                    + groovyCompileOptions.optionMap()) {
-                source.addToAntBuilder(delegate, 'src', FileCollection.AntType.MatchingTask)
-                javac([source: sourceCompatibility, target: targetCompatibility] + filterNonGroovycOptions(compileOptions)) {
-                    compileOptions.compilerArgs.each {value ->
+            def task = groovyc([includeAntRuntime: false, destdir: spec.destinationDir, classpath: ((spec.classpath as List) + antBuilderClasspath).join(File.pathSeparator)]
+                    + spec.groovyCompileOptions.optionMap()) {
+                spec.source.addToAntBuilder(delegate, 'src', FileCollection.AntType.MatchingTask)
+                javac([source: spec.sourceCompatibility, target: spec.targetCompatibility] + filterNonGroovycOptions(spec.compileOptions)) {
+                    spec.compileOptions.compilerArgs.each {value ->
                         compilerarg(value: value)
                     }
                 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
index ecb09e0..213363d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
@@ -19,51 +19,42 @@ package org.gradle.api.internal.tasks.compile
 import org.gradle.api.AntBuilder
 import org.gradle.api.file.FileCollection
 import org.gradle.api.tasks.WorkResult
-import org.gradle.api.tasks.compile.CompileOptions
-import org.gradle.api.internal.Factory
+import org.gradle.internal.Factory
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
 /**
  * @author Hans Dockter
  */
-class AntJavaCompiler implements JavaCompiler {
-    private static Logger logger = LoggerFactory.getLogger(AntJavaCompiler)
-    static final String CLASSPATH_ID = 'compile.classpath'
-    FileCollection source;
-    File destinationDir;
-    Iterable<File> classpath;
-    String sourceCompatibility;
-    String targetCompatibility;
-    CompileOptions compileOptions = new CompileOptions()
-    final Factory<AntBuilder> antBuilderFactory
+class AntJavaCompiler implements org.gradle.api.internal.tasks.compile.Compiler<JavaCompileSpec> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(AntJavaCompiler)
+    private static final String CLASSPATH_ID = 'compile.classpath'
 
-    def AntJavaCompiler(Factory<AntBuilder> antBuilderFactory) {
-        this.antBuilderFactory = antBuilderFactory
-    }
+    private final Factory<AntBuilder> antBuilderFactory
 
-    void setDependencyCacheDir(File dir) {
-        // don't care
+    AntJavaCompiler(Factory<AntBuilder> antBuilderFactory) {
+        this.antBuilderFactory = antBuilderFactory
     }
 
-    WorkResult execute() {
+    WorkResult execute(JavaCompileSpec spec) {
         def ant = antBuilderFactory.create()
-        
-        createAntClassPath(ant, classpath)
+
+        createAntClassPath(ant, spec.classpath)
         Map otherArgs = [
                 includeAntRuntime: false,
-                destdir: destinationDir,
+                destdir: spec.destinationDir,
                 classpathref: CLASSPATH_ID,
                 sourcepath: '',
-                target: targetCompatibility,
-                source: sourceCompatibility
+                target: spec.targetCompatibility,
+                source: spec.sourceCompatibility
         ]
 
-        Map options = otherArgs + compileOptions.optionMap()
-        logger.debug("Running ant javac with the following options {}", options)
+        Map options = otherArgs + spec.compileOptions.optionMap()
+        LOGGER.info("Compiling with Ant javac task.")
+        LOGGER.debug("Ant javac task options: {}", options)
         def task = ant.javac(options) {
-            source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
-            compileOptions.compilerArgs.each {value ->
+            spec.source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
+            spec.compileOptions.compilerArgs.each {value ->
                 compilerarg(value: value)
             }
         }
@@ -75,7 +66,7 @@ class AntJavaCompiler implements JavaCompiler {
     private void createAntClassPath(AntBuilder ant, Iterable classpath) {
         ant.path(id: CLASSPATH_ID) {
             classpath.each {
-                logger.debug("Add {} to Ant classpath!", it)
+                LOGGER.debug("Add {} to Ant classpath!", it)
                 pathelement(location: it)
             }
         }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
new file mode 100644
index 0000000..b6a9a97
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 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.compile;
+
+import com.google.common.collect.Iterables;
+import groovy.lang.GroovyClassLoader;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.messages.SimpleMessage;
+import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit;
+import org.codehaus.groovy.tools.javac.JavaCompiler;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.DefaultClassPath;
+import org.gradle.util.FilteringClassLoader;
+
+import java.io.File;
+import java.io.Serializable;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ApiGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec>, Serializable {
+    private final Compiler<JavaCompileSpec> javaCompiler;
+
+    public ApiGroovyCompiler(Compiler<JavaCompileSpec> javaCompiler) {
+        this.javaCompiler = javaCompiler;
+    }
+
+    public WorkResult execute(final GroovyJavaJointCompileSpec spec) {
+        CompilerConfiguration configuration = new CompilerConfiguration();
+        configuration.setVerbose(spec.getGroovyCompileOptions().isVerbose());
+        configuration.setSourceEncoding(spec.getGroovyCompileOptions().getEncoding());
+        configuration.setTargetBytecode(spec.getTargetCompatibility());
+        configuration.setTargetDirectory(spec.getDestinationDir());
+        Map<String, Object> jointCompilationOptions = new HashMap<String, Object>();
+        jointCompilationOptions.put("stubDir", spec.getGroovyCompileOptions().getStubDir());
+        jointCompilationOptions.put("keepStubs", spec.getGroovyCompileOptions().isKeepStubs());
+        configuration.setJointCompilationOptions(jointCompilationOptions);
+
+        // Necessary for Groovy compilation to pick up output of regular and joint Java compilation,
+        // and for joint Java compilation to pick up the output of regular Java compilation.
+        // Assumes that output of regular Java compilation (which is not under this task's control) also goes
+        // into spec.getDestinationDir(). We could configure this on source set level, but then spec.getDestinationDir()
+        // would end up on the compile class path of every compile task for that source set, which may not be desirable.
+        spec.setClasspath(Iterables.concat(spec.getClasspath(), Collections.singleton(spec.getDestinationDir())));
+
+        URLClassLoader classPathLoader = new TransformingClassLoader(new DefaultClassPath(spec.getClasspath()));
+        GroovyClassLoader compileClasspathClassLoader = new GroovyClassLoader(classPathLoader, null);
+
+        FilteringClassLoader groovyCompilerClassLoader = new FilteringClassLoader(GroovyClassLoader.class.getClassLoader());
+        groovyCompilerClassLoader.allowPackage("org.codehaus.groovy");
+        groovyCompilerClassLoader.allowPackage("groovy");
+
+        // AST transforms need their own class loader that shares compiler classes with the compiler itself
+        final GroovyClassLoader astTransformClassLoader = new GroovyClassLoader(groovyCompilerClassLoader, null);
+        // can't delegate to compileClasspathLoader because this would result in ASTTransformation interface
+        // (which is implemented by the transform class) being loaded by compileClasspathClassLoader (which is
+        // where the transform class is loaded from)
+        for (File file : spec.getClasspath()) {
+            astTransformClassLoader.addClasspath(file.getPath());
+        }
+
+        JavaAwareCompilationUnit unit = new JavaAwareCompilationUnit(configuration, compileClasspathClassLoader) {
+            @Override
+            public GroovyClassLoader getTransformLoader() {
+                return astTransformClassLoader;
+            }
+        };
+        unit.addSources(Iterables.toArray(spec.getSource(), File.class));
+        unit.setCompilerFactory(new org.codehaus.groovy.tools.javac.JavaCompilerFactory() {
+            public JavaCompiler createCompiler(final CompilerConfiguration config) {
+                return new JavaCompiler() {
+                    public void compile(List<String> files, CompilationUnit cu) {
+                        spec.setSource(spec.getSource().filter(new Spec<File>() {
+                            public boolean isSatisfiedBy(File file) {
+                                return file.getName().endsWith(".java");
+                            }
+                        }));
+                        spec.getCompileOptions().getCompilerArgs().add("-sourcepath");
+                        spec.getCompileOptions().getCompilerArgs().add(((File) config.getJointCompilationOptions().get("stubDir")).getAbsolutePath());
+                        try {
+                            javaCompiler.execute(spec);
+                        } catch (CompilationFailedException e) {
+                            cu.getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(), cu));
+                        }
+                    }
+                };
+            }
+        });
+
+        try {
+            unit.compile();
+        } catch (org.codehaus.groovy.control.CompilationFailedException e) {
+            System.err.println(e.getMessage());
+            throw new CompilationFailedException();
+        }
+
+        return new SimpleWorkResult(true);
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
new file mode 100644
index 0000000..ea85ee7
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * Executes the Java command line compiler specified in {@code JavaCompileSpec.forkOptions.getExecutable()}.
+ */
+public class CommandLineJavaCompiler implements Compiler<JavaCompileSpec> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommandLineJavaCompiler.class);
+
+    private final CommandLineJavaCompilerArgumentsGenerator argumentsGenerator;
+    private final File workingDir;
+
+    public CommandLineJavaCompiler(TemporaryFileProvider tempFileProvider, File workingDir) {
+        argumentsGenerator = new CommandLineJavaCompilerArgumentsGenerator(tempFileProvider);
+        this.workingDir = workingDir;
+    }
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        String executable = spec.getCompileOptions().getForkOptions().getExecutable();
+        LOGGER.info("Compiling with Java command line compiler '{}'.", executable);
+
+        Iterable<String> args = argumentsGenerator.generate(spec);
+        ExecHandle handle = createCompilerHandle(executable, args);
+        executeCompiler(handle);
+
+        return new SimpleWorkResult(true);
+    }
+
+    private ExecHandle createCompilerHandle(String executable, Iterable<String> args) {
+        ExecHandleBuilder builder = new ExecHandleBuilder();
+        builder.setWorkingDir(workingDir);
+        builder.setExecutable(executable);
+        builder.setArgs(args);
+        builder.setIgnoreExitValue(true);
+        return builder.build();
+    }
+
+    private void executeCompiler(ExecHandle handle) {
+        handle.start();
+        ExecResult result = handle.waitForFinish();
+        if (result.getExitValue() != 0) {
+            throw new CompilationFailedException(result.getExitValue());
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java
new file mode 100644
index 0000000..c0963df
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.compile;
+
+import com.google.common.collect.Iterables;
+
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+public class CommandLineJavaCompilerArgumentsGenerator {
+    private final TemporaryFileProvider tempFileProvider;
+
+    public CommandLineJavaCompilerArgumentsGenerator(TemporaryFileProvider tempFileProvider) {
+        this.tempFileProvider = tempFileProvider;
+    }
+
+    public Iterable<String> generate(JavaCompileSpec spec) {
+        JavaCompilerArgumentsBuilder builder = new JavaCompilerArgumentsBuilder(spec);
+        List<String> launcherOptions = builder.includeLauncherOptions(true).includeMainOptions(false).includeSourceFiles(false).build();
+        List<String> remainingArgs = builder.includeLauncherOptions(false).includeMainOptions(true).includeSourceFiles(true).build();
+        Iterable<String> allArgs = Iterables.concat(launcherOptions, remainingArgs);
+        if (exceedsWindowsCommandLineLengthLimit(allArgs)) {
+            return Iterables.concat(launcherOptions, shortenArgs(remainingArgs));
+        }
+        return allArgs;
+    }
+
+    private boolean exceedsWindowsCommandLineLengthLimit(Iterable<String> args) {
+        int length = 0;
+        for (String arg : args) {
+            length += arg.length() + 1;
+            // limit is 2047 on older Windows systems, and 8191 on newer ones
+            // http://support.microsoft.com/kb/830473
+            // let's play it safe, no need to optimize
+            if (length > 1500) { return true; }
+        }
+        return false;
+    }
+
+    private Iterable<String> shortenArgs(List<String> args) {
+        File file = tempFileProvider.createTemporaryFile("compile-args", null, "java-compiler");
+        // use platform character and line encoding
+        GFileUtils.writeLines(file, quoteArgs(args));
+        return Collections.singleton("@" + file.getPath());
+    }
+
+    private List<String> quoteArgs(List<String> args) {
+        ListIterator<String> iterator = args.listIterator();
+        while (iterator.hasNext()) {
+            String arg = iterator.next();
+            // assumption: blindly quoting all args is fine
+            // as for how to quote, see http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javac.html#commandlineargfile
+            iterator.set("\"" + arg.replace("\\", "\\\\") + "\"");
+        }
+        return args;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompilationFailedException.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompilationFailedException.java
new file mode 100644
index 0000000..e196371
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompilationFailedException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.compile;
+
+public class CompilationFailedException extends RuntimeException {
+    public CompilationFailedException() {
+        super("Compilation failed; see the compiler error output for details.");
+    }
+
+    public CompilationFailedException(int exitCode) {
+        super(String.format("Compilation failed with exit code %d; see the compiler error output for details.", exitCode));
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompileSpec.java
new file mode 100644
index 0000000..ca2c159
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompileSpec.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 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.compile;
+
+public interface CompileSpec {
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java
index 601cb87..2b9cb68 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java
@@ -15,17 +15,8 @@
  */
 package org.gradle.api.internal.tasks.compile;
 
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.tasks.WorkResult;
 
-import java.io.File;
-
-public interface Compiler {
-    void setSource(FileCollection source);
-
-    void setDestinationDir(File destinationDir);
-
-    void setClasspath(Iterable<File> classpath);
-
-    WorkResult execute();
+public interface Compiler<T extends CompileSpec> {
+    WorkResult execute(T spec);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultGroovyJavaJointCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultGroovyJavaJointCompileSpec.java
new file mode 100644
index 0000000..caf43a3
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultGroovyJavaJointCompileSpec.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.tasks.compile.GroovyCompileOptions;
+
+import java.io.File;
+
+public class DefaultGroovyJavaJointCompileSpec extends DefaultJavaCompileSpec implements GroovyJavaJointCompileSpec {
+    private final GroovyCompileOptions compileOptions = new GroovyCompileOptions();
+    private Iterable<File> groovyClasspath;
+
+    public GroovyCompileOptions getGroovyCompileOptions() {
+        return compileOptions;
+    }
+
+    public Iterable<File> getGroovyClasspath() {
+        return groovyClasspath;
+    }
+
+    public void setGroovyClasspath(Iterable<File> groovyClasspath) {
+        this.groovyClasspath = groovyClasspath;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompileSpec.java
new file mode 100644
index 0000000..ce65051
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompileSpec.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.tasks.compile.CompileOptions;
+
+import java.io.File;
+
+public class DefaultJavaCompileSpec extends DefaultJvmLanguageCompileSpec implements JavaCompileSpec {
+    private String sourceCompatibility;
+    private String targetCompatibility;
+    private File dependencyCacheDir;
+    private final CompileOptions compileOptions = new CompileOptions();
+
+    public CompileOptions getCompileOptions() {
+        return compileOptions;
+    }
+
+    public File getDependencyCacheDir() {
+        return dependencyCacheDir;
+    }
+
+    public void setDependencyCacheDir(File dependencyCacheDir) {
+        this.dependencyCacheDir = dependencyCacheDir;
+    }
+
+    public String getSourceCompatibility() {
+        return sourceCompatibility;
+    }
+
+    public void setSourceCompatibility(String sourceCompatibility) {
+        this.sourceCompatibility = sourceCompatibility;
+    }
+
+    public String getTargetCompatibility() {
+        return targetCompatibility;
+    }
+
+    public void setTargetCompatibility(String targetCompatibility) {
+        this.targetCompatibility = targetCompatibility;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactory.java
new file mode 100644
index 0000000..eeffee8
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactory.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2011 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.compile;
+
+import org.gradle.api.AntBuilder;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.daemon.DaemonJavaCompiler;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.internal.Factory;
+
+public class DefaultJavaCompilerFactory implements JavaCompilerFactory {
+    private final static Logger LOGGER = Logging.getLogger(DefaultJavaCompilerFactory.class);
+
+    private final ProjectInternal project;
+    private final TemporaryFileProvider tempFileProvider;
+    private final Factory<AntBuilder> antBuilderFactory;
+    private final JavaCompilerFactory inProcessCompilerFactory;
+    private boolean groovyJointCompilation;
+
+    public DefaultJavaCompilerFactory(ProjectInternal project, TemporaryFileProvider tempFileProvider, Factory<AntBuilder> antBuilderFactory, JavaCompilerFactory inProcessCompilerFactory){
+        this.project = project;
+        this.tempFileProvider = tempFileProvider;
+        this.antBuilderFactory = antBuilderFactory;
+        this.inProcessCompilerFactory = inProcessCompilerFactory;
+    }
+
+    /**
+     * If true, the Java compiler to be created is used for joint compilation
+     * together with a Groovy compiler in the compiler daemon.
+     * In that case, the Groovy normalizing and daemon compilers should be used.
+     */
+    public void setGroovyJointCompilation(boolean flag) {
+        groovyJointCompilation = flag;
+    }
+
+    public Compiler<JavaCompileSpec> create(CompileOptions options) {
+        fallBackToAntIfNecessary(options);
+
+        if (options.isUseAnt()) {
+            return new AntJavaCompiler(antBuilderFactory);
+        }
+
+        Compiler<JavaCompileSpec> result = createTargetCompiler(options);
+        if (!groovyJointCompilation) {
+            result = new NormalizingJavaCompiler(result);
+        }
+        return result;
+    }
+
+    private void fallBackToAntIfNecessary(CompileOptions options) {
+        if (options.isUseAnt()) { return; }
+
+        if (options.getCompiler() != null) {
+            LOGGER.warn("Falling back to Ant javac task ('CompileOptions.useAnt = true') because 'CompileOptions.compiler' is set.");
+            options.setUseAnt(true);
+        }
+    }
+
+    private Compiler<JavaCompileSpec> createTargetCompiler(CompileOptions options) {
+        if (options.isFork() && options.getForkOptions().getExecutable() != null) {
+            return new CommandLineJavaCompiler(tempFileProvider, project.getProjectDir());
+        }
+
+        Compiler<JavaCompileSpec> compiler = inProcessCompilerFactory.create(options);
+        if (options.isFork() && !groovyJointCompilation) {
+            return new DaemonJavaCompiler(project, compiler);
+        }
+
+        return compiler;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJvmLanguageCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJvmLanguageCompileSpec.java
new file mode 100644
index 0000000..d59bb51
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DefaultJvmLanguageCompileSpec.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.file.FileCollection;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultJvmLanguageCompileSpec implements JvmLanguageCompileSpec, Serializable {
+    private Iterable<File> classpath;
+    private File destinationDir;
+    private FileCollection source;
+
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    public FileCollection getSource() {
+        return source;
+    }
+
+    public void setSource(FileCollection source) {
+        this.source = source;
+    }
+
+    public Iterable<File> getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(Iterable<File> classpath) {
+        this.classpath = classpath;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DelegatingGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DelegatingGroovyCompiler.java
new file mode 100644
index 0000000..d7c9874
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DelegatingGroovyCompiler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.tasks.WorkResult;
+
+public class DelegatingGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec> {
+    private final GroovyCompilerFactory compilerFactory;
+
+    public DelegatingGroovyCompiler(GroovyCompilerFactory compilerFactory) {
+        this.compilerFactory = compilerFactory;
+    }
+
+    public WorkResult execute(GroovyJavaJointCompileSpec spec) {
+        Compiler<GroovyJavaJointCompileSpec> delegate = compilerFactory.create(spec.getGroovyCompileOptions(), spec.getCompileOptions());
+        return delegate.execute(spec);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DelegatingJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DelegatingJavaCompiler.java
new file mode 100644
index 0000000..b414d72
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/DelegatingJavaCompiler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.compile;
+
+import org.gradle.api.tasks.WorkResult;
+
+public class DelegatingJavaCompiler implements Compiler<JavaCompileSpec> {
+    private final JavaCompilerFactory compilerFactory;
+    
+    public DelegatingJavaCompiler(JavaCompilerFactory compilerFactory) {
+        this.compilerFactory = compilerFactory;
+    }
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        Compiler<JavaCompileSpec> delegate = compilerFactory.create(spec.getCompileOptions());
+        return delegate.execute(spec);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileSpec.java
new file mode 100644
index 0000000..aa1e5f2
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompileSpec.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.tasks.compile.GroovyCompileOptions;
+
+import java.io.File;
+
+public interface GroovyCompileSpec extends JvmLanguageCompileSpec {
+    GroovyCompileOptions getGroovyCompileOptions();
+
+    Iterable<File> getGroovyClasspath();
+
+    void setGroovyClasspath(Iterable<File> classpath);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompiler.java
deleted file mode 100644
index 57523e4..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompiler.java
+++ /dev/null
@@ -1,26 +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.api.internal.tasks.compile;
-
-import org.gradle.api.tasks.compile.GroovyCompileOptions;
-
-import java.io.File;
-
-public interface GroovyCompiler extends Compiler {
-    GroovyCompileOptions getGroovyCompileOptions();
-
-    void setGroovyClasspath(Iterable<File> classpath);
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompilerFactory.java
new file mode 100644
index 0000000..2df95d4
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompilerFactory.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.project.IsolatedAntBuilder;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonFactory;
+import org.gradle.api.internal.tasks.compile.daemon.CompilerDaemonManager;
+import org.gradle.api.internal.tasks.compile.daemon.DaemonGroovyCompiler;
+import org.gradle.api.internal.tasks.compile.daemon.InProcessCompilerDaemonFactory;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.api.tasks.compile.GroovyCompileOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GroovyCompilerFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(GroovyCompilerFactory.class);
+    private final ProjectInternal project;
+    private final IsolatedAntBuilder antBuilder;
+    private final ClassPathRegistry classPathRegistry;
+    private final DefaultJavaCompilerFactory javaCompilerFactory;
+
+    public GroovyCompilerFactory(ProjectInternal project, IsolatedAntBuilder antBuilder, ClassPathRegistry classPathRegistry, DefaultJavaCompilerFactory javaCompilerFactory) {
+        this.project = project;
+        this.antBuilder = antBuilder;
+        this.classPathRegistry = classPathRegistry;
+        this.javaCompilerFactory = javaCompilerFactory;
+    }
+
+    Compiler<GroovyJavaJointCompileSpec> create(GroovyCompileOptions groovyOptions, CompileOptions javaOptions) {
+        // Some sanity checking of options
+        if (groovyOptions.isUseAnt() && !javaOptions.isUseAnt()) {
+            LOGGER.warn("When groovyOptions.useAnt is enabled, options.useAnt must also be enabled. Ignoring options.useAnt = false.");
+            javaOptions.setUseAnt(true);
+        } else if (!groovyOptions.isUseAnt() && javaOptions.isUseAnt()) {
+            LOGGER.warn("When groovyOptions.useAnt is disabled, options.useAnt must also be disabled. Ignoring options.useAnt = true.");
+            javaOptions.setUseAnt(false);
+        }
+
+        if (groovyOptions.isUseAnt()) {
+            return new AntGroovyCompiler(antBuilder, classPathRegistry);
+        }
+
+        javaCompilerFactory.setGroovyJointCompilation(true);
+        Compiler<JavaCompileSpec> javaCompiler = javaCompilerFactory.create(javaOptions);
+        Compiler<GroovyJavaJointCompileSpec> groovyCompiler = new ApiGroovyCompiler(javaCompiler);
+        CompilerDaemonFactory daemonFactory;
+        if (groovyOptions.isFork()) {
+            daemonFactory = CompilerDaemonManager.getInstance();
+        } else {
+            daemonFactory = InProcessCompilerDaemonFactory.getInstance();
+        }
+        groovyCompiler = new DaemonGroovyCompiler(project, groovyCompiler, daemonFactory);
+        return new NormalizingGroovyCompiler(groovyCompiler);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompileSpec.java
new file mode 100644
index 0000000..a43aaeb
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompileSpec.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 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.compile;
+
+public interface GroovyJavaJointCompileSpec extends JavaCompileSpec, GroovyCompileSpec {
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompiler.java
deleted file mode 100644
index 95a8756..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompiler.java
+++ /dev/null
@@ -1,19 +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.api.internal.tasks.compile;
-
-public interface GroovyJavaJointCompiler extends GroovyCompiler, JavaSourceCompiler {
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
new file mode 100644
index 0000000..89e5197
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.compile;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.util.ReflectionUtil;
+
+public class InProcessJavaCompilerFactory implements JavaCompilerFactory {
+    private static final boolean SUN_COMPILER_AVAILABLE = ReflectionUtil.isClassAvailable("com.sun.tools.javac.Main");
+
+    public Compiler<JavaCompileSpec> create(CompileOptions options) {
+        if (Jvm.current().isJava6Compatible()) {
+            return createJdk6Compiler();
+        }
+        if (SUN_COMPILER_AVAILABLE) {
+            return new SunCompilerFactory().create();
+        }
+        throw new RuntimeException("Cannot find a Java compiler API. Please let us know which JDK/platform you are using. To work around this problem, try 'compileJava.options.useAnt=true'.");
+    }
+
+    private Compiler<JavaCompileSpec> createJdk6Compiler() {
+        try {
+            // excluded when Gradle is compiled against JDK5, hence we can't reference it statically
+            Class<?> clazz = getClass().getClassLoader().loadClass("org.gradle.api.internal.tasks.compile.jdk6.Jdk6JavaCompiler");
+            return (Compiler<JavaCompileSpec>) clazz.newInstance();
+        } catch (Exception e) {
+            throw new GradleException("Internal error: couldn't load or instantiate class Jdk6JavaCompiler", e);
+        }
+    }
+
+    // nested class to enforce lazy class loading
+    private static class SunCompilerFactory {
+        Compiler<JavaCompileSpec> create() {
+            return new SunJavaCompiler();
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java
index fa8e089..01eafc9 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java
@@ -16,28 +16,23 @@
 package org.gradle.api.internal.tasks.compile;
 
 import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.tasks.compile.GroovyCompileOptions;
 
-import java.io.File;
-
-public class IncrementalGroovyCompiler extends IncrementalJavaSourceCompiler<GroovyJavaJointCompiler> implements GroovyJavaJointCompiler {
+public class IncrementalGroovyCompiler extends IncrementalJavaCompilerSupport<GroovyJavaJointCompileSpec> {
+    private final Compiler<GroovyJavaJointCompileSpec> compiler;
     private final TaskOutputsInternal taskOutputs;
 
-    public IncrementalGroovyCompiler(GroovyJavaJointCompiler compiler, TaskOutputsInternal taskOutputs) {
-        super(compiler);
+    public IncrementalGroovyCompiler(Compiler<GroovyJavaJointCompileSpec> compiler, TaskOutputsInternal taskOutputs) {
+        this.compiler = compiler;
         this.taskOutputs = taskOutputs;
     }
 
-    public GroovyCompileOptions getGroovyCompileOptions() {
-        return getCompiler().getGroovyCompileOptions();
-    }
-
-    public void setGroovyClasspath(Iterable<File> classpath) {
-        getCompiler().setGroovyClasspath(classpath);
+    @Override
+    protected Compiler<GroovyJavaJointCompileSpec> getCompiler() {
+        return compiler;
     }
 
     @Override
-    protected StaleClassCleaner createCleaner() {
+    protected StaleClassCleaner createCleaner(GroovyJavaJointCompileSpec spec) {
         return new SimpleStaleClassCleaner(taskOutputs);
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java
index 72060f5..2e40c72 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java
@@ -16,32 +16,30 @@
 package org.gradle.api.internal.tasks.compile;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.TaskOutputsInternal;
 
-import java.io.File;
-
-public class IncrementalJavaCompiler extends IncrementalJavaSourceCompiler<JavaCompiler> implements JavaCompiler {
+public class IncrementalJavaCompiler extends IncrementalJavaCompilerSupport<JavaCompileSpec> implements Compiler<JavaCompileSpec> {
+    private final Compiler<JavaCompileSpec> compiler;
     private final Factory<AntBuilder> antBuilderFactory;
     private final TaskOutputsInternal taskOutputs;
-    private File dependencyCacheDir;
 
-    public IncrementalJavaCompiler(JavaCompiler compiler, Factory<AntBuilder> antBuilderFactory,
-                                    TaskOutputsInternal taskOutputs) {
-        super(compiler);
+    public IncrementalJavaCompiler(Compiler<JavaCompileSpec> compiler, Factory<AntBuilder> antBuilderFactory,
+                                   TaskOutputsInternal taskOutputs) {
+        this.compiler = compiler;
         this.antBuilderFactory = antBuilderFactory;
         this.taskOutputs = taskOutputs;
     }
 
-    public void setDependencyCacheDir(File dir) {
-        dependencyCacheDir = dir;
-        getCompiler().setDependencyCacheDir(dir);
+    @Override
+    protected Compiler<JavaCompileSpec> getCompiler() {
+        return compiler;
     }
 
-    protected StaleClassCleaner createCleaner() {
-        if (getCompileOptions().isUseDepend()) {
-            AntDependsStaleClassCleaner cleaner = new AntDependsStaleClassCleaner((Factory) antBuilderFactory);
-            cleaner.setDependencyCacheDir(dependencyCacheDir);
+    protected StaleClassCleaner createCleaner(JavaCompileSpec spec) {
+        if (spec.getCompileOptions().isUseDepend()) {
+            AntDependsStaleClassCleaner cleaner = new AntDependsStaleClassCleaner(antBuilderFactory);
+            cleaner.setDependencyCacheDir(spec.getDependencyCacheDir());
             return cleaner;
         } else {
             return new SimpleStaleClassCleaner(taskOutputs);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerSupport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerSupport.java
new file mode 100644
index 0000000..90cecb3
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerSupport.java
@@ -0,0 +1,39 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.tasks.WorkResult;
+
+/**
+ * A dumb incremental compiler. Deletes stale classes before invoking the actual compiler
+ */
+public abstract class IncrementalJavaCompilerSupport<T extends JavaCompileSpec> implements Compiler<T> {
+    public WorkResult execute(T spec) {
+        StaleClassCleaner cleaner = createCleaner(spec);
+
+        cleaner.setDestinationDir(spec.getDestinationDir());
+        cleaner.setSource(spec.getSource());
+        cleaner.setCompileOptions(spec.getCompileOptions());
+        cleaner.execute();
+
+        Compiler<? super T> compiler = getCompiler();
+        return compiler.execute(spec);
+    }
+
+    protected abstract Compiler<T> getCompiler();
+
+    protected abstract StaleClassCleaner createCleaner(T spec);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompiler.java
deleted file mode 100644
index ce8b918..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompiler.java
+++ /dev/null
@@ -1,77 +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.api.internal.tasks.compile;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.WorkResult;
-import org.gradle.api.tasks.compile.CompileOptions;
-
-import java.io.File;
-
-/**
- * A dumb incremental compiler. Deletes stale classes before invoking the actual compiler
- */
-public abstract class IncrementalJavaSourceCompiler<T extends JavaSourceCompiler> implements JavaSourceCompiler {
-    private final T compiler;
-    private FileCollection source;
-    private File destinationDir;
-
-    public IncrementalJavaSourceCompiler(T compiler) {
-        this.compiler = compiler;
-    }
-
-    public T getCompiler() {
-        return compiler;
-    }
-
-    public CompileOptions getCompileOptions() {
-        return compiler.getCompileOptions();
-    }
-
-    public void setSourceCompatibility(String sourceCompatibility) {
-        compiler.setSourceCompatibility(sourceCompatibility);
-    }
-
-    public void setTargetCompatibility(String targetCompatibility) {
-        compiler.setTargetCompatibility(targetCompatibility);
-    }
-
-    public void setSource(FileCollection source) {
-        this.source = source;
-        compiler.setSource(source);
-    }
-
-    public void setDestinationDir(File destinationDir) {
-        this.destinationDir = destinationDir;
-        compiler.setDestinationDir(destinationDir);
-    }
-
-    public void setClasspath(Iterable<File> classpath) {
-        compiler.setClasspath(classpath);
-    }
-
-    public WorkResult execute() {
-        StaleClassCleaner cleaner = createCleaner();
-        cleaner.setDestinationDir(destinationDir);
-        cleaner.setSource(source);
-        cleaner.setCompileOptions(compiler.getCompileOptions());
-        cleaner.execute();
-
-        return compiler.execute();
-    }
-
-    protected abstract StaleClassCleaner createCleaner();
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompileSpec.java
new file mode 100644
index 0000000..9f30458
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompileSpec.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.tasks.compile.CompileOptions;
+
+import java.io.File;
+
+public interface JavaCompileSpec extends JvmLanguageCompileSpec {
+    CompileOptions getCompileOptions();
+
+    File getDependencyCacheDir();
+
+    void setDependencyCacheDir(File dependencyCacheDir);
+
+    String getSourceCompatibility();
+
+    void setSourceCompatibility(String sourceCompatibility);
+
+    String getTargetCompatibility();
+
+    void setTargetCompatibility(String targetCompatibility);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompiler.java
deleted file mode 100644
index f12c2cb..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompiler.java
+++ /dev/null
@@ -1,22 +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.api.internal.tasks.compile;
-
-import java.io.File;
-
-public interface JavaCompiler extends JavaSourceCompiler {
-    void setDependencyCacheDir(File dir);
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java
new file mode 100644
index 0000000..3cf154a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2012 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.compile;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.JavaVersion;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.api.tasks.compile.ForkOptions;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JavaCompilerArgumentsBuilder {
+    private final JavaCompileSpec spec;
+
+    private boolean includeLauncherOptions;
+    private boolean includeMainOptions = true;
+    private boolean includeSourceFiles;
+
+    private List<String> args;
+
+    public JavaCompilerArgumentsBuilder(JavaCompileSpec spec) {
+        this.spec = spec;
+    }
+
+    public JavaCompilerArgumentsBuilder includeLauncherOptions(boolean flag) {
+        includeLauncherOptions = flag;
+        return this;
+    }
+
+    public JavaCompilerArgumentsBuilder includeMainOptions(boolean flag) {
+        includeMainOptions = flag;
+        return this;
+    }
+
+    public JavaCompilerArgumentsBuilder includeSourceFiles(boolean flag) {
+        includeSourceFiles = flag;
+        return this;
+    }
+
+    public List<String> build() {
+        args = new ArrayList<String>();
+
+        addLauncherOptions();
+        addMainOptions();
+        addSourceFiles();
+
+        return args;
+    }
+
+    private void addLauncherOptions() {
+        if (!includeLauncherOptions) { return; }
+
+        ForkOptions forkOptions = spec.getCompileOptions().getForkOptions();
+        if (forkOptions.getMemoryInitialSize() != null) {
+            args.add("-J-Xms" + forkOptions.getMemoryInitialSize().trim());
+        }
+        if (forkOptions.getMemoryMaximumSize() != null) {
+            args.add("-J-Xmx" + forkOptions.getMemoryMaximumSize().trim());
+        }
+        if (forkOptions.getJvmArgs() != null) {
+            args.addAll(forkOptions.getJvmArgs());
+        }
+    }
+
+    private void addMainOptions() {
+        if (!includeMainOptions) { return; }
+
+        String sourceCompatibility = spec.getSourceCompatibility();
+        if (sourceCompatibility != null && !JavaVersion.current().equals(JavaVersion.toVersion(sourceCompatibility))) {
+            args.add("-source");
+            args.add(sourceCompatibility);
+        }
+        String targetCompatibility = spec.getTargetCompatibility();
+        if (targetCompatibility != null && !JavaVersion.current().equals(JavaVersion.toVersion(targetCompatibility))) {
+            args.add("-target");
+            args.add(targetCompatibility);
+        }
+        File destinationDir = spec.getDestinationDir();
+        if (destinationDir != null) {
+            args.add("-d");
+            args.add(destinationDir.getPath());
+        }
+        CompileOptions compileOptions = spec.getCompileOptions();
+        if (compileOptions.isVerbose()) {
+            args.add("-verbose");
+        }
+        if (compileOptions.isDeprecation()) {
+            args.add("-deprecation");
+        }
+        if (!compileOptions.isWarnings()) {
+            args.add("-nowarn");
+        }
+        if (compileOptions.isDebug()) {
+            if (compileOptions.getDebugOptions().getDebugLevel() != null) {
+                args.add("-g:" + compileOptions.getDebugOptions().getDebugLevel().trim());
+            } else {
+                args.add("-g");
+            }
+        } else {
+            args.add("-g:none");
+        }
+        if (compileOptions.getEncoding() != null) {
+            args.add("-encoding");
+            args.add(compileOptions.getEncoding());
+        }
+        if (compileOptions.getBootClasspath() != null) {
+            args.add("-bootclasspath");
+            args.add(compileOptions.getBootClasspath());
+        }
+        if (compileOptions.getExtensionDirs() != null) {
+            args.add("-extdirs");
+            args.add(compileOptions.getExtensionDirs());
+        }
+        Iterable<File> classpath = spec.getClasspath();
+        if (classpath != null && classpath.iterator().hasNext()) {
+            args.add("-classpath");
+            args.add(toFileCollection(classpath).getAsPath());
+        }
+        if (compileOptions.getCompilerArgs() != null) {
+            args.addAll(compileOptions.getCompilerArgs());
+        }
+    }
+
+    private void addSourceFiles() {
+        if (!includeSourceFiles) { return; }
+
+        for (File file : spec.getSource()) {
+            args.add(file.getPath());
+        }
+    }
+
+    private FileCollection toFileCollection(Iterable<File> classpath) {
+        if (classpath instanceof FileCollection) {
+            return (FileCollection) classpath;
+        }
+        return new SimpleFileCollection(Lists.newArrayList(classpath));
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerFactory.java
new file mode 100644
index 0000000..8cfcfe6
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.compile;
+
+import org.gradle.api.tasks.compile.CompileOptions;
+
+/**
+ * Creates Java compilers based on the provided compile options.
+ */
+public interface JavaCompilerFactory {
+    Compiler<JavaCompileSpec> create(CompileOptions options);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaSourceCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaSourceCompiler.java
deleted file mode 100644
index b411b45..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaSourceCompiler.java
+++ /dev/null
@@ -1,26 +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.api.internal.tasks.compile;
-
-import org.gradle.api.tasks.compile.CompileOptions;
-
-public interface JavaSourceCompiler extends Compiler {
-    CompileOptions getCompileOptions();
-
-    void setSourceCompatibility(String sourceCompatibility);
-
-    void setTargetCompatibility(String targetCompatibility);
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JvmLanguageCompileSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JvmLanguageCompileSpec.java
new file mode 100644
index 0000000..fba2c6c
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JvmLanguageCompileSpec.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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.compile;
+
+import org.gradle.api.file.FileCollection;
+
+import java.io.File;
+
+public interface JvmLanguageCompileSpec extends CompileSpec {
+    File getDestinationDir();
+
+    void setDestinationDir(File destinationDir);
+
+    FileCollection getSource();
+
+    void setSource(FileCollection source);
+
+    Iterable<File> getClasspath();
+
+    void setClasspath(Iterable<File> classpath);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
new file mode 100644
index 0000000..37518a2
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 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.compile;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A Groovy {@link org.gradle.api.internal.tasks.compile.Compiler} which does some normalization of the compile configuration and behaviour before delegating to some other compiler.
+ */
+public class NormalizingGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec> {
+    private static final Logger LOGGER = Logging.getLogger(NormalizingGroovyCompiler.class);
+    private final Compiler<GroovyJavaJointCompileSpec> delegate;
+
+    public NormalizingGroovyCompiler(Compiler<GroovyJavaJointCompileSpec> delegate) {
+        this.delegate = delegate;
+    }
+
+    public WorkResult execute(GroovyJavaJointCompileSpec spec) {
+        resolveAndFilterSourceFiles(spec);
+        resolveClasspath(spec);
+        resolveNonStringsInCompilerArgs(spec);
+        logSourceFiles(spec);
+        logCompilerArguments(spec);
+        return delegateAndHandleErrors(spec);
+    }
+
+    private void resolveAndFilterSourceFiles(GroovyJavaJointCompileSpec spec) {
+        FileCollection groovyJavaOnly = spec.getSource().filter(new Spec<File>() {
+            public boolean isSatisfiedBy(File element) {
+                return element.getName().endsWith(".groovy") || element.getName().endsWith(".java");
+            }
+        });
+
+        spec.setSource(new SimpleFileCollection(groovyJavaOnly.getFiles()));
+    }
+
+    private void resolveClasspath(GroovyJavaJointCompileSpec spec) {
+        spec.setClasspath(new SimpleFileCollection(Lists.newArrayList(spec.getClasspath())));
+        spec.setGroovyClasspath(new SimpleFileCollection(Lists.newArrayList(spec.getGroovyClasspath())));
+    }
+
+    private void resolveNonStringsInCompilerArgs(GroovyJavaJointCompileSpec spec) {
+        // in particular, this is about GStrings
+        spec.getCompileOptions().setCompilerArgs(CollectionUtils.toStringList(spec.getCompileOptions().getCompilerArgs()));
+    }
+
+    private void logSourceFiles(GroovyJavaJointCompileSpec spec) {
+        if (!spec.getCompileOptions().isListFiles()) { return; }
+
+        StringBuilder builder = new StringBuilder();
+        builder.append("Source files to be compiled:");
+        for (File file : spec.getSource()) {
+            builder.append('\n');
+            builder.append(file);
+        }
+
+        LOGGER.quiet(builder.toString());
+    }
+
+    private void logCompilerArguments(GroovyJavaJointCompileSpec spec) {
+        if (!LOGGER.isDebugEnabled()) { return; }
+
+        List<String> compilerArgs = new JavaCompilerArgumentsBuilder(spec).includeLauncherOptions(true).includeSourceFiles(true).build();
+        String joinedArgs = Joiner.on(' ').join(compilerArgs);
+        LOGGER.debug("Java compiler arguments: {}", joinedArgs);
+    }
+
+    private WorkResult delegateAndHandleErrors(GroovyJavaJointCompileSpec spec) {
+        try {
+            return delegate.execute(spec);
+        } catch (CompilationFailedException e) {
+            if (spec.getCompileOptions().isFailOnError()) {
+                throw e;
+            }
+            LOGGER.debug("Ignoring compilation failure.");
+            return new SimpleWorkResult(false);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java
new file mode 100644
index 0000000..21fdb2b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompiler.java
@@ -0,0 +1,104 @@
+/*
+ * 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.tasks.compile;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A Java {@link Compiler} which does some normalization of the compile configuration and behaviour before delegating to some other compiler.
+ */
+public class NormalizingJavaCompiler implements Compiler<JavaCompileSpec> {
+    private static final Logger LOGGER = Logging.getLogger(NormalizingJavaCompiler.class);
+    private final Compiler<JavaCompileSpec> delegate;
+
+    public NormalizingJavaCompiler(Compiler<JavaCompileSpec> delegate) {
+        this.delegate = delegate;
+    }
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        resolveAndFilterSourceFiles(spec);
+        resolveClasspath(spec);
+        resolveNonStringsInCompilerArgs(spec);
+        logSourceFiles(spec);
+        logCompilerArguments(spec);
+        return delegateAndHandleErrors(spec);
+    }
+
+    private void resolveAndFilterSourceFiles(JavaCompileSpec spec) {
+        // this mimics the behavior of the Ant javac task (and therefore AntJavaCompiler),
+        // which silently excludes files not ending in .java
+        FileCollection javaOnly = spec.getSource().filter(new Spec<File>() {
+            public boolean isSatisfiedBy(File element) {
+                return element.getName().endsWith(".java");
+            }
+        });
+
+        spec.setSource(new SimpleFileCollection(javaOnly.getFiles()));
+    }
+
+    private void resolveClasspath(JavaCompileSpec spec) {
+        spec.setClasspath(new SimpleFileCollection(Lists.newArrayList(spec.getClasspath())));
+    }
+
+    private void resolveNonStringsInCompilerArgs(JavaCompileSpec spec) {
+        // in particular, this is about GStrings
+        spec.getCompileOptions().setCompilerArgs(CollectionUtils.toStringList(spec.getCompileOptions().getCompilerArgs()));
+    }
+
+    private void logSourceFiles(JavaCompileSpec spec) {
+        if (!spec.getCompileOptions().isListFiles()) { return; }
+
+        StringBuilder builder = new StringBuilder();
+        builder.append("Source files to be compiled:");
+        for (File file : spec.getSource()) {
+            builder.append('\n');
+            builder.append(file);
+        }
+
+        LOGGER.quiet(builder.toString());
+    }
+
+    private void logCompilerArguments(JavaCompileSpec spec) {
+        if (!LOGGER.isDebugEnabled()) { return; }
+
+        List<String> compilerArgs = new JavaCompilerArgumentsBuilder(spec).includeLauncherOptions(true).includeSourceFiles(true).build();
+        String joinedArgs = Joiner.on(' ').join(compilerArgs);
+        LOGGER.debug("Compiler arguments: {}", joinedArgs);
+    }
+
+    private WorkResult delegateAndHandleErrors(JavaCompileSpec spec) {
+        try {
+            return delegate.execute(spec);
+        } catch (CompilationFailedException e) {
+            if (spec.getCompileOptions().isFailOnError()) {
+                throw e;
+            }
+            LOGGER.debug("Ignoring compilation failure.");
+            return new SimpleWorkResult(false);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleWorkResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleWorkResult.java
new file mode 100644
index 0000000..c1a8993
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleWorkResult.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.compile;
+
+import org.gradle.api.tasks.WorkResult;
+
+public class SimpleWorkResult implements WorkResult {
+    private final boolean didWork;
+
+    public SimpleWorkResult(boolean didWork) {
+        this.didWork = didWork;
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java
new file mode 100644
index 0000000..4b15316
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SunJavaCompiler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.compile;
+
+import com.sun.tools.javac.Main;
+import org.gradle.api.tasks.WorkResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class SunJavaCompiler implements Compiler<JavaCompileSpec>, Serializable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SunJavaCompiler.class);
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        LOGGER.info("Compiling with Sun Java compiler API.");
+
+        String[] options = createCommandLineOptions(spec);
+        int exitCode = Main.compile(options);
+        if (exitCode != 0) {
+            throw new CompilationFailedException();
+        }
+
+        return new SimpleWorkResult(true);
+    }
+
+    private String[] createCommandLineOptions(JavaCompileSpec spec) {
+        List<String> options = new JavaCompilerArgumentsBuilder(spec).includeSourceFiles(true).build();
+        return options.toArray(new String[options.size()]);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java
new file mode 100644
index 0000000..fd442ad
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2012 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.compile;
+
+import com.google.common.io.ByteStreams;
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.ClassPath;
+import org.gradle.util.MutableURLClassLoader;
+import org.objectweb.asm.*;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transforms @GroovyASTTransformationClass(classes = {classLiterals}) into @GroovyASTTransformationClass([classNames]), to work around Groovy's
+ * loading of transformer classes.
+ */
+class TransformingClassLoader extends MutableURLClassLoader {
+    private static final String ANNOTATION_DESCRIPTOR = Type.getType(GroovyASTTransformationClass.class).getDescriptor();
+
+    public TransformingClassLoader(ClassPath classpath) {
+        super(null, classpath);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        URL resource = findResource(name.replace(".", "/") + ".class");
+        if (resource == null) {
+            throw new ClassNotFoundException(name);
+        }
+        try {
+            byte[] bytes = loadBytecode(resource);
+            bytes = transform(bytes);
+            return super.defineClass(name, bytes, 0, bytes.length);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private byte[] loadBytecode(URL resource) throws IOException {
+        InputStream inputStream = resource.openStream();
+        try {
+            return ByteStreams.toByteArray(inputStream);
+        } finally {
+            inputStream.close();
+        }
+    }
+
+    private byte[] transform(byte[] bytes) {
+        // First scan for annotation, and short circuit transformation if not present
+        ClassReader classReader = new ClassReader(bytes);
+
+        AnnotationDetector detector = new AnnotationDetector();
+        classReader.accept(detector, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
+        if (!detector.found) {
+            return bytes;
+        }
+
+        ClassWriter classWriter = new ClassWriter(0);
+        classReader.accept(new TransformingAdapter(classWriter), 0);
+        bytes = classWriter.toByteArray();
+        return bytes;
+    }
+
+    private static class AnnotationDetector implements ClassVisitor {
+        private boolean found;
+
+        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        }
+
+        public void visitSource(String source, String debug) {
+        }
+
+        public void visitOuterClass(String owner, String name, String desc) {
+        }
+
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            if (desc.equals(ANNOTATION_DESCRIPTOR)) {
+                found = true;
+            }
+            return null;
+        }
+
+        public void visitAttribute(Attribute attr) {
+        }
+
+        public void visitInnerClass(String name, String outerName, String innerName, int access) {
+        }
+
+        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+            return null;
+        }
+
+        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+            return null;
+        }
+
+        public void visitEnd() {
+        }
+    }
+
+    private static class TransformingAdapter extends ClassAdapter {
+        public TransformingAdapter(ClassWriter classWriter) {
+            super(classWriter);
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            if (desc.equals(ANNOTATION_DESCRIPTOR)) {
+                return new AnnotationTransformingVisitor(super.visitAnnotation(desc, visible));
+            }
+            return super.visitAnnotation(desc, visible);
+        }
+
+        private static class AnnotationTransformingVisitor implements AnnotationVisitor {
+            private final AnnotationVisitor annotationVisitor;
+            private final List<String> names = new ArrayList<String>();
+
+            public AnnotationTransformingVisitor(AnnotationVisitor annotationVisitor) {
+                this.annotationVisitor = annotationVisitor;
+            }
+
+            public void visit(String name, Object value) {
+                annotationVisitor.visit(name, value);
+            }
+
+            public void visitEnum(String name, String desc, String value) {
+                annotationVisitor.visitEnum(name, desc, value);
+            }
+
+            public AnnotationVisitor visitAnnotation(String name, String desc) {
+                return annotationVisitor.visitAnnotation(name, desc);
+            }
+
+            public AnnotationVisitor visitArray(String name) {
+                if (name.equals("classes")) {
+                    return new EmptyVisitor(){
+                        @Override
+                        public void visit(String name, Object value) {
+                            Type type = (Type) value;
+                            names.add(type.getClassName());
+                        }
+                    };
+                } else if (name.equals("value")) {
+                    return new EmptyVisitor() {
+                        @Override
+                        public void visit(String name, Object value) {
+                            String type = (String) value;
+                            names.add(type);
+                        }
+                    };
+                } else {
+                    return annotationVisitor.visitArray(name);
+                }
+            }
+
+            public void visitEnd() {
+                if (!names.isEmpty()) {
+                    AnnotationVisitor visitor = annotationVisitor.visitArray("value");
+                    for (String name : names) {
+                        visitor.visit(null, name);
+                    }
+                    visitor.visitEnd();
+                }
+                annotationVisitor.visitEnd();
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompileResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompileResult.java
new file mode 100644
index 0000000..74d3d66
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompileResult.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.tasks.WorkResult;
+
+import java.io.Serializable;
+
+public class CompileResult implements WorkResult, Serializable {
+    private final boolean didWork;
+    private final Throwable exception;
+
+    public CompileResult(boolean didWork, @Nullable Throwable exception) {
+        this.didWork = didWork;
+        this.exception = exception;
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+
+    @Nullable
+    public Throwable getException() {
+        return exception;
+    }
+
+    public boolean isSuccess() {
+        return exception == null;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemon.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemon.java
new file mode 100644
index 0000000..8751d3f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemon.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+import org.gradle.api.internal.tasks.compile.Compiler;
+
+/**
+ * A service that executes compilers in a (potentially) long-lived process.
+ */
+public interface CompilerDaemon {
+    <T extends CompileSpec> CompileResult execute(Compiler<T> compiler, T spec);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonClient.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonClient.java
new file mode 100644
index 0000000..9c07da1
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonClient.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+
+public class CompilerDaemonClient implements CompilerDaemon, CompilerDaemonClientProtocol, Stoppable {
+    private final DaemonForkOptions forkOptions;
+    private final CompilerDaemonServerProtocol server;
+    private final BlockingQueue<CompileResult> compileResults = new SynchronousQueue<CompileResult>();
+
+    public CompilerDaemonClient(DaemonForkOptions forkOptions, CompilerDaemonServerProtocol server) {
+        this.forkOptions = forkOptions;
+        this.server = server;
+    }
+
+    public <T extends CompileSpec> CompileResult execute(Compiler<T> compiler, T spec) {
+        server.execute(compiler, spec);
+        try {
+            return compileResults.take();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public boolean isCompatibleWith(DaemonForkOptions required) {
+        return forkOptions.isCompatibleWith(required);
+    }
+
+    public void stop() {
+        server.stop();
+    }
+
+    public void executed(CompileResult result) {
+        try {
+            compileResults.put(result);
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonClientProtocol.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonClientProtocol.java
new file mode 100644
index 0000000..f8d6f01
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonClientProtocol.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+/**
+ * Client part of the compiler daemon protocol. Used to report back compilation results.
+ */
+public interface CompilerDaemonClientProtocol {
+    void executed(CompileResult result);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java
new file mode 100644
index 0000000..1e22f4b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.compile.daemon;
+
+import org.gradle.api.internal.project.ProjectInternal;
+
+public interface CompilerDaemonFactory {
+    CompilerDaemon getDaemon(ProjectInternal project, DaemonForkOptions forkOptions);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java
new file mode 100644
index 0000000..e29882b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import net.jcip.annotations.NotThreadSafe;
+
+import org.gradle.BuildAdapter;
+import org.gradle.BuildResult;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.process.internal.WorkerProcess;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+import java.io.File;
+
+/**
+ * Controls the lifecycle of the compiler daemon and provides access to it.
+ */
+ at NotThreadSafe
+public class CompilerDaemonManager implements CompilerDaemonFactory {
+    private static final Logger LOGGER = Logging.getLogger(CompilerDaemonManager.class);
+    private static final CompilerDaemonManager INSTANCE = new CompilerDaemonManager();
+    
+    private volatile CompilerDaemonClient client;
+    private volatile WorkerProcess process;
+    
+    public static CompilerDaemonManager getInstance() {
+        return INSTANCE;
+    }
+    
+    public CompilerDaemon getDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
+        if (client != null && !client.isCompatibleWith(forkOptions)) {
+            stop();
+        }
+        if (client == null) {
+            startDaemon(project, forkOptions);
+            stopDaemonOnceBuildFinished(project);
+        }
+        return client;
+    }
+    
+    public void stop() {
+        if (client == null) {
+            return;
+        }
+
+        LOGGER.info("Stopping Gradle compiler daemon.");
+
+        client.stop();
+        client = null;
+        process.waitForStop();
+        process = null;
+
+        LOGGER.info("Gradle compiler daemon stopped.");
+    }
+    
+    private void startDaemon(ProjectInternal project, DaemonForkOptions forkOptions) {
+        LOGGER.info("Starting Gradle compiler daemon.");
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug(forkOptions.toString());
+        }
+
+        WorkerProcessBuilder builder = project.getServices().getFactory(WorkerProcessBuilder.class).create();
+        builder.setLogLevel(project.getGradle().getStartParameter().getLogLevel()); // NOTE: might make sense to respect per-compile-task log level
+        builder.applicationClasspath(forkOptions.getClasspath());
+        builder.sharedPackages(forkOptions.getSharedPackages());
+        File toolsJar = Jvm.current().getToolsJar();
+        if (toolsJar != null) {
+            builder.getApplicationClasspath().add(toolsJar); // for SunJavaCompiler
+        }
+        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
+        javaCommand.setMinHeapSize(forkOptions.getMinHeapSize());
+        javaCommand.setMaxHeapSize(forkOptions.getMaxHeapSize());
+        javaCommand.setJvmArgs(forkOptions.getJvmArgs());
+        javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
+        process = builder.worker(new CompilerDaemonServer()).build();
+        process.start();
+        CompilerDaemonServerProtocol server = process.getConnection().addOutgoing(CompilerDaemonServerProtocol.class);
+        client = new CompilerDaemonClient(forkOptions, server);
+        process.getConnection().addIncoming(CompilerDaemonClientProtocol.class, client);
+
+        LOGGER.info("Gradle compiler daemon started.");
+    }
+    
+    private void stopDaemonOnceBuildFinished(ProjectInternal project) {
+        project.getGradle().addBuildListener(new BuildAdapter() {
+            @Override
+            public void buildFinished(BuildResult result) {
+                stop();
+            }
+        });
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java
new file mode 100644
index 0000000..bd4ae6d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.UncheckedException;
+import org.gradle.process.internal.WorkerProcessContext;
+
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+
+
+public class CompilerDaemonServer implements Action<WorkerProcessContext>, CompilerDaemonServerProtocol, Serializable {
+    private static final Logger LOGGER = Logging.getLogger(CompilerDaemonServer.class);
+    
+    private volatile CompilerDaemonClientProtocol client;
+    private volatile CountDownLatch stop;
+    
+    public void execute(WorkerProcessContext context) {
+        client = context.getServerConnection().addOutgoing(CompilerDaemonClientProtocol.class);
+        stop = new CountDownLatch(1);
+        context.getServerConnection().addIncoming(CompilerDaemonServerProtocol.class, this);
+        try {
+            stop.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+    }
+
+    public <T extends CompileSpec> void execute(Compiler<T> compiler, T spec) {
+        try {
+            LOGGER.info("Executing {} in compiler daemon.", compiler);
+            WorkResult result = compiler.execute(spec);
+            LOGGER.info("Successfully executed {} in compiler daemon.", compiler);
+            client.executed(new CompileResult(result.getDidWork(), null));
+        } catch (Throwable t) {
+            LOGGER.info("Exception executing {} in compiler daemon: {}.", compiler, t);
+            client.executed(new CompileResult(true, t));
+        }
+    }
+
+    public void stop() {
+        stop.countDown();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java
new file mode 100644
index 0000000..c0b6823
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonServerProtocol.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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.compile.daemon;
+
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.Stoppable;
+
+/**
+ * Server part of the compiler daemon protocol. Used to submit compilation jobs.
+ */
+public interface CompilerDaemonServerProtocol extends Stoppable {
+    <T extends CompileSpec> void execute(Compiler<T> compiler, T spec);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptions.java
new file mode 100644
index 0000000..2699774
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptions.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Sets;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Nullable;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+public class DaemonForkOptions {
+    private final String minHeapSize;
+    private final String maxHeapSize;
+    private final Iterable<String> jvmArgs;
+    private final Iterable<File> classpath;
+    private final Iterable<String> sharedPackages;
+
+    public DaemonForkOptions(@Nullable String minHeapSize, @Nullable String maxHeapSize, Iterable<String> jvmArgs) {
+        this(minHeapSize, maxHeapSize, jvmArgs, Collections.<File>emptyList(), Collections.<String>emptyList());
+    }
+    
+    public DaemonForkOptions(@Nullable String minHeapSize, @Nullable String maxHeapSize, Iterable<String> jvmArgs, Iterable<File> classpath,
+                             Iterable<String> sharedPackages) {
+        this.minHeapSize = minHeapSize;
+        this.maxHeapSize = maxHeapSize;
+        this.jvmArgs = jvmArgs;
+        this.classpath = classpath;
+        this.sharedPackages = sharedPackages;
+    }
+
+    public String getMinHeapSize() {
+        return minHeapSize;
+    }
+
+    public String getMaxHeapSize() {
+        return maxHeapSize;
+    }
+
+    public Iterable<String> getJvmArgs() {
+        return jvmArgs;
+    }
+
+    public Iterable<File> getClasspath() {
+        return classpath;
+    }
+
+    public Iterable<String> getSharedPackages() {
+        return sharedPackages;
+    }
+
+    public boolean isCompatibleWith(DaemonForkOptions other) {
+        return getHeapSizeMb(minHeapSize) >= getHeapSizeMb(other.getMinHeapSize())
+                && getHeapSizeMb(maxHeapSize) >= getHeapSizeMb(other.getMaxHeapSize())
+                && getNormalizedJvmArgs(jvmArgs).containsAll(getNormalizedJvmArgs(other.getJvmArgs()))
+                && getNormalizedClasspath(classpath).containsAll(getNormalizedClasspath(other.getClasspath()))
+                && getNormalizedSharedPackages(sharedPackages).containsAll(getNormalizedSharedPackages(other.sharedPackages));
+    }
+
+    // one way to merge fork options, good for current use case
+    public DaemonForkOptions mergeWith(DaemonForkOptions other) {
+        String mergedMinHeapSize = mergeHeapSize(minHeapSize, other.minHeapSize);
+        String mergedMaxHeapSize = mergeHeapSize(maxHeapSize, other.maxHeapSize);
+        Set<String> mergedJvmArgs = getNormalizedJvmArgs(jvmArgs);
+        mergedJvmArgs.addAll(getNormalizedJvmArgs(other.getJvmArgs()));
+        Set<File> mergedClasspath = getNormalizedClasspath(classpath);
+        mergedClasspath.addAll(getNormalizedClasspath(other.classpath));
+        Set<String> mergedAllowedPackages = getNormalizedSharedPackages(sharedPackages);
+        mergedAllowedPackages.addAll(getNormalizedSharedPackages(other.sharedPackages));
+        return new DaemonForkOptions(mergedMinHeapSize, mergedMaxHeapSize, mergedJvmArgs, mergedClasspath, mergedAllowedPackages);
+    }
+
+    private int getHeapSizeMb(String heapSize) {
+        if (heapSize == null) {
+            return -1; // unspecified
+        }
+
+        String normalized = heapSize.trim().toLowerCase();
+        try {
+            if (normalized.endsWith("m")) {
+                return Integer.parseInt(normalized.substring(0, normalized.length() - 1));
+            }
+            if (normalized.endsWith("g")) {
+                return Integer.parseInt(normalized.substring(0, normalized.length() - 1)) * 1024;
+            }
+        } catch (NumberFormatException e) {
+            throw new InvalidUserDataException("Cannot parse heap size: " + heapSize, e);
+        }
+        throw new InvalidUserDataException("Cannot parse heap size: " + heapSize);
+    }
+    
+    private String mergeHeapSize(String heapSize1, String heapSize2) {
+        int mergedHeapSizeMb = Math.max(getHeapSizeMb(heapSize1), getHeapSizeMb(heapSize2));
+        return mergedHeapSizeMb == -1 ? null : String.valueOf(mergedHeapSizeMb) + "m";
+    }
+    
+    private Set<String> getNormalizedJvmArgs(Iterable<String> jvmArgs) {
+        Set<String> normalized = Sets.newLinkedHashSet();
+        for (String jvmArg : jvmArgs) {
+            normalized.add(jvmArg.trim());
+        }
+        return normalized;
+    }
+    
+    private Set<File> getNormalizedClasspath(Iterable<File> classpath) {
+        return Sets.newLinkedHashSet(classpath);
+    }
+    
+    private Set<String> getNormalizedSharedPackages(Iterable<String> allowedPackages) {
+        return Sets.newLinkedHashSet(allowedPackages);
+    }
+    
+    public String toString() {
+        return Objects.toStringHelper(this).add("minHeapSize", minHeapSize).add("maxHeapSize", maxHeapSize).add("jvmArgs", jvmArgs).add("classpath", classpath).toString();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java
new file mode 100644
index 0000000..efda938
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonGroovyCompiler.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 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.compile.daemon;
+
+import com.google.common.collect.Iterables;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.GroovyJavaJointCompileSpec;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.compile.ForkOptions;
+import org.gradle.api.tasks.compile.GroovyForkOptions;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+public class DaemonGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec> {
+    private final ProjectInternal project;
+    private final Compiler<GroovyJavaJointCompileSpec> delegate;
+    private final CompilerDaemonFactory daemonFactory;
+
+    public DaemonGroovyCompiler(ProjectInternal project, Compiler<GroovyJavaJointCompileSpec> delegate, CompilerDaemonFactory daemonFactory) {
+        this.project = project;
+        this.delegate = delegate;
+        this.daemonFactory = daemonFactory;
+    }
+
+    public WorkResult execute(GroovyJavaJointCompileSpec spec) {
+        DaemonForkOptions daemonForkOptions = createDaemonForkOptions(spec);
+        CompilerDaemon daemon = daemonFactory.getDaemon(project, daemonForkOptions);
+        CompileResult result = daemon.execute(delegate, spec);
+        if (result.isSuccess()) {
+            return result;
+        }
+        throw UncheckedException.throwAsUncheckedException(result.getException());
+    }
+
+    private DaemonForkOptions createDaemonForkOptions(GroovyJavaJointCompileSpec spec) {
+        return createJavaForkOptions(spec).mergeWith(createGroovyForkOptions(spec));
+    }
+    
+    private DaemonForkOptions createJavaForkOptions(GroovyJavaJointCompileSpec spec) {
+        ForkOptions options = spec.getCompileOptions().getForkOptions();
+        return new DaemonForkOptions(options.getMemoryInitialSize(), options.getMemoryMaximumSize(), options.getJvmArgs());
+    }
+
+    private DaemonForkOptions createGroovyForkOptions(GroovyJavaJointCompileSpec spec) {
+        GroovyForkOptions options = spec.getGroovyCompileOptions().getForkOptions();
+        // Ant is optional dependency of groovy(-all) module but mandatory dependency of Groovy compiler;
+        // that's why we add it here. The following assumes that any Groovy compiler version supported by Gradle
+        // is compatible with Gradle's current Ant version.
+        Collection<File> antFiles = project.getServices().get(ClassPathRegistry.class).getClassPath("ANT").getAsFiles();
+        Iterable<File> groovyFiles = Iterables.concat(spec.getGroovyClasspath(), antFiles);
+        List<String> groovyPackages = Arrays.asList("groovy", "org.codehaus.groovy", "groovyjarjarantlr", "groovyjarjarasm", "groovyjarjarcommonscli", "org.apache.tools.ant", "com.sun.tools.javac");
+        return new DaemonForkOptions(options.getMemoryInitialSize(), options.getMemoryMaximumSize(),
+                options.getJvmArgs(), groovyFiles, groovyPackages);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java
new file mode 100644
index 0000000..59fe52e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonJavaCompiler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.compile.daemon;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.compile.ForkOptions;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.util.Collections;
+
+public class DaemonJavaCompiler implements Compiler<JavaCompileSpec> {
+    private final ProjectInternal project;
+    private final Compiler<JavaCompileSpec> delegate;
+
+    public DaemonJavaCompiler(ProjectInternal project, Compiler<JavaCompileSpec> delegate) {
+        this.project = project;
+        this.delegate = delegate;
+    }
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        ForkOptions forkOptions = spec.getCompileOptions().getForkOptions();
+        DaemonForkOptions daemonForkOptions = new DaemonForkOptions(
+                forkOptions.getMemoryInitialSize(), forkOptions.getMemoryMaximumSize(), forkOptions.getJvmArgs(),
+                Collections.<File>emptyList(), Collections.singleton("com.sun.tools.javac"));
+        CompilerDaemon daemon = CompilerDaemonManager.getInstance().getDaemon(project, daemonForkOptions);
+        CompileResult result = daemon.execute(delegate, spec);
+        if (result.isSuccess()) {
+            return result;
+        }
+        throw UncheckedException.throwAsUncheckedException(result.getException());
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
new file mode 100644
index 0000000..8ced16c
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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.compile.daemon;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.CompileSpec;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+
+public class InProcessCompilerDaemonFactory implements CompilerDaemonFactory {
+    private static final InProcessCompilerDaemonFactory INSTANCE = new InProcessCompilerDaemonFactory();
+    private final ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory();
+
+    public static InProcessCompilerDaemonFactory getInstance() {
+        return INSTANCE;
+    }
+
+    public CompilerDaemon getDaemon(ProjectInternal project, final DaemonForkOptions forkOptions) {
+        return new CompilerDaemon() {
+            public <T extends CompileSpec> CompileResult execute(Compiler<T> compiler, T spec) {
+                ClassLoader groovyClassLoader = classLoaderFactory.createIsolatedClassLoader(new DefaultClassPath(forkOptions.getClasspath()));
+                FilteringClassLoader filteredGroovy = classLoaderFactory.createFilteringClassLoader(groovyClassLoader);
+                for (String packageName : forkOptions.getSharedPackages()) {
+                    filteredGroovy.allowPackage(packageName);
+                }
+
+                ClassLoader workerClassLoader = new MutableURLClassLoader(filteredGroovy, ClasspathUtil.getClasspath(compiler.getClass().getClassLoader()));
+
+                try {
+                    byte[] serializedWorker = GUtil.serialize(new Worker<T>(compiler, spec));
+                    ClassLoaderObjectInputStream inputStream = new ClassLoaderObjectInputStream(new ByteArrayInputStream(serializedWorker), workerClassLoader);
+                    Callable<?> worker = (Callable<?>) inputStream.readObject();
+                    Object result = worker.call();
+                    byte[] serializedResult = GUtil.serialize(result);
+                    inputStream = new ClassLoaderObjectInputStream(new ByteArrayInputStream(serializedResult), getClass().getClassLoader());
+                    return (CompileResult) inputStream.readObject();
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        };
+    }
+
+    private static class Worker<T extends CompileSpec> implements Callable<Object>, Serializable {
+        private final Compiler<T> compiler;
+        private final T spec;
+
+        private Worker(Compiler<T> compiler, T spec) {
+            this.compiler = compiler;
+            this.spec = spec;
+        }
+
+        public Object call() throws Exception {
+            return new CompileResult(compiler.execute(spec).getDidWork(), null);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java
new file mode 100644
index 0000000..4ec606b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/jdk6/Jdk6JavaCompiler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.compile.jdk6;
+
+import org.gradle.api.internal.tasks.compile.*;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.util.List;
+
+public class Jdk6JavaCompiler implements Compiler<JavaCompileSpec>, Serializable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Jdk6JavaCompiler.class);
+
+    public WorkResult execute(JavaCompileSpec spec) {
+        LOGGER.info("Compiling with JDK 6 Java compiler API.");
+
+        JavaCompiler.CompilationTask task = createCompileTask(spec);
+        boolean success = task.call();
+        if (!success) {
+            throw new CompilationFailedException();
+        }
+
+        return new SimpleWorkResult(true);
+    }
+
+    private JavaCompiler.CompilationTask createCompileTask(JavaCompileSpec spec) {
+        List<String> options = new JavaCompilerArgumentsBuilder(spec).build();
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        if(compiler==null){
+            throw new RuntimeException("Cannot find System Java Compiler. Ensure that you have installed a JDK (not just a JRE) and configured your JAVA_HOME system variable to point to the according directory.");
+        }
+        CompileOptions compileOptions = spec.getCompileOptions();
+        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, compileOptions.getEncoding() != null ? Charset.forName(compileOptions.getEncoding()) : null);
+        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(spec.getSource());
+        return compiler.getTask(null, null, null, options, null, compilationUnits);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java
new file mode 100644
index 0000000..3fa55ae
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestOutputEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.testing;
+
+import org.gradle.api.tasks.testing.TestOutputEvent;
+
+import java.io.Serializable;
+
+public class DefaultTestOutputEvent implements Serializable, TestOutputEvent {
+
+    private final Destination destination;
+    private final String message;
+
+    public DefaultTestOutputEvent(Destination destination, String message) {
+        this.destination = destination;
+        this.message = message;
+    }
+
+    public Destination getDestination() {
+        return destination;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
index cfefa2e..68a04dd 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing;
 
-import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.internal.Stoppable;
 
 /**
  * A processor for executing tests. Implementations are not required to be thread-safe.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
index f4d2a79..89dcbe1 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
@@ -39,7 +39,7 @@ public interface TestFramework {
     /**
      * Returns a factory which is used to create a {@link org.gradle.api.internal.tasks.testing.TestClassProcessor} in
      * each worker process. This factory is serialized across to the worker process, and then it's {@link
-     * org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory#create(org.gradle.api.internal.project.ServiceRegistry)}
+     * org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory#create(org.gradle.internal.service.ServiceRegistry)}
      * method is called to create the test processor.
      */
     WorkerTestClassProcessorFactory getProcessorFactory();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestOutputEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestOutputEvent.java
deleted file mode 100644
index e810205..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestOutputEvent.java
+++ /dev/null
@@ -1,41 +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.api.internal.tasks.testing;
-
-import java.io.Serializable;
-
-public class TestOutputEvent implements Serializable {
-    public enum Destination {
-        StdOut, StdErr
-    }
-
-    private final Destination destination;
-    private final String message;
-
-    public TestOutputEvent(Destination destination, String message) {
-        this.destination = destination;
-        this.message = message;
-    }
-
-    public Destination getDestination() {
-        return destination;
-    }
-
-    public String getMessage() {
-        return message;
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java
index 72b5ced..08bec94 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.internal.tasks.testing;
 
+import org.gradle.api.tasks.testing.TestOutputEvent;
+
 /**
  * A processor for test results. Implementations are not required to be thread-safe.
  */
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java
index 3981954..6e243dc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing;
 
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 
 public interface WorkerTestClassProcessorFactory {
     TestClassProcessor create(ServiceRegistry serviceRegistry);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
index c146669..f721cda 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
@@ -41,16 +41,15 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
     private final File testClassesDirectory;
     private final FileCollection testClasspath;
     private List<File> testClassDirectories;
-    private ClassFileExtractionManager classFileExtractionManager;
+    private final ClassFileExtractionManager classFileExtractionManager;
     private final Map<File, Boolean> superClasses;
+    private TestClassProcessor testClassProcessor;
+    private final List<String> knownTestCaseClassNames;
 
-    protected TestClassProcessor testClassProcessor;
-
-    protected List<String> knownTestCaseClassNames;
-
-    protected AbstractTestFrameworkDetector(File testClassesDirectory, FileCollection testClasspath) {
+    protected AbstractTestFrameworkDetector(File testClassesDirectory, FileCollection testClasspath, ClassFileExtractionManager classFileExtractionManager) {
         this.testClassesDirectory = testClassesDirectory;
         this.testClasspath = testClasspath;
+        this.classFileExtractionManager = classFileExtractionManager;
         this.superClasses = new HashMap<File, Boolean>();
         this.knownTestCaseClassNames = new ArrayList<String>();
         addKnownTestCaseClassNames(TEST_CASE, GROOVY_TEST_CASE);
@@ -83,13 +82,11 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
     }
 
     private void prepareClasspath() {
-        if (classFileExtractionManager != null) {
+        if (testClassDirectories != null) {
             return;
         }
 
-        classFileExtractionManager = new ClassFileExtractionManager();
         testClassDirectories = new ArrayList<File>();
-
         testClassDirectories.add(testClassesDirectory);
         if (testClasspath != null) {
             for (File file : testClasspath) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
index 8164089..75ddcb1 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
@@ -32,11 +32,13 @@ import java.util.*;
  */
 public class ClassFileExtractionManager {
     private static final Logger LOGGER = LoggerFactory.getLogger(ClassFileExtractionManager.class);
-    protected final Map<String, Set<File>> packageJarFilesMappings;
-    protected final Map<String, File> extractedJarClasses;
-    protected final Set<String> unextractableClasses;
+    private final Map<String, Set<File>> packageJarFilesMappings;
+    private final Map<String, File> extractedJarClasses;
+    private final Set<String> unextractableClasses;
+    private final File tempDir;
 
-    public ClassFileExtractionManager() {
+    public ClassFileExtractionManager(File tempDir) {
+        this.tempDir = tempDir;
         packageJarFilesMappings = new HashMap<String, Set<File>>();
         extractedJarClasses = new HashMap<String, File>();
         unextractableClasses = new TreeSet<String>();
@@ -132,10 +134,8 @@ public class ClassFileExtractionManager {
 
     File tempFile() {
         try {
-            final File tempFile = File.createTempFile("jar_extract_", "_tmp");
-
+            final File tempFile = File.createTempFile("jar_extract_", "_tmp", tempDir);
             tempFile.deleteOnExit();
-
             return tempFile;
         } catch (IOException e) {
             throw new GradleException("failed to create temp file to extract class from jar into", e);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
index ff466d6..4bc9b10 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.tasks.testing.detection;
 
 import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java
index 2f2d4c1..b1ec1fa 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java
@@ -16,6 +16,7 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.tasks.testing.detection.ClassFileExtractionManager;
 import org.gradle.api.internal.tasks.testing.detection.TestClassVisitor;
 import org.gradle.api.internal.tasks.testing.detection.AbstractTestFrameworkDetector;
 import org.slf4j.Logger;
@@ -29,8 +30,8 @@ import java.io.File;
 public class JUnitDetector extends AbstractTestFrameworkDetector<JUnitTestClassDetecter> {
     private static final Logger LOGGER = LoggerFactory.getLogger(JUnitDetector.class);
 
-    JUnitDetector(File testClassesDirectory, FileCollection testClasspath) {
-        super(testClassesDirectory, testClasspath);
+    public JUnitDetector(File testClassesDirectory, FileCollection testClasspath, ClassFileExtractionManager classFileExtractionManager) {
+        super(testClassesDirectory, testClasspath, classFileExtractionManager);
     }
 
     protected JUnitTestClassDetecter createClassVisitor() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
index 7850e2f..f958a02 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
@@ -16,70 +16,42 @@
 
 package org.gradle.api.internal.tasks.testing.junit;
 
-import org.gradle.api.internal.tasks.testing.*;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.TimeProvider;
-import org.junit.runner.Description;
+import org.gradle.internal.concurrent.ThreadSafe;
 import org.junit.runner.Request;
 import org.junit.runner.Runner;
-import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
 import org.junit.runner.notification.RunNotifier;
 
 public class JUnitTestClassExecuter {
     private final ClassLoader applicationClassLoader;
     private final RunListener listener;
-    private final TestResultProcessor resultProcessor;
-    private final IdGenerator<?> idGenerator;
-    private final TimeProvider timeProvider;
+    private final TestClassExecutionListener executionListener;
 
-    public JUnitTestClassExecuter(ClassLoader applicationClassLoader, RunListener listener, TestResultProcessor resultProcessor, IdGenerator<?> idGenerator, TimeProvider timeProvider) {
+    public JUnitTestClassExecuter(ClassLoader applicationClassLoader, RunListener listener, TestClassExecutionListener executionListener) {
+        assert executionListener instanceof ThreadSafe;
         this.applicationClassLoader = applicationClassLoader;
         this.listener = listener;
-        this.resultProcessor = resultProcessor;
-        this.idGenerator = idGenerator;
-        this.timeProvider = timeProvider;
+        this.executionListener = executionListener;
     }
 
     public void execute(String testClassName) {
-        TestDescriptorInternal testInternal = new DefaultTestClassDescriptor(idGenerator.generateId(), testClassName);
-        resultProcessor.started(testInternal, new TestStartEvent(timeProvider.getCurrentTime()));
+        executionListener.testClassStarted(testClassName);
 
-        Runner runner = createTest(testClassName);
-        RunNotifier notifier = new RunNotifier();
-        notifier.addListener(listener);
-        runner.run(notifier);
-
-        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime()));
-    }
-
-    private Runner createTest(String testClassName) {
+        Throwable failure = null;
         try {
-            Class<?> testClass = Class.forName(testClassName, true, applicationClassLoader);
-            return Request.aClass(testClass).getRunner();
-        } catch (Throwable e) {
-            return new BrokenTest(Description.createSuiteDescription(String.format("initializationError(%s)", testClassName)), e);
-        }
-    }
-
-    private static class BrokenTest extends Runner {
-        private final Throwable failure;
-        private final Description description;
-
-        public BrokenTest(Description description, Throwable failure) {
-            this.failure = failure;
-            this.description = description;
+            runTestClass(testClassName);
+        } catch (Throwable throwable) {
+            failure = throwable;
         }
 
-        public Description getDescription() {
-            return description;
-        }
+        executionListener.testClassFinished(failure);
+    }
 
-        @Override
-        public void run(RunNotifier notifier) {
-            notifier.fireTestStarted(description);
-            notifier.fireTestFailure(new Failure(description, failure));
-            notifier.fireTestFinished(description);
-        }
+    private void runTestClass(String testClassName) throws ClassNotFoundException {
+        Class<?> testClass = Class.forName(testClassName, true, applicationClassLoader);
+        Runner runner = Request.aClass(testClass).getRunner();
+        RunNotifier notifier = new RunNotifier();
+        notifier.addListener(listener);
+        runner.run(notifier);
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
old mode 100644
new mode 100755
index 410b0e8..e42f672
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
@@ -23,6 +23,8 @@ import org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestRes
 import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.StandardOutputRedirector;
+import org.gradle.messaging.actor.Actor;
+import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.util.IdGenerator;
 import org.gradle.util.TimeProvider;
 import org.gradle.util.TrueTimeProvider;
@@ -35,27 +37,37 @@ public class JUnitTestClassProcessor implements TestClassProcessor {
     private static final Logger LOGGER = LoggerFactory.getLogger(JUnitTestClassProcessor.class);
     private final File testResultsDir;
     private final IdGenerator<?> idGenerator;
+    private final ActorFactory actorFactory;
     private final StandardOutputRedirector outputRedirector;
     private final TimeProvider timeProvider = new TrueTimeProvider();
     private JUnitTestClassExecuter executer;
+    private Actor resultProcessorActor;
 
-    public JUnitTestClassProcessor(File testResultsDir, IdGenerator<?> idGenerator,
+    public JUnitTestClassProcessor(File testResultsDir, IdGenerator<?> idGenerator, ActorFactory actorFactory,
                                    StandardOutputRedirector standardOutputRedirector) {
         this.testResultsDir = testResultsDir;
         this.idGenerator = idGenerator;
+        this.actorFactory = actorFactory;
         this.outputRedirector = standardOutputRedirector;
     }
 
     public void startProcessing(TestResultProcessor resultProcessor) {
+        // Build a result processor chain
         ClassLoader applicationClassLoader = Thread.currentThread().getContextClassLoader();
-        ListenerBroadcast<TestResultProcessor> processors = new ListenerBroadcast<TestResultProcessor>(
-                TestResultProcessor.class);
+        ListenerBroadcast<TestResultProcessor> processors = new ListenerBroadcast<TestResultProcessor>(TestResultProcessor.class);
         processors.add(new JUnitXmlReportGenerator(testResultsDir));
         processors.add(resultProcessor);
         TestResultProcessor resultProcessorChain = new AttachParentTestResultProcessor(new CaptureTestOutputTestResultProcessor(processors.getSource(), outputRedirector));
-        JUnitTestResultProcessorAdapter listener = new JUnitTestResultProcessorAdapter(resultProcessorChain,
-                timeProvider, idGenerator);
-        executer = new JUnitTestClassExecuter(applicationClassLoader, listener, resultProcessorChain, idGenerator, timeProvider);
+        TestClassExecutionEventGenerator eventGenerator = new TestClassExecutionEventGenerator(resultProcessorChain, idGenerator, timeProvider);
+
+        // Wrap the result processor chain up in a blocking actor, to make the whole thing thread-safe
+        resultProcessorActor = actorFactory.createBlockingActor(eventGenerator);
+        TestResultProcessor threadSafeResultProcessor = resultProcessorActor.getProxy(TestResultProcessor.class);
+        TestClassExecutionListener threadSafeTestClassListener = resultProcessorActor.getProxy(TestClassExecutionListener.class);
+
+        // Build the JUnit adaptor stuff
+        JUnitTestEventAdapter junitEventAdapter = new JUnitTestEventAdapter(threadSafeResultProcessor, timeProvider, idGenerator);
+        executer = new JUnitTestClassExecuter(applicationClassLoader, junitEventAdapter, threadSafeTestClassListener);
     }
 
     public void processTestClass(TestClassRunInfo testClass) {
@@ -64,5 +76,6 @@ public class JUnitTestClassProcessor implements TestClassProcessor {
     }
 
     public void stop() {
+        resultProcessorActor.stop();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
new file mode 100644
index 0000000..32f013e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
@@ -0,0 +1,140 @@
+/*
+ * 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.testing.junit;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.internal.concurrent.ThreadSafe;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.TimeProvider;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JUnitTestEventAdapter extends RunListener {
+    private final TestResultProcessor resultProcessor;
+    private final TimeProvider timeProvider;
+    private final IdGenerator<?> idGenerator;
+    private final Object lock = new Object();
+    private final Map<Description, TestDescriptorInternal> executing = new HashMap<Description, TestDescriptorInternal>();
+
+    public JUnitTestEventAdapter(TestResultProcessor resultProcessor, TimeProvider timeProvider,
+                                 IdGenerator<?> idGenerator) {
+        assert resultProcessor instanceof ThreadSafe;
+        this.resultProcessor = resultProcessor;
+        this.timeProvider = timeProvider;
+        this.idGenerator = idGenerator;
+    }
+
+    @Override
+    public void testStarted(Description description) throws Exception {
+        TestDescriptorInternal descriptor = descriptor(idGenerator.generateId(), description);
+        synchronized (lock) {
+            TestDescriptorInternal oldTest = executing.put(description, descriptor);
+            assert oldTest == null : String.format("Unexpected start event for %s", description);
+        }
+        resultProcessor.started(descriptor, startEvent());
+    }
+
+    @Override
+    public void testFailure(Failure failure) throws Exception {
+        TestDescriptorInternal testInternal;
+        synchronized (lock) {
+            testInternal = executing.get(failure.getDescription());
+        }
+        boolean needEndEvent = false;
+        if (testInternal == null) {
+            // This can happen when, for example, a @BeforeClass or @AfterClass method fails
+            needEndEvent = true;
+            testInternal = failureDescriptor(idGenerator.generateId(), failure.getDescription());
+            resultProcessor.started(testInternal, startEvent());
+        }
+        resultProcessor.failure(testInternal.getId(), failure.getException());
+        if (needEndEvent) {
+            resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime()));
+        }
+    }
+
+    @Override
+    public void testIgnored(Description description) throws Exception {
+        if (methodName(description) == null) {
+            // An @Ignored class, ignore the event. We don't get testIgnored events for each method, which would be kind of nice
+            return;
+        }
+        TestDescriptorInternal testInternal = descriptor(idGenerator.generateId(), description);
+        resultProcessor.started(testInternal, startEvent());
+        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime(), TestResult.ResultType.SKIPPED));
+    }
+
+    @Override
+    public void testFinished(Description description) throws Exception {
+        long endTime = timeProvider.getCurrentTime();
+        TestDescriptorInternal testInternal;
+        synchronized (lock) {
+            testInternal = executing.remove(description);
+            if (testInternal == null && executing.size() == 1) {
+                // Assume that test has renamed itself
+                testInternal = executing.values().iterator().next();
+                executing.clear();
+            }
+            assert testInternal != null : String.format("Unexpected end event for %s", description);
+        }
+        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(endTime));
+    }
+
+    private TestStartEvent startEvent() {
+        return new TestStartEvent(timeProvider.getCurrentTime());
+    }
+
+    private TestDescriptorInternal descriptor(Object id, Description description) {
+        return new DefaultTestDescriptor(id, className(description), methodName(description));
+    }
+
+    private TestDescriptorInternal failureDescriptor(Object id, Description description) {
+        if (methodName(description) != null) {
+            return new DefaultTestDescriptor(id, className(description), methodName(description));
+        } else {
+            return new DefaultTestDescriptor(id, className(description), "classMethod");
+        }
+    }
+
+    // Use this instead of Description.getMethodName(), it is not available in JUnit <= 4.5
+    private String methodName(Description description) {
+        Matcher matcher = methodStringMatcher(description);
+        if (matcher.matches()) {
+            return matcher.group(1);
+        }
+        return null;
+    }
+
+    // Use this instead of Description.getClassName(), it is not available in JUnit <= 4.5
+    private String className(Description description) {
+        Matcher matcher = methodStringMatcher(description);
+        return matcher.matches() ? matcher.group(2) : description.toString();
+    }
+
+    private Matcher methodStringMatcher(Description description) {
+        return Pattern.compile("(.*)\\((.*)\\)").matcher(description.toString());
+    }
+
+}
+
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
index 95588f1..f8cc67b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
@@ -17,14 +17,16 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.api.internal.tasks.testing.detection.ClassFileExtractionManager;
 import org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport;
 import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
+import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.util.IdGenerator;
 
@@ -44,7 +46,7 @@ public class JUnitTestFramework implements TestFramework {
         this.testTask = testTask;
         reporter = new DefaultTestReport();
         options = new JUnitOptions();
-        detector = new JUnitDetector(testTask.getTestClassesDir(), testTask.getClasspath());
+        detector = new JUnitDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDir()));
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
@@ -99,7 +101,7 @@ public class JUnitTestFramework implements TestFramework {
         }
 
         public TestClassProcessor create(ServiceRegistry serviceRegistry) {
-            return new JUnitTestClassProcessor(testResultsDir, serviceRegistry.get(IdGenerator.class), new JULRedirector());
+            return new JUnitTestClassProcessor(testResultsDir, serviceRegistry.get(IdGenerator.class), serviceRegistry.get(ActorFactory.class), new JULRedirector());
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestResultProcessorAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestResultProcessorAdapter.java
deleted file mode 100644
index 3bd5596..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestResultProcessorAdapter.java
+++ /dev/null
@@ -1,138 +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.api.internal.tasks.testing.junit;
-
-import org.gradle.api.internal.tasks.testing.*;
-import org.gradle.api.tasks.testing.TestResult;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.TimeProvider;
-import org.junit.runner.Description;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class JUnitTestResultProcessorAdapter extends RunListener {
-    private final TestResultProcessor resultProcessor;
-    private final TimeProvider timeProvider;
-    private final IdGenerator<?> idGenerator;
-    private final Object lock = new Object();
-    private final Map<Description, TestDescriptorInternal> executing = new HashMap<Description, TestDescriptorInternal>();
-
-    public JUnitTestResultProcessorAdapter(TestResultProcessor resultProcessor, TimeProvider timeProvider,
-                                           IdGenerator<?> idGenerator) {
-        this.resultProcessor = resultProcessor;
-        this.timeProvider = timeProvider;
-        this.idGenerator = idGenerator;
-    }
-
-    @Override
-    public void testStarted(Description description) throws Exception {
-        TestDescriptorInternal descriptor = descriptor(idGenerator.generateId(), description);
-        synchronized (lock) {
-            TestDescriptorInternal oldTest = executing.put(description, descriptor);
-            assert oldTest == null : String.format("Unexpected start event for %s", description);
-        }
-        resultProcessor.started(descriptor, startEvent());
-    }
-
-    @Override
-    public void testFailure(Failure failure) throws Exception {
-        TestDescriptorInternal testInternal;
-        synchronized (lock) {
-            testInternal = executing.get(failure.getDescription());
-        }
-        boolean needEndEvent = false;
-        if (testInternal == null) {
-            // This can happen when, for example, a @BeforeClass or @AfterClass method fails
-            needEndEvent = true;
-            testInternal = failureDescriptor(idGenerator.generateId(), failure.getDescription());
-            resultProcessor.started(testInternal, startEvent());
-        }
-        resultProcessor.failure(testInternal.getId(), failure.getException());
-        if (needEndEvent) {
-            resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime()));
-        }
-    }
-
-    @Override
-    public void testIgnored(Description description) throws Exception {
-        if (methodName(description) == null) {
-            // An @Ignored class, ignore the event. We don't get testIgnored events for each method, which would be kind of nice
-            return;
-        }
-        TestDescriptorInternal testInternal = descriptor(idGenerator.generateId(), description);
-        resultProcessor.started(testInternal, startEvent());
-        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime(), TestResult.ResultType.SKIPPED));
-    }
-
-    @Override
-    public void testFinished(Description description) throws Exception {
-        long endTime = timeProvider.getCurrentTime();
-        TestDescriptorInternal testInternal;
-        synchronized (lock) {
-            testInternal = executing.remove(description);
-            if (testInternal == null && executing.size() == 1) {
-                // Assume that test has renamed itself
-                testInternal = executing.values().iterator().next();
-                executing.clear();
-            }
-            assert testInternal != null : String.format("Unexpected end event for %s", description);
-        }
-        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(endTime));
-    }
-
-    private TestStartEvent startEvent() {
-        return new TestStartEvent(timeProvider.getCurrentTime());
-    }
-
-    private TestDescriptorInternal descriptor(Object id, Description description) {
-        return new DefaultTestDescriptor(id, className(description), methodName(description));
-    }
-
-    private TestDescriptorInternal failureDescriptor(Object id, Description description) {
-        if (methodName(description) != null) {
-            return new DefaultTestDescriptor(id, className(description), methodName(description));
-        } else {
-            return new DefaultTestDescriptor(id, className(description), "classMethod");
-        }
-    }
-
-    // Use this instead of Description.getMethodName(), it is not available in JUnit <= 4.5
-    private String methodName(Description description) {
-        Matcher matcher = methodStringMatcher(description);
-        if (matcher.matches()) {
-            return matcher.group(1);
-        }
-        return null;
-    }
-
-    // Use this instead of Description.getClassName(), it is not available in JUnit <= 4.5
-    private String className(Description description) {
-        Matcher matcher = methodStringMatcher(description);
-        return matcher.matches() ? matcher.group(2) : description.toString();
-    }
-
-    private Matcher methodStringMatcher(Description description) {
-        return Pattern.compile("(.*)\\((.*)\\)").matcher(description.toString());
-    }
-
-}
-
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
index da5f014..3950523 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
@@ -20,10 +20,12 @@ import org.apache.tools.ant.util.DOMElementWriter;
 import org.apache.tools.ant.util.DateUtils;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
-import org.gradle.api.internal.tasks.testing.TestOutputEvent;
 import org.gradle.api.internal.tasks.testing.results.StateTrackingTestResultProcessor;
+import org.gradle.api.internal.tasks.testing.results.TestState;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 import org.gradle.api.tasks.testing.TestResult;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -50,13 +52,12 @@ public class JUnitXmlReportGenerator extends StateTrackingTestResultProcessor {
         try {
             documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
         hostName = getHostname();
     }
 
-    @Override
-    public void output(Object testId, TestOutputEvent event) {
+    public void output(TestDescriptor test, TestOutputEvent event) {
         outputs.get(event.getDestination()).append(event.getMessage());
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java
new file mode 100644
index 0000000..010a0dc
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 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.testing.junit;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.TimeProvider;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class TestClassExecutionEventGenerator implements TestResultProcessor, TestClassExecutionListener {
+    private final TestResultProcessor resultProcessor;
+    private final IdGenerator<?> idGenerator;
+    private final TimeProvider timeProvider;
+    private final Set<Object> currentTests = new LinkedHashSet<Object>();
+    private boolean testsStarted;
+    private TestDescriptorInternal currentTestClass;
+
+    public TestClassExecutionEventGenerator(TestResultProcessor resultProcessor, IdGenerator<?> idGenerator, TimeProvider timeProvider) {
+        this.resultProcessor = resultProcessor;
+        this.idGenerator = idGenerator;
+        this.timeProvider = timeProvider;
+    }
+
+    public void testClassStarted(String testClassName) {
+        currentTestClass = new DefaultTestClassDescriptor(idGenerator.generateId(), testClassName);
+        resultProcessor.started(currentTestClass, new TestStartEvent(timeProvider.getCurrentTime()));
+    }
+
+    public void testClassFinished(Throwable failure) {
+        long now = timeProvider.getCurrentTime();
+        try {
+            if (failure != null) {
+                if (currentTests.isEmpty()) {
+                    String testName = testsStarted ? "executionError": "initializationError";
+                    DefaultTestDescriptor initializationError = new DefaultTestDescriptor(idGenerator.generateId(), currentTestClass.getClassName(), testName);
+                    resultProcessor.started(initializationError, new TestStartEvent(now));
+                    resultProcessor.failure(initializationError.getId(), failure);
+                    resultProcessor.completed(initializationError.getId(), new TestCompleteEvent(now));
+                } else {
+                    for (Object test : currentTests) {
+                        resultProcessor.failure(test, failure);
+                        resultProcessor.completed(test, new TestCompleteEvent(now));
+                    }
+                }
+            }
+            resultProcessor.completed(currentTestClass.getId(), new TestCompleteEvent(now));
+        } finally {
+            testsStarted = false;
+            currentTests.clear();
+            currentTestClass = null;
+        }
+    }
+
+    public void started(TestDescriptorInternal test, TestStartEvent event) {
+        resultProcessor.started(test, event);
+        testsStarted = true;
+        currentTests.add(test.getId());
+    }
+
+    public void completed(Object testId, TestCompleteEvent event) {
+        currentTests.remove(testId);
+        resultProcessor.completed(testId, event);
+    }
+
+    public void output(Object testId, TestOutputEvent event) {
+        resultProcessor.output(testId, event);
+    }
+
+    public void failure(Object testId, Throwable result) {
+        resultProcessor.failure(testId, result);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionListener.java
new file mode 100644
index 0000000..83e3fb9
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionListener.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.testing.junit;
+
+public interface TestClassExecutionListener {
+    void testClassStarted(String testClassName);
+
+    void testClassFinished(Throwable failure);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
index a053524..65f71be 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
@@ -16,9 +16,11 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.gradle.api.Action;
+import org.gradle.reporting.CodePanelRenderer;
 import org.w3c.dom.Element;
 
 class ClassPageRenderer extends PageRenderer<ClassTestResults> {
+    private final CodePanelRenderer codePanelRenderer = new CodePanelRenderer();
 
     @Override protected void renderBreadcrumbs(Element parent) {
         Element div = append(parent, "div");
@@ -53,17 +55,17 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
             append(div, "a").setAttribute("name", test.getId().toString());
             appendWithText(div, "h3", test.getName()).setAttribute("class", test.getStatusClass());
             for (TestFailure failure : test.getFailures()) {
-                appendWithText(div, "pre", failure.getStackTrace()).setAttribute("class", "stackTrace");
+                codePanelRenderer.render(failure.getStackTrace(), div);
             }
         }
     }
 
     private void renderStdOut(Element parent) {
-        appendWithText(parent, "pre", getResults().getStandardOutput());
+        codePanelRenderer.render(getResults().getStandardOutput().toString(), parent);
     }
 
     private void renderStdErr(Element parent) {
-        appendWithText(parent, "pre", getResults().getStandardError());
+        codePanelRenderer.render(getResults().getStandardError().toString(), parent);
     }
 
     @Override protected void registerTabs() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
index 7e915df..e67f325 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
@@ -15,28 +15,30 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
+import org.gradle.reporting.HtmlReportRenderer;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;
 
-import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import java.io.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
 import java.math.BigDecimal;
 
 public class DefaultTestReport implements TestReporter {
+    private final HtmlReportRenderer htmlRenderer = new HtmlReportRenderer();
     private File resultDir;
     private File reportDir;
-    private DocumentBuilder documentBuilder;
-    private Transformer transformer;
+
+    public DefaultTestReport() {
+        htmlRenderer.requireResource(getClass().getResource("/org/gradle/reporting/report.js"));
+        htmlRenderer.requireResource(getClass().getResource("/org/gradle/reporting/base-style.css"));
+        htmlRenderer.requireResource(getClass().getResource("/org/gradle/reporting/css3-pie-1.0beta3.htc"));
+        htmlRenderer.requireResource(getClass().getResource("style.css"));
+    }
 
     public void setTestResultsDir(File resultDir) {
         this.resultDir = resultDir;
@@ -118,57 +120,12 @@ public class DefaultTestReport implements TestReporter {
                     generatePage(classResults, new ClassPageRenderer(), new File(reportDir, classResults.getName() + ".html"));
                 }
             }
-
-            copyResources();
-
         } catch (Exception e) {
             throw new GradleException(String.format("Could not generate test report to '%s'.", reportDir), e);
         }
     }
 
     private <T extends CompositeTestResults> void generatePage(T model, PageRenderer<T> renderer, File outputFile) throws Exception {
-        if (documentBuilder == null) {
-            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-        }
-        Document document = documentBuilder.newDocument();
-        renderer.render(document, model);
-
-        if (transformer == null) {
-            TransformerFactory factory = TransformerFactory.newInstance();
-            transformer = factory.newTransformer();
-            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-            transformer.setOutputProperty(OutputKeys.METHOD, "html");
-            transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "text/html");
-        }
-
-        outputFile.getParentFile().mkdirs();
-        Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "utf-8"));
-        try {
-            writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n");
-            transformer.transform(new DOMSource(document), new StreamResult(writer));
-        } finally {
-            writer.close();
-        }
-    }
-
-    private void copyResources() throws IOException {
-        copyResource("style.css");
-        copyResource("report.js");
-        copyResource("css3-pie-1.0beta3.htc");
-    }
-
-    private void copyResource(String resourceName) throws IOException {
-        File cssFile = new File(reportDir, resourceName);
-        OutputStream outputStream = new FileOutputStream(cssFile);
-        try {
-            InputStream cssResource = getClass().getResourceAsStream(resourceName);
-            try {
-                IOUtils.copy(cssResource, outputStream);
-            } finally {
-                cssResource.close();
-            }
-        } finally {
-            outputStream.close();
-        }
+        htmlRenderer.renderer(renderer).writeTo(model, outputFile);
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
index 7fa7d51..6620edc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
@@ -16,18 +16,14 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.gradle.api.Action;
-import org.gradle.util.GradleVersion;
-import org.w3c.dom.Document;
+import org.gradle.reporting.DomReportRenderer;
+import org.gradle.reporting.TabbedPageRenderer;
+import org.gradle.reporting.TabsRenderer;
 import org.w3c.dom.Element;
 
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-abstract class PageRenderer<T extends CompositeTestResults> {
+abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRenderer<T> {
     private T results;
-    private List<TabDefinition> tabs = new ArrayList<TabDefinition>();
+    private final TabsRenderer<T> tabsRenderer = new TabsRenderer<T>();
 
     protected T getResults() {
         return results;
@@ -37,25 +33,17 @@ abstract class PageRenderer<T extends CompositeTestResults> {
 
     protected abstract void registerTabs();
 
-    protected void addTab(String title, Action<Element> contentRenderer) {
-        tabs.add(new TabDefinition(title, contentRenderer));
+    protected void addTab(String title, final Action<Element> contentRenderer) {
+        tabsRenderer.add(title, new DomReportRenderer<T>() {
+            @Override
+            public void render(T model, Element parent) {
+                contentRenderer.execute(parent);
+            }
+        });
     }
 
     protected void renderTabs(Element element) {
-        Element tabs = appendWithId(element, "div", "tabs");
-        Element ul = append(tabs, "ul");
-        ul.setAttribute("class", "tabLinks");
-        for (int i = 0; i < this.tabs.size(); i++) {
-            TabDefinition tab = this.tabs.get(i);
-            Element li = append(ul, "li");
-            Element a = appendWithText(li, "a", tab.title);
-            String tabId = String.format("tab%s", i);
-            a.setAttribute("href", "#" + tabId);
-            Element tabDiv = appendWithId(tabs, "div", tabId);
-            tabDiv.setAttribute("class", "tab");
-            appendWithText(tabDiv, "h2", tab.title);
-            tab.renderer.execute(tabDiv);
-        }
+        tabsRenderer.render(getModel(), element);
     }
 
     protected void addFailuresTab() {
@@ -79,30 +67,6 @@ abstract class PageRenderer<T extends CompositeTestResults> {
         }
     }
 
-    protected Element append(Element parent, String name) {
-        Element element = parent.getOwnerDocument().createElement(name);
-        parent.appendChild(element);
-        return element;
-    }
-
-    protected Element appendWithId(Element parent, String name, String id) {
-        Element element = parent.getOwnerDocument().createElement(name);
-        parent.appendChild(element);
-        element.setAttribute("id", id);
-        return element;
-    }
-
-    protected Element appendWithText(Element parent, String name, Object textContent) {
-        Element element = parent.getOwnerDocument().createElement(name);
-        parent.appendChild(element);
-        appendText(element, textContent);
-        return element;
-    }
-
-    protected void appendText(Element element, Object textContent) {
-        element.appendChild(element.getOwnerDocument().createTextNode(textContent.toString()));
-    }
-
     protected Element appendTableAndRow(Element parent) {
         return append(append(parent, "table"), "tr");
     }
@@ -111,88 +75,82 @@ abstract class PageRenderer<T extends CompositeTestResults> {
         return append(append(parent, "td"), "div");
     }
 
-    protected Element appendLink(Element parent, String href, Object textContent) {
-        Element element = appendWithText(parent, "a", textContent);
-        element.setAttribute("href", href);
-        return element;
+    protected <T extends TestResultModel> DomReportRenderer<T> withStatus(final DomReportRenderer<T> renderer) {
+        return new DomReportRenderer<T>() {
+            @Override
+            public void render(T model, Element parent) {
+                parent.setAttribute("class", model.getStatusClass());
+                renderer.render(model, parent);
+            }
+        };
     }
 
-    void render(Document document, T results) {
-        this.results = results;
-
-        registerTabs();
-
-        Element html = document.createElement("html");
-        document.appendChild(html);
-
-        // <head>
-        Element head = append(html, "head");
-        appendWithText(head, "title", String.format("Test results - %s", results.getTitle()));
-        Element link = append(head, "link");
-        link.setAttribute("rel", "stylesheet");
-        link.setAttribute("href", "style.css");
-        link.setAttribute("type", "text/css");
-        Element script = append(head, "script");
-        script.setAttribute("src", "report.js");
-        script.setAttribute("type", "text/javascript");
-
-        // <body>
-        Element body = append(html, "body");
-        Element content = appendWithId(body, "div", "content");
-        appendWithText(content, "h1", results.getTitle());
-        renderBreadcrumbs(content);
-
-        // summary
-        Element summary = appendWithId(content, "div", "summary");
-        Element row = appendTableAndRow(summary);
-        Element group = appendCell(row);
-        group.setAttribute("class", "summaryGroup");
-        Element summaryRow = appendTableAndRow(group);
-
-        Element tests = appendCell(summaryRow);
-        tests.setAttribute("id", "tests");
-        tests.setAttribute("class", "infoBox");
-        Element div = appendWithText(tests, "div", results.getTestCount());
-        div.setAttribute("class", "counter");
-        appendWithText(tests, "p", "tests");
-
-        Element failures = appendCell(summaryRow);
-        failures.setAttribute("id", "failures");
-        failures.setAttribute("class", "infoBox");
-        div = appendWithText(failures, "div", results.getFailureCount());
-        div.setAttribute("class", "counter");
-        appendWithText(failures, "p", "failures");
-
-        Element duration = appendCell(summaryRow);
-        duration.setAttribute("id", "duration");
-        duration.setAttribute("class", "infoBox");
-        div = appendWithText(duration, "div", results.getFormattedDuration());
-        div.setAttribute("class", "counter");
-        appendWithText(duration, "p", "duration");
-
-        Element successRate = appendCell(row);
-        successRate.setAttribute("id", "successRate");
-        successRate.setAttribute("class", String.format("infoBox %s", results.getStatusClass()));
-        div = appendWithText(successRate, "div", results.getFormattedSuccessRate());
-        div.setAttribute("class", "percent");
-        appendWithText(successRate, "p", "successful");
-
-        renderTabs(content);
-
-        Element footer = appendWithId(content, "div", "footer");
-        Element footerText = append(footer, "p");
-        appendText(footerText, "Generated by ");
-        appendLink(footerText, "http://www.gradle.org/", String.format("Gradle %s", GradleVersion.current().getVersion()));
-        appendText(footerText, String.format(" at %s", DateFormat.getDateTimeInstance().format(new Date())));
+    @Override
+    protected String getTitle() {
+        return getModel().getTitle();
     }
 
-    private static class TabDefinition {
-        private final String title;
-        private final Action<Element> renderer;
+    @Override
+    protected String getPageTitle() {
+        return String.format("Test results - %s", getModel().getTitle());
+    }
 
-        public TabDefinition(String title, Action<Element> renderer) {
-            this.title = title;
-            this.renderer = renderer;
-        }
+    @Override
+    protected DomReportRenderer<T> getHeaderRenderer() {
+        return new DomReportRenderer<T>() {
+            @Override
+            public void render(T model, Element content) {
+                PageRenderer.this.results = model;
+                renderBreadcrumbs(content);
+
+                // summary
+                Element summary = appendWithId(content, "div", "summary");
+                Element row = appendTableAndRow(summary);
+                Element group = appendCell(row);
+                group.setAttribute("class", "summaryGroup");
+                Element summaryRow = appendTableAndRow(group);
+
+                Element tests = appendCell(summaryRow);
+                tests.setAttribute("id", "tests");
+                tests.setAttribute("class", "infoBox");
+                Element div = appendWithText(tests, "div", results.getTestCount());
+                div.setAttribute("class", "counter");
+                appendWithText(tests, "p", "tests");
+
+                Element failures = appendCell(summaryRow);
+                failures.setAttribute("id", "failures");
+                failures.setAttribute("class", "infoBox");
+                div = appendWithText(failures, "div", results.getFailureCount());
+                div.setAttribute("class", "counter");
+                appendWithText(failures, "p", "failures");
+
+                Element duration = appendCell(summaryRow);
+                duration.setAttribute("id", "duration");
+                duration.setAttribute("class", "infoBox");
+                div = appendWithText(duration, "div", results.getFormattedDuration());
+                div.setAttribute("class", "counter");
+                appendWithText(duration, "p", "duration");
+
+                Element successRate = appendCell(row);
+                successRate.setAttribute("id", "successRate");
+                successRate.setAttribute("class", String.format("infoBox %s", results.getStatusClass()));
+                div = appendWithText(successRate, "div", results.getFormattedSuccessRate());
+                div.setAttribute("class", "percent");
+                appendWithText(successRate, "p", "successful");
+            }
+        };
+    }
+
+    @Override
+    protected DomReportRenderer<T> getContentRenderer() {
+        return new DomReportRenderer<T>() {
+            @Override
+            public void render(T model, Element content) {
+                PageRenderer.this.results = model;
+                tabsRenderer.clear();
+                registerTabs();
+                renderTabs(content);
+            }
+        };
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResultModel.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResultModel.java
index b6f33fb..b5b37b7 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResultModel.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/TestResultModel.java
@@ -16,14 +16,10 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.gradle.api.tasks.testing.TestResult;
-
-import java.math.BigDecimal;
+import org.gradle.reporting.DurationFormatter;
 
 public abstract class TestResultModel {
-    public static final int MILLIS_PER_SECOND = 1000;
-    public static final int MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
-    public static final int MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
-    public static final int MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;
+    public static final DurationFormatter DURATION_FORMATTER = new DurationFormatter();
 
     public abstract TestResult.ResultType getResultType();
 
@@ -32,35 +28,7 @@ public abstract class TestResultModel {
     public abstract String getTitle();
 
     public String getFormattedDuration() {
-        Long duration = getDuration();
-        if (duration == 0) {
-            return "0s";
-        }
-
-        StringBuilder result = new StringBuilder();
-
-        long days = duration / MILLIS_PER_DAY;
-        duration = duration % MILLIS_PER_DAY;
-        if (days > 0) {
-            result.append(days);
-            result.append("d");
-        }
-        long hours = duration / MILLIS_PER_HOUR;
-        duration = duration % MILLIS_PER_HOUR;
-        if (hours > 0 || result.length() > 0) {
-            result.append(hours);
-            result.append("h");
-        }
-        long minutes = duration / MILLIS_PER_MINUTE;
-        duration = duration % MILLIS_PER_MINUTE;
-        if (minutes > 0 || result.length() > 0) {
-            result.append(minutes);
-            result.append("m");
-        }
-        int secondsScale = result.length() > 0 ? 2 : 3;
-        result.append(BigDecimal.valueOf(duration).divide(BigDecimal.valueOf(MILLIS_PER_SECOND)).setScale(secondsScale, BigDecimal.ROUND_HALF_UP));
-        result.append("s");
-        return result.toString();
+        return DURATION_FORMATTER.format(getDuration());
     }
 
     public String getStatusClass() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java
new file mode 100644
index 0000000..8e9f5f3
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.testing.logging;
+
+import org.gradle.api.tasks.testing.TestLogging;
+
+/**
+ * by Szczepan Faber, created at: 10/31/11
+ */
+public class DefaultTestLogging implements TestLogging {
+
+    boolean showStandardStreams;
+
+    public boolean getShowStandardStreams() {
+        return showStandardStreams;
+    }
+
+    public TestLogging setShowStandardStreams(boolean standardStreams) {
+        this.showStandardStreams = standardStreams;
+        return this;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLogger.java
new file mode 100644
index 0000000..84b08ee
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLogger.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.testing.logging;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.TestLogging;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+import org.gradle.api.tasks.testing.TestOutputListener;
+import org.slf4j.Logger;
+
+/**
+ * Test output listener that logs extra stuff based on the logging configuration
+ * <p>
+ * by Szczepan Faber, created at: 10/31/11
+ */
+public class StandardStreamsLogger implements TestOutputListener {
+    private final Logger logger;
+    private final TestLogging logging;
+    private TestDescriptor currentTestDescriptor;
+
+    public StandardStreamsLogger(Logger logger, TestLogging logging) {
+        this.logger = logger;
+        this.logging = logging;
+    }
+
+    public void onOutput(TestDescriptor testDescriptor, TestOutputEvent outputEvent) {
+        if (logging.getShowStandardStreams()) {
+            if (!testDescriptor.equals(currentTestDescriptor)) {
+                currentTestDescriptor = testDescriptor;
+                logger.info(StringUtils.capitalize(testDescriptor.toString() + " output:"));
+            }
+            if (outputEvent.getDestination() == TestOutputEvent.Destination.StdOut) {
+                logger.info(outputEvent.getMessage());
+            } else if (outputEvent.getDestination() == TestOutputEvent.Destination.StdErr) {
+                logger.error(outputEvent.getMessage());
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessor.java
index 5059881..9ed8e27 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessor.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.testing.processors;
 
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 import org.gradle.logging.StandardOutputRedirector;
 
 /**
@@ -27,7 +28,7 @@ import org.gradle.logging.StandardOutputRedirector;
 public class CaptureTestOutputTestResultProcessor implements TestResultProcessor {
     private final TestResultProcessor processor;
     private final StandardOutputRedirector outputRedirector;
-    private Object suite;
+    private Object suiteId;
 
     public CaptureTestOutputTestResultProcessor(TestResultProcessor processor, StandardOutputRedirector outputRedirector) {
         this.processor = processor;
@@ -36,34 +37,39 @@ public class CaptureTestOutputTestResultProcessor implements TestResultProcessor
 
     public void started(final TestDescriptorInternal test, TestStartEvent event) {
         processor.started(test, event);
-        if (suite != null) {
+
+        //should redirect output for every particular test
+        redirectOutputFor(test.getId());
+
+        //currently our test reports include std out/err per test class (aka suite) not per test method (aka test)
+        //for historical reasons. Therefore we only start/stop redirector per suite.
+        if (suiteId != null) {
             return;
         }
-        suite = test.getId();
-        outputRedirector.redirectStandardOutputTo(new StandardOutputListener() {
-            public void onOutput(CharSequence output) {
-                processor.output(suite, new TestOutputEvent(TestOutputEvent.Destination.StdOut, output.toString()));
-            }
-        });
-        outputRedirector.redirectStandardErrorTo(new StandardOutputListener() {
-            public void onOutput(CharSequence output) {
-                processor.output(suite, new TestOutputEvent(TestOutputEvent.Destination.StdErr, output.toString()));
-            }
-        });
+        suiteId = test.getId();
         outputRedirector.start();
     }
 
     public void completed(Object testId, TestCompleteEvent event) {
-        if (testId.equals(suite)) {
+        if (testId.equals(suiteId)) {
+            //when suite is completed we no longer redirect for this suite
             try {
                 outputRedirector.stop();
             } finally {
-                suite = null;
+                suiteId = null;
             }
+        } else {
+            //when test is completed, should redirect output for the 'suite' to log things like @AfterSuite, etc.
+            redirectOutputFor(suiteId);
         }
         processor.completed(testId, event);
     }
 
+    private void redirectOutputFor(final Object testId) {
+        outputRedirector.redirectStandardOutputTo(new StdOutForwarder(testId));
+        outputRedirector.redirectStandardErrorTo(new StdErrForwarder(testId));
+    }
+
     public void output(Object testId, TestOutputEvent event) {
         processor.output(testId, event);
     }
@@ -71,4 +77,29 @@ public class CaptureTestOutputTestResultProcessor implements TestResultProcessor
     public void failure(Object testId, Throwable result) {
         processor.failure(testId, result);
     }
+
+
+    class StdOutForwarder implements StandardOutputListener {
+        private final Object testId;
+
+        public StdOutForwarder(Object testId) {
+            this.testId = testId;
+        }
+
+        public void onOutput(CharSequence output) {
+            processor.output(testId, new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, output.toString()));
+        }
+    }
+
+    class StdErrForwarder implements StandardOutputListener {
+        private final Object testId;
+
+        public StdErrForwarder(Object testId) {
+            this.testId = testId;
+        }
+
+        public void onOutput(CharSequence output) {
+            processor.output(testId, new DefaultTestOutputEvent(TestOutputEvent.Destination.StdErr, output.toString()));
+        }
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java
index c028311..641145c 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java
@@ -16,15 +16,15 @@
 
 package org.gradle.api.internal.tasks.testing.processors;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.UncheckedException;
 import org.gradle.messaging.actor.Actor;
 import org.gradle.messaging.actor.ActorFactory;
-import org.gradle.messaging.concurrent.CompositeStoppable;
 import org.gradle.messaging.dispatch.DispatchException;
-import org.gradle.util.UncheckedException;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -74,7 +74,7 @@ public class MaxNParallelTestClassProcessor implements TestClassProcessor {
         try {
             new CompositeStoppable(processors).add(actors).add(resultProcessorActor).stop();
         } catch (DispatchException e) {
-            throw UncheckedException.asUncheckedException(e.getCause());
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.java
old mode 100644
new mode 100755
index 4fc5c24..2d63f9d
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.tasks.testing.processors;
 
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java
index 4c1ba4e..f4aa2c4 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.tasks.testing.results;
 
 import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 
 public class AttachParentTestResultProcessor implements TestResultProcessor {
     private Object rootId;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
index d72e78c..8fc3882 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
@@ -30,14 +30,14 @@ public class DefaultTestResult implements TestResult, Serializable {
     private final long successfulCount;
     private final long failedCount;
 
-    public DefaultTestResult(ResultType result, List<Throwable> failures, long startTime, long endTime, long testCount, long successfulCount, long failedCount) {
-        this.failures = failures;
-        this.result = result;
-        this.startTime = startTime;
-        this.endTime = endTime;
-        this.testCount = testCount;
-        this.successfulCount = successfulCount;
-        this.failedCount = failedCount;
+    public DefaultTestResult(TestState state) {
+        this.failures = state.failures;
+        this.result = state.resultType;
+        this.startTime = state.getStartTime();
+        this.endTime = state.getEndTime();
+        this.testCount = state.testCount;
+        this.successfulCount = state.successfulCount;
+        this.failedCount = state.failedCount;
     }
 
     public ResultType getResultType() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java
index dedb1ee..6675967 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java
@@ -19,6 +19,7 @@ package org.gradle.api.internal.tasks.testing.results;
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 
 public class LoggingResultProcessor implements TestResultProcessor {
     private static final Logger LOGGER = Logging.getLogger(LoggingResultProcessor.class);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
index efea308..4e1e793 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
@@ -17,22 +17,22 @@
 package org.gradle.api.internal.tasks.testing.results;
 
 import org.gradle.api.internal.tasks.testing.*;
-import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 public abstract class StateTrackingTestResultProcessor implements TestResultProcessor {
     private final Map<Object, TestState> executing = new HashMap<Object, TestState>();
+    private TestDescriptor currentParent;
 
-    public void started(TestDescriptorInternal test, TestStartEvent event) {
+    public final void started(TestDescriptorInternal test, TestStartEvent event) {
         TestDescriptorInternal parent = null;
         if (event.getParentId() != null) {
             parent = executing.get(event.getParentId()).test;
         }
-        TestState state = new TestState(new DecoratingTestDescriptor(test, parent), event);
+        TestState state = new TestState(new DecoratingTestDescriptor(test, parent), event, executing);
         TestState oldState = executing.put(test.getId(), state);
         if (oldState != null) {
             throw new IllegalArgumentException(String.format("Received a start event for %s with duplicate id '%s'.",
@@ -42,96 +42,69 @@ public abstract class StateTrackingTestResultProcessor implements TestResultProc
         started(state);
     }
 
-    public void completed(Object testId, TestCompleteEvent event) {
+    public final void completed(Object testId, TestCompleteEvent event) {
         TestState testState = executing.remove(testId);
         if (testState == null) {
             throw new IllegalArgumentException(String.format(
-                    "Received a completed event for test with unknown id '%s'.", testId));
+                    "Received a completed event for test with unknown id '%s'. Registered test ids: '%s'",
+                    testId, executing.keySet()));
         }
 
+        //In case the output event arrives after completion of the test
+        //and we need to have a matching descriptor to inform the user which test this output belongs to
+        //we will use the current parent
+
+        //(SF) This approach should generally work because at the moment we reset capturing output per suite
+        //(see CaptureTestOutputTestResultProcessor) and that reset happens earlier in the chain.
+        //So in theory when suite is completed, the output redirector has been already stopped
+        //and there shouldn't be any output events passed
+        //See also GRADLE-2035
+        currentParent = testState.test.getParent();
+
         testState.completed(event);
         completed(testState);
     }
 
-    public void failure(Object testId, Throwable result) {
+    public final void failure(Object testId, Throwable result) {
         TestState testState = executing.get(testId);
         if (testState == null) {
-            throw new IllegalArgumentException(String.format("Received a failure event for test with unknown id '%s'.",
-                    testId));
+            throw new IllegalArgumentException(String.format(
+                    "Received a failure event for test with unknown id '%s'. Registered test ids: '%s'",
+                    testId, executing.keySet()));
         }
         testState.failures.add(result);
     }
 
-    public void output(Object testId, TestOutputEvent event) {
-        // Don't care
+    public final void output(Object testId, TestOutputEvent event) {
+        output(findDescriptor(testId), event);
     }
 
-    protected void started(TestState state) {
-    }
-
-    protected void completed(TestState state) {
-    }
-
-    public class TestState {
-        public final TestDescriptorInternal test;
-        final TestStartEvent startEvent;
-        public boolean failedChild;
-        public List<Throwable> failures = new ArrayList<Throwable>();
-        public long testCount;
-        public long successfulCount;
-        public long failedCount;
-        public TestResult.ResultType resultType;
-        TestCompleteEvent completeEvent;
-
-        public TestState(TestDescriptorInternal test, TestStartEvent startEvent) {
-            this.test = test;
-            this.startEvent = startEvent;
+    private TestDescriptor findDescriptor(Object testId) {
+        TestState state = executing.get(testId);
+        if (state != null) {
+            return state.test;
         }
 
-        public boolean isFailed() {
-            return failedChild || !failures.isEmpty();
+        TestDescriptor d = currentParent;
+        if (d != null) {
+            return d;
         }
 
-        public long getStartTime() {
-            return startEvent.getStartTime();
-        }
+        //in theory this should not happen
+        return new UnknownTestDescriptor();
+    }
 
-        public long getEndTime() {
-            return completeEvent.getEndTime();
-        }
+    protected void output(TestDescriptor descriptor, TestOutputEvent event) {
+        // Don't care
+    }
 
-        public long getExecutionTime() {
-            return completeEvent.getEndTime() - startEvent.getStartTime();
-        }
+    protected void started(TestState state) {
+    }
 
-        public void completed(TestCompleteEvent event) {
-            this.completeEvent = event;
-            resultType = isFailed() ? TestResult.ResultType.FAILURE
-                    : event.getResultType() != null ? event.getResultType() : TestResult.ResultType.SUCCESS;
-
-            if (!test.isComposite()) {
-                testCount = 1;
-                switch (resultType) {
-                    case SUCCESS:
-                        successfulCount = 1;
-                        break;
-                    case FAILURE:
-                        failedCount = 1;
-                        break;
-                }
-            }
-
-            if (startEvent.getParentId() != null) {
-                TestState parentState = executing.get(startEvent.getParentId());
-                if (parentState != null) {
-                    if (isFailed()) {
-                        parentState.failedChild = true;
-                    }
-                    parentState.testCount += testCount;
-                    parentState.successfulCount += successfulCount;
-                    parentState.failedCount += failedCount;
-                }
-            }
-        }
+    protected void completed(TestState state) {
+    }
+
+    protected TestState getTestStateFor(Object testId) {
+        return executing.get(testId);
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java
index 8821e93..1525082 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java
@@ -17,35 +17,40 @@
 package org.gradle.api.internal.tasks.testing.results;
 
 import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
-import org.gradle.api.tasks.testing.TestListener;
-import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.api.tasks.testing.*;
 
 public class TestListenerAdapter extends StateTrackingTestResultProcessor {
-    private final TestListener listener;
+    private final TestListener testListener;
+    private final TestOutputListener testOutputListener;
 
-    public TestListenerAdapter(TestListener listener) {
-        this.listener = listener;
+    public TestListenerAdapter(TestListener testListener, TestOutputListener testOutputListener) {
+        this.testListener = testListener;
+        this.testOutputListener = testOutputListener;
     }
 
     @Override
     protected void started(TestState state) {
         TestDescriptorInternal test = state.test;
         if (test.isComposite()) {
-            listener.beforeSuite(test);
+            testListener.beforeSuite(test);
         } else {
-            listener.beforeTest(test);
+            testListener.beforeTest(test);
         }
     }
 
     @Override
     protected void completed(TestState state) {
-        TestResult result = new DefaultTestResult(state.resultType, state.failures, state.getStartTime(),
-                state.getEndTime(), state.testCount, state.successfulCount, state.failedCount);
+        TestResult result = new DefaultTestResult(state);
         TestDescriptorInternal test = state.test;
         if (test.isComposite()) {
-            listener.afterSuite(test, result);
+            testListener.afterSuite(test, result);
         } else {
-            listener.afterTest(test, result);
+            testListener.afterTest(test, result);
         }
     }
+
+    @Override
+    public void output(TestDescriptor test, TestOutputEvent event) {
+        testOutputListener.onOutput(test, event);
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java
new file mode 100644
index 0000000..17dc070
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestState.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 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.testing.results;
+
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent;
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
+import org.gradle.api.internal.tasks.testing.TestStartEvent;
+import org.gradle.api.tasks.testing.TestResult;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Extracted from the StateTrackingTestResultProcessor
+ *
+ * by Szczepan Faber, created at: 10/14/11
+ */
+public class TestState {
+    public final TestDescriptorInternal test;
+    final TestStartEvent startEvent;
+    private final Map<Object, TestState> executing;
+    public boolean failedChild;
+    public List<Throwable> failures = new ArrayList<Throwable>();
+    public long testCount;
+    public long successfulCount;
+    public long failedCount;
+    public TestResult.ResultType resultType;
+    TestCompleteEvent completeEvent;
+
+    public TestState(TestDescriptorInternal test, TestStartEvent startEvent, Map<Object, TestState> executing) {
+        this.test = test;
+        this.startEvent = startEvent;
+        this.executing = executing;
+    }
+
+    public boolean isFailed() {
+        return failedChild || !failures.isEmpty();
+    }
+
+    public long getStartTime() {
+        return startEvent.getStartTime();
+    }
+
+    public long getEndTime() {
+        return completeEvent.getEndTime();
+    }
+
+    public long getExecutionTime() {
+        return completeEvent.getEndTime() - startEvent.getStartTime();
+    }
+
+    public void completed(TestCompleteEvent event) {
+        this.completeEvent = event;
+        resultType = isFailed() ? TestResult.ResultType.FAILURE
+                : event.getResultType() != null ? event.getResultType() : TestResult.ResultType.SUCCESS;
+
+        if (!test.isComposite()) {
+            testCount = 1;
+            switch (resultType) {
+                case SUCCESS:
+                    successfulCount = 1;
+                    break;
+                case FAILURE:
+                    failedCount = 1;
+                    break;
+            }
+        }
+
+        if (startEvent.getParentId() != null) {
+            TestState parentState = executing.get(startEvent.getParentId());
+            if (parentState != null) {
+                if (isFailed()) {
+                    parentState.failedChild = true;
+                }
+                parentState.testCount += testCount;
+                parentState.successfulCount += successfulCount;
+                parentState.failedCount += failedCount;
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java
new file mode 100644
index 0000000..3962a70
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/UnknownTestDescriptor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.testing.results;
+
+import org.gradle.api.tasks.testing.TestDescriptor;
+
+/**
+ * by Szczepan Faber, created at: 1/8/12
+ */
+public class UnknownTestDescriptor implements TestDescriptor {
+
+    public String getName() {
+        return "Unknown test (possible bug, please report)";
+    }
+
+    public String getClassName() {
+        return null;
+    }
+
+    public boolean isComposite() {
+        return false;
+    }
+
+    public TestDescriptor getParent() {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGConfigurationListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGConfigurationListener.java
new file mode 100644
index 0000000..ea65083
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGConfigurationListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.testing.testng;
+
+import org.testng.ITestResult;
+
+/**
+ * Our version of TestNG's IConfigurationListener. Can be adapted to
+ * org.testng.internal.IConfigurationListener (from TestNG < 6.2) or
+ * org.testng.IConfigurationListener2 (from TestNG >= 6.2). Became
+ * necessary because TestNG 6.2 changed the package name of the former
+ * interface from org.testng.internal to org.testng.
+ * 
+ * @see TestNGListenerAdapterFactory
+ */
+public interface TestNGConfigurationListener {
+    /**
+     * Invoked whenever a configuration method succeeded.
+     */
+    void onConfigurationSuccess(ITestResult itr);
+
+    /**
+     * Invoked whenever a configuration method failed.
+     */
+    void onConfigurationFailure(ITestResult itr);
+
+    /**
+     * Invoked whenever a configuration method was skipped.
+     */
+    void onConfigurationSkip(ITestResult itr);
+    /**
+     * Invoked before a configuration method is invoked. 
+     * 
+     * Note: This method is only invoked for TestNG 6.2 or higher.
+     */
+    void beforeConfiguration(ITestResult tr);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java
index a832a3a..14e23e5 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java
@@ -17,6 +17,7 @@ package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.tasks.testing.detection.AbstractTestFrameworkDetector;
+import org.gradle.api.internal.tasks.testing.detection.ClassFileExtractionManager;
 import org.gradle.api.internal.tasks.testing.detection.TestClassVisitor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,8 +30,8 @@ import java.io.File;
 class TestNGDetector extends AbstractTestFrameworkDetector<TestNGTestClassDetecter> {
     private static final Logger LOGGER = LoggerFactory.getLogger(TestNGDetector.class);
 
-    TestNGDetector(File testClassesDirectory, FileCollection testClasspath) {
-        super(testClassesDirectory, testClasspath);
+    TestNGDetector(File testClassesDirectory, FileCollection testClasspath, ClassFileExtractionManager classFileExtractionManager) {
+        super(testClassesDirectory, testClasspath, classFileExtractionManager);
     }
 
     protected TestNGTestClassDetecter createClassVisitor() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java
new file mode 100644
index 0000000..a801553
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 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.testing.testng;
+
+import org.gradle.util.ReflectionUtil;
+import org.testng.ITestListener;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+class TestNGListenerAdapterFactory {
+    private final ClassLoader classLoader;
+
+    TestNGListenerAdapterFactory(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public ITestListener createAdapter(ITestListener listener) {
+        Class<?> testNG6Class = tryLoadClass("org.testng.IConfigurationListener2");
+        if (testNG6Class != null) {
+            return createProxy(testNG6Class, listener);
+        }
+
+        Class<?> testNG5Class = tryLoadClass("org.testng.internal.IConfigurationListener");
+        if (testNG5Class != null) {
+            return createProxy(testNG5Class, listener);
+        }
+
+        throw new UnsupportedOperationException("Neither found interface 'org.testng.IConfigurationListener2' nor interface 'org.testng.internal.IConfigurationListener'. Which version of TestNG are you using?");
+    }
+    
+    private Class<?> tryLoadClass(String name) {
+        try {
+            return classLoader.loadClass(name);
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+    
+    private ITestListener createProxy(Class<?> configListenerClass, final ITestListener listener) {
+        return (ITestListener) Proxy.newProxyInstance(classLoader, new Class<?>[] {ITestListener.class, configListenerClass}, new InvocationHandler() {
+            public Object invoke(Object proxy, Method method, Object[] args) {
+                return ReflectionUtil.invoke(listener, method.getName(), args);
+            }
+        });
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
old mode 100644
new mode 100755
index 22b2a04..0e5a681
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
@@ -1,97 +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.internal.tasks.testing.testng;
-
-import org.gradle.api.GradleException;
-import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor;
-import org.gradle.api.tasks.testing.testng.TestNGOptions;
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
-import org.gradle.logging.StandardOutputRedirector;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.GUtil;
-import org.gradle.util.IdGenerator;
-import org.testng.TestNG;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-public class TestNGTestClassProcessor implements TestClassProcessor {
-    private final List<Class<?>> testClasses = new ArrayList<Class<?>>();
-    private final File testReportDir;
-    private final TestNGOptions options;
-    private final List<File> suiteFiles;
-    private final IdGenerator<?> idGenerator;
-    private final StandardOutputRedirector outputRedirector;
-    private TestNGTestResultProcessorAdapter testResultProcessor;
-    private ClassLoader applicationClassLoader;
-
-    public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator, StandardOutputRedirector outputRedirector) {
-        this.testReportDir = testReportDir;
-        this.options = options;
-        this.suiteFiles = suiteFiles;
-        this.idGenerator = idGenerator;
-        this.outputRedirector = outputRedirector;
-    }
-
-    public void startProcessing(TestResultProcessor resultProcessor) {
-        testResultProcessor = new TestNGTestResultProcessorAdapter(new CaptureTestOutputTestResultProcessor(resultProcessor, outputRedirector), idGenerator);
-        applicationClassLoader = Thread.currentThread().getContextClassLoader();
-    }
-
-    public void processTestClass(TestClassRunInfo testClass) {
-        try {
-            testClasses.add(applicationClassLoader.loadClass(testClass.getTestClassName()));
-        } catch (Throwable e) {
-            throw new GradleException(String.format("Could not load test class '%s'.", testClass.getTestClassName()), e);
-        }
-    }
-
-    public void stop() {
-        TestNG testNg = new TestNG();
-        testNg.setOutputDirectory(testReportDir.getAbsolutePath());
-        testNg.setDefaultSuiteName(options.getSuiteName());
-        testNg.setDefaultTestName(options.getTestName());
-        testNg.setAnnotations(options.getAnnotations());
-        if (options.getJavadocAnnotations()) {
-            testNg.setSourcePath(GUtil.join(options.getTestResources(), File.pathSeparator));
-        }
-        testNg.setUseDefaultListeners(options.getUseDefaultListeners());
-        testNg.addListener(testResultProcessor);
-        testNg.setVerbose(0);
-        testNg.setGroups(GUtil.join(options.getIncludeGroups(), ","));
-        testNg.setExcludedGroups(GUtil.join(options.getExcludeGroups(), ","));
-        for (String listenerClass : options.getListeners()) {
-            try {
-                testNg.addListener(applicationClassLoader.loadClass(listenerClass).newInstance());
-            } catch (Throwable e) {
-                throw new GradleException(String.format("Could not add a test listener with class '%s'.", listenerClass), e);
-            }
-        }
-
-        if (!suiteFiles.isEmpty()) {
-            testNg.setTestSuites(GFileUtils.toPaths(suiteFiles));
-        } else {
-            Class[] classes = testClasses.toArray(new Class[testClasses.size()]);
-            testNg.setTestClasses(classes);
-        }
-
-        testNg.run();
-    }
-}
+/*
+ * 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.testing.testng;
+
+import groovy.lang.MissingMethodException;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor;
+import org.gradle.api.tasks.testing.testng.TestNGOptions;
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
+import org.gradle.logging.StandardOutputRedirector;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.ReflectionUtil;
+import org.testng.ITestListener;
+import org.testng.TestNG;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestNGTestClassProcessor implements TestClassProcessor {
+    private final List<Class<?>> testClasses = new ArrayList<Class<?>>();
+    private final File testReportDir;
+    private final TestNGOptions options;
+    private final List<File> suiteFiles;
+    private final IdGenerator<?> idGenerator;
+    private final StandardOutputRedirector outputRedirector;
+    private TestNGTestResultProcessorAdapter testResultProcessor;
+    private ClassLoader applicationClassLoader;
+
+    public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator, StandardOutputRedirector outputRedirector) {
+        this.testReportDir = testReportDir;
+        this.options = options;
+        this.suiteFiles = suiteFiles;
+        this.idGenerator = idGenerator;
+        this.outputRedirector = outputRedirector;
+    }
+
+    public void startProcessing(TestResultProcessor resultProcessor) {
+        testResultProcessor = new TestNGTestResultProcessorAdapter(new CaptureTestOutputTestResultProcessor(resultProcessor, outputRedirector), idGenerator);
+        applicationClassLoader = Thread.currentThread().getContextClassLoader();
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        try {
+            testClasses.add(applicationClassLoader.loadClass(testClass.getTestClassName()));
+        } catch (Throwable e) {
+            throw new GradleException(String.format("Could not load test class '%s'.", testClass.getTestClassName()), e);
+        }
+    }
+
+    public void stop() {
+        TestNG testNg = new TestNG();
+        testNg.setOutputDirectory(testReportDir.getAbsolutePath());
+        testNg.setDefaultSuiteName(options.getSuiteName());
+        testNg.setDefaultTestName(options.getTestName());
+        try {
+            ReflectionUtil.invoke(testNg, "setAnnotations", options.getAnnotations());
+        } catch (MissingMethodException e) {
+            /* do nothing; method has been removed in TestNG 6.3 */
+        }
+        if (options.getJavadocAnnotations()) {
+            testNg.setSourcePath(GUtil.join(options.getTestResources(), File.pathSeparator));
+        }
+        testNg.setUseDefaultListeners(options.getUseDefaultListeners());
+        testNg.addListener((Object) adaptListener(testResultProcessor));
+        testNg.setVerbose(0);
+        testNg.setGroups(GUtil.join(options.getIncludeGroups(), ","));
+        testNg.setExcludedGroups(GUtil.join(options.getExcludeGroups(), ","));
+        for (String listenerClass : options.getListeners()) {
+            try {
+                testNg.addListener(applicationClassLoader.loadClass(listenerClass).newInstance());
+            } catch (Throwable e) {
+                throw new GradleException(String.format("Could not add a test listener with class '%s'.", listenerClass), e);
+            }
+        }
+
+        if (!suiteFiles.isEmpty()) {
+            testNg.setTestSuites(GFileUtils.toPaths(suiteFiles));
+        } else {
+            Class[] classes = testClasses.toArray(new Class[testClasses.size()]);
+            testNg.setTestClasses(classes);
+        }
+
+        testNg.run();
+    }
+
+    private ITestListener adaptListener(ITestListener listener) {
+        TestNGListenerAdapterFactory factory = new TestNGListenerAdapterFactory(applicationClassLoader);
+        return factory.createAdapter(listener);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
index e42d9ef..3745bc4 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
@@ -18,10 +18,11 @@ package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.Action;
 import org.gradle.api.JavaVersion;
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestFramework;
 import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.api.internal.tasks.testing.detection.ClassFileExtractionManager;
 import org.gradle.api.internal.tasks.testing.junit.JULRedirector;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
@@ -45,7 +46,7 @@ public class TestNGTestFramework implements TestFramework {
         this.testTask = testTask;
         options = new TestNGOptions(testTask.getProject().getProjectDir());
         options.setAnnotationsOnSourceCompatibility(JavaVersion.toVersion(testTask.getProject().property("sourceCompatibility")));
-        detector = new TestNGDetector(testTask.getTestClassesDir(), testTask.getClasspath());
+        detector = new TestNGDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDir()));
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
index 4cd59a5..964d307 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
@@ -1,132 +1,144 @@
-/*
- * 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.testing.testng;
-
-import org.gradle.api.internal.tasks.testing.*;
-import org.gradle.api.tasks.testing.TestResult;
-import org.gradle.util.IdGenerator;
-import org.testng.ITestContext;
-import org.testng.ITestListener;
-import org.testng.ITestNGMethod;
-import org.testng.ITestResult;
-import org.testng.internal.IConfigurationListener;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class TestNGTestResultProcessorAdapter implements ITestListener, IConfigurationListener {
-    private final TestResultProcessor resultProcessor;
-    private final IdGenerator<?> idGenerator;
-    private final Object lock = new Object();
-    private Map<String, Object> suites = new HashMap<String, Object>();
-    private Map<String, Object> tests = new HashMap<String, Object>();
-    private Map<ITestNGMethod, Object> testMethodToSuiteMapping = new HashMap<ITestNGMethod, Object>();
-
-    public TestNGTestResultProcessorAdapter(TestResultProcessor resultProcessor, IdGenerator<?> idGenerator) {
-        this.resultProcessor = resultProcessor;
-        this.idGenerator = idGenerator;
-    }
-
-    public void onStart(ITestContext iTestContext) {
-        TestDescriptorInternal testInternal;
-        synchronized (lock) {
-            testInternal = new DefaultTestSuiteDescriptor(idGenerator.generateId(), iTestContext.getName());
-            suites.put(testInternal.getName(), testInternal.getId());
-            for (ITestNGMethod method : iTestContext.getAllTestMethods()) {
-                testMethodToSuiteMapping.put(method, testInternal.getId());
-            }
-        }
-        resultProcessor.started(testInternal, new TestStartEvent(iTestContext.getStartDate().getTime()));
-    }
-
-    public void onFinish(ITestContext iTestContext) {
-        Object id;
-        synchronized (lock) {
-            id = suites.remove(iTestContext.getName());
-            for (ITestNGMethod method : iTestContext.getAllTestMethods()) {
-                testMethodToSuiteMapping.remove(method);
-            }
-        }
-        resultProcessor.completed(id, new TestCompleteEvent(iTestContext.getEndDate().getTime()));
-    }
-
-    public void onTestStart(ITestResult iTestResult) {
-        TestDescriptorInternal testInternal;
-        Object parentId;
-        synchronized (lock) {
-            testInternal = new DefaultTestMethodDescriptor(idGenerator.generateId(), iTestResult.getTestClass().getName(), iTestResult.getName());
-            Object oldTestId = tests.put(testInternal.getName(), testInternal.getId());
-            assert oldTestId == null;
-            parentId = testMethodToSuiteMapping.get(iTestResult.getMethod());
-            assert parentId != null;
-        }
-        resultProcessor.started(testInternal, new TestStartEvent(iTestResult.getStartMillis(), parentId));
-    }
-
-    public void onTestSuccess(ITestResult iTestResult) {
-        onTestFinished(iTestResult, TestResult.ResultType.SUCCESS);
-    }
-
-    public void onTestFailure(ITestResult iTestResult) {
-        onTestFinished(iTestResult, TestResult.ResultType.FAILURE);
-    }
-
-    public void onTestSkipped(ITestResult iTestResult) {
-        onTestFinished(iTestResult, TestResult.ResultType.SKIPPED);
-    }
-
-    public void onTestFailedButWithinSuccessPercentage(ITestResult iTestResult) {
-        onTestFinished(iTestResult, TestResult.ResultType.SUCCESS);
-    }
-
-    private void onTestFinished(ITestResult iTestResult, TestResult.ResultType resultType) {
-        Object testId;
-        TestStartEvent startEvent = null;
-        synchronized (lock) {
-            testId = tests.remove(iTestResult.getName());
-            if (testId == null) {
-                // This can happen when a method fails which this method depends on 
-                testId = idGenerator.generateId();
-                Object parentId = testMethodToSuiteMapping.get(iTestResult.getMethod());
-                startEvent = new TestStartEvent(iTestResult.getStartMillis(), parentId);
-            }
-        }
-        if (startEvent != null) {
-            // Synthesize a start event
-            resultProcessor.started(new DefaultTestMethodDescriptor(testId, iTestResult.getTestClass().getName(), iTestResult.getName()), startEvent);
-        }
-        if (resultType == TestResult.ResultType.FAILURE) {
-            resultProcessor.failure(testId, iTestResult.getThrowable());
-        }
-        resultProcessor.completed(testId, new TestCompleteEvent(iTestResult.getEndMillis(), resultType));
-    }
-
-    public void onConfigurationSuccess(ITestResult testResult) {
-    }
-
-    public void onConfigurationSkip(ITestResult testResult) {
-    }
-
-    public void onConfigurationFailure(ITestResult testResult) {
-        // Synthesise a test for the broken configuration method
-        TestDescriptorInternal test = new DefaultTestMethodDescriptor(idGenerator.generateId(),
-                testResult.getMethod().getTestClass().getName(), testResult.getMethod().getMethodName());
-        resultProcessor.started(test, new TestStartEvent(testResult.getStartMillis()));
-        resultProcessor.failure(test.getId(), testResult.getThrowable());
-        resultProcessor.completed(test.getId(), new TestCompleteEvent(testResult.getEndMillis(), TestResult.ResultType.FAILURE));
-    }
-}
+/*
+ * 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.testing.testng;
+
+import com.google.common.collect.Maps;
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.util.IdGenerator;
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+public class TestNGTestResultProcessorAdapter implements ITestListener, TestNGConfigurationListener {
+    private final TestResultProcessor resultProcessor;
+    private final IdGenerator<?> idGenerator;
+    private final Object lock = new Object();
+    private Map<String, Object> suites = new HashMap<String, Object>();
+    private Map<ITestResult, Object> tests = new HashMap<ITestResult, Object>();
+    private Map<ITestNGMethod, Object> testMethodToSuiteMapping = new HashMap<ITestNGMethod, Object>();
+    private ConcurrentMap<ITestResult, Boolean> failedConfigurations = Maps.newConcurrentMap();
+
+    public TestNGTestResultProcessorAdapter(TestResultProcessor resultProcessor, IdGenerator<?> idGenerator) {
+        this.resultProcessor = resultProcessor;
+        this.idGenerator = idGenerator;
+    }
+
+    public void onStart(ITestContext iTestContext) {
+        TestDescriptorInternal testInternal;
+        synchronized (lock) {
+            testInternal = new DefaultTestSuiteDescriptor(idGenerator.generateId(), iTestContext.getName());
+            suites.put(testInternal.getName(), testInternal.getId());
+            for (ITestNGMethod method : iTestContext.getAllTestMethods()) {
+                testMethodToSuiteMapping.put(method, testInternal.getId());
+            }
+        }
+        resultProcessor.started(testInternal, new TestStartEvent(iTestContext.getStartDate().getTime()));
+    }
+
+    public void onFinish(ITestContext iTestContext) {
+        Object id;
+        synchronized (lock) {
+            id = suites.remove(iTestContext.getName());
+            for (ITestNGMethod method : iTestContext.getAllTestMethods()) {
+                testMethodToSuiteMapping.remove(method);
+            }
+        }
+        resultProcessor.completed(id, new TestCompleteEvent(iTestContext.getEndDate().getTime()));
+    }
+
+    public void onTestStart(ITestResult iTestResult) {
+        TestDescriptorInternal testInternal;
+        Object parentId;
+        synchronized (lock) {
+            testInternal = new DefaultTestMethodDescriptor(idGenerator.generateId(), iTestResult.getTestClass().getName(), iTestResult.getName());
+            Object oldTestId = tests.put(iTestResult, testInternal.getId());
+            assert oldTestId == null : "Apparently some other test has started but it hasn't finished. "
+                    + "Expect the resultProcessor to break. "
+                    + "Don't expect to see this assertion stack trace due to the current architecture";
+
+            parentId = testMethodToSuiteMapping.get(iTestResult.getMethod());
+            assert parentId != null;
+        }
+        resultProcessor.started(testInternal, new TestStartEvent(iTestResult.getStartMillis(), parentId));
+    }
+
+    public void onTestSuccess(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.SUCCESS);
+    }
+
+    public void onTestFailure(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.FAILURE);
+    }
+
+    public void onTestSkipped(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.SKIPPED);
+    }
+
+    public void onTestFailedButWithinSuccessPercentage(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.SUCCESS);
+    }
+
+    private void onTestFinished(ITestResult iTestResult, TestResult.ResultType resultType) {
+        Object testId;
+        TestStartEvent startEvent = null;
+        synchronized (lock) {
+            testId = tests.remove(iTestResult);
+            if (testId == null) {
+                // This can happen when a method fails which this method depends on 
+                testId = idGenerator.generateId();
+                Object parentId = testMethodToSuiteMapping.get(iTestResult.getMethod());
+                startEvent = new TestStartEvent(iTestResult.getStartMillis(), parentId);
+            }
+        }
+        if (startEvent != null) {
+            // Synthesize a start event
+            resultProcessor.started(new DefaultTestMethodDescriptor(testId, iTestResult.getTestClass().getName(), iTestResult.getName()), startEvent);
+        }
+        if (resultType == TestResult.ResultType.FAILURE) {
+            resultProcessor.failure(testId, iTestResult.getThrowable());
+        }
+        resultProcessor.completed(testId, new TestCompleteEvent(iTestResult.getEndMillis(), resultType));
+    }
+
+    public void onConfigurationSuccess(ITestResult testResult) {
+    }
+
+    public void onConfigurationSkip(ITestResult testResult) {
+    }
+
+    public void onConfigurationFailure(ITestResult testResult) {
+        if (failedConfigurations.put(testResult, true) != null) {
+            // work around for bug in TestNG 6.2+: listener is notified twice per event
+            return;
+        }
+        // Synthesise a test for the broken configuration method
+        TestDescriptorInternal test = new DefaultTestMethodDescriptor(idGenerator.generateId(),
+                testResult.getMethod().getTestClass().getName(), testResult.getMethod().getMethodName());
+        resultProcessor.started(test, new TestStartEvent(testResult.getStartMillis()));
+        resultProcessor.failure(test.getId(), testResult.getThrowable());
+        resultProcessor.completed(test.getId(), new TestCompleteEvent(testResult.getEndMillis(), TestResult.ResultType.FAILURE));
+    }
+
+    public void beforeConfiguration(ITestResult tr) {
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
old mode 100644
new mode 100755
index 6a46207..c30c74c
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.tasks.testing.worker;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.Factory;
+import org.gradle.internal.Factory;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
old mode 100644
new mode 100755
index a7bd8a9..156a224
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
@@ -1,96 +1,135 @@
-/*
- * 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.testing.worker;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.project.DefaultServiceRegistry;
-import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
-import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
-import org.gradle.listener.ContextClassLoaderProxy;
-import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.process.internal.WorkerProcessContext;
-import org.gradle.util.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.Serializable;
-import java.util.concurrent.CountDownLatch;
-
-public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClassProcessor, Serializable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(TestWorker.class);
-    public static final String WORKER_ID_SYS_PROPERTY = "org.gradle.test.worker";
-    private final WorkerTestClassProcessorFactory factory;
-    private CountDownLatch completed;
-    private TestClassProcessor processor;
-    private TestResultProcessor resultProcessor;
-
-    public TestWorker(WorkerTestClassProcessorFactory factory) {
-        this.factory = factory;
-    }
-
-    public void execute(WorkerProcessContext workerProcessContext) {
-        LOGGER.info("{} executing tests.", workerProcessContext.getDisplayName());
-
-        completed = new CountDownLatch(1);
-
-        System.setProperty(WORKER_ID_SYS_PROPERTY, workerProcessContext.getWorkerId().toString());
-        
-        ObjectConnection serverConnection = workerProcessContext.getServerConnection();
-
-        IdGenerator<Object> idGenerator = new CompositeIdGenerator(workerProcessContext.getWorkerId(),
-                new LongIdGenerator());
-
-        DefaultServiceRegistry testServices = new DefaultServiceRegistry();
-        testServices.add(IdGenerator.class, idGenerator);
-        TestClassProcessor targetProcessor = factory.create(testServices);
-
-        targetProcessor = new WorkerTestClassProcessor(targetProcessor, idGenerator.generateId(),
-                workerProcessContext.getDisplayName(), new TrueTimeProvider());
-        ContextClassLoaderProxy<TestClassProcessor> proxy = new ContextClassLoaderProxy<TestClassProcessor>(
-                TestClassProcessor.class, targetProcessor, workerProcessContext.getApplicationClassLoader());
-        processor = proxy.getSource();
-
-        this.resultProcessor = serverConnection.addOutgoing(TestResultProcessor.class);
-
-        serverConnection.addIncoming(RemoteTestClassProcessor.class, this);
-
-        try {
-            completed.await();
-        } catch (InterruptedException e) {
-            throw new UncheckedException(e);
-        }
-        LOGGER.info("{} finished executing tests.", workerProcessContext.getDisplayName());
-    }
-
-    public void startProcessing() {
-        processor.startProcessing(resultProcessor);
-    }
-
-    public void processTestClass(TestClassRunInfo testClass) {
-        processor.processTestClass(testClass);
-    }
-
-    public void stop() {
-        try {
-            processor.stop();
-        } finally {
-            completed.countDown();
-        }
-    }
-}
+/*
+ * 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.testing.worker;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.listener.ContextClassLoaderProxy;
+import org.gradle.messaging.actor.ActorFactory;
+import org.gradle.messaging.actor.internal.DefaultActorFactory;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.process.internal.WorkerProcessContext;
+import org.gradle.util.CompositeIdGenerator;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.LongIdGenerator;
+import org.gradle.util.TrueTimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+
+public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClassProcessor, Serializable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TestWorker.class);
+    public static final String WORKER_ID_SYS_PROPERTY = "org.gradle.test.worker";
+    private final WorkerTestClassProcessorFactory factory;
+    private CountDownLatch completed;
+    private TestClassProcessor processor;
+    private TestResultProcessor resultProcessor;
+
+    public TestWorker(WorkerTestClassProcessorFactory factory) {
+        this.factory = factory;
+    }
+
+    public void execute(final WorkerProcessContext workerProcessContext) {
+        LOGGER.info("{} executing tests.", workerProcessContext.getDisplayName());
+
+        completed = new CountDownLatch(1);
+
+        System.setProperty(WORKER_ID_SYS_PROPERTY, workerProcessContext.getWorkerId().toString());
+
+        DefaultServiceRegistry testServices = new TestFrameworkServiceRegistry(workerProcessContext);
+        startReceivingTests(workerProcessContext, testServices);
+
+        try {
+            try {
+                completed.await();
+            } catch (InterruptedException e) {
+                throw new UncheckedException(e);
+            }
+        } finally {
+            LOGGER.info("{} finished executing tests.", workerProcessContext.getDisplayName());
+            // Clean out any security manager the tests might have installed
+            System.setSecurityManager(null);
+            testServices.close();
+        }
+    }
+
+    private void startReceivingTests(WorkerProcessContext workerProcessContext, ServiceRegistry testServices) {
+        TestClassProcessor targetProcessor = factory.create(testServices);
+        IdGenerator<Object> idGenerator = testServices.get(IdGenerator.class);
+
+        targetProcessor = new WorkerTestClassProcessor(targetProcessor, idGenerator.generateId(),
+                workerProcessContext.getDisplayName(), new TrueTimeProvider());
+        ContextClassLoaderProxy<TestClassProcessor> proxy = new ContextClassLoaderProxy<TestClassProcessor>(
+                TestClassProcessor.class, targetProcessor, workerProcessContext.getApplicationClassLoader());
+        processor = proxy.getSource();
+
+        ObjectConnection serverConnection = workerProcessContext.getServerConnection();
+        this.resultProcessor = serverConnection.addOutgoing(TestResultProcessor.class);
+        serverConnection.addIncoming(RemoteTestClassProcessor.class, this);
+    }
+
+    public void startProcessing() {
+        processor.startProcessing(resultProcessor);
+    }
+
+    public void processTestClass(final TestClassRunInfo testClass) {
+        try {
+            processor.processTestClass(testClass);
+        } finally {
+            // Clean the interrupted status
+            Thread.interrupted();
+        }
+    }
+
+    public void stop() {
+        try {
+            processor.stop();
+        } finally {
+            completed.countDown();
+        }
+    }
+
+    private static class TestFrameworkServiceRegistry extends DefaultServiceRegistry {
+        private final WorkerProcessContext workerProcessContext;
+
+        public TestFrameworkServiceRegistry(WorkerProcessContext workerProcessContext) {
+            this.workerProcessContext = workerProcessContext;
+        }
+
+        protected IdGenerator<Object> createIdGenerator() {
+            return new CompositeIdGenerator(workerProcessContext.getWorkerId(), new LongIdGenerator());
+        }
+
+        protected ExecutorFactory createExecutorFactory() {
+            return new DefaultExecutorFactory();
+        }
+
+        protected ActorFactory createActorFactory() {
+            return new DefaultActorFactory(get(ExecutorFactory.class));
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
index b583ffc..bcc6f5e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
@@ -214,7 +214,7 @@ public class DefaultManifest implements org.gradle.api.java.archives.Manifest {
         } catch (ManifestException e) {
             throw new org.gradle.api.java.archives.ManifestException(e.getMessage(), e);
         } catch (IOException e) {
-            throw new UncheckedIOException(e.getMessage(), e);
+            throw new UncheckedIOException(e);
         }
     }
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
index a8ac5e1..531af92 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.java.archives.internal;
 
+import com.google.common.collect.Sets;
 import groovy.lang.Closure;
 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 import org.gradle.api.Action;
@@ -60,7 +61,7 @@ public class DefaultManifestMergeSpec implements ManifestMergeSpec {
     private DefaultManifest mergeManifest(DefaultManifest baseManifest, DefaultManifest toMergeManifest, FileResolver fileResolver) {
         DefaultManifest mergedManifest = new DefaultManifest(fileResolver);
         mergeSection(null, mergedManifest, baseManifest.getAttributes(), toMergeManifest.getAttributes());
-        Set<String> allSections = GUtil.addSets(baseManifest.getSections().keySet(), toMergeManifest.getSections().keySet());
+        Set<String> allSections = Sets.union(baseManifest.getSections().keySet(), toMergeManifest.getSections().keySet());
         for (String section : allSections) {
             mergeSection(section, mergedManifest,
                     GUtil.elvis(baseManifest.getSections().get(section), new DefaultAttributes()),
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
index 9c1eb2c..8dda0bb 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPlugin.groovy
@@ -1,126 +1,133 @@
-/*
- * Copyright 2011 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.plugins
-
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.file.CopySpec
-import org.gradle.api.tasks.JavaExec
-import org.gradle.api.tasks.Sync
-import org.gradle.api.tasks.application.CreateStartScripts
-import org.gradle.api.tasks.bundling.Zip
-import org.gradle.api.GradleException
-
-/**
- * <p>A {@link Plugin} which runs a project as a Java Application.</p>
- *
- * @author Rene Groeschke
- */
-class ApplicationPlugin implements Plugin<Project> {
-    static final String APPLICATION_PLUGIN_NAME = "application"
-    static final String APPLICATION_GROUP = APPLICATION_PLUGIN_NAME
-
-    static final String TASK_RUN_NAME = "run"
-    static final String TASK_START_SCRIPTS_NAME = "startScripts"
-    static final String TASK_INSTALL_NAME = "installApp"
-    static final String TASK_DIST_ZIP_NAME = "distZip"
-
-    private Project project
-    private ApplicationPluginConvention pluginConvention
-
-    void apply(final Project project) {
-        this.project = project
-        project.plugins.apply(JavaPlugin)
-
-        addPluginConvention()
-        addRunTask()
-        addCreateScriptsTask()
-        addInstallTask()
-        addDistZipTask()
-    }
-
-    private void addPluginConvention() {
-        pluginConvention = new ApplicationPluginConvention()
-        pluginConvention.applicationName = project.name
-        project.convention.plugins.application = pluginConvention
-    }
-
-    private void addRunTask() {
-        def run = project.tasks.add(TASK_RUN_NAME, JavaExec)
-        run.description = "Runs this project as a JVM application"
-        run.group = APPLICATION_GROUP
-        run.classpath = project.sourceSets.main.runtimeClasspath
-        run.conventionMapping.main = { pluginConvention.mainClassName }
-    }
-
-    // @Todo: refactor this task configuration to extend a copy task and use replace tokens
-    private void addCreateScriptsTask() {
-        def startScripts = project.tasks.add(TASK_START_SCRIPTS_NAME, CreateStartScripts)
-        startScripts.description = "Creates OS specific scripts to run the project as a JVM application."
-        startScripts.classpath = project.tasks[JavaPlugin.JAR_TASK_NAME].outputs.files + project.configurations.runtime
-        startScripts.conventionMapping.mainClassName = { pluginConvention.mainClassName }
-        startScripts.conventionMapping.applicationName = { pluginConvention.applicationName }
-        startScripts.conventionMapping.outputDir = { new File(project.buildDir, 'scripts') }
-    }
-
-    private void addInstallTask() {
-        def installTask = project.tasks.add(TASK_INSTALL_NAME, Sync)
-        installTask.description = "Installs the project as a JVM application along with libs and OS specific scripts."
-        installTask.group = APPLICATION_GROUP
-        installTask.with(createDistSpec())
-        installTask.into { project.file("${project.buildDir}/install/${pluginConvention.applicationName}") }
-        installTask.doFirst {
-            if (destinationDir.directory) {
-                if (!new File(destinationDir, 'lib').directory || !new File(destinationDir, 'bin').directory) {
-                    throw new GradleException("The specified installation directory '${destinationDir}' is neither empty nor does it contain an installation for '${pluginConvention.applicationName}'.\n" +
-                            "If you really want to install to this directory, delete it and run the install task again.\n" +
-                            "Alternatively, choose a different installation directory."
-                    )
-                }
-            }
-        }
-        installTask.doLast {
-            project.ant.chmod(file: "${destinationDir.absolutePath}/bin/${pluginConvention.applicationName}", perm: 'ugo+x')
-        }
-    }
-
-    private void addDistZipTask() {
-        def distZipTask = project.tasks.add(TASK_DIST_ZIP_NAME, Zip)
-        distZipTask.description = "Bundles the project as a JVM application with libs and OS specific scripts."
-        distZipTask.group = APPLICATION_GROUP
-        distZipTask.conventionMapping.baseName = { pluginConvention.applicationName }
-        def baseDir = { distZipTask.archiveName - ".zip" }
-        distZipTask.into(baseDir) {
-            with(createDistSpec())
-        }
-    }
-
-    private CopySpec createDistSpec() {
-        def jar = project.tasks[JavaPlugin.JAR_TASK_NAME]
-        def startScripts = project.tasks[TASK_START_SCRIPTS_NAME]
-
-        project.copySpec {
-            into("lib") {
-                from(jar.outputs.files)
-                from(project.configurations.runtime)
-            }
-            into("bin") {
-                from(startScripts.outputs.files)
-                fileMode = 0755
-            }
-        }
-    }
+/*
+ * Copyright 2011 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.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.file.CopySpec
+import org.gradle.api.tasks.JavaExec
+import org.gradle.api.tasks.Sync
+import org.gradle.api.tasks.application.CreateStartScripts
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.GradleException
+
+/**
+ * <p>A {@link Plugin} which runs a project as a Java Application.</p>
+ *
+ * @author Rene Groeschke
+ */
+class ApplicationPlugin implements Plugin<Project> {
+    static final String APPLICATION_PLUGIN_NAME = "application"
+    static final String APPLICATION_GROUP = APPLICATION_PLUGIN_NAME
+
+    static final String TASK_RUN_NAME = "run"
+    static final String TASK_START_SCRIPTS_NAME = "startScripts"
+    static final String TASK_INSTALL_NAME = "installApp"
+    static final String TASK_DIST_ZIP_NAME = "distZip"
+
+    private Project project
+    private ApplicationPluginConvention pluginConvention
+
+    void apply(final Project project) {
+        this.project = project
+        project.plugins.apply(JavaPlugin)
+
+        addPluginConvention()
+        addRunTask()
+        addCreateScriptsTask()
+
+        configureDistSpec(pluginConvention.applicationDistribution)
+
+        addInstallTask()
+        addDistZipTask()
+    }
+
+    private void addPluginConvention() {
+        pluginConvention = new ApplicationPluginConvention(project)
+        pluginConvention.applicationName = project.name
+        project.convention.plugins.application = pluginConvention
+    }
+
+    private void addRunTask() {
+        def run = project.tasks.add(TASK_RUN_NAME, JavaExec)
+        run.description = "Runs this project as a JVM application"
+        run.group = APPLICATION_GROUP
+        run.classpath = project.sourceSets.main.runtimeClasspath
+        run.conventionMapping.main = { pluginConvention.mainClassName }
+    }
+
+    // @Todo: refactor this task configuration to extend a copy task and use replace tokens
+    private void addCreateScriptsTask() {
+        def startScripts = project.tasks.add(TASK_START_SCRIPTS_NAME, CreateStartScripts)
+        startScripts.description = "Creates OS specific scripts to run the project as a JVM application."
+        startScripts.classpath = project.tasks[JavaPlugin.JAR_TASK_NAME].outputs.files + project.configurations.runtime
+        startScripts.conventionMapping.mainClassName = { pluginConvention.mainClassName }
+        startScripts.conventionMapping.applicationName = { pluginConvention.applicationName }
+        startScripts.conventionMapping.outputDir = { new File(project.buildDir, 'scripts') }
+    }
+
+    private void addInstallTask() {
+        def installTask = project.tasks.add(TASK_INSTALL_NAME, Sync)
+        installTask.description = "Installs the project as a JVM application along with libs and OS specific scripts."
+        installTask.group = APPLICATION_GROUP
+        installTask.with pluginConvention.applicationDistribution
+        installTask.into { project.file("${project.buildDir}/install/${pluginConvention.applicationName}") }
+        installTask.doFirst {
+            if (destinationDir.directory) {
+                if (!new File(destinationDir, 'lib').directory || !new File(destinationDir, 'bin').directory) {
+                    throw new GradleException("The specified installation directory '${destinationDir}' is neither empty nor does it contain an installation for '${pluginConvention.applicationName}'.\n" +
+                            "If you really want to install to this directory, delete it and run the install task again.\n" +
+                            "Alternatively, choose a different installation directory."
+                    )
+                }
+            }
+        }
+        installTask.doLast {
+            project.ant.chmod(file: "${destinationDir.absolutePath}/bin/${pluginConvention.applicationName}", perm: 'ugo+x')
+        }
+    }
+
+    private void addDistZipTask() {
+        def distZipTask = project.tasks.add(TASK_DIST_ZIP_NAME, Zip)
+        distZipTask.description = "Bundles the project as a JVM application with libs and OS specific scripts."
+        distZipTask.group = APPLICATION_GROUP
+        distZipTask.conventionMapping.baseName = { pluginConvention.applicationName }
+        def baseDir = { distZipTask.archiveName - ".zip" }
+        distZipTask.into(baseDir) {
+            with(pluginConvention.applicationDistribution)
+        }
+    }
+
+    private CopySpec configureDistSpec(CopySpec distSpec) {
+        def jar = project.tasks[JavaPlugin.JAR_TASK_NAME]
+        def startScripts = project.tasks[TASK_START_SCRIPTS_NAME]
+
+        distSpec.with {
+            from(project.file("src/dist"))
+
+            into("lib") {
+                from(jar)
+                from(project.configurations.runtime)
+            }
+            into("bin") {
+                from(startScripts)
+                fileMode = 0755
+            }
+        }
+
+        distSpec
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy
index 3ecc9d4..85f3f27 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ApplicationPluginConvention.groovy
@@ -15,7 +15,10 @@
  */
 package org.gradle.api.plugins
 
- /**
+import org.gradle.api.Project
+import org.gradle.api.file.CopySpec
+
+/**
  * <p>A {@link Convention} used for the ApplicationPlugin.</p>
  *
  * @author Rene Groeschke
@@ -30,4 +33,30 @@ class ApplicationPluginConvention {
      * The fully qualified name of the application's main class.
      */
     String mainClassName
+
+    /**
+     * <p>The specification of the contents of the distribution.</p>
+     * <p>
+     * Use this {@link org.gradle.api.file.CopySpec} to include extra files/resource in the application distribution.
+     * <pre autoTested=''>
+     * apply plugin: 'application'
+     *
+     * applicationDistribution.from("some/dir") {
+     *   include "*.txt"
+     * }
+     * </pre>
+     * <p>
+     * Note that the application plugin pre configures this spec to; include the contents of "{@code src/dist}",
+     * copy the application start scripts into the "{@code bin}" directory, and copy the built jar and its dependencies
+     * into the "{@code lib}" directory.
+     */
+    CopySpec applicationDistribution
+
+    final Project project
+
+    ApplicationPluginConvention(Project project) {
+        this.project = project
+        applicationDistribution = project.copySpec {}
+    }
+
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
index aa00187..841ac24 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
@@ -17,18 +17,19 @@
 
 package org.gradle.api.plugins
 
+import org.apache.commons.lang.StringUtils
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.Rule
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
 import org.gradle.api.tasks.Delete
 import org.gradle.api.tasks.Upload
 import org.gradle.api.tasks.bundling.AbstractArchiveTask
 import org.gradle.api.tasks.bundling.Jar
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.Dependency
-import org.apache.commons.lang.StringUtils
 
 /**
  * <p>A  {@link org.gradle.api.Plugin}  which defines a basic project lifecycle and some common convention properties.</p>
@@ -40,11 +41,12 @@ class BasePlugin implements Plugin<Project> {
     public static final String UPLOAD_GROUP = 'upload'
 
     public void apply(Project project) {
-        project.convention.plugins.base = new BasePluginConvention(project)
+        def convention = new BasePluginConvention(project)
+        project.convention.plugins.base = convention
 
         configureBuildConfigurationRule(project)
         configureUploadRules(project)
-        configureArchiveDefaults(project, project.convention.plugins.base)
+        configureArchiveDefaults(project, convention)
         configureConfigurations(project)
 
         addClean(project)
@@ -56,7 +58,7 @@ class BasePlugin implements Plugin<Project> {
         Task assembleTask = project.tasks.add(ASSEMBLE_TASK_NAME);
         assembleTask.description = "Assembles all Jar, War, Zip, and Tar archives.";
         assembleTask.group = BUILD_GROUP
-        assembleTask.dependsOn project.tasks.withType(AbstractArchiveTask.class)
+        assembleTask.dependsOn project.configurations[Dependency.ARCHIVES_CONFIGURATION].allArtifacts.buildDependencies
     }
 
     private void configureArchiveDefaults(Project project, BasePluginConvention pluginConvention) {
@@ -115,7 +117,7 @@ class BasePlugin implements Plugin<Project> {
                     if (taskName.startsWith(prefix)) {
                         Configuration configuration = project.configurations.findByName(StringUtils.uncapitalize(taskName.substring(prefix.length())))
                         if (configuration != null) {
-                            project.tasks.add(taskName).dependsOn(configuration.getBuildArtifacts()).setDescription(String.format("Builds the artifacts belonging to %s.", configuration))
+                            project.tasks.add(taskName).dependsOn(configuration.getAllArtifacts()).setDescription(String.format("Builds the artifacts belonging to %s.", configuration))
                         }
                     }
                 },
@@ -136,7 +138,7 @@ class BasePlugin implements Plugin<Project> {
                     description
                 },
                 apply: {String taskName ->
-                    Set<Configuration> configurations = project.configurations.all
+                    Set<Configuration> configurations = project.configurations
                     for (Configuration configuration: configurations) {
                         if (taskName == configuration.uploadTaskName) {
                             createUploadTask(configuration.uploadTaskName, configuration, project)
@@ -167,9 +169,17 @@ class BasePlugin implements Plugin<Project> {
         project.setProperty("status", "integration");
 
         Configuration archivesConfiguration = configurations.add(Dependency.ARCHIVES_CONFIGURATION).
-                setDescription("Configuration for the default artifacts.");
+                setDescription("Configuration for archive artifacts.");
+
+        configurations.add(Dependency.DEFAULT_CONFIGURATION).
+                setDescription("Configuration for default artifacts.");
+
+        def defaultArtifacts = project.extensions.create("defaultArtifacts", DefaultArtifactPublicationSet, archivesConfiguration.artifacts)
 
-        configurations.add(Dependency.DEFAULT_CONFIGURATION).extendsFrom(archivesConfiguration).
-                setDescription("Configuration for the default artifacts and their dependencies.");
+        configurations.all {
+            artifacts.all { artifact ->
+                defaultArtifacts.addCandidate(artifact)
+            }
+        }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
index 8d46d2e..77887ae 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
@@ -20,18 +20,18 @@ import org.gradle.api.Action;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.file.FileTreeElement;
-import org.gradle.api.internal.DynamicObjectAware;
-import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.DefaultGroovySourceSet;
 import org.gradle.api.internal.tasks.DefaultSourceSet;
+import org.gradle.api.reporting.ReportingExtension;
 import org.gradle.api.specs.Spec;
-import org.gradle.api.tasks.ConventionValue;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.compile.GroovyCompile;
 import org.gradle.api.tasks.javadoc.Groovydoc;
 
 import java.io.File;
+import java.util.concurrent.Callable;
 
 /**
  * <p>A {@link org.gradle.api.Plugin} which extends the {@link org.gradle.api.plugins.JavaBasePlugin} to provide support for compiling and documenting Groovy
@@ -39,10 +39,10 @@ import java.io.File;
  *
  * @author Hans Dockter
  */
-public class GroovyBasePlugin implements Plugin<Project> {
+public class GroovyBasePlugin implements Plugin<ProjectInternal> {
     public static final String GROOVY_CONFIGURATION_NAME = "groovy";
 
-    public void apply(Project project) {
+    public void apply(ProjectInternal project) {
         JavaBasePlugin javaBasePlugin = project.getPlugins().apply(JavaBasePlugin.class);
 
         project.getConfigurations().add(GROOVY_CONFIGURATION_NAME).setVisible(false).setTransitive(false).
@@ -57,8 +57,8 @@ public class GroovyBasePlugin implements Plugin<Project> {
     private void configureCompileDefaults(final Project project) {
         project.getTasks().withType(GroovyCompile.class, new Action<GroovyCompile>() {
             public void execute(GroovyCompile compile) {
-                compile.getConventionMapping().map("groovyClasspath", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                compile.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME).copy().setTransitive(true);
                     }
                 });
@@ -66,12 +66,11 @@ public class GroovyBasePlugin implements Plugin<Project> {
         });
     }
 
-    private void configureSourceSetDefaults(final Project project, final JavaBasePlugin javaBasePlugin) {
-        final ProjectInternal projectInternal = (ProjectInternal) project;
+    private void configureSourceSetDefaults(final ProjectInternal project, final JavaBasePlugin javaBasePlugin) {
         project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().all(new Action<SourceSet>() {
             public void execute(SourceSet sourceSet) {
-                final DefaultGroovySourceSet groovySourceSet = new DefaultGroovySourceSet(((DefaultSourceSet) sourceSet).getDisplayName(), projectInternal.getFileResolver());
-                ((DynamicObjectAware) sourceSet).getConvention().getPlugins().put("groovy", groovySourceSet);
+                final DefaultGroovySourceSet groovySourceSet = new DefaultGroovySourceSet(((DefaultSourceSet) sourceSet).getDisplayName(), project.getFileResolver());
+                new DslObject(sourceSet).getConvention().getPlugins().put("groovy", groovySourceSet);
 
                 groovySourceSet.getGroovy().srcDir(String.format("src/%s/groovy", sourceSet.getName()));
                 sourceSet.getResources().getFilter().exclude(new Spec<FileTreeElement>() {
@@ -87,11 +86,7 @@ public class GroovyBasePlugin implements Plugin<Project> {
                 javaBasePlugin.configureForSourceSet(sourceSet, compile);
                 compile.dependsOn(sourceSet.getCompileJavaTaskName());
                 compile.setDescription(String.format("Compiles the %s Groovy source.", sourceSet.getName()));
-                compile.conventionMapping("defaultSource", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return groovySourceSet.getGroovy();
-                    }
-                });
+                compile.setSource(groovySourceSet.getGroovy());
 
                 project.getTasks().getByName(sourceSet.getClassesTaskName()).dependsOn(compileTaskName);
             }
@@ -101,24 +96,24 @@ public class GroovyBasePlugin implements Plugin<Project> {
     private void configureGroovydoc(final Project project) {
         project.getTasks().withType(Groovydoc.class, new Action<Groovydoc>() {
             public void execute(Groovydoc groovydoc) {
-                groovydoc.getConventionMapping().map("groovyClasspath", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                groovydoc.getConventionMapping().map("groovyClasspath", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME).copy().setTransitive(true);
                     }
                 });
-                groovydoc.getConventionMapping().map("destinationDir", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return new File(java(convention).getDocsDir(), "groovydoc");
+                groovydoc.getConventionMapping().map("destinationDir", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return new File(java(project.getConvention()).getDocsDir(), "groovydoc");
                     }
                 });
-                groovydoc.getConventionMapping().map("docTitle", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return convention.getPlugin(ReportingBasePluginConvention.class).getApiDocTitle();
+                groovydoc.getConventionMapping().map("docTitle", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return project.getExtensions().getByType(ReportingExtension.class).getApiDocTitle();
                     }
                 });
-                groovydoc.getConventionMapping().map("windowTitle", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return convention.getPlugin(ReportingBasePluginConvention.class).getApiDocTitle();
+                groovydoc.getConventionMapping().map("windowTitle", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return project.getExtensions().getByType(ReportingExtension.class).getApiDocTitle();
                     }
                 });
             }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
index 8a4b6a6..a2fbe8f 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
@@ -18,7 +18,7 @@ package org.gradle.api.plugins;
 
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
-import org.gradle.api.internal.DynamicObjectAware;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.tasks.GroovySourceSet;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.javadoc.Groovydoc;
@@ -52,9 +52,9 @@ public class GroovyPlugin implements Plugin<Project> {
 
         JavaPluginConvention convention = project.getConvention().getPlugin(JavaPluginConvention.class);
         SourceSet sourceSet = convention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
-        groovyDoc.setClasspath(sourceSet.getClasses().plus(sourceSet.getCompileClasspath()));
+        groovyDoc.setClasspath(sourceSet.getOutput().plus(sourceSet.getCompileClasspath()));
 
-        GroovySourceSet groovySourceSet = ((DynamicObjectAware) sourceSet).getConvention().getPlugin(GroovySourceSet.class);
+        GroovySourceSet groovySourceSet = new DslObject(sourceSet).getConvention().getPlugin(GroovySourceSet.class);
         groovyDoc.setSource(groovySourceSet.getGroovy());
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
index f95d0d4..fd3c12b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
@@ -17,10 +17,12 @@
 package org.gradle.api.plugins;
 
 import org.gradle.api.*;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.IConventionAware;
 import org.gradle.api.internal.plugins.ProcessResources;
-import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.reporting.ReportingExtension;
 import org.gradle.api.tasks.Copy;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.compile.AbstractCompile;
@@ -33,6 +35,7 @@ import org.gradle.api.tasks.testing.TestResult;
 import org.gradle.util.WrapUtil;
 
 import java.io.File;
+import java.util.concurrent.Callable;
 
 /**
  * <p>A {@link org.gradle.api.Plugin} which compiles and tests Java source, and assembles it into a JAR file.</p>
@@ -57,8 +60,8 @@ public class JavaBasePlugin implements Plugin<Project> {
         configureCompileDefaults(project, javaConvention);
         configureSourceSetDefaults(javaConvention);
 
-        configureJavaDoc(project);
-        configureTest(project);
+        configureJavaDoc(project, javaConvention);
+        configureTest(project, javaConvention);
         configureCheck(project);
         configureBuild(project);
         configureBuildNeeded(project);
@@ -69,28 +72,52 @@ public class JavaBasePlugin implements Plugin<Project> {
         pluginConvention.getSourceSets().all(new Action<SourceSet>() {
             public void execute(final SourceSet sourceSet) {
                 final Project project = pluginConvention.getProject();
-                ConventionMapping conventionMapping = ((IConventionAware) sourceSet).getConventionMapping();
 
-                conventionMapping.map("classesDir", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                ConventionMapping outputConventionMapping = ((IConventionAware) sourceSet.getOutput()).getConventionMapping();
+
+                ConfigurationContainer configurations = project.getConfigurations();
+
+                Configuration compileConfiguration = configurations.findByName(sourceSet.getCompileConfigurationName());
+                if (compileConfiguration == null) {
+                    compileConfiguration = configurations.add(sourceSet.getCompileConfigurationName());
+                }
+                compileConfiguration.setVisible(false);
+                compileConfiguration.setDescription(String.format("Classpath for compiling the %s sources.", sourceSet.getName()));
+
+                Configuration runtimeConfiguration = configurations.findByName(sourceSet.getRuntimeConfigurationName());
+                if (runtimeConfiguration == null) {
+                    runtimeConfiguration = configurations.add(sourceSet.getRuntimeConfigurationName());
+                }
+                runtimeConfiguration.setVisible(false);
+                runtimeConfiguration.extendsFrom(compileConfiguration);
+                runtimeConfiguration.setDescription(String.format("Classpath for running the compiled %s classes.", sourceSet.getName()));
+
+                sourceSet.setCompileClasspath(compileConfiguration);
+                sourceSet.setRuntimeClasspath(sourceSet.getOutput().plus(runtimeConfiguration));
+
+                outputConventionMapping.map("classesDir", new Callable<Object>() {
+                    public Object call() throws Exception {
                         String classesDirName = String.format("classes/%s", sourceSet.getName());
                         return new File(project.getBuildDir(), classesDirName);
                     }
                 });
+                outputConventionMapping.map("resourcesDir", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        String classesDirName = String.format("resources/%s", sourceSet.getName());
+                        return new File(project.getBuildDir(), classesDirName);
+                    }
+                });
+
                 sourceSet.getJava().srcDir(String.format("src/%s/java", sourceSet.getName()));
                 sourceSet.getResources().srcDir(String.format("src/%s/resources", sourceSet.getName()));
 
                 Copy processResources = project.getTasks().add(sourceSet.getProcessResourcesTaskName(), ProcessResources.class);
                 processResources.setDescription(String.format("Processes the %s.", sourceSet.getResources()));
-                conventionMapping = processResources.getConventionMapping();
-                conventionMapping.map("defaultSource", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return sourceSet.getResources();
-                    }
-                });
-                conventionMapping.map("destinationDir", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return sourceSet.getClassesDir();
+                ConventionMapping conventionMapping = processResources.getConventionMapping();
+                processResources.from(sourceSet.getResources());
+                conventionMapping.map("destinationDir", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return sourceSet.getOutput().getResourcesDir();
                     }
                 });
 
@@ -102,6 +129,7 @@ public class JavaBasePlugin implements Plugin<Project> {
                 classes.dependsOn(sourceSet.getProcessResourcesTaskName(), compileTaskName);
                 classes.setDescription(String.format("Assembles the %s classes.", sourceSet.getName()));
                 classes.setGroup(BasePlugin.BUILD_GROUP);
+                classes.dependsOn(sourceSet.getOutput().getDirs());
 
                 sourceSet.compiledBy(sourceSet.getClassesTaskName());
             }
@@ -112,19 +140,15 @@ public class JavaBasePlugin implements Plugin<Project> {
         ConventionMapping conventionMapping;
         compile.setDescription(String.format("Compiles the %s.", sourceSet.getJava()));
         conventionMapping = compile.getConventionMapping();
-        conventionMapping.map("classpath", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+        compile.setSource(sourceSet.getJava());
+        conventionMapping.map("classpath", new Callable<Object>() {
+            public Object call() throws Exception {
                 return sourceSet.getCompileClasspath();
             }
         });
-        conventionMapping.map("defaultSource", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return sourceSet.getJava();
-            }
-        });
-        conventionMapping.map("destinationDir", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return sourceSet.getClassesDir();
+        conventionMapping.map("destinationDir", new Callable<Object>() {
+            public Object call() throws Exception {
+                return sourceSet.getOutput().getClassesDir();
             }
         });
     }
@@ -133,13 +157,13 @@ public class JavaBasePlugin implements Plugin<Project> {
         project.getTasks().withType(AbstractCompile.class, new Action<AbstractCompile>() {
             public void execute(final AbstractCompile compile) {
                 ConventionMapping conventionMapping = compile.getConventionMapping();
-                conventionMapping.map("sourceCompatibility", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                conventionMapping.map("sourceCompatibility", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return javaConvention.getSourceCompatibility().toString();
                     }
                 });
-                conventionMapping.map("targetCompatibility", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                conventionMapping.map("targetCompatibility", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return javaConvention.getTargetCompatibility().toString();
                     }
                 });
@@ -148,8 +172,8 @@ public class JavaBasePlugin implements Plugin<Project> {
         project.getTasks().withType(Compile.class, new Action<Compile>() {
             public void execute(final Compile compile) {
                 ConventionMapping conventionMapping = compile.getConventionMapping();
-                conventionMapping.map("dependencyCacheDir", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                conventionMapping.map("dependencyCacheDir", new Callable<Object>() {
+                    public Object call() throws Exception {
                         return javaConvention.getDependencyCacheDir();
                     }
                 });
@@ -157,17 +181,17 @@ public class JavaBasePlugin implements Plugin<Project> {
         });
     }
 
-    private void configureJavaDoc(final Project project) {
+    private void configureJavaDoc(final Project project, final JavaPluginConvention convention) {
         project.getTasks().withType(Javadoc.class, new Action<Javadoc>() {
             public void execute(Javadoc javadoc) {
-                javadoc.getConventionMapping().map("destinationDir", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return new File(convention.getPlugin(JavaPluginConvention.class).getDocsDir(), "javadoc");
+                javadoc.getConventionMapping().map("destinationDir", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return new File(convention.getDocsDir(), "javadoc");
                     }
                 });
-                javadoc.getConventionMapping().map("title", new ConventionValue() {
-                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                        return convention.getPlugin(ReportingBasePluginConvention.class).getApiDocTitle();
+                javadoc.getConventionMapping().map("title", new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return project.getExtensions().getByType(ReportingExtension.class).getApiDocTitle();
                     }
                 });
             }
@@ -202,10 +226,10 @@ public class JavaBasePlugin implements Plugin<Project> {
         buildTask.dependsOn(BUILD_TASK_NAME);
     }
 
-    private void configureTest(final Project project) {
+    private void configureTest(final Project project, final JavaPluginConvention convention) {
         project.getTasks().withType(Test.class, new Action<Test>() {
             public void execute(Test test) {
-                configureTestDefaults(test, project);
+                configureTestDefaults(test, project, convention);
             }
         });
         project.afterEvaluate(new Action<Project>() {
@@ -277,15 +301,15 @@ public class JavaBasePlugin implements Plugin<Project> {
         return value;
     }
 
-    private void configureTestDefaults(Test test, Project project) {
-        test.getConventionMapping().map("testResultsDir", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return convention.getPlugin(JavaPluginConvention.class).getTestResultsDir();
+    private void configureTestDefaults(Test test, Project project, final JavaPluginConvention convention) {
+        test.getConventionMapping().map("testResultsDir", new Callable<Object>() {
+            public Object call() throws Exception {
+                return convention.getTestResultsDir();
             }
         });
-        test.getConventionMapping().map("testReportDir", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return convention.getPlugin(JavaPluginConvention.class).getTestReportDir();
+        test.getConventionMapping().map("testReportDir", new Callable<Object>() {
+            public Object call() throws Exception {
+                return convention.getTestReportDir();
             }
         });
         test.workingDir(project.getProjectDir());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
index 1c8f75a..1221624 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
@@ -25,7 +25,9 @@ import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.internal.plugins.EmbeddableJavaProject;
+import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.bundling.Jar;
 import org.gradle.api.tasks.javadoc.Javadoc;
@@ -61,12 +63,11 @@ public class JavaPlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPlugins().apply(JavaBasePlugin.class);
 
-        JavaPluginConvention javaConvention = (JavaPluginConvention) project.getConvention().getPlugins().get("java");
+        JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class);
         project.getConvention().getPlugins().put("embeddedJavaProject", new EmbeddableJavaProjectImpl(javaConvention));
 
-        configureConfigurations(project);
-
         configureSourceSets(javaConvention);
+        configureConfigurations(project);
 
         configureJavaDoc(javaConvention);
         configureTest(project, javaConvention);
@@ -77,20 +78,11 @@ public class JavaPlugin implements Plugin<Project> {
     private void configureSourceSets(final JavaPluginConvention pluginConvention) {
         final Project project = pluginConvention.getProject();
 
-        pluginConvention.getSourceSets().all(new Action<SourceSet>() {
-            public void execute(SourceSet sourceSet) {
-                sourceSet.setCompileClasspath(project.getConfigurations().getByName(COMPILE_CONFIGURATION_NAME));
-                sourceSet.setRuntimeClasspath(sourceSet.getClasses().plus(project.getConfigurations().getByName(
-                        RUNTIME_CONFIGURATION_NAME)));
-            }
-        });
         SourceSet main = pluginConvention.getSourceSets().add(SourceSet.MAIN_SOURCE_SET_NAME);
 
         SourceSet test = pluginConvention.getSourceSets().add(SourceSet.TEST_SOURCE_SET_NAME);
-        test.setCompileClasspath(project.files(main.getClasses(), project.getConfigurations().getByName(
-                TEST_COMPILE_CONFIGURATION_NAME)));
-        test.setRuntimeClasspath(project.files(test.getClasses(), main.getClasses(),
-                project.getConfigurations().getByName(TEST_RUNTIME_CONFIGURATION_NAME)));
+        test.setCompileClasspath(project.files(main.getOutput(), project.getConfigurations().getByName(TEST_COMPILE_CONFIGURATION_NAME)));
+        test.setRuntimeClasspath(project.files(test.getOutput(), main.getOutput(), project.getConfigurations().getByName(TEST_RUNTIME_CONFIGURATION_NAME)));
     }
 
     private void configureJavaDoc(final JavaPluginConvention pluginConvention) {
@@ -100,7 +92,7 @@ public class JavaPlugin implements Plugin<Project> {
         Javadoc javadoc = project.getTasks().add(JAVADOC_TASK_NAME, Javadoc.class);
         javadoc.setDescription("Generates Javadoc API documentation for the main source code.");
         javadoc.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
-        javadoc.setClasspath(mainSourceSet.getClasses().plus(mainSourceSet.getCompileClasspath()));
+        javadoc.setClasspath(mainSourceSet.getOutput().plus(mainSourceSet.getCompileClasspath()));
         javadoc.setSource(mainSourceSet.getAllJava());
         addDependsOnTaskInOtherProjects(javadoc, true, JAVADOC_TASK_NAME, COMPILE_CONFIGURATION_NAME);
     }
@@ -111,22 +103,22 @@ public class JavaPlugin implements Plugin<Project> {
         jar.getManifest().from(pluginConvention.getManifest());
         jar.setDescription("Assembles a jar archive containing the main classes.");
         jar.setGroup(BasePlugin.BUILD_GROUP);
-        jar.from(pluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getClasses());
+        jar.from(pluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput());
         jar.getMetaInf().from(new Callable() {
             public Object call() throws Exception {
                 return pluginConvention.getMetaInf();
             }
         });
 
-        project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION).addArtifact(new ArchivePublishArtifact(
-                jar));
+        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(new ArchivePublishArtifact(jar));
+        project.getConfigurations().getByName(RUNTIME_CONFIGURATION_NAME).getArtifacts().add(new ArchivePublishArtifact(jar));
     }
 
     private void configureBuild(Project project) {
         addDependsOnTaskInOtherProjects(project.getTasks().getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME), true,
-                JavaBasePlugin.BUILD_TASK_NAME, TEST_RUNTIME_CONFIGURATION_NAME);
+                JavaBasePlugin.BUILD_NEEDED_TASK_NAME, TEST_RUNTIME_CONFIGURATION_NAME);
         addDependsOnTaskInOtherProjects(project.getTasks().getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME), false,
-                JavaBasePlugin.BUILD_TASK_NAME, TEST_RUNTIME_CONFIGURATION_NAME);
+                JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, TEST_RUNTIME_CONFIGURATION_NAME);
     }
 
     private void configureTest(final Project project, final JavaPluginConvention pluginConvention) {
@@ -134,7 +126,7 @@ public class JavaPlugin implements Plugin<Project> {
             public void execute(Test test) {
                 test.getConventionMapping().map("testClassesDir", new Callable<Object>() {
                     public Object call() throws Exception {
-                        return pluginConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME).getClassesDir();
+                        return pluginConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME).getOutput().getClassesDir();
                     }
                 });
                 test.getConventionMapping().map("classpath", new Callable<Object>() {
@@ -155,20 +147,15 @@ public class JavaPlugin implements Plugin<Project> {
         test.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
     }
 
-    void configureConfigurations(final Project project) {
+    void configureConfigurations(Project project) {
         ConfigurationContainer configurations = project.getConfigurations();
-        Configuration compileConfiguration = configurations.add(COMPILE_CONFIGURATION_NAME).setVisible(false).
-                setDescription("Classpath for compiling the sources.");
-        Configuration runtimeConfiguration = configurations.add(RUNTIME_CONFIGURATION_NAME).setVisible(false)
-                .extendsFrom(compileConfiguration).
-                        setDescription("Classpath for running the compiled sources.");
+        Configuration compileConfiguration = configurations.getByName(COMPILE_CONFIGURATION_NAME);
+        Configuration runtimeConfiguration = configurations.getByName(RUNTIME_CONFIGURATION_NAME);
 
-        Configuration compileTestsConfiguration = configurations.add(TEST_COMPILE_CONFIGURATION_NAME).setVisible(false)
-                .extendsFrom(compileConfiguration).setDescription("Classpath for compiling the test sources.");
+        Configuration compileTestsConfiguration = configurations.getByName(TEST_COMPILE_CONFIGURATION_NAME);
+        compileTestsConfiguration.extendsFrom(compileConfiguration);
 
-        configurations.add(TEST_RUNTIME_CONFIGURATION_NAME).setVisible(false).extendsFrom(runtimeConfiguration,
-                compileTestsConfiguration).
-                setDescription("Classpath for running the test sources.");
+        configurations.getByName(TEST_RUNTIME_CONFIGURATION_NAME).extendsFrom(runtimeConfiguration, compileTestsConfiguration);
 
         configurations.getByName(Dependency.DEFAULT_CONFIGURATION).extendsFrom(runtimeConfiguration);
     }
@@ -207,7 +194,10 @@ public class JavaPlugin implements Plugin<Project> {
         }
 
         public FileCollection getRuntimeClasspath() {
-            return convention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
+            FileCollection runtimeClasspath = convention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
+            ProjectInternal project = convention.getProject();
+            FileCollection gradleApi = project.getConfigurations().detachedConfiguration(project.getDependencies().gradleApi(), project.getDependencies().localGroovy());
+            return runtimeClasspath.minus(gradleApi);
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
index 8531fe4..bfc6454 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
@@ -17,11 +17,14 @@ package org.gradle.api.plugins
 
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
-import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.Instantiator
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.tasks.DefaultSourceSetContainer
 import org.gradle.api.java.archives.Manifest
 import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.SourceSetContainer
 import org.gradle.util.ConfigureUtil
 
@@ -59,6 +62,12 @@ class JavaPluginConvention {
     private JavaVersion srcCompat
     private JavaVersion targetCompat
 
+    /**
+     * Deprecated. Please use jar.metaInf instead. The property didn't add much value over the jar's setting
+     * and Gradle offers convenient ways of configuring all tasks of given type should someone needed.
+     * <p>
+     * The lines of metaInf file that will be configured by default to every jar task.
+     */
     @Deprecated
     List metaInf
 
@@ -67,8 +76,8 @@ class JavaPluginConvention {
 
     JavaPluginConvention(Project project) {
         this.project = project
-        def classGenerator = project.services.get(ClassGenerator)
-        sourceSets = classGenerator.newInstance(DefaultSourceSetContainer.class, project.fileResolver, project.tasks, classGenerator)
+        def instantiator = project.services.get(Instantiator)
+        sourceSets = instantiator.newInstance(DefaultSourceSetContainer.class, project.fileResolver, project.tasks, instantiator)
         dependencyCacheDirName = 'dependency-cache'
         docsDirName = 'docs'
         testResultsDirName = 'test-results'
@@ -82,6 +91,21 @@ class JavaPluginConvention {
      *
      * <p>The given closure is executed to configure the {@link SourceSetContainer}. The {@link SourceSetContainer}
      * is passed to the closure as its delegate.
+     * <p>
+     * See the example below how {@link SourceSet} 'main' is accessed and how the {@link SourceDirectorySet} 'java'
+     * is configured to exclude some package from compilation.
+     *
+     * <pre autoTested=''>
+     * apply plugin: 'java'
+     *
+     * sourceSets {
+     *   main {
+     *     java {
+     *       exclude 'some/unwanted/package/**'
+     *     }
+     *   }
+     * }
+     * </pre>
      *
      * @param closure The closure to execute.
      */
@@ -115,14 +139,14 @@ class JavaPluginConvention {
     }
 
     private File getReportsDir() {
-        project.convention.plugins.reportingBase.reportsDir
+        project.extensions.getByType(ReportingExtension).baseDir
     }
 
     /**
      * Returns the source compatibility used for compiling Java sources.
      */
     JavaVersion getSourceCompatibility() {
-        srcCompat ?: JavaVersion.VERSION_1_5
+        srcCompat ?: JavaVersion.current()
     }
 
     /**
@@ -164,6 +188,6 @@ class JavaPluginConvention {
      * @param closure The closure to use to configure the manifest.
      */
     public Manifest manifest(Closure closure) {
-        return ConfigureUtil.configure(closure, new DefaultManifest(((ProjectInternal) getProject()).fileResolver));
+        return ConfigureUtil.configure(closure, new DefaultManifest((getProject()).fileResolver));
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
index 97af509..245f237 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
@@ -18,13 +18,12 @@ package org.gradle.api.plugins;
 import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
-import org.gradle.api.internal.IConventionAware;
-import org.gradle.api.tasks.ConventionValue;
 import org.gradle.api.tasks.diagnostics.DependencyReportTask;
 import org.gradle.api.tasks.diagnostics.PropertyReportTask;
 import org.gradle.api.tasks.diagnostics.TaskReportTask;
 
 import java.io.File;
+import java.util.concurrent.Callable;
 
 /**
  * <p>A {@link Plugin} which adds some project visualization report tasks to a project.</p>
@@ -37,49 +36,49 @@ public class ProjectReportsPlugin implements Plugin<Project> {
 
     public void apply(Project project) {
         project.getPlugins().apply(ReportingBasePlugin.class);
-        project.getConvention().getPlugins().put("projectReports", new ProjectReportsPluginConvention(project));
+        final ProjectReportsPluginConvention convention = new ProjectReportsPluginConvention(project);
+        project.getConvention().getPlugins().put("projectReports", convention);
 
         TaskReportTask taskReportTask = project.getTasks().add(TASK_REPORT, TaskReportTask.class);
         taskReportTask.setDescription("Generates a report about your tasks.");
-        taskReportTask.conventionMapping("outputFile", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return new File(convention.getPlugin(ProjectReportsPluginConvention.class).getProjectReportDir(), "tasks.txt");
+        taskReportTask.conventionMapping("outputFile", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(convention.getProjectReportDir(), "tasks.txt");
             }
         });
-        taskReportTask.conventionMapping("projects", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return convention.getPlugin(ProjectReportsPluginConvention.class).getProjects();
+        taskReportTask.conventionMapping("projects", new Callable<Object>() {
+            public Object call() throws Exception {
+                return convention.getProjects();
             }
         });
 
         PropertyReportTask propertyReportTask = project.getTasks().add(PROPERTY_REPORT, PropertyReportTask.class);
         propertyReportTask.setDescription("Generates a report about your properties.");
-        propertyReportTask.conventionMapping("outputFile", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return new File(convention.getPlugin(ProjectReportsPluginConvention.class).getProjectReportDir(), "properties.txt");
+        propertyReportTask.conventionMapping("outputFile", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(convention.getProjectReportDir(), "properties.txt");
             }
         });
-        propertyReportTask.conventionMapping("projects", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return convention.getPlugin(ProjectReportsPluginConvention.class).getProjects();
+        propertyReportTask.conventionMapping("projects", new Callable<Object>() {
+            public Object call() throws Exception {
+                return convention.getProjects();
             }
         });
 
         DependencyReportTask dependencyReportTask = project.getTasks().add(DEPENDENCY_REPORT,
                 DependencyReportTask.class);
         dependencyReportTask.setDescription("Generates a report about your library dependencies.");
-        dependencyReportTask.conventionMapping("outputFile", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return new File(convention.getPlugin(ProjectReportsPluginConvention.class).getProjectReportDir(), "dependencies.txt");
+        dependencyReportTask.conventionMapping("outputFile", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(convention.getProjectReportDir(), "dependencies.txt");
             }
         });
-        dependencyReportTask.conventionMapping("projects", new ConventionValue() {
-            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
-                return convention.getPlugin(ProjectReportsPluginConvention.class).getProjects();
+        dependencyReportTask.conventionMapping("projects", new Callable<Object>() {
+            public Object call() throws Exception {
+                return convention.getProjects();
             }
         });
 
-
         Task projectReportTask = project.getTasks().add(PROJECT_REPORT);
         projectReportTask.dependsOn(TASK_REPORT, PROPERTY_REPORT, DEPENDENCY_REPORT);
         projectReportTask.setDescription("Generates a report about your project.");
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy
index b46a6b5..1009a7d 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy
@@ -17,6 +17,7 @@ package org.gradle.api.plugins
 
 import org.gradle.api.Project
 import org.gradle.util.WrapUtil
+import org.gradle.api.reporting.ReportingExtension
 
 public class ProjectReportsPluginConvention {
     /**
@@ -33,7 +34,7 @@ public class ProjectReportsPluginConvention {
      * Returns the directory to generate the project reports into.
      */
     File getProjectReportDir() {
-        new File(project.convention.getPlugin(ReportingBasePluginConvention).reportsDir, projectReportDirName)
+        project.extensions.getByType(ReportingExtension).file(projectReportDirName)
     }
 
     Set<Project> getProjects() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
index 0a66df0..b706d28 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
@@ -16,8 +16,9 @@
 package org.gradle.api.plugins;
 
 import org.gradle.api.Plugin;
-import org.gradle.api.Project;
+import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.reporting.ReportingExtension;
 
 /**
  * <p>A {@link Plugin} which provides the basic skeleton for reporting.</p>
@@ -30,9 +31,13 @@ import org.gradle.api.internal.project.ProjectInternal;
  *
  * </ul>
  */
-public class ReportingBasePlugin implements Plugin<Project> {
-    public void apply(Project project) {
+public class ReportingBasePlugin implements Plugin<ProjectInternal> {
+    public void apply(ProjectInternal project) {
         Convention convention = project.getConvention();
-        convention.getPlugins().put("reportingBase", new ReportingBasePluginConvention((ProjectInternal) project));
+        ReportingExtension extension = project.getServices().get(Instantiator.class).newInstance(ReportingExtension.class, project);
+        project.getExtensions().add(ReportingExtension.NAME, extension);
+
+        // This convention is deprecated
+        convention.getPlugins().put("reportingBase", new ReportingBasePluginConvention(project, extension));
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
index edb9cd1..9ba14f8 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
@@ -15,61 +15,86 @@
  */
 package org.gradle.api.plugins;
 
-import org.gradle.api.Project;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.reporting.ReportingExtension;
+import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
+import java.util.concurrent.Callable;
 
 /**
  * <p>A {@code BasePluginConvention} defines the convention properties and methods used by the {@link
  * ReportingBasePlugin}.</p>
+ * <p>
+ * This convention has been deprecated. Use the reporting extension instead:
+ * </p>
+ * <pre>
+ * reporting {
+ *     baseDir "the-reports"
+ * }
+ * </pre>
+ *
+ * @deprecated This convention has been deprecated and replaced by {@link ReportingExtension}
  */
+ at Deprecated
 public class ReportingBasePluginConvention {
-    private String reportsDirName = "reports";
-    private ProjectInternal project;
+    private ReportingExtension extension;
+    private final ProjectInternal project;
 
-    public ReportingBasePluginConvention(ProjectInternal project) {
+    public ReportingBasePluginConvention(ProjectInternal project, ReportingExtension extension) {
         this.project = project;
+        this.extension = extension;
     }
 
     /**
      * Returns the name of the reports directory, relative to the project's build directory.
      *
+     * @deprecated use {@link org.gradle.api.reporting.ReportingExtension#getBaseDir()}
      * @return The reports directory name. Never returns null.
      */
+    @Deprecated
     public String getReportsDirName() {
-        return reportsDirName;
+        DeprecationLogger.nagUserOfReplacedProperty("reportsDirName", "reporting.baseDir");
+        return extension.getBaseDir().getName();
     }
 
     /**
      * Sets the name of the reports directory, relative to the project's build directory.
      *
+     * @deprecated use {@link ReportingExtension#setBaseDir(Object)}
      * @param reportsDirName The reports directory name. Should not be null.
      */
-    public void setReportsDirName(String reportsDirName) {
-        this.reportsDirName = reportsDirName;
+    @Deprecated
+    public void setReportsDirName(final String reportsDirName) {
+        DeprecationLogger.nagUserOfReplacedProperty("reportsDirName", "reporting.baseDir");
+        extension.setBaseDir(new Callable<File>() {
+            public File call() throws Exception {
+                return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve(reportsDirName);
+            }
+        });
     }
 
     /**
      * Returns the directory containing all reports for this project.
      *
+     * @deprecated use {@link org.gradle.api.reporting.ReportingExtension#getBaseDir()}
      * @return The reports directory. Never returns null.
      */
+    @Deprecated
     public File getReportsDir() {
-        return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve(reportsDirName);
+        DeprecationLogger.nagUserOfReplacedProperty("reportsDir", "reporting.baseDir");
+        return extension.getBaseDir();
     }
 
     /**
      * Returns the title for API documentation for the project.
      *
+     * @deprecated use {@link org.gradle.api.reporting.ReportingExtension#getApiDocTitle()}
      * @return The title. Never returns null.
      */
+    @Deprecated
     public String getApiDocTitle() {
-        Object version = project.getVersion();
-        if (Project.DEFAULT_VERSION.equals(version)) {
-            return String.format("%s API", project.getName());
-        } else {
-            return String.format("%s %s API", project.getName(), version);
-        }
+        DeprecationLogger.nagUserOfReplacedProperty("apiDocTitle", "reporting.apiDocTitle");
+        return extension.getApiDocTitle();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
index 74a5a84..7244571 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
@@ -21,12 +21,10 @@ import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 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.file.FileCollection;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.tasks.SourceSet;
-import org.gradle.api.tasks.bundling.Jar;
 import org.gradle.api.tasks.bundling.War;
 
 import java.util.concurrent.Callable;
@@ -76,30 +74,10 @@ public class WarPlugin implements Plugin<Project> {
         War war = project.getTasks().add(WAR_TASK_NAME, War.class);
         war.setDescription("Generates a war archive with all the compiled classes, the web-app content and the libraries.");
         war.setGroup(BasePlugin.BUILD_GROUP);
-        Configuration archivesConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
-        disableJarTaskAndRemoveFromArchivesConfiguration(project, archivesConfiguration);
-        archivesConfiguration.addArtifact(new ArchivePublishArtifact(war));
+        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(new ArchivePublishArtifact(war));
         configureConfigurations(project.getConfigurations());
     }
 
-    private void disableJarTaskAndRemoveFromArchivesConfiguration(Project project, Configuration archivesConfiguration) {
-        Jar jarTask = (Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
-        jarTask.setEnabled(false);
-        removeJarTaskFromArchivesConfiguration(archivesConfiguration, jarTask);
-    }
-
-    private void removeJarTaskFromArchivesConfiguration(Configuration archivesConfiguration, Jar jar) {
-        // todo: There should be a richer connection between an ArchiveTask and a PublishArtifact
-        for (PublishArtifact publishArtifact : archivesConfiguration.getAllArtifacts()) {
-            if (publishArtifact instanceof ArchivePublishArtifact) {
-                ArchivePublishArtifact archivePublishArtifact = (ArchivePublishArtifact) publishArtifact;
-                if (archivePublishArtifact.getArchiveTask() == jar) {
-                    archivesConfiguration.removeArtifact(publishArtifact);
-                }
-            }
-        }
-    }
-
     public void configureConfigurations(ConfigurationContainer configurationContainer) {
         Configuration provideCompileConfiguration = configurationContainer.add(PROVIDED_COMPILE_CONFIGURATION_NAME).setVisible(false).
                 setDescription("Additional compile classpath for libraries that should not be part of the WAR archive.");
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Report.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Report.java
new file mode 100644
index 0000000..a2ef25d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Report.java
@@ -0,0 +1,110 @@
+/*
+ * 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.reporting;
+
+import org.gradle.api.Namer;
+import org.gradle.util.Configurable;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * A file based report to be created.
+ */
+public interface Report extends Serializable, Configurable<Report> {
+    
+    Namer<Report> NAMER = new Namer<Report>() {
+        public String determineName(Report report) {
+            return report.getName();
+        }
+    };
+
+    /**
+     * The symbolic name of this report.
+     *
+     * The name of the report usually indicates the format (e.g. xml, html etc.) but can be anything.
+     *
+     * When part of a {@link ReportContainer}, reports are accessed via their name. That is, given a report container variable
+     * named {@code reports} containing a report who's {@code getName()} returns {@code "html"}, the report could be accessed
+     * via:
+     *
+     * <pre>
+     * reports.html
+     * </pre>
+     *
+     * @return The name of this report.
+     */
+    String getName();
+
+    /**
+     * Whether or not this report should be generated by whatever generates it.
+     *
+     * If {@code true}, the generator of this report will generate it at the appropriate time.
+     * If {@code false}, the generator of this report will not generate this report.
+     *
+     * @return Whether or not this report should be generated by whatever generates it.
+     */
+    boolean isEnabled();
+
+    /**
+     * Whether or not this report should be generated by whatever generates it.
+     *
+     * @see #isEnabled()
+     * @param enabled Whether or not this report should be generated by whatever generates it.
+     */
+    void setEnabled(boolean enabled);
+
+    /**
+     * The location on the filesystem of the report when it is generated.
+     *
+     * Depending on the {@link #getOutputType() output type} of the report, this may point to
+     * a file or a directory.
+     *
+     * Subtypes may implement setters for the destination.
+     *
+     * @return The location on the filesystem of the report when it is generated
+     */
+    File getDestination();
+
+    /**
+     * The type of output the report produces
+     */
+    enum OutputType {
+
+        /**
+         * The report outputs a single file.
+         *
+         * That is, the {@link #getDestination()} file points a single file.
+         */
+        FILE,
+
+        /**
+         * The report outputs files into a directory.
+         *
+         * That is, the {@link #getDestination()} file points to a directory.
+         */
+        DIRECTORY
+    }
+
+    /**
+     * The type of output that the report generates.
+     *
+     * @return The type of output that the report generates.
+     */
+    OutputType getOutputType();
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
new file mode 100644
index 0000000..95aa5f9
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportContainer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.reporting;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.util.Configurable;
+
+/**
+ * A container of potential reports.
+ * 
+ * Things that produce reports (typically tasks) expose a report container that contains {@link Report} objects for each
+ * possible report that they can produce. Each report object can be configured individually, including whether or not it should
+ * be produced by way of its {@link Report#setEnabled(boolean) enabled} property.
+ *
+ * ReportContainer implementations are <b>immutable</b> in that standard collection methods such as {@code add()}, {@code remove()}
+ * and {@code clear()} will throw an {@link ImmutableViolationException}. However, implementations may provide new methods that allow
+ * the addition of new report object and/or the removal of existing report objects.
+ * 
+ * @param <T> The base report type for reports of this container.
+ */
+public interface ReportContainer<T extends Report> extends NamedDomainObjectSet<T>, Configurable<ReportContainer<T>> {
+
+    /**
+     * The exception thrown when any of this container's mutation methods are called.
+     *
+     * This applies to the standard {@link java.util.Collection} methods such as {@code add()}, {@code remove()}
+     * and {@code clear()}.
+     */
+    public static class ImmutableViolationException extends GradleException {
+        public ImmutableViolationException() {
+            super("ReportContainer objects are immutable");
+        }
+    }
+
+    /**
+     * Returns an immutable collection of all the enabled reports.
+     *
+     * The returned collection is live. That is, as reports are enabled/disabled the returned collection always
+     * reflects the current set of enabled reports.
+     *
+     * @return The enabled reports.
+     */
+    NamedDomainObjectSet<T> getEnabled();
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Reporting.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Reporting.java
new file mode 100644
index 0000000..b3e907d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/Reporting.java
@@ -0,0 +1,52 @@
+/*
+ * 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.reporting;
+
+import groovy.lang.Closure;
+
+/**
+ * An object that provides reporting options
+ *
+ * @param <T> The base type of the report container
+ */
+public interface Reporting<T extends ReportContainer> {
+
+    /**
+     * Returns the report container.
+     *
+     * @return The report container
+     */
+    T getReports();
+
+    /**
+     * Allow configuration of the report container by closure.
+     *
+     * For example…
+     *
+     * <pre>
+     * reports {
+     *   html {
+     *     enabled false
+     *   }
+     *   xml.destination "build/reports/myReport.xml"
+     * }
+     * </pre>
+     * @param closure The configuration
+     * @return The report container
+     */
+    T reports(Closure closure);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
new file mode 100644
index 0000000..a3d9e42
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/ReportingExtension.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2011 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.reporting;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * Adds base configuration for reporting tasks.
+ *
+ * Example usage:
+ *
+ * <pre>
+ * reporting {
+ *     baseDir "$buildDir/our-reports"
+ * }
+ * </pre>
+ */
+public class ReportingExtension {
+
+    /**
+     * The name of this extension ("{@value}")
+     */
+    public static final String NAME = "reporting";
+    
+    /**
+     * The default name of the base directory for all reports, relative to {@link org.gradle.api.Project#getBuildDir()} ({@value}).
+     */
+    public static final String DEFAULT_REPORTS_DIR_NAME = "reports";
+
+    private final ProjectInternal project;
+    private Object baseDir;
+
+
+    public ReportingExtension(Project project) {
+        this.project = (ProjectInternal)project;
+        this.baseDir = new Callable<File>() {
+            public File call() throws Exception {
+                return ReportingExtension.this.project.getFileResolver().
+                        withBaseDir(ReportingExtension.this.project.getBuildDir()).
+                        resolve(DEFAULT_REPORTS_DIR_NAME);
+            }
+        };
+    }
+
+    /**
+     * The base directory for all reports
+     *
+     * This value can be changed, so any files derived from this should be calculated on demand.
+     * 
+     * @return The base directory for all reports
+     */
+    public File getBaseDir() {
+        return project.file(baseDir);
+    }
+
+    /**
+     * Sets the base directory to use for all reports
+     * 
+     * The value will be converted to a {@code File} on demand via {@link Project#file(Object)}.
+     *
+     * @param baseDir The base directory to use for all reports
+     */
+    public void setBaseDir(Object baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    /**
+     * Creates a file object for the given path, relative to {@link #getBaseDir()}.
+     *
+     * The reporting base dir can be changed, so users of this method should use it on demand where appropriate.
+     *
+     * @param path the relative path
+     * @return a file object at the given path relative to {@link #getBaseDir()}
+     */
+    public File file(String path) {  // TODO should this take Object?
+        return this.project.getFileResolver().withBaseDir(getBaseDir()).resolve(path);
+    }
+
+    // TODO this doesn't belong here, that java plugin should add an extension to this guy with this
+    public String getApiDocTitle() {
+        Object version = project.getVersion();
+        if (Project.DEFAULT_VERSION.equals(version)) {
+            return String.format("%s API", project.getName());
+        } else {
+            return String.format("%s %s API", project.getName(), version);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
new file mode 100644
index 0000000..037c78e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/SingleFileReport.java
@@ -0,0 +1,40 @@
+/*
+ * 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.reporting;
+
+/**
+ * A report that is a single file.
+ */
+public interface SingleFileReport extends Report {
+
+    /**
+     * Sets the destination for the report.
+     * 
+     * The file parameter is evaluated as per {@link org.gradle.api.Project#file(Object)}.
+     * 
+     * @param file The destination for the report.
+     */
+    void setDestination(Object file);
+
+    /**
+     * Always returns {@link Report.OutputType#FILE}
+     *
+     * @return {@link Report.OutputType#FILE}
+     */
+    OutputType getOutputType();
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
new file mode 100644
index 0000000..4d47744
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
@@ -0,0 +1,80 @@
+/*
+ * 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.reporting.internal;
+
+import groovy.lang.Closure;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.internal.ConfigureDelegate;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.reporting.ReportContainer;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.SortedMap;
+
+public class DefaultReportContainer<T extends Report> extends DefaultNamedDomainObjectSet<T> implements ReportContainer<T> {
+
+    private NamedDomainObjectSet<T> enabled;
+
+    public DefaultReportContainer(Class<? extends T> type, Instantiator instantiator) {
+        super(type, instantiator, Report.NAMER);
+
+        enabled = matching(new Spec<T>() {
+            public boolean isSatisfiedBy(T element) {
+                return element.isEnabled();
+            }
+        });
+
+        beforeChange(new Runnable() {
+            public void run() {
+                throw new ImmutableViolationException();
+            }
+        });
+    }
+
+    public NamedDomainObjectSet<T> getEnabled() {
+        return enabled;
+    }
+
+    public ReportContainer<T> configure(Closure cl) {
+        ConfigureUtil.configure(cl, new ConfigureDelegate(cl.getOwner(), this), false);
+        return this;
+    }
+    
+    public T getFirstEnabled() {
+        SortedMap<String, T> map = enabled.getAsMap();
+        if (map.isEmpty()) {
+            return null;
+        } else {
+            return map.get(map.firstKey());
+        }
+    }
+
+    protected <N extends T> N add(Class<N> clazz, Object... constructionArgs) {
+        N report = getInstantiator().newInstance(clazz, constructionArgs);
+
+        if (report.getName().equals("enabled")) {
+            throw new InvalidUserDataException("Reports that are part of a ReportContainer cannot be named 'enabled'");
+        }
+
+        getStore().add(report);
+        return report;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
new file mode 100644
index 0000000..30dd192
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
@@ -0,0 +1,77 @@
+/*
+ * 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.reporting.internal;
+
+import groovy.lang.Closure;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.reporting.Report;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+
+public class SimpleReport implements Report {
+                          
+    private String name;
+    private FileResolver fileResolver;
+
+    private Object destination;
+    private boolean enabled;
+    private OutputType outputType;
+
+    public SimpleReport(String name, OutputType outputType, FileResolver fileResolver) {
+        this.name = name;
+        this.fileResolver = fileResolver;
+        this.outputType = outputType;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return String.format("Report %s", getName());
+    }
+    
+    public File getDestination() {
+        return destination == null ? null : resolveToFile(destination);
+    }
+
+    protected void setDestination(Object destination) {
+        this.destination = destination;
+    }
+
+    public OutputType getOutputType() {
+        return outputType;
+    }
+
+    private File resolveToFile(Object file) {
+        return fileResolver.resolve(file);
+    }
+    
+    public Report configure(Closure configure) {
+        return ConfigureUtil.configure(configure, this, false);
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
new file mode 100644
index 0000000..e00e60f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
@@ -0,0 +1,28 @@
+/*
+ * 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.reporting.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.project.ProjectInternal;
+
+public class TaskGeneratedReport extends SimpleReport {
+
+    public TaskGeneratedReport(String name, OutputType outputType, Task task) {
+        super(name, outputType, ((ProjectInternal)(task.getProject())).getFileResolver());
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java
new file mode 100644
index 0000000..751d83b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedSingleFileReport.java
@@ -0,0 +1,32 @@
+/*
+ * 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.reporting.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.SingleFileReport;
+
+public class TaskGeneratedSingleFileReport extends TaskGeneratedReport implements SingleFileReport {
+    public TaskGeneratedSingleFileReport(String name, Task task) {
+        super(name, OutputType.FILE, task);
+    }
+
+    @Override
+    public void setDestination(Object destination) {
+        // super implementation is protected, we are promoting
+        super.setDestination(destination);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
new file mode 100644
index 0000000..6e2f6d5
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
@@ -0,0 +1,82 @@
+/*
+ * 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.reporting.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.Instantiator;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputDirectories;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+abstract public class TaskReportContainer<T extends Report> extends DefaultReportContainer<T> {
+
+    private final TaskInternal task;
+
+    final Transformer<File, Report> toFile = new Transformer<File, Report>() {
+        public File transform(Report original) {
+            return original.getDestination();
+        }
+    };
+
+    private static final Spec<Report> IS_DIRECTORY_OUTPUT_TYPE = new Spec<Report>() {
+        public boolean isSatisfiedBy(Report report) {
+            return report.getOutputType() == Report.OutputType.DIRECTORY;
+        }
+    };
+
+    private static final Spec<Report> IS_FILE_OUTPUT_TYPE = Specs.not(IS_DIRECTORY_OUTPUT_TYPE);
+
+    public TaskReportContainer(Class<? extends T> type, final Task task) {
+        super(type, ((ProjectInternal) task.getProject()).getServices().get(Instantiator.class));
+        this.task = (TaskInternal) task;
+    }
+    
+    protected Task getTask() {
+        return task;
+    }
+
+    @OutputDirectories
+    public Set<File> getEnabledDirectoryReportDestinations() {
+        return CollectionUtils.collect(CollectionUtils.filter(getEnabled(), IS_DIRECTORY_OUTPUT_TYPE), toFile);
+    }
+
+    @OutputFiles
+    public Set<File> getEnabledFileReportDestinations() {
+        return CollectionUtils.collect(CollectionUtils.filter(getEnabled(), IS_FILE_OUTPUT_TYPE), toFile);
+    }
+    
+    @Input
+    public SortedSet<String> getEnabledReportNames() {
+        return CollectionUtils.collect(getEnabled(), new TreeSet<String>(), new Transformer<String, Report>() {
+            public String transform(Report report) {
+                return report.getName();
+            }
+        });        
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/package-info.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/package-info.java
new file mode 100644
index 0000000..0a6a3f7
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for reporting.
+ */
+package org.gradle.api.reporting;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
index 6077a19..9b578ce 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
@@ -19,10 +19,23 @@ import groovy.lang.Closure;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.SourceDirectorySet;
 
-import java.io.File;
-
 /**
- * <p>A {@code SourceSet} represents a logical group of Java source and resources.</p>
+ * A {@code SourceSet} represents a logical group of Java source and resources.
+ * <p>
+ * See the example below how {@link SourceSet} 'main' is accessed and how the {@link SourceDirectorySet} 'java'
+ * is configured to exclude some package from compilation.
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ *
+ * sourceSets {
+ *   main {
+ *     java {
+ *       exclude 'some/unwanted/package/**'
+ *     }
+ *   }
+ * }
+ * </pre>
  */
 public interface SourceSet {
     /**
@@ -70,26 +83,13 @@ public interface SourceSet {
      */
     void setRuntimeClasspath(FileCollection classpath);
 
-    /**
-     * Returns the directory to assemble the compiled classes into.
-     *
-     * @return The classes dir. Never returns null.
-     */
-    File getClassesDir();
-
-    /**
-     * Sets the directory to assemble the compiled classes into.
-     *
-     * @param classesDir the classes dir. Should not be null.
-     */
-    void setClassesDir(File classesDir);
-
-    /**
-     * Returns the compiled classes directory for this source set.
+   /**
+     * {@link SourceSetOutput} is a {@link FileCollection} of all output directories (compiled classes, processed resources, etc.)
+     *  and it provides means configure the default output dirs and register additional output dirs. See examples in {@link SourceSetOutput}
      *
-     * @return The classes dir, as a {@link FileCollection}.
+     * @return The output dirs, as a {@link SourceSetOutput}.
      */
-    FileCollection getClasses();
+    SourceSetOutput getOutput();
 
     /**
      * Registers a set of tasks which are responsible for compiling this source set into the classes directory. The
@@ -101,7 +101,7 @@ public interface SourceSet {
     SourceSet compiledBy(Object... taskPaths);
 
     /**
-     * Returns the non-Java resources which are to be copied into the class output directory.
+     * Returns the non-Java resources which are to be copied into the resources output directory.
      *
      * @return the resources. Never returns null.
      */
@@ -186,4 +186,16 @@ public interface SourceSet {
      * @return The task name, generally of the form ${verb}${name}${noun}
      */
     String getTaskName(String verb, String target);
+
+    /**
+     * Returns the name of the compile configuration for this source set.
+     * @return The configuration name
+     */
+    String getCompileConfigurationName();
+
+    /**
+     * Returns the name of the runtime configuration for this source set.
+     * @return The runtime configuration name
+     */
+    String getRuntimeConfigurationName();
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
index 678af03..d2ceb64 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
@@ -18,12 +18,12 @@ package org.gradle.api.tasks;
 import groovy.lang.Closure;
 import org.gradle.api.NamedDomainObjectContainer;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.NamedDomainObjectCollection;
+import org.gradle.api.NamedDomainObjectSet;
 
 /**
  * A {@code SourceSetContainer} manages a set of {@link SourceSet} objects.
  */
-public interface SourceSetContainer extends NamedDomainObjectContainer<SourceSet>, NamedDomainObjectCollection<SourceSet> {
+public interface SourceSetContainer extends NamedDomainObjectContainer<SourceSet>, NamedDomainObjectSet<SourceSet> {
     /**
      * Adds a source set with the given name.
      *
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetOutput.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetOutput.java
new file mode 100644
index 0000000..7b37684
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSetOutput.java
@@ -0,0 +1,147 @@
+/*
+ * 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 org.gradle.api.file.FileCollection;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * A collection of all output directories (compiled classes, processed resources, etc.) - notice that {@link SourceSetOutput} extends {@link FileCollection}.
+ * <p>
+ * Provides output information of the source set. Allows configuring the default output dirs and specify additional output dirs.
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ *
+ * sourceSets {
+ *   main {
+ *     //if you truly want to override the defaults:
+ *     output.resourcesDir = 'out/res'
+ *     output.classesDir   = 'out/bin'
+ *   }
+ * }
+ * </pre>
+ *
+ * Working with generated resources.
+ * <p>
+ * In general, we recommend generating resources into folders different than the regular resourcesDir and classesDir.
+ * Usually, it makes the build easier to understand and maintain. Also it gives some additional benefits
+ * because other Gradle plugins can take advantage of the output dirs 'registered' in the SourceSet.output.
+ * For example: Java plugin will use those dirs in calculating class paths and for jarring the content;
+ * IDEA and Eclipse plugins will put those folders on relevant classpath.
+ * <p>
+ * An example how to work with generated resources:
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ *
+ * def generatedResources = "$buildDir/generated-resources/main"
+ *
+ * sourceSets {
+ *   main {
+ *     //let's register an output folder on the main SourceSet:
+ *     output.dir(generatedResources, builtBy: 'generateMyResources')
+ *     //it is now a part of the 'main' classpath and will be a part of the jar
+ *   }
+ * }
+ *
+ * //a task that generates the resources:
+ * task generateMyResources {
+ *   doLast {
+ *     def generated = new File(generatedResources, "myGeneratedResource.properties")
+ *     generated.text = "message=Stay happy!"
+ *   }
+ * }
+ *
+ * //Java plugin task 'classes' and 'testClasses' will automatically depend on relevant tasks registered with 'builtBy'
+ *
+ * //Eclipse/IDEA plugins will automatically depend on 'generateMyResources'
+ * //because the output dir was registered with 'builtBy' information
+ * apply plugin: 'idea'; apply plugin: 'eclipse'
+ * </pre>
+ *
+ * Find more information in {@link #dir(java.util.Map, Object)} and {@link #getDirs()}
+ */
+public interface SourceSetOutput extends FileCollection {
+
+    /**
+     * Returns the directory to assemble the compiled classes into.
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @return The classes dir. Never returns null.
+     */
+    File getClassesDir();
+
+    /**
+     * Sets the directory to assemble the compiled classes into.
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @param classesDir the classes dir. Should not be null.
+     */
+    void setClassesDir(Object classesDir);
+
+    /**
+     * Returns the output directory for resources
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @return The dir resources are copied to.
+     */
+    File getResourcesDir();
+
+    /**
+     * Sets the output directory for resources
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @param resourcesDir the classes dir. Should not be null.
+     */
+    void setResourcesDir(Object resourcesDir);
+
+    /**
+     * Registers an extra output dir and the builtBy information. Useful for generated resources.
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @param options - use 'builtBy' key to configure the 'builtBy' task of the dir
+     * @param dir - will be resolved as {@link org.gradle.api.Project#file(Object)}
+     */
+    void dir(Map<String, Object> options, Object dir);
+
+    /**
+     * Registers an extra output dir. Useful for generated resources.
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @param dir - will be resolved as {@link org.gradle.api.Project#file(Object)}
+     */
+    void dir(Object dir);
+
+    /**
+     * Returns all dirs registered with with #dir method.
+     * Each file is resolved as {@link org.gradle.api.Project#file(Object)}
+     * <p>
+     * See example at {@link SourceSetOutput}
+     *
+     * @return a new instance of registered dirs with resolved files
+     */
+    FileCollection getDirs();
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
index 77f96f7..b3b9348 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/application/CreateStartScripts.groovy
@@ -15,15 +15,14 @@
  */
 package org.gradle.api.tasks.application
 
-import groovy.text.SimpleTemplateEngine
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.ConventionTask
+import org.gradle.api.internal.plugins.StartScriptGenerator
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.InputFiles
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
 import org.gradle.util.GUtil
-import org.gradle.util.TextUtil
 
 /**
  * <p>A {@link org.gradle.api.Task} for creating OS dependent start scripts.</p>
@@ -98,44 +97,14 @@ class CreateStartScripts extends ConventionTask {
     void generate() {
         getOutputDir().mkdirs()
 
-        //ref all files in classpath
-        def unixLibPath = "\$APP_HOME/lib/"
-        def unixClassPath = new StringBuffer()
-
-        def windowsLibPath = "%APP_HOME%\\lib\\"
-        def windowsClassPath = new StringBuffer()
-
-        classpath.each {
-            unixClassPath << "$unixLibPath${it.name}:"
-            windowsClassPath << "$windowsLibPath${it.name};"
-        }
-
-        generateUnixScript(
-                applicationName: getApplicationName(),
-                optsEnvironmentVar: getOptsEnvironmentVar(),
-                mainClassName: getMainClassName(),
-                classpath: unixClassPath)
-        generateWindowsScript(
-                applicationName: getApplicationName(),
-                optsEnvironmentVar: getOptsEnvironmentVar(),
-                exitEnvironmentVar: getExitEnvironmentVar(),
-                mainClassName: getMainClassName(),
-                classpath: windowsClassPath)
-    }
-
-    private void generateUnixScript(Map binding) {
-        generateScript('unixStartScript.txt', binding, TextUtil.unixLineSeparator, getUnixScript())
-    }
-
-    private void generateWindowsScript(Map binding) {
-        generateScript('windowsStartScript.txt', binding, TextUtil.windowsLineSeparator, getWindowsScript())
-    }
-
-    private void generateScript(String templateName, Map binding, String lineSeparator, File outputFile) {
-        def engine = new SimpleTemplateEngine()
-        def templateText = CreateStartScripts.getResourceAsStream(templateName).text
-        def output = engine.createTemplate(templateText).make(binding)
-        def nativeOutput = TextUtil.convertLineSeparators(output as String, lineSeparator)
-        outputFile.write(nativeOutput)
+        def generator = new StartScriptGenerator()
+        generator.applicationName = getApplicationName()
+        generator.mainClassName = getMainClassName()
+        generator.optsEnvironmentVar = getOptsEnvironmentVar()
+        generator.exitEnvironmentVar = getExitEnvironmentVar()
+        generator.classpath = getClasspath().collect { "lib/${it.name}" }
+        generator.scriptRelPath = "bin/${getUnixScript().name}"
+        generator.generateUnixScript(getUnixScript())
+        generator.generateWindowsScript(getWindowsScript())
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
index f9e6629..6fc0a30 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
@@ -42,7 +42,7 @@ public class Jar extends Zip {
         // Add these as separate specs, so they are not affected by the changes to the main spec
         metaInf = copyAction.rootSpec.addFirst().into('META-INF')
         metaInf.addChild().from {
-            MapFileTree manifestSource = new MapFileTree(temporaryDir)
+            MapFileTree manifestSource = new MapFileTree(temporaryDirFactory)
             manifestSource.add('MANIFEST.MF') {OutputStream outstr ->
                 Manifest manifest = getManifest() ?: new DefaultManifest(null)
                 manifest.writeTo(new OutputStreamWriter(outstr))
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.groovy
deleted file mode 100644
index 9accbfc..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.groovy
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.api.tasks.compile
-
-import java.lang.reflect.Field
-import java.lang.reflect.Modifier
-
-/**
- * @author Hans Dockter
- */
-class AbstractOptions {
-
-    void define(Map args) {
-        args.each {String key, Object value ->
-            this."$key" = value
-        }
-    }
-
-    Map optionMap() {
-        getClass().declaredFields.findAll {Field field -> isOptionField(field)}.inject([:]) {Map optionMap, Field field ->
-            addValueToMapIfNotNull(optionMap, field)
-        }
-    }
-
-    // todo: change modifier to private when GROOVY-2565 is fixed.
-    protected Map addValueToMapIfNotNull(Map map, Field field) {
-        def value = this."${field.name}"
-        if (value != null) {map.put(antProperty(field.name), antValue(field.name, value))}
-        map
-    }
-
-    // todo: change modifier to private when GROOVY-2565 is fixed.
-    protected boolean isOptionField(Field field) {
-        ((field.getModifiers() & Modifier.STATIC) == 0) &&
-                (field.getName() != "metaClass") &&
-                (!excludedFieldsFromOptionMap().contains(field.name))
-    }
-
-    private def antProperty(String fieldName) {
-        String antProperty = null
-        if (fieldName2AntMap().keySet().contains(fieldName)) {
-            antProperty = fieldName2AntMap()[fieldName]
-        }
-        antProperty ?: fieldName
-    }
-
-    private def antValue(String fieldName, def value) {
-        if (fieldValue2AntMap().keySet().contains(fieldName)) {
-            return fieldValue2AntMap()[fieldName]()
-        }
-        value
-    }
-
-    List excludedFieldsFromOptionMap() {
-        []
-    }
-
-    Map fieldName2AntMap() {
-        [:]
-    }
-
-    Map fieldValue2AntMap() {
-        [:]
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java
new file mode 100644
index 0000000..1f6df6e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java
@@ -0,0 +1,98 @@
+/*
+ * 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.api.tasks.compile;
+
+import com.google.common.collect.Maps;
+import org.gradle.api.Nullable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.util.JavaReflectionUtil;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Base class for compilation-related options.
+ *
+ * @author Hans Dockter
+ */
+public abstract class AbstractOptions implements Serializable {
+    private static final long serialVersionUID = 0;
+
+    public void define(@Nullable Map<String, Object> args) {
+        if (args == null) { return; }
+        for (Map.Entry<String, Object> arg: args.entrySet()) {
+            JavaReflectionUtil.setProperty(this, arg.getKey(), arg.getValue());
+        }
+    }
+
+    public Map<String, Object> optionMap() {
+        Map<String, Object> map = Maps.newHashMap();
+        for (Field field: getClass().getDeclaredFields()) {
+            if (!isOptionField(field)) { continue; }
+            addValueToMapIfNotNull(map, field);
+        }
+        return map;
+    }
+
+    private void addValueToMapIfNotNull(Map<String, Object> map, Field field) {
+        Object value = JavaReflectionUtil.getProperty(this, field.getName());
+        if (value != null) {
+            map.put(antProperty(field.getName()), antValue(field.getName(), value));
+        }
+    }
+
+    private boolean isOptionField(Field field) {
+        return ((field.getModifiers() & Modifier.STATIC) == 0)
+                && (!field.getName().equals("metaClass"))
+                && (!excludedFieldsFromOptionMap().contains(field.getName()));
+    }
+
+    private String antProperty(String fieldName) {
+        if (fieldName2AntMap().containsKey(fieldName)) {
+            return fieldName2AntMap().get(fieldName);
+        }
+        return fieldName;
+    }
+
+    private Object antValue(String fieldName, Object value) {
+        if (fieldValue2AntMap().containsKey(fieldName)) {
+            try {
+                return fieldValue2AntMap().get(fieldName).call();
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+        return value;
+    }
+
+    protected List<String> excludedFieldsFromOptionMap() {
+        return Collections.emptyList();
+    }
+
+    protected Map<String, String> fieldName2AntMap() {
+        return Collections.emptyMap();
+    }
+
+    protected Map<String, ? extends Callable<Object>> fieldValue2AntMap() {
+        return Collections.emptyMap();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
index 4ef3a35..a56a101 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
@@ -17,10 +17,11 @@
 package org.gradle.api.tasks.compile;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.tasks.compile.AntJavaCompiler;
-import org.gradle.api.internal.tasks.compile.IncrementalJavaCompiler;
-import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.*;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.internal.Factory;
 import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.OutputDirectory;
 import org.gradle.api.tasks.TaskAction;
@@ -34,25 +35,29 @@ import java.io.File;
  * @author Hans Dockter
  */
 public class Compile extends AbstractCompile {
-
-    private JavaCompiler javaCompiler;
-
+    private Compiler<JavaCompileSpec> javaCompiler;
     private File dependencyCacheDir;
+    private final JavaCompileSpec spec = new DefaultJavaCompileSpec();
 
     public Compile() {
         Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
-        javaCompiler = new IncrementalJavaCompiler(new AntJavaCompiler(antBuilderFactory), antBuilderFactory, getOutputs());
+        JavaCompilerFactory inProcessCompilerFactory = new InProcessJavaCompilerFactory();
+        ProjectInternal projectInternal = (ProjectInternal) getProject();
+        TemporaryFileProvider tempFileProvider = projectInternal.getServices().get(TemporaryFileProvider.class);
+        JavaCompilerFactory defaultCompilerFactory = new DefaultJavaCompilerFactory(projectInternal, tempFileProvider, antBuilderFactory, inProcessCompilerFactory);
+        Compiler<JavaCompileSpec> delegatingCompiler = new DelegatingJavaCompiler(defaultCompilerFactory);
+        javaCompiler = new IncrementalJavaCompiler(delegatingCompiler, antBuilderFactory, getOutputs());
     }
 
     @TaskAction
     protected void compile() {
-        javaCompiler.setSource(getSource());
-        javaCompiler.setDestinationDir(getDestinationDir());
-        javaCompiler.setClasspath(getClasspath());
-        javaCompiler.setDependencyCacheDir(getDependencyCacheDir());
-        javaCompiler.setSourceCompatibility(getSourceCompatibility());
-        javaCompiler.setTargetCompatibility(getTargetCompatibility());
-        WorkResult result = javaCompiler.execute();
+        spec.setSource(getSource());
+        spec.setDestinationDir(getDestinationDir());
+        spec.setClasspath(getClasspath());
+        spec.setDependencyCacheDir(getDependencyCacheDir());
+        spec.setSourceCompatibility(getSourceCompatibility());
+        spec.setTargetCompatibility(getTargetCompatibility());
+        WorkResult result = javaCompiler.execute(spec);
         setDidWork(result.getDidWork());
     }
 
@@ -72,14 +77,14 @@ public class Compile extends AbstractCompile {
      */
     @Nested
     public CompileOptions getOptions() {
-        return javaCompiler.getCompileOptions();
+        return spec.getCompileOptions();
     }
 
-    public JavaCompiler getJavaCompiler() {
+    public Compiler<JavaCompileSpec> getJavaCompiler() {
         return javaCompiler;
     }
 
-    public void setJavaCompiler(JavaCompiler javaCompiler) {
+    public void setJavaCompiler(Compiler<JavaCompileSpec> javaCompiler) {
         this.javaCompiler = javaCompiler;
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.groovy
deleted file mode 100644
index 2499e1c..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.groovy
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.api.tasks.compile
-
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Nested
-import org.gradle.api.tasks.Optional
-
-/**
- * @author Hans Dockter
- */
-class CompileOptions extends AbstractOptions {
-    /**
-     * Specifies whether the compile task should fail when compilation fails. The default is {@code true}.
-     */
-    @Input
-    boolean failOnError = true
-    boolean verbose = false
-    boolean listFiles = false
-
-    /**
-     * Specifies whether to log details of usage of deprecated members or classes. The default is {@code false}.
-     */
-    boolean deprecation = false
-
-    /**
-     * Specifies whether to log warning messages. The default is {@code true}.
-     */
-    boolean warnings = true
-
-    /**
-     * The source encoding name. Uses the platform default encoding if not specified. The default is {@code null}.
-     */
-    @Input @Optional
-    String encoding = null
-    @Input
-    boolean optimize
-
-    /**
-     * Specifies whether debugging information should be included in the generated {@code .class} files. The default
-     * is {@code true}.
-     */
-    @Input
-    boolean debug = true
-
-    /**
-     * The options for debugging information generation.
-     */
-    @Nested
-    DebugOptions debugOptions = new DebugOptions()
-
-    /**
-     * Specifies whether to run the compiler in a child process. The default is {@code false}.
-     */
-    boolean fork = false
-
-    /**
-     * The options for running the compiler in a child process.
-     */
-    @Nested
-    ForkOptions forkOptions = new ForkOptions()
-
-    /**
-     * Specifies whether to use the Ant {@code <depend>} task.
-     */
-    boolean useDepend = false
-
-    /**
-     * The options for using the Ant {@code <depend>} task.
-     */
-    DependOptions dependOptions = new DependOptions()
-
-    /**
-     * The compiler to use.
-     */
-    @Input @Optional
-    String compiler = null
-    @Input
-    boolean includeJavaRuntime = false
-
-    /**
-     * The bootstrap classpath to use when compiling.
-     */
-    @Input @Optional
-    String bootClasspath = null
-
-    /**
-     * The extension dirs to use when compiling.
-     */
-    @Input @Optional
-    String extensionDirs = null
-
-    /**
-     * The arguments to pass to the compiler.
-     */
-    @Input
-    List compilerArgs = []
-
-    CompileOptions fork(Map forkArgs) {
-        fork = true
-        forkOptions.define(forkArgs)
-        this
-    }
-
-    CompileOptions debug(Map debugArgs) {
-        debug = true
-        debugOptions.define(debugArgs)
-        this
-    }
-
-    /**
-     * Set the dependency options from a map.  See  {@link DependOptions}  for
-     * a list of valid properties.  Calling this method will enable use
-     * of the depend task during a compile.
-     */
-    CompileOptions depend(Map dependArgs) {
-        useDepend = true
-        dependOptions.define(dependArgs)
-        this
-    }
-
-    List excludedFieldsFromOptionMap() {
-        ['debugOptions', 'forkOptions', 'compilerArgs', 'dependOptions', 'useDepend']
-    }
-
-    Map fieldName2AntMap() {
-        [
-                warnings: 'nowarn',
-                bootClasspath: 'bootclasspath',
-                extensionDirs: 'extdirs',
-                failOnError: 'failonerror',
-                listFiles: 'listfiles',
-        ]
-    }
-
-    Map fieldValue2AntMap() {
-        [
-                warnings: {!warnings}
-        ]
-    }
-
-    Map optionMap() {
-        super.optionMap() + forkOptions.optionMap() + debugOptions.optionMap()
-    }
-}
-
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java
new file mode 100644
index 0000000..cac73b8
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java
@@ -0,0 +1,372 @@
+/*
+ * 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.api.tasks.compile;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.Optional;
+import org.gradle.util.DeprecationLogger;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Main options for Java compilation.
+ *
+ * @author Hans Dockter
+ */
+public class CompileOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    /**
+     * Specifies whether the compile task should fail when compilation fails. The default is {@code true}.
+     */
+    @Input
+    private boolean failOnError = true;
+
+    public boolean isFailOnError() {
+        return failOnError;
+    }
+
+    // @Input not recognized if there is only an "is" method
+    public boolean getFailOnError() {
+        return failOnError;
+    }
+
+    public void setFailOnError(boolean failOnError) {
+        this.failOnError = failOnError;
+    }
+
+    /**
+     * Specifies whether the compile task should produce verbose output.
+     */
+    private boolean verbose;
+
+    public boolean isVerbose() {
+        return verbose;
+    }
+
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    /**
+     * Specifies whether the compile task should list the files to be compiled.
+     */
+    private boolean listFiles;
+
+    public boolean isListFiles() {
+        return listFiles;
+    }
+
+    public void setListFiles(boolean listFiles) {
+        this.listFiles = listFiles;
+    }
+
+    /**
+     * Specifies whether to log details of usage of deprecated members or classes. The default is {@code false}.
+     */
+    private boolean deprecation;
+
+    public boolean isDeprecation() {
+        return deprecation;
+    }
+
+    public void setDeprecation(boolean deprecation) {
+        this.deprecation = deprecation;
+    }
+
+    /**
+     * Specifies whether to log warning messages. The default is {@code true}.
+     */
+    private boolean warnings = true;
+
+    public boolean isWarnings() {
+        return warnings;
+    }
+
+    public void setWarnings(boolean warnings) {
+        this.warnings = warnings;
+    }
+
+    /**
+     * The source encoding name. Uses the platform default encoding if not specified. The default is {@code null}.
+     */
+    @Input @Optional
+    private String encoding;
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    /**
+     * Whether to produce optimized byte code. Note that this flag is ignored by Sun's javac starting with
+     * JDK 1.3 (since compile-time optimization is unnecessary)
+     */
+    @Input
+    private boolean optimize;
+
+    public boolean isOptimize() {
+        return optimize;
+    }
+
+    // @Input not recognized if there is only an "is" method
+    public boolean getOptimize() {
+        return optimize;
+    }
+
+    public void setOptimize(boolean optimize) {
+        this.optimize = optimize;
+    }
+
+    /**
+     * Specifies whether debugging information should be included in the generated {@code .class} files. The default
+     * is {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     */
+    @Input
+    private boolean debug = true;
+
+    public boolean isDebug() {
+        return debug;
+    }
+
+    // @Input not recognized if there is only an "is" method
+    public boolean getDebug() {
+        return debug;
+    }
+
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+    }
+
+    /**
+     * Options for generating debugging information.
+     */
+    @Nested
+    private DebugOptions debugOptions = new DebugOptions();
+
+    public DebugOptions getDebugOptions() {
+        return debugOptions;
+    }
+
+    public void setDebugOptions(DebugOptions debugOptions) {
+        this.debugOptions = debugOptions;
+    }
+
+    /**
+     * Specifies whether to run the compiler in its own process. The default is {@code false}.
+     */
+    private boolean fork;
+
+    public boolean isFork() {
+        return fork;
+    }
+
+    public void setFork(boolean fork) {
+        this.fork = fork;
+    }
+
+    /**
+     * The options for running the compiler in a child process.
+     */
+    @Nested
+    private ForkOptions forkOptions = new ForkOptions();
+
+    public ForkOptions getForkOptions() {
+        return forkOptions;
+    }
+
+    public void setForkOptions(ForkOptions forkOptions) {
+        this.forkOptions = forkOptions;
+    }
+
+    /**
+     * Specifies whether to use the Ant {@code <depend>} task.
+     */
+    private boolean useDepend;
+
+    public boolean isUseDepend() {
+        return useDepend;
+    }
+
+    public void setUseDepend(boolean useDepend) {
+        this.useDepend = useDepend;
+    }
+
+    /**
+     * The options for using the Ant {@code <depend>} task.
+     */
+    private DependOptions dependOptions = new DependOptions();
+
+    public DependOptions getDependOptions() {
+        return dependOptions;
+    }
+
+    public void setDependOptions(DependOptions dependOptions) {
+        this.dependOptions = dependOptions;
+    }
+
+    /**
+     * The compiler to use.
+     */
+    @Deprecated
+    @Input @Optional
+    private String compiler;
+
+    public String getCompiler() {
+        return compiler;
+    }
+
+    void setCompiler(String compiler) {
+        DeprecationLogger.nagUserOfDiscontinuedProperty("CompileOptions.compiler", "To use an alternative compiler, "
+                + "set 'CompileOptions.fork' to 'true', and 'CompileOptions.forkOptions.executable' to the path of the compiler executable.");
+        this.compiler = compiler;
+    }
+
+    @Input
+    private boolean includeJavaRuntime;
+
+    public boolean isIncludeJavaRuntime() {
+        return includeJavaRuntime;
+    }
+
+    // @Input not recognized if there is only an "is" method
+    public boolean getIncludeJavaRuntime() {
+        return includeJavaRuntime;
+    }
+
+    public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
+        this.includeJavaRuntime = includeJavaRuntime;
+    }
+
+    /**
+     * The bootstrap classpath to use when compiling.
+     */
+    @Input @Optional
+    private String bootClasspath;
+
+    public String getBootClasspath() {
+        return bootClasspath;
+    }
+
+    public void setBootClasspath(String bootClasspath) {
+        this.bootClasspath = bootClasspath;
+    }
+
+    /**
+     * The extension dirs to use when compiling.
+     */
+    @Input @Optional
+    private String extensionDirs;
+
+    public String getExtensionDirs() {
+        return extensionDirs;
+    }
+
+    public void setExtensionDirs(String extensionDirs) {
+        this.extensionDirs = extensionDirs;
+    }
+
+    /**
+     * The arguments to pass to the compiler.
+     */
+    @Input
+    private List<String> compilerArgs = Lists.newArrayList();
+
+    public List<String> getCompilerArgs() {
+        return compilerArgs;
+    }
+
+    public void setCompilerArgs(List<String> compilerArgs) {
+        this.compilerArgs = compilerArgs;
+    }
+
+    /**
+     * Whether to use the Ant javac task or Gradle's own Java compiler integration.
+     * Defaults to <tt>false</tt>.
+     */
+    private boolean useAnt;
+
+    public boolean isUseAnt() {
+        return useAnt;
+    }
+
+    public void setUseAnt(boolean useAnt) {
+        this.useAnt = useAnt;
+    }
+
+    /**
+     * Convenience method to set fork options with named parameter syntax.
+     */
+    CompileOptions fork(Map<String, Object> forkArgs) {
+        fork = true;
+        forkOptions.define(forkArgs);
+        return this;
+    }
+
+    /**
+     * Convenience method to set debug options with named parameter syntax.
+     */
+    CompileOptions debug(Map<String, Object> debugArgs) {
+        debug = true;
+        debugOptions.define(debugArgs);
+        return this;
+    }
+
+    /**
+     * Set the dependency options from a map.  See  {@link DependOptions}  for
+     * a list of valid properties.  Calling this method will enable use
+     * of the depend task during a compile.
+     */
+    CompileOptions depend(Map<String, Object> dependArgs) {
+        useDepend = true;
+        dependOptions.define(dependArgs);
+        return this;
+    }
+
+    protected List<String> excludedFieldsFromOptionMap() {
+        return Arrays.asList("debugOptions", "forkOptions", "compilerArgs", "dependOptions", "useDepend", "useAnt");
+    }
+
+    protected Map<String, String> fieldName2AntMap() {
+        return ImmutableMap.of("warnings", "nowarn", "bootClasspath", "bootclasspath", "extensionDirs", "extdirs", "failOnError", "failonerror", "listFiles", "listfiles");
+    }
+
+    protected Map<String, ? extends Callable<Object>> fieldValue2AntMap() {
+        return ImmutableMap.of("warnings", new Callable<Object>() {
+            public Object call() {
+                return !warnings;
+            }
+        });
+    }
+
+    public Map<String, Object> optionMap() {
+        Map<String, Object> map = super.optionMap();
+        map.putAll(debugOptions.optionMap());
+        map.putAll(forkOptions.optionMap());
+        return map;
+    }
+}
+
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.groovy
deleted file mode 100644
index 739e1fd..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.groovy
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.api.tasks.compile
-
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Optional
-
-/**
- * @author Hans Dockter
- */
-class DebugOptions extends AbstractOptions {
-    @Input @Optional
-    String debugLevel = null
-
-    Map fieldName2AntMap() {
-        [debugLevel: 'debuglevel']
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java
new file mode 100644
index 0000000..37fdece
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api.tasks.compile;
+
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+import java.util.Map;
+
+/**
+ * Debug options for Java compilation.
+ *
+ * @author Hans Dockter
+ */
+public class DebugOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    /**
+     * Tells which debugging information will be generated. The value is a comma-separated
+     * list of any of the following keywords (without spaces in between):
+     *
+     * <dl>
+     *     <dt>{@code source}
+     *     <dd>Source file debugging information
+     *     <dt>{@code lines}
+     *     <dd>Line number debugging information
+     *     <dt>{@code vars}
+     *     <dd>Local variable debugging information
+     * </dl>
+     *
+     * By default, only source and line debugging information will be generated.
+     *
+     * <p>This option only takes effect if {@link CompileOptions#debug} is set to {@code true}.
+     */
+    @Input @Optional
+    private String debugLevel;
+
+    public String getDebugLevel() {
+        return debugLevel;
+    }
+
+    public void setDebugLevel(String debugLevel) {
+        this.debugLevel = debugLevel;
+    }
+
+    protected Map<String, String> fieldName2AntMap() {
+        return ImmutableMap.of("debugLevel", "debuglevel");
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.groovy
deleted file mode 100644
index 75e9105..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.groovy
+++ /dev/null
@@ -1,51 +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.api.tasks.compile
-
-
-/**
- * <p>Options to send to Ant's depend task.  Depends will delete out of date class files before compiling.
- * This is not fool-proof, but will cut down on the frequency of having to do a clean build.  This may or may
- * not be faster than a clean build.</p>
- * See the <a href="http://ant.apache.org/manual/OptionalTasks/depend.html" target="_blank">Ant Reference</a>
- * for more information.</p>
- * <h2>Ant Options</h2>
- * <ul>
- *      <li>srcDir  - <b>IGNORED</b> - set automatically</li>
- *      <li>destDir - <b>IGNORED</b> - set automatically</li>
- *      <li>cache - <b>IGNORED</b> - set automatically</li>
- *      <li>closure - boolean controlling depth of dependency graph traversal</li>
- *      <li>dump - dump dependency information to log</li>
- *      <li>classpath - extra classes to check</li>
- *      <li>warnOnRmiStubs - disables warnings for rmi stubs with no source</li>
- * </ul>
- * <p>
- * There is an additional "useCache" boolean option to enable/disable caching of dependency information.  It is true
- * by default.</p>
- * @author Steve Appling
- */
-public class DependOptions extends AbstractOptions {
-    boolean useCache = true
-    boolean closure = false
-    boolean dump = false
-    String classpath = ""
-    boolean warnOnRmiStubs = true
-
-    List excludedFieldsFromOptionMap() {
-        ['srcDir', 'destDir', 'cache', 'useCache']
-    }
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java
new file mode 100644
index 0000000..57face3
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java
@@ -0,0 +1,102 @@
+/*
+ * 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.compile;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * <p>Options to send to Ant's depend task. Depends will delete out of date class files before compiling.
+ * This is not fool-proof, but will cut down on the frequency of having to do a clean build. This may or may
+ * not be faster than a clean build.
+ * See the <a href="http://ant.apache.org/manual/OptionalTasks/depend.html" target="_blank">Ant Reference</a>
+ * for more information.
+ *
+ * <h2>Ant Options</h2>
+ * <ul>
+ *      <li>srcDir  - <b>IGNORED</b> - set automatically</li>
+ *      <li>destDir - <b>IGNORED</b> - set automatically</li>
+ *      <li>cache - <b>IGNORED</b> - set automatically</li>
+ *      <li>closure - boolean controlling depth of dependency graph traversal</li>
+ *      <li>dump - dump dependency information to log</li>
+ *      <li>classpath - extra classes to check</li>
+ *      <li>warnOnRmiStubs - disables warnings for rmi stubs with no source</li>
+ * </ul>
+ *
+ * <p>
+ * There is an additional <tt>useCache</tt> boolean option to enable/disable caching of dependency information. It is true
+ * by default.
+ *
+ * @author Steve Appling
+ */
+public class DependOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    private boolean useCache = true;
+
+    public boolean isUseCache() {
+        return useCache;
+    }
+
+    public void setUseCache(boolean useCache) {
+        this.useCache = useCache;
+    }
+
+    private boolean closure;
+
+    public boolean isClosure() {
+        return closure;
+    }
+
+    public void setClosure(boolean closure) {
+        this.closure = closure;
+    }
+
+    private boolean dump;
+
+    public boolean isDump() {
+        return dump;
+    }
+
+    public void setDump(boolean dump) {
+        this.dump = dump;
+    }
+
+    private String classpath = "";
+
+    public String getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(String classpath) {
+        this.classpath = classpath;
+    }
+
+    private boolean warnOnRmiStubs = true;
+
+    public boolean isWarnOnRmiStubs() {
+        return warnOnRmiStubs;
+    }
+
+    public void setWarnOnRmiStubs(boolean warnOnRmiStubs) {
+        this.warnOnRmiStubs = warnOnRmiStubs;
+    }
+
+    public List<String> excludedFieldsFromOptionMap() {
+        return ImmutableList.of("srcDir", "destDir", "cache", "useCache");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.groovy
deleted file mode 100644
index 56f9dfe..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.api.tasks.compile
-
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Optional
-
-/**
- * @author Hans Dockter
- */
-class ForkOptions extends AbstractOptions {
-    /**
-     * The executable to use to fork the compiler.
-     */
-    @Input @Optional
-    String executable = null
-
-    /**
-     * The initial heap size for the compiler process.
-     */
-    String memoryInitialSize = null
-
-    /**
-     * The maximum heap size for the compiler process.
-     */
-    String memoryMaximumSize = null
-    String tempDir = null
-
-    /**
-     * The JVM command-line arguments for the compiler process.
-     */
-    List jvmArgs
-
-    Map fieldName2AntMap() {
-        [tempDir: 'tempdir']
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java
new file mode 100644
index 0000000..d0d4d18
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java
@@ -0,0 +1,112 @@
+/*
+ * 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.api.tasks.compile;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Fork options for Java compilation.
+ *
+ * @author Hans Dockter
+ */
+public class ForkOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    /**
+     * The executable to use to fork the compiler.
+     */
+    @Input @Optional
+    private String executable;
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public void setExecutable(String executable) {
+        this.executable = executable;
+    }
+
+    /**
+     * The initial heap size for the compiler process.
+     */
+    private String memoryInitialSize;
+
+    public String getMemoryInitialSize() {
+        return memoryInitialSize;
+    }
+
+    public void setMemoryInitialSize(String memoryInitialSize) {
+        this.memoryInitialSize = memoryInitialSize;
+    }
+
+    /**
+     * The maximum heap size for the compiler process.
+     */
+    private String memoryMaximumSize;
+
+    public String getMemoryMaximumSize() {
+        return memoryMaximumSize;
+    }
+
+    public void setMemoryMaximumSize(String memoryMaximumSize) {
+        this.memoryMaximumSize = memoryMaximumSize;
+    }
+
+    /**
+   * Directory for temporary files. Only used if compilation is done by an
+   * underlying Ant javac task, happens in a forked process, and the command
+   * line args length exceeds 4k. Defaults to <tt>java.io.tmpdir</tt>.
+   */
+    private String tempDir;
+
+    public String getTempDir() {
+        return tempDir;
+    }
+
+    public void setTempDir(String tempDir) {
+        this.tempDir = tempDir;
+    }
+
+    /**
+     * Any additional JVM arguments for the compiler process.
+     */
+    private List<String> jvmArgs = Lists.newArrayList();
+
+    public List<String> getJvmArgs() {
+        return jvmArgs;
+    }
+
+    public void setJvmArgs(List<String> jvmArgs) {
+        this.jvmArgs = jvmArgs;
+    }
+
+    public Map<String, String> fieldName2AntMap() {
+        return ImmutableMap.of("tempDir", "tempdir");
+    }
+
+    public List<String> excludedFieldsFromOptionMap() {
+        return ImmutableList.of("jvmArgs");
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
index d8326d6..4960631 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
@@ -16,16 +16,19 @@
 
 package org.gradle.api.tasks.compile;
 
+import org.gradle.api.AntBuilder;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.file.TemporaryFileProvider;
 import org.gradle.api.internal.project.IsolatedAntBuilder;
-import org.gradle.api.internal.tasks.compile.AntGroovyCompiler;
-import org.gradle.api.internal.tasks.compile.GroovyJavaJointCompiler;
-import org.gradle.api.internal.tasks.compile.IncrementalGroovyCompiler;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.compile.*;
+import org.gradle.api.internal.tasks.compile.Compiler;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.WorkResult;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -38,32 +41,46 @@ import java.util.List;
  * @author Hans Dockter
  */
 public class GroovyCompile extends AbstractCompile {
-    private GroovyJavaJointCompiler compiler;
-
+    private Compiler<GroovyJavaJointCompileSpec> compiler;
     private FileCollection groovyClasspath;
-
+    private final GroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec();
+    private final TemporaryFileProvider tempFileProvider;
+    
     public GroovyCompile() {
+        ProjectInternal projectInternal = (ProjectInternal) getProject();
         IsolatedAntBuilder antBuilder = getServices().get(IsolatedAntBuilder.class);
         ClassPathRegistry classPathRegistry = getServices().get(ClassPathRegistry.class);
-        compiler = new IncrementalGroovyCompiler(new AntGroovyCompiler(antBuilder, classPathRegistry), getOutputs());
+        Factory<AntBuilder> antBuilderFactory = getServices().getFactory(AntBuilder.class);
+        JavaCompilerFactory inProcessCompilerFactory = new InProcessJavaCompilerFactory();
+        tempFileProvider = projectInternal.getServices().get(TemporaryFileProvider.class);
+        DefaultJavaCompilerFactory javaCompilerFactory = new DefaultJavaCompilerFactory(projectInternal, tempFileProvider, antBuilderFactory, inProcessCompilerFactory);
+        javaCompilerFactory.setGroovyJointCompilation(false);
+        GroovyCompilerFactory groovyCompilerFactory = new GroovyCompilerFactory(projectInternal, antBuilder, classPathRegistry, javaCompilerFactory);
+        Compiler<GroovyJavaJointCompileSpec> delegatingCompiler = new DelegatingGroovyCompiler(groovyCompilerFactory);
+        compiler = new IncrementalGroovyCompiler(delegatingCompiler, getOutputs());
     }
 
     protected void compile() {
         List<File> taskClasspath = new ArrayList<File>(getGroovyClasspath().getFiles());
         throwExceptionIfTaskClasspathIsEmpty(taskClasspath);
-        compiler.setSource(getSource());
-        compiler.setDestinationDir(getDestinationDir());
-        compiler.setClasspath(getClasspath());
-        compiler.setSourceCompatibility(getSourceCompatibility());
-        compiler.setTargetCompatibility(getTargetCompatibility());
-        compiler.setGroovyClasspath(taskClasspath);
-        WorkResult result = compiler.execute();
+        spec.setSource(getSource());
+        spec.setDestinationDir(getDestinationDir());
+        spec.setClasspath(getClasspath());
+        spec.setSourceCompatibility(getSourceCompatibility());
+        spec.setTargetCompatibility(getTargetCompatibility());
+        spec.setGroovyClasspath(taskClasspath);
+        if (spec.getGroovyCompileOptions().getStubDir() == null) {
+            File dir = tempFileProvider.newTemporaryFile("groovy-java-stubs");
+            dir.mkdirs();
+            spec.getGroovyCompileOptions().setStubDir(dir);
+        }
+        WorkResult result = compiler.execute(spec);
         setDidWork(result.getDidWork());
     }
 
     private void throwExceptionIfTaskClasspathIsEmpty(Collection<File> taskClasspath) {
         if (taskClasspath.size() == 0) {
-            throw new InvalidUserDataException("You must assign a Groovy library to the groovy configuration!");
+            throw new InvalidUserDataException("You must assign a Groovy library to the 'groovy' configuration.");
         }
     }
 
@@ -75,17 +92,17 @@ public class GroovyCompile extends AbstractCompile {
      */
     @Nested
     public GroovyCompileOptions getGroovyOptions() {
-        return compiler.getGroovyCompileOptions();
+        return spec.getGroovyCompileOptions();
     }
 
     /**
      * Returns the options for Java compilation.
      *
-     * @return The java compile options. Never returns null.
+     * @return The Java compile options. Never returns null.
      */
     @Nested
     public CompileOptions getOptions() {
-        return compiler.getCompileOptions();
+        return spec.getCompileOptions();
     }
 
     /**
@@ -107,11 +124,11 @@ public class GroovyCompile extends AbstractCompile {
         this.groovyClasspath = groovyClasspath;
     }
 
-    public GroovyJavaJointCompiler getCompiler() {
+    public Compiler<GroovyJavaJointCompileSpec> getCompiler() {
         return compiler;
     }
 
-    public void setCompiler(GroovyJavaJointCompiler compiler) {
+    public void setCompiler(Compiler<GroovyJavaJointCompileSpec> compiler) {
         this.compiler = compiler;
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.groovy
deleted file mode 100644
index 7c78296..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.groovy
+++ /dev/null
@@ -1,56 +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.tasks.compile
-
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.Optional;
-
-/**
- * @author Hans Dockter
- */
-public class GroovyCompileOptions extends AbstractOptions {
-    boolean failOnError = true
-    boolean verbose = false
-    boolean listFiles = false
-    @Input @Optional
-    String encoding = null
-    boolean fork = true
-    GroovyForkOptions forkOptions = new GroovyForkOptions()
-    @Input
-    boolean includeJavaRuntime = false
-    boolean stacktrace
-
-    GroovyCompileOptions fork(Map forkArgs) {
-        fork = true
-        forkOptions.define(forkArgs)
-        this
-    }
-
-    List excludedFieldsFromOptionMap() {
-        ['forkOptions']
-    }
-
-    Map fieldName2AntMap() {
-        [
-                failOnError: 'failonerror',
-                listFiles: 'listfiles',
-        ]
-    }
-
-    Map optionMap() {
-        super.optionMap() + forkOptions.optionMap()
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java
new file mode 100644
index 0000000..f2d4370
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java
@@ -0,0 +1,196 @@
+/*
+ * 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.tasks.compile;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.tasks.Input;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Compilation options to be passed to the Groovy compiler.
+ *
+ * @author Hans Dockter
+ */
+public class GroovyCompileOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    /**
+     * Tells whether the compilation task should fail if compile errors occurred. Defaults to <tt>true</tt>.
+     */
+    private boolean failOnError = true;
+
+    public boolean isFailOnError() {
+        return failOnError;
+    }
+
+    public void setFailOnError(boolean failOnError) {
+        this.failOnError = failOnError;
+    }
+
+    /**
+     * Tells whether to turn on verbose output. Defaults to <tt>false</tt>.
+     */
+    private boolean verbose;
+
+    public boolean isVerbose() {
+        return verbose;
+    }
+
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    /**
+     * Tells whether to print which source files are to be compiled. Defaults to <tt>false</tt>.
+     */
+    private boolean listFiles;
+
+    public boolean isListFiles() {
+        return listFiles;
+    }
+
+    public void setListFiles(boolean listFiles) {
+        this.listFiles = listFiles;
+    }
+
+    /**
+     * The source encoding. Defaults to <tt>UTF-8</tt>.
+     */
+    @Input
+    private String encoding = "UTF-8";
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    /**
+     * Tells whether to run the Groovy compiler in a separate process. Defaults to <tt>true</tt>.
+     */
+    private boolean fork = true;
+
+    public boolean isFork() {
+        return fork;
+    }
+
+    public void setFork(boolean fork) {
+        this.fork = fork;
+    }
+
+    /**
+     * Options for running the Groovy compiler in a separate process. These options only take effect
+     * if <tt>fork</tt> is set to <tt>true</tt>.
+     */
+    private GroovyForkOptions forkOptions = new GroovyForkOptions();
+
+    public GroovyForkOptions getForkOptions() {
+        return forkOptions;
+    }
+
+    public void setForkOptions(GroovyForkOptions forkOptions) {
+        this.forkOptions = forkOptions;
+    }
+
+    /**
+     * Tells whether the Java runtime should be put on the compiler's compile class path. Defaults to <tt>false</tt>.
+     */
+    @Input
+    private boolean includeJavaRuntime;
+
+    public boolean isIncludeJavaRuntime() {
+        return includeJavaRuntime;
+    }
+
+    public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
+        this.includeJavaRuntime = includeJavaRuntime;
+    }
+
+    /**
+     * Tells whether to print a stack trace when the compiler hits a problem (like a compile error).
+     * Defaults to <tt>false</tt>.
+     */
+    private boolean stacktrace;
+
+    public boolean isStacktrace() {
+        return stacktrace;
+    }
+
+    public void setStacktrace(boolean stacktrace) {
+        this.stacktrace = stacktrace;
+    }
+
+    private boolean useAnt;
+
+    public boolean isUseAnt() {
+        return useAnt;
+    }
+
+    public void setUseAnt(boolean useAnt) {
+        this.useAnt = useAnt;
+    }
+
+    private File stubDir;
+
+    public File getStubDir() {
+        return stubDir;
+    }
+
+    public void setStubDir(File stubDir) {
+        this.stubDir = stubDir;
+    }
+
+    private boolean keepStubs;
+
+    public boolean isKeepStubs() {
+        return keepStubs;
+    }
+
+    public void setKeepStubs(boolean keepStubs) {
+        this.keepStubs = keepStubs;
+    }
+
+    /**
+     * Shortcut for setting both <tt>fork</tt> and <tt>forkOptions</tt>.
+     *
+     * @param forkArgs fork options in map notation
+     */
+    public GroovyCompileOptions fork(Map forkArgs) {
+        fork = true;
+        forkOptions.define(forkArgs);
+        return this;
+    }
+
+    public List<String> excludedFieldsFromOptionMap() {
+        return ImmutableList.of("forkOptions", "useAnt", "stubDir", "keepStubs");
+    }
+
+    public Map<String, String> fieldName2AntMap() {
+        return ImmutableMap.of("failOnError", "failonerror", "listFiles", "listfiles");
+    }
+
+    public Map<String, Object> optionMap() {
+        Map<String, Object> map = super.optionMap();
+        map.putAll(forkOptions.optionMap());
+        return map;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.groovy
deleted file mode 100644
index 2055d5e..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.groovy
+++ /dev/null
@@ -1,24 +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.tasks.compile;
-
-/**
- * @author Hans Dockter
- */
-class GroovyForkOptions extends AbstractOptions {
-    String memoryInitialSize = null
-    String memoryMaximumSize = null
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java
new file mode 100644
index 0000000..535e958
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java
@@ -0,0 +1,67 @@
+/*
+ * 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.tasks.compile;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Fork options for Groovy compilation.
+ *
+ * @author Hans Dockter
+ */
+public class GroovyForkOptions extends AbstractOptions {
+    private static final long serialVersionUID = 0;
+
+    private String memoryInitialSize;
+
+    private String memoryMaximumSize;
+
+    /**
+     * Any additional JVM arguments for the compiler process.
+     */
+    private List<String> jvmArgs = Lists.newArrayList();
+
+    public String getMemoryInitialSize() {
+        return memoryInitialSize;
+    }
+
+    public void setMemoryInitialSize(String memoryInitialSize) {
+        this.memoryInitialSize = memoryInitialSize;
+    }
+
+    public String getMemoryMaximumSize() {
+        return memoryMaximumSize;
+    }
+
+    public void setMemoryMaximumSize(String memoryMaximumSize) {
+        this.memoryMaximumSize = memoryMaximumSize;
+    }
+
+    public List<String> getJvmArgs() {
+        return jvmArgs;
+    }
+
+    public void setJvmArgs(List<String> jvmArgs) {
+        this.jvmArgs = jvmArgs;
+    }
+
+    public List<String> excludedFieldsFromOptionMap() {
+        return ImmutableList.of("jvmArgs");
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
index 6144cc8..27f6667 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
@@ -100,34 +100,34 @@ public class Groovydoc extends SourceTask {
     }
 
     /**
-     * Returns the classpath used to locate classes referenced by the documented sources.
+     * Returns the classpath containing the Groovy library to be used.
      *
-     * @return The classpath used to locate classes referenced by the documented sources
-     */
+     * @return The classpath containing the Groovy library to be used
+     */   
     @InputFiles
     public FileCollection getGroovyClasspath() {
         return groovyClasspath;
     }
 
     /**
-     * Sets the classpath used to locate classes referenced by the documented sources.
+     * Sets the classpath containing the Groovy library to be used.
      */
     public void setGroovyClasspath(FileCollection groovyClasspath) {
         this.groovyClasspath = groovyClasspath;
     }
 
     /**
-     * Returns the classpath containing the Groovy library to be used.
-     *
-     * @return The classpath containing the Groovy library to be used
-     */
+      * Returns the classpath used to locate classes referenced by the documented sources.
+      *
+      * @return The classpath used to locate classes referenced by the documented sources
+      */
     @InputFiles
     public FileCollection getClasspath() {
         return classpath;
     }
 
     /**
-     * Sets the classpath containing the Groovy library to be used.
+     * Sets the classpath used to locate classes referenced by the documented sources.
      */
     public void setClasspath(FileCollection classpath) {
         this.classpath = classpath;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
index 847151b..3da6c79 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
@@ -26,6 +26,8 @@ import org.gradle.process.internal.ExecAction;
 import org.gradle.process.internal.ExecException;
 import org.gradle.util.GUtil;
 
+import groovy.lang.Closure;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -35,7 +37,7 @@ import java.util.List;
  * <p>Generates HTML API documentation for Java classes.</p>
  * <p>
  * If you create your own Javadoc tasks remember to specify the 'source' property!
- * Without source the javadoc task will not create any documentation. Example:
+ * Without source the Javadoc task will not create any documentation. Example:
  * <pre autoTested=''>
  * apply plugin: 'java'
  *
@@ -59,7 +61,7 @@ import java.util.List;
  *
  * task generateRestApiDocs(type: Javadoc) {
  *   source = sourceSets.main.allJava
- *   destinationDir = file("${reportsDir.absolutePath}/rest-api-docs")
+ *   destinationDir = reporting.file("rest-api-docs")
  *   options.docletpath = configurations.jaxDoclet.files.asType(List)
  *   options.doclet = "com.lunatech.doclets.jax.jaxrs.JAXRSDoclet"
  *   options.addStringOption("jaxrscontext", "http://localhost:8080/myapp")
@@ -205,7 +207,7 @@ public class Javadoc extends SourceTask {
     }
 
     /**
-     * Returns whether javadoc generation is accompanied by verbose output.
+     * Returns whether Javadoc generation is accompanied by verbose output.
      *
      * @see #setVerbose(boolean)
      */
@@ -214,8 +216,8 @@ public class Javadoc extends SourceTask {
     }
 
     /**
-     * Sets whether javadoc generation is accompanied by verbose output or not. The verbose output is done via println
-     * (by the underlying ant task). Thus it is not handled by our logging.
+     * Sets whether Javadoc generation is accompanied by verbose output or not. The verbose output is done via println
+     * (by the underlying Ant task). Thus it is not handled by our logging.
      *
      * @param verbose Whether the output should be verbose.
      */
@@ -245,7 +247,7 @@ public class Javadoc extends SourceTask {
     }
 
     /**
-     * Returns the javadoc generation options.
+     * Returns the Javadoc generation options.
      *
      * @return The options. Never returns null.
      */
@@ -255,7 +257,7 @@ public class Javadoc extends SourceTask {
     }
 
     /**
-     * Sets the javadoc generation options.
+     * Sets the Javadoc generation options.
      *
      * @param options The options. Must not be null.
      */
@@ -264,8 +266,17 @@ public class Javadoc extends SourceTask {
     }
 
     /**
-     * Specifies whether this task should fail when errors are encountered during javadoc generation. When {@code true},
-     * this task will fail on javadoc error. When {@code false}, this task will ignore javadoc errors.
+     * Convenience method for configuring Javadoc generation options.
+     *
+     * @param block The configuration block for Javadoc generation options.
+     */
+    public void options(Closure block) {
+        getProject().configure(getOptions(), block);
+    }
+
+    /**
+     * Specifies whether this task should fail when errors are encountered during Javadoc generation. When {@code true},
+     * this task will fail on Javadoc error. When {@code false}, this task will ignore Javadoc errors.
      */
     @Input
     public boolean isFailOnError() {
@@ -281,8 +292,8 @@ public class Javadoc extends SourceTask {
     }
 
     /**
-     * Returns the javadoc executable to use to generation the javadoc. When {@code null}, the javadoc executable for
-     * the current jvm is used.
+     * Returns the Javadoc executable to use to generate the Javadoc. When {@code null}, the Javadoc executable for
+     * the current JVM is used.
      *
      * @return The executable. May be null.
      */
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
index d78edc0..9c1b8b8 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
@@ -28,6 +28,8 @@ import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.detection.DefaultTestExecuter;
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter;
 import org.gradle.api.internal.tasks.testing.junit.JUnitTestFramework;
+import org.gradle.api.internal.tasks.testing.logging.DefaultTestLogging;
+import org.gradle.api.internal.tasks.testing.logging.StandardStreamsLogger;
 import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
 import org.gradle.api.internal.tasks.testing.results.TestLogger;
 import org.gradle.api.internal.tasks.testing.results.TestSummaryListener;
@@ -55,6 +57,34 @@ import java.util.Set;
 
 /**
  * Executes tests. Supports JUnit (3.8.x or 4.x) or TestNG tests.
+ * <p>
+ * An example with a blend of various settings
+ * <pre autoTested=''>
+ * apply plugin: 'java' //so that 'test' task is added
+ *
+ * test {
+ *   //configuring a system property for tests
+ *   systemProperty 'some.prop', 'value'
+ *
+ *   //tuning the included/excluded tests
+ *   include 'org/foo/**'
+ *   exclude 'org/boo/**'
+ *
+ *   //makes the standard streams (err and out) visible at console when running tests
+ *   testLogging.showStandardStreams = true
+ *
+ *   //tweaking memory settings for the forked vm that runs tests
+ *   jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m'
+ *
+ *   //listening to test execution events
+ *   beforeTest { descriptor ->
+ *      logger.lifecycle("Running test: " + descriptor)
+ *   }
+ *   onOutput { descriptor, event ->
+ *      logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message )
+ *   }
+ * }
+ * </pre>
  *
  * @author Hans Dockter
  */
@@ -74,10 +104,13 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     private long forkEvery;
     private int maxParallelForks = 1;
     private ListenerBroadcast<TestListener> testListenerBroadcaster;
+    private final ListenerBroadcast<TestOutputListener> testOutputListenerBroadcaster;
+    private final TestLogging testLogging = new DefaultTestLogging();
 
     public Test() {
         testListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(
                 TestListener.class);
+        testOutputListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(TestOutputListener.class);
         this.testExecuter = new DefaultTestExecuter(getServices().getFactory(WorkerProcessBuilder.class), getServices().get(
                 ActorFactory.class));
         options = new DefaultJavaForkOptions(getServices().get(FileResolver.class));
@@ -191,6 +224,34 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     /**
      * {@inheritDoc}
      */
+    public String getMinHeapSize() {
+        return options.getMinHeapSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getDefaultCharacterEncoding() {
+        return options.getDefaultCharacterEncoding();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDefaultCharacterEncoding(String defaultCharacterEncoding) {
+        options.setDefaultCharacterEncoding(defaultCharacterEncoding);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setMinHeapSize(String heapSize) {
+        options.setMinHeapSize(heapSize);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public String getMaxHeapSize() {
         return options.getMaxHeapSize();
     }
@@ -327,13 +388,15 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         TestSummaryListener listener = new TestSummaryListener(LoggerFactory.getLogger(Test.class));
         addTestListener(listener);
         addTestListener(new TestLogger(getServices().get(ProgressLoggerFactory.class)));
+        addTestOutputListener(new StandardStreamsLogger(LoggerFactory.getLogger(Test.class), testLogging));
 
-        TestResultProcessor resultProcessor = new TestListenerAdapter(getTestListenerBroadcaster().getSource());
+        TestResultProcessor resultProcessor = new TestListenerAdapter(
+                getTestListenerBroadcaster().getSource(), testOutputListenerBroadcaster.getSource());
         testExecuter.execute(this, resultProcessor);
 
         testFramework.report();
 
-        if (!isIgnoreFailures() && listener.hadFailures()) {
+        if (!getIgnoreFailures() && listener.hadFailures()) {
             throw new GradleException("There were failing tests. See the report at " + getTestReportDir() + ".");
         }
     }
@@ -347,7 +410,11 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Registers a test listener with this task.  This listener will NOT be notified of tests executed by other tasks.
+     * Registers a test listener with this task. Consider also the following handy methods for quicker hooking into test execution:
+     * {@link #beforeTest(groovy.lang.Closure)}, {@link #afterTest(groovy.lang.Closure)},
+     * {@link #beforeSuite(groovy.lang.Closure)}, {@link #afterSuite(groovy.lang.Closure)}
+     * <p>
+     * This listener will NOT be notified of tests executed by other tasks.
      * To get that behavior, use {@link org.gradle.api.invocation.Gradle#addListener(Object)}.
      *
      * @param listener The listener to add.
@@ -357,6 +424,15 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
+     * Registers a output listener with this task. Quicker way of hooking into output events is using the {@link #onOutput(groovy.lang.Closure)} method.
+     *
+     * @param listener The listener to add.
+     */
+    public void addTestOutputListener(TestOutputListener listener) {
+        testOutputListenerBroadcaster.add(listener);
+    }
+
+    /**
      * Unregisters a test listener with this task.  This method will only remove listeners that were added by calling
      * {@link #addTestListener(org.gradle.api.tasks.testing.TestListener)} on this task.  If the listener was registered
      * with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
@@ -369,6 +445,18 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
+     * Unregisters a test output listener with this task.  This method will only remove listeners that were added by calling
+     * {@link #addTestOutputListener(org.gradle.api.tasks.testing.TestOutputListener)} on this task.  If the listener was registered
+     * with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
+     * Instead, use {@link org.gradle.api.invocation.Gradle#removeListener(Object)}.
+     *
+     * @param listener The listener to remove.
+     */
+    public void removeTestOutputListener(TestOutputListener listener) {
+        testOutputListenerBroadcaster.remove(listener);
+    }
+
+    /**
      * <p>Adds a closure to be notified before a test suite is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
      * instance is passed to the closure as a parameter.</p>
      *
@@ -415,6 +503,28 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
+     * Adds a closure to be notified when output from the test received.
+     * A {@link org.gradle.api.tasks.testing.TestDescriptor}
+     * and {@link org.gradle.api.tasks.testing.TestOutputEvent} instance are passed to the closure as a parameter.
+     * <pre autoTested=''>
+     * apply plugin: 'java'
+     *
+     * test {
+     *   onOutput { descriptor, event ->
+     *     if (event.destination == TestOutputEvent.Destination.StdErr) {
+     *       logger.error("Test: " + descriptor + ", error: " + event.message)
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param closure The closure to call.
+     */
+    public void onOutput(Closure closure) {
+        testOutputListenerBroadcaster.add("onOutput", closure);
+    }
+
+    /**
      * Adds include patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
      *
      * @see #setIncludes(Iterable)
@@ -434,11 +544,17 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public Test include(Spec<FileTreeElement> includeSpec) {
         patternSet.include(includeSpec);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public Test include(Closure includeSpec) {
         patternSet.include(includeSpec);
         return this;
@@ -464,11 +580,17 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public Test exclude(Spec<FileTreeElement> excludeSpec) {
         patternSet.exclude(excludeSpec);
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     public Test exclude(Closure excludeSpec) {
         patternSet.exclude(excludeSpec);
         return this;
@@ -574,16 +696,15 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * {@inheritDoc}
      */
     @Input
-    public boolean isIgnoreFailures() {
+    public boolean getIgnoreFailures() {
         return ignoreFailures;
     }
 
     /**
      * {@inheritDoc}
      */
-    public Test setIgnoreFailures(boolean ignoreFailures) {
+    public void setIgnoreFailures(boolean ignoreFailures) {
         this.ignoreFailures = ignoreFailures;
-        return this;
     }
 
     public TestFramework getTestFramework() {
@@ -644,7 +765,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     /**
      * Specifies that JUnit should be used to execute the tests.
      *
-     * @param testFrameworkConfigure A closure used to configure the JUint options. This closure is passed an instance
+     * @param testFrameworkConfigure A closure used to configure the JUnit options. This closure is passed an instance
      * of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
      */
     public void useJUnit(Closure testFrameworkConfigure) {
@@ -661,8 +782,8 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     /**
      * Specifies that TestNG should be used to execute the tests.
      *
-     * @param testFrameworkConfigure A closure used to configure the JUint options. This closure is passed an instance
-     * of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
+     * @param testFrameworkConfigure A closure used to configure the TestNG options. This closure is passed an instance
+     * of type {@link org.gradle.api.tasks.testing.testng.TestNGOptions}.
      */
     public void useTestNG(Closure testFrameworkConfigure) {
         useTestFramework(new TestNGTestFramework(this), testFrameworkConfigure);
@@ -755,7 +876,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
      * @return The maximum number of forked test processes.
      */
     public int getMaxParallelForks() {
-        return maxParallelForks;
+        return getDebug() ? 1 : maxParallelForks;
     }
 
     /**
@@ -779,16 +900,37 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     @InputFiles
     @Input // Also marked as input to force tests to run when the set of candidate class files changes 
     public FileTree getCandidateClassFiles() {
-        PatternSet patterns = new PatternSet();
-        patterns.copyFrom(patternSet);
-        if (!isScanForTestClasses()) {
-            if (patterns.getIncludes().isEmpty()) {
-                patterns.include("**/*Tests.class", "**/*Test.class");
-            }
-            if (patterns.getExcludes().isEmpty()) {
-                patterns.exclude("**/Abstract*.class");
-            }
-        }
-        return getProject().fileTree(getTestClassesDir()).matching(patterns);
+        return getProject().fileTree(getTestClassesDir()).matching(patternSet);
+    }
+
+    /**
+     * Allows configuring the logging of the test execution, for example log eagerly the standard output, etc.
+     * <pre autoTested=''>
+     * apply plugin: 'java'
+     *
+     * //makes the standard streams (err and out) visible at console when running tests
+     * test.testLogging.showStandardStreams = true
+     * </pre>
+     *
+     * @return test logging configuration
+     */
+    public TestLogging getTestLogging() {
+        return testLogging;
+    }
+
+    /**
+     * Allows configuring the logging of the test execution, for example log eagerly the standard output, etc.
+     * <pre autoTested=''>
+     * apply plugin: 'java'
+     *
+     * //makes the standard streams (err and out) visible at console when running tests
+     * test.testLogging {
+     *   showStandardStreams = true
+     * }
+     * </pre>
+     * @param closure configure closure
+     */
+    public void testLogging(Closure closure) {
+        ConfigureUtil.configure(closure, testLogging);
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java
new file mode 100644
index 0000000..46f19cc
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.testing;
+
+/**
+ * Configures logging of the test execution, e.g. whether the std err / out should be eagerly shown
+ */
+public interface TestLogging {
+
+    /**
+     * Whether to show eagerly the standard stream events. Standard output is printed at INFO level, standard error at ERROR level.
+     *
+     * @param standardStreams to configure
+     * @return this logging instance
+     */
+    TestLogging setShowStandardStreams(boolean standardStreams);
+
+    /**
+     * Whether to show eagerly the standard stream events. Standard output is printed at INFO level, standard error at ERROR level.
+     */
+    boolean getShowStandardStreams();
+
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestOutputEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestOutputEvent.java
new file mode 100644
index 0000000..126e10c
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestOutputEvent.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.testing;
+
+/**
+ * Standard output or standard error message logged during the execution of the test
+ */
+public interface TestOutputEvent {
+
+    /**
+     * Destination of the message
+     */
+    Destination getDestination();
+
+    /**
+     * Message content
+     */
+    String getMessage();
+
+    /**
+     * Destination of the message
+     */
+    enum Destination {
+        StdOut, StdErr
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestOutputListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestOutputListener.java
new file mode 100644
index 0000000..c1c6b05
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestOutputListener.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.testing;
+
+/**
+ * Listens to the output events like printing to standard output or error
+ */
+public interface TestOutputListener {
+
+    /**
+     * Fired when during test execution anything is printed to standard output or error
+     *
+     * @param testDescriptor describes the test
+     * @param outputEvent the event that contains the output message and the destination (standard output or error, etc.)
+     */
+    void onOutput(TestDescriptor testDescriptor, TestOutputEvent outputEvent);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestResult.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestResult.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
index fbfc740..3fa6a89 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
@@ -13,8 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
 package org.gradle.api.tasks.testing.testng
 
 import groovy.xml.MarkupBuilder
@@ -25,9 +23,8 @@ import org.gradle.api.tasks.testing.TestFrameworkOptions
  * @author Tom Eyckmans
  */
 public class TestNGOptions extends TestFrameworkOptions implements Serializable {
-
-    public static final String JDK_ANNOTATIONS = 'JDK'
-    public static final String JAVADOC_ANNOTATIONS = 'Javadoc'
+    static final String JDK_ANNOTATIONS = 'JDK'
+    static final String JAVADOC_ANNOTATIONS = 'Javadoc'
 
     /**
      * When true, Javadoc annotations are used for these tests. When false, JDK annotations are used. If you use
@@ -38,8 +35,8 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
      */
     boolean javadocAnnotations
 
-    def String getAnnotations() {
-        return javadocAnnotations ? JAVADOC_ANNOTATIONS : JDK_ANNOTATIONS;
+    String getAnnotations() {
+        javadocAnnotations ? JAVADOC_ANNOTATIONS : JDK_ANNOTATIONS
     }
 
     /**
@@ -105,7 +102,7 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
     transient MarkupBuilder suiteXmlBuilder = null
     private File projectDir
 
-    public TestNGOptions(File projectDir) {
+    TestNGOptions(File projectDir) {
         this.projectDir = projectDir
     }
 
@@ -120,14 +117,14 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
     MarkupBuilder suiteXmlBuilder() {
         suiteXmlWriter = new StringWriter()
         suiteXmlBuilder = new MarkupBuilder(suiteXmlWriter)
-        return suiteXmlBuilder
+        suiteXmlBuilder
     }
 
     /**
      * Add suite files by Strings. Each suiteFile String should be a path relative to the project root.
      */
     void suites(String ... suiteFiles) {
-        suiteFiles.each {it ->
+        suiteFiles.each {
             suiteXmlFiles.add(new File(projectDir, it))
         }
     }
@@ -145,16 +142,16 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
         suites.addAll(suiteXmlFiles)
 
         if (suiteXmlBuilder != null) {
-            File buildSuiteXml = new File(testSuitesDir.absolutePath, "build-suite.xml");
+            File buildSuiteXml = new File(testSuitesDir.absolutePath, "build-suite.xml")
 
             if (buildSuiteXml.exists()) {
                 if (!buildSuiteXml.delete()) {
-                    throw new RuntimeException("failed to remove already existing build-suite.xml file");
+                    throw new RuntimeException("failed to remove already existing build-suite.xml file")
                 }
             }
 
-            buildSuiteXml.append('<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">');
-            buildSuiteXml.append(suiteXmlWriter.toString());
+            buildSuiteXml.append('<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">')
+            buildSuiteXml.append(suiteXmlWriter.toString())
 
             suites.add(buildSuiteXml);
         }
@@ -164,37 +161,35 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
 
     TestNGOptions jdkAnnotations() {
         javadocAnnotations = false
-        return this
+        this
     }
 
     TestNGOptions javadocAnnotations() {
         javadocAnnotations = true
-        return this
+        this
     }
 
-    public TestNGOptions includeGroups(String ... includeGroups) {
+    TestNGOptions includeGroups(String ... includeGroups) {
         this.includeGroups.addAll(Arrays.asList(includeGroups))
-        return this
+        this
     }
 
-    public TestNGOptions excludeGroups(String ... excludeGroups) {
+    TestNGOptions excludeGroups(String ... excludeGroups) {
         this.excludeGroups.addAll(Arrays.asList(excludeGroups))
-        return this;
+        this
     }
 
     TestNGOptions useDefaultListeners() {
-        useDefaultListeners = true;
-
-        return this;
+        useDefaultListeners = true
+        this
     }
 
     TestNGOptions useDefaultListeners(boolean useDefaultListeners) {
-        this.useDefaultListeners = useDefaultListeners;
-
-        return this;
+        this.useDefaultListeners = useDefaultListeners
+        this
     }
 
-    public def propertyMissing(String name) {
+    Object propertyMissing(String name) {
         if (suiteXmlBuilder != null) {
             return suiteXmlBuilder.getMetaClass()."${name}"
         } else {
@@ -202,9 +197,9 @@ public class TestNGOptions extends TestFrameworkOptions implements Serializable
         }
     }
 
-    public def methodMissing(String name, args) {
+    Object methodMissing(String name, Object args) {
         if (suiteXmlBuilder != null) {
-            return suiteXmlBuilder.getMetaClass().invokeMethod(suiteXmlBuilder, name, args);
+            return suiteXmlBuilder.getMetaClass().invokeMethod(suiteXmlBuilder, name, args)
         } else {
             return super.methodMissing(name, args)
         }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java
new file mode 100644
index 0000000..8c5d3bc
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/Wrapper.java
@@ -0,0 +1,280 @@
+/*
+ * 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.wrapper;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.plugins.StartScriptGenerator;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.util.*;
+import org.gradle.wrapper.GradleWrapperMain;
+import org.gradle.wrapper.Install;
+import org.gradle.wrapper.WrapperExecutor;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * <p>Generates scripts (for *nix and windows) which allow you to build your project with Gradle, without having to
+ * install Gradle.
+ *
+ * <p>When a user executes a wrapper script the first time, the script downloads and installs the appropriate Gradle
+ * distribution and runs the build against this downloaded distribution. Any installed Gradle distribution is ignored
+ * when using the wrapper scripts.
+ *
+ * <p>The scripts generated by this task are intended to be committed to your version control system. This task also
+ * generates a small {@code gradle-wrapper.jar} bootstrap JAR file and properties file which should also be committed to
+ * your VCS. The scripts delegates to this JAR.
+ *
+ * @author Hans Dockter
+ */
+public class Wrapper extends DefaultTask {
+    public static final String DEFAULT_DISTRIBUTION_PARENT_NAME = Install.DEFAULT_DISTRIBUTION_PATH;
+
+    private String distributionUrl;
+
+    /**
+     * Specifies how the wrapper path should be interpreted.
+     */
+    public enum PathBase {
+        PROJECT, GRADLE_USER_HOME
+    }
+
+    private Object scriptFile;
+    private Object jarFile;
+
+    @Input
+    private String distributionPath;
+
+    @Input
+    private PathBase distributionBase = PathBase.GRADLE_USER_HOME;
+
+    private GradleVersion gradleVersion;
+
+    @Input
+    private String archivePath;
+
+    @Input
+    private PathBase archiveBase = PathBase.GRADLE_USER_HOME;
+
+    private final DistributionLocator locator = new DistributionLocator();
+
+    public Wrapper() {
+        scriptFile = "gradlew";
+        jarFile = "gradle/wrapper/gradle-wrapper.jar";
+        distributionPath = DEFAULT_DISTRIBUTION_PARENT_NAME;
+        archivePath = DEFAULT_DISTRIBUTION_PARENT_NAME;
+        gradleVersion = GradleVersion.current();
+    }
+
+    @TaskAction
+    void generate() {
+        File jarFileDestination = getJarFile();
+        File unixScript = getScriptFile();
+        FileResolver resolver = getServices().get(FileResolver.class).withBaseDir(unixScript.getParentFile());
+        String jarFileRelativePath = resolver.resolveAsRelativePath(jarFileDestination);
+
+        writeProperties(getPropertiesFile());
+
+        URL jarFileSource = Wrapper.class.getResource("/gradle-wrapper.jar");
+        if (jarFileSource == null) {
+            throw new GradleException("Cannot locate wrapper JAR resource.");
+        }
+        GFileUtils.copyURLToFile(jarFileSource, jarFileDestination);
+
+        StartScriptGenerator generator = new StartScriptGenerator();
+        generator.setApplicationName("Gradle");
+        generator.setMainClassName(GradleWrapperMain.class.getName());
+        generator.setClasspath(WrapUtil.toList(jarFileRelativePath));
+        generator.setOptsEnvironmentVar("GRADLE_OPTS");
+        generator.setExitEnvironmentVar("GRADLE_EXIT_CONSOLE");
+        generator.setAppNameSystemProperty("org.gradle.appname");
+        generator.setScriptRelPath(unixScript.getName());
+        generator.generateUnixScript(unixScript);
+        generator.generateWindowsScript(getBatchScript());
+    }
+
+    private void writeProperties(File propertiesFileDestination) {
+        Properties wrapperProperties = new Properties();
+        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_URL_PROPERTY, getDistributionUrl());
+        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY, distributionBase.toString());
+        wrapperProperties.put(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY, distributionPath);
+        wrapperProperties.put(WrapperExecutor.ZIP_STORE_BASE_PROPERTY, archiveBase.toString());
+        wrapperProperties.put(WrapperExecutor.ZIP_STORE_PATH_PROPERTY, archivePath);
+        GUtil.saveProperties(wrapperProperties, propertiesFileDestination);
+    }
+
+    /**
+     * Returns the file to write the wrapper script to.
+     */
+    @OutputFile
+    public File getScriptFile() {
+        return getProject().file(scriptFile);
+    }
+
+    public void setScriptFile(Object scriptFile) {
+        this.scriptFile = scriptFile;
+    }
+
+    /**
+     * Returns the file to write the wrapper batch script to.
+     */
+    @OutputFile
+    public File getBatchScript() {
+        File scriptFile = getScriptFile();
+        return new File(scriptFile.getParentFile(), scriptFile.getName().replaceFirst("(\\.[^\\.]+)?$", ".bat"));
+    }
+
+    /**
+     * Returns the file to write the wrapper jar file to.
+     */
+    @OutputFile
+    public File getJarFile() {
+        return getProject().file(jarFile);
+    }
+
+    public void setJarFile(Object jarFile) {
+        this.jarFile = jarFile;
+    }
+
+    /**
+     * Returns the file to write the wrapper properties to.
+     */
+    @OutputFile
+    public File getPropertiesFile() {
+        File jarFileDestination = getJarFile();
+        return new File(jarFileDestination.getParentFile(), jarFileDestination.getName().replaceAll("\\.jar$",
+                ".properties"));
+    }
+
+    /**
+     * Returns the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
+     * distribution base directory
+     *
+     * @see #setDistributionPath(String)
+     */
+    public String getDistributionPath() {
+        return distributionPath;
+    }
+
+    /**
+     * Sets the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
+     * distribution base directory
+     *
+     * @see #setDistributionPath(String)
+     */
+    public void setDistributionPath(String distributionPath) {
+        this.distributionPath = distributionPath;
+    }
+
+    /**
+     * Returns the gradle version for the wrapper.
+     *
+     * @see #setGradleVersion(String)
+     */
+    public String getGradleVersion() {
+        return gradleVersion.getVersion();
+    }
+
+    /**
+     * The version of the gradle distribution required by the wrapper. This is usually the same version of Gradle you
+     * use for building your project.
+     */
+    public void setGradleVersion(String gradleVersion) {
+        this.gradleVersion = GradleVersion.version(gradleVersion);
+    }
+
+    /**
+     * The URL to download the gradle distribution from.
+     *
+     * <p>If not set, the download URL is the default for the specified {@link #getGradleVersion()}.
+     *
+     * <p>If {@link #getGradleVersion()} is not set, will return null.
+     *
+     * <p>The wrapper downloads a certain distribution only once and caches it. If your distribution base is the
+     * project, you might submit the distribution to your version control system. That way no download is necessary at
+     * all. This might be in particular interesting, if you provide a custom gradle snapshot to the wrapper, because you
+     * don't need to provide a download server then.
+     */
+    @Input
+    public String getDistributionUrl() {
+        if (distributionUrl != null) {
+            return distributionUrl;
+        } else if (gradleVersion != null) {
+            return locator.getDistributionFor(gradleVersion).toString();
+        } else {
+            return null;
+        }
+    }
+
+    public void setDistributionUrl(String url) {
+        this.distributionUrl = url;
+    }
+
+    /**
+     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
+     * the gradle user home dir.
+     */
+    public PathBase getDistributionBase() {
+        return distributionBase;
+    }
+
+    /**
+     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
+     * the gradle user home dir.
+     */
+    public void setDistributionBase(PathBase distributionBase) {
+        this.distributionBase = distributionBase;
+    }
+
+    /**
+     * Returns the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is
+     * relative to the archive base directory.
+     */
+    public String getArchivePath() {
+        return archivePath;
+    }
+
+    /**
+     * Set's the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is relative
+     * to the parent dir specified with {@link #getArchiveBase()}.
+     */
+    public void setArchivePath(String archivePath) {
+        this.archivePath = archivePath;
+    }
+
+    /**
+     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
+     * gradle user home dir.
+     */
+    public PathBase getArchiveBase() {
+        return archiveBase;
+    }
+
+    /**
+     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
+     * gradle user home dir.
+     */
+    public void setArchiveBase(PathBase archiveBase) {
+        this.archiveBase = archiveBase;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java
new file mode 100644
index 0000000..2af2ed3
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/wrapper/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The Gradle wrapper {@link org.gradle.api.Task}.
+ */
+package org.gradle.api.tasks.wrapper;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
old mode 100644
new mode 100755
index 64035a2..26de0fa
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
@@ -1,967 +1,985 @@
-/*
- * 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.external.javadoc;
-
-import org.gradle.external.javadoc.internal.JavadocOptionFile;
-import org.gradle.external.javadoc.internal.GroupsJavadocOptionFileOption;
-import org.gradle.external.javadoc.internal.LinksOfflineJavadocOptionFileOption;
-
-import java.io.File;
-import java.util.*;
-
-/**
- * Provides the options for the standard Javadoc doclet.
- * 
- * @author Tom Eyckmans
- */
-public class StandardJavadocDocletOptions extends CoreJavadocOptions implements MinimalJavadocOptions {
-
-    public StandardJavadocDocletOptions() {
-        this(new JavadocOptionFile());
-    }
-
-    public StandardJavadocDocletOptions(JavadocOptionFile javadocOptionFile) {
-        super(javadocOptionFile);
-
-        destinationDirectory = addFileOption("d");
-        use = addBooleanOption("use");
-        version = addBooleanOption("version");
-        author = addBooleanOption("author");
-        splitIndex = addBooleanOption("splitindex");
-        header = addStringOption("header");
-        windowTitle = addStringOption("windowtitle");
-        docTitle = addStringOption("doctitle");
-        footer = addStringOption("footer");
-        bottom = addStringOption("bottom");
-        links = addMultilineStringsOption("link");
-        linksOffline = addOption(new LinksOfflineJavadocOptionFileOption("linkoffline"));
-        linkSource = addBooleanOption("linksource");
-        groups = addOption(new GroupsJavadocOptionFileOption("group"));
-        noDeprecated = addBooleanOption("nodeprecated");
-        noDeprecatedList = addBooleanOption("nodeprecatedlist");
-        noSince = addBooleanOption("nosince");
-        noTree = addBooleanOption("notree");
-        noIndex = addBooleanOption("noindex");
-        noHelp = addBooleanOption("nohelp");
-        noNavBar = addBooleanOption("nonavbar");
-        helpFile = addFileOption("helpfile");
-        stylesheetFile = addFileOption("stylesheetfile");
-        serialWarn = addBooleanOption("serialwarn");
-        charSet = addStringOption("charset");
-        docEncoding = addStringOption("docencoding");
-        keyWords = addBooleanOption("keywords");
-        tags = addStringsOption("tags");
-        tagletPath = addPathOption("tagletpath");
-        docFilesSubDirs = addBooleanOption("docfilessubdirs");
-        excludeDocFilesSubDir = addStringsOption("excludedocfilessubdir", ":");
-        noQualifiers = addStringsOption("noqualifier", ":");
-        noTimestamp = addBooleanOption("notimestamp");
-        noComment = addBooleanOption("nocomment");
-    }
-
-    /**
-     * -d  directory
-     * Specifies the destination directory where javadoc saves the generated HTML files. (The "d" means "destination.")
-     * Omitting this option causes the files to be saved to the current directory.
-     * The value directory can be absolute, or relative to the current working directory.
-     * As of 1.4, the destination directory is automatically created when javadoc is run.
-     * For example, the following generates the documentation for the package com.mypackage and
-     * saves the results in the C:/user/doc/ directory:
-     * <p/>
-     * C:> javadoc -d /user/doc com.mypackage
-     */
-    private final JavadocOptionFileOption<File> destinationDirectory;
-
-    public File getDestinationDirectory() {
-        return destinationDirectory.getValue();
-    }
-
-    public void setDestinationDirectory(File directory) {
-        this.destinationDirectory.setValue(directory);
-    }
-
-    public StandardJavadocDocletOptions destinationDirectory(File destinationDirectory) {
-        setDestinationDirectory(destinationDirectory);
-        return this;
-    }
-
-    /**
-     * -use
-     * Includes one "Use" page for each documented class and package. The page describes what packages, classes, methods,
-     * constructors and fields use any API of the given class or package. Given class C,
-     * things that use class C would include subclasses of C, fields declared as C, methods that return C,
-     * and methods and constructors with parameters of type C.
-     * For example, let's look at what might appear on the "Use" page for String.
-     * The getName() method in the java.awt.Font class returns type String. Therefore, getName() uses String,
-     * and you will find that method on the "Use" page for String.
-     * <p/>
-     * Note that this documents only uses of the API, not the implementation.
-     * If a method uses String in its implementation but does not take a string as an argument or return a string,
-     * that is not considered a "use" of String.
-     * <p/>
-     * You can access the generated "Use" page by first going to the class or package,
-     * then clicking on the "Use" link in the navigation bar.
-     */
-    private final JavadocOptionFileOption<Boolean> use;
-
-    public boolean isUse() {
-        return use.getValue();
-    }
-
-    public void setUse(boolean use) {
-        this.use.setValue(use);
-    }
-
-    public StandardJavadocDocletOptions use(boolean use) {
-        setUse(use);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions use() {
-        return use(true);
-    }
-
-    /**
-     * -version
-     * Includes the @version text in the generated docs. This text is omitted by default.
-     * To tell what version of the Javadoc tool you are using, use the -J-version option.
-     */
-    private final JavadocOptionFileOption<Boolean> version;
-
-    public boolean isVersion() {
-        return version.getValue();
-    }
-
-    public void setVersion(boolean version) {
-        this.version.setValue(version);
-    }
-
-    public StandardJavadocDocletOptions version(boolean version) {
-        setVersion(version);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions version() {
-        return version(true);
-    }
-
-    /**
-     * -author
-     * Includes the @author text in the generated docs.
-     */
-    private final JavadocOptionFileOption<Boolean> author;
-
-    public boolean isAuthor() {
-        return author.getValue();
-    }
-
-    public void setAuthor(boolean author) {
-        this.author.setValue(author);
-    }
-
-    public StandardJavadocDocletOptions author(boolean author) {
-        setAuthor(author);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions author() {
-        return author(true);
-    }
-
-    /**
-     * -splitindex
-     * Splits the index file into multiple files, alphabetically, one file per letter,
-     * plus a file for any index entries that start with non-alphabetical characters.
-     */
-    private final JavadocOptionFileOption<Boolean> splitIndex;
-
-    public boolean isSplitIndex() {
-        return splitIndex.getValue();
-    }
-
-    public void setSplitIndex(boolean splitIndex) {
-        this.splitIndex.setValue(splitIndex);
-    }
-
-    public StandardJavadocDocletOptions splitIndex(boolean splitIndex) {
-        setSplitIndex(splitIndex);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions splitIndex() {
-        return splitIndex(true);
-    }
-
-    /**
-     * -windowtitle  title
-     * Specifies the title to be placed in the HTML <title> tag.
-     * This appears in the window title and in any browser bookmarks (favorite places) that someone creates for this page.
-     * This title should not contain any HTML tags, as the browser will not properly interpret them.
-     * Any internal quotation marks within title may have to be escaped. If -windowtitle is omitted,
-     * the Javadoc tool uses the value of -doctitle for this option.
-     * C:> javadoc -windowtitle "Java 2 Platform" com.mypackage
-     */
-    private final JavadocOptionFileOption<String> windowTitle;
-
-    public String getWindowTitle() {
-        return windowTitle.getValue();
-    }
-
-    public void setWindowTitle(String windowTitle) {
-        this.windowTitle.setValue(windowTitle);
-    }
-
-    public StandardJavadocDocletOptions windowTitle(String windowTitle) {
-        setWindowTitle(windowTitle);
-        return this;
-    }
-
-    /**
-     * -header  header
-     * Specifies the header text to be placed at the top of each output file. The header will be placed to the right of
-     * the upper navigation bar. header may contain HTML tags and white space, though if it does, it must be enclosed
-     * in quotes. Any internal quotation marks within header may have to be escaped.
-     * C:> javadoc -header "<b>Java 2 Platform </b><br>v1.4" com.mypackage
-     */
-    private final JavadocOptionFileOption<String> header;
-
-    public String getHeader() {
-        return header.getValue();
-    }
-
-    public void setHeader(String header) {
-        this.header.setValue(header);
-    }
-
-    public StandardJavadocDocletOptions header(String header) {
-        setHeader(header);
-        return this;
-    }
-
-
-    /**
-     * -doctitle  title
-     * Specifies the title to be placed near the top of the overview summary file. The title will be placed as a centered,
-     * level-one heading directly beneath the upper navigation bar. The title may contain html tags and white space,
-     * though if it does, it must be enclosed in quotes. Any internal quotation marks within title may have to be escaped.
-     * C:> javadoc -doctitle "Java<sup><font size=\"-2\">TM</font></sup>" com.mypackage
-     */
-    private final JavadocOptionFileOption<String> docTitle;
-
-    public String getDocTitle() {
-        return docTitle.getValue();
-    }
-
-    public void setDocTitle(String docTitle) {
-        this.docTitle.setValue(docTitle);
-    }
-
-    public StandardJavadocDocletOptions docTitle(String docTitle) {
-        setDocTitle(docTitle);
-        return this;
-    }
-
-    /**
-     * -footer  footer
-     * Specifies the footer text to be placed at the bottom of each output file.
-     * The footer will be placed to the right of the lower navigation bar. footer may contain html tags and white space,
-     * though if it does, it must be enclosed in quotes. Any internal quotation marks within footer may have to be escaped.
-     */
-    private final JavadocOptionFileOption<String> footer;
-
-    public String getFooter() {
-        return footer.getValue();
-    }
-
-    public void setFooter(String footer) {
-        this.footer.setValue(footer);
-    }
-
-    public StandardJavadocDocletOptions footer(String footer) {
-        setFooter(footer);
-        return this;
-    }
-
-    /**
-     * -bottom  text
-     * Specifies the text to be placed at the bottom of each output file.
-     * The text will be placed at the bottom of the page, below the lower navigation bar.
-     * The text may contain HTML tags and white space, though if it does, it must be enclosed in quotes.
-     * Any internal quotation marks within text may have to be escaped.
-     */
-    private final JavadocOptionFileOption<String> bottom;
-
-    public String getBottom() {
-        return bottom.getValue();
-    }
-
-    public void setBottom(String bottom) {
-        this.bottom.setValue(bottom);
-    }
-
-    public StandardJavadocDocletOptions bottom(String bottom) {
-        setBottom(bottom);
-        return this;
-    }
-
-    /**
-     * -link  extdocURL
-     * Creates links to existing javadoc-generated documentation of external referenced classes. It takes one argument:
-     * <p/>
-     * extdocURL is the absolute or relative URL of the directory containing the external javadoc-generated documentation
-     * you want to link to. Examples are shown below.
-     * The package-list file must be found in this directory (otherwise, use -linkoffline).
-     * The Javadoc tool reads the package names from the package-list file and then links to those packages at that URL.
-     * When the Javadoc tool is run, the extdocURL value is copied literally into the <A HREF> links that are created.
-     * Therefore, extdocURL must be the URL to the directory, not to a file.
-     * You can use an absolute link for extdocURL to enable your docs to link to a document on any website,
-     * or can use a relative link to link only to a relative location. If relative,
-     * the value you pass in should be the relative path from the destination directory (specified with -d) to the directory containing the packages being linked to.
-     * <p/>
-     * When specifying an absolute link you normally use an http: link. However,
-     * if you want to link to a file system that has no web server, you can use a file: link -- however,
-     * do this only if everyone wanting to access the generated documentation shares the same file system.
-     */
-    private final JavadocOptionFileOption<List<String>> links;
-
-    public List<String> getLinks() {
-        return links.getValue();
-    }
-
-    public void setLinks(List<String> links) {
-        this.links.setValue(links);
-    }
-
-    public StandardJavadocDocletOptions links(String... links) {
-        this.links.getValue().addAll(Arrays.asList(links));
-        return this;
-    }
-
-    public StandardJavadocDocletOptions linksFile(File linksFile) {
-        return (StandardJavadocDocletOptions) optionFiles(linksFile);
-    }
-
-    /**
-     * -linkoffline  extdocURL  packagelistLoc
-     * This option is a variation of -link; they both create links to javadoc-generated documentation
-     * for external referenced classes. Use the -linkoffline option when linking to a document on the web
-     * when the Javadoc tool itself is "offline" -- that is, it cannot access the document through a web connection.
-     * More specifically, use -linkoffline if the external document's package-list file is not accessible or
-     * does not exist at the extdocURL location but does exist at a different location,
-     * which can be specified by packageListLoc (typically local). Thus, if extdocURL is accessible only on the World Wide Web,
-     * -linkoffline removes the constraint that the Javadoc tool have a web connection when generating the documentation.
-     * <p/>
-     * Another use is as a "hack" to update docs: After you have run javadoc on a full set of packages,
-     * then you can run javadoc again on onlya smaller set of changed packages,
-     * so that the updated files can be inserted back into the original set. Examples are given below.
-     * <p/>
-     * The -linkoffline option takes two arguments -- the first for the string to be embedded in the <a href> links,
-     * the second telling it where to find package-list:
-     * <p/>
-     * extdocURL is the absolute or relative URL of the directory containing the external javadoc-generated documentation you want to link to.
-     * If relative, the value should be the relative path from the destination directory (specified with -d) to the root of the packages being linked to.
-     * For more details, see extdocURL in the -link option.
-     * packagelistLoc is the path or URL to the directory containing the package-list file for the external documentation.
-     * This can be a URL (http: or file:) or file path, and can be absolute or relative. If relative,
-     * make it relative to the current directory from where javadoc was run. Do not include the package-list filename.
-     */
-    private final JavadocOptionFileOption<List<JavadocOfflineLink>> linksOffline;
-
-    public List<JavadocOfflineLink> getLinksOffline() {
-        return linksOffline.getValue();
-    }
-
-    public void setLinksOffline(List<JavadocOfflineLink> linksOffline) {
-        this.linksOffline.setValue(linksOffline);
-    }
-
-    public StandardJavadocDocletOptions linksOffline(String extDocUrl, String packageListLoc) {
-        this.linksOffline.getValue().add(new JavadocOfflineLink(extDocUrl, packageListLoc));
-        return this;
-    }
-
-    public StandardJavadocDocletOptions linksOfflineFile(File linksOfflineFile) {
-        return (StandardJavadocDocletOptions) optionFiles(linksOfflineFile);
-    }
-
-    /**
-     * -linksource
-     * Creates an HTML version of each source file (with line numbers) and adds links to them from the standard HTML documentation. Links are created for classes, interfaces, constructors, methods and fields whose declarations are in a source file. Otherwise, links are not created, such as for default constructors and generated classes.
-     * This option exposes all private implementation details in the included source files, including private classes, private fields, and the bodies of private methods, regardless of the -public, -package, -protected and -private options. Unless you also use the -private option, not all private classes or interfaces will necessarily be accessible via links.
-     * <p/>
-     * Each link appears on the name of the identifier in its declaration. For example, the link to the source code of the Button class would be on the word "Button":
-     * <p/>
-     * public class Button
-     * extends Component
-     * implements Accessible
-     * and the link to the source code of the getLabel() method in the Button class would be on the word "getLabel":
-     * public String getLabel()
-     */
-    private final JavadocOptionFileOption<Boolean> linkSource;
-
-    public boolean isLinkSource() {
-        return linkSource.getValue();
-    }
-
-    public void setLinkSource(boolean linkSource) {
-        this.linkSource.setValue(linkSource);
-    }
-
-    public StandardJavadocDocletOptions linkSource(boolean linkSource) {
-        setLinkSource(linkSource);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions linkSource() {
-        return linkSource(true);
-    }
-
-    /**
-     * -group  groupheading  packagepattern:packagepattern:...
-     * Separates packages on the overview page into whatever groups you specify, one group per table.
-     * You specify each group with a different -group option.
-     * The groups appear on the page in the order specified on the command line; packages are alphabetized within a group.
-     * For a given -group option, the packages matching the list of packagepattern expressions appear in a table
-     * with the heading groupheading.
-     * groupheading can be any text, and can include white space. This text is placed in the table heading for the group.
-     * packagepattern can be any package name, or can be the start of any package name followed by an asterisk (*).
-     * The asterisk is a wildcard meaning "match any characters". This is the only wildcard allowed.
-     * Multiple patterns can be included in a group by separating them with colons (:).
-     * NOTE: If using an asterisk in a pattern or pattern list, the pattern list must be inside quotes,
-     * such as "java.lang*:java.util"
-     * If you do not supply any -group option, all packages are placed in one group with the heading "Packages".
-     * If the all groups do not include all documented packages,
-     * any leftover packages appear in a separate group with the heading "Other Packages".
-     * <p/>
-     * For example, the following option separates the four documented packages into core,
-     * extension and other packages. Notice the trailing "dot" does not appear in "java.lang*" -- including the dot,
-     * such as "java.lang.*" would omit the java.lang package.
-     * <p/>
-     * C:> javadoc -group "Core Packages" "java.lang*:java.util"
-     * -group "Extension Packages" "javax.*"
-     * java.lang java.lang.reflect java.util javax.servlet java.new
-     * This results in the groupings:
-     * Core Packages
-     * java.lang
-     * java.lang.reflect
-     * java.util
-     * Extension Packages
-     * javax.servlet
-     * Other Packages
-     * java.new
-     */
-    private final JavadocOptionFileOption<Map<String, List<String>>> groups;
-
-    public Map<String, List<String>> getGroups() {
-        return groups.getValue();
-    }
-
-    public void setGroups(Map<String, List<String>> groups) {
-        this.groups.setValue(groups);
-    }
-
-    public StandardJavadocDocletOptions group(Map<String, List<String>> groups) {
-        setGroups(groups);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions group(String groupName, List<String> packagePatterns) {
-        this.groups.getValue().put(groupName, packagePatterns);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions group(String groupName, String... packagePatterns) {
-        return group(groupName, Arrays.asList(packagePatterns));
-    }
-
-    public StandardJavadocDocletOptions groupsFile(File groupsFile) {
-        return (StandardJavadocDocletOptions) optionFiles(groupsFile);
-    }
-
-    /**
-     * -nodeprecated
-     * Prevents the generation of any deprecated API at all in the documentation.
-     * This does what -nodeprecatedlist does, plus it does not generate any deprecated API throughout the rest of the documentation.
-     * This is useful when writing code and you don't want to be distracted by the deprecated code.
-     */
-    private final JavadocOptionFileOption<Boolean> noDeprecated;
-
-    public boolean isNoDeprecated() {
-        return noDeprecated.getValue();
-    }
-
-    public void setNoDeprecated(boolean noDeprecated) {
-        this.noDeprecated.setValue(noDeprecated);
-    }
-
-    public StandardJavadocDocletOptions noDeprecated(boolean nodeprecated) {
-        setNoDeprecated(nodeprecated);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noDeprecated() {
-        return noDeprecated(true);
-    }
-
-    /**
-     * -nodeprecatedlist
-     * Prevents the generation of the file containing the list of deprecated APIs (deprecated-list.html) and
-     * the link in the navigation bar to that page.
-     * (However, javadoc continues to generate the deprecated API throughout the rest of the document.)
-     * This is useful if your source code contains no deprecated API, and you want to make the navigation bar cleaner.
-     */
-    private final JavadocOptionFileOption<Boolean> noDeprecatedList;
-
-    public boolean isNoDeprecatedList() {
-        return noDeprecatedList.getValue();
-    }
-
-    public void setNoDeprecatedList(boolean noDeprecatedList) {
-        this.noDeprecatedList.setValue(noDeprecatedList);
-    }
-
-    public StandardJavadocDocletOptions noDeprecatedList(boolean noDeprecatedList) {
-        setNoDeprecatedList(noDeprecatedList);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noDeprecatedList() {
-        return noDeprecatedList(true);
-    }
-
-    /**
-     * -nosince
-     * Omits from the generated docs the "Since" sections associated with the @since tags.
-     */
-    private final JavadocOptionFileOption<Boolean> noSince;
-
-    public boolean isNoSince() {
-        return noSince.getValue();
-    }
-
-    public void setNoSince(boolean noSince) {
-        this.noSince.setValue(noSince);
-    }
-
-    public StandardJavadocDocletOptions noSince(boolean noSince) {
-        setNoSince(noSince);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noSince() {
-        return noSince(true);
-    }
-
-    /**
-     * -notree
-     * Omits the class/interface hierarchy pages from the generated docs.
-     * These are the pages you reach using the "Tree" button in the navigation bar.
-     * The hierarchy is produced by default.
-     */
-    private final JavadocOptionFileOption<Boolean> noTree;
-
-    public boolean isNoTree() {
-        return noTree.getValue();
-    }
-
-    public void setNoTree(boolean noTree) {
-        this.noTree.setValue(noTree);
-    }
-
-    public StandardJavadocDocletOptions noTree(boolean noTree) {
-        setNoTree(noTree);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noTree() {
-        return noTree(true);
-    }
-
-    /**
-     * -noindex
-     * Omits the index from the generated docs. The index is produced by default.
-     */
-    private final JavadocOptionFileOption<Boolean> noIndex;
-
-    public boolean isNoIndex() {
-        return noIndex.getValue();
-    }
-
-    public void setNoIndex(boolean noIndex) {
-        this.noIndex.setValue(noIndex);
-    }
-
-    public StandardJavadocDocletOptions noIndex(boolean noIndex) {
-        setNoIndex(noIndex);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noIndex() {
-        return noIndex(true);
-    }
-
-    /**
-     * -nohelp
-     * Omits the HELP link in the navigation bars at the top and bottom of each page of output.
-     */
-    private final JavadocOptionFileOption<Boolean> noHelp;
-
-    public boolean isNoHelp() {
-        return noHelp.getValue();
-    }
-
-    public void setNoHelp(boolean noHelp) {
-        this.noHelp.setValue(noHelp);
-    }
-
-    public StandardJavadocDocletOptions noHelp(boolean noHelp) {
-        setNoHelp(noHelp);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noHelp() {
-        return noHelp(true);
-    }
-
-    /**
-     * -nonavbar
-     * Prevents the generation of the navigation bar, header and footer,
-     * otherwise found at the top and bottom of the generated pages. Has no affect on the "bottom" option.
-     * The -nonavbar option is useful when you are interested only in the content and have no need for navigation,
-     * such as converting the files to PostScript or PDF for print only.
-     */
-    private final JavadocOptionFileOption<Boolean> noNavBar;
-
-    public boolean isNoNavBar() {
-        return noNavBar.getValue();
-    }
-
-    public void setNoNavBar(boolean noNavBar) {
-        this.noNavBar.setValue(noNavBar);
-    }
-
-    public StandardJavadocDocletOptions noNavBar(boolean noNavBar) {
-        setNoNavBar(noNavBar);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noNavBar() {
-        return noNavBar(true);
-    }
-
-    /**
-     * -helpfile  path/filename
-     * Specifies the path of an alternate help file path\filename that the HELP link in the top and bottom navigation bars link to. Without this option, the Javadoc tool automatically creates a help file help-doc.html that is hard-coded in the Javadoc tool. This option enables you to override this default. The filename can be any name and is not restricted to help-doc.html -- the Javadoc tool will adjust the links in the navigation bar accordingly. For example:
-     * <p/>
-     * C:> javadoc -helpfile C:/user/myhelp.html java.awt
-     */
-    private final JavadocOptionFileOption<File> helpFile;
-
-    public File getHelpFile() {
-        return helpFile.getValue();
-    }
-
-    public void setHelpFile(File helpFile) {
-        this.helpFile.setValue(helpFile);
-    }
-
-    public StandardJavadocDocletOptions helpFile(File helpFile) {
-        setHelpFile(helpFile);
-        return this;
-    }
-
-    /**
-     * -stylesheetfile  path\filename
-     * Specifies the path of an alternate HTML stylesheet file. Without this option, the Javadoc tool automatically creates a stylesheet file stylesheet.css that is hard-coded in the Javadoc tool. This option enables you to override this default. The filename can be any name and is not restricted to stylesheet.css. For example:
-     * <p/>
-     * C:> javadoc -stylesheetfile C:/user/mystylesheet.css com.mypackage
-     */
-    private final JavadocOptionFileOption<File> stylesheetFile;
-
-    public File getStylesheetFile() {
-        return stylesheetFile.getValue();
-    }
-
-    public void setStylesheetFile(File stylesheetFile) {
-        this.stylesheetFile.setValue(stylesheetFile);
-    }
-
-    public StandardJavadocDocletOptions stylesheetFile(File stylesheetFile) {
-        setStylesheetFile(stylesheetFile);
-        return this;
-    }
-
-    /**
-     * -serialwarn
-     * Generates compile-time warnings for missing @serial tags.
-     * By default, Javadoc 1.2.2 (and later versions) generates no serial warnings.
-     * (This is a reversal from earlier versions.) Use this option to display the serial warnings,
-     * which helps to properly document default serializable fields and writeExternal methods.
-     */
-    private final JavadocOptionFileOption<Boolean> serialWarn;
-
-    public boolean isSerialWarn() {
-        return serialWarn.getValue();
-    }
-
-    public void setSerialWarn(boolean serialWarn) {
-        this.serialWarn.setValue(serialWarn);
-    }
-
-    public StandardJavadocDocletOptions serialWarn(boolean serialWarn) {
-        setSerialWarn(serialWarn);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions serialWarn() {
-        return serialWarn(true);
-    }
-
-    /**
-     * -charset  name
-     * Specifies the HTML character set for this document. The name should be a preferred MIME name as given in the IANA Registry. For example:
-     * <p/>
-     * C:> javadoc -charset "iso-8859-1" mypackage
-     * <p/>
-     * would insert the following line in the head of every generated page:
-     * <p/>
-     * <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
-     * <p/>
-     * This META tag is described in the HTML standard. (4197265 and 4137321)
-     * <p/>
-     * Also see -encoding and -docencoding.
-     */
-    private final JavadocOptionFileOption<String> charSet;
-
-    public String getCharSet() {
-        return charSet.getValue();
-    }
-
-    public void setCharSet(String charSet) {
-        this.charSet.setValue(charSet);
-    }
-
-    public StandardJavadocDocletOptions charSet(String charSet) {
-        setCharSet(charSet);
-        return this;
-    }
-
-    /**
-     * -docencoding  name
-     * Specifies the encoding of the generated HTML files. The name should be a preferred MIME name as given in the IANA Registry. If you omit this option but use -encoding, then the encoding of the generated HTML files is determined by -encoding. Example:
-     * <p/>
-     * % javadoc -docencoding "ISO-8859-1" mypackage
-     * <p/>
-     * Also see -encoding and -charset.
-     */
-    private final JavadocOptionFileOption<String> docEncoding;
-
-    public String getDocEncoding() {
-        return docEncoding.getValue();
-    }
-
-    public void setDocEncoding(String docEncoding) {
-        this.docEncoding.setValue(docEncoding);
-    }
-
-    public StandardJavadocDocletOptions docEncoding(String docEncoding) {
-        setDocEncoding(docEncoding);
-        return this;
-    }
-
-    /**
-     * -keywords.
-     */
-    private final JavadocOptionFileOption<Boolean> keyWords;
-
-    public boolean isKeyWords() {
-        return keyWords.getValue();
-    }
-
-    public void setKeyWords(boolean keyWords) {
-        this.keyWords.setValue(keyWords);
-    }
-
-    public StandardJavadocDocletOptions keyWords(boolean keyWords) {
-        setKeyWords(keyWords);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions keyWords() {
-        return keyWords(true);
-    }
-
-    /**
-     * -tag  tagname:Xaoptcmf:"taghead".
-     * -taglet  class.
-     */
-    private final JavadocOptionFileOption<List<String>> tags;
-
-    public List<String> getTags() {
-        return tags.getValue();
-    }
-
-    public void setTags(List<String> tags) {
-        this.tags.setValue(tags);
-    }
-
-    public StandardJavadocDocletOptions tags(List<String> tags) {
-        this.tags.getValue().addAll(tags);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions tags(String... tags) {
-        return tags(Arrays.asList(tags));
-    }
-
-    public StandardJavadocDocletOptions taglets(String... taglets) {
-        return tags(Arrays.asList(taglets));
-    }
-
-    public StandardJavadocDocletOptions tagsFile(File tagsFile) {
-        return (StandardJavadocDocletOptions) optionFiles(tagsFile);
-    }
-
-    /**
-     * -tagletpath  tagletpathlist.
-     */
-    private final JavadocOptionFileOption<List<File>> tagletPath;
-
-    public List<File> getTagletPath() {
-        return tagletPath.getValue();
-    }
-
-    public void setTagletPath(List<File> tagletPath) {
-        this.tagletPath.setValue(tagletPath);
-    }
-
-    public StandardJavadocDocletOptions tagletPath(List<File> tagletPath) {
-        this.tagletPath.getValue().addAll(tagletPath);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions tagletPath(File... tagletPath) {
-        return tagletPath(Arrays.asList(tagletPath));
-    }
-
-    /**
-     * -docfilessubdirs.
-     */
-    private final JavadocOptionFileOption<Boolean> docFilesSubDirs;
-
-    public boolean isDocFilesSubDirs() {
-        return docFilesSubDirs.getValue();
-    }
-
-    public void setDocFilesSubDirs(boolean docFilesSubDirs) {
-        this.docFilesSubDirs.setValue(docFilesSubDirs);
-    }
-
-    public StandardJavadocDocletOptions docFilesSubDirs(boolean docFilesSubDirs) {
-        setDocFilesSubDirs(docFilesSubDirs);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions docFilesSubDirs() {
-        return docFilesSubDirs(true);
-    }
-
-    /**
-     * -excludedocfilessubdir  name1:name2...
-     */
-    private final JavadocOptionFileOption<List<String>> excludeDocFilesSubDir;
-
-    public List<String> getExcludeDocFilesSubDir() {
-        return excludeDocFilesSubDir.getValue();
-    }
-
-    public void setExcludeDocFilesSubDir(List<String> excludeDocFilesSubDir) {
-        this.excludeDocFilesSubDir.setValue(excludeDocFilesSubDir);
-    }
-
-    public StandardJavadocDocletOptions excludeDocFilesSubDir(List<String> excludeDocFilesSubDir) {
-        this.excludeDocFilesSubDir.getValue().addAll(excludeDocFilesSubDir);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions excludeDocFilesSubDir(String... excludeDocFilesSubDir) {
-        return excludeDocFilesSubDir(Arrays.asList(excludeDocFilesSubDir));
-    }
-
-    /**
-     * -noqualifier  all  |  packagename1:packagename2:...
-     */
-    private final JavadocOptionFileOption<List<String>> noQualifiers;
-
-    public List<String> getNoQualifiers() {
-        return noQualifiers.getValue();
-    }
-
-    public void setNoQualifiers(List<String> noQualifiers) {
-        this.noQualifiers.setValue(noQualifiers);
-    }
-
-    public StandardJavadocDocletOptions noQualifier(List<String> noQualifiers) {
-        this.noQualifiers.getValue().addAll(noQualifiers);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noQualifiers(String... noQualifiers) {
-        return noQualifier(Arrays.asList(noQualifiers));
-    }
-
-    public final JavadocOptionFileOption<Boolean> noTimestamp;
-
-    public boolean isNoTimestamp() {
-        return noTimestamp.getValue();
-    }
-
-    public void setNoTimestamp(boolean noTimestamp) {
-        this.noTimestamp.setValue(noTimestamp);
-    }
-
-    public StandardJavadocDocletOptions noTimestamp(boolean noTimestamp) {
-        setNoTimestamp(noTimestamp);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noTimestamp() {
-        return noTimestamp(true);
-    }
-
-    /**
-     * -nocomment.
-     */
-    private final JavadocOptionFileOption<Boolean> noComment;
-
-    public boolean isNoComment() {
-        return noComment.getValue();
-    }
-
-    public void setNoComment(boolean noComment) {
-        this.noComment.setValue(noComment);
-    }
-
-    public StandardJavadocDocletOptions noComment(boolean noComment) {
-        setNoComment(noComment);
-        return this;
-    }
-
-    public StandardJavadocDocletOptions noComment() {
-        return noComment(true);
-    }
-}
+/*
+ * 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.external.javadoc;
+
+import org.gradle.external.javadoc.internal.JavadocOptionFile;
+import org.gradle.external.javadoc.internal.GroupsJavadocOptionFileOption;
+import org.gradle.external.javadoc.internal.LinksOfflineJavadocOptionFileOption;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Provides the options for the standard Javadoc doclet.
+ * 
+ * @author Tom Eyckmans
+ */
+public class StandardJavadocDocletOptions extends CoreJavadocOptions implements MinimalJavadocOptions {
+
+    public StandardJavadocDocletOptions() {
+        this(new JavadocOptionFile());
+    }
+
+    public StandardJavadocDocletOptions(JavadocOptionFile javadocOptionFile) {
+        super(javadocOptionFile);
+
+        destinationDirectory = addFileOption("d");
+        use = addBooleanOption("use");
+        version = addBooleanOption("version");
+        author = addBooleanOption("author");
+        splitIndex = addBooleanOption("splitindex");
+        header = addStringOption("header");
+        windowTitle = addStringOption("windowtitle");
+        docTitle = addStringOption("doctitle");
+        footer = addStringOption("footer");
+        bottom = addStringOption("bottom");
+        links = addMultilineStringsOption("link");
+        linksOffline = addOption(new LinksOfflineJavadocOptionFileOption("linkoffline"));
+        linkSource = addBooleanOption("linksource");
+        groups = addOption(new GroupsJavadocOptionFileOption("group"));
+        noDeprecated = addBooleanOption("nodeprecated");
+        noDeprecatedList = addBooleanOption("nodeprecatedlist");
+        noSince = addBooleanOption("nosince");
+        noTree = addBooleanOption("notree");
+        noIndex = addBooleanOption("noindex");
+        noHelp = addBooleanOption("nohelp");
+        noNavBar = addBooleanOption("nonavbar");
+        helpFile = addFileOption("helpfile");
+        stylesheetFile = addFileOption("stylesheetfile");
+        serialWarn = addBooleanOption("serialwarn");
+        charSet = addStringOption("charset");
+        docEncoding = addStringOption("docencoding");
+        keyWords = addBooleanOption("keywords");
+        tags = addMultilineStringsOption("tag");
+        taglets = addMultilineStringsOption("taglet");
+        tagletPath = addPathOption("tagletpath");
+        docFilesSubDirs = addBooleanOption("docfilessubdirs");
+        excludeDocFilesSubDir = addStringsOption("excludedocfilessubdir", ":");
+        noQualifiers = addStringsOption("noqualifier", ":");
+        noTimestamp = addBooleanOption("notimestamp");
+        noComment = addBooleanOption("nocomment");
+    }
+
+    /**
+     * -d  directory
+     * Specifies the destination directory where javadoc saves the generated HTML files. (The "d" means "destination.")
+     * Omitting this option causes the files to be saved to the current directory.
+     * The value directory can be absolute, or relative to the current working directory.
+     * As of 1.4, the destination directory is automatically created when javadoc is run.
+     * For example, the following generates the documentation for the package com.mypackage and
+     * saves the results in the C:/user/doc/ directory:
+     * <p/>
+     * C:> javadoc -d /user/doc com.mypackage
+     */
+    private final JavadocOptionFileOption<File> destinationDirectory;
+
+    public File getDestinationDirectory() {
+        return destinationDirectory.getValue();
+    }
+
+    public void setDestinationDirectory(File directory) {
+        this.destinationDirectory.setValue(directory);
+    }
+
+    public StandardJavadocDocletOptions destinationDirectory(File destinationDirectory) {
+        setDestinationDirectory(destinationDirectory);
+        return this;
+    }
+
+    /**
+     * -use
+     * Includes one "Use" page for each documented class and package. The page describes what packages, classes, methods,
+     * constructors and fields use any API of the given class or package. Given class C,
+     * things that use class C would include subclasses of C, fields declared as C, methods that return C,
+     * and methods and constructors with parameters of type C.
+     * For example, let's look at what might appear on the "Use" page for String.
+     * The getName() method in the java.awt.Font class returns type String. Therefore, getName() uses String,
+     * and you will find that method on the "Use" page for String.
+     * <p/>
+     * Note that this documents only uses of the API, not the implementation.
+     * If a method uses String in its implementation but does not take a string as an argument or return a string,
+     * that is not considered a "use" of String.
+     * <p/>
+     * You can access the generated "Use" page by first going to the class or package,
+     * then clicking on the "Use" link in the navigation bar.
+     */
+    private final JavadocOptionFileOption<Boolean> use;
+
+    public boolean isUse() {
+        return use.getValue();
+    }
+
+    public void setUse(boolean use) {
+        this.use.setValue(use);
+    }
+
+    public StandardJavadocDocletOptions use(boolean use) {
+        setUse(use);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions use() {
+        return use(true);
+    }
+
+    /**
+     * -version
+     * Includes the @version text in the generated docs. This text is omitted by default.
+     * To tell what version of the Javadoc tool you are using, use the -J-version option.
+     */
+    private final JavadocOptionFileOption<Boolean> version;
+
+    public boolean isVersion() {
+        return version.getValue();
+    }
+
+    public void setVersion(boolean version) {
+        this.version.setValue(version);
+    }
+
+    public StandardJavadocDocletOptions version(boolean version) {
+        setVersion(version);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions version() {
+        return version(true);
+    }
+
+    /**
+     * -author
+     * Includes the @author text in the generated docs.
+     */
+    private final JavadocOptionFileOption<Boolean> author;
+
+    public boolean isAuthor() {
+        return author.getValue();
+    }
+
+    public void setAuthor(boolean author) {
+        this.author.setValue(author);
+    }
+
+    public StandardJavadocDocletOptions author(boolean author) {
+        setAuthor(author);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions author() {
+        return author(true);
+    }
+
+    /**
+     * -splitindex
+     * Splits the index file into multiple files, alphabetically, one file per letter,
+     * plus a file for any index entries that start with non-alphabetical characters.
+     */
+    private final JavadocOptionFileOption<Boolean> splitIndex;
+
+    public boolean isSplitIndex() {
+        return splitIndex.getValue();
+    }
+
+    public void setSplitIndex(boolean splitIndex) {
+        this.splitIndex.setValue(splitIndex);
+    }
+
+    public StandardJavadocDocletOptions splitIndex(boolean splitIndex) {
+        setSplitIndex(splitIndex);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions splitIndex() {
+        return splitIndex(true);
+    }
+
+    /**
+     * -windowtitle  title
+     * Specifies the title to be placed in the HTML <title> tag.
+     * This appears in the window title and in any browser bookmarks (favorite places) that someone creates for this page.
+     * This title should not contain any HTML tags, as the browser will not properly interpret them.
+     * Any internal quotation marks within title may have to be escaped. If -windowtitle is omitted,
+     * the Javadoc tool uses the value of -doctitle for this option.
+     * C:> javadoc -windowtitle "Java 2 Platform" com.mypackage
+     */
+    private final JavadocOptionFileOption<String> windowTitle;
+
+    public String getWindowTitle() {
+        return windowTitle.getValue();
+    }
+
+    public void setWindowTitle(String windowTitle) {
+        this.windowTitle.setValue(windowTitle);
+    }
+
+    public StandardJavadocDocletOptions windowTitle(String windowTitle) {
+        setWindowTitle(windowTitle);
+        return this;
+    }
+
+    /**
+     * -header  header
+     * Specifies the header text to be placed at the top of each output file. The header will be placed to the right of
+     * the upper navigation bar. header may contain HTML tags and white space, though if it does, it must be enclosed
+     * in quotes. Any internal quotation marks within header may have to be escaped.
+     * C:> javadoc -header "<b>Java 2 Platform </b><br>v1.4" com.mypackage
+     */
+    private final JavadocOptionFileOption<String> header;
+
+    public String getHeader() {
+        return header.getValue();
+    }
+
+    public void setHeader(String header) {
+        this.header.setValue(header);
+    }
+
+    public StandardJavadocDocletOptions header(String header) {
+        setHeader(header);
+        return this;
+    }
+
+
+    /**
+     * -doctitle  title
+     * Specifies the title to be placed near the top of the overview summary file. The title will be placed as a centered,
+     * level-one heading directly beneath the upper navigation bar. The title may contain html tags and white space,
+     * though if it does, it must be enclosed in quotes. Any internal quotation marks within title may have to be escaped.
+     * C:> javadoc -doctitle "Java<sup><font size=\"-2\">TM</font></sup>" com.mypackage
+     */
+    private final JavadocOptionFileOption<String> docTitle;
+
+    public String getDocTitle() {
+        return docTitle.getValue();
+    }
+
+    public void setDocTitle(String docTitle) {
+        this.docTitle.setValue(docTitle);
+    }
+
+    public StandardJavadocDocletOptions docTitle(String docTitle) {
+        setDocTitle(docTitle);
+        return this;
+    }
+
+    /**
+     * -footer  footer
+     * Specifies the footer text to be placed at the bottom of each output file.
+     * The footer will be placed to the right of the lower navigation bar. footer may contain html tags and white space,
+     * though if it does, it must be enclosed in quotes. Any internal quotation marks within footer may have to be escaped.
+     */
+    private final JavadocOptionFileOption<String> footer;
+
+    public String getFooter() {
+        return footer.getValue();
+    }
+
+    public void setFooter(String footer) {
+        this.footer.setValue(footer);
+    }
+
+    public StandardJavadocDocletOptions footer(String footer) {
+        setFooter(footer);
+        return this;
+    }
+
+    /**
+     * -bottom  text
+     * Specifies the text to be placed at the bottom of each output file.
+     * The text will be placed at the bottom of the page, below the lower navigation bar.
+     * The text may contain HTML tags and white space, though if it does, it must be enclosed in quotes.
+     * Any internal quotation marks within text may have to be escaped.
+     */
+    private final JavadocOptionFileOption<String> bottom;
+
+    public String getBottom() {
+        return bottom.getValue();
+    }
+
+    public void setBottom(String bottom) {
+        this.bottom.setValue(bottom);
+    }
+
+    public StandardJavadocDocletOptions bottom(String bottom) {
+        setBottom(bottom);
+        return this;
+    }
+
+    /**
+     * -link  extdocURL
+     * Creates links to existing javadoc-generated documentation of external referenced classes. It takes one argument:
+     * <p/>
+     * extdocURL is the absolute or relative URL of the directory containing the external javadoc-generated documentation
+     * you want to link to. Examples are shown below.
+     * The package-list file must be found in this directory (otherwise, use -linkoffline).
+     * The Javadoc tool reads the package names from the package-list file and then links to those packages at that URL.
+     * When the Javadoc tool is run, the extdocURL value is copied literally into the <A HREF> links that are created.
+     * Therefore, extdocURL must be the URL to the directory, not to a file.
+     * You can use an absolute link for extdocURL to enable your docs to link to a document on any website,
+     * or can use a relative link to link only to a relative location. If relative,
+     * the value you pass in should be the relative path from the destination directory (specified with -d) to the directory containing the packages being linked to.
+     * <p/>
+     * When specifying an absolute link you normally use an http: link. However,
+     * if you want to link to a file system that has no web server, you can use a file: link -- however,
+     * do this only if everyone wanting to access the generated documentation shares the same file system.
+     */
+    private final JavadocOptionFileOption<List<String>> links;
+
+    public List<String> getLinks() {
+        return links.getValue();
+    }
+
+    public void setLinks(List<String> links) {
+        this.links.setValue(links);
+    }
+
+    public StandardJavadocDocletOptions links(String... links) {
+        this.links.getValue().addAll(Arrays.asList(links));
+        return this;
+    }
+
+    public StandardJavadocDocletOptions linksFile(File linksFile) {
+        return (StandardJavadocDocletOptions) optionFiles(linksFile);
+    }
+
+    /**
+     * -linkoffline  extdocURL  packagelistLoc
+     * This option is a variation of -link; they both create links to javadoc-generated documentation
+     * for external referenced classes. Use the -linkoffline option when linking to a document on the web
+     * when the Javadoc tool itself is "offline" -- that is, it cannot access the document through a web connection.
+     * More specifically, use -linkoffline if the external document's package-list file is not accessible or
+     * does not exist at the extdocURL location but does exist at a different location,
+     * which can be specified by packageListLoc (typically local). Thus, if extdocURL is accessible only on the World Wide Web,
+     * -linkoffline removes the constraint that the Javadoc tool have a web connection when generating the documentation.
+     * <p/>
+     * Another use is as a "hack" to update docs: After you have run javadoc on a full set of packages,
+     * then you can run javadoc again on only a smaller set of changed packages,
+     * so that the updated files can be inserted back into the original set. Examples are given below.
+     * <p/>
+     * The -linkoffline option takes two arguments -- the first for the string to be embedded in the <a href> links,
+     * the second telling it where to find package-list:
+     * <p/>
+     * extdocURL is the absolute or relative URL of the directory containing the external javadoc-generated documentation you want to link to.
+     * If relative, the value should be the relative path from the destination directory (specified with -d) to the root of the packages being linked to.
+     * For more details, see extdocURL in the -link option.
+     * packagelistLoc is the path or URL to the directory containing the package-list file for the external documentation.
+     * This can be a URL (http: or file:) or file path, and can be absolute or relative. If relative,
+     * make it relative to the current directory from where javadoc was run. Do not include the package-list filename.
+     */
+    private final JavadocOptionFileOption<List<JavadocOfflineLink>> linksOffline;
+
+    public List<JavadocOfflineLink> getLinksOffline() {
+        return linksOffline.getValue();
+    }
+
+    public void setLinksOffline(List<JavadocOfflineLink> linksOffline) {
+        this.linksOffline.setValue(linksOffline);
+    }
+
+    public StandardJavadocDocletOptions linksOffline(String extDocUrl, String packageListLoc) {
+        this.linksOffline.getValue().add(new JavadocOfflineLink(extDocUrl, packageListLoc));
+        return this;
+    }
+
+    public StandardJavadocDocletOptions linksOfflineFile(File linksOfflineFile) {
+        return (StandardJavadocDocletOptions) optionFiles(linksOfflineFile);
+    }
+
+    /**
+     * -linksource
+     * Creates an HTML version of each source file (with line numbers) and adds links to them from the standard HTML documentation. Links are created for classes, interfaces, constructors, methods and fields whose declarations are in a source file. Otherwise, links are not created, such as for default constructors and generated classes.
+     * This option exposes all private implementation details in the included source files, including private classes, private fields, and the bodies of private methods, regardless of the -public, -package, -protected and -private options. Unless you also use the -private option, not all private classes or interfaces will necessarily be accessible via links.
+     * <p/>
+     * Each link appears on the name of the identifier in its declaration. For example, the link to the source code of the Button class would be on the word "Button":
+     * <p/>
+     * public class Button
+     * extends Component
+     * implements Accessible
+     * and the link to the source code of the getLabel() method in the Button class would be on the word "getLabel":
+     * public String getLabel()
+     */
+    private final JavadocOptionFileOption<Boolean> linkSource;
+
+    public boolean isLinkSource() {
+        return linkSource.getValue();
+    }
+
+    public void setLinkSource(boolean linkSource) {
+        this.linkSource.setValue(linkSource);
+    }
+
+    public StandardJavadocDocletOptions linkSource(boolean linkSource) {
+        setLinkSource(linkSource);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions linkSource() {
+        return linkSource(true);
+    }
+
+    /**
+     * -group  groupheading  packagepattern:packagepattern:...
+     * Separates packages on the overview page into whatever groups you specify, one group per table.
+     * You specify each group with a different -group option.
+     * The groups appear on the page in the order specified on the command line; packages are alphabetized within a group.
+     * For a given -group option, the packages matching the list of packagepattern expressions appear in a table
+     * with the heading groupheading.
+     * groupheading can be any text, and can include white space. This text is placed in the table heading for the group.
+     * packagepattern can be any package name, or can be the start of any package name followed by an asterisk (*).
+     * The asterisk is a wildcard meaning "match any characters". This is the only wildcard allowed.
+     * Multiple patterns can be included in a group by separating them with colons (:).
+     * NOTE: If using an asterisk in a pattern or pattern list, the pattern list must be inside quotes,
+     * such as "java.lang*:java.util"
+     * If you do not supply any -group option, all packages are placed in one group with the heading "Packages".
+     * If the all groups do not include all documented packages,
+     * any leftover packages appear in a separate group with the heading "Other Packages".
+     * <p/>
+     * For example, the following option separates the four documented packages into core,
+     * extension and other packages. Notice the trailing "dot" does not appear in "java.lang*" -- including the dot,
+     * such as "java.lang.*" would omit the java.lang package.
+     * <p/>
+     * C:> javadoc -group "Core Packages" "java.lang*:java.util"
+     * -group "Extension Packages" "javax.*"
+     * java.lang java.lang.reflect java.util javax.servlet java.new
+     * This results in the groupings:
+     * Core Packages
+     * java.lang
+     * java.lang.reflect
+     * java.util
+     * Extension Packages
+     * javax.servlet
+     * Other Packages
+     * java.new
+     */
+    private final JavadocOptionFileOption<Map<String, List<String>>> groups;
+
+    public Map<String, List<String>> getGroups() {
+        return groups.getValue();
+    }
+
+    public void setGroups(Map<String, List<String>> groups) {
+        this.groups.setValue(groups);
+    }
+
+    public StandardJavadocDocletOptions group(Map<String, List<String>> groups) {
+        setGroups(groups);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions group(String groupName, List<String> packagePatterns) {
+        this.groups.getValue().put(groupName, packagePatterns);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions group(String groupName, String... packagePatterns) {
+        return group(groupName, Arrays.asList(packagePatterns));
+    }
+
+    public StandardJavadocDocletOptions groupsFile(File groupsFile) {
+        return (StandardJavadocDocletOptions) optionFiles(groupsFile);
+    }
+
+    /**
+     * -nodeprecated
+     * Prevents the generation of any deprecated API at all in the documentation.
+     * This does what -nodeprecatedlist does, plus it does not generate any deprecated API throughout the rest of the documentation.
+     * This is useful when writing code and you don't want to be distracted by the deprecated code.
+     */
+    private final JavadocOptionFileOption<Boolean> noDeprecated;
+
+    public boolean isNoDeprecated() {
+        return noDeprecated.getValue();
+    }
+
+    public void setNoDeprecated(boolean noDeprecated) {
+        this.noDeprecated.setValue(noDeprecated);
+    }
+
+    public StandardJavadocDocletOptions noDeprecated(boolean nodeprecated) {
+        setNoDeprecated(nodeprecated);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noDeprecated() {
+        return noDeprecated(true);
+    }
+
+    /**
+     * -nodeprecatedlist
+     * Prevents the generation of the file containing the list of deprecated APIs (deprecated-list.html) and
+     * the link in the navigation bar to that page.
+     * (However, javadoc continues to generate the deprecated API throughout the rest of the document.)
+     * This is useful if your source code contains no deprecated API, and you want to make the navigation bar cleaner.
+     */
+    private final JavadocOptionFileOption<Boolean> noDeprecatedList;
+
+    public boolean isNoDeprecatedList() {
+        return noDeprecatedList.getValue();
+    }
+
+    public void setNoDeprecatedList(boolean noDeprecatedList) {
+        this.noDeprecatedList.setValue(noDeprecatedList);
+    }
+
+    public StandardJavadocDocletOptions noDeprecatedList(boolean noDeprecatedList) {
+        setNoDeprecatedList(noDeprecatedList);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noDeprecatedList() {
+        return noDeprecatedList(true);
+    }
+
+    /**
+     * -nosince
+     * Omits from the generated docs the "Since" sections associated with the @since tags.
+     */
+    private final JavadocOptionFileOption<Boolean> noSince;
+
+    public boolean isNoSince() {
+        return noSince.getValue();
+    }
+
+    public void setNoSince(boolean noSince) {
+        this.noSince.setValue(noSince);
+    }
+
+    public StandardJavadocDocletOptions noSince(boolean noSince) {
+        setNoSince(noSince);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noSince() {
+        return noSince(true);
+    }
+
+    /**
+     * -notree
+     * Omits the class/interface hierarchy pages from the generated docs.
+     * These are the pages you reach using the "Tree" button in the navigation bar.
+     * The hierarchy is produced by default.
+     */
+    private final JavadocOptionFileOption<Boolean> noTree;
+
+    public boolean isNoTree() {
+        return noTree.getValue();
+    }
+
+    public void setNoTree(boolean noTree) {
+        this.noTree.setValue(noTree);
+    }
+
+    public StandardJavadocDocletOptions noTree(boolean noTree) {
+        setNoTree(noTree);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noTree() {
+        return noTree(true);
+    }
+
+    /**
+     * -noindex
+     * Omits the index from the generated docs. The index is produced by default.
+     */
+    private final JavadocOptionFileOption<Boolean> noIndex;
+
+    public boolean isNoIndex() {
+        return noIndex.getValue();
+    }
+
+    public void setNoIndex(boolean noIndex) {
+        this.noIndex.setValue(noIndex);
+    }
+
+    public StandardJavadocDocletOptions noIndex(boolean noIndex) {
+        setNoIndex(noIndex);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noIndex() {
+        return noIndex(true);
+    }
+
+    /**
+     * -nohelp
+     * Omits the HELP link in the navigation bars at the top and bottom of each page of output.
+     */
+    private final JavadocOptionFileOption<Boolean> noHelp;
+
+    public boolean isNoHelp() {
+        return noHelp.getValue();
+    }
+
+    public void setNoHelp(boolean noHelp) {
+        this.noHelp.setValue(noHelp);
+    }
+
+    public StandardJavadocDocletOptions noHelp(boolean noHelp) {
+        setNoHelp(noHelp);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noHelp() {
+        return noHelp(true);
+    }
+
+    /**
+     * -nonavbar
+     * Prevents the generation of the navigation bar, header and footer,
+     * otherwise found at the top and bottom of the generated pages. Has no affect on the "bottom" option.
+     * The -nonavbar option is useful when you are interested only in the content and have no need for navigation,
+     * such as converting the files to PostScript or PDF for print only.
+     */
+    private final JavadocOptionFileOption<Boolean> noNavBar;
+
+    public boolean isNoNavBar() {
+        return noNavBar.getValue();
+    }
+
+    public void setNoNavBar(boolean noNavBar) {
+        this.noNavBar.setValue(noNavBar);
+    }
+
+    public StandardJavadocDocletOptions noNavBar(boolean noNavBar) {
+        setNoNavBar(noNavBar);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noNavBar() {
+        return noNavBar(true);
+    }
+
+    /**
+     * -helpfile  path/filename
+     * Specifies the path of an alternate help file path\filename that the HELP link in the top and bottom navigation bars link to. Without this option, the Javadoc tool automatically creates a help file help-doc.html that is hard-coded in the Javadoc tool. This option enables you to override this default. The filename can be any name and is not restricted to help-doc.html -- the Javadoc tool will adjust the links in the navigation bar accordingly. For example:
+     * <p/>
+     * C:> javadoc -helpfile C:/user/myhelp.html java.awt
+     */
+    private final JavadocOptionFileOption<File> helpFile;
+
+    public File getHelpFile() {
+        return helpFile.getValue();
+    }
+
+    public void setHelpFile(File helpFile) {
+        this.helpFile.setValue(helpFile);
+    }
+
+    public StandardJavadocDocletOptions helpFile(File helpFile) {
+        setHelpFile(helpFile);
+        return this;
+    }
+
+    /**
+     * -stylesheetfile  path\filename
+     * Specifies the path of an alternate HTML stylesheet file. Without this option, the Javadoc tool automatically creates a stylesheet file stylesheet.css that is hard-coded in the Javadoc tool. This option enables you to override this default. The filename can be any name and is not restricted to stylesheet.css. For example:
+     * <p/>
+     * C:> javadoc -stylesheetfile C:/user/mystylesheet.css com.mypackage
+     */
+    private final JavadocOptionFileOption<File> stylesheetFile;
+
+    public File getStylesheetFile() {
+        return stylesheetFile.getValue();
+    }
+
+    public void setStylesheetFile(File stylesheetFile) {
+        this.stylesheetFile.setValue(stylesheetFile);
+    }
+
+    public StandardJavadocDocletOptions stylesheetFile(File stylesheetFile) {
+        setStylesheetFile(stylesheetFile);
+        return this;
+    }
+
+    /**
+     * -serialwarn
+     * Generates compile-time warnings for missing @serial tags.
+     * By default, Javadoc 1.2.2 (and later versions) generates no serial warnings.
+     * (This is a reversal from earlier versions.) Use this option to display the serial warnings,
+     * which helps to properly document default serializable fields and writeExternal methods.
+     */
+    private final JavadocOptionFileOption<Boolean> serialWarn;
+
+    public boolean isSerialWarn() {
+        return serialWarn.getValue();
+    }
+
+    public void setSerialWarn(boolean serialWarn) {
+        this.serialWarn.setValue(serialWarn);
+    }
+
+    public StandardJavadocDocletOptions serialWarn(boolean serialWarn) {
+        setSerialWarn(serialWarn);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions serialWarn() {
+        return serialWarn(true);
+    }
+
+    /**
+     * -charset  name
+     * Specifies the HTML character set for this document. The name should be a preferred MIME name as given in the IANA Registry. For example:
+     * <p/>
+     * C:> javadoc -charset "iso-8859-1" mypackage
+     * <p/>
+     * would insert the following line in the head of every generated page:
+     * <p/>
+     * <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+     * <p/>
+     * This META tag is described in the HTML standard. (4197265 and 4137321)
+     * <p/>
+     * Also see -encoding and -docencoding.
+     */
+    private final JavadocOptionFileOption<String> charSet;
+
+    public String getCharSet() {
+        return charSet.getValue();
+    }
+
+    public void setCharSet(String charSet) {
+        this.charSet.setValue(charSet);
+    }
+
+    public StandardJavadocDocletOptions charSet(String charSet) {
+        setCharSet(charSet);
+        return this;
+    }
+
+    /**
+     * -docencoding  name
+     * Specifies the encoding of the generated HTML files. The name should be a preferred MIME name as given in the IANA Registry. If you omit this option but use -encoding, then the encoding of the generated HTML files is determined by -encoding. Example:
+     * <p/>
+     * % javadoc -docencoding "ISO-8859-1" mypackage
+     * <p/>
+     * Also see -encoding and -charset.
+     */
+    private final JavadocOptionFileOption<String> docEncoding;
+
+    public String getDocEncoding() {
+        return docEncoding.getValue();
+    }
+
+    public void setDocEncoding(String docEncoding) {
+        this.docEncoding.setValue(docEncoding);
+    }
+
+    public StandardJavadocDocletOptions docEncoding(String docEncoding) {
+        setDocEncoding(docEncoding);
+        return this;
+    }
+
+    /**
+     * -keywords.
+     */
+    private final JavadocOptionFileOption<Boolean> keyWords;
+
+    public boolean isKeyWords() {
+        return keyWords.getValue();
+    }
+
+    public void setKeyWords(boolean keyWords) {
+        this.keyWords.setValue(keyWords);
+    }
+
+    public StandardJavadocDocletOptions keyWords(boolean keyWords) {
+        setKeyWords(keyWords);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions keyWords() {
+        return keyWords(true);
+    }
+
+    /**
+     * -tag  tagname:Xaoptcmf:"taghead".
+     */
+    private final JavadocOptionFileOption<List<String>> tags;
+
+    public List<String> getTags() {
+        return tags.getValue();
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags.setValue(tags);
+    }
+
+    public StandardJavadocDocletOptions tags(List<String> tags) {
+        this.tags.getValue().addAll(tags);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions tags(String... tags) {
+        return tags(Arrays.asList(tags));
+    }
+
+    public StandardJavadocDocletOptions tagsFile(File tagsFile) {
+        return (StandardJavadocDocletOptions) optionFiles(tagsFile);
+    }
+
+    /**
+     * -taglet  class.
+     */
+    private final JavadocOptionFileOption<List<String>> taglets;
+
+    public List<String> getTaglets() {
+        return taglets.getValue();
+    }
+
+    public void setTaglets(List<String> taglets) {
+        this.taglets.setValue(taglets);
+    }
+
+    public StandardJavadocDocletOptions taglets(List<String> taglets) {
+        this.taglets.getValue().addAll(taglets);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions taglets(String... taglets) {
+        return taglets(Arrays.asList(taglets));
+    }
+
+    /**
+     * -tagletpath  tagletpathlist.
+     */
+    private final JavadocOptionFileOption<List<File>> tagletPath;
+
+    public List<File> getTagletPath() {
+        return tagletPath.getValue();
+    }
+
+    public void setTagletPath(List<File> tagletPath) {
+        this.tagletPath.setValue(tagletPath);
+    }
+
+    public StandardJavadocDocletOptions tagletPath(List<File> tagletPath) {
+        this.tagletPath.getValue().addAll(tagletPath);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions tagletPath(File... tagletPath) {
+        return tagletPath(Arrays.asList(tagletPath));
+    }
+
+    /**
+     * -docfilessubdirs.
+     */
+    private final JavadocOptionFileOption<Boolean> docFilesSubDirs;
+
+    public boolean isDocFilesSubDirs() {
+        return docFilesSubDirs.getValue();
+    }
+
+    public void setDocFilesSubDirs(boolean docFilesSubDirs) {
+        this.docFilesSubDirs.setValue(docFilesSubDirs);
+    }
+
+    public StandardJavadocDocletOptions docFilesSubDirs(boolean docFilesSubDirs) {
+        setDocFilesSubDirs(docFilesSubDirs);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions docFilesSubDirs() {
+        return docFilesSubDirs(true);
+    }
+
+    /**
+     * -excludedocfilessubdir  name1:name2...
+     */
+    private final JavadocOptionFileOption<List<String>> excludeDocFilesSubDir;
+
+    public List<String> getExcludeDocFilesSubDir() {
+        return excludeDocFilesSubDir.getValue();
+    }
+
+    public void setExcludeDocFilesSubDir(List<String> excludeDocFilesSubDir) {
+        this.excludeDocFilesSubDir.setValue(excludeDocFilesSubDir);
+    }
+
+    public StandardJavadocDocletOptions excludeDocFilesSubDir(List<String> excludeDocFilesSubDir) {
+        this.excludeDocFilesSubDir.getValue().addAll(excludeDocFilesSubDir);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions excludeDocFilesSubDir(String... excludeDocFilesSubDir) {
+        return excludeDocFilesSubDir(Arrays.asList(excludeDocFilesSubDir));
+    }
+
+    /**
+     * -noqualifier  all  |  packagename1:packagename2:...
+     */
+    private final JavadocOptionFileOption<List<String>> noQualifiers;
+
+    public List<String> getNoQualifiers() {
+        return noQualifiers.getValue();
+    }
+
+    public void setNoQualifiers(List<String> noQualifiers) {
+        this.noQualifiers.setValue(noQualifiers);
+    }
+
+    public StandardJavadocDocletOptions noQualifier(List<String> noQualifiers) {
+        this.noQualifiers.getValue().addAll(noQualifiers);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noQualifiers(String... noQualifiers) {
+        return noQualifier(Arrays.asList(noQualifiers));
+    }
+
+    public final JavadocOptionFileOption<Boolean> noTimestamp;
+
+    public boolean isNoTimestamp() {
+        return noTimestamp.getValue();
+    }
+
+    public void setNoTimestamp(boolean noTimestamp) {
+        this.noTimestamp.setValue(noTimestamp);
+    }
+
+    public StandardJavadocDocletOptions noTimestamp(boolean noTimestamp) {
+        setNoTimestamp(noTimestamp);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noTimestamp() {
+        return noTimestamp(true);
+    }
+
+    /**
+     * -nocomment.
+     */
+    private final JavadocOptionFileOption<Boolean> noComment;
+
+    public boolean isNoComment() {
+        return noComment.getValue();
+    }
+
+    public void setNoComment(boolean noComment) {
+        this.noComment.setValue(noComment);
+    }
+
+    public StandardJavadocDocletOptions noComment(boolean noComment) {
+        setNoComment(noComment);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noComment() {
+        return noComment(true);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java
index 2c0a2b9..cdba757 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilder.java
@@ -18,10 +18,10 @@ package org.gradle.external.javadoc.internal;
 
 import org.gradle.api.GradleException;
 import org.gradle.external.javadoc.MinimalJavadocOptions;
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.internal.DefaultExecAction;
 import org.gradle.process.internal.ExecAction;
 import org.gradle.util.GUtil;
-import org.gradle.util.Jvm;
 
 import java.io.File;
 import java.io.IOException;
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
new file mode 100644
index 0000000..bd7005a
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
@@ -0,0 +1,164 @@
+#!/bin/bash
+
+##############################################################################
+##
+##  ${applicationName} start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="${applicationName}"
+APP_BASE_NAME=`basename "\$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "\$*"
+}
+
+die ( ) {
+    echo
+    echo "\$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if \$cygwin ; then
+    [ -n "\$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "\$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: \$0 may be a link
+PRG="\$0"
+# Need this for relative symlinks.
+while [ -h "\$PRG" ] ; do
+    ls=`ls -ld "\$PRG"`
+    link=`expr "\$ls" : '.*-> \\(.*\\)\$'`
+    if expr "\$link" : '/.*' > /dev/null; then
+        PRG="\$link"
+    else
+        PRG=`dirname "\$PRG"`"/\$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"\$PRG\"`/${appHomeRelativePath}"
+APP_HOME="`pwd -P`"
+cd "\$SAVED"
+
+CLASSPATH=$classpath
+
+# Determine the Java command to use to start the JVM.
+if [ -n "\$JAVA_HOME" ] ; then
+    if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="\$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="\$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "\$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "\$cygwin" = "false" -a "\$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ \$? -eq 0 ] ; then
+        if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then
+            MAX_FD="\$MAX_FD_LIMIT"
+        fi
+        ulimit -n \$MAX_FD
+        if [ \$? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: \$MAX_FD"
+        fi
+    else
+        warn "Could not query businessSystem maximum file descriptor limit: \$MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if \$darwin; then
+    GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if \$cygwin ; then
+    APP_HOME=`cygpath --path --mixed "\$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in \$ROOTDIRSRAW ; do
+        ROOTDIRS="\$ROOTDIRS\$SEP\$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^(\$ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "\$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "\$@" ; do
+        CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -`
+        CHECK2=`echo "\$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"`
+        else
+            eval `echo args\$i`="\"\$arg\""
+        fi
+        i=\$((i+1))
+    done
+    case \$i in
+        (0) set -- ;;
+        (1) set -- "\$args0" ;;
+        (2) set -- "\$args0" "\$args1" ;;
+        (3) set -- "\$args0" "\$args1" "\$args2" ;;
+        (4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;;
+        (5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;;
+        (6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;;
+        (7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;;
+        (8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;;
+        (9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And ${optsEnvironmentVar} values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("\$@")
+}
+eval splitJvmOpts \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}
+<% if ( appNameSystemProperty ) { %>JVM_OPTS[\${#JVM_OPTS[*]}]="-D${appNameSystemProperty}=\$APP_BASE_NAME"<% } %>
+
+exec "\$JAVACMD" "\${JVM_OPTS[@]}" -classpath "\$CLASSPATH" ${mainClassName} "\$@"
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
new file mode 100644
index 0000000..7e034c0
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
@@ -0,0 +1,91 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem
+ at rem  ${applicationName} startup script for Windows
+ at rem
+ at rem ##########################################################################
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+ at rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.\
+
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%${appHomeRelativePath}
+
+ at rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+ at rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%\$
+
+:execute
+ at rem Setup the command line
+
+set CLASSPATH=$classpath
+
+ at rem Execute ${applicationName}
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -classpath "%CLASSPATH%" ${mainClassName} %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%${exitEnvironmentVar}%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css b/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css
index 90294dc..2440a1f 100644
--- a/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css
+++ b/subprojects/plugins/src/main/resources/org/gradle/api/internal/tasks/testing/junit/report/style.css
@@ -1,31 +1,4 @@
 
-body {
-    margin: 0;
-    padding: 0;
-    font-family: sans-serif;
-    font-size: 12pt;
-}
-
-body, a, a:visited {
-    color: #303030;
-}
-
-ul {
-    margin-left: 0;
-}
-
-#content {
-    padding-left: 50px;
-    padding-right: 50px;
-    padding-top: 30px;
-    padding-bottom: 30px;
-}
-
-#content h1 {
-    font-size: 160%;
-    margin-bottom: 10px;
-}
-
 #summary {
     margin-top: 30px;
     margin-bottom: 40px;
@@ -39,15 +12,6 @@ ul {
     vertical-align: top;
 }
 
-#footer {
-    margin-top: 100px;
-    font-size: 80%;
-}
-
-#footer, #footer a {
-    color: #a0a0a0;
-}
-
 .breadcrumbs, .breadcrumbs a {
     color: #606060;
 }
@@ -107,10 +71,6 @@ div.failures, #successRate.failures {
     border-color: #b60808;
 }
 
-h2 {
-    font-size: 120%;
-}
-
 ul.linkList {
     padding-left: 0;
 }
@@ -119,94 +79,3 @@ ul.linkList li {
     list-style: none;
     margin-bottom: 5px;
 }
-
-ul.tabLinks {
-    padding-left: 0;
-    padding-top: 10px;
-    padding-bottom: 10px;
-    overflow: hidden;
-}
-
-ul.tabLinks li {
-    float: left;
-    height: 100%;
-    list-style: none;
-    padding-left: 10px;
-    padding-right: 10px;
-    padding-top: 5px;
-    padding-bottom: 5px;
-    margin-bottom: 0;
-    -moz-border-radius: 7px;
-    border-radius: 7px;
-    margin-right: 25px;
-    border: solid 1px #d4d4d4;
-    background-color: #f0f0f0;
-    behavior: url(css3-pie-1.0beta3.htc);
-}
-
-ul.tabLinks li:hover {
-    background-color: #fafafa;
-}
-
-ul.tabLinks li.selected {
-    background-color: #c5f0f5;
-    border-color: #c5f0f5;
-}
-
-ul.tabLinks a {
-    font-size: 120%;
-    display: block;
-    outline: none;
-    text-decoration: none;
-    margin: 0;
-    padding: 0;
-}
-
-ul.tabLinks li:hover a {
-    text-decoration: underline;
-}
-
-ul.tabLinks li h2 {
-    margin: 0;
-    padding: 0;
-}
-
-div.tab {
-}
-
-div.selected {
-    display: block;
-}
-
-div.deselected {
-    display: none;
-}
-
-div.tab table {
-    width: 100%;
-    border-collapse: collapse;
-}
-
-div.tab th, div.tab table {
-    border-bottom: solid #d0d0d0 1px;
-}
-
-div.tab th {
-    text-align: left;
-}
-
-div.tab td {
-    padding-top: 5px;
-    padding-bottom: 5px;
-}
-
-div.tab pre {
-    font-size: 11pt;
-    padding-top: 10px;
-    padding-bottom: 10px;
-    padding-left: 10px;
-    padding-right: 10px;
-    background-color: #f7f7f7;
-    border: solid 1px #d0d0d0;
-    overflow-x: auto;
-}
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/tasks/application/unixStartScript.txt b/subprojects/plugins/src/main/resources/org/gradle/api/tasks/application/unixStartScript.txt
deleted file mode 100644
index 667cd5f..0000000
--- a/subprojects/plugins/src/main/resources/org/gradle/api/tasks/application/unixStartScript.txt
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/bin/bash
-
-##############################################################################
-##
-##  ${applicationName} start up script for UN*X
-##
-##############################################################################
-
-# Uncomment those lines to set JVM options. ${optsEnvironmentVar} and JAVA_OPTS can be used together.
-# ${optsEnvironmentVar}="\$${optsEnvironmentVar} -Xmx512m"
-# JAVA_OPTS="\$JAVA_OPTS -Xmx512m"
-
-APP_NAME=${applicationName}
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "\$*"
-}
-
-die ( ) {
-    echo
-    echo "\$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# Attempt to set JAVA_HOME if it's not already set.
-if [ -z "\$JAVA_HOME" ] ; then
-    if \$darwin ; then
-        [ -z "\$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
-        [ -z "\$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
-    else
-        javaExecutable="`which javac`"
-        [ -z "\$javaExecutable" -o "`expr \"\$javaExecutable\" : '\\([^ ]*\\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
-        # readlink(1) is not available as standard on Solaris 10.
-        readLink=`which readlink`
-        [ `expr "\$readLink" : '\\([^ ]*\\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
-        javaExecutable="`readlink -f \"\$javaExecutable\"`"
-        javaHome="`dirname \"\$javaExecutable\"`"
-        javaHome=`expr "\$javaHome" : '\\(.*\\)/bin'`
-        export JAVA_HOME="\$javaHome"
-    fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if \$cygwin ; then
-    [ -n "\$JAVACMD" ] && JAVACMD=`cygpath --unix "\$JAVACMD"`
-    [ -n "\$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "\$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: \$0 may be a link
-PRG="\$0"
-# Need this for relative symlinks.
-while [ -h "\$PRG" ] ; do
-    ls=`ls -ld "\$PRG"`
-    link=`expr "\$ls" : '.*-> \\(.*\\)\$'`
-    if expr "\$link" : '/.*' > /dev/null; then
-        PRG="\$link"
-    else
-        PRG=`dirname "\$PRG"`"/\$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"\$PRG\"`/.."
-APP_HOME="`pwd -P`"
-cd "\$SAVED"
-
-CLASSPATH=$classpath
-
-# Determine the Java command to use to start the JVM.
-if [ -z "\$JAVACMD" ] ; then
-    if [ -n "\$JAVA_HOME" ] ; then
-        if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then
-            # IBM's JDK on AIX uses strange locations for the executables
-            JAVACMD="\$JAVA_HOME/jre/sh/java"
-        else
-            JAVACMD="\$JAVA_HOME/bin/java"
-        fi
-    else
-        JAVACMD="java"
-    fi
-fi
-if [ ! -x "\$JAVACMD" ] ; then
-    die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-if [ -z "\$JAVA_HOME" ] ; then
-    warn "JAVA_HOME environment variable is not set"
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "\$cygwin" = "false" -a "\$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ \$? -eq 0 ] ; then
-        if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then
-            MAX_FD="\$MAX_FD_LIMIT"
-        fi
-        ulimit -n \$MAX_FD
-        if [ \$? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: \$MAX_FD"
-        fi
-    else
-        warn "Could not query businessSystem maximum file descriptor limit: \$MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add APP_NAME to the JAVA_OPTS as -Xdock:name
-if \$darwin; then
-    JAVA_OPTS="\$JAVA_OPTS -Xdock:name=APP_NAME"
-# we may also want to set -Xdock:image
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if \$cygwin ; then
-    APP_HOME=`cygpath --path --mixed "\$APP_HOME"`
-    JAVA_HOME=`cygpath --path --mixed "\$JAVA_HOME"`
-    CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in \$ROOTDIRSRAW ; do
-        ROOTDIRS="\$ROOTDIRS\$SEP\$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^(\$ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "\$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "\$@" ; do
-        CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -`
-        CHECK2=`echo "\$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"`
-        else
-            eval `echo args\$i`="\"\$arg\""
-        fi
-        i=\$((i+1))
-    done
-    case \$i in
-        (0) set -- ;;
-        (1) set -- "\$args0" ;;
-        (2) set -- "\$args0" "\$args1" ;;
-        (3) set -- "\$args0" "\$args1" "\$args2" ;;
-        (4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;;
-        (5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;;
-        (6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;;
-        (7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;;
-        (8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;;
-        (9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;;
-    esac
-fi
-
-exec "\$JAVACMD" \$JAVA_OPTS \$${optsEnvironmentVar} -classpath "\$CLASSPATH" ${mainClassName} "\$@"
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/tasks/application/windowsStartScript.txt b/subprojects/plugins/src/main/resources/org/gradle/api/tasks/application/windowsStartScript.txt
deleted file mode 100644
index 895f959..0000000
--- a/subprojects/plugins/src/main/resources/org/gradle/api/tasks/application/windowsStartScript.txt
+++ /dev/null
@@ -1,82 +0,0 @@
- at if "%DEBUG%" == "" @echo off
- at rem ##########################################################################
- at rem
- at rem  ${applicationName} startup script for Windows
- at rem
- at rem ##########################################################################
-
- at rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
- at rem Uncomment those lines to set JVM options. ${optsEnvironmentVar} and JAVA_OPTS can be used together.
- at rem set ${optsEnvironmentVar}=%${optsEnvironmentVar}% -Xmx512m
- at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.\
-
-set APP_HOME=%DIRNAME%..
-
- at rem Find java.exe
-set JAVA_EXE=java.exe
-if not defined JAVA_HOME goto init
-
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-echo.
-goto fail
-
-:init
- at rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
- at rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
- at rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%\$
-
-:execute
- at rem Setup the command line
-
-
-set CLASSPATH=$classpath
-
- at rem Execute ${applicationName}
-"%JAVA_EXE%" %JAVA_OPTS% %${optsEnvironmentVar}% -classpath "%CLASSPATH%" ${mainClassName} %CMD_LINE_ARGS%
-
-:end
- at rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
-
-rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%${exitEnvironmentVar}%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/DefaultArtifactPublicationSetTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/DefaultArtifactPublicationSetTest.groovy
new file mode 100644
index 0000000..5fbe7a7
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/DefaultArtifactPublicationSetTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.artifacts.PublishArtifactSet
+import org.gradle.api.artifacts.PublishArtifact
+
+class DefaultArtifactPublicationSetTest extends Specification {
+    final PublishArtifactSet publications = Mock()
+    final DefaultArtifactPublicationSet publication = new DefaultArtifactPublicationSet(publications)
+
+    def "adds jar artifact to publication"() {
+        def artifact = artifact("jar")
+
+        when:
+        publication.addCandidate(artifact)
+
+        then:
+        1 * publications.add(artifact)
+    }
+
+    def "adds war artifact to publication"() {
+        def artifact = artifact("war")
+
+        when:
+        publication.addCandidate(artifact)
+
+        then:
+        1 * publications.add(artifact)
+    }
+
+    def "adds ear artifact to publication"() {
+        def artifact = artifact("ear")
+
+        when:
+        publication.addCandidate(artifact)
+
+        then:
+        1 * publications.add(artifact)
+    }
+
+    def "prefers war over jar artifact"() {
+        def jar = artifact("jar")
+        def war = artifact("war")
+
+        given:
+        publication.addCandidate(jar)
+
+        when:
+        publication.addCandidate(war)
+
+        then:
+        1 * publications.remove(jar)
+        1 * publications.add(war)
+
+        when:
+        publication.addCandidate(jar)
+
+        then:
+        0 * publications._
+    }
+
+    def "prefers ear over jar artifact"() {
+        def jar = artifact("jar")
+        def ear = artifact("ear")
+
+        given:
+        publication.addCandidate(jar)
+
+        when:
+        publication.addCandidate(ear)
+
+        then:
+        1 * publications.remove(jar)
+        1 * publications.add(ear)
+
+        when:
+        publication.addCandidate(jar)
+
+        then:
+        0 * publications._
+    }
+
+    def "prefers ear over war artifact"() {
+        def war = artifact("war")
+        def ear = artifact("ear")
+
+        given:
+        publication.addCandidate(war)
+
+        when:
+        publication.addCandidate(ear)
+
+        then:
+        1 * publications.remove(war)
+        1 * publications.add(ear)
+
+        when:
+        publication.addCandidate(war)
+
+        then:
+        0 * publications._
+    }
+
+    def "adds other types of artifacts"() {
+        def jar = artifact("jar")
+        def exe = artifact("exe")
+
+        given:
+        publication.addCandidate(jar)
+
+        when:
+        publication.addCandidate(exe)
+
+        then:
+        1 * publications.add(exe)
+        0 * publications._
+    }
+
+    def PublishArtifact artifact(String type) {
+        PublishArtifact artifact = Mock()
+        _ * artifact.toString() >> type
+        _ * artifact.type >> type
+        return artifact
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy
new file mode 100644
index 0000000..708f46f
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/plugins/StartScriptGeneratorTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * 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.plugins
+
+import spock.lang.Specification
+import org.gradle.util.WrapUtil
+import org.gradle.util.TextUtil
+
+class StartScriptGeneratorTest extends Specification {
+
+    def generator = new StartScriptGenerator();
+
+    def "classpath for unix script uses slashes as path separator"() {
+        given:
+        generator.applicationName = "TestApp"
+        generator.setClasspath(WrapUtil.toList("path\\to\\Jar.jar"))
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.contains("CLASSPATH=\$APP_HOME/path/to/Jar.jar")
+    }
+
+
+    def "unix script uses unix line separator"() {
+        given:
+        generator.applicationName = "TestApp"
+        generator.scriptRelPath = "bin"
+        when:
+        String unixScriptContent = generator.generateUnixScriptContent()
+        then:
+        unixScriptContent.split(TextUtil.windowsLineSeparator).length == 1
+        unixScriptContent.split(TextUtil.unixLineSeparator).length == 164
+    }
+
+    def "classpath for windows script uses backslash as path separator and windows line separator"() {
+        given:
+        generator.applicationName = "TestApp"
+        generator.setClasspath(WrapUtil.toList("path/to/Jar.jar"))
+        generator.scriptRelPath = "bin"
+        when:
+        String windowsScriptContent = generator.generateWindowsScriptContent()
+        then:
+        windowsScriptContent.contains("set CLASSPATH=%APP_HOME%\\path\\to\\Jar.jar")
+        windowsScriptContent.split(TextUtil.windowsLineSeparator).length == 90
+    }
+
+    def "windows script uses windows line separator"() {
+        given:
+        generator.applicationName = "TestApp"
+        generator.scriptRelPath = "bin"
+        when:
+        String windowsScriptContent = generator.generateWindowsScriptContent()
+        then:
+        windowsScriptContent.split(TextUtil.windowsLineSeparator).length == 90
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
index caf1279..6b683b6 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
@@ -15,21 +15,21 @@
  */
 package org.gradle.api.internal.tasks;
 
-import org.gradle.api.internal.AsmBackedClassGenerator;
-import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.DirectInstantiator;
 import org.gradle.api.tasks.SourceSet;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import org.junit.Test;
 
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
 public class DefaultSourceSetContainerTest {
-    private final DefaultSourceSetContainer container = new DefaultSourceSetContainer(null, null, new AsmBackedClassGenerator());
+    private final DefaultSourceSetContainer container = new DefaultSourceSetContainer(null, null, new DirectInstantiator());
 
     @Test
     public void createsASourceSet() {
         SourceSet set = container.create("main");
         assertThat(set, instanceOf(DefaultSourceSet.class));
-        assertThat(set, instanceOf(IConventionAware.class));
         assertThat(set.getName(), equalTo("main"));
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy
index dbd0438..c623e6f 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy
@@ -28,20 +28,29 @@ class DefaultSourceSetTest {
     private final FileResolver fileResolver = [resolve: {it as File}] as FileResolver
     private final TaskResolver taskResolver = [resolveTask: {name -> [getName: {name}] as Task}] as TaskResolver
 
+    private DefaultSourceSet sourceSet(String name) {
+        def s = new DefaultSourceSet(name, fileResolver)
+        s.classes = new DefaultSourceSetOutput(s.displayName, fileResolver, taskResolver)
+        return s
+    }
+
     @Test
     public void hasUsefulDisplayName() {
-        SourceSet sourceSet = new DefaultSourceSet('int-test', fileResolver, taskResolver)
+        SourceSet sourceSet = sourceSet('int-test')
         assertThat(sourceSet.toString(), equalTo('source set int test'));
     }
 
     @Test public void defaultValues() {
-        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
+        SourceSet sourceSet = sourceSet('set-name')
+
+        assertThat(sourceSet.output.classesDir, nullValue())
+        assertThat(sourceSet.output.files, isEmpty())
+        assertThat(sourceSet.output.displayName, equalTo('set name output'))
+        assertThat(sourceSet.output.toString(), equalTo('set name output'))
+        assertThat(sourceSet.output.buildDependencies.getDependencies(null), isEmpty())
 
-        assertThat(sourceSet.classesDir, nullValue())
-        assertThat(sourceSet.classes.files, isEmpty())
-        assertThat(sourceSet.classes.displayName, equalTo('set name classes'))
-        assertThat(sourceSet.classes.toString(), equalTo('set name classes'))
-        assertThat(sourceSet.classes.buildDependencies.getDependencies(null), isEmpty())
+        assertThat(sourceSet.output.classesDir, nullValue())
+        assertThat(sourceSet.output.resourcesDir, nullValue())
 
         assertThat(sourceSet.compileClasspath, nullValue())
 
@@ -78,8 +87,8 @@ class DefaultSourceSetTest {
         assertThat(sourceSet.allSource.source, hasItem(sourceSet.java))
     }
 
-    @Test public void constructsTaskNamesUsingSourceSetName() {
-        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
+    @Test public void constructsNamesUsingSourceSetName() {
+        SourceSet sourceSet = sourceSet('set-name')
 
         assertThat(sourceSet.classesTaskName, equalTo('setNameClasses'))
         assertThat(sourceSet.getCompileTaskName('java'), equalTo('compileSetNameJava'))
@@ -88,10 +97,12 @@ class DefaultSourceSetTest {
         assertThat(sourceSet.getTaskName('build', null), equalTo('buildSetName'))
         assertThat(sourceSet.getTaskName(null, 'jar'), equalTo('setNameJar'))
         assertThat(sourceSet.getTaskName('build', 'jar'), equalTo('buildSetNameJar'))
+        assertThat(sourceSet.compileConfigurationName, equalTo("setNameCompile"))
+        assertThat(sourceSet.runtimeConfigurationName, equalTo("setNameRuntime"))
     }
 
-    @Test public void mainSourceSetUsesSpecialCaseTaskNames() {
-        SourceSet sourceSet = new DefaultSourceSet('main', fileResolver, taskResolver)
+    @Test public void mainSourceSetUsesSpecialCaseNames() {
+        SourceSet sourceSet = sourceSet('main')
 
         assertThat(sourceSet.classesTaskName, equalTo('classes'))
         assertThat(sourceSet.getCompileTaskName('java'), equalTo('compileJava'))
@@ -100,38 +111,40 @@ class DefaultSourceSetTest {
         assertThat(sourceSet.getTaskName('build', null), equalTo('buildMain'))
         assertThat(sourceSet.getTaskName(null, 'jar'), equalTo('jar'))
         assertThat(sourceSet.getTaskName('build', 'jar'), equalTo('buildJar'))
+        assertThat(sourceSet.compileConfigurationName, equalTo("compile"))
+        assertThat(sourceSet.runtimeConfigurationName, equalTo("runtime"))
     }
 
     @Test public void canConfigureResources() {
-        SourceSet sourceSet = new DefaultSourceSet('main', fileResolver, taskResolver)
+        SourceSet sourceSet = sourceSet('main')
         sourceSet.resources { srcDir 'src/resources' }
         assertThat(sourceSet.resources.srcDirs, equalTo([new File('src/resources').canonicalFile] as Set))
     }
     
     @Test public void canConfigureJavaSource() {
-        SourceSet sourceSet = new DefaultSourceSet('main', fileResolver, taskResolver)
+        SourceSet sourceSet = sourceSet('main')
         sourceSet.java { srcDir 'src/java' }
         assertThat(sourceSet.java.srcDirs, equalTo([new File('src/java').canonicalFile] as Set))
     }
 
     @Test
     public void classesCollectionTracksChangesToClassesDir() {
-        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
-        assertThat(sourceSet.classes.files, isEmpty())
+        SourceSet sourceSet = sourceSet('set-name')
+        assertThat(sourceSet.output.files, isEmpty())
 
-        sourceSet.classesDir = new File('classes')
-        assertThat(sourceSet.classes.files, equalTo([new File('classes')] as Set))
-        sourceSet.classesDir = new File('other-classes')
-        assertThat(sourceSet.classes.files, equalTo([new File('other-classes')] as Set))
+        sourceSet.output.classesDir = new File('classes')
+        assertThat(sourceSet.output.files, equalTo([new File('classes')] as Set))
+        sourceSet.output.classesDir = new File('other-classes')
+        assertThat(sourceSet.output.files, equalTo([new File('other-classes')] as Set))
     }
 
     @Test
     public void classesCollectionDependenciesTrackChangesToCompileTasks() {
-        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
-        assertThat(sourceSet.classes.buildDependencies.getDependencies(null), isEmpty())
+        SourceSet sourceSet = sourceSet('set-name')
+        assertThat(sourceSet.output.buildDependencies.getDependencies(null), isEmpty())
 
-        sourceSet.classesDir = new File('classes')
+        sourceSet.output.classesDir = new File('classes')
         sourceSet.compiledBy('a', 'b')
-        assertThat(sourceSet.classes.buildDependencies.getDependencies(null)*.name as Set, equalTo(['a', 'b'] as Set))
+        assertThat(sourceSet.output.buildDependencies.getDependencies(null)*.name as Set, equalTo(['a', 'b'] as Set))
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
new file mode 100644
index 0000000..6e6cf69
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 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.compile
+
+import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import com.google.common.collect.Lists
+
+import org.junit.Rule
+import spock.lang.Specification
+
+class CommandLineJavaCompilerArgumentsGeneratorTest extends Specification {
+    @Rule TemporaryFolder tempDir
+    TemporaryFileProvider tempFileProvider = Mock()
+    CommandLineJavaCompilerArgumentsGenerator argsGenerator = new CommandLineJavaCompilerArgumentsGenerator(tempFileProvider)
+
+    def "inlines arguments if they are short enough"() {
+        def spec = createCompileSpec(25)
+
+        when:
+        def args = argsGenerator.generate(spec)
+
+        then:
+        0 * tempFileProvider._
+        Lists.newArrayList(args) == ["-J-Xmx256m", "-g", "-classpath", spec.classpath.asPath, *spec.source*.path]
+    }
+
+    def "creates arguments file if arguments get too long"() {
+        def spec = createCompileSpec(100)
+        def argsFile = tempDir.createFile("compile-args")
+        tempFileProvider.createTemporaryFile(*_) >> argsFile
+
+        when:
+        def args = argsGenerator.generate(spec)
+
+        then: "argument list only contains launcher args and reference to args file"
+        Lists.newArrayList(args) == ["-J-Xmx256m", "@$argsFile"]
+
+        and: "args file contains remaining arguments (one per line, quoted)"
+        argsFile.readLines() == [quote("-g"), quote("-classpath"), quote("$spec.classpath.asPath"), *(spec.source*.path.collect { quote(it) })]
+    }
+
+    def createCompileSpec(numFiles) {
+        def sources = createFiles(numFiles)
+        def classpath = createFiles(numFiles)
+        def spec = new DefaultJavaCompileSpec()
+        spec.compileOptions.forkOptions.memoryMaximumSize = "256m"
+        spec.source = new SimpleFileCollection(sources)
+        spec.classpath = new SimpleFileCollection(classpath)
+        spec
+    }
+
+    def createFiles(numFiles) {
+        (1..numFiles).collect { new File("/foo/bar/File$it") }
+    }
+
+    def quote(arg) {
+      "\"${arg.replace("\\", "\\\\")}\""
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy
new file mode 100644
index 0000000..dcc670e
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DefaultJavaCompilerFactoryTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.compile
+
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.compile.daemon.DaemonJavaCompiler
+import org.gradle.api.tasks.compile.CompileOptions
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.internal.Factory
+
+import spock.lang.Specification
+
+class DefaultJavaCompilerFactoryTest extends Specification {
+    def inProcessCompiler = Mock(Compiler)
+    def inProcessCompilerFactory = Mock(JavaCompilerFactory)
+    def factory = new DefaultJavaCompilerFactory(Mock(ProjectInternal), Mock(TemporaryFileProvider), Mock(Factory), inProcessCompilerFactory)
+    def options = new CompileOptions()
+    
+    def setup() {
+        inProcessCompilerFactory.create(_) >> inProcessCompiler
+    }
+
+    def "creates Ant compiler when useAnt=true"() {
+        options.useAnt = true
+        options.fork = fork
+        
+        expect:
+        factory.create(options) instanceof AntJavaCompiler
+        
+        where: fork << [false, true]
+    }
+
+    def "falls back to Ant compiler when options.compiler is set"() {
+        options.useAnt = false
+        options.compiler = "jikes"
+
+        expect:
+        factory.create(options) instanceof AntJavaCompiler
+    }
+    
+    def "creates in-process compiler when fork=false"() {
+        options.useAnt = false
+        options.fork = false
+
+        expect:
+        def compiler = factory.create(options)
+        compiler instanceof NormalizingJavaCompiler
+        compiler.delegate.is(inProcessCompiler)
+    }
+
+    def "creates command line compiler when fork=true and forkOptions.executable is set"() {
+        options.useAnt = false
+        options.fork = true
+        options.forkOptions.executable = "/path/to/javac"
+
+        expect:
+        expect:
+        def compiler = factory.create(options)
+        compiler instanceof NormalizingJavaCompiler
+        compiler.delegate instanceof CommandLineJavaCompiler
+    }
+
+    def "creates daemon compiler when fork=true"() {
+        options.useAnt = false
+        options.fork = true
+
+        expect:
+        def compiler = factory.create(options)
+        compiler instanceof NormalizingJavaCompiler
+        compiler.delegate instanceof DaemonJavaCompiler
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DelegatingJavaCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DelegatingJavaCompilerTest.groovy
new file mode 100644
index 0000000..307f4e4
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/DelegatingJavaCompilerTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.compile
+
+import spock.lang.Specification
+
+class DelegatingJavaCompilerTest extends Specification {
+    def "configures and executes the chosen compiler"() {
+        Compiler<JavaCompileSpec> chosen = Mock()
+        JavaCompileSpec spec = Mock()
+
+        def switchable = new DelegatingJavaCompiler({ chosen } as JavaCompilerFactory)
+        
+        when:
+        switchable.execute(spec)
+        
+        then:
+        1 * chosen.execute(spec)
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy
new file mode 100644
index 0000000..799a18f
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactoryTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.compile
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.api.tasks.compile.CompileOptions
+
+import spock.lang.Specification
+
+class InProcessJavaCompilerFactoryTest extends Specification {
+    def factory = new InProcessJavaCompilerFactory()
+    def options = new CompileOptions()
+    
+    @Requires(TestPrecondition.JDK6)
+    def "creates JDK 6 compiler on JDK 6"() {
+        expect:
+        factory.create(options).getClass().name == "org.gradle.api.internal.tasks.compile.jdk6.Jdk6JavaCompiler"
+    }
+
+    @Requires(TestPrecondition.JDK5)
+    def "creates Sun compiler on JDK 5"() {
+        expect:
+        factory.create(options) instanceof SunJavaCompiler
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerTest.groovy
new file mode 100644
index 0000000..d39407a
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompilerTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.compile
+
+import spock.lang.Specification
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.file.FileCollection
+
+class IncrementalJavaCompilerTest extends Specification {
+    private final Compiler<JavaCompileSpec> target = Mock()
+    private final JavaCompileSpec spec = Mock()
+    private final StaleClassCleaner cleaner = Mock()
+    private final IncrementalJavaCompilerSupport<JavaCompileSpec> compiler = new IncrementalJavaCompilerSupport<JavaCompileSpec>() {
+        @Override
+        protected Compiler<JavaCompileSpec> getCompiler() {
+            return target
+        }
+
+        protected StaleClassCleaner createCleaner(JavaCompileSpec spec) {
+            return cleaner
+        }
+    }
+    
+    def cleansStaleClassesAndThenInvokesCompiler() {
+        WorkResult result = Mock()
+        File destDir = new File('dest')
+        FileCollection source = Mock()
+        _ * spec.destinationDir >> destDir
+        _ * spec.source >> source
+
+        when:
+        def r = compiler.execute(spec)
+
+        then:
+        r == result
+
+        and:
+        1 * cleaner.setDestinationDir(destDir)
+        1 * cleaner.setSource(source)
+
+        and:
+        1 * cleaner.execute()
+
+        and:
+        1 * target.execute(spec) >> result
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompilerTest.groovy
deleted file mode 100644
index 5de4093..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompilerTest.groovy
+++ /dev/null
@@ -1,48 +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.api.internal.tasks.compile
-
-import spock.lang.Specification
-import org.gradle.api.tasks.WorkResult
-import org.gradle.api.file.FileCollection
-
-class IncrementalJavaSourceCompilerTest extends Specification {
-    private final JavaSourceCompiler target = Mock()
-    private final StaleClassCleaner cleaner = Mock()
-    private final IncrementalJavaSourceCompiler compiler = new IncrementalJavaSourceCompiler(target) {
-        protected StaleClassCleaner createCleaner() {
-            return cleaner
-        }
-    }
-    
-    def cleansStaleClassesAndThenInvokesCompiler() {
-        WorkResult result = Mock()
-        File destDir = new File('dest')
-        FileCollection source = Mock()
-        compiler.destinationDir = destDir
-        compiler.source = source
-
-        when:
-        def r = compiler.execute()
-
-        then:
-        r == result
-        1 * cleaner.setDestinationDir(destDir)
-        1 * cleaner.setSource(source)
-        1 * cleaner.execute()
-        1 * target.execute() >> result
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilderTest.groovy
new file mode 100644
index 0000000..c6f8fd8
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilderTest.groovy
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2011 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.compile
+
+import spock.lang.Specification
+import org.gradle.api.JavaVersion
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+
+class JavaCompilerArgumentsBuilderTest extends Specification {
+    def spec = new DefaultJavaCompileSpec()
+    def builder = new JavaCompilerArgumentsBuilder(spec)
+
+    def "generates options for an unconfigured spec"() {
+        expect:
+        builder.build() == ["-g"]
+    }
+
+    def "generates no -source option when current Jvm Version is used"() {
+        spec.sourceCompatibility = JavaVersion.current().toString();
+
+        expect:
+        builder.build() == ["-g"]
+    }
+
+    def "generates -source option when compatibility differs from current Jvm version"() {
+        spec.sourceCompatibility = "1.4"
+
+        expect:
+        builder.build() == ["-source", "1.4", "-g"]
+    }
+
+    def "generates no -target option when current Jvm Version is used"() {
+        spec.targetCompatibility = JavaVersion.current().toString();
+
+        expect:
+        builder.build() == ["-g"]
+    }
+
+    def "generates -target option when compatibility differs current Jvm version"() {
+        spec.targetCompatibility = "1.4"
+
+        expect:
+        builder.build() == ["-target", "1.4", "-g"]
+    }
+
+    def "generates -d option"() {
+        def file = new File("/project/build")
+        spec.destinationDir = file
+
+        expect:
+        builder.build() == ["-d", file.path, "-g"]
+    }
+
+    def "generates -verbose option"() {
+        when:
+        spec.compileOptions.verbose = true
+
+        then:
+        builder.build() == ["-verbose", "-g"]
+
+        when:
+        spec.compileOptions.verbose = false
+
+        then:
+        builder.build() == ["-g"]
+    }
+
+    def "generates -deprecation option"() {
+        when:
+        spec.compileOptions.deprecation = true
+
+        then:
+        builder.build() == ["-deprecation", "-g"]
+
+        when:
+        spec.compileOptions.deprecation = false
+
+        then:
+        builder.build() == ["-g"]
+    }
+
+    def "generates -nowarn option"() {
+        when:
+        spec.compileOptions.warnings = true
+
+        then:
+        builder.build() == ["-g"]
+
+        when:
+        spec.compileOptions.warnings = false
+
+        then:
+        builder.build() == ["-nowarn", "-g"]
+    }
+
+    def "generates -g option"() {
+        when:
+        spec.compileOptions.debug = true
+
+        then:
+        builder.build() == ["-g"]
+
+        when:
+        spec.compileOptions.debugOptions.debugLevel = "source,vars"
+
+        then:
+        builder.build() == ["-g:source,vars"]
+
+        when:
+        spec.compileOptions.debug = false
+
+        then:
+        builder.build() == ["-g:none"]
+    }
+
+    def "generates -encoding option"() {
+        spec.compileOptions.encoding = "some-encoding"
+
+        expect:
+        builder.build() == ["-g", "-encoding", "some-encoding"]
+    }
+
+    def "generates -bootclasspath option"() {
+        spec.compileOptions.bootClasspath = "/lib/lib1.jar:/lib/lib2.jar"
+
+        expect:
+        builder.build() == ["-g", "-bootclasspath", "/lib/lib1.jar:/lib/lib2.jar"]
+    }
+
+    def "generates -extdirs option"() {
+        spec.compileOptions.extensionDirs = "/dir1:/dir2"
+
+        expect:
+        builder.build() == ["-g", "-extdirs", "/dir1:/dir2"]
+    }
+
+    def "generates -classpath option"() {
+        def file1 = new File("/lib/lib1.jar")
+        def file2 = new File("/lib/lib2.jar")
+        spec.classpath = [file1, file2]
+
+        expect:
+        builder.build() == ["-g", "-classpath", "$file1$File.pathSeparator$file2"]
+    }
+
+    def "adds custom compiler args"() {
+        spec.compileOptions.compilerArgs = ["-a", "value-a", "-b", "value-b"]
+
+        expect:
+        builder.build() == ["-g", "-a", "value-a", "-b", "value-b"]
+    }
+
+    def "can include/exclude main options"() {
+        spec.sourceCompatibility = "1.4"
+
+        when:
+        builder.includeMainOptions(true)
+
+        then:
+        builder.build() == ["-source", "1.4", "-g"]
+
+        when:
+        builder.includeMainOptions(false)
+
+        then:
+        builder.build() == []
+    }
+
+    def "includes main options by default"() {
+        spec.sourceCompatibility = "1.4"
+
+        expect:
+        builder.build() == ["-source", "1.4", "-g"]
+    }
+
+    def "can include/exclude launcher options"() {
+        spec.compileOptions.forkOptions.with {
+            memoryInitialSize = "64m"
+            memoryMaximumSize = "1g"
+        }
+
+        when:
+        builder.includeLauncherOptions(true)
+
+        then:
+        builder.build() == ["-J-Xms64m", "-J-Xmx1g", "-g"]
+
+        when:
+        builder.includeLauncherOptions(false)
+
+        then:
+        builder.build() == ["-g"]
+    }
+
+    def "does not include launcher options by default"() {
+        spec.compileOptions.forkOptions.with {
+            memoryInitialSize = "64m"
+            memoryMaximumSize = "1g"
+        }
+
+        expect:
+        builder.build() == ["-g"]
+    }
+
+    def "can include/exclude source files"() {
+        def file1 = new File("/src/Person.java")
+        def file2 = new File("Computer.java")
+        spec.source = new SimpleFileCollection(file1, file2)
+
+        when:
+        builder.includeSourceFiles(true)
+
+        then:
+        builder.build() == ["-g", file1.path, file2.path]
+
+        when:
+        builder.includeSourceFiles(false)
+
+        then:
+        builder.build() == ["-g"]
+    }
+
+    def "does not include source files by default"() {
+        def file1 = new File("/src/Person.java")
+        def file2 = new File("Computer.java")
+        spec.source = new SimpleFileCollection(file1, file2)
+
+        expect:
+        builder.build() == ["-g"]
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompilerTest.groovy
new file mode 100644
index 0000000..121a54b
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingJavaCompilerTest.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 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.compile
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+
+import groovy.transform.InheritConstructors
+
+import spock.lang.Specification
+
+class NormalizingJavaCompilerTest extends Specification {
+    Compiler<JavaCompileSpec> target = Mock()
+    JavaCompileSpec spec = new DefaultJavaCompileSpec()
+    NormalizingJavaCompiler compiler = new NormalizingJavaCompiler(target)
+
+    def setup() {
+        spec.source = files("Source1.java", "Source2.java", "Source3.java")
+        spec.classpath = files("Dep1.jar", "Dep2.jar", "Dep3.jar")
+    }
+
+    def "delegates to target compiler after resolving source and classpath"() {
+        WorkResult workResult = Mock()
+
+        when:
+        def result = compiler.execute(spec)
+
+        then:
+        1 * target.execute(spec) >> {
+            assert spec.source.getClass() == SimpleFileCollection
+            assert spec.source.files == old(spec.source.files)
+            assert spec.classpath.getClass() == SimpleFileCollection
+            assert spec.classpath.files == old(spec.classpath.files)
+            workResult
+        }
+        result == workResult
+    }
+
+    def "silently excludes source files not ending in .java"() {
+        spec.source = files("House.scala", "Person1.java", "package.html", "Person2.java")
+
+        when:
+        compiler.execute(spec)
+
+        then:
+        1 * target.execute(spec) >> {
+            assert spec.source.files == files("Person1.java", "Person2.java").files
+        }
+    }
+
+    def "propagates compile failure when failOnError is true"() {
+        def failure
+        target.execute(spec) >> { throw failure = new CompilationFailedException() }
+
+        spec.compileOptions.failOnError = true
+
+        when:
+        compiler.execute(spec)
+        
+        then:
+        CompilationFailedException e = thrown()
+        e == failure
+    }
+
+    def "ignores compile failure when failOnError is false"() {
+        target.execute(spec) >> { throw new CompilationFailedException() }
+
+        spec.compileOptions.failOnError = false
+
+        when:
+        def result = compiler.execute(spec)
+
+        then:
+        noExceptionThrown()
+        !result.didWork
+    }
+
+    def "propagates other failure"() {
+        def failure
+        target.execute(spec) >> { throw failure = new RuntimeException() }
+
+        when:
+        compiler.execute(spec)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "resolves any non-strings that make it into custom compiler args"() {
+        spec.compileOptions.compilerArgs << "a dreaded ${"GString"}"
+        spec.compileOptions.compilerArgs << 42
+        assert !spec.compileOptions.compilerArgs.any { it instanceof String }
+
+        when:
+        compiler.execute(spec)
+
+        then:
+        1 * target.execute(_) >> { JavaCompileSpec spec ->
+            assert spec.compileOptions.compilerArgs.every { it instanceof String }
+        }
+    }
+
+    private files(String... paths) {
+        new TestFileCollection(paths.collect { new File(it) })
+    }
+
+    // file collection whose type is distinguishable from SimpleFileCollection
+    @InheritConstructors
+    static class TestFileCollection extends SimpleFileCollection {}
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
new file mode 100644
index 0000000..b791c51
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 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.compile
+
+import spock.lang.Specification
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+import org.gradle.util.ClasspathUtil
+import org.gradle.util.DefaultClassPath
+
+class TransformingClassLoaderTest extends Specification {
+    TransformingClassLoader loader
+    Class<?> classAnnotation
+
+    def setup() {
+        def classPath = new DefaultClassPath(ClasspathUtil.getClasspathForClass(getClass()), ClasspathUtil.getClasspathForClass(GroovyASTTransformationClass))
+        loader = new TransformingClassLoader(classPath)
+        classAnnotation = loader.loadClass(GroovyASTTransformationClass.name)
+    }
+
+    def "loads class annotated with transformer name"() {
+        expect:
+        def cl = loader.loadClass(WithNameSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == ['some-type'] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer names"() {
+        expect:
+        def cl = loader.loadClass(WithNamesSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == ['some-type', 'some-other-type'] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer class"() {
+        expect:
+        def cl = loader.loadClass(WithClassSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == [Transformer.name] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer classes"() {
+        expect:
+        def cl = loader.loadClass(WithClassesSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() == [Transformer.name, Runnable.name] as String[]
+        annotation.classes() == [] as Class[]
+    }
+
+    def "loads class annotated with transformer names and classes"() {
+        expect:
+        def cl = loader.loadClass(WithBothSpecified.name)
+        def annotation = cl.getAnnotation(classAnnotation)
+        annotation.value() as Set == ["some-type", Transformer.name, Runnable.name] as Set
+        annotation.classes() == [] as Class[]
+    }
+
+    static class Transformer {
+    }
+}
+
+ at GroovyASTTransformationClass("some-type")
+ at interface WithNameSpecified {
+}
+
+ at GroovyASTTransformationClass(["some-type", "some-other-type"])
+ at interface WithNamesSpecified {
+}
+
+ at GroovyASTTransformationClass(classes = [TransformingClassLoaderTest.Transformer])
+ at interface WithClassSpecified {
+}
+
+ at GroovyASTTransformationClass(classes = [TransformingClassLoaderTest.Transformer, Runnable])
+ at interface WithClassesSpecified {
+}
+
+ at GroovyASTTransformationClass(value = "some-type", classes = [TransformingClassLoaderTest.Transformer, Runnable])
+ at interface WithBothSpecified {
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptionsMergeTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptionsMergeTest.groovy
new file mode 100644
index 0000000..c047729
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptionsMergeTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 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.compile.daemon
+
+import spock.lang.Specification
+
+class DaemonForkOptionsMergeTest extends Specification {
+    DaemonForkOptions options1 = new DaemonForkOptions("200m", "1g", [" -Dfork=true ", "-Xdebug=false"],
+            [new File("lib/lib1.jar"), new File("lib/lib2.jar")], ["foo.bar", "baz.bar"])
+    DaemonForkOptions options2 = new DaemonForkOptions("1g", "2000m", ["-XX:MaxHeapSize=300m", "-Dfork=true"],
+            [new File("lib/lib2.jar"), new File("lib/lib3.jar")], ["baz.bar", "other"])
+    DaemonForkOptions merged = options1.mergeWith(options2)
+
+    def "takes highest minHeapSize"() {
+        expect:
+        merged.minHeapSize == "1024m"
+    }
+
+    def "takes highest maxHeapSize"() {
+        expect:
+        merged.maxHeapSize == "2000m"
+    }
+
+    def "concatenates jvmArgs (retaining order, eliminating duplicates)"() {
+        expect:
+        merged.jvmArgs as List == ["-Dfork=true", "-Xdebug=false", "-XX:MaxHeapSize=300m"]
+    }
+
+    def "concatenates classpath (retaining order, eliminating duplicates)"() {
+        expect:
+        merged.classpath as List == [new File("lib/lib1.jar"), new File("lib/lib2.jar"), new File("lib/lib3.jar")]
+    }
+
+    def "concatenates sharedPackages (retaining order, eliminating duplicates)"() {
+        expect:
+        merged.sharedPackages as List == ["foo.bar", "baz.bar", "other"]
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptionsTest.groovy
new file mode 100644
index 0000000..5c70af6
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/daemon/DaemonForkOptionsTest.groovy
@@ -0,0 +1,176 @@
+/*
+ * 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.tasks.compile.daemon
+
+import spock.lang.Specification
+
+class DaemonForkOptionsTest extends Specification {
+    def "is compatible with itself"() {
+        def settings = new DaemonForkOptions("128m", "1g", ["-server", "-esa"], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], ["foo.bar", "foo.baz"])
+
+        expect:
+        settings.isCompatibleWith(settings)
+    }
+
+    def "is compatible with same settings"() {
+        def settings1 = new DaemonForkOptions("128m", "1g", ["-server", "-esa"], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], ["foo.bar", "foo.baz"])
+        def settings2 = new DaemonForkOptions("128m", "1g", ["-server", "-esa"], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], ["foo.bar", "foo.baz"])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+
+    def "is compatible with different representation of same memory requirements"() {
+        def settings1 = new DaemonForkOptions("1024m", "2g", [])
+        def settings2 = new DaemonForkOptions("1g", "2048m", [])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with lower memory requirements"() {
+        def settings1 = new DaemonForkOptions("128m", "1g", [])
+        def settings2 = new DaemonForkOptions("64m", "512m", [])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is not compatible with higher memory requirements"() {
+        def settings1 = new DaemonForkOptions("128m", "1g", [])
+        def settings2 = new DaemonForkOptions("256m", "512m", [])
+
+        expect:
+        !settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with same set of JVM args"() {
+        def settings1 = new DaemonForkOptions(null, null, ["-server", "-esa"])
+        def settings2 = new DaemonForkOptions(null, null, ["-esa", "-server"])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with subset of JVM args"() {
+        def settings1 = new DaemonForkOptions(null, null, ["-server", "-esa"])
+        def settings2 = new DaemonForkOptions(null, null, ["-server"])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is not compatible with different set of JVM args"() {
+        def settings1 = new DaemonForkOptions(null, null, ["-server", "-esa"])
+        def settings2 = new DaemonForkOptions(null, null, ["-client", "-esa"])
+
+        expect:
+        !settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with same class path"() {
+        def settings1 = new DaemonForkOptions(null, null, [], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], [])
+        def settings2 = new DaemonForkOptions(null, null, [], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], [])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with subset of class path"() {
+        def settings1 = new DaemonForkOptions(null, null, [], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], [])
+        def settings2 = new DaemonForkOptions(null, null, [], [new File("lib/lib1.jar")], [])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is not compatible with different class path"() {
+        def settings1 = new DaemonForkOptions(null, null, [], [new File("lib/lib1.jar"), new File("lib/lib2.jar")], [])
+        def settings2 = new DaemonForkOptions(null, null, [], [new File("lib/lib1.jar"), new File("lib/lib3.jar")], [])
+
+        expect:
+        !settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with same set of shared packages"() {
+        def settings1 = new DaemonForkOptions(null, null, [], [], ["foo.bar", "foo.baz"])
+        def settings2 = new DaemonForkOptions(null, null, [], [], ["foo.bar", "foo.baz"])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is compatible with subset of shared packages"() {
+        def settings1 = new DaemonForkOptions(null, null, [], [], ["foo.bar", "foo.baz"])
+        def settings2 = new DaemonForkOptions(null, null, [], [], ["foo.baz"])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "is not compatible with different set of shared packages"() {
+        def settings1 = new DaemonForkOptions(null, null, [], [], ["foo.bar", "foo.baz"])
+        def settings2 = new DaemonForkOptions(null, null, [], [], ["foo.pic", "foo.baz"])
+
+        expect:
+        !settings1.isCompatibleWith(settings2)
+    }
+
+    def "string values are trimmed"() {
+        def settings1 = new DaemonForkOptions("128m ", "1g", [" -server", "-esa"])
+        def settings2 = new DaemonForkOptions("128m", " 1g", ["-server", "-esa "])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "capitalization of memory options is irrelevant"() {
+        def settings1 = new DaemonForkOptions("128M", "1g", [])
+        def settings2 = new DaemonForkOptions("128m", "1G", [])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+    }
+
+    def "capitalization of JVM args is relevant"() {
+        def settings1 = new DaemonForkOptions("128M", "1g", ["-Server", "-esa"])
+        def settings2 = new DaemonForkOptions("128M", "1g", ["-server", "-esa"])
+
+        expect:
+        !settings1.isCompatibleWith(settings2)
+    }
+
+    def "unspecified class path and shared packages default to empty list"() {
+        when:
+        def options = new DaemonForkOptions("128m", "1g", ["-server", "-esa"])
+
+        then:
+        options.classpath == []
+        options.sharedPackages == []
+    }
+
+    def "unspecified memory options are only compatible with unspecified memory options"() {
+        def settings1 = new DaemonForkOptions(null, null, [])
+        def settings2 = new DaemonForkOptions(null, null, [])
+        def settings3 = new DaemonForkOptions("8m", "64m", [])
+
+        expect:
+        settings1.isCompatibleWith(settings2)
+        !settings1.isCompatibleWith(settings3)
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScannerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScannerTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
index e62d031..1745a53 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
@@ -42,13 +42,16 @@ import junit.extensions.TestSetup
 import org.junit.After
 import org.gradle.api.tasks.testing.TestResult
 import junit.framework.TestSuite
+import org.gradle.messaging.actor.ActorFactory
+import org.gradle.messaging.actor.TestActorFactory
 
 @RunWith(JMock.class)
 class JUnitTestClassProcessorTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
     @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
     private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.dir, new LongIdGenerator(), {} as StandardOutputRedirector);
+    private final ActorFactory actorFactory = new TestActorFactory()
+    private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.dir, new LongIdGenerator(), actorFactory, {} as StandardOutputRedirector);
 
     @Test
     public void executesAJUnit4TestClass() {
@@ -571,6 +574,71 @@ class JUnitTestClassProcessorTest {
     }
 
     @Test
+    public void executesATestClassWithRunnerThatBreaksBeforeRunningAnyTests() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithBrokenRunner.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('initializationError'))
+                assertThat(test.className, equalTo(ATestClassWithBrokenRunner.class.name))
+            }
+            one(resultProcessor).failure(2L, CustomRunnerWithBrokenRunMethod.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithBrokenRunner.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithRunnerThatBreaksAfterRunningSomeTests() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('ok1'))
+                assertThat(test.className, equalTo(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class.name))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('broken'))
+                assertThat(test.className, equalTo(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class.name))
+            }
+            one(resultProcessor).failure(3L, CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests.failure)
+            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithRunnerThatBreaksAfterRuningSomeTests.class));
+        processor.stop();
+    }
+
+    @Test
     public void executesATestClassWhichCannotBeLoaded() {
         String testClassName = 'org.gradle.api.internal.tasks.testing.junit.ATestClassWhichCannotBeLoaded'
 
@@ -831,6 +899,49 @@ public class CustomRunnerWithBrokenConstructor extends Runner {
     }
 }
 
+ at RunWith(CustomRunnerWithBrokenRunMethod.class)
+public class ATestClassWithBrokenRunner {}
+
+public class CustomRunnerWithBrokenRunMethod extends Runner {
+    static RuntimeException failure = new RuntimeException()
+    final Class<?> type
+
+    def CustomRunnerWithBrokenRunMethod(Class<?> type) {
+        this.type = type
+    }
+
+    Description getDescription() {
+        return Description.createSuiteDescription(type)
+    }
+
+    void run(RunNotifier notifier) {
+        throw failure.fillInStackTrace();
+    }
+}
+
+ at RunWith(CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests.class)
+public class ATestClassWithRunnerThatBreaksAfterRuningSomeTests {}
+
+public class CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests extends Runner {
+    static RuntimeException failure = new RuntimeException()
+    final Class<?> type
+
+    def CustomRunnerWithRunMethodThatBreaksAfterRunningSomeTests(Class<?> type) {
+        this.type = type
+    }
+
+    Description getDescription() {
+        return Description.createSuiteDescription(type)
+    }
+
+    void run(RunNotifier notifier) {
+        notifier.fireTestStarted(Description.createTestDescription(type, "ok1"))
+        notifier.fireTestFinished(Description.createTestDescription(type, "ok1"))
+        notifier.fireTestStarted(Description.createTestDescription(type, "broken"))
+        throw failure.fillInStackTrace();
+    }
+}
+
 public class ATestClassWhichCannotBeLoaded {
     static {
         throw new NoClassDefFoundError()
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
index 56381f1..b1150d3 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
@@ -17,11 +17,12 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.AntBuilder;
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
+import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.util.IdGenerator;
 import org.jmock.Expectations;
 import org.junit.Before;
@@ -53,6 +54,7 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
             allowing(testMock).getTestClassesDir(); will(returnValue(testClassesDir));
             allowing(testMock).getClasspath(); will(returnValue(classpathMock));
             allowing(testMock).getAnt(); will(returnValue(context.mock(AntBuilder.class)));
+            allowing(testMock).getTemporaryDir(); will(returnValue(temporaryDir));
         }});
     }
 
@@ -69,10 +71,12 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
     public void testCreatesTestProcessor() {
         jUnitTestFramework = new JUnitTestFramework(testMock);
         setMocks();
+        final ActorFactory actorFactory = context.mock(ActorFactory.class);
 
         context.checking(new Expectations() {{
             one(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
             one(serviceRegistry).get(IdGenerator.class); will(returnValue(idGenerator));
+            one(serviceRegistry).get(ActorFactory.class); will(returnValue(actorFactory));
         }});
 
         TestClassProcessor testClassProcessor = jUnitTestFramework.getProcessorFactory().create(serviceRegistry);
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy
new file mode 100644
index 0000000..c61296f
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012 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.testing.junit
+
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.util.IdGenerator
+import org.gradle.util.TimeProvider
+import spock.lang.Specification
+import org.gradle.api.tasks.testing.TestDescriptor
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+
+class TestClassExecutionEventGeneratorTest extends Specification {
+    final TestResultProcessor target = Mock()
+    final IdGenerator<Object> idGenerator = Mock()
+    final TimeProvider timeProvider = Mock()
+    final TestClassExecutionEventGenerator processor = new TestClassExecutionEventGenerator(target, idGenerator, timeProvider)
+
+    def "fires event on test class start"() {
+        given:
+        idGenerator.generateId() >> 1
+        timeProvider.currentTime >> 1200
+
+        when:
+        processor.testClassStarted("some-test")
+
+        then:
+        1 * target.started({it.id == 1 && it.className == 'some-test'}, {it.startTime == 1200})
+        0 * target._
+    }
+
+    def "fires event on test class finish"() {
+        given:
+        idGenerator.generateId() >> 1
+        timeProvider.currentTime >>> [1200, 1300]
+
+        and:
+        processor.testClassStarted("some-test")
+
+        when:
+        processor.testClassFinished(null)
+
+        then:
+        1 * target.completed(1, {it.endTime == 1300})
+        0 * target._
+    }
+
+    def "synthesises a broken test when test class fails and no tests have been started"() {
+        def failure = new RuntimeException()
+
+        given:
+        idGenerator.generateId() >>> [1, 2]
+
+        and:
+        processor.testClassStarted("some-test")
+
+        when:
+        processor.testClassFinished(failure)
+
+        then:
+        1 * target.started({it.id == 2 && it.className == 'some-test' && it.name == 'initializationError'}, !null)
+        1 * target.failure(2, failure)
+        1 * target.completed(2, !null)
+        1 * target.completed(1, !null)
+        0 * target._
+    }
+
+    def "fires event on test class failure when some tests have been started"() {
+        def failure = new RuntimeException()
+        TestDescriptorInternal test1 = Mock()
+        TestDescriptorInternal test2 = Mock()
+
+        given:
+        idGenerator.generateId() >> 1
+        test1.id >> 2
+        test2.id >> 3
+
+        and:
+        processor.testClassStarted("some-test")
+        processor.started(test1, null)
+        processor.started(test2, null)
+
+        when:
+        processor.testClassFinished(failure)
+
+        then:
+        1 * target.failure(2, failure)
+        1 * target.failure(3, failure)
+        1 * target.completed(2, !null)
+        1 * target.completed(3, !null)
+        1 * target.completed(1, !null)
+        0 * target._
+    }
+
+    def "synthesises a broken test when test class fails and some tests have been completed"() {
+        def failure = new RuntimeException()
+        TestDescriptorInternal test = Mock()
+
+        given:
+        idGenerator.generateId() >>> [1, 3]
+        test.id >> 2
+
+        and:
+        processor.testClassStarted("some-test")
+        processor.started(test, null)
+        processor.completed(2, null)
+
+        when:
+        processor.testClassFinished(failure)
+
+        then:
+        1 * target.started({it.id == 3 && it.className == 'some-test' && it.name == 'executionError'}, !null)
+        1 * target.failure(3, failure)
+        1 * target.completed(3, !null)
+        1 * target.completed(1, !null)
+        0 * target._
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy
index b641fed..6dd21b8 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/CompositeTestResultsTest.groovy
@@ -63,6 +63,6 @@ class CompositeTestResultsTest extends Specification {
     }
 
     private TestResult test() {
-        return new TestResult('test', 45, null)
+        return new TestResult('test', 45, new ClassTestResults('test', null))
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
index 2327d5a..3db359b 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
@@ -360,13 +360,13 @@ class TestResultsFixture {
     void assertHasStandardOutput(String stdout) {
         def tab = findTab('Standard output')
         assert tab != null
-        assert tab.PRE[0].text() == stdout.trim()
+        assert tab.SPAN[0].PRE[0].text() == stdout.trim()
     }
 
     void assertHasStandardError(String stderr) {
         def tab = findTab('Standard error')
         assert tab != null
-        assert tab.PRE[0].text() == stderr.trim()
+        assert tab.SPAN[0].PRE[0].text() == stderr.trim()
     }
 
     private def findTab(String title) {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLoggerTest.groovy
new file mode 100644
index 0000000..9dffe7e
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLoggerTest.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.testing.logging;
+
+
+import org.gradle.api.internal.tasks.testing.DefaultTestDescriptor
+import org.gradle.api.internal.tasks.testing.DefaultTestOutputEvent
+import org.gradle.api.tasks.testing.TestLogging
+import org.gradle.api.tasks.testing.TestOutputEvent.Destination
+import org.slf4j.Logger
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/4/11
+ */
+public class StandardStreamsLoggerTest extends Specification {
+
+    def logger = Mock(Logger)
+    def test = new DefaultTestDescriptor("1", "DogTest", "should bark")
+    def event = new DefaultTestOutputEvent(Destination.StdOut, "woof!")
+
+    def "does not show standard streams"() {
+        given:
+        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: false } as TestLogging)
+
+        when:
+        streamsLogger.onOutput(test, event)
+
+        then:
+        0 * logger._
+    }
+
+    def "includes header once"() {
+        given:
+        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: true } as TestLogging)
+
+        when:
+        streamsLogger.onOutput(test, event)
+
+        then:
+        1 * logger.info({ it.contains("should bark") })
+        1 * logger.info({ it.contains("woof!")})
+
+        when:
+        streamsLogger.onOutput(test, event)
+        streamsLogger.onOutput(test, event)
+
+        then:
+        2 * logger.info({ !it.contains("should bark") && it.contains("woof!")})
+    }
+
+    def "includes header once per series of output events"() {
+        given:
+        def testTwo = new DefaultTestDescriptor("2", "DogTest", "should growl")
+        def eventTwo = new DefaultTestOutputEvent(Destination.StdOut, "grrr!")
+        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: true } as TestLogging)
+
+        when:
+        streamsLogger.onOutput(test, event)
+        streamsLogger.onOutput(test, event)
+
+        then:
+        1 * logger.info({ it.contains("should bark") })
+        2 * logger.info({ !it.contains("should bark") && it.contains("woof!")})
+        0 * logger._
+
+        when:
+        streamsLogger.onOutput(testTwo, eventTwo)
+        streamsLogger.onOutput(testTwo, eventTwo)
+
+        then:
+        1 * logger.info({ it.contains("should growl") })
+        2 * logger.info({ !it.contains("should growl") && it.contains("grrr!")})
+        0 * logger._
+
+        when:
+        //let's say test one is still pushing some output, include the header accordingly
+        streamsLogger.onOutput(test, event)
+        streamsLogger.onOutput(test, event)
+
+        then:
+        1 * logger.info({ it.contains("should bark") })
+        2 * logger.info({ !it.contains("should bark") && it.contains("woof!")})
+        0 * logger._
+    }
+
+    def "uses error level for errors"() {
+        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: true } as TestLogging)
+        def event = new DefaultTestOutputEvent(Destination.StdErr, "boom!")
+
+        when:
+        streamsLogger.onOutput(test, event)
+
+        then:
+        1 * logger.info({ it.contains("should bark")})
+        1 * logger.error({ it.contains("boom!")})
+        0 * logger._
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessorTest.groovy
index 80525b1..d59e224 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/CaptureTestOutputTestResultProcessorTest.groovy
@@ -16,14 +16,15 @@
 
 package org.gradle.api.internal.tasks.testing.processors
 
+import org.gradle.api.tasks.testing.TestOutputEvent
 import org.gradle.logging.StandardOutputRedirector
 import spock.lang.Specification
 import org.gradle.api.internal.tasks.testing.*
 
 class CaptureTestOutputTestResultProcessorTest extends Specification {
-    private final TestResultProcessor target = Mock()
-    private final StandardOutputRedirector redirector = Mock()
-    private final CaptureTestOutputTestResultProcessor processor = new CaptureTestOutputTestResultProcessor(target, redirector)
+    def TestResultProcessor target = Mock()
+    def StandardOutputRedirector redirector = Mock()
+    def processor = new CaptureTestOutputTestResultProcessor(target, redirector)
 
     def capturesStdOutputAndStdErrorWhileTestIsExecuting() {
         TestDescriptorInternal test = Mock()
@@ -61,4 +62,50 @@ class CaptureTestOutputTestResultProcessorTest extends Specification {
         1 * redirector.stop()
         1 * target.completed(testId, completeEvent)
     }
+
+    def "configures redirector for suite and test"() {
+        //this test is not beautiful and it is also very strict.
+        //it's to avoid tricky bugs related to not having correct test ids passed onto the 'output' events
+        //it is covered in the integration tests but I decided to have hardcore mocking test for this algorithm as well :)
+        given:
+        def suite = new DefaultTestClassDescriptor("1", "DogTest");
+        def test = new DefaultTestClassDescriptor("1.1", "shouldBark");
+
+        when:
+        processor.started(suite, Mock(TestStartEvent))
+
+        then:
+        1 * redirector.start()
+
+        1 * redirector.redirectStandardErrorTo({ it.testId == '1' })
+        1 * redirector.redirectStandardOutputTo({ it.testId == '1' })
+
+        0 * redirector._
+
+        when:
+        processor.started(test, Mock(TestStartEvent))
+
+        then:
+        1 * redirector.redirectStandardErrorTo({ it.testId == '1.1' })
+        1 * redirector.redirectStandardOutputTo({ it.testId == '1.1' })
+
+        0 * redirector._
+
+        when:
+        processor.completed('1.1', Mock(TestCompleteEvent))
+
+        then:
+        1 * redirector.redirectStandardErrorTo({ it.testId == '1' })
+        1 * redirector.redirectStandardOutputTo({ it.testId == '1' })
+
+        0 * redirector._
+
+        when:
+        processor.completed('1', Mock(TestCompleteEvent))
+
+        then:
+
+        1 * redirector.stop()
+        0 * redirector._
+    }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy
index 1e119cd..8a51bb2 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.api.internal.tasks.testing.TestResultProcessor
 import org.gradle.messaging.actor.Actor
 import org.gradle.messaging.actor.ActorFactory
 import spock.lang.Specification
-import org.gradle.api.internal.Factory
+import org.gradle.internal.Factory
 
 class MaxNParallelTestClassProcessorTest extends Specification {
     private final Factory<TestClassProcessor> factory = Mock()
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java
old mode 100644
new mode 100755
index e901786..8e93e9e
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java
@@ -1,155 +1,156 @@
-/*
- * 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.testing.processors;
-
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
-import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
- at RunWith(JMock.class)
-public class RestartEveryNTestClassProcessorTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final Factory<TestClassProcessor> factory = context.mock(Factory.class);
-    private final TestClassProcessor delegate = context.mock(TestClassProcessor.class);
-    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
-    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
-    private final TestClassRunInfo test3 = context.mock(TestClassRunInfo.class, "test3");
-    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private RestartEveryNTestClassProcessor processor = new RestartEveryNTestClassProcessor(factory, 2);
-
-    @Test
-    public void onFirstTestCreatesDelegateProcessor() {
-        context.checking(new Expectations() {{
-            one(factory).create();
-            will(returnValue(delegate));
-
-            one(delegate).startProcessing(resultProcessor);
-            one(delegate).processTestClass(test1);
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-    }
-
-    @Test
-    public void onNthTestEndsProcessingOnDelegateProcessor() {
-        context.checking(new Expectations() {{
-            one(factory).create();
-            will(returnValue(delegate));
-
-            one(delegate).startProcessing(resultProcessor);
-            one(delegate).processTestClass(test1);
-            one(delegate).processTestClass(test2);
-            one(delegate).stop();
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.processTestClass(test2);
-    }
-
-    @Test
-    public void onNPlus1TestCreatesNewDelegateProcessor() {
-        context.checking(new Expectations() {{
-            one(factory).create();
-            will(returnValue(delegate));
-
-            one(delegate).startProcessing(resultProcessor);
-            one(delegate).processTestClass(test1);
-            one(delegate).processTestClass(test2);
-            one(delegate).stop();
-
-            TestClassProcessor delegate2 = context.mock(TestClassProcessor.class, "delegate2");
-
-            one(factory).create();
-            will(returnValue(delegate2));
-
-            one(delegate2).startProcessing(resultProcessor);
-            one(delegate2).processTestClass(test3);
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.processTestClass(test2);
-        processor.processTestClass(test3);
-    }
-
-    @Test
-    public void onEndOfProcessingEndsProcessingOnDelegateProcessor() {
-        context.checking(new Expectations() {{
-            one(factory).create();
-            will(returnValue(delegate));
-
-            one(delegate).startProcessing(resultProcessor);
-            one(delegate).processTestClass(test1);
-            one(delegate).stop();
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.stop();
-    }
-
-    @Test
-    public void onEndOfProcessingDoesNothingWhenNoTestsReceived() {
-        processor.stop();
-    }
-
-    @Test
-    public void onEndOfProcessingDoesNothingWhenOnNthTest() {
-        context.checking(new Expectations() {{
-            one(factory).create();
-            will(returnValue(delegate));
-
-            one(delegate).startProcessing(resultProcessor);
-            one(delegate).processTestClass(test1);
-            one(delegate).processTestClass(test2);
-            one(delegate).stop();
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.processTestClass(test2);
-        processor.stop();
-    }
-
-    @Test
-    public void usesSingleBatchWhenNEqualsZero() {
-        processor = new RestartEveryNTestClassProcessor(factory, 0);
-
-        context.checking(new Expectations() {{
-            one(factory).create();
-            will(returnValue(delegate));
-
-            one(delegate).startProcessing(resultProcessor);
-            one(delegate).processTestClass(test1);
-            one(delegate).processTestClass(test2);
-            one(delegate).stop();
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.processTestClass(test2);
-        processor.stop();
-    }
-}
+/*
+ * 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.testing.processors;
+
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class RestartEveryNTestClassProcessorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @SuppressWarnings("unchecked")
+    private final Factory<TestClassProcessor> factory = context.mock(Factory.class);
+    private final TestClassProcessor delegate = context.mock(TestClassProcessor.class);
+    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
+    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
+    private final TestClassRunInfo test3 = context.mock(TestClassRunInfo.class, "test3");
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
+    private RestartEveryNTestClassProcessor processor = new RestartEveryNTestClassProcessor(factory, 2);
+
+    @Test
+    public void onFirstTestCreatesDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+    }
+
+    @Test
+    public void onNthTestEndsProcessingOnDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+    }
+
+    @Test
+    public void onNPlus1TestCreatesNewDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+
+            TestClassProcessor delegate2 = context.mock(TestClassProcessor.class, "delegate2");
+
+            one(factory).create();
+            will(returnValue(delegate2));
+
+            one(delegate2).startProcessing(resultProcessor);
+            one(delegate2).processTestClass(test3);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+        processor.processTestClass(test3);
+    }
+
+    @Test
+    public void onEndOfProcessingEndsProcessingOnDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.stop();
+    }
+
+    @Test
+    public void onEndOfProcessingDoesNothingWhenNoTestsReceived() {
+        processor.stop();
+    }
+
+    @Test
+    public void onEndOfProcessingDoesNothingWhenOnNthTest() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+        processor.stop();
+    }
+
+    @Test
+    public void usesSingleBatchWhenNEqualsZero() {
+        processor = new RestartEveryNTestClassProcessor(factory, 0);
+
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+        processor.stop();
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy
new file mode 100644
index 0000000..8ad5615
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResultTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.testing.results;
+
+
+import org.gradle.api.internal.tasks.testing.DefaultTestDescriptor
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent
+import org.gradle.api.internal.tasks.testing.TestStartEvent
+import org.gradle.api.tasks.testing.TestResult.ResultType
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 10/14/11
+ */
+public class DefaultTestResultTest extends Specification {
+
+    def "construct itself from the state"() {
+        expect:
+        def state = new TestState(new DefaultTestDescriptor("12", "FooTest", "shouldWork"), new TestStartEvent(100L), new HashMap());
+        state.completed(new TestCompleteEvent(200L, ResultType.SKIPPED))
+
+        when:
+        def result = new DefaultTestResult(state)
+
+        then:
+        result.getStartTime() == 100L
+        result.getEndTime() == 200L
+        result.resultType == ResultType.SKIPPED
+        result.exceptions.is(state.failures)
+        result.testCount == state.testCount
+        result.successfulTestCount == state.successfulCount
+        result.failedTestCount == state.failedCount
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
index 317c6fd..310e567 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
@@ -15,341 +15,172 @@
  */
 package org.gradle.api.internal.tasks.testing.results
 
-import org.gradle.api.internal.tasks.testing.TestCompleteEvent
-import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
-import org.gradle.api.internal.tasks.testing.TestStartEvent
-import org.gradle.api.tasks.testing.TestDescriptor
-import org.gradle.api.tasks.testing.TestListener
-import org.gradle.api.tasks.testing.TestResult
 import org.gradle.api.tasks.testing.TestResult.ResultType
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
 import org.junit.Test
-import org.junit.runner.RunWith
-import static org.gradle.util.Matchers.*
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
+import spock.lang.Issue
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.testing.*
+import org.gradle.api.tasks.testing.*
 
- at RunWith(JMock.class)
-class TestListenerAdapterTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final TestListener listener = context.mock(TestListener.class)
-    private final TestListenerAdapter adapter = new TestListenerAdapter(listener)
+class TestListenerAdapterTest extends Specification {
 
-    @Test
-    public void createsAResultForATest() {
-        TestDescriptorInternal test = test('id')
-
-        context.checking {
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.startTime, equalTo(100L))
-                assertThat(result.endTime, equalTo(200L))
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(1L))
-                assertThat(result.failedTestCount, equalTo(0L))
-            }
-        }
+    def listener = Mock(TestListener.class)
+    def outputListener = Mock(TestOutputListener.class)
+    TestListenerAdapter adapter = new TestListenerAdapter(listener, outputListener)
+
+    public void notifiesBefore() {
+        given:
+        TestDescriptor test = new DefaultTestDescriptor("id", "Foo", "bar");
 
+        when:
+        adapter.started(test, new TestStartEvent(100L))
+
+        then:
+        1 * listener.beforeTest({ it instanceof DecoratingTestDescriptor && it.descriptor == test })
+        0 * _._
+    }
+
+    public void notifiesAfter() {
+        given:
+        TestDescriptor test = new DefaultTestDescriptor("id", "Foo", "bar");
+
+        when:
         adapter.started(test, new TestStartEvent(100L))
         adapter.completed('id', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeTest(_)
+        1 * listener.afterTest(
+                { it instanceof DecoratingTestDescriptor && it.descriptor == test },
+                { it.successfulTestCount == 1 && it.testCount == 1 && it.failedTestCount == 0 }
+        )
+        0 * _._
     }
 
-    @Test
     public void createsAResultForATestWithFailure() {
+        given:
         RuntimeException failure = new RuntimeException()
+        TestDescriptor test = new DefaultTestDescriptor("15", "Foo", "bar");
 
-        TestDescriptorInternal test = test('id')
-
-        context.checking {
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(result.resultType, equalTo(ResultType.FAILURE))
-                assertThat(result.exception, sameInstance(failure))
-                assertThat(result.exceptions, equalTo([failure]))
-                assertThat(result.startTime, equalTo(100L))
-                assertThat(result.endTime, equalTo(200L))
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(0L))
-                assertThat(result.failedTestCount, equalTo(1L))
-            }
-        }
-
+        when:
         adapter.started(test, new TestStartEvent(100L))
-        adapter.failure('id', failure)
-        adapter.completed('id', new TestCompleteEvent(200L))
+        adapter.failure('15', failure)
+        adapter.completed('15', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeTest(_)
+        1 * listener.afterTest({ it.descriptor == test },
+                { it.successfulTestCount == 0 && it.testCount == 1 && it.failedTestCount == 1 && it.exception.is(failure) })
+        0 * _._
     }
 
-    @Test
     public void createsAResultForATestWithMultipleFailures() {
+        given:
         RuntimeException failure1 = new RuntimeException()
         RuntimeException failure2 = new RuntimeException()
+        TestDescriptor test = new DefaultTestDescriptor("15", "Foo", "bar");
 
-        TestDescriptorInternal test = test('id')
-
-        context.checking {
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(result.resultType, equalTo(ResultType.FAILURE))
-                assertThat(result.exception, sameInstance(failure1))
-                assertThat(result.exceptions, equalTo([failure1, failure2]))
-                assertThat(result.startTime, equalTo(100L))
-                assertThat(result.endTime, equalTo(200L))
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(0L))
-                assertThat(result.failedTestCount, equalTo(1L))
-            }
-        }
-
+        when:
         adapter.started(test, new TestStartEvent(100L))
-        adapter.failure('id', failure1)
-        adapter.failure('id', failure2)
-        adapter.completed('id', new TestCompleteEvent(200L))
-    }
-
-    @Test
-    public void createsAResultForASkippedTest() {
-        TestDescriptorInternal test = test('id')
-
-        context.checking {
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(result.resultType, equalTo(ResultType.SKIPPED))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.startTime, equalTo(100L))
-                assertThat(result.endTime, equalTo(200L))
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(0L))
-                assertThat(result.failedTestCount, equalTo(0L))
-            }
-        }
+        adapter.failure('15', failure1)
+        adapter.failure('15', failure2)
+        adapter.completed('15', new TestCompleteEvent(200L))
 
-        adapter.started(test, new TestStartEvent(100L))
-        adapter.completed('id', new TestCompleteEvent(200L, ResultType.SKIPPED))
+        then:
+        1 * listener.afterTest(_,
+                { it.exception.is(failure1) && it.exceptions == [failure1, failure2] })
     }
 
-    @Test
     public void createsAnAggregateResultForEmptyTestSuite() {
-        TestDescriptorInternal suite = suite('id')
-
-        context.checking {
-            one(listener).beforeSuite(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.startTime, equalTo(100L))
-                assertThat(result.endTime, equalTo(200L))
-                assertThat(result.testCount, equalTo(0L))
-                assertThat(result.successfulTestCount, equalTo(0L))
-                assertThat(result.failedTestCount, equalTo(0L))
-            }
-        }
+        given:
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("15", "FastTests");
 
+        when:
         adapter.started(suite, new TestStartEvent(100L))
-        adapter.completed('id', new TestCompleteEvent(200L))
+        adapter.completed('15', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeSuite({it.descriptor == suite})
+        1 * listener.afterSuite({it.descriptor == suite}, {
+            it.testCount == 0 && it.resultType == ResultType.SUCCESS
+        })
+        0 * _._
     }
 
-    @Test
     public void createsAnAggregateResultForTestSuiteWithPassedTest() {
-        TestDescriptorInternal suite = suite('id')
-        TestDescriptorInternal test = test('testid')
-
-        context.checking {
-            one(listener).beforeSuite(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(t.parent.descriptor, equalTo(suite))
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-            }
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(1L))
-                assertThat(result.failedTestCount, equalTo(0L))
-            }
-        }
+        given:
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("suiteId", "FastTests");
+        TestDescriptor test = new DefaultTestDescriptor("testId", "DogTest", "shouldBarkAtStrangers");
 
+        when:
         adapter.started(suite, new TestStartEvent(100L))
-        adapter.started(test, new TestStartEvent(100L, 'id'))
-        adapter.completed('testid', new TestCompleteEvent(200L))
-        adapter.completed('id', new TestCompleteEvent(200L))
+        adapter.started(test, new TestStartEvent(100L, 'suiteId'))
+        adapter.completed('testId', new TestCompleteEvent(200L))
+        adapter.completed('suiteId', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeSuite({it.descriptor == suite})
+        1 * listener.beforeTest({it.descriptor == test})
+        1 * listener.afterTest({it.descriptor == test}, _ as TestResult)
+        1 * listener.afterSuite({it.descriptor == suite}, { it.testCount == 1 })
+        0 * _._
     }
 
-    @Test
     public void createsAnAggregateResultForTestSuiteWithFailedTest() {
-        TestDescriptorInternal suite = suite('id')
-        TestDescriptorInternal ok = test('ok')
-        TestDescriptorInternal broken = test('broken')
-
-        context.checking {
-            one(listener).beforeSuite(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(ok))
-                assertThat(t.parent.descriptor, equalTo(suite))
-            }
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(broken))
-                assertThat(t.parent.descriptor, equalTo(suite))
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(result.resultType, equalTo(ResultType.FAILURE))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.testCount, equalTo(2L))
-                assertThat(result.successfulTestCount, equalTo(1L))
-                assertThat(result.failedTestCount, equalTo(1L))
-            }
-        }
+        given:
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("suiteId", "FastTests");
+        TestDescriptor ok = new DefaultTestDescriptor("okId", "DogTest", "shouldBarkAtStrangers");
+        TestDescriptor broken = new DefaultTestDescriptor("brokenId", "DogTest", "shouldDrinkMilk");
 
+        when:
         adapter.started(suite, new TestStartEvent(100L))
-        adapter.started(ok, new TestStartEvent(100L, 'id'))
-        adapter.started(broken, new TestStartEvent(100L, 'id'))
-        adapter.completed('ok', new TestCompleteEvent(200L))
-        adapter.failure('broken', new RuntimeException())
-        adapter.completed('broken', new TestCompleteEvent(200L))
-        adapter.completed('id', new TestCompleteEvent(200L))
+        adapter.started(ok, new TestStartEvent(100L, 'suiteId'))
+        adapter.started(broken, new TestStartEvent(100L, 'suiteId'))
+        adapter.completed('okId', new TestCompleteEvent(200L))
+        adapter.failure('brokenId', new RuntimeException())
+        adapter.completed('brokenId', new TestCompleteEvent(200L))
+        adapter.completed('suiteId', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeSuite({it.descriptor == suite})
+        1 * listener.beforeTest({it.descriptor == ok && it.parent.descriptor == suite})
+        1 * listener.beforeTest({it.descriptor == broken && it.parent.descriptor == suite})
+        1 * listener.afterTest({it.descriptor == ok}, _ as TestResult)
+        1 * listener.afterTest({it.descriptor == broken}, _ as TestResult)
+        1 * listener.afterSuite({it.descriptor == suite}, { it.testCount == 2 && it.failedTestCount == 1 && it.successfulTestCount == 1 })
+        0 * _._
     }
 
-    @Test
     public void createsAnAggregateResultForTestSuiteWithSkippedTest() {
-        TestDescriptorInternal suite = suite('id')
-        TestDescriptorInternal test = test('testid')
-
-        context.checking {
-            one(listener).beforeSuite(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(t.parent, nullValue())
-            }
-            one(listener).beforeTest(withParam(notNullValue()))
-            will { TestDescriptor t ->
-                assertThat(t.descriptor, equalTo(test))
-                assertThat(t.parent.descriptor, equalTo(suite))
-            }
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(0L))
-                assertThat(result.failedTestCount, equalTo(0L))
-            }
-        }
+        given:
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("suiteId", "FastTests");
+        TestDescriptor test = new DefaultTestDescriptor("testId", "DogTest", "shouldBarkAtStrangers");
 
+        when:
         adapter.started(suite, new TestStartEvent(100L))
-        adapter.started(test, new TestStartEvent(100L, 'id'))
-        adapter.completed('testid', new TestCompleteEvent(200L, ResultType.SKIPPED))
-        adapter.completed('id', new TestCompleteEvent(200L))
+        adapter.started(test, new TestStartEvent(100L, 'suiteId'))
+        adapter.completed('testId', new TestCompleteEvent(200L, ResultType.SKIPPED))
+        adapter.completed('suiteId', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeSuite({it.descriptor == suite})
+        1 * listener.beforeTest({it.descriptor == test && it.parent.descriptor == suite})
+        1 * listener.afterTest({it.descriptor == test}, _ as TestResult)
+        1 * listener.afterSuite({it.descriptor == suite},
+                { it.resultType == ResultType.SUCCESS && it.testCount == 1 && it.failedTestCount == 0 && it.successfulTestCount == 0 })
+        0 * _._
     }
 
     @Test
     public void createsAnAggregateResultForTestSuiteWithNestedSuites() {
-        TestDescriptorInternal root = suite('root')
-        TestDescriptorInternal suite1 = suite('suite1')
-        TestDescriptorInternal suite2 = suite('suite2')
-        TestDescriptorInternal ok = test('ok')
-        TestDescriptorInternal broken = test('broken')
-
-        context.checking {
-            one(listener).beforeSuite(withParam(notNullValue()))
-            one(listener).beforeSuite(withParam(notNullValue()))
-            one(listener).beforeTest(withParam(notNullValue()))
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            one(listener).beforeSuite(withParam(notNullValue()))
-            one(listener).beforeTest(withParam(notNullValue()))
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite1))
-                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(1L))
-                assertThat(result.failedTestCount, equalTo(0L))
-            }
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite2))
-                assertThat(result.resultType, equalTo(ResultType.FAILURE))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.testCount, equalTo(1L))
-                assertThat(result.successfulTestCount, equalTo(0L))
-                assertThat(result.failedTestCount, equalTo(1L))
-            }
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(root))
-                assertThat(result.resultType, equalTo(ResultType.FAILURE))
-                assertThat(result.exception, nullValue())
-                assertThat(result.exceptions, isEmpty())
-                assertThat(result.testCount, equalTo(2L))
-                assertThat(result.successfulTestCount, equalTo(1L))
-                assertThat(result.failedTestCount, equalTo(1L))
-            }
-        }
-
+        given:
+        TestDescriptor root = new DefaultTestSuiteDescriptor("root", "AllTests");
+        TestDescriptor suite1 = new DefaultTestSuiteDescriptor("suite1", "FastTests");
+        TestDescriptor suite2 = new DefaultTestSuiteDescriptor("suite2", "SlowTests");
+        TestDescriptor ok = new DefaultTestDescriptor("ok", "DogTest", "shouldBarkAtStrangers");
+        TestDescriptor broken = new DefaultTestDescriptor("broken", "DogTest", "shouldDrinkMilk");
+
+        when:
         adapter.started(root, new TestStartEvent(100L))
         adapter.started(suite1, new TestStartEvent(100L, 'root'))
         adapter.started(ok, new TestStartEvent(100L, 'suite1'))
@@ -361,53 +192,131 @@ class TestListenerAdapterTest {
         adapter.completed('broken', new TestCompleteEvent(200L))
         adapter.completed('suite2', new TestCompleteEvent(200L))
         adapter.completed('root', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeSuite({it.descriptor == root})
+        1 * listener.beforeSuite({it.descriptor == suite1})
+        1 * listener.beforeSuite({it.descriptor == suite2})
+
+
+        1 * listener.beforeTest({it.descriptor == ok && it.parent.descriptor == suite1})
+        1 * listener.beforeTest({it.descriptor == broken && it.parent.descriptor == suite2})
+
+        1 * listener.afterTest({it.descriptor == ok}, _ as TestResult)
+        1 * listener.afterTest({it.descriptor == broken}, _ as TestResult)
+
+        1 * listener.afterSuite({it.descriptor == root}, { it.successfulTestCount == 1 && it.testCount == 2 && it.resultType == ResultType.FAILURE})
+        1 * listener.afterSuite({it.descriptor == suite1}, { it.successfulTestCount == 1 && it.testCount == 1 && it.resultType == ResultType.SUCCESS})
+        1 * listener.afterSuite({it.descriptor == suite2}, { it.successfulTestCount == 0 && it.testCount == 1 && it.resultType == ResultType.FAILURE})
+
+        0 * _._
     }
 
-    @Test
     public void createsAnAggregateResultForTestSuiteWithFailure() {
-        TestDescriptorInternal suite = suite('id')
-        TestDescriptorInternal test = test('testid')
+        given:
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("id", "FastTests");
+        TestDescriptor test = new DefaultTestDescriptor("testid", "DogTest", "shouldBarkAtStrangers");
         RuntimeException failure = new RuntimeException()
 
-        context.checking {
-            one(listener).beforeSuite(withParam(notNullValue()))
-            one(listener).beforeTest(withParam(notNullValue()))
-            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
-            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
-            will { TestDescriptor t, TestResult result ->
-                assertThat(t.descriptor, equalTo(suite))
-                assertThat(result.resultType, equalTo(ResultType.FAILURE))
-                assertThat(result.exception, sameInstance(failure))
-                assertThat(result.exceptions, equalTo([failure]))
-            }
-        }
-
+        when:
         adapter.started(suite, new TestStartEvent(100L))
         adapter.started(test, new TestStartEvent(100L, 'id'))
         adapter.completed('testid', new TestCompleteEvent(200L, ResultType.SKIPPED))
         adapter.failure('id', failure)
         adapter.completed('id', new TestCompleteEvent(200L))
+
+        then:
+        1 * listener.beforeSuite({it.descriptor == suite})
+        1 * listener.beforeTest({it.descriptor == test && it.parent.descriptor == suite})
+        1 * listener.afterTest({it.descriptor == test}, _ as TestResult)
+        1 * listener.afterSuite({it.descriptor == suite},
+                { it.resultType == ResultType.FAILURE && it.exception.is(failure) && it.exceptions == [failure] })
+        0 * _._
     }
 
-    private TestDescriptorInternal test(String id) {
-        TestDescriptorInternal test = context.mock(TestDescriptorInternal.class, id)
-        context.checking {
-            allowing(test).getId()
-            will(returnValue(id))
-            allowing(test).isComposite()
-            will(returnValue(false))
-        }
-        return test
+    def "notifies output listener"() {
+        given:
+        def event = new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, "hey!")
+        def test = new DefaultTestDescriptor("testid", "DogTest", "shouldBarkAtStrangers");
+
+        when:
+        adapter.started(test, new TestStartEvent(100L))
+        adapter.output("testid", event)
+
+        then:
+        1 * outputListener.onOutput({it.descriptor == test}, event)
+    }
+
+    @Issue("GRADLE-2035")
+    def "behaves gracefully even if cannot match output to the test"() {
+        given:
+        def event = new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, "hey!")
+
+        when:
+        adapter.output("testid", event)
+
+        then:
+        1 * outputListener.onOutput({it instanceof UnknownTestDescriptor}, event)
     }
 
-    private TestDescriptorInternal suite(String id) {
-        TestDescriptorInternal test = context.mock(TestDescriptorInternal.class, id)
-        context.checking {
-            allowing(test).getId()
-            will(returnValue(id))
-            allowing(test).isComposite()
-            will(returnValue(true))
-        }
-        return test
+    @Issue("GRADLE-2035")
+    def "output can be received after test completion"() {
+        given:
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("1", "DogTest");
+        TestDescriptor test1 = new DefaultTestDescriptor("1.1", "DogTest", "shouldBarkAtStrangers");
+
+        def woof = new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, "woof woof!")
+
+        when:
+        adapter.started(suite, new TestStartEvent(100L))
+        adapter.started(test1, new TestStartEvent(100L, '1'))
+
+        adapter.completed('1.1', new TestCompleteEvent(200L))
+        adapter.output('1.1', woof)
+
+        adapter.completed('1', new TestCompleteEvent(200L))
+
+        then:
+        1 * outputListener.onOutput({ it.id == '1' }, woof)
+    }
+
+    @Issue("GRADLE-2035")
+    def "outputs for completed tests use parent descriptors"() {
+        given:
+        TestDescriptor root = new DefaultTestSuiteDescriptor("1", "CanineSuite");
+        TestDescriptor suite = new DefaultTestSuiteDescriptor("1.1", "DogTest");
+        TestDescriptor test1 = new DefaultTestDescriptor("1.1.1", "DogTest", "shouldBarkAtStrangers");
+        TestDescriptor test2 = new DefaultTestDescriptor("1.1.2", "DogTest", "shouldLoiter");
+
+        def woof = new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, "woof woof!")
+        def grrr = new DefaultTestOutputEvent(TestOutputEvent.Destination.StdErr, "grrr!")
+
+        when:
+        adapter.started(root, new TestStartEvent(1))
+        adapter.started(suite, new TestStartEvent(1, '1'))
+        adapter.started(test1, new TestStartEvent(1, '1.1'))
+        adapter.started(test2, new TestStartEvent(1, '1.1'))
+
+        adapter.completed('1.1.1', new TestCompleteEvent(1))
+
+        //test completed but we receive an output
+        adapter.output('1.1.1', woof)
+
+        adapter.completed('1.1.2', new TestCompleteEvent(1))
+        adapter.completed('1.1', new TestCompleteEvent(1))
+
+        //the suite is completed but for we receive an output
+        adapter.output('1.1.1', woof)
+
+        adapter.completed('1', new TestCompleteEvent(1))
+
+        //all tests are completed but we receive an output for one of the other tests
+        adapter.output('1.1.2', grrr)
+
+        then:
+        1 * outputListener.onOutput({ it instanceof DecoratingTestDescriptor && it.id == '1.1' }, woof)
+        1 * outputListener.onOutput({ it instanceof DecoratingTestDescriptor && it.id == '1' }, woof)
+        1 * outputListener.onOutput({ it instanceof UnknownTestDescriptor }, grrr)
+        0 * outputListener._
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
old mode 100644
new mode 100755
index 7d0774e..9005e22
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
@@ -1,136 +1,118 @@
-/*
- * 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.testing.results
-
-import org.gradle.api.tasks.testing.TestDescriptor
-import org.gradle.api.tasks.testing.TestResult
-import org.gradle.util.JUnit4GroovyMockery
-import org.jmock.integration.junit4.JMock
-import org.junit.runner.RunWith
-import org.slf4j.Logger
-import org.junit.Test
-import org.junit.Before
-import static org.junit.Assert.*
-import org.gradle.api.internal.tasks.testing.TestSuiteExecutionException
-
- at RunWith(JMock.class)
-public class TestSummaryListenerTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final Logger logger = context.mock(Logger.class)
-    private final RuntimeException failure = new RuntimeException()
-    private final TestSummaryListener listener = new TestSummaryListener(logger)
-
-    @Before
-    public void setup() {
-        context.checking {
-            ignoring(logger).debug(withParam(anything()), (Object) withParam(anything()))
-        }
-    }
-
-    @Test
-    public void logsSuccessfulTests() {
-        context.checking {
-            one(logger).info('{} PASSED', '<test>')
-        }
-        listener.afterTest(test('<test>'), result(TestResult.ResultType.SUCCESS))
-    }
-
-    @Test
-    public void logsSkippedTests() {
-        context.checking {
-            one(logger).info('{} SKIPPED', '<test>')
-        }
-        listener.afterTest(test('<test>'), result(TestResult.ResultType.SKIPPED))
-    }
-
-    @Test
-    public void logsFailedTestExecution() {
-        context.checking {
-            one(logger).info('{} FAILED: {}', '<test>', failure)
-            one(logger).error('Test {} FAILED', '<class>')
-        }
-        listener.afterTest(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
-    }
-
-    @Test
-    public void logsFailedTestExecutionWhenTestHasNoClass() {
-        context.checking {
-            one(logger).error('{} FAILED: {}', '<test>', failure)
-        }
-        listener.afterTest(test('<test>'), result(TestResult.ResultType.FAILURE))
-    }
-
-    @Test
-    public void logsFailedSuiteExecution() {
-        context.checking {
-            one(logger).info('{} FAILED: {}', '<test>', failure)
-            one(logger).error('Test {} FAILED', '<class>')
-        }
-        listener.afterSuite(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
-    }
-
-    @Test
-    public void logsFailedSuiteExecutionWhenSuiteHasNoClass() {
-        context.checking {
-            one(logger).error('{} FAILED: {}', '<test>', failure)
-        }
-        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE))
-    }
-
-    @Test
-    public void logsFailedSuiteExecutionWhenSuiteHasNoException() {
-        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, null))
-    }
-
-    @Test
-    public void logsSuiteInternalException() {
-        TestSuiteExecutionException failure = new TestSuiteExecutionException('broken', new RuntimeException())
-        context.checking {
-            one(logger).error('Execution for <test> FAILED', failure)
-        }
-        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, failure))
-    }
-
-    @Test
-    public void doesNotLogFailedClassMoreThanOnce() {
-        context.checking {
-            ignoring(logger).info(withParam(anything()), (Object) withParam(anything()), (Object) withParam(anything()))
-            one(logger).error('Test {} FAILED', '<class>')
-        }
-        listener.afterTest(test('<test1>', '<class>'), result(TestResult.ResultType.FAILURE))
-        listener.afterTest(test('<test2>', '<class>'), result(TestResult.ResultType.FAILURE))
-        listener.afterSuite(test('<test3>', '<class>'), result(TestResult.ResultType.FAILURE))
-    }
-
-    @Test
-    public void usesRootSuiteResultsToDetermineIfTestsHasFailed() {
-        listener.afterSuite(test('<test>', null, null), result(TestResult.ResultType.FAILURE, null, 3, 5))
-        assertTrue(listener.hadFailures())
-    }
-
-    private TestResult result(TestResult.ResultType type, Throwable failure = this.failure, long failures = 0, long total = 0) {
-        return [getResultType: {-> type}, getException: {-> failure}, getTestCount: {-> total}, getFailedTestCount: {-> failures}] as TestResult
-    }
-
-    private TestDescriptor test(String name, String className = null, TestDescriptor parent = [:] as TestDescriptor) {
-        return [toString: {-> name}, getClassName: {-> className}, getParent: {-> parent}] as TestDescriptor
-    }
-}
-
+/*
+ * 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.testing.results
+
+import org.gradle.api.internal.tasks.testing.TestSuiteExecutionException
+import org.gradle.api.tasks.testing.TestDescriptor
+import org.gradle.api.tasks.testing.TestResult
+import org.slf4j.Logger
+import spock.lang.Specification
+import static org.junit.Assert.assertTrue
+
+public class TestSummaryListenerTest extends Specification {
+    
+    def logger = Mock(Logger.class)
+    def failure = new RuntimeException()
+    def listener = new TestSummaryListener(logger)
+
+    def "logs successful tests"() {
+        when:
+        listener.afterTest(test('<test>'), result(TestResult.ResultType.SUCCESS))
+        then:
+        1 * logger.info('{} PASSED', '<test>')
+        0 * logger._
+    }
+
+    def "logs skipped tests"() {
+        when:
+        listener.afterTest(test('<test>'), result(TestResult.ResultType.SKIPPED))
+        then:
+        1 * logger.info('{} SKIPPED', '<test>')
+        0 * logger._
+    }
+
+    def "logs failed test execution"() {
+        when:
+        listener.afterTest(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
+        then:
+        1 * logger.info('{} FAILED: {}', '<test>', failure)
+        1 * logger.error('Test {} FAILED', '<class>')
+        0 * logger._
+    }
+
+    def "logs failed test execution when test has no class"() {
+        when:
+        listener.afterTest(test('<test>'), result(TestResult.ResultType.FAILURE))
+        then:
+        1 * logger.error('{} FAILED: {}', '<test>', failure)
+        0 * logger._
+    }
+
+    def "logs failed suite execution"() {
+        when:
+        listener.afterSuite(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
+        then:
+        1 * logger.info('{} FAILED: {}', '<test>', failure)
+        1 * logger.error('Test {} FAILED', '<class>')
+        0 * logger._
+    }
+
+    def "logs Failed Suite Execution When Suite Has No Class"() {
+        when:
+        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE))
+        then:
+        1 * logger.error('{} FAILED: {}', '<test>', failure)
+        0 * logger._
+    }
+
+    def "logs Failed Suite Execution When Suite Has No Exception"() {
+        expect:
+        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, null))
+    }
+
+    def "logs Suite Internal Exception"() {
+        given:
+        def failure = new TestSuiteExecutionException('broken', new RuntimeException())
+        when:
+        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, failure))
+        then:
+        1 * logger.error('Execution for <test> FAILED', failure)
+        0 * logger._
+    }
+
+    def "does Not Log Failed Class More Than Once"() {
+        when:
+        listener.afterTest(test('<test1>', '<class>'), result(TestResult.ResultType.FAILURE))
+        listener.afterTest(test('<test2>', '<class>'), result(TestResult.ResultType.FAILURE))
+        listener.afterSuite(test('<test3>', '<class>'), result(TestResult.ResultType.FAILURE))
+        then:
+        1 * logger.error('Test {} FAILED', '<class>')
+    }
+
+    def "uses Root Suite Results To Determine If Tests Has Failed"() {
+        listener.afterSuite(test('<test>', null, null), result(TestResult.ResultType.FAILURE, null, 3, 5))
+        assertTrue(listener.hadFailures())
+    }
+
+    private TestResult result(TestResult.ResultType type, Throwable failure = this.failure, long failures = 0, long total = 0) {
+        return [getResultType: {-> type}, getException: {-> failure}, getTestCount: {-> total}, getFailedTestCount: {-> failures}] as TestResult
+    }
+
+    private TestDescriptor test(String name, String className = null, TestDescriptor parent = [:] as TestDescriptor) {
+        return [toString: {-> name}, getClassName: {-> className}, getParent: {-> parent}] as TestDescriptor
+    }
+}
+
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactorySpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactorySpec.groovy
new file mode 100644
index 0000000..14f5963
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGListenerAdapterFactorySpec.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.testing.testng
+
+import spock.lang.Specification
+import org.testng.IConfigurationListener2
+import org.testng.ITestResult
+import org.testng.ITestListener
+
+class TestNGListenerAdapterFactorySpec extends Specification {
+    TestNGListenerAdapterFactory factory = new TestNGListenerAdapterFactory(getClass().classLoader)
+    MyListener listener = Mock()
+
+    def "adapts to IConfigurationListener2 interface if available on class path"() {
+        expect:
+        factory.createAdapter(listener) instanceof IConfigurationListener2
+    }
+
+    /**
+     * covered by {@link org.gradle.testing.testng.TestNGIntegrationTest}
+     */
+    def "adapts to internal IConfigurationListener interface if available on class path"() {}
+
+    def "adapter forwards all IConfigurationListener2 calls"() {
+        IConfigurationListener2 adapter = factory.createAdapter(listener)
+        ITestResult result = Mock()
+
+        when:
+        adapter.beforeConfiguration(result)
+        adapter.onConfigurationSuccess(result)
+        adapter.onConfigurationFailure(result)
+        adapter.onConfigurationSkip(result)
+
+        then:
+        1 * listener.beforeConfiguration(result)
+        1 * listener.onConfigurationSuccess(result)
+        1 * listener.onConfigurationFailure(result)
+        1 * listener.onConfigurationSkip(result)
+    }
+
+    def "adapter forwards all ITestListener calls"() {
+        ITestListener adapter = factory.createAdapter(listener)
+        ITestResult result = Mock()
+
+        when:
+        adapter.onTestStart(result)
+        adapter.onTestSuccess(result)
+        adapter.onTestFailure(result)
+        adapter.onTestSkipped(result)
+        adapter.onTestFailedButWithinSuccessPercentage(result)
+        adapter.onStart(null) // we don't mock ITestContext as it would require us to put Guice on test class path
+        adapter.onFinish(null)
+
+        then:
+        1 * listener.onTestStart(result)
+        1 * listener.onTestSuccess(result)
+        1 * listener.onTestFailure(result)
+        1 * listener.onTestSkipped(result)
+        1 * listener.onTestFailedButWithinSuccessPercentage(result)
+        1 * listener.onStart(null)
+        1 * listener.onFinish(null)
+    }
+}
+
+interface MyListener extends ITestListener, IConfigurationListener2 {}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
index 1ed12c3..0bfeb2d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.JavaVersion;
-import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
old mode 100644
new mode 100755
index 6db4a91..819e852
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
@@ -1,141 +1,141 @@
-/*
- * 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.testing.worker;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.Factory;
-import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
-import org.gradle.api.internal.tasks.testing.TestResultProcessor;
-import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
-import org.gradle.messaging.remote.ObjectConnection;
-import org.gradle.process.JavaForkOptions;
-import org.gradle.process.internal.JavaExecHandleBuilder;
-import org.gradle.process.internal.WorkerProcess;
-import org.gradle.process.internal.WorkerProcessBuilder;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.List;
-
-import static java.util.Arrays.asList;
-import static org.hamcrest.Matchers.notNullValue;
-
- at RunWith(JMock.class)
-public class ForkingTestClassProcessorTest {
-    private final JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    private final WorkerTestClassProcessorFactory processorFactory = context.mock(WorkerTestClassProcessorFactory.class);
-    private final Factory<WorkerProcessBuilder> workerFactory = context.mock(Factory.class);
-    private final WorkerProcess workerProcess = context.mock(WorkerProcess.class);
-    private final RemoteTestClassProcessor worker = context.mock(RemoteTestClassProcessor.class);
-    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
-    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
-    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
-    private final List<File> appClassPath = asList(new File("classpath.jar"));
-    private final JavaForkOptions options = context.mock(JavaForkOptions.class);
-    private final Action<WorkerProcessBuilder> action = context.mock(Action.class);
-    private final ForkingTestClassProcessor processor = new ForkingTestClassProcessor(workerFactory, processorFactory, options, appClassPath, action);
-
-    @Test
-    public void onFirstTestCaseStartsWorkerProcess() {
-        expectWorkerProcessStarted();
-        context.checking(new Expectations() {{
-            one(worker).processTestClass(test1);
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-    }
-
-    @Test
-    public void onSubsequentTestCaseForwardsTestToWorkerProcess() {
-        expectWorkerProcessStarted();
-        context.checking(new Expectations() {{
-            one(worker).processTestClass(test1);
-            one(worker).processTestClass(test2);
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.processTestClass(test2);
-    }
-
-    @Test
-    public void onEndProcessingWaitsForWorkerProcessToStop() {
-        expectWorkerProcessStarted();
-        context.checking(new Expectations() {{
-            one(worker).processTestClass(test1);
-            one(worker).stop();
-            one(workerProcess).waitForStop();
-        }});
-
-        processor.startProcessing(resultProcessor);
-        processor.processTestClass(test1);
-        processor.stop();
-    }
-
-    @Test
-    public void onEndProcessingDoesNothingIfNoTestsProcessed() {
-        processor.startProcessing(resultProcessor);
-        processor.stop();
-    }
-
-    private void expectWorkerProcessStarted() {
-        context.checking(new Expectations() {{
-            WorkerProcessBuilder builder = context.mock(WorkerProcessBuilder.class);
-            ObjectConnection connection = context.mock(ObjectConnection.class);
-            JavaExecHandleBuilder javaCommandBuilder = context.mock(JavaExecHandleBuilder.class);
-
-            one(workerFactory).create();
-            will(returnValue(builder));
-
-            one(builder).worker(with(notNullValue(TestWorker.class)));
-
-            one(builder).applicationClasspath(appClassPath);
-
-            one(builder).setLoadApplicationInSystemClassLoader(true);
-
-            one(action).execute(builder);
-            
-            allowing(builder).getJavaCommand();
-            will(returnValue(javaCommandBuilder));
-
-            one(options).copyTo(javaCommandBuilder);
-
-            one(builder).build();
-            will(returnValue(workerProcess));
-
-            allowing(workerProcess).getConnection();
-            will(returnValue(connection));
-
-            one(connection).addIncoming(TestResultProcessor.class, resultProcessor);
-            
-            one(connection).addOutgoing(RemoteTestClassProcessor.class);
-            will(returnValue(worker));
-
-            one(workerProcess).start();
-
-            one(worker).startProcessing();
-        }});
-    }
-}
+/*
+ * 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.testing.worker;
+
+import org.gradle.api.Action;
+import org.gradle.internal.Factory;
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.process.JavaForkOptions;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.process.internal.WorkerProcess;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.notNullValue;
+
+ at RunWith(JMock.class)
+public class ForkingTestClassProcessorTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final WorkerTestClassProcessorFactory processorFactory = context.mock(WorkerTestClassProcessorFactory.class);
+    @SuppressWarnings("unchecked")
+    private final Factory<WorkerProcessBuilder> workerFactory = context.mock(Factory.class);
+    private final WorkerProcess workerProcess = context.mock(WorkerProcess.class);
+    private final RemoteTestClassProcessor worker = context.mock(RemoteTestClassProcessor.class);
+    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
+    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
+    private final List<File> appClassPath = asList(new File("classpath.jar"));
+    private final JavaForkOptions options = context.mock(JavaForkOptions.class);
+    @SuppressWarnings("unchecked")
+    private final Action<WorkerProcessBuilder> action = context.mock(Action.class);
+    private final ForkingTestClassProcessor processor = new ForkingTestClassProcessor(workerFactory, processorFactory, options, appClassPath, action);
+
+    @Test
+    public void onFirstTestCaseStartsWorkerProcess() {
+        expectWorkerProcessStarted();
+        context.checking(new Expectations() {{
+            one(worker).processTestClass(test1);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+    }
+
+    @Test
+    public void onSubsequentTestCaseForwardsTestToWorkerProcess() {
+        expectWorkerProcessStarted();
+        context.checking(new Expectations() {{
+            one(worker).processTestClass(test1);
+            one(worker).processTestClass(test2);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+    }
+
+    @Test
+    public void onEndProcessingWaitsForWorkerProcessToStop() {
+        expectWorkerProcessStarted();
+        context.checking(new Expectations() {{
+            one(worker).processTestClass(test1);
+            one(worker).stop();
+            one(workerProcess).waitForStop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.stop();
+    }
+
+    @Test
+    public void onEndProcessingDoesNothingIfNoTestsProcessed() {
+        processor.startProcessing(resultProcessor);
+        processor.stop();
+    }
+
+    private void expectWorkerProcessStarted() {
+        context.checking(new Expectations() {{
+            WorkerProcessBuilder builder = context.mock(WorkerProcessBuilder.class);
+            ObjectConnection connection = context.mock(ObjectConnection.class);
+            JavaExecHandleBuilder javaCommandBuilder = context.mock(JavaExecHandleBuilder.class);
+
+            one(workerFactory).create();
+            will(returnValue(builder));
+
+            one(builder).worker(with(notNullValue(TestWorker.class)));
+
+            one(builder).applicationClasspath(appClassPath);
+
+            one(builder).setLoadApplicationInSystemClassLoader(true);
+
+            one(action).execute(builder);
+            
+            allowing(builder).getJavaCommand();
+            will(returnValue(javaCommandBuilder));
+
+            one(options).copyTo(javaCommandBuilder);
+
+            one(builder).build();
+            will(returnValue(workerProcess));
+
+            allowing(workerProcess).getConnection();
+            will(returnValue(connection));
+
+            one(connection).addIncoming(TestResultProcessor.class, resultProcessor);
+            
+            one(connection).addOutgoing(RemoteTestClassProcessor.class);
+            will(returnValue(worker));
+
+            one(workerProcess).start();
+
+            one(worker).startProcessing();
+        }});
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy
old mode 100644
new mode 100755
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
index 8a0acb7..3874554 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ApplicationPluginTest.groovy
@@ -25,12 +25,13 @@ import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
 import spock.lang.Specification
 import org.gradle.api.tasks.Sync
+import org.gradle.api.file.CopySpec
 
 class ApplicationPluginTest extends Specification {
     private final Project project = HelperUtil.createRootProject();
     private final ApplicationPlugin plugin = new ApplicationPlugin();
 
-    public void appliesJavaPluginAndAddsConventionObjectWithDefaultValues() {
+    def "applies JavaPlugin and adds convention object with default values"() {
         when:
         plugin.apply(project)
 
@@ -39,9 +40,10 @@ class ApplicationPluginTest extends Specification {
         project.convention.getPlugin(ApplicationPluginConvention.class) != null
         project.applicationName == project.name
         project.mainClassName == null
+        project.applicationDistribution instanceof CopySpec
     }
 
-    public void addsRunTasksToProject() {
+    def "adds run task to project"() {
         when:
         plugin.apply(project)
 
@@ -52,7 +54,7 @@ class ApplicationPluginTest extends Specification {
         task Matchers.dependsOn('classes')
     }
 
-    public void addsCreateStartScriptsTaskToProject() {
+    public void "adds startScripts task to project"() {
         when:
         plugin.apply(project)
 
@@ -63,7 +65,7 @@ class ApplicationPluginTest extends Specification {
         task.outputDir == project.file('build/scripts')
     }
 
-    public void addsInstallTaskToProjectWithDefaultTarget() {
+    public void "adds installApp task to project with default target"() {
         when:
         plugin.apply(project)
 
@@ -73,7 +75,7 @@ class ApplicationPluginTest extends Specification {
         task.destinationDir == project.file("build/install/${project.applicationName}")
     }
 
-    public void addsDistZipTaskToProject() {
+    def "adds distZip task to project"() {
         when:
         plugin.apply(project)
 
@@ -83,7 +85,7 @@ class ApplicationPluginTest extends Specification {
         task.archiveName == "${project.applicationName}.zip"
     }
 
-    public void canChangeApplicationName() {
+    public void "applicationName is configurable"() {
         when:
         plugin.apply(project)
         project.applicationName = "SuperApp";
@@ -99,7 +101,7 @@ class ApplicationPluginTest extends Specification {
         distZipTask.archiveName == "SuperApp.zip"
     }
     
-    public void setMainClassNameSetsMainInRunTask() {
+    public void "mainClassName in project delegates to main in run task"() {
         when:
         plugin.apply(project)
         project.mainClassName = "Acme";
@@ -109,7 +111,7 @@ class ApplicationPluginTest extends Specification {
         run.main == "Acme"
     }
 
-    public void setMainClassNameSetsMainClassNameInCreateStartScriptsTask() {
+    public void "mainClassName in project delegates to mainClassName in startScripts task"() {
         when:
         plugin.apply(project);
         project.mainClassName = "Acme"
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
index 8dbf227..b6de9c9 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.api.internal.project.DefaultProject
 import org.gradle.util.HelperUtil
 import org.junit.Before
 import org.junit.Test
-import static org.junit.Assert.*
+import static org.junit.Assert.assertEquals
 
 /**
  * @author Hans Dockter
@@ -43,7 +43,7 @@ class BasePluginConventionTest {
     }
 
     @Test public void dirsRelativeToBuildDir() {
-        project.buildDirName = 'mybuild'
+        project.buildDir = project.file('mybuild')
         convention.distsDirName = 'mydists'
         assertEquals(project.file('mybuild/mydists'), convention.distsDir)
         convention.libsDirName = 'mylibs'
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
index d223d71..d057909 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
@@ -18,157 +18,199 @@ package org.gradle.api.plugins
 
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Dependency
 import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.api.internal.tasks.DefaultTaskDependency
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
 import org.gradle.api.tasks.Delete
 import org.gradle.api.tasks.Upload
-import org.gradle.util.HelperUtil
-import org.junit.Test
-import static org.gradle.util.Matchers.*
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-import org.gradle.api.Task
 import org.gradle.api.tasks.bundling.Jar
-import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.bundling.Tar
-import org.gradle.api.artifacts.Dependency
-import org.gradle.util.WrapUtil
-import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import static org.gradle.util.Matchers.dependsOn
+import static org.hamcrest.Matchers.instanceOf
 
 /**
  * @author Hans Dockter
  */
-class BasePluginTest {
+class BasePluginTest extends Specification {
     private final Project project = HelperUtil.createRootProject()
     private final BasePlugin plugin = new BasePlugin()
 
-    @Test public void addsConventionObject() {
+    public void addsConventionObjects() {
+        when:
         plugin.apply(project)
 
-        assertThat(project.convention.plugins.base, instanceOf(BasePluginConvention))
+        then:
+        project.convention.plugins.base instanceof BasePluginConvention
+        project.extensions.findByType(DefaultArtifactPublicationSet) != null
     }
 
-    @Test public void createsTasksAndAppliesMappings() {
+    public void createsTasksAndAppliesMappings() {
+        when:
         plugin.apply(project)
 
-        def task = project.tasks[BasePlugin.CLEAN_TASK_NAME]
-        assertThat(task, instanceOf(Delete))
-        assertThat(task, dependsOn())
-        assertThat(task.targetFiles.files, equalTo([project.buildDir] as Set))
+        then:
+        def clean = project.tasks[BasePlugin.CLEAN_TASK_NAME]
+        clean instanceOf(Delete)
+        clean dependsOn()
+        clean.targetFiles.files == [project.buildDir] as Set
 
-        task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
-        assertThat(task, instanceOf(DefaultTask))
+        and:
+        def assemble = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assemble instanceOf(DefaultTask)
     }
 
-    @Test public void addsRulesWhenAConfigurationIsAdded() {
+    public void assembleTaskBuildsThePublishedArtifacts() {
+        given:
+        def someJar = project.tasks.add('someJar', Jar)
+
+        when:
         plugin.apply(project)
+        project.artifacts.archives someJar
 
-        assertThat(project.tasks.rules.size(), equalTo(3))
+        then:
+        def assemble = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assemble dependsOn('someJar')
     }
 
-    @Test public void addsImplicitTasksForConfiguration() {
+    public void addsRulesWhenAConfigurationIsAdded() {
+        when:
         plugin.apply(project)
 
-        Task producer = [getName: {-> 'producer'}] as Task
-        PublishArtifact artifactStub = [getBuildDependencies: {-> new DefaultTaskDependency().add(producer) }] as PublishArtifact
-
-        project.configurations.getByName('archives').addArtifact(artifactStub)
-
-        def task = project.tasks['buildArchives']
-        assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn('producer'))
-
-        task = project.tasks['uploadArchives']
-        assertThat(task, instanceOf(Upload))
-        assertThat(task, dependsOn('producer'))
-
-        project.configurations.add('conf').addArtifact(artifactStub)
-
-        task = project.tasks['buildConf']
-        assertThat(task, instanceOf(DefaultTask))
-        assertThat(task, dependsOn('producer'))
-
-        task = project.tasks['uploadConf']
-        assertThat(task, instanceOf(Upload))
-        assertThat(task, dependsOn('producer'))
-        assertThat(task.configuration, sameInstance(project.configurations.conf))
+        then:
+        !project.tasks.rules.empty
     }
 
-    @Test public void addsACleanRule() {
+    public void addsImplicitTasksForConfiguration() {
+        given:
+        def someJar = project.tasks.add('someJar', Jar)
+
+        when:
         plugin.apply(project)
+        project.artifacts.archives someJar
+
+        then:
+        def buildArchives = project.tasks['buildArchives']
+        buildArchives instanceOf(DefaultTask)
+        buildArchives dependsOn('someJar')
+
+        and:
+        def uploadArchives = project.tasks['uploadArchives']
+        uploadArchives instanceOf(Upload)
+        uploadArchives dependsOn('someJar')
+
+        when:
+        project.configurations.add('conf')
+        project.artifacts.conf someJar
+
+        then:
+        def buildConf = project.tasks['buildConf']
+        buildConf instanceOf(DefaultTask)
+        buildConf dependsOn('someJar')
+
+        and:
+        def uploadConf = project.tasks['uploadConf']
+        uploadConf instanceOf(Upload)
+        uploadConf dependsOn('someJar')
+        uploadConf.configuration == project.configurations.conf
+    }
 
+    public void addsACleanRule() {
+        given:
         Task test = project.task('test')
         test.outputs.files(project.buildDir)
 
+        when:
+        plugin.apply(project)
+
+        then:
         Task cleanTest = project.tasks['cleanTest']
-        assertThat(cleanTest, instanceOf(Delete))
-        assertThat(cleanTest.delete, equalTo([test.outputs.files] as Set))
+        cleanTest instanceOf(Delete)
+        cleanTest.delete == [test.outputs.files] as Set
     }
 
-    @Test public void cleanRuleIsCaseSensitive() {
-        plugin.apply(project)
-
+    public void cleanRuleIsCaseSensitive() {
+        given:
         project.task('testTask')
         project.task('12')
 
-        assertThat(project.tasks.findByName('cleantestTask'), nullValue())
-        assertThat(project.tasks.findByName('cleanTesttask'), nullValue())
-        assertThat(project.tasks.findByName('cleanTestTask'), instanceOf(Delete.class))
-        assertThat(project.tasks.findByName('clean12'), instanceOf(Delete.class))
+        when:
+        plugin.apply(project)
+
+        then:
+        project.tasks.findByName('cleantestTask') == null
+        project.tasks.findByName('cleanTesttask') == null
+        project.tasks.findByName('cleanTestTask') instanceof Delete
+        project.tasks.findByName('clean12') instanceof Delete
     }
 
-    @Test public void appliesMappingsForArchiveTasks() {
+    public void appliesMappingsForArchiveTasks() {
+        when:
         plugin.apply(project)
-
         project.version = '1.0'
 
-        def task = project.tasks.add('someJar', Jar)
-        assertThat(task.destinationDir, equalTo(project.libsDir))
-        assertThat(task.version, equalTo(project.version))
-        assertThat(task.baseName, equalTo(project.archivesBaseName))
-
-        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn('someJar'))
-
-        task = project.tasks.add('someZip', Zip)
-        assertThat(task.destinationDir, equalTo(project.distsDir))
-        assertThat(task.version, equalTo(project.version))
-        assertThat(task.baseName, equalTo(project.archivesBaseName))
-
-        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn('someJar', 'someZip'))
-
-        task = project.tasks.add('someTar', Tar)
-        assertThat(task.destinationDir, equalTo(project.distsDir))
-        assertThat(task.version, equalTo(project.version))
-        assertThat(task.baseName, equalTo(project.archivesBaseName))
-
-        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn('someJar', 'someZip', 'someTar'))
+        then:
+        def someJar = project.tasks.add('someJar', Jar)
+        someJar.destinationDir == project.libsDir
+        someJar.version == project.version
+        someJar.baseName == project.archivesBaseName
+
+        and:
+        def someZip = project.tasks.add('someZip', Zip)
+        someZip.destinationDir == project.distsDir
+        someZip.version == project.version
+        someZip.baseName == project.archivesBaseName
+
+        and:
+        def someTar = project.tasks.add('someTar', Tar)
+        someTar.destinationDir == project.distsDir
+        someTar.version == project.version
+        someTar.baseName == project.archivesBaseName
     }
 
-    @Test public void usesNullVersionWhenProjectVersionNotSpecified() {
+    public void usesNullVersionWhenProjectVersionNotSpecified() {
+        when:
         plugin.apply(project)
 
+        then:
         def task = project.tasks.add('someJar', Jar)
-        assertThat(task.version, nullValue())
+        task.version == null
 
+        when:
         project.version = '1.0'
 
-        task = project.tasks.add('someOtherJar', Jar)
-        assertThat(task.version, equalTo('1.0'))
+        then:
+        task.version == '1.0'
     }
 
-    @Test public void addsConfigurationsToTheProject() {
+    public void addsConfigurationsToTheProject() {
+        when:
         plugin.apply(project)
 
-        assertThat(project.status, equalTo("integration"))
+        then:
+        def defaultConfig = project.configurations[Dependency.DEFAULT_CONFIGURATION]
+        defaultConfig.extendsFrom == [] as Set
+        defaultConfig.visible
+        defaultConfig.transitive
+
+        and:
+        def archives = project.configurations[Dependency.ARCHIVES_CONFIGURATION]
+        defaultConfig.extendsFrom == [] as Set
+        archives.visible
+        archives.transitive
+    }
+
+    public void addsEveryPublishedArtifactToTheArchivesConfiguration() {
+        PublishArtifact artifact = Mock()
 
-        def configuration = project.configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
-        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(WrapUtil.toSet(Dependency.ARCHIVES_CONFIGURATION)))
-        assertTrue(configuration.visible)
-        assertTrue(configuration.transitive)
+        when:
+        plugin.apply(project)
+        project.configurations.add("custom").artifacts.add(artifact)
 
-        configuration = project.configurations.getByName(Dependency.ARCHIVES_CONFIGURATION)
-        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(WrapUtil.toSet()))
-        assertTrue(configuration.visible)
-        assertTrue(configuration.transitive)
+        then:
+        project.configurations[Dependency.ARCHIVES_CONFIGURATION].artifacts.contains(artifact)
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
index 8fa73ce..b7223ab 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.api.plugins
 
 import org.gradle.api.Project
 import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.compile.GroovyCompile
 import org.gradle.api.tasks.javadoc.Groovydoc
 import org.gradle.util.HelperUtil
@@ -67,7 +68,6 @@ class GroovyBasePluginTest {
         def task = project.tasks['compileCustomGroovy']
         assertThat(task, instanceOf(GroovyCompile.class))
         assertThat(task.description, equalTo('Compiles the custom Groovy source.'))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.groovy))
         assertThat(task, dependsOn('compileCustomJava'))
     }
 
@@ -82,9 +82,9 @@ class GroovyBasePluginTest {
     @Test public void configuresAdditionalTasksDefinedByTheBuildScript() {
         groovyBasePlugin.apply(project)
 
-        def task = project.createTask('otherGroovydoc', type: Groovydoc)
+        def task = project.task('otherGroovydoc', type: Groovydoc)
         assertThat(task.destinationDir, equalTo(new File(project.docsDir, 'groovydoc')))
-        assertThat(task.docTitle, equalTo(project.apiDocTitle))
-        assertThat(task.windowTitle, equalTo(project.apiDocTitle))
+        assertThat(task.docTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
+        assertThat(task.windowTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
index a38d8fc..64c7f7b 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.api.plugins
 
 import org.gradle.api.Project
 import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.compile.GroovyCompile
 import org.gradle.api.tasks.javadoc.Groovydoc
 import org.gradle.util.HelperUtil
@@ -72,13 +73,11 @@ class GroovyPluginTest {
         def task = project.tasks['compileGroovy']
         assertThat(task, instanceOf(GroovyCompile.class))
         assertThat(task.description, equalTo('Compiles the main Groovy source.'))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.groovy))
         assertThat(task, dependsOn(JavaPlugin.COMPILE_JAVA_TASK_NAME))
 
         task = project.tasks['compileTestGroovy']
         assertThat(task, instanceOf(GroovyCompile.class))
         assertThat(task.description, equalTo('Compiles the test Groovy source.'))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.test.groovy))
         assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
     }
 
@@ -101,7 +100,7 @@ class GroovyPluginTest {
         assertThat(task, instanceOf(Groovydoc.class))
         assertThat(task.destinationDir, equalTo(new File(project.docsDir, 'groovydoc')))
         assertThat(task.source.files, equalTo(project.sourceSets.main.groovy.files))
-        assertThat(task.docTitle, equalTo(project.apiDocTitle))
-        assertThat(task.windowTitle, equalTo(project.apiDocTitle))
+        assertThat(task.docTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
+        assertThat(task.windowTitle, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
index 09c5964..b002d2a 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
@@ -13,24 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-
- 
 package org.gradle.api.plugins
 
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.compile.Compile
 import org.gradle.api.tasks.javadoc.Javadoc
+import org.gradle.api.tasks.testing.Test
 import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
-import spock.lang.Specification
-import static org.gradle.util.WrapUtil.toLinkedSet
-import org.gradle.api.tasks.testing.Test
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
+import spock.lang.Specification
+import static org.gradle.util.Matchers.sameCollection
+import static org.gradle.util.WrapUtil.toLinkedSet
 
 /**
  * @author Hans Dockter
@@ -61,28 +60,74 @@ class JavaBasePluginTest extends Specification {
         def set = project.sourceSets.custom
         set.java.srcDirs == toLinkedSet(project.file('src/custom/java'))
         set.resources.srcDirs == toLinkedSet(project.file('src/custom/resources'))
-        set.classesDir == new File(project.buildDir, 'classes/custom')
-        Matchers.builtBy('customClasses').matches(set.classes)
+        set.output.classesDir == new File(project.buildDir, 'classes/custom')
+        Matchers.builtBy('customClasses').matches(set.output)
 
         def processResources = project.tasks['processCustomResources']
         processResources.description == 'Processes the custom resources.'
         processResources instanceof Copy
         Matchers.dependsOn().matches(processResources)
-        processResources.destinationDir == project.sourceSets.custom.classesDir
-        processResources.defaultSource == project.sourceSets.custom.resources
+        processResources.destinationDir == project.sourceSets.custom.output.resourcesDir
+        def resources = processResources.source
+        resources sameCollection(project.sourceSets.custom.resources)
 
         def compileJava = project.tasks['compileCustomJava']
         compileJava.description == 'Compiles the custom Java source.'
         compileJava instanceof Compile
         Matchers.dependsOn().matches(compileJava)
-        compileJava.defaultSource == project.sourceSets.custom.java
         compileJava.classpath.is(project.sourceSets.custom.compileClasspath)
-        compileJava.destinationDir == project.sourceSets.custom.classesDir
-
+        compileJava.destinationDir == project.sourceSets.custom.output.classesDir
+        def sources = compileJava.source
+        sources sameCollection(project.sourceSets.custom.java)
         def classes = project.tasks['customClasses']
         classes.description == 'Assembles the custom classes.'
         classes instanceof DefaultTask
         Matchers.dependsOn('processCustomResources', 'compileCustomJava').matches(classes)
+        classes.dependsOn.contains project.sourceSets.custom.output.dirs
+    }
+    
+    void tasksReflectChangesToSourceSetConfiguration() {
+        def classesDir = project.file('target/classes')
+        def resourcesDir = project.file('target/resources')
+
+        when:
+        javaBasePlugin.apply(project)
+        project.sourceSets.add('custom')
+        project.sourceSets.custom.output.classesDir = classesDir
+        project.sourceSets.custom.output.resourcesDir = resourcesDir
+
+        then:
+        def processResources = project.tasks['processCustomResources']
+        processResources.destinationDir == resourcesDir
+
+        def compileJava = project.tasks['compileCustomJava']
+        compileJava.destinationDir == classesDir
+    }
+
+    void createsConfigurationsForNewSourceSet() {
+        when:
+        javaBasePlugin.apply(project)
+        def sourceSet = project.sourceSets.add('custom')
+
+        then:
+        def compile = project.configurations.customCompile
+        compile.transitive
+        !compile.visible
+        compile.extendsFrom == [] as Set
+        compile.description == 'Classpath for compiling the custom sources.'
+
+        and:
+        def runtime = project.configurations.customRuntime
+        runtime.transitive
+        !runtime.visible
+        runtime.extendsFrom == [compile] as Set
+        runtime.description == 'Classpath for running the compiled custom classes.'
+
+        and:
+        def runtimeClasspath = sourceSet.runtimeClasspath
+        def compileClasspath = sourceSet.compileClasspath
+        compileClasspath == compile
+        runtimeClasspath sameCollection(sourceSet.output + runtime)
     }
 
     void appliesMappingsToTasksDefinedByBuildScript() {
@@ -90,23 +135,23 @@ class JavaBasePluginTest extends Specification {
         javaBasePlugin.apply(project)
 
         then:
-        def compile = project.createTask('customCompile', type: Compile)
+        def compile = project.task('customCompile', type: Compile)
         compile.sourceCompatibility == project.sourceCompatibility.toString()
 
-        def test = project.createTask('customTest', type: Test.class)
+        def test = project.task('customTest', type: Test.class)
         test.workingDir == project.projectDir
         test.testResultsDir == project.testResultsDir
         test.testReportDir == project.testReportDir
 
-        def javadoc = project.createTask('customJavadoc', type: Javadoc)
+        def javadoc = project.task('customJavadoc', type: Javadoc)
         javadoc.destinationDir == project.file("$project.docsDir/javadoc")
-        javadoc.title == project.apiDocTitle
+        javadoc.title == project.extensions.getByType(ReportingExtension).apiDocTitle
     }
 
     void appliesMappingsToCustomJarTasks() {
         when:
         javaBasePlugin.apply(project)
-        def task = project.createTask('customJar', type: Jar)
+        def task = project.task('customJar', type: Jar)
 
         then:
         Matchers.dependsOn().matches(task)
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
index 1927ce3..351357d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
@@ -45,7 +45,7 @@ class JavaPluginConventionTest {
     public final TemporaryFolder tmpDir = new TemporaryFolder()
 
     @Before public void setUp() {
-        project.convention.plugins.reportingBase = new ReportingBasePluginConvention(project)
+        project.plugins.apply(ReportingBasePlugin)
         convention = new JavaPluginConvention(project)
     }
 
@@ -56,18 +56,22 @@ class JavaPluginConventionTest {
         assertEquals('docs', convention.docsDirName)
         assertEquals('test-results', convention.testResultsDirName)
         assertEquals('tests', convention.testReportDirName)
-        assertEquals(JavaVersion.VERSION_1_5, convention.sourceCompatibility)
-        assertEquals(JavaVersion.VERSION_1_5, convention.targetCompatibility)
+    }
+
+    @Test public void sourceCompatibilityDefaultsToCurentJvmVersion() {
+        JavaVersion currentJvmVersion = JavaVersion.toVersion(System.properties["java.version"]);
+        assertEquals(currentJvmVersion, convention.sourceCompatibility)
+        assertEquals(currentJvmVersion, convention.targetCompatibility)
     }
 
     @Test public void canConfigureSourceSets() {
         File dir = new File('classes-dir')
         convention.sourceSets {
             main {
-                classesDir = dir
+                output.classesDir = dir
             }
         }
-        assertThat(convention.sourceSets.main.classesDir, equalTo(project.file(dir)))
+        assertThat(convention.sourceSets.main.output.classesDir, equalTo(project.file(dir)))
     }
     
     @Test public void testDefaultDirs() {
@@ -75,7 +79,7 @@ class JavaPluginConventionTest {
     }
 
     @Test public void testDynamicDirs() {
-        project.buildDirName = 'mybuild'
+        project.buildDir = project.file('mybuild')
         checkDirs()
     }
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
index ac3d071..35070db 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
@@ -20,9 +20,9 @@ import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Dependency
-import org.gradle.api.internal.artifacts.configurations.Configurations
 import org.gradle.api.internal.plugins.EmbeddableJavaProject
 import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.bundling.Jar
@@ -32,13 +32,11 @@ import org.gradle.util.HelperUtil
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.junit.Test
-import static org.gradle.util.Matchers.builtBy
-import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.Matchers.*
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.gradle.util.WrapUtil.toSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import static org.gradle.util.Matchers.sameCollection
 
 /**
  * @author Hans Dockter
@@ -55,35 +53,46 @@ class JavaPluginTest {
         assertThat(project.convention.plugins.embeddedJavaProject, instanceOf(EmbeddableJavaProject))
         assertThat(project.convention.plugins.embeddedJavaProject.rebuildTasks, equalTo([BasePlugin.CLEAN_TASK_NAME, JavaBasePlugin.BUILD_TASK_NAME]))
         assertThat(project.convention.plugins.embeddedJavaProject.buildTasks, equalTo([JavaBasePlugin.BUILD_TASK_NAME]))
-        assertThat(project.convention.plugins.embeddedJavaProject.runtimeClasspath, sameInstance(project.sourceSets.main.runtimeClasspath))
+        assertThat(project.convention.plugins.embeddedJavaProject.runtimeClasspath, notNullValue())
     }
 
     @Test public void addsConfigurationsToTheProject() {
         javaPlugin.apply(project)
 
-        def configuration = project.configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME)
-        assertFalse(configuration.visible)
-        assertTrue(configuration.transitive)
+        def compile = project.configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME)
+        assertThat(compile.extendsFrom, equalTo(toSet()))
+        assertFalse(compile.visible)
+        assertTrue(compile.transitive)
 
-        configuration = project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME)
-        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(JavaPlugin.COMPILE_CONFIGURATION_NAME)))
-        assertFalse(configuration.visible)
-        assertTrue(configuration.transitive)
+        def runtime = project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME)
+        assertThat(runtime.extendsFrom, equalTo(toSet(compile)))
+        assertFalse(runtime.visible)
+        assertTrue(runtime.transitive)
 
-        configuration = project.configurations.getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME)
-        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(JavaPlugin.COMPILE_CONFIGURATION_NAME)))
-        assertFalse(configuration.visible)
-        assertTrue(configuration.transitive)
+        def testCompile = project.configurations.getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME)
+        assertThat(testCompile.extendsFrom, equalTo(toSet(compile)))
+        assertFalse(testCompile.visible)
+        assertTrue(testCompile.transitive)
 
-        configuration = project.configurations.getByName(JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME)
-        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME, JavaPlugin.RUNTIME_CONFIGURATION_NAME)))
-        assertFalse(configuration.visible)
-        assertTrue(configuration.transitive)
+        def testRuntime = project.configurations.getByName(JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME)
+        assertThat(testRuntime.extendsFrom, equalTo(toSet(runtime, testCompile)))
+        assertFalse(testRuntime.visible)
+        assertTrue(testRuntime.transitive)
 
-        configuration = project.configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
-        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(Dependency.ARCHIVES_CONFIGURATION, JavaPlugin.RUNTIME_CONFIGURATION_NAME)))
+        def defaultConfig = project.configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
+        assertThat(defaultConfig.extendsFrom, equalTo(toSet(runtime)))
     }
 
+    @Test public void addsJarAsPublication() {
+        javaPlugin.apply(project)
+        
+        def runtimeConfiguration = project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME)
+        assertThat(runtimeConfiguration.artifacts.collect { it.archiveTask }, equalTo([project.tasks.getByName(JavaPlugin.JAR_TASK_NAME)]))
+
+        def archivesConfiguration = project.configurations.getByName(Dependency.ARCHIVES_CONFIGURATION)
+        assertThat(archivesConfiguration.artifacts.collect { it.archiveTask }, equalTo([project.tasks.getByName(JavaPlugin.JAR_TASK_NAME)]))
+    }
+    
     @Test public void createsStandardSourceSetsAndAppliesMappings() {
         javaPlugin.apply(project)
 
@@ -91,8 +100,9 @@ class JavaPluginTest {
         assertThat(set.java.srcDirs, equalTo(toLinkedSet(project.file('src/main/java'))))
         assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/main/resources'))))
         assertThat(set.compileClasspath, sameInstance(project.configurations.compile))
-        assertThat(set.classesDir, equalTo(new File(project.buildDir, 'classes/main')))
-        assertThat(set.classes, builtBy(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(set.output.classesDir, equalTo(new File(project.buildDir, 'classes/main')))
+        assertThat(set.output.resourcesDir, equalTo(new File(project.buildDir, 'resources/main')))
+        assertThat(set.output, builtBy(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.runtime))
         assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/main')))
 
@@ -101,8 +111,9 @@ class JavaPluginTest {
         assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/test/resources'))))
         assertThat(set.compileClasspath.sourceCollections, hasItem(project.configurations.testCompile))
         assertThat(set.compileClasspath, hasItem(new File(project.buildDir, 'classes/main')))
-        assertThat(set.classesDir, equalTo(new File(project.buildDir, 'classes/test')))
-        assertThat(set.classes, builtBy(JavaPlugin.TEST_CLASSES_TASK_NAME))
+        assertThat(set.output.classesDir, equalTo(new File(project.buildDir, 'classes/test')))
+        assertThat(set.output.resourcesDir, equalTo(new File(project.buildDir, 'resources/test')))
+        assertThat(set.output, builtBy(JavaPlugin.TEST_CLASSES_TASK_NAME))
         assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.testRuntime))
         assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/main')))
         assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/test')))
@@ -114,11 +125,10 @@ class JavaPluginTest {
         def set = project.sourceSets.add('custom')
         assertThat(set.java.srcDirs, equalTo(toLinkedSet(project.file('src/custom/java'))))
         assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/custom/resources'))))
-        assertThat(set.compileClasspath, sameInstance(project.configurations.compile))
-        assertThat(set.classesDir, equalTo(new File(project.buildDir, 'classes/custom')))
-        assertThat(set.classes, builtBy('customClasses'))
-        assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.runtime))
-        assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/custom')))
+        assertThat(set.compileClasspath, sameInstance(project.configurations.customCompile))
+        assertThat(set.output.classesDir, equalTo(new File(project.buildDir, 'classes/custom')))
+        assertThat(set.output, builtBy('customClasses'))
+        assertThat(set.runtimeClasspath, sameCollection(set.output + project.configurations.customRuntime))
     }
 
     @Test public void createsStandardTasksAndAppliesMappings() {
@@ -127,16 +137,16 @@ class JavaPluginTest {
         def task = project.tasks[JavaPlugin.PROCESS_RESOURCES_TASK_NAME]
         assertThat(task, instanceOf(Copy))
         assertThat(task, dependsOn())
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.resources))
-        assertThat(task.destinationDir, equalTo(project.sourceSets.main.classesDir))
+        assertThat(task.source, sameCollection(project.sourceSets.main.resources))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_JAVA_TASK_NAME]
         assertThat(task, instanceOf(Compile))
         assertThat(task, dependsOn())
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.java))
         assertThat(task.classpath, sameInstance(project.sourceSets.main.compileClasspath))
-        assertThat(task.destinationDir, equalTo(project.sourceSets.main.classesDir))
-
+        assertThat(task.destinationDir, equalTo(project.sourceSets.main.output.classesDir))
+        assertThat(task.source, sameCollection(project.sourceSets.main.java))
+        
         task = project.tasks[JavaPlugin.CLASSES_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
         assertThat(task, dependsOn(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, JavaPlugin.COMPILE_JAVA_TASK_NAME))
@@ -144,15 +154,15 @@ class JavaPluginTest {
         task = project.tasks[JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME]
         assertThat(task, instanceOf(Copy))
         assertThat(task, dependsOn())
-        assertThat(task.defaultSource, equalTo(project.sourceSets.test.resources))
-        assertThat(task.destinationDir, equalTo(project.sourceSets.test.classesDir))
+        assertThat(task.source, sameCollection(project.sourceSets.test.resources))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.resourcesDir))
 
         task = project.tasks[JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME]
         assertThat(task, instanceOf(Compile))
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.test.java))
         assertThat(task.classpath, sameInstance(project.sourceSets.test.compileClasspath))
-        assertThat(task.destinationDir, equalTo(project.sourceSets.test.classesDir))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.test.output.classesDir))
+        assertThat(task.source, sameCollection(project.sourceSets.test.java))
 
         task = project.tasks[JavaPlugin.TEST_CLASSES_TASK_NAME]
         assertThat(task, instanceOf(DefaultTask))
@@ -162,14 +172,14 @@ class JavaPluginTest {
         assertThat(task, instanceOf(org.gradle.api.tasks.testing.Test))
         assertThat(task, dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.classpath, equalTo(project.sourceSets.test.runtimeClasspath))
-        assertThat(task.testClassesDir, equalTo(project.sourceSets.test.classesDir))
+        assertThat(task.testClassesDir, equalTo(project.sourceSets.test.output.classesDir))
         assertThat(task.workingDir, equalTo(project.projectDir))
 
         task = project.tasks[JavaPlugin.JAR_TASK_NAME]
         assertThat(task, instanceOf(Jar))
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.destinationDir, equalTo(project.libsDir))
-        assertThat(task.copyAction.mainSpec.sourcePaths, equalTo([project.sourceSets.main.classes] as Set))
+        assertThat(task.copyAction.mainSpec.sourcePaths, equalTo([project.sourceSets.main.output] as Set))
         assertThat(task.manifest, notNullValue())
         assertThat(task.manifest, not(sameInstance(project.manifest)))
         assertThat(task.manifest.mergeSpecs.size(), equalTo(1))
@@ -188,9 +198,9 @@ class JavaPluginTest {
         assertThat(task, instanceOf(Javadoc))
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.source.files, equalTo(project.sourceSets.main.allJava.files))
-        assertThat(task.classpath, sameCollection(project.files(project.sourceSets.main.classes, project.sourceSets.main.compileClasspath)))
+        assertThat(task.classpath, sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/javadoc")))
-        assertThat(task.title, equalTo(project.apiDocTitle))
+        assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
 
         task = project.tasks["buildArchives"]
         assertThat(task, instanceOf(DefaultTask))
@@ -212,9 +222,9 @@ class JavaPluginTest {
     @Test public void appliesMappingsToTasksAddedByTheBuildScript() {
         javaPlugin.apply(project);
 
-        def task = project.createTask('customTest', type: org.gradle.api.tasks.testing.Test.class)
+        def task = project.task('customTest', type: org.gradle.api.tasks.testing.Test.class)
         assertThat(task.classpath, equalTo(project.sourceSets.test.runtimeClasspath))
-        assertThat(task.testClassesDir, equalTo(project.sourceSets.test.classesDir))
+        assertThat(task.testClassesDir, equalTo(project.sourceSets.test.output.classesDir))
         assertThat(task.workingDir, equalTo(project.projectDir))
         assertThat(task.testResultsDir, equalTo(project.testResultsDir))
         assertThat(task.testReportDir, equalTo(project.testReportDir))
@@ -231,16 +241,16 @@ class JavaPluginTest {
         javaPlugin.apply(appProject);
 
         appProject.dependencies {
-            compile project(path: middleProject.path, configuration: 'compile')
+            compile middleProject
         }
         middleProject.dependencies {
-            compile project(path: commonProject.path, configuration: 'compile')
+            compile commonProject
         }
 
-        Task task = middleProject.tasks[JavaBasePlugin.BUILD_NEEDED_TASK_NAME];
-        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, equalTo([':middle:build', ':common:build'] as Set))
+        Task task = middleProject.tasks['buildNeeded'];
+        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, equalTo([':middle:build', ':common:buildNeeded'] as Set))
 
-        task = middleProject.tasks[JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME];
-        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, equalTo([':middle:build', ':app:build'] as Set))
+        task = middleProject.tasks['buildDependents'];
+        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, equalTo([':middle:build', ':app:buildDependents'] as Set))
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
new file mode 100644
index 0000000..5748ae7
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+// Note: ReportingBasePluginConvention has been deprecated
+public class ReportingBasePluginConventionTest extends Specification {
+
+    Project project = ProjectBuilder.builder().build()
+    ReportingExtension extension = new ReportingExtension(project)
+    ReportingBasePluginConvention convention = new ReportingBasePluginConvention(project, extension)
+
+    def "defaults to reports dir in build dir"() {
+        expect:
+        convention.reportsDirName == ReportingExtension.DEFAULT_REPORTS_DIR_NAME
+        convention.reportsDir == new File(project.buildDir, ReportingExtension.DEFAULT_REPORTS_DIR_NAME)
+    }
+
+    def "can set reports dir by name, relative to build dir"() {
+        when:
+        convention.reportsDirName = "something-else"
+        
+        then:
+        convention.reportsDir == new File(project.buildDir, "something-else")
+        
+        when:
+        project.buildDir = new File(project.buildDir, "new-build-dir")
+
+        then:
+        convention.reportsDir == new File(project.buildDir, "something-else")
+    }
+
+    def "calculates api doc title from project name and version"() {
+        expect:
+        project.version == Project.DEFAULT_VERSION
+
+        and:
+        convention.apiDocTitle == "$project.name API"
+
+        when:
+        project.version = "1.0"
+
+        then:
+        convention.apiDocTitle == "$project.name 1.0 API"
+    }
+
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.java
deleted file mode 100644
index 30da5c8..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.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.api.plugins;
-
-import org.gradle.api.Project;
-import static org.hamcrest.Matchers.*;
-
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.Expectations;
-import static org.junit.Assert.*;
-import org.junit.Test;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
- at RunWith(JMock.class)
-public class ReportingBasePluginConventionTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final ReportingBasePluginConvention convention = new ReportingBasePluginConvention(project);
-    private final File buildDir = new File("build-dir");
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(project).getBuildDir();
-            will(returnValue(buildDir));
-        }});
-    }
-
-    @Test
-    public void defaultValues() {
-        assertThat(convention.getReportsDirName(), equalTo("reports"));
-    }
-
-    @Test
-    public void calculatesReportsDirFromReportsDirName() {
-        context.checking(new Expectations(){{
-            FileResolver fileResolver = context.mock(FileResolver.class);
-            FileResolver buildDirResolver = context.mock(FileResolver.class, "buildDir");
-            allowing(project).getFileResolver();
-            will(returnValue(fileResolver));
-            one(fileResolver).withBaseDir(buildDir);
-            will(returnValue(buildDirResolver));
-            one(buildDirResolver).resolve("new-reports");
-            will(returnValue(new File(buildDir, "new-reports")));
-        }});
-
-        convention.setReportsDirName("new-reports");
-        assertThat(convention.getReportsDir(), equalTo(new File(buildDir, "new-reports")));
-    }
-
-    @Test
-    public void calculatesApiDocTitleFromProjectNameAndVersion() {
-        context.checking(new Expectations(){{
-            allowing(project).getName();
-            will(returnValue("<name>"));
-            one(project).getVersion();
-            will(returnValue(Project.DEFAULT_VERSION));
-        }});
-        assertThat(convention.getApiDocTitle(), equalTo("<name> API"));
-
-        context.checking(new Expectations(){{
-            one(project).getVersion();
-            will(returnValue("<not-the-default>"));
-        }});
-        assertThat(convention.getApiDocTitle(), equalTo("<name> <not-the-default> API"));
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
new file mode 100644
index 0000000..b47b413
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.plugins
+
+import org.gradle.util.HelperUtil
+
+import spock.lang.Specification
+import org.gradle.api.Project
+import org.gradle.api.reporting.ReportingExtension
+
+public class ReportingBasePluginTest extends Specification {
+
+    Project project = HelperUtil.createRootProject();
+    
+    def setup() {
+        project.plugins.apply(ReportingBasePlugin)
+    }
+    
+    def addsTasksAndConventionToProject() {
+        expect:
+        project.convention.plugins.get("reportingBase") instanceof ReportingBasePluginConvention
+    }
+    
+    def "adds reporting extension"() {
+        expect:
+        project.reporting instanceof ReportingExtension
+        
+        project.configure(project) {
+            reporting {
+                baseDir "somewhere"
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.java
deleted file mode 100644
index b5dc652..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.java
+++ /dev/null
@@ -1,32 +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.plugins;
-
-import org.gradle.api.Project;
-import org.gradle.util.HelperUtil;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import org.junit.Test;
-
-public class ReportingBasePluginTest {
-    @Test
-    public void addsTasksAndConventionToProject() {
-        Project project = HelperUtil.createRootProject();
-        new ReportingBasePlugin().apply(project);
-
-        assertThat(project.getConvention().getPlugins().get("reportingBase"), instanceOf(ReportingBasePluginConvention.class));
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
index f9a4db1..0e1b351 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
@@ -24,8 +24,8 @@ import org.gradle.api.tasks.bundling.War
 import org.gradle.util.HelperUtil
 import org.junit.Before
 import org.junit.Test
-import static org.gradle.util.Matchers.*
-import static org.gradle.util.WrapUtil.*
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.WrapUtil.toSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
@@ -82,7 +82,7 @@ class WarPluginTest {
         assertThat(task.destinationDir, equalTo(project.libsDir))
 
         task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
-        assertThat(task, dependsOn(JavaPlugin.JAR_TASK_NAME, WarPlugin.WAR_TASK_NAME))
+        assertThat(task, dependsOn(WarPlugin.WAR_TASK_NAME))
     }
 
     @Test public void dependsOnRuntimeConfig() {
@@ -114,20 +114,18 @@ class WarPluginTest {
         }
 
         def task = project.tasks[WarPlugin.WAR_TASK_NAME]
-        assertThat(task.classpath.files as List, equalTo([project.sourceSets.main.classesDir, runtimeJar, compileJar]))
+        assertThat(task.classpath.files as List, equalTo([project.sourceSets.main.output.classesDir, project.sourceSets.main.output.resourcesDir, runtimeJar, compileJar]))
     }
 
     @Test public void appliesMappingsToArchiveTasks() {
         warPlugin.apply(project)
 
-        def task = project.createTask('customWar', type: War)
+        def task = project.task('customWar', type: War)
         assertThat(task, dependsOn(hasItems(JavaPlugin.CLASSES_TASK_NAME)))
         assertThat(task.destinationDir, equalTo(project.libsDir))
-
-        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn(JavaPlugin.JAR_TASK_NAME, WarPlugin.WAR_TASK_NAME, 'customWar'))
     }
 
-    @Test public void addsDefaultWarToArchiveConfiguration() {
+    @Test public void replacesJarAsPublication() {
         warPlugin.apply(project)
 
         Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy
new file mode 100644
index 0000000..a592345
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/ReportingExtensionTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.reporting
+
+import spock.lang.Specification
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.api.Project
+
+class ReportingExtensionTest extends Specification {
+    
+    Project project = ProjectBuilder.builder().build()
+    ReportingExtension extension = new ReportingExtension(project)
+    
+    def "defaults to reports dir in build dir"() {
+        expect:
+        extension.baseDir == new File(project.buildDir, ReportingExtension.DEFAULT_REPORTS_DIR_NAME)
+
+        when:
+        project.buildDir = project.file("newBuildDir")
+
+        then:
+        extension.baseDir == new File(project.buildDir, ReportingExtension.DEFAULT_REPORTS_DIR_NAME)
+    }
+    
+    def "reports dir can be changed lazily"() {
+        given:
+        def dir = "a"
+
+        when:
+        extension.baseDir = { dir }
+
+        then:
+        extension.baseDir == project.file("a")
+
+        when:
+        dir = "b"
+
+        then:
+        extension.baseDir == project.file("b")
+
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
new file mode 100644
index 0000000..97038a9
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * 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.reporting.internal
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.api.internal.DirectInstantiator
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.api.reporting.Report
+import org.gradle.api.reporting.ReportContainer
+import spock.lang.Specification
+
+class DefaultReportContainerTest extends Specification {
+
+    static Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+
+    static class TestReportContainer extends DefaultReportContainer {
+        TestReportContainer(Closure c) {
+            super(Report, DefaultReportContainerTest.instantiator)
+            
+            c.delegate = new Object() {
+                Report createReport(String name) {
+                    add(SimpleReport, name, Report.OutputType.FILE, new IdentityFileResolver())
+                }
+            }
+            
+            c()
+        }
+    }
+
+    DefaultReportContainer createContainer(Closure c) {
+        instantiator.newInstance(TestReportContainer, c)
+    }
+
+    def container
+
+    def setup() {
+        container = createContainer {
+            createReport("a")
+            createReport("b")
+            createReport("c")
+        }
+    }
+    
+    def "reports given at construction are available"() {
+        when:
+        container.configure { a { } }
+
+        then:
+        notThrown(MissingPropertyException)
+    }
+
+    def "container is immutable"() {
+        when:
+        container.add(new SimpleReport("d", Report.OutputType.FILE, new IdentityFileResolver()))
+        
+        then:
+        thrown(ReportContainer.ImmutableViolationException)
+        
+        when:
+        container.clear()
+
+        then:
+        thrown(ReportContainer.ImmutableViolationException)
+    }
+    
+    def "enable empty by default"() {
+        expect:
+        container.every { !it.enabled } && container.enabled.empty
+    }
+    
+    def "can change enabled"() {
+        when:
+        container.each { it.enabled = false }
+        
+        then:
+        container.enabled.empty
+        
+        when:
+        container.configure {
+            a.enabled true
+            b.enabled true
+        }
+        
+        then:
+        container.enabled.size() == 2
+    }
+
+    def "cannot add report named 'enabled'"() {
+        when:
+        createContainer {
+            createReport "enabled"
+        }
+        
+        then:
+        thrown(InvalidUserDataException)
+    }
+    
+    def "cant access or configure non existent report"() {
+        when:
+        container.configure {
+            dontexist {
+                
+            }
+        }
+        
+        then:
+        thrown(MissingMethodException)
+    }
+
+    def cleanupSpec() {
+        instantiator = null
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskGeneratedReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskGeneratedReportTest.groovy
new file mode 100644
index 0000000..fdec4d8
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskGeneratedReportTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * 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.reporting.internal
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.reporting.Report
+import org.gradle.api.tasks.Copy
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+class TaskGeneratedReportTest extends Specification {
+
+    def "can resolve destination"() {
+        given:
+        Project project = ProjectBuilder.builder().build()
+        Task task = project.task("task", type: Copy)
+        TaskGeneratedReport report = new TaskGeneratedReport("report", Report.OutputType.FILE, task)
+        
+        when:
+        report.destination = "foo"
+        
+        then:
+        report.destination == project.file("foo")
+    }
+
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
new file mode 100644
index 0000000..399b41e
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
@@ -0,0 +1,108 @@
+/*
+ * 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.reporting.internal
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.internal.Instantiator
+import org.gradle.api.reporting.Report
+import org.gradle.api.tasks.Nested
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+
+class TaskReportContainerTest extends Specification {
+
+    final Project project = ProjectBuilder.builder().build()
+    final TestTask task = project.task("testTask", type: TestTask)
+
+    static class TestTask extends DefaultTask {
+        @Nested
+        TaskReportContainer<Report> reports
+    }
+    
+    static class TestReportContainer extends TaskReportContainer<Report> {
+        TestReportContainer(Task task, Closure c) {
+            super(Report, task)
+
+            c.delegate = new Object() {
+                Report file(String name) {
+                    add(TaskGeneratedReport, name, Report.OutputType.FILE, task)
+                }
+                Report dir(String name) {
+                    add(TaskGeneratedReport, name, Report.OutputType.DIRECTORY, task)
+                }
+            }
+
+            c()
+        }
+    }
+
+    def container
+
+    DefaultReportContainer createContainer(Closure c) {
+        container = project.services.get(Instantiator).newInstance(TestReportContainer, task, c)
+        container.all {
+            it.enabled true
+            destination it.name
+        }
+        task.reports = container
+        container
+    }
+
+    List<File> getOutputFiles(task = task) {
+        task.outputs.files.files.toList().sort()
+    }
+
+    List<String> getInputPropertyValue() {
+        task.inputs.properties["reports.enabledReportNames"] as List<String>
+    }
+
+    def "tasks inputs and outputs are wired correctly"() {
+        when:
+        createContainer {
+            dir("b")
+            file("a")
+        }
+
+        then:
+        outputFiles == [container.a.destination, container.b.destination]
+        inputPropertyValue == ["a", "b"]
+
+        when:
+        container.b.enabled false
+
+        then:
+        outputFiles == [container.a.destination]
+        inputPropertyValue == ["a"]
+
+        when:
+        container*.enabled false
+
+        then:
+        outputFiles == []
+        inputPropertyValue == []
+
+        when:
+        container.a.enabled true
+
+        then:
+        outputFiles == [container.a.destination]
+        inputPropertyValue == ["a"]
+    }
+    
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy
index 9369600..21213d4 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy
@@ -15,36 +15,68 @@
  */
 package org.gradle.api.tasks.compile
 
-import org.junit.Test
-import static org.gradle.util.Matchers.*
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
+import spock.lang.Specification
 
-public class AbstractOptionsTest {
-    private final TestOptions options = new TestOptions()
+public class AbstractOptionsTest extends Specification {
+    def "options map contains all properties with non-null values"() {
+        expect:
+        options.optionMap() == map
 
-    @Test
-    public void hasEmptyOptionsMapWhenEverythingIsNull() {
-        assertThat(options.optionMap(), isEmptyMap())
+        where:
+        options                                                                 | map
+        new TestOptions()                                                       | [stringProp: "initial value"]
+        new TestOptions(intProp: 42, stringProp: "new value", objectProp: [21]) | [intProp: 42, stringProp: "new value", objectProp: [21]]
+        new TestOptions(stringProp: null)                                       | [:]
     }
 
-    @Test
-    public void optionsMapIncludesNonNullValues() {
-        assertThat(options.optionMap(), isEmptyMap())
+    def "property names can be mapped"() {
+        def options = new NameMappingOptions(intProp: 42, stringProp: "new value", objectProp: [21])
 
-        options.intProp = 9
-        Map expected = new LinkedHashMap();
-        expected.intProp = 9
-        assertThat(options.optionMap(), equalTo(expected))
+        expect:
+        options.optionMap() == [intProp2: 42, stringProp: "new value", objectProp2: [21]]
+    }
+
+    def "property values can be mapped"() {
+        def options = new ValueMappingOptions(intProp: 42, stringProp: "new value", objectProp: [21])
+
+        expect:
+        options.optionMap() == [intProp: 42, stringProp: "new valuenew value", objectProp: [21]]
+    }
+
+    def "has limitation that a concrete options class must directly extend AbstractOptions"() {
+        def options = new IndirectlyExtendingOptions(intProp: 42, stringProp: "new value", objectProp: [21])
+
+        expect:
+        options.optionMap() == [:]
+    }
 
-        options.stringProp = 'string'
-        expected.stringProp = 'string'
-        assertThat(options.optionMap(), equalTo(expected))
+    static class TestOptions extends AbstractOptions {
+        Integer intProp
+        String stringProp = "initial value"
+        Object objectProp
     }
+
+    static class NameMappingOptions extends AbstractOptions {
+        Integer intProp
+        String stringProp = "initial value"
+        Object objectProp
+
+        Map fieldName2AntMap() {
+            [intProp: 'intProp2', objectProp: 'objectProp2']
+        }
+    }
+
+    static class ValueMappingOptions extends AbstractOptions {
+        Integer intProp
+        String stringProp = "initial value"
+        Object objectProp
+
+        @Override
+        Map fieldValue2AntMap() {
+            [stringProp: { stringProp * 2 }]
+        }
+    }
+
+    static class IndirectlyExtendingOptions extends TestOptions {}
 }
 
-class TestOptions extends AbstractOptions {
-    Integer intProp
-    String stringProp
-    Object objectProp
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy
index be829aa..35023ce 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy
@@ -19,7 +19,6 @@ package org.gradle.api.tasks.compile
 import org.junit.Before
 import org.junit.Test
 import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*
 import static org.gradle.util.Matchers.*
 
 /**
@@ -48,6 +47,7 @@ class CompileOptionsTest {
         assertFalse(compileOptions.listFiles)
         assertFalse(compileOptions.verbose)
         assertFalse(compileOptions.fork)
+        assertFalse(compileOptions.useAnt)
 
         assertThat(compileOptions.compilerArgs, isEmpty())
         assertNull(compileOptions.encoding)
@@ -59,7 +59,7 @@ class CompileOptionsTest {
         assertNotNull(compileOptions.debugOptions)
     }
 
-    @Test public void testOptionMapForDebugAndForkOptions() {
+    @Test public void testOptionMapForDebugOptions() {
         Map optionMap = compileOptions.optionMap()
         assertEquals(optionMap.subMap(TEST_DEBUG_OPTION_MAP.keySet()), TEST_DEBUG_OPTION_MAP)
         assertEquals(optionMap.subMap(TEST_FORK_OPTION_MAP.keySet()), TEST_FORK_OPTION_MAP)
@@ -92,7 +92,6 @@ class CompileOptionsTest {
                 deprecation: 'deprecation',
                 warnings: 'nowarn',
                 debug: 'debug',
-                fork: 'fork',
                 includeJavaRuntime: 'includeJavaRuntime'
         ]
         booleans.keySet().each {compileOptions."$it" = true}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
index 198ea5b..a2302d1 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
@@ -17,9 +17,11 @@
 package org.gradle.api.tasks.compile;
 
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.internal.tasks.compile.*;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.util.GFileUtils;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.core.IsNull;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Before;
@@ -28,8 +30,9 @@ import org.junit.runner.RunWith;
 
 import java.io.File;
 
-import static org.gradle.util.Matchers.*;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.gradle.api.internal.tasks.compile.Compiler;
 
 /**
  * @author Hans Dockter
@@ -38,14 +41,14 @@ import static org.junit.Assert.*;
 public class CompileTest extends AbstractCompileTest {
     private Compile compile;
 
-    private JavaCompiler compilerMock;
+    private Compiler<JavaCompileSpec> compilerMock;
 
-    private Mockery context = new Mockery();
+    private Mockery context = new JUnit4GroovyMockery();
 
     @Before public void setUp()  {
         super.setUp();
         compile = createTask(Compile.class);
-        compilerMock = context.mock(JavaCompiler.class);
+        compilerMock = context.mock(Compiler.class);
         compile.setJavaCompiler(compilerMock);
 
         GFileUtils.touch(new File(srcDir, "incl/file.java"));
@@ -60,13 +63,7 @@ public class CompileTest extends AbstractCompileTest {
         context.checking(new Expectations() {{
             WorkResult result = context.mock(WorkResult.class);
 
-            one(compilerMock).setSource(with(hasSameItems(compile.getSource())));
-            one(compilerMock).setClasspath(compile.getClasspath());
-            one(compilerMock).setDestinationDir(compile.getDestinationDir());
-            one(compilerMock).setDependencyCacheDir(compile.getDependencyCacheDir());
-            one(compilerMock).setSourceCompatibility(compile.getSourceCompatibility());
-            one(compilerMock).setTargetCompatibility(compile.getTargetCompatibility());
-            one(compilerMock).execute();
+            one(compilerMock).execute(with(IsNull.<JavaCompileSpec>notNullValue()));
             will(returnValue(result));
             allowing(result).getDidWork();
             will(returnValue(numFilesCompiled > 0));
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy
index 61c086f..1fd1453 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy
@@ -33,10 +33,13 @@ class ForkOptionsTest {
     }
 
     @Test public void testCompileOptions() {
-        assertNull(forkOptions.executable)
-        assertNull(forkOptions.memoryInitialSize)
-        assertNull(forkOptions.memoryMaximumSize)
-        assertNull(forkOptions.tempDir)
+        forkOptions.with {
+            assert executable == null
+            assert memoryInitialSize == null
+            assert memoryMaximumSize == null
+            assert tempDir == null
+            assert jvmArgs == []
+        }
     }
 
     @Test public void testOptionMap() {
@@ -50,7 +53,7 @@ class ForkOptionsTest {
 
     @Test public void testDefine() {
         forkOptions.define(PROPS.keySet().inject([:]) { Map map, String prop ->
-            map[prop] = "${prop}Value"
+            map[prop] = "${prop}Value" as String
             map
         })
         PROPS.keySet().each {assertEquals("${it}Value" as String, forkOptions."${it}")}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
index cc32a27..f409317 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
@@ -40,7 +40,7 @@ class GroovyCompileOptionsTest {
         assertFalse(compileOptions.listFiles)
         assertFalse(compileOptions.verbose)
         assertTrue(compileOptions.fork)
-        assertNull(compileOptions.encoding)
+        assertEquals('UTF-8', compileOptions.encoding)
         assertNotNull(compileOptions.forkOptions)
     }
 
@@ -49,22 +49,6 @@ class GroovyCompileOptionsTest {
         assertEquals(optionMap.subMap(TEST_FORK_OPTION_MAP.keySet()), TEST_FORK_OPTION_MAP)
     }
 
-    @Test public void testOptionMapWithNullables() {
-        Map optionMap = compileOptions.optionMap()
-        Map nullables = [
-                encoding: 'encoding'
-        ]
-        nullables.each {String field, String antProperty ->
-            assertFalse(optionMap.keySet().contains(antProperty))
-        }
-
-        nullables.keySet().each {compileOptions."$it" = "${it}Value"}
-        optionMap = compileOptions.optionMap()
-        nullables.each {String field, String antProperty ->
-            assertEquals("${field}Value" as String, optionMap[antProperty])
-        }
-    }
-
     @Test public void testOptionMapWithTrueFalseValues() {
         Map booleans = [
                 failOnError: 'failonerror',
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
index 5ce614c..0b49d01 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
@@ -19,9 +19,12 @@ package org.gradle.api.tasks.compile;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.tasks.compile.GroovyJavaJointCompiler;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.GroovyJavaJointCompileSpec;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.util.GFileUtils;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.core.IsNull;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Assert;
@@ -34,22 +37,22 @@ import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 
-import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.*;
-import static org.junit.Assert.*;
+import static org.gradle.util.WrapUtil.toList;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @author Hans Dockter
  */
 @RunWith(org.jmock.integration.junit4.JMock.class)
 public class GroovyCompileTest extends AbstractCompileTest {
-    static final List TEST_GROOVY_CLASSPATH = toList(new File("groovy.jar"));
+    static final List<File> TEST_GROOVY_CLASSPATH = toList(new File("groovy.jar"));
 
     private GroovyCompile testObj;
 
-    GroovyJavaJointCompiler groovyCompilerMock;
+    Compiler<GroovyJavaJointCompileSpec> groovyCompilerMock;
 
-    JUnit4Mockery context = new JUnit4Mockery();
+    JUnit4Mockery context = new JUnit4GroovyMockery();
 
     public AbstractCompile getCompile() {
         return testObj;
@@ -59,7 +62,7 @@ public class GroovyCompileTest extends AbstractCompileTest {
     public void setUp() {
         super.setUp();
         testObj = createTask(GroovyCompile.class);
-        groovyCompilerMock = context.mock(GroovyJavaJointCompiler.class);
+        groovyCompilerMock = context.mock(Compiler.class);
         testObj.setCompiler(groovyCompilerMock);
 
         GFileUtils.touch(new File(srcDir, "incl/file.groovy"));
@@ -74,13 +77,7 @@ public class GroovyCompileTest extends AbstractCompileTest {
         context.checking(new Expectations(){{
             WorkResult result = context.mock(WorkResult.class);
 
-            one(groovyCompilerMock).setSource(with(hasSameItems(testObj.getSource())));
-            one(groovyCompilerMock).setDestinationDir(testObj.getDestinationDir());
-            one(groovyCompilerMock).setClasspath(testObj.getClasspath());
-            one(groovyCompilerMock).setSourceCompatibility(testObj.getSourceCompatibility());
-            one(groovyCompilerMock).setTargetCompatibility(testObj.getTargetCompatibility());
-            one(groovyCompilerMock).setGroovyClasspath(TEST_GROOVY_CLASSPATH);
-            one(groovyCompilerMock).execute();
+            one(groovyCompilerMock).execute(with(IsNull.<GroovyJavaJointCompileSpec>notNullValue()));
             will(returnValue(result));
             allowing(result).getDidWork();
             will(returnValue(numFilesCompiled > 0));
@@ -103,7 +100,7 @@ public class GroovyCompileTest extends AbstractCompileTest {
 
     @Test
     public void testExecuteWithEmptyGroovyClasspath() {
-        setUpMocksAndAttributes(testObj, Collections.emptyList());
+        setUpMocksAndAttributes(testObj, Collections.<File>emptyList());
         try {
             testObj.compile();
         } catch (InvalidUserDataException e) {
@@ -112,7 +109,7 @@ public class GroovyCompileTest extends AbstractCompileTest {
         Assert.fail();
     }
 
-    void setUpMocksAndAttributes(GroovyCompile compile, final List groovyClasspath) {
+    void setUpMocksAndAttributes(GroovyCompile compile, final List<File> groovyClasspath) {
         super.setUpMocksAndAttributes(compile);
 
         final FileCollection groovyClasspathCollection = context.mock(FileCollection.class);
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
index fb33392..66ef05b 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
@@ -34,6 +34,7 @@ public class GroovyForkOptionsTest {
     @Test public void testCompileOptions() {
         assertNull(forkOptions.memoryInitialSize)
         assertNull(forkOptions.memoryMaximumSize)
+        assertTrue(forkOptions.jvmArgs.empty)
     }
 
     @Test public void testOptionMap() {
@@ -47,7 +48,7 @@ public class GroovyForkOptionsTest {
 
     @Test public void testDefine() {
         forkOptions.define(PROPS.keySet().inject([:]) { Map map, String prop ->
-            map[prop] = "${prop}Value"
+            map[prop] = "${prop}Value" as String
             map
         })
         PROPS.keySet().each {assertEquals("${it}Value" as String, forkOptions."${it}")}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
index 919ec4c..497dc68 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
@@ -104,7 +104,7 @@ public class TestTest extends AbstractConventionTaskTest {
         assertNull(test.getTestReportDir());
         assertThat(test.getIncludes(), isEmpty());
         assertThat(test.getExcludes(), isEmpty());
-        assertFalse(test.isIgnoreFailures());
+        assertFalse(test.getIgnoreFailures());
     }
 
     @org.junit.Test
@@ -146,12 +146,12 @@ public class TestTest extends AbstractConventionTaskTest {
     }
 
     @org.junit.Test
-    public void testAddsDefaultIncludeAndExcludePatternsWhenTestScanningIsOff() {
+    public void testDisablesParallelExecutionWhenInDebugMode() {
         configureTask();
-        test.setScanForTestClasses(false);
+        test.setDebug(true);
+        test.setMaxParallelForks(4);
 
-        FileTree classFiles = test.getCandidateClassFiles();
-        assertIsDirectoryTree(classFiles, toSet("**/*Tests.class", "**/*Test.class"), toSet("**/Abstract*.class"));
+        assertEquals(1, test.getMaxParallelForks());
     }
 
     private void assertIsDirectoryTree(FileTree classFiles, Set<String> includes, Set<String> excludes) {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
new file mode 100644
index 0000000..d2ef62b
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.wrapper;
+
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.AbstractTaskTest;
+import org.gradle.util.*;
+import org.gradle.wrapper.GradleWrapperMain;
+import org.gradle.wrapper.WrapperExecutor;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class WrapperTest extends AbstractTaskTest {
+
+    private Wrapper wrapper;
+    private String targetWrapperJarPath;
+    private TestFile expectedTargetWrapperJar;
+    private File expectedTargetWrapperProperties;
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        wrapper = createTask(Wrapper.class);
+        wrapper.setGradleVersion("1.0");
+        targetWrapperJarPath = "gradle/wrapper";
+        expectedTargetWrapperJar = new TestFile(getProject().getProjectDir(),
+                targetWrapperJarPath + "/gradle-wrapper.jar");
+        expectedTargetWrapperProperties = new File(getProject().getProjectDir(),
+                targetWrapperJarPath + "/gradle-wrapper.properties");
+        new File(getProject().getProjectDir(), targetWrapperJarPath).mkdirs();
+        wrapper.setDistributionPath("somepath");
+    }
+
+    public AbstractTask getTask() {
+        return wrapper;
+    }
+
+    @Test
+    public void testWrapperDefaults() {
+        wrapper = createTask(Wrapper.class);
+        assertEquals(new File(getProject().getProjectDir(), "gradle/wrapper/gradle-wrapper.jar"), wrapper.getJarFile());
+        assertEquals(new File(getProject().getProjectDir(), "gradlew"), wrapper.getScriptFile());
+        assertEquals(new File(getProject().getProjectDir(), "gradlew.bat"), wrapper.getBatchScript());
+        assertEquals(GradleVersion.current().getVersion(), wrapper.getGradleVersion());
+        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getDistributionPath());
+        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getArchivePath());
+        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getDistributionBase());
+        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getArchiveBase());
+        assertNotNull(wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testDeterminesWindowsScriptPathFromUnixScriptPath() {
+        wrapper.setScriptFile("build/gradle.sh");
+        assertEquals(getProject().file("build/gradle.bat"), wrapper.getBatchScript());
+
+        wrapper.setScriptFile("build/gradle-wrapper");
+        assertEquals(getProject().file("build/gradle-wrapper.bat"), wrapper.getBatchScript());
+    }
+
+    @Test
+    public void testDeterminesPropertiesFilePathFromJarPath() {
+        wrapper.setJarFile("build/gradle-wrapper.jar");
+        assertEquals(getProject().file("build/gradle-wrapper.properties"), wrapper.getPropertiesFile());
+    }
+    
+    @Test
+    public void testDownloadsFromReleaseRepositoryForReleaseVersions() {
+        wrapper.setGradleVersion("0.9.1");
+        assertEquals("http://services.gradle.org/distributions/gradle-0.9.1-bin.zip", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testDownloadsFromReleaseRepositoryForPreviewReleaseVersions() {
+        wrapper.setGradleVersion("1.0-milestone-1");
+        assertEquals("http://services.gradle.org/distributions/gradle-1.0-milestone-1-bin.zip", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testDownloadsFromSnapshotRepositoryForSnapshotVersions() {
+        wrapper.setGradleVersion("0.9.1-20101224110000+1100");
+        assertEquals("http://services.gradle.org/distributions-snapshots/gradle-0.9.1-20101224110000+1100-bin.zip", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testUsesExplicitlyDefinedDistributionUrl() {
+        wrapper.setGradleVersion("0.9");
+        wrapper.setDistributionUrl("http://some-url");
+        assertEquals("http://some-url", wrapper.getDistributionUrl());
+    }
+
+    @Test
+    public void testExecuteWithNonExistingWrapperJarParentDir() throws IOException {
+        checkExecute();
+    }
+
+    @Test
+    public void testCheckInputs() throws IOException {
+        assertThat(wrapper.getInputs().getProperties().keySet(),
+                equalTo(WrapUtil.toSet("distributionBase", "distributionPath", "distributionUrl", "archiveBase", "archivePath")));
+    }
+
+    @Test
+    public void testExecuteWithExistingWrapperJarParentDirAndExistingWrapperJar() throws IOException {
+        File jarDir = new File(getProject().getProjectDir(), "lib");
+        jarDir.mkdirs();
+        File wrapperJar = new File(getProject().getProjectDir(), targetWrapperJarPath);
+        File parentFile = expectedTargetWrapperJar.getParentFile();
+        assertTrue(parentFile.isDirectory() || parentFile.mkdirs());
+        try {
+            assertTrue(expectedTargetWrapperJar.createNewFile());
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not create %s.", wrapperJar), e);
+        }
+        checkExecute();
+    }
+
+    private void checkExecute() throws IOException {
+        wrapper.execute();
+        TestFile unjarDir = tmpDir.createDir("unjar");
+        expectedTargetWrapperJar.unzipTo(unjarDir);
+        unjarDir.file(GradleWrapperMain.class.getName().replace(".", "/") + ".class").assertIsFile();
+        Properties properties = GUtil.loadProperties(expectedTargetWrapperProperties);
+        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY), wrapper.getDistributionUrl());
+        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY), wrapper.getDistributionBase().toString());
+        assertEquals(properties.getProperty(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY), wrapper.getDistributionPath());
+        assertEquals(properties.getProperty(WrapperExecutor.ZIP_STORE_BASE_PROPERTY), wrapper.getArchiveBase().toString());
+        assertEquals(properties.getProperty(WrapperExecutor.ZIP_STORE_PATH_PROPERTY), wrapper.getArchivePath());
+    }
+
+    private String toNative(String s) {
+        return s.replace("/", File.separator);
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
index b6d872d..5ef1024 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
@@ -91,6 +91,7 @@ public class StandardJavadocDocletOptionsTest {
         assertNull(options.getDocEncoding());
         assertFalse(options.isKeyWords());
         assertEmpty(options.getTags());
+        assertEmpty(options.getTaglets());
         assertEmpty(options.getTagletPath());
         assertFalse(options.isDocFilesSubDirs());
         assertEmpty(options.getExcludeDocFilesSubDir());
@@ -148,6 +149,7 @@ public class StandardJavadocDocletOptionsTest {
             one(optionFileMock).addStringOption("docencoding");
             one(optionFileMock).addBooleanOption("keywords");
             one(optionFileMock).addStringOption("tags");
+            one(optionFileMock).addStringOption("taglets");
             one(optionFileMock).addPathOption("tagletpath");
             one(optionFileMock).addBooleanOption("docfilessubdirs");
             one(optionFileMock).addStringsOption("excludedocfilessubdir", ":");
@@ -444,21 +446,30 @@ public class StandardJavadocDocletOptionsTest {
     }
 
     @Test
-    public void testFluentTagsAndTaglets() {
-        final String[] tagletsValue = new String[]{"com.sun.tools.doclets.ToDoTaglet"};
+    public void testFluentTags() {
         final String[] tagsValue = new String[]{"param", "return", "todo:a:\"To Do:\""};
 
         final List<String> tempList = new ArrayList<String>();
-        tempList.addAll(Arrays.asList(tagletsValue));
         tempList.addAll(Arrays.asList(tagsValue));
 
         final Object[] totalTagsValue = tempList.toArray();
-        assertEquals(options, options.taglets(tagletsValue));
         assertEquals(options, options.tags(tagsValue));
         assertArrayEquals(totalTagsValue, options.getTags().toArray());
     }
 
     @Test
+    public void testFluentTaglets() {
+        final String[] tagletsValue = new String[]{"com.sun.tools.doclets.ToDoTaglet"};
+
+        final List<String> tempList = new ArrayList<String>();
+        tempList.addAll(Arrays.asList(tagletsValue));
+
+        final Object[] totalTagletsValue = tempList.toArray();
+        assertEquals(options, options.taglets(tagletsValue));
+        assertArrayEquals(totalTagletsValue, options.getTaglets().toArray());
+    }
+
+    @Test
     public void testFluentTagletPath() {
         final File[] tagletPathValue = new File[]{new File("tagletOne.jar"), new File("tagletTwo.jar")};
         assertEquals(options, options.tagletPath(tagletPathValue));
@@ -486,7 +497,7 @@ public class StandardJavadocDocletOptionsTest {
     }
 
     @Test
-    public void testFluetNoTimestamp() {
+    public void testFluentNoTimestamp() {
         assertEquals(options, options.noTimestamp());
         assertTrue(options.isNoTimestamp());
     }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy
index 8e37f4b..2f2261d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocExecHandleBuilderTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.external.javadoc.internal;
 
 
-import org.gradle.util.Jvm
+import org.gradle.internal.jvm.Jvm
 import org.junit.Test
 import spock.lang.Specification
 import static org.junit.Assert.assertTrue
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java
index 8252717..a221d96 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileTest.java
@@ -17,32 +17,23 @@
 package org.gradle.external.javadoc.internal;
 
 import org.gradle.external.javadoc.JavadocOptionFileOption;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
+import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
-import org.junit.Before;
+import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
-import static org.junit.Assert.*;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @author Tom Eyckmans
  */
 public class JavadocOptionFileTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private JavadocOptionFileOption optionFileOptionMock;
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    @SuppressWarnings("unchecked")
+    private JavadocOptionFileOption<String> optionFileOptionMock = context.mock(JavadocOptionFileOption.class);
     private final String optionName = "testOption";
-    
-
-    private JavadocOptionFile optionFile;
-
-    @Before
-    public void setUp() {
-        context.setImposteriser(ClassImposteriser.INSTANCE);
-
-        optionFileOptionMock = context.mock(JavadocOptionFileOption.class);
-
-        optionFile = new JavadocOptionFile();
-    }
+    private JavadocOptionFile optionFile = new JavadocOptionFile();
 
     @Test
     public void testDefaults() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java b/subprojects/plugins/src/testFixtures/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
similarity index 100%
rename from subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
rename to subprojects/plugins/src/testFixtures/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
diff --git a/subprojects/scala/scala.gradle b/subprojects/scala/scala.gradle
index 0d3af1b..cb618f5 100644
--- a/subprojects/scala/scala.gradle
+++ b/subprojects/scala/scala.gradle
@@ -14,16 +14,12 @@
  * limitations under the License.
  */
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
     compile project(':core')
     compile project(':plugins')
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testCompile project(path: ':plugins', configuration: 'testFixtures')
     testCompile libraries.slf4j_api
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
 }
 
-
-
+useTestFixtures(project: ":plugins") // includes core test fixtures
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
new file mode 100644
index 0000000..b5a190a
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
@@ -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.scala.compile
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+import org.junit.Rule
+import org.junit.Test
+
+class IncrementalScalaCompileIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void recompilesSourceWhenPropertiesChange() {
+        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
+
+        distribution.testFile('build.gradle').text += '''
+            compileScala.options.debug = false
+'''
+
+        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
+
+        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava', ':compileScala')
+    }
+
+    @Test
+    public void recompilesDependentClasses() {
+        executer.withTasks("classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('src/main/scala/IPerson.scala').assertIsFile().copyFrom(distribution.testFile('NewIPerson.scala'))
+
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileScala'.");
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala b/subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala
rename to subprojects/scala/src/integTest/resources/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
index 623f3dd..dab1fc9 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
@@ -21,19 +21,15 @@ import org.gradle.api.tasks.WorkResult
 import org.gradle.api.tasks.scala.ScalaCompileOptions
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+import org.gradle.api.internal.tasks.compile.Compiler
 
-class AntScalaCompiler implements ScalaCompiler {
+class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
     private static Logger logger = LoggerFactory.getLogger(AntScalaCompiler)
 
     private final IsolatedAntBuilder antBuilder
     private final Iterable<File> bootclasspathFiles
     private final Iterable<File> extensionDirs
-    FileCollection source
-    File destinationDir
-    Iterable<File> classpath
-    Iterable<File> scalaClasspath
-    ScalaCompileOptions scalaCompileOptions = new ScalaCompileOptions()
-    
+
     def AntScalaCompiler(IsolatedAntBuilder antBuilder) {
         this.antBuilder = antBuilder
         this.bootclasspathFiles = []
@@ -46,15 +42,18 @@ class AntScalaCompiler implements ScalaCompiler {
         this.extensionDirs = extensionDirs
     }
 
-    WorkResult execute() {
+    WorkResult execute(ScalaCompileSpec spec) {
+        File destinationDir = spec.destinationDir
+        ScalaCompileOptions scalaCompileOptions = spec.scalaCompileOptions
         Map options = ['destDir': destinationDir] + scalaCompileOptions.optionMap()
         String taskName = scalaCompileOptions.useCompileDaemon ? 'fsc' : 'scalac'
+        Iterable<File> compileClasspath = spec.classpath
 
-        antBuilder.withClasspath(scalaClasspath).execute { ant ->
+        antBuilder.withClasspath(spec.scalaClasspath).execute { ant ->
             taskdef(resource: 'scala/tools/ant/antlib.xml')
 
             "${taskName}"(options) {
-                source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
+                spec.source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
                 bootclasspathFiles.each {file ->
                     bootclasspath(location: file)
                 }
@@ -62,7 +61,7 @@ class AntScalaCompiler implements ScalaCompiler {
                     extdirs(location: dir)
                 }
                 classpath(location: destinationDir)
-                classpath.each {file ->
+                compileClasspath.each {file ->
                     classpath(location: file)
                 }
             }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaCompileSpec.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaCompileSpec.java
new file mode 100644
index 0000000..db504d1
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaCompileSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.scala;
+
+import org.gradle.api.internal.tasks.compile.DefaultJvmLanguageCompileSpec;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+
+import java.io.File;
+
+public class DefaultScalaCompileSpec extends DefaultJvmLanguageCompileSpec implements ScalaCompileSpec {
+    private final ScalaCompileOptions options = new ScalaCompileOptions();
+    private Iterable<File> scalaClasspath;
+
+    public ScalaCompileOptions getScalaCompileOptions() {
+        return options;
+    }
+
+    public Iterable<File> getScalaClasspath() {
+        return scalaClasspath;
+    }
+
+    public void setScalaClasspath(Iterable<File> scalaClasspath) {
+        this.scalaClasspath = scalaClasspath;
+    }
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompileSpec.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompileSpec.java
new file mode 100644
index 0000000..05f3e47
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompileSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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.scala;
+
+import org.gradle.api.internal.tasks.compile.DefaultJavaCompileSpec;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+
+import java.io.File;
+
+public class DefaultScalaJavaJointCompileSpec extends DefaultJavaCompileSpec implements ScalaJavaJointCompileSpec {
+    private final ScalaCompileOptions options = new ScalaCompileOptions();
+    private Iterable<File> scalaClasspath;
+
+    public ScalaCompileOptions getScalaCompileOptions() {
+        return options;
+    }
+
+    public Iterable<File> getScalaClasspath() {
+        return scalaClasspath;
+    }
+
+    public void setScalaClasspath(Iterable<File> scalaClasspath) {
+        this.scalaClasspath = scalaClasspath;
+    }
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java
index 5ba9b92..404ea5e 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java
@@ -15,71 +15,31 @@
  */
 package org.gradle.api.internal.tasks.scala;
 
-import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
-import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
 import org.gradle.api.tasks.WorkResult;
-import org.gradle.api.tasks.compile.CompileOptions;
-import org.gradle.api.tasks.scala.ScalaCompileOptions;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
 
-import java.io.File;
+public class DefaultScalaJavaJointCompiler implements Compiler<ScalaJavaJointCompileSpec> {
+    private final Compiler<ScalaCompileSpec> scalaCompiler;
+    private final Compiler<JavaCompileSpec> javaCompiler;
 
-public class DefaultScalaJavaJointCompiler implements ScalaJavaJointCompiler {
-    private final ScalaCompiler scalaCompiler;
-    private final JavaCompiler javaCompiler;
-    private FileCollection source;
-
-    public DefaultScalaJavaJointCompiler(ScalaCompiler scalaCompiler, JavaCompiler javaCompiler) {
+    public DefaultScalaJavaJointCompiler(Compiler<ScalaCompileSpec> scalaCompiler, Compiler<JavaCompileSpec> javaCompiler) {
         this.scalaCompiler = scalaCompiler;
         this.javaCompiler = javaCompiler;
     }
 
-    public ScalaCompileOptions getScalaCompileOptions() {
-        return scalaCompiler.getScalaCompileOptions();
-    }
-
-    public void setScalaClasspath(Iterable<File> classpath) {
-        scalaCompiler.setScalaClasspath(classpath);
-    }
-
-    public void setSource(FileCollection source) {
-        this.source = source;
-        scalaCompiler.setSource(source);
-    }
-
-    public void setDestinationDir(File destinationDir) {
-        scalaCompiler.setDestinationDir(destinationDir);
-        javaCompiler.setDestinationDir(destinationDir);
-    }
-
-    public void setClasspath(Iterable<File> classpath) {
-        scalaCompiler.setClasspath(classpath);
-        javaCompiler.setClasspath(classpath);
-    }
-
-    public CompileOptions getCompileOptions() {
-        return javaCompiler.getCompileOptions();
-    }
-
-    public void setSourceCompatibility(String sourceCompatibility) {
-        javaCompiler.setSourceCompatibility(sourceCompatibility);
-    }
-
-    public void setTargetCompatibility(String targetCompatibility) {
-        javaCompiler.setTargetCompatibility(targetCompatibility);
-    }
-
-    public WorkResult execute() {
-        scalaCompiler.execute();
+    public WorkResult execute(ScalaJavaJointCompileSpec spec) {
+        scalaCompiler.execute(spec);
 
         PatternFilterable patternSet = new PatternSet();
         patternSet.include("**/*.java");
-        FileTree javaSource = source.getAsFileTree().matching(patternSet);
+        FileTree javaSource = spec.getSource().getAsFileTree().matching(patternSet);
         if (!javaSource.isEmpty()) {
-            javaCompiler.setSource(javaSource);
-            javaCompiler.execute();
+            spec.setSource(javaSource);
+            javaCompiler.execute(spec);
         }
 
         return new WorkResult() {
@@ -88,4 +48,5 @@ public class DefaultScalaJavaJointCompiler implements ScalaJavaJointCompiler {
             }
         };
     }
+
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java
index 4b0bde7..55dbb7a 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java
@@ -16,32 +16,28 @@
 package org.gradle.api.internal.tasks.scala;
 
 import org.gradle.api.internal.TaskOutputsInternal;
-import org.gradle.api.internal.tasks.compile.IncrementalJavaSourceCompiler;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.compile.IncrementalJavaCompilerSupport;
 import org.gradle.api.internal.tasks.compile.SimpleStaleClassCleaner;
 import org.gradle.api.internal.tasks.compile.StaleClassCleaner;
-import org.gradle.api.tasks.scala.ScalaCompileOptions;
 
-import java.io.File;
-
-public class IncrementalScalaCompiler extends IncrementalJavaSourceCompiler<ScalaJavaJointCompiler>
-        implements ScalaJavaJointCompiler {
+public class IncrementalScalaCompiler extends IncrementalJavaCompilerSupport<ScalaJavaJointCompileSpec>
+        implements Compiler<ScalaJavaJointCompileSpec> {
+    private final Compiler<ScalaJavaJointCompileSpec> compiler;
     private final TaskOutputsInternal taskOutputs;
 
-    public IncrementalScalaCompiler(ScalaJavaJointCompiler compiler, TaskOutputsInternal taskOutputs) {
-        super(compiler);
+    public IncrementalScalaCompiler(Compiler<ScalaJavaJointCompileSpec> compiler, TaskOutputsInternal taskOutputs) {
+        this.compiler = compiler;
         this.taskOutputs = taskOutputs;
     }
 
-    public ScalaCompileOptions getScalaCompileOptions() {
-        return getCompiler().getScalaCompileOptions();
-    }
-
-    public void setScalaClasspath(Iterable<File> classpath) {
-        getCompiler().setScalaClasspath(classpath);
+    @Override
+    protected Compiler<ScalaJavaJointCompileSpec> getCompiler() {
+        return compiler;
     }
 
     @Override
-    protected StaleClassCleaner createCleaner() {
+    protected StaleClassCleaner createCleaner(ScalaJavaJointCompileSpec spec) {
         return new SimpleStaleClassCleaner(taskOutputs);
     }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompileSpec.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompileSpec.java
new file mode 100644
index 0000000..346bd7f
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompileSpec.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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.scala;
+
+import org.gradle.api.internal.tasks.compile.JvmLanguageCompileSpec;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+
+import java.io.File;
+
+public interface ScalaCompileSpec extends JvmLanguageCompileSpec {
+    ScalaCompileOptions getScalaCompileOptions();
+
+    Iterable<File> getScalaClasspath();
+
+    void setScalaClasspath(Iterable<File> classpath);
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompiler.java
deleted file mode 100644
index 854ce4b..0000000
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompiler.java
+++ /dev/null
@@ -1,27 +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.api.internal.tasks.scala;
-
-import org.gradle.api.internal.tasks.compile.Compiler;
-import org.gradle.api.tasks.scala.ScalaCompileOptions;
-
-import java.io.File;
-
-public interface ScalaCompiler extends Compiler {
-    ScalaCompileOptions getScalaCompileOptions();
-
-    void setScalaClasspath(Iterable<File> classpath);
-}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompileSpec.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompileSpec.java
new file mode 100644
index 0000000..3d9f0da
--- /dev/null
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompileSpec.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012 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.scala;
+
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
+
+public interface ScalaJavaJointCompileSpec extends ScalaCompileSpec, JavaCompileSpec {
+}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompiler.java
deleted file mode 100644
index 2b3a309..0000000
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompiler.java
+++ /dev/null
@@ -1,21 +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.api.internal.tasks.scala;
-
-import org.gradle.api.internal.tasks.compile.JavaSourceCompiler;
-
-public interface ScalaJavaJointCompiler extends ScalaCompiler, JavaSourceCompiler {
-}
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
index 7e3b403..a51a621 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
@@ -22,6 +22,7 @@ import org.gradle.api.file.FileTreeElement
 import org.gradle.api.internal.tasks.DefaultScalaSourceSet
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.scala.ScalaCompile
 import org.gradle.api.tasks.scala.ScalaDoc
@@ -54,7 +55,7 @@ public class ScalaBasePlugin implements Plugin<Project> {
             scalaCompile.dependsOn sourceSet.compileJavaTaskName
             javaPlugin.configureForSourceSet(sourceSet, scalaCompile);
             scalaCompile.description = "Compiles the $sourceSet.scala.";
-            scalaCompile.conventionMapping.defaultSource = { sourceSet.scala }
+            scalaCompile.source = sourceSet.scala
 
             project.tasks[sourceSet.classesTaskName].dependsOn(taskName)
         }
@@ -69,7 +70,7 @@ public class ScalaBasePlugin implements Plugin<Project> {
     private void configureScaladoc(final Project project) {
         project.getTasks().withType(ScalaDoc.class) {ScalaDoc scalaDoc ->
             scalaDoc.conventionMapping.destinationDir = { project.file("$project.docsDir/scaladoc") }
-            scalaDoc.conventionMapping.title = { project.apiDocTitle }
+            scalaDoc.conventionMapping.title = { project.extensions.getByType(ReportingExtension).apiDocTitle }
             scalaDoc.scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
         }
     }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
index d49e94e..0aa1beb 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
@@ -35,8 +35,8 @@ public class ScalaPlugin implements Plugin<Project> {
 
     private void configureScaladoc(final Project project) {
         project.getTasks().withType(ScalaDoc.class) {ScalaDoc scalaDoc ->
-            scalaDoc.conventionMapping.classpath = { project.sourceSets.main.classes + project.sourceSets.main.compileClasspath }
-            scalaDoc.conventionMapping.defaultSource = { project.sourceSets.main.scala }
+            scalaDoc.conventionMapping.classpath = { project.sourceSets.main.output + project.sourceSets.main.compileClasspath }
+            scalaDoc.source = project.sourceSets.main.scala
         }
         ScalaDoc scalaDoc = project.tasks.add(SCALA_DOC_TASK_NAME, ScalaDoc.class)
         scalaDoc.description = "Generates scaladoc for the main source code.";
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
index 504fd3b..b24d961 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
@@ -20,24 +20,25 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.project.IsolatedAntBuilder;
 import org.gradle.api.internal.tasks.compile.AntJavaCompiler;
-import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec;
 import org.gradle.api.internal.tasks.scala.*;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.Nested;
 import org.gradle.api.tasks.compile.AbstractCompile;
 import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.api.internal.tasks.compile.Compiler;
 
 /**
  * Compiles Scala source files, and optionally, Java source files.
  */
 public class ScalaCompile extends AbstractCompile {
     private FileCollection scalaClasspath;
-
-    private ScalaJavaJointCompiler compiler;
+    private Compiler<ScalaJavaJointCompileSpec> compiler;
+    private final ScalaJavaJointCompileSpec spec = new DefaultScalaJavaJointCompileSpec();
 
     public ScalaCompile() {
-        ScalaCompiler scalaCompiler = new AntScalaCompiler(getServices().get(IsolatedAntBuilder.class));
-        JavaCompiler javaCompiler = new AntJavaCompiler(getServices().getFactory(AntBuilder.class));
+        Compiler<ScalaCompileSpec> scalaCompiler = new AntScalaCompiler(getServices().get(IsolatedAntBuilder.class));
+        Compiler<JavaCompileSpec> javaCompiler = new AntJavaCompiler(getServices().getFactory(AntBuilder.class));
         compiler = new IncrementalScalaCompiler(new DefaultScalaJavaJointCompiler(scalaCompiler, javaCompiler), getOutputs());
     }
 
@@ -53,11 +54,11 @@ public class ScalaCompile extends AbstractCompile {
         this.scalaClasspath = scalaClasspath;
     }
 
-    public ScalaJavaJointCompiler getCompiler() {
+    public Compiler<ScalaJavaJointCompileSpec> getCompiler() {
         return compiler;
     }
 
-    public void setCompiler(ScalaJavaJointCompiler compiler) {
+    public void setCompiler(Compiler<ScalaJavaJointCompileSpec> compiler) {
         this.compiler = compiler;
     }
 
@@ -66,7 +67,7 @@ public class ScalaCompile extends AbstractCompile {
      */
     @Nested
     public ScalaCompileOptions getScalaCompileOptions() {
-        return compiler.getScalaCompileOptions();
+        return spec.getScalaCompileOptions();
     }
 
     /**
@@ -74,18 +75,18 @@ public class ScalaCompile extends AbstractCompile {
      */
     @Nested
     public CompileOptions getOptions() {
-        return compiler.getCompileOptions();
+        return spec.getCompileOptions();
     }
 
     @Override
     protected void compile() {
         FileTree source = getSource();
-        compiler.setSource(source);
-        compiler.setDestinationDir(getDestinationDir());
-        compiler.setClasspath(getClasspath());
-        compiler.setScalaClasspath(getScalaClasspath());
-        compiler.setSourceCompatibility(getSourceCompatibility());
-        compiler.setTargetCompatibility(getTargetCompatibility());
-        compiler.execute();
+        spec.setSource(source);
+        spec.setDestinationDir(getDestinationDir());
+        spec.setClasspath(getClasspath());
+        spec.setScalaClasspath(getScalaClasspath());
+        spec.setSourceCompatibility(getSourceCompatibility());
+        spec.setTargetCompatibility(getTargetCompatibility());
+        compiler.execute(spec);
     }
 }
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy
index f55713b..e374c55 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy
@@ -16,43 +16,48 @@
 package org.gradle.api.internal.tasks.scala
 
 import spock.lang.Specification
-import org.gradle.api.internal.tasks.compile.JavaCompiler
 import org.gradle.api.file.FileCollection
 import org.gradle.api.file.FileTree
+import org.gradle.api.internal.tasks.compile.JvmLanguageCompileSpec
+import org.gradle.api.internal.tasks.compile.Compiler
+import org.gradle.api.internal.tasks.compile.JavaCompileSpec
 
 class DefaultScalaJavaJointCompilerTest extends Specification {
-    private final ScalaCompiler scalaCompiler = Mock()
-    private final JavaCompiler javaCompiler = Mock()
+    private final Compiler<ScalaCompileSpec> scalaCompiler = Mock()
+    private final JvmLanguageCompileSpec scalaSpec = Mock()
+    private final Compiler<JavaCompileSpec> javaCompiler = Mock()
     private final FileCollection source = Mock()
     private final FileTree sourceTree = Mock()
     private final FileTree javaSource = Mock()
+    private final ScalaJavaJointCompileSpec spec = Mock()
     private final DefaultScalaJavaJointCompiler compiler = new DefaultScalaJavaJointCompiler(scalaCompiler, javaCompiler)
 
     def executesScalaCompilerThenJavaCompiler() {
-        compiler.source = source
+        given:
+        _ * spec.source >> source
 
         when:
-        def result = compiler.execute()
+        def result = compiler.execute(spec)
 
         then:
         result.didWork
-        1 * scalaCompiler.execute()
+        1 * scalaCompiler.execute(spec)
         1 * source.getAsFileTree() >> sourceTree
         1 * sourceTree.matching(!null) >> javaSource
-        _ * javaSource.isEmpty() >> false
-        1 * javaCompiler.setSource(!null)
-        1 * javaCompiler.execute()
+        javaSource.isEmpty() >> false
+        1 * spec.setSource(javaSource)
+        1 * javaCompiler.execute(spec)
     }
 
     def doesNotInvokeJavaCompilerWhenNoJavaSource() {
-        compiler.source = source
+        _ * spec.source >> source
 
         when:
-        def result = compiler.execute()
+        def result = compiler.execute(spec)
 
         then:
         result.didWork
-        1 * scalaCompiler.execute()
+        1 * scalaCompiler.execute(spec)
         1 * source.getAsFileTree() >> sourceTree
         1 * sourceTree.matching(!null) >> javaSource
         _ * javaSource.isEmpty() >> true
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
index 06c1328..8818f37 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
@@ -15,17 +15,19 @@
  */
 package org.gradle.api.plugins.scala
 
-import static org.hamcrest.Matchers.*
-
 import org.gradle.api.Project
 import org.gradle.api.internal.artifacts.configurations.Configurations
 import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.scala.ScalaCompile
 import org.gradle.api.tasks.scala.ScalaDoc
 import org.gradle.util.HelperUtil
 import org.junit.Test
-import static org.gradle.util.Matchers.*
-import static org.gradle.util.WrapUtil.*
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.Matchers.isEmpty
+import static org.gradle.util.WrapUtil.toLinkedSet
+import static org.gradle.util.WrapUtil.toSet
+import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
 public class ScalaBasePluginTest {
@@ -63,7 +65,7 @@ public class ScalaBasePluginTest {
         assertThat(task.description, equalTo('Compiles the custom Scala source.'))
         assertThat(task.classpath, equalTo(project.sourceSets.custom.compileClasspath))
         assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.scala))
+        assertThat(task.source as List, equalTo(project.sourceSets.custom.scala as List))
         assertThat(task, dependsOn('compileCustomJava'))
     }
     
@@ -78,8 +80,8 @@ public class ScalaBasePluginTest {
     @Test public void configuresCompileTasksDefinedByTheBuildScript() {
         scalaPlugin.apply(project)
 
-        def task = project.createTask('otherCompile', type: ScalaCompile)
-        assertThat(task.defaultSource, nullValue())
+        def task = project.task('otherCompile', type: ScalaCompile)
+        assertThat(task.source, isEmpty())
         assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
         assertThat(task, dependsOn())
     }
@@ -87,9 +89,9 @@ public class ScalaBasePluginTest {
     @Test public void configuresScalaDocTasksDefinedByTheBuildScript() {
         scalaPlugin.apply(project)
 
-        def task = project.createTask('otherScaladoc', type: ScalaDoc)
+        def task = project.task('otherScaladoc', type: ScalaDoc)
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
-        assertThat(task.title, equalTo(project.apiDocTitle))
+        assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
         assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
         assertThat(task, dependsOn())
     }
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
index 50ca921..659b634 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
@@ -17,16 +17,17 @@ package org.gradle.api.plugins.scala
 
 import org.gradle.api.Project
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.reporting.ReportingExtension
 import org.gradle.api.tasks.scala.ScalaCompile
 import org.gradle.api.tasks.scala.ScalaDoc
 import org.gradle.util.HelperUtil
+import org.gradle.util.Matchers
 import org.junit.Test
 import static org.gradle.util.Matchers.dependsOn
 import static org.gradle.util.WrapUtil.toLinkedSet
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 import static org.junit.Assert.assertTrue
-import org.gradle.util.Matchers
 
 public class ScalaPluginTest {
 
@@ -57,14 +58,14 @@ public class ScalaPluginTest {
         assertThat(task, instanceOf(ScalaCompile.class))
         assertThat(task.description, equalTo('Compiles the main Scala source.'))
         assertThat(task.classpath, equalTo(project.sourceSets.main.compileClasspath))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.scala))
+        assertThat(task.source as List, equalTo(project.sourceSets.main.scala  as List))
         assertThat(task, dependsOn(JavaPlugin.COMPILE_JAVA_TASK_NAME))
 
         task = project.tasks['compileTestScala']
         assertThat(task, instanceOf(ScalaCompile.class))
         assertThat(task.description, equalTo('Compiles the test Scala source.'))
         assertThat(task.classpath, equalTo(project.sourceSets.test.compileClasspath))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.test.scala))
+        assertThat(task.source as List, equalTo(project.sourceSets.test.scala as List))
         assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
     }
 
@@ -85,16 +86,16 @@ public class ScalaPluginTest {
         assertThat(task, instanceOf(ScalaDoc.class))
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
         assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
-        assertThat(task.defaultSource, equalTo(project.sourceSets.main.scala))
-        assertThat(task.classpath, Matchers.sameCollection(project.files(project.sourceSets.main.classes, project.sourceSets.main.compileClasspath)))
-        assertThat(task.title, equalTo(project.apiDocTitle))
+        assertThat(task.source as List, equalTo(project.sourceSets.main.scala as List))
+        assertThat(task.classpath, Matchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
+        assertThat(task.title, equalTo(project.extensions.getByType(ReportingExtension).apiDocTitle))
     }
 
     @Test public void configuresScalaDocTasksDefinedByTheBuildScript() {
         scalaPlugin.apply(project)
 
-        def task = project.createTask('otherScaladoc', type: ScalaDoc)
+        def task = project.task('otherScaladoc', type: ScalaDoc)
         assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
-        assertThat(task.classpath, Matchers.sameCollection(project.files(project.sourceSets.main.classes, project.sourceSets.main.compileClasspath)))
+        assertThat(task.classpath, Matchers.sameCollection(project.files(project.sourceSets.main.output, project.sourceSets.main.compileClasspath)))
     }
 }
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
index 8feeca2..adbe004 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
@@ -17,11 +17,13 @@ package org.gradle.api.tasks.scala;
 
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
-import org.gradle.api.internal.tasks.scala.ScalaJavaJointCompiler;
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.internal.tasks.scala.ScalaJavaJointCompileSpec;
 import org.gradle.api.tasks.compile.AbstractCompile;
 import org.gradle.api.tasks.compile.AbstractCompileTest;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.core.IsNull;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
@@ -29,13 +31,11 @@ import org.junit.Test;
 
 import java.io.File;
 
-import static org.gradle.util.Matchers.*;
-
 public class ScalaCompileTest extends AbstractCompileTest {
 
     private ScalaCompile scalaCompile;
 
-    private ScalaJavaJointCompiler scalaCompiler;
+    private Compiler<ScalaJavaJointCompileSpec> scalaCompiler;
     private JUnit4Mockery context = new JUnit4GroovyMockery();
 
     @Override
@@ -53,7 +53,7 @@ public class ScalaCompileTest extends AbstractCompileTest {
     public void setUp() {
         super.setUp();
         scalaCompile = createTask(ScalaCompile.class);
-        scalaCompiler = context.mock(ScalaJavaJointCompiler.class);
+        scalaCompiler = context.mock(Compiler.class);
         scalaCompile.setCompiler(scalaCompiler);
 
         GFileUtils.touch(new File(srcDir, "incl/file.scala"));
@@ -64,13 +64,7 @@ public class ScalaCompileTest extends AbstractCompileTest {
     public void testExecuteDoingWork() {
         setUpMocksAndAttributes(scalaCompile);
         context.checking(new Expectations() {{
-            one(scalaCompiler).setSource(with(hasSameItems(scalaCompile.getSource())));
-            one(scalaCompiler).setDestinationDir(scalaCompile.getDestinationDir());
-            one(scalaCompiler).setClasspath(scalaCompile.getClasspath());
-            one(scalaCompiler).setScalaClasspath(scalaCompile.getScalaClasspath());
-            one(scalaCompiler).setSourceCompatibility(scalaCompile.getSourceCompatibility());
-            one(scalaCompiler).setTargetCompatibility(scalaCompile.getTargetCompatibility());
-            one(scalaCompiler).execute();
+            one(scalaCompiler).execute(with(IsNull.<ScalaJavaJointCompileSpec>notNullValue()));
         }});
 
         scalaCompile.compile();
diff --git a/subprojects/signing/signing.gradle b/subprojects/signing/signing.gradle
new file mode 100644
index 0000000..dd4ae7f
--- /dev/null
+++ b/subprojects/signing/signing.gradle
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+
+    compile project(':core')
+    compile project(":plugins")
+    compile project(":maven")
+
+    compile module("org.bouncycastle:bcpg-jdk15:1.46") {
+        dependency "org.bouncycastle:bcprov-jdk15:1.46 at jar"
+    }
+}
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/NoSigningCredentialsIntegrationSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/NoSigningCredentialsIntegrationSpec.groovy
new file mode 100644
index 0000000..80b8922
--- /dev/null
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/NoSigningCredentialsIntegrationSpec.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.signing
+
+class NoSigningCredentialsIntegrationSpec extends SigningIntegrationSpec {
+
+    def setup() {
+        executer.withArguments("-info")
+    }
+
+    def "trying to perform a signing operation without a signatory produces reasonable error"() {
+        when:
+        buildFile << """
+            signing {
+                sign jar
+            }
+        """ << uploadArchives()
+        
+        then:
+        fails ":uploadArchives"
+        
+        and:
+        failureHasCause "Cannot perform signing task ':signJar' because it has no configured signatory"
+    }
+
+    def "trying to perform a signing operation without a signatory when not required does not error, and other artifacts still uploaded"() {
+        when:
+        buildFile << """
+            signing {
+                sign configurations.archives
+                required = { false }
+            }
+        """ << uploadArchives() << signDeploymentPom()
+
+        then:
+        succeeds ":uploadArchives"
+
+        and:
+        ":signArchives" in skippedTasks
+
+        and:
+        jarUploaded()
+        signatureNotUploaded()
+        pom().exists()
+        !pomSignature().exists()
+
+        when:
+        buildFile << keyInfo.addAsPropertiesScript()
+
+        then:
+        succeeds ":uploadArchives"
+
+        and:
+        ":signArchives" in nonSkippedTasks
+
+        and:
+        jarUploaded()
+        signatureUploaded()
+        pom().exists()
+        pomSignature().exists()
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningConfigurationsIntegrationSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningConfigurationsIntegrationSpec.groovy
new file mode 100644
index 0000000..ce3f69c
--- /dev/null
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningConfigurationsIntegrationSpec.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.signing
+
+class SigningConfigurationsIntegrationSpec extends SigningIntegrationSpec {
+    
+    def "signing configurations"() {
+        given:
+        buildFile << """
+            configurations {
+                meta
+            }
+            
+            signing {
+                sign configurations.archives, configurations.meta
+            }
+
+            ${keyInfo.addAsPropertiesScript()}
+            ${getJavadocAndSourceJarsScript("meta")}
+        """
+        
+        when:
+        run "buildSignatures"
+        
+        then:
+        executedAndNotSkipped ":signArchives", ":signMeta"
+        
+        and:
+        file("build", "libs", "sign-1.0.jar.asc").text
+        file("build", "libs", "sign-1.0-javadoc.jar.asc").text
+        file("build", "libs", "sign-1.0-sources.jar.asc").text
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy
new file mode 100644
index 0000000..62584f5
--- /dev/null
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import static org.gradle.util.TextUtil.escapeString
+
+abstract class SigningIntegrationSpec extends AbstractIntegrationSpec {
+    
+    @Rule public final TestResources resources = new TestResources("keys")
+
+    String jarFileName = "sign-1.0.jar"
+
+    def setup() {
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'signing'
+            archivesBaseName = 'sign'
+            group = 'sign'
+            version = '1.0'
+        """
+        
+        file("src", "main", "java", "Thing.java") << """
+            public class Thing {}
+        """
+    }
+
+    static class KeyInfo {
+        String keyId
+        String password
+        String keyRingFilePath
+        
+        Map<String, String> asProperties(String name = null) {
+            def prefix = name ? "signing.${name}." : "signing."
+            def properties = [:]
+            properties[prefix + "keyId"] = keyId
+            properties[prefix + "password"] = password
+            properties[prefix + "secretKeyRingFile"] = keyRingFilePath
+            properties
+        }
+        
+        String addAsPropertiesScript(addTo = "project.ext", name = null) {
+            asProperties(name).collect { k, v ->
+                "${addTo}.setProperty('${escapeString(k)}', '${escapeString(v)}')"
+            }.join(";")
+        }
+    }
+    
+    KeyInfo getKeyInfo(set = "default") {
+        new KeyInfo(
+            keyId: file(set, "keyId.txt").text.trim(),
+            password: file(set, "password.txt").text.trim(),
+            keyRingFilePath: file(set, "secring.gpg")
+        )
+    }
+    
+    String getJavadocAndSourceJarsScript(String configurationName = null) {
+        def tasks = """
+            task("sourcesJar", type: Jar, dependsOn: classes) { 
+                classifier = 'sources' 
+                from sourceSets.main.allSource
+            } 
+
+            task("javadocJar", type: Jar, dependsOn: javadoc) { 
+                classifier = 'javadoc' 
+                from javadoc.destinationDir 
+            } 
+        """
+        
+        if (configurationName == null) {
+            tasks
+        } else {
+            tasks + """
+                configurations {
+                    $configurationName
+                }
+                
+                artifacts {
+                    $configurationName sourcesJar, javadocJar
+                }
+            """
+        }
+    }
+    
+    String uploadArchives() {
+        return """
+            apply plugin: "maven"
+            uploadArchives {
+                repositories {
+                    mavenDeployer {
+                        repository(url: "file://\$buildDir/m2Repo/")
+                    }
+                    flatDir {
+                        name "fileRepo"
+                        dirs "build/fileRepo"
+                    }
+                    ivy {
+                        url "file://\$buildDir/ivyRepo/"
+                        layout "pattern"
+                        artifactPattern "\$buildDir/ivyRepo/[artifact]-[revision](.[ext])"
+                        ivyPattern "\$buildDir/ivyRepo/[artifact]-[revision](.[ext])"
+                    }
+                }
+            }
+        """
+    }
+
+    File m2RepoFile(String name) {
+        file("build", "m2Repo", "sign", "sign", "1.0", name)
+    }
+
+    File ivyRepoFile(String name) {
+        file("build", "ivyRepo", name)
+    }
+
+    File fileRepoFile(String name) {
+        file("build", "fileRepo", name)
+    }
+
+
+    void jarUploaded(String jarFileName = jarFileName) {
+        assert m2RepoFile(jarFileName).exists()
+        assert m2RepoFile(jarFileName).exists()
+        assert ivyRepoFile(jarFileName).exists()
+        assert fileRepoFile(jarFileName).exists()
+    }
+
+    void jarNotUploaded() {
+        assert !m2RepoFile(jarFileName).exists()
+        assert !ivyRepoFile(jarFileName).exists()
+        assert !fileRepoFile(jarFileName).exists()
+    }
+
+    void signatureUploaded(String jarFileName = jarFileName) {
+        assert m2RepoFile("${jarFileName}.asc").exists()
+        assert ivyRepoFile("${jarFileName - '.jar'}.asc").exists()
+        assert fileRepoFile("${jarFileName - '.jar'}.asc").exists()
+    }
+
+    void signatureNotUploaded(String jarFileName = jarFileName) {
+        assert !m2RepoFile("${jarFileName}.asc").exists()
+        assert !ivyRepoFile("${jarFileName - '.jar'}.asc").exists()
+        assert !fileRepoFile("${jarFileName - '.jar'}.asc").exists()
+    }
+
+    String signDeploymentPom() {
+        return """
+            uploadArchives {
+                repositories.mavenDeployer {
+                    beforeDeployment { signing.signPom(it) }
+                }
+            }
+        """
+    }
+    
+    File pom(String name = "sign-1.0") {
+        m2RepoFile("${name}.pom")
+    }
+
+    File pomSignature(String name = "sign-1.0") {
+        m2RepoFile("${name}.pom.asc")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
new file mode 100644
index 0000000..ca72b22
--- /dev/null
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.integtests.fixtures.*
+
+import org.junit.*
+
+class SigningSamplesSpec extends AbstractIntegrationSpec {
+    @Rule public final Sample mavenSample = new Sample()
+
+    @UsesSample('signing/maven')
+    def "upload attaches signatures"() {
+        given:
+        sample mavenSample
+
+        when:
+        run "uploadArchives"
+
+        then:
+        repo.module('gradle', 'maven', '1.0').assertArtifactsPublished('maven-1.0.pom', 'maven-1.0.pom.asc', 'maven-1.0.jar', 'maven-1.0.jar.asc')
+    }
+
+    @UsesSample('signing/conditional')
+    def "conditional signing"() {
+        given:
+        sample mavenSample
+
+        when:
+        run "uploadArchives"
+
+        then:
+        ":signArchives" in skippedTasks
+
+        and:
+        repo.module('gradle', 'conditional', '1.0-SNAPSHOT').assertArtifactsPublished('conditional-1.0-SNAPSHOT.pom', 'conditional-1.0-SNAPSHOT.jar')
+    }
+
+    MavenRepository getRepo() {
+        return new MavenRepository(mavenSample.dir.file("build/repo"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningTasksIntegrationSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningTasksIntegrationSpec.groovy
new file mode 100644
index 0000000..fbdab59
--- /dev/null
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningTasksIntegrationSpec.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2011 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.signing
+
+class SigningTasksIntegrationSpec extends SigningIntegrationSpec {
+    
+    def "sign jar with default signatory"() {
+        given:
+        buildFile << """
+            ${keyInfo.addAsPropertiesScript()}
+            
+            signing {
+                sign jar
+            }
+        """
+        
+        when:
+        run "signJar"
+        
+        then:
+        ":signJar" in nonSkippedTasks
+        
+        and:
+        file("build", "libs", "sign-1.0.jar.asc").text
+        
+        when:
+        run "signJar"
+        
+        then:
+        ":signJar" in skippedTasks
+    }
+    
+    def "sign multiple jars with default signatory"() {
+        given:
+        buildFile << """
+            ${keyInfo.addAsPropertiesScript()}
+            ${javadocAndSourceJarsScript}
+            
+            signing {
+                sign jar, javadocJar, sourcesJar
+            }
+        """
+        
+        when:
+        run "signJar", "signJavadocJar", "signSourcesJar"
+        
+        then:
+        [":signJar", ":signJavadocJar", ":signSourcesJar"].every { it in nonSkippedTasks }
+        
+        and:
+        file("build", "libs", "sign-1.0.jar.asc").text
+        file("build", "libs", "sign-1.0-javadoc.jar.asc").text
+        file("build", "libs", "sign-1.0-sources.jar.asc").text
+        
+        when:
+        run "signJar", "signJavadocJar", "signSourcesJar"
+        
+        then:
+        [":signJar", ":signJavadocJar", ":signSourcesJar"].every { it in skippedTasks }
+    }
+    
+    def "trying to sign a task that isn't an archive task gives nice enough message"() {
+        given:
+        buildFile << """
+            signing {
+                sign clean
+            }
+        """
+        
+        when:
+        runAndFail "signClean"
+        
+        then:
+        failureHasCause "You cannot sign tasks that are not 'archive' tasks, such as 'jar', 'zip' etc. (you tried to sign task ':clean')"
+    }
+    
+    def "changes to task information after signing block are respected"() {
+        given:
+        buildFile << """
+            ${keyInfo.addAsPropertiesScript()}
+            
+            signing {
+                sign jar
+            }
+            
+            jar {
+                baseName = "changed"
+                classifier = "custom"
+            }
+        """
+        
+        when:
+        run "signJar"
+        
+        then:
+        ":signJar" in nonSkippedTasks
+        
+        and:
+        file("build", "libs", "changed-1.0-custom.jar.asc").text
+        
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/keyId.txt b/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/keyId.txt
new file mode 100644
index 0000000..20c5786
--- /dev/null
+++ b/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/keyId.txt
@@ -0,0 +1 @@
+24875D73
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/password.txt b/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/password.txt
new file mode 100644
index 0000000..82be7ae
--- /dev/null
+++ b/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/password.txt
@@ -0,0 +1 @@
+gradle
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/secring.gpg b/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/secring.gpg
new file mode 100644
index 0000000..47a24fe
Binary files /dev/null and b/subprojects/signing/src/integTest/resources/org/gradle/plugins/signing/keys/default/secring.gpg differ
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/Sign.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/Sign.groovy
new file mode 100644
index 0000000..5a8302c
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/Sign.groovy
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.Task
+import org.gradle.api.DefaultTask
+import org.gradle.api.DomainObjectSet
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.api.InvalidUserDataException
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.internal.DefaultDomainObjectSet
+
+import org.gradle.plugins.signing.signatory.Signatory
+import org.gradle.plugins.signing.type.SignatureType
+
+
+/**
+ * A task for creating digital signature files for one or more; tasks, files, publishable artifacts or configurations.
+ * 
+ * <p>The task produces {@link Signature}</p> objects that are publishable artifacts and can be assigned to another configuration.
+ * <p>
+ * The signature objects are created with defaults and using this tasks signatory and signature type.
+ */
+class Sign extends DefaultTask implements SignatureSpec {
+    
+    SignatureType signatureType
+    
+    /**
+     * The signatory to the generated signatures.
+     */
+    Signatory signatory
+    
+    /**
+     * Whether or not this task should fail if no signatory or signature type are configured at generation time.
+     * 
+     * <p>Defaults to {@code true}.</p>
+     */
+    boolean required = true
+    
+    final private DefaultDomainObjectSet<Signature> signatures = new DefaultDomainObjectSet<Signature>(Signature)
+    
+    Sign() {
+        super()
+
+        // If we aren't required and don't have a signatory then we just don't run
+        onlyIf {
+            isRequired() || getSignatory() != null
+        }
+
+        // Have to include this in the up-to-date checks because the signatory may have changed
+        inputs.property("signatory") { getSignatory()?.keyId?.asHex }
+        
+        inputs.files { getSignatures()*.toSign }
+        outputs.files { getSignatures()*.toSign }
+    }
+    
+    /**
+     * Configures the task to sign the archive produced for each of the given tasks (which must be archive tasks).
+     */
+    void sign(Task... tasks) {
+        for (task in tasks) {
+            if (!(task instanceof AbstractArchiveTask)) {
+                throw new InvalidUserDataException("You cannot sign tasks that are not 'archive' tasks, such as 'jar', 'zip' etc. (you tried to sign $task)")
+            }
+            
+            dependsOn(task)
+            addSignature(new Signature({ task.archivePath }, { task.classifier }, this, this))
+        }
+    }
+
+    /**
+     * Configures the task to sign each of the given artifacts
+     */
+    void sign(PublishArtifact... publishArtifacts) {
+        for (publishArtifact in publishArtifacts) {
+            dependsOn(publishArtifact)
+            addSignature(new Signature(publishArtifact, this, this))
+        }
+    }
+
+    /**
+     * Configures the task to sign each of the given files
+     */    
+    void sign(File... files) {
+        sign(null, *files)
+    }
+    
+    /**
+     * Configures the task to sign each of the given artifacts, using the given classifier as the classifier for the resultant signature publish artifact.
+     */
+    void sign(String classifier, File... files) {
+        for (file in files) {
+            addSignature(new Signature(file, classifier, this, this))
+        }
+    }
+
+    /**
+     * Configures the task to sign every artifact of the given configurations
+     */
+    void sign(Configuration... configurations) {
+        for (configuration in configurations) {
+            configuration.allArtifacts.all { PublishArtifact artifact ->
+                if (!(artifact instanceof Signature)) {
+                    sign(artifact)
+                }
+            }
+            configuration.allArtifacts.whenObjectRemoved { PublishArtifact artifact ->
+                signatures.remove(signatures.find { it.toSignArtifact == artifact })
+            }
+        }
+    }
+    
+    private addSignature(Signature signature) {
+        signatures.add(signature)
+    }
+    
+    /**
+     * Changes the signature file representation for the signatures.
+     */
+    void signatureType(SignatureType type) {
+        this.signatureType = signatureType
+    }
+    
+    /**
+     * Changes the signatory of the signatures.
+     */
+    void signatory(Signatory signatory) {
+        this.signatory = signatory
+    }
+    
+    /**
+     * Change whether or not this task should fail if no signatory or signature type are configured at the time of generation.
+     */
+    void required(boolean required) {
+        setRequired(required)
+    }
+    
+    /**
+     * Generates the signature files.
+     */
+    @TaskAction
+    void generate() {
+        if (getSignatory() == null) {
+            throw new InvalidUserDataException("Cannot perform signing task '${getPath()}' because it has no configured signatory")
+        }
+        
+        getSignatures()*.generate()
+    }
+    
+    /**
+     * The signatures generated by this task.
+     */
+    DomainObjectSet<Signature> getSignatures() {
+        signatures
+    }
+    
+    /**
+     * Returns the the single signature generated by this task.
+     *
+     * @return The signature.
+     * @throws IllegalStateException if there is not exactly one signature.
+     */
+    Signature getSingleSignature() {
+        def signatureSet = getSignatures()
+        if (signatureSet.size() == 0) {
+            throw new IllegalStateException("Expected %s to contain exactly one signature, however, it contains no signatures.")
+        } else if (signatureSet.size() == 1) {
+            signatureSet.toList().first()
+        } else {
+            throw new IllegalStateException("Expected %s to contain exactly one signature, however, it contains no ${signatureSet.size()} signatures.")
+        }
+    }
+    
+    /**
+     * All of the files that will be signed by this task.
+     */
+    FileCollection getFilesToSign() {
+        new SimpleFileCollection(*getSignatures()*.toSign.findAll({ it != null }))
+    }
+    
+    /**
+     * All of the signature files that will be generated by this operation.
+     */
+    FileCollection getSignatureFiles() {
+        new SimpleFileCollection(*getSignatures()*.file.findAll({ it != null }))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SignOperation.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SignOperation.groovy
new file mode 100644
index 0000000..2896b89
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SignOperation.groovy
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.artifacts.PublishArtifact
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+
+import org.gradle.util.ConfigureUtil
+
+import org.gradle.plugins.signing.signatory.Signatory
+import org.gradle.plugins.signing.type.SignatureType
+
+/**
+ * A sign operation creates digital signatures for one or more files or {@link org.gradle.api.artifacts.PublishArtifact publish artifacts}.
+ * 
+ * <p>The external representation of the signature is specified by the {@link #type signature type property}, while the {@link #signatory}
+ * property specifies who is to sign.
+ * <p>
+ * A sign operation manages one or more {@link Signature} objects. The {@code sign} methods are used to register things to generate signatures
+ * for. The {@link #execute()} method generates the signatures for all of the registered items at that time.
+ */
+class SignOperation implements SignatureSpec {
+
+    /**
+     * The file representation of the signature(s).
+     */
+    SignatureType signatureType
+    
+    /**
+     * The signatory to the generated digital signatures.
+     */
+    Signatory signatory
+
+    /**
+     * Whether or not it is required that this signature be generated.
+     */
+    boolean required
+
+    final private List<Signature> signatures = []
+        
+    String getDisplayName() {
+        "SignOperation"
+    }
+    
+    String toString() {
+        getDisplayName()
+    }
+    
+    /**
+     * Registers signatures for the given artifacts.
+     * 
+     * @see Signature#Signature(PublishArtifact,SignatureSpec,Object)
+     * @return this
+     */
+    SignOperation sign(PublishArtifact... artifacts) {
+        for (artifact in artifacts) {
+            signatures << new Signature(artifact, this)
+        }
+        this
+    }
+    
+    /**
+     * Registers signatures for the given files.
+     * 
+     * @see Signature#Signature(File,SignatureSpec,Object)
+     * @return this
+     */
+    SignOperation sign(File... files) {
+        for (file in files) {
+            signatures << new Signature(file, this)
+        }
+        this
+    }
+    
+    /**
+     * Registers signatures (with the given classifier) for the given files
+     * 
+     * @see Signature#Signature(File,String,SignatureSpec,Object)
+     * @return this
+     */
+    SignOperation sign(String classifier, File... files) {
+        for (file in files) {
+            signatures << new Signature(file, classifier, this)
+        }
+        this
+    }
+    
+    /**
+     * Change the signature type for signature generation.
+     */
+    SignOperation signatureType(SignatureType type) {
+        this.type = type
+        this
+    }
+    
+    /**
+     * Change the signatory for signature generation.
+     */
+    SignOperation signatory(Signatory signatory) {
+        this.signatory = signatory
+    }
+    
+    /**
+     * Executes the given closure against this object.
+     */
+    SignOperation configure(Closure closure) {
+        ConfigureUtil.configure(closure, this, false)
+        this
+    }
+    
+    /**
+     * Generates actual signature files for all of the registered signatures.
+     * 
+     * <p>The signatures are generated with the configuration they have at this time, which includes the signature type
+     * and signatory of this operation at this time.
+     * <p>
+     * This method can be called multiple times, with the signatures being generated with their current configuration each time.
+     * 
+     * @see Signature#generate()
+     * @return this
+     */
+    SignOperation execute() {
+        signatures*.generate()
+        this
+    }
+    
+    /**
+     * The registered signatures.
+     */
+    List<Signature> getSignatures() {
+        new ArrayList(signatures)
+    }
+    
+    /**
+     * Returns the the single registered signature.
+     *
+     * @return The signature.
+     * @throws IllegalStateException if there is not exactly one registered signature.
+     */
+    Signature getSingleSignature() {
+        if (signatures.size() == 0) {
+            throw new IllegalStateException("Expected %s to contain exactly one signature, however, it contains no signatures.")
+        } else if (signatures.size() == 1) {
+            signatures.first()
+        } else {
+            throw new IllegalStateException("Expected %s to contain exactly one signature, however, it contains no ${signature.size()} signatures.")
+        }
+    }
+    
+    /**
+     * All of the files that will be signed by this operation.
+     */
+    FileCollection getFilesToSign() {
+        new SimpleFileCollection(*signatures*.toSign.findAll({ it != null }))
+    }
+    
+    /**
+     * All of the signature files that will be generated by this operation.
+     */
+    FileCollection getSignatureFiles() {
+        new SimpleFileCollection(*signatures*.file.findAll({ it != null }))
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/Signature.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/Signature.groovy
new file mode 100644
index 0000000..ef7fb22
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/Signature.groovy
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.artifacts.publish.AbstractPublishArtifact
+
+import org.gradle.plugins.signing.signatory.Signatory
+import org.gradle.plugins.signing.type.SignatureType
+
+import org.gradle.api.InvalidUserDataException
+
+/**
+ * A digital signature file artifact.
+ * 
+ * <p>A signature file is always generated from another file, which may be a {@link PublishArtifact}.</p>
+ */
+class Signature extends AbstractPublishArtifact {
+    
+    /**
+     * The specification of how to generate the signature.
+     */
+     SignatureSpec signatureSpec
+    
+    /**
+     * The artifact that this signature is for, which may be {@code null} if this signature is not for an artifact.
+     */
+    final PublishArtifact toSignArtifact
+    
+    /**
+     * The name of the signature artifact.
+     * 
+     * @see #getName()
+     */
+    String name
+    
+    /**
+     * The extension of the signature artifact.
+     * 
+     * @see #getExtension()
+     */
+    String extension
+    
+    /**
+     * The type of the signature artifact.
+     * 
+     * @see #getType()
+     */
+    String type
+    
+    /**
+     * The classifier of the signature artifact.
+     * 
+     * @see #getClassifier()
+     */
+    String classifier
+    
+    /**
+     * The date of the signature arifact.
+     * 
+     * @see #getDate()
+     */
+    Date date
+    
+    /**
+     * The signature file.
+     * 
+     * @see #getFile()
+     */
+    File file
+
+    /*
+        To fully support laziness, we store callbacks for what is to be signed and the classifier for what is to be signed.
+        
+        This is necessary because we may be signing an ArchivePublishArtifact which is “live” in that it's tied to an AbstractArchiveTask
+        which may be modified after the user has declared that they want the artifact signed
+    */
+    private final Closure toSignGenerator
+    private final Closure classifierGenerator
+    
+    /**
+     * Creates a signature artifact for the given public artifact.
+     * 
+     * <p>The file to sign will be the file of the given artifact and the classifier of this signature artifact
+     * will default to the classifier of the given artifact to sign.</p>
+     * <p>
+     * The artifact to sign may change after being used as the source for this signature.</p>
+     * 
+     * @param toSign The artifact that is to be signed
+     * @param signatureSpec The specification of how the artifact is to be signed
+     * @param tasks The task(s) that will invoke {@link #generate()} on this signature (optional)
+     */
+    Signature(PublishArtifact toSign, SignatureSpec signatureSpec, Object... tasks) {
+        this({ toSign.file }, { toSign.classifier }, signatureSpec, tasks)
+        this.toSignArtifact = toSign
+    }
+
+    /**
+     * Creates a signature artifact for the given file.
+     * 
+     * @param toSign The file that is to be signed
+     * @param signatureSpec The specification of how the artifact is to be signed
+     * @param tasks The task(s) that will invoke {@link #generate()} on this signature (optional)
+     */
+    Signature(File toSign, SignatureSpec signatureSpec, Object... tasks) {
+        this({ toSign }, null, signatureSpec, tasks)
+    }
+
+    /**
+     * Creates a signature artifact for the given file, with the given classifier.
+     * 
+     * @param toSign The file that is to be signed
+     * @param classifier The classifier to assign to the signature (should match the files)
+     * @param signatureSpec The specification of how the artifact is to be signed
+     * @param tasks The task(s) that will invoke {@link #generate()} on this signature (optional)
+     */
+    Signature(File toSign, String classifier, SignatureSpec signatureSpec, Object... tasks) {
+        this({ toSign }, { classifier }, signatureSpec, tasks)
+    }
+    
+    /**
+     * Creates a signature artifact for the file returned by the {@code toSign} closure.
+     * 
+     * <p>The closures will be “evaluated” on demand whenever the value is needed (e.g. at generation time)</p>
+     * 
+     * @param toSign A closure that produces a File for the object to sign (non File return values will be used as the path to the file)
+     * @param classifier A closure that produces the classifier to assign to the signature artifact on demand
+     * @param signatureSpec The specification of how the artifact is to be signed
+     * @param tasks The task(s) that will invoke {@link #generate()} on this signature (optional)
+     */
+    Signature(Closure toSign, Closure classifier, SignatureSpec signatureSpec, Object... tasks) {
+        super(tasks)
+        this.toSignGenerator = toSign
+        this.classifierGenerator = classifier
+        this.signatureSpec = signatureSpec
+    }
+    
+    /**
+     * The file that is to be signed.
+     * 
+     * @return The file. May be {@code null} if unknown at this time.
+     */
+    File getToSign() {
+        def toSign = toSignGenerator?.call()
+        if (toSign == null) {
+            null
+        } else if (toSign instanceof File) {
+            toSign
+        } else {
+            new File(toSign.toString())
+        }
+    }
+
+    /**
+     * The name of the signature artifact.
+     * 
+     * <p>Defaults to the name of the signature {@link #getFile() file}.
+     * 
+     * @return The name. May be {@code null} if unknown at this time.
+     */
+    String getName() {
+        name == null ? (toSignArtifact?.name ?: getFile()?.name) : name
+    }
+    
+    /**
+     * The extension of the signature artifact.
+     * 
+     * <p>Defaults to the specified file extension of the {@link #getSignatureType() signature type}.</p>
+     * 
+     * @return The extension. May be {@code null} if unknown at this time.
+     */
+    String getExtension() {
+        extension == null ? getSignatureType()?.extension : extension
+    }
+
+    /**
+     * The type of the signature artifact.
+     * 
+     * <p>Defaults to the extension of the {@link #getToSign() file to sign} plus the extension of the {@link #getSignatureType() signature type}.
+     * For example, when signing the file ‘my.zip’ with a signature type with extension ‘sig’, the default type is ‘zip.sig’.</p>
+     * 
+     * @return The type. May be {@code null} if the file to sign or signature type are unknown at this time.
+     */
+    String getType() {
+        if (type == null) {
+            def toSign = getToSign()
+            def signatureType = getSignatureType()
+
+            if (toSign != null && signatureType != null) {
+                signatureType.combinedExtension(toSign)
+            } else {
+                null
+            }
+        } else {
+            type
+        }
+    }
+
+    /**
+     * The classifier of the signature artifact.
+     * 
+     * <p>Defaults to the classifier of the source artifact (if signing an artifact) 
+     * or the given classifier at construction (if given).</p>
+     * 
+     * @return The classifier. May be {@code null} if unknown at this time.
+     */
+    String getClassifier() {
+        classifier == null ? classifierGenerator?.call()?.toString() : classifier
+    }
+
+    /**
+     * The date of the signature artifact.
+     * 
+     * <p>Defaults to the last modified time of the {@link #getFile() signature file} (if exists)</p>
+     * 
+     * @return The date of the signature. May be {@code null} if unknown at this time.
+     */
+    Date getDate() {
+        if (date == null) {
+            def file = getFile()
+            if (file == null) {
+                null
+            } else {
+                def modified = file.lastModified()
+                if (modified == 0) {
+                    null
+                } else {
+                    new Date(modified)
+                }
+            }
+        } else {
+            date
+        }
+    }
+    
+    /**
+     * The file for the generated signature, which may not yet exist.
+     * 
+     * <p>Defaults to a file alongside the {@link #getToSign() file to sign} with the extension of the {@link #getSignatureType() signature type}.</p>
+     * 
+     * @return The signature file. May be {@code null} if unknown at this time.
+     */
+    File getFile() {
+
+        if (file == null) {
+            def toSign = getToSign()
+            def signatureType = getSignatureType()
+
+            if (toSign != null && signatureType != null) {
+                signatureType.fileFor(toSign)
+            } else {
+                null
+            }
+        } else {
+            file
+        }
+    }
+    
+    /**
+     * The signatory of this signature file.
+     * 
+     * @return The signatory. May be {@code null} if unknown at this time.
+     */
+    Signatory getSignatory() {
+        signatureSpec.getSignatory()
+    }
+    
+    /**
+     * The file representation type of the signature.
+     * 
+     * @return The signature type. May be {@code null} if unknown at this time.
+     */
+    SignatureType getSignatureType() {
+        signatureSpec.getSignatureType()
+    }
+    
+    /**
+     * Generates the signature file.
+     * 
+     * <p>In order to generate the signature, the {@link #getToSign() file to sign}, {@link #getSignatory() signatory} and {@link #getSignatureType() signature type}
+     * must be known (i.e. non {@code null}).</p>
+     * 
+     * @throws InvalidUserDataException if the there is insufficient information available to generate the signature.
+     */
+    void generate() {
+        def toSign = getToSign()
+        if (toSign == null) {
+            if (signatureSpec.required) {
+                throw new InvalidUserDataException("Unable to generate signature as the file to sign has not been specified")
+            } else {
+                return
+            }
+        }
+        
+        def signatory = getSignatory()
+        if (signatory == null) {
+            if (signatureSpec.required) {
+                throw new InvalidUserDataException("Unable to generate signature for '$toSign' as no signatory is available to sign")
+            } else {
+                return
+            }
+        }
+        
+        def signatureType = getSignatureType()
+        if (signatureType == null) {
+            if (signatureSpec.required) {
+                throw new InvalidUserDataException("Unable to generate signature for '$toSign' as no signature type has been configured")
+            } else {
+                return
+            }
+        }
+        
+        signatureType.sign(signatory, toSign)
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SignatureSpec.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SignatureSpec.groovy
new file mode 100644
index 0000000..3e652e0
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SignatureSpec.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.plugins.signing.type.SignatureType
+import org.gradle.plugins.signing.signatory.Signatory
+
+/**
+ * Specifies how objects will be signed.
+ */
+interface SignatureSpec {
+    
+    /**
+     * The signatory that will be performing the signing.
+     * 
+     * @return the signatory, or {@code null} if none specified.
+     */
+    Signatory getSignatory()
+    
+    /**
+     * Sets the signatory that will be signing the input.
+     * 
+     * @param signatory The signatory
+     */
+    void setSignatory(Signatory signatory)
+
+    /**
+     * The signature representation that will be created.
+     * 
+     * @return the signature type, or {@code null} if none specified.
+     */
+    SignatureType getSignatureType()
+    
+    /**
+     * Sets the signature representation that the signatures will be produced as.
+     * 
+     * @param type the signature type to use
+     */
+    void setSignatureType(SignatureType type)
+
+    /**
+     * Whether or not it is required that this signature be generated.
+     *
+     * A signature may not be able to be generated if a signatory and/or a signature type have not been specified.
+     * If it is required and cannot be generated, an exception will be thrown. Otherwise, it will not be generated.
+     *
+     * @return Whether or not it is required that this signature be generated.
+     */
+    boolean isRequired()
+
+    /**
+     * Whether or not it is required that this signature be generated.
+     *
+     * @see #isRequired
+     * @param Whether or not it is required that this signature be generated.
+     *
+     */
+    void setRequired(boolean required)
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
new file mode 100644
index 0000000..61e23e5
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.Task
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.IConventionAware
+
+import org.gradle.plugins.signing.signatory.*
+
+import org.gradle.plugins.signing.type.SignatureType
+import org.gradle.plugins.signing.type.SignatureTypeProvider
+import org.gradle.plugins.signing.type.DefaultSignatureTypeProvider
+
+import org.gradle.plugins.signing.signatory.pgp.PgpSignatoryProvider
+import java.util.concurrent.Callable
+import org.gradle.api.artifacts.maven.MavenDeployment
+import org.gradle.util.ConfigureUtil
+import org.gradle.api.internal.Instantiator
+
+/**
+ * The global signing configuration for a project.
+ */
+class SigningExtension {
+    
+    /**
+     * The name of the configuration that all signature artifacts will be placed into ("signatures")
+     */
+    static final String DEFAULT_CONFIGURATION_NAME = "signatures"
+    
+    /**
+     * The project that the settings are for
+     */
+    final Project project
+    
+    /**
+     * The configuration that signature artifacts will be placed into.
+     * 
+     * <p>Changing this will not affect any signing already configured.</p>
+     */
+    Configuration configuration
+
+    private required = true
+
+    /**
+     * Whether or not this task should fail if no signatory or signature type are configured at generation time.
+     *
+     * If {@code required} is a {@link Callable}, it will be stored and "called" on demand (i.e. when {@link #isRequired()}
+     * is called) and the return value will be interpreting according to the Groovy Truth. For example:
+     *
+     * <pre>
+     * signing {
+     *   required = { gradle.taskGraph.hasTask("uploadArchives") }
+     * }
+     * </pre>
+     *
+     * Because the task graph is not known until Gradle starts executing, we must use defer the decision. We can do this via using a
+     * {@link Closure} (which is a {@link Callable}).
+     *
+     * For any other type, the value will be stored and evaluated on demand according to the Groovy Truth.
+     *
+     * <pre>
+     * signing {
+     *   required = false
+     * }
+     * </pre>
+     */
+    void setRequired(Object required) {
+        this.required = required
+    }
+
+    /**
+     * Whether or not this task should fail if no signatory or signature type are configured at generation time.
+     * 
+     * <p>Defaults to {@code true}.</p>
+     *
+     * @see #setRequired(Object)
+     */
+    boolean isRequired() {
+        if (required instanceof Callable) {
+            required.call()
+        } else {
+            required
+        }
+    }
+    
+    /**
+     * The provider of signature types.
+     */
+    SignatureTypeProvider signatureTypes
+    
+    /**
+     * The provider of signatories.
+     */
+    SignatoryProvider signatories
+    
+    /**
+     * Configures the signing settings for the given project.
+     */
+    SigningExtension(Project project) {
+        this.project = project
+        this.configuration = getDefaultConfiguration()
+        this.signatureTypes = createSignatureTypeProvider()
+        this.signatories = createSignatoryProvider()
+
+        project.tasks.withType(Sign) { task ->
+            addSignatureSpecConventions(task)
+        }
+    }
+    
+    /**
+     * Provides the configuration that signature artifacts are added to. Called once during construction.
+     */
+    protected Configuration getDefaultConfiguration() {
+        def configurations = project.configurations
+        def configuration = configurations.findByName(DEFAULT_CONFIGURATION_NAME)
+        if (configuration == null) {
+            configuration = configurations.add(DEFAULT_CONFIGURATION_NAME)
+        }
+        configuration
+    }
+    
+    /**
+     * Provides the signature type provider. Called once during construction.
+     */
+    protected SignatureTypeProvider createSignatureTypeProvider() {
+        new DefaultSignatureTypeProvider()
+    }
+    
+    /**
+     * Provides the signatory provider. Called once during construction.
+     */
+    protected SignatoryProvider createSignatoryProvider() {
+        new PgpSignatoryProvider()
+    }
+    
+    /**
+     * Configures the signatory provider (delegating to its {@link SignatoryProvider#configure(SigningExtension,Closure) configure method}).
+     * 
+     * @param closure the signatory provider configuration DSL
+     * @return the configured signatory provider
+     */
+    SignatoryProvider signatories(Closure closure) {
+        signatories.configure(this, closure)
+        signatories
+    }
+    
+    /**
+     * The signatory that will be used for signing when an explicit signatory has not been specified.
+     * 
+     * <p>Delegates to the signatory provider's default signatory.</p>
+     */
+    Signatory getSignatory() {
+        signatories.getDefaultSignatory(project)
+    }
+    
+    /**
+     * The signature type that will be used for signing files when an explicit signature type has not been specified.
+     * 
+     * <p>Delegates to the signature type provider's default type.</p>
+     */
+    SignatureType getSignatureType() {
+        signatureTypes.defaultType
+    }
+    
+    /**
+     * The configuration that signature artifacts are added to.
+     */
+    Configuration getConfiguration() {
+        configuration
+    }
+
+    /**
+     * Adds conventions to the given spec, using this settings object's default signatory and signature type as the
+     * default signatory and signature type for the spec.
+     */
+    protected void addSignatureSpecConventions(SignatureSpec spec) {
+        if (!(spec instanceof IConventionAware)) {
+            throw new InvalidUserDataException("Cannot add conventions to signature spec '$spec' as it is not convention aware")
+        }
+        
+        spec.conventionMapping.map('signatory') { getSignatory() }
+        spec.conventionMapping.map('signatureType') { getSignatureType() }
+        spec.conventionMapping.required = { isRequired() }
+    }
+    
+    /**
+     * Creates signing tasks that depend on and sign the "archive" produced by the given tasks.
+     *
+     * <p>The created tasks will be named "sign<i><input task name capitalized></i>". That is, given a task with the name "jar"
+     * the created task will be named "signJar".
+     * <p>
+     * If the task is not an {@link org.gradle.api.tasks.bundling.AbstractArchiveTask}, an 
+     * {@link org.gradle.api.InvalidUserDataException} will be thrown.</p>
+     * <p>
+     * The signature artifact for the created task is added to the {@link #getConfiguration() for this settings object}.
+     *
+     * @param task The tasks whose archives are to be signed
+     * @return the created tasks.
+     */
+    List<Sign> sign(Task... tasksToSign) {
+        tasksToSign.collect { taskToSign ->
+            def signTask = project.task("sign${taskToSign.name.capitalize()}", type: Sign) {
+                sign taskToSign
+            }
+            addSignaturesToConfiguration(signTask, getConfiguration())
+            signTask
+        }
+    }
+    
+    /**
+     * Creates signing tasks that sign {@link Configuration#getAllArtifacts() all of the artifacts} of the given configurations.
+     *
+     * <p>The created tasks will be named "sign<i><configuration name capitalized></i>". That is, given a configuration with the name "archives"
+     * the created task will be named "signArchives".
+     *
+     * The signature artifact for the created task is added to the {@link #getConfiguration() for this settings object}.
+     *
+     * @param task The configurations whose archives are to be signed
+     * @return the created tasks.
+     */
+    List<Sign> sign(Configuration... configurations) {
+        configurations.collect { configuration ->
+            def signTask = project.task("sign${configuration.name.capitalize()}", type: Sign) {
+                sign configuration
+            }
+            this.addSignaturesToConfiguration(signTask, getConfiguration())
+            signTask
+        }
+    }
+    
+    protected addSignaturesToConfiguration(Sign task, Configuration configuration) {
+        task.signatures.all { configuration.artifacts.add(it) }
+        task.signatures.whenObjectRemoved { configuration.artifacts.remove(it) }
+    }
+
+    /**
+     * Digitally signs the publish artifacts, generating signature files alongside them.
+     *
+     * <p>The project's default signatory and default signature type from the
+     * {@link SigningExtension signing settings} will be used to generate the signature.
+     * The returned {@link SignOperation sign operation} gives access to the created signature files.
+     * <p>
+     * If there is no configured default signatory available, the sign operation will fail.
+     *
+     * @param publishArtifacts The publish artifacts to sign
+     * @return The executed {@link SignOperation sign operation}
+     */
+    SignOperation sign(PublishArtifact... publishArtifacts) {
+        doSignOperation {
+            sign(*publishArtifacts)
+        }
+    }
+
+    /**
+     * Digitally signs the files, generating signature files alongside them.
+     *
+     * <p>The project's default signatory and default signature type from the
+     * {@link SigningExtension signing settings} will be used to generate the signature.
+     * The returned {@link SignOperation sign operation} gives access to the created signature files.
+     * <p>
+     * If there is no configured default signatory available, the sign operation will fail.
+     *
+     * @param publishArtifacts The files to sign.
+     * @return The executed {@link SignOperation sign operation}.
+     */
+    SignOperation sign(File... files) {
+        doSignOperation {
+            sign(*files)
+        }
+    }
+
+    /**
+     * Digitally signs the files, generating signature files alongside them.
+     *
+     * <p>The project's default signatory and default signature type from the
+     * {@link SigningExtension signing settings} will be used to generate the signature.
+     * The returned {@link SignOperation sign operation} gives access to the created signature files.
+     * <p>
+     * If there is no configured default signatory available, the sign operation will fail.
+     *
+     * @param classifier The classifier to assign to the created signature artifacts.
+     * @param publishArtifacts The publish artifacts to sign.
+     * @return The executed {@link SignOperation sign operation}.
+     */
+    SignOperation sign(String classifier, File... files) {
+        doSignOperation {
+            sign(classifier, *files)
+        }
+    }
+
+    /**
+     * Creates a new {@link SignOperation sign operation} using the given closure to configure it before executing it.
+     *
+     * <p>The project's default signatory and default signature type from the
+     * {@link SigningExtension signing settings} will be used to generate the signature.
+     * The returned {@link SignOperation sign operation} gives access to the created signature files.
+     * <p>
+     * If there is no configured default signatory available (and one is not explicitly specified in this operation's configuration),
+     * the sign operation will fail.
+     *
+     * @param closure The configuration of the {@link SignOperation sign operation}.
+     * @return The executed {@link SignOperation sign operation}.
+     */
+    SignOperation sign(Closure closure) {
+        doSignOperation(closure)
+    }
+
+    /**
+     * Signs the POM artifact for the given maven deployment.
+     *
+     * <p>You can use this method to sign the generated pom when publishing to a maven repository with the maven plugin.
+     * </p>
+     * <pre autoTested=''>
+     * uploadArchives {
+     *   repositories {
+     *     mavenDeployer {
+     *       beforeDeployment { MavenDeployment deployment ->
+     *         signing.signPom(deployment)
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
+     * <p>You can optionally provide a configuration closure to fine tune the {@link SignOperation sign operation} for the POM.</p>
+     * <p>
+     * If {@link #isRequired()} is false and the signature cannot be generated (e.g. no configured signatory),
+     * this method will silently do nothing. That is, a signature for the POM file will not be uploaded.
+     *
+     * @param mavenDeployment The deployment to sign the POM of
+     * @param closure the configuration of the underlying {@link SignOperation sign operation} for the pom (optional)
+     * @return the generated signature artifact
+     */
+    Signature signPom(MavenDeployment mavenDeployment, Closure closure = null) {
+        def signOperation = sign {
+            sign(mavenDeployment.pomArtifact)
+            ConfigureUtil.configure(closure, delegate)
+        }
+
+        def pomSignature = signOperation.singleSignature
+        if (!pomSignature.file.exists()) {
+            // This means that the signature was not required and we couldn't generate the signature
+            // (most likely project.required == false and there is no signatory)
+            // So just noop
+            return
+        }
+
+        // Have to alter the "type" of the artifact to match what is published
+        // See http://issues.gradle.org/browse/GRADLE-1589
+        pomSignature.type = "pom." + pomSignature.signatureType.extension
+
+        mavenDeployment.addArtifact(pomSignature)
+        pomSignature
+    }
+
+    protected SignOperation doSignOperation(Closure setup) {
+        def operation = project.services.get(Instantiator).newInstance(SignOperation)
+        addSignatureSpecConventions(operation)
+        operation.configure(setup)
+        operation.execute()
+        operation
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy
new file mode 100644
index 0000000..c4cd3e5
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.BasePlugin
+
+/**
+ * Adds the ability to digitially sign files and artifacts.
+ */
+class SigningPlugin implements Plugin<Project> {
+
+    /**
+     * <p>Adds the ability to digitially sign files and artifacts.</p>
+     * 
+     * <p>Adds the extensnion {@link org.gradle.plugins.signing.SigningExtension} with the name “signing”.
+     * <p>Also adds conventions to all {@link org.gradle.plugins.signing.Sign sign tasks} to use the signing extension setting defaults.</p>
+     * 
+     * @see org.gradle.plugins.signing.SigningExtension
+     */
+    void apply(Project project) {
+        project.plugins.apply(BasePlugin)
+
+        project.extensions.create("signing", SigningExtension, project)
+        def extension = project.signing
+
+        def convention = new SigningPluginConvention(extension)
+        project.convention.plugins.signing = convention
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy
new file mode 100644
index 0000000..11ae0e1
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPluginConvention.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.maven.MavenDeployment
+import org.gradle.util.DeprecationLogger
+
+/**
+ * @deprecated Use {@link SigningExtension}
+ */
+class SigningPluginConvention {
+    
+    private SigningExtension extension
+    
+    SigningPluginConvention(SigningExtension extension) {
+        this.extension = extension
+    }
+
+    /**
+     * @deprecated Use {@link SigningExtension#sign(PublishArtifact...) project.signing.sign(PublishArtifact...) }
+     */
+    SignOperation sign(PublishArtifact... publishArtifacts) {
+        DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
+        extension.sign(publishArtifacts)
+    }
+
+    /**
+     * @deprecated Use {@link SigningExtension#sign(File...) project.signing.sign(File...) }
+     */
+    SignOperation sign(File... files) {
+        DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
+        extension.sign(files)
+    }
+    
+    /**
+     * @deprecated Use {@link SigningExtension#sign(String, File...) project.signing.sign(String, File...)}
+     */
+    SignOperation sign(String classifier, File... files) {
+        DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
+        extension.sign(classifier, files)
+    }
+    
+    /**
+     * @deprecated Use {@link SigningExtension#sign(Closure) project.signing.sign \{ } }
+     */
+    SignOperation sign(Closure closure) {
+        DeprecationLogger.nagUserOfReplacedMethod("sign()", "signing.sign()")
+        extension.sign(closure)
+    }
+    
+    /**
+     * @deprecated Use {@link SigningExtension#signPom() project.signing.signPom}
+     */
+    Signature signPom(MavenDeployment mavenDeployment, Closure closure = null) {
+        DeprecationLogger.nagUserOfReplacedMethod("signPom()", "signing.signPom()")
+        extension.signPom(mavenDeployment, closure)
+    }
+    
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/Signatory.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/Signatory.groovy
new file mode 100644
index 0000000..ebb0449
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/Signatory.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.signing.signatory
+
+/**
+ * A signatory is an object capable of providing a signature for an arbitrary stream of bytes.
+ */
+interface Signatory {
+    
+    /**
+     * <p>An identifying name for this signatory.</p>
+     * 
+     * <p>The name must be constant for the life of the signatory and should uniquely identify it
+     * within a project.</p>
+     */
+    String getName()
+    
+    /**
+     * Exhausts {@code toSign}, and writes the signature to {@code signatureDestination}.
+     * The caller is responsible for closing the streams, though the output WILL be flushed.
+     * 
+     * @param toSign The source of the data to be signed
+     * @param destination Where the signature will be written to
+     */
+    void sign(InputStream toSign, OutputStream destination)
+    
+    /**
+     * Exhausts {@code toSign}, and returns the raw signature bytes.
+     * 
+     * @param toSign The source of the data to be signed
+     * @return The raw bytes of the signature
+     */
+    byte[] sign(InputStream toSign)
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/SignatoryProvider.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/SignatoryProvider.groovy
new file mode 100644
index 0000000..cc84f97
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/SignatoryProvider.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.signing.signatory
+
+import org.gradle.plugins.signing.SigningExtension
+import org.gradle.api.Project
+
+/**
+ * <p>Provides implementations of signatory implementations for a project.</p>
+ */
+interface SignatoryProvider<T extends Signatory> {
+    
+    /**
+     * Evaluates the given DSL-containing-closure as signatory configuration.
+     * 
+     * @param settings The signing settings for the project the configure is happening for
+     */
+    void configure(SigningExtension settings, Closure closure)
+    
+    /**
+     * <p>Attempts to create a signatory for the project that will be used everywhere something is to be signed 
+     * and an explicit signatory has not been set (for the task/operation).</p>
+     * 
+     * <p>This may be called multiple times and the implementor is free to return a different instance if the
+     * project state has changed in someway that influences the default signatory.</p>
+     * 
+     * @param project The project which the signatory is for
+     * @return The signatory, or {@code null} if there is insufficient information available to create one.
+     */
+    T getDefaultSignatory(Project project)
+    
+    /**
+     * Retrieves the signatory with the given name.
+     * 
+     * @param name The desired signatory's name.
+     * @return The signatory with the given name if found, or {@code null} if no signatory is found with this name.
+     */
+    T getSignatory(String name)
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/SignatorySupport.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/SignatorySupport.groovy
new file mode 100644
index 0000000..0a12756
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/SignatorySupport.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.signing.signatory
+
+abstract class SignatorySupport implements Signatory {
+    
+    byte[] sign(InputStream toSign) {
+        def signature = new ByteArrayOutputStream()
+        sign(toSign, signature)
+        signature.toByteArray()
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpKeyId.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpKeyId.groovy
new file mode 100644
index 0000000..c091ecb
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpKeyId.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2011 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.signing.signatory.pgp
+
+import org.bouncycastle.openpgp.PGPPublicKey
+import org.bouncycastle.openpgp.PGPSignature
+
+/**
+ * A normalised form for keys, which are friendliest for users as hex strings but used internally as longs.
+ */
+class PgpKeyId implements Comparable<PgpKeyId> {
+    
+    final String asHex
+    final long asLong
+
+    PgpKeyId(long keyId) {
+        asLong = keyId
+        asHex = toHex(keyId)
+    }
+
+    PgpKeyId(PGPPublicKey keyId) {
+        this(keyId.keyID)
+    }
+
+    PgpKeyId(PGPSignature signature) {
+        this(signature.keyID)
+    }
+
+    public PgpKeyId(String keyId) {
+        asLong = toLong(keyId)
+        asHex = toHex(asLong)
+    }
+
+    static String toHex(long keyId) {
+        String.format("%08X", (0xFFFFFFFFL & keyId))
+    }
+    
+    static long toLong(String keyId) {
+        if (keyId == null) {
+            throw new IllegalArgumentException("'keyId' cannot be null")
+        }
+        
+        def normalised
+        def keyIdUpped = keyId.toUpperCase()
+        if (keyIdUpped.size() == 10) {
+            if (!keyIdUpped.startsWith("0X")) {
+                throw new IllegalArgumentException("10 character key IDs must start with 0x (given value: $keyId)")
+            }
+
+            normalised = keyIdUpped.substring(2)
+        } else if (keyIdUpped.size() == 8) {
+            if (keyId.startsWith("0X")) {
+                throw new IllegalArgumentException("8 character key IDs must not start with 0x (given value: $keyId)")
+            }
+            
+            normalised = keyIdUpped
+        } else {
+            throw new IllegalStateException("The key ID must be in a valid form (eg 00B5050F or 0x00B5050F), given value: $keyId")
+        }
+        
+        try {
+            Long.parseLong(normalised, 16)
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("the key id '$keyId' is not a valid hex string")
+        }
+    }
+
+    @Override
+    boolean equals(Object other) {
+        other instanceof PgpKeyId && other.asHex == this.asHex
+    }
+
+    @Override
+    int hashCode() {
+        asHex.hashCode()
+    }
+
+    @Override
+    String toString() {
+        asHex
+    }
+
+    int compareTo(PgpKeyId other) {
+        other == null ? -1 : this.asHex.compareTo(other.asHex)
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatory.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatory.groovy
new file mode 100644
index 0000000..6398bea
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatory.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.signing.signatory.pgp
+
+import org.bouncycastle.openpgp.PGPUtil
+import org.bouncycastle.openpgp.PGPSecretKey
+import org.bouncycastle.openpgp.PGPPrivateKey
+import org.bouncycastle.openpgp.PGPSignature
+import org.bouncycastle.openpgp.PGPSignatureGenerator
+import org.bouncycastle.bcpg.BCPGOutputStream
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+
+import java.security.Security
+
+import org.gradle.plugins.signing.signatory.SignatorySupport
+
+class PgpSignatory extends SignatorySupport {
+    
+    final String name
+    final private String password
+    final private PGPSecretKey secretKey
+    final private PGPPrivateKey privateKey
+    
+    PgpSignatory(String name, PGPSecretKey secretKey, String password) {
+        // ok to call multiple times, will be ignored
+        Security.addProvider(new BouncyCastleProvider())
+        
+        this.name = name
+        this.password = password
+        this.secretKey = secretKey
+        this.privateKey = secretKey.extractPrivateKey(password.toCharArray(), BouncyCastleProvider.PROVIDER_NAME)
+    }
+
+    PgpKeyId getKeyId() {
+        new PgpKeyId(secretKey.keyID)
+    }
+    
+    PGPSignatureGenerator createSignatureGenerator() {
+        def generator = new PGPSignatureGenerator(secretKey.publicKey.algorithm, PGPUtil.SHA1, BouncyCastleProvider.PROVIDER_NAME)
+        generator.initSign(PGPSignature.BINARY_DOCUMENT, privateKey)
+        generator
+    }
+    
+    /**
+     * Exhausts {@code toSign}, and writes the signature to {@code signatureDestination}.
+     * 
+     * The caller is responsible for closing the streams, though the output WILL be flushed.
+     */
+    void sign(InputStream toSign, OutputStream signatureDestination) {
+        def generator = createSignatureGenerator()
+        
+        def buffer = new byte[1024]
+        def read = toSign.read(buffer)
+        while (read > 0) {
+            generator.update(buffer, 0, read)
+            read = toSign.read(buffer)
+        }
+
+        // BCPGOutputStream seems to do some internal buffering, it's unclear whether it's stricly required here though
+        def bufferedOutput = new BCPGOutputStream(signatureDestination)
+        def signature = generator.generate()
+        signature.encode(bufferedOutput)
+        bufferedOutput.flush()
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatoryFactory.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatoryFactory.groovy
new file mode 100644
index 0000000..3b60fed
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatoryFactory.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2011 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.signing.signatory.pgp
+
+import org.bouncycastle.openpgp.PGPSecretKey
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection
+
+import org.gradle.api.Project
+import org.gradle.api.InvalidUserDataException
+
+class PgpSignatoryFactory {
+    
+    static private final PROPERTIES = ["keyId", "secretKeyRingFile", "password"]
+    
+    PgpSignatory createSignatory(Project project, boolean required = false) {
+        readProperties(project, null, "default", required)
+    }
+    
+    PgpSignatory createSignatory(Project project, String propertyPrefix, boolean required = false) {
+        readProperties(project, propertyPrefix, propertyPrefix, required)
+    }
+
+    PgpSignatory createSignatory(Project project, String propertyPrefix, String name, boolean required = false) {
+        readProperties(project, propertyPrefix, name, required)
+    }
+    
+    protected PgpSignatory readProperties(Project project, String prefix, String name, boolean required = false) {
+        def qualifiedProperties = PROPERTIES.collect { getQualifiedPropertyName(prefix, it) }
+        def values = []
+        for (property in qualifiedProperties) {
+            if (project.hasProperty(property)) {
+                values << project[property]
+            } else {
+                if (required) {
+                    throw new InvalidUserDataException("property '$property' could not be found on project and is needed for signing")
+                } else {
+                    return null
+                }
+            }
+        }
+        
+        def keyId = values[0].toString()
+        def keyRing = project.file(values[1].toString())
+        def password = values[2].toString()
+        
+        createSignatory(name, keyId, keyRing, password)
+    }
+    
+    protected getQualifiedPropertyName(String propertyPrefix, String name) {
+        "signing.${propertyPrefix ? propertyPrefix + '.' : ''}${name}"
+    }
+    
+    PgpSignatory createSignatory(String name, String keyId, File keyRing, String password) {
+        createSignatory(name, readSecretKey(keyId, keyRing), password)
+    }
+    
+    PgpSignatory createSignatory(String name, PGPSecretKey secretKey, String password) {
+        new PgpSignatory(name, secretKey, password)
+    }
+        
+    PGPSecretKey readSecretKey(String keyId, File file) {
+        if (!file.exists()) {
+            throw new InvalidUserDataException("Unable to retrieve secret key from key ring file '$file' as it does not exist")
+        }
+        
+        file.withInputStream { readSecretKey(it, keyId, "file: $file.absolutePath") }
+    }
+    
+    protected PGPSecretKey readSecretKey(InputStream input, String keyId, String sourceDescription) {
+        def keyRingCollection
+        try {
+            keyRingCollection = new PGPSecretKeyRingCollection(input)
+        } catch (Exception e) {
+            throw new InvalidUserDataException("Unable to read secret key from $sourceDescription (it may not be a PGP secret key ring)", e)
+        }
+        
+        readSecretKey(keyRingCollection, normalizeKeyId(keyId), sourceDescription)
+    }
+    
+    protected PGPSecretKey readSecretKey(PGPSecretKeyRingCollection keyRings, PgpKeyId keyId, String sourceDescription) {
+        def key = keyRings.keyRings.find { new PgpKeyId(it.secretKey.keyID) == keyId }?.secretKey
+        if (key == null) {
+            throw new InvalidUserDataException("did not find secret key for id '$keyId' in key source '$sourceDescription'")
+        }
+        key
+    }
+    
+    // TODO - move out to DSL adapter layer (i.e. signatories container)
+    protected PgpKeyId normalizeKeyId(String keyId) {
+        try {
+            new PgpKeyId(keyId)
+        } catch (IllegalArgumentException e) {
+            throw new InvalidUserDataException(e.message)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatoryProvider.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatoryProvider.groovy
new file mode 100644
index 0000000..ad6ff42
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/signatory/pgp/PgpSignatoryProvider.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.signing.signatory.pgp
+
+import org.gradle.plugins.signing.signatory.SignatoryProvider
+
+import org.gradle.util.ConfigureUtil
+
+import org.gradle.api.Project
+
+import org.gradle.plugins.signing.SigningExtension
+
+class PgpSignatoryProvider implements SignatoryProvider/*<PgpSignatory>*/ { // Groovy can't deal with this - 1.7.10
+    
+    private final factory = new PgpSignatoryFactory()
+    private final Map<String, PgpSignatory> signatories = [:]
+    
+    void configure(SigningExtension settings, Closure closure) {
+        ConfigureUtil.configure(closure, new Dsl(settings.project, signatories, factory))
+    }
+    
+    PgpSignatory getDefaultSignatory(Project project) {
+        factory.createSignatory(project)
+    }
+    
+    PgpSignatory getSignatory(String name) {
+        signatories[name]
+    }
+    
+    PgpSignatory propertyMissing(String signatoryName) {
+        getSignatory(signatoryName)
+    }
+}
+
+class Dsl {
+    
+    private final project
+    private final signatories
+    private final factory
+    
+    Dsl(Project project, Map<String, PgpSignatory> signatories, PgpSignatoryFactory factory) {
+        this.project = project
+        this.signatories = signatories
+        this.factory = factory
+    }
+    
+    def methodMissing(String name, args) {
+        def signatory
+        if (args.size() == 3) {
+            def keyId = args[0].toString()
+            def keyRing = project.file(args[1].toString())
+            def password = args[2].toString()
+
+            signatory = factory.createSignatory(name, keyId, keyRing, password)
+        } else if (args.size() == 0) {
+            signatory = factory.createSignatory(project, name, true)
+        } else {
+            throw new Exception("Invalid args ($name: $args)")
+        }
+        
+        signatories[signatory.name] = signatory
+    }
+}
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/AbstractSignatureType.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/AbstractSignatureType.groovy
new file mode 100644
index 0000000..a4afdd8
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/AbstractSignatureType.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.signing.type
+
+import org.gradle.plugins.signing.signatory.Signatory
+
+abstract class AbstractSignatureType implements SignatureType {
+
+    File sign(Signatory signatory, File toSign) {
+        def signatureFile = fileFor(toSign)
+        toSign.withInputStream { toSignStream ->
+            signatureFile.withOutputStream { signatureFileStream ->
+                sign(signatory, toSignStream, signatureFileStream)
+            }
+        }
+        signatureFile
+    }
+    
+    void sign(Signatory signatory, InputStream toSign, OutputStream destination) {
+        signatory.sign(toSign, destination)
+    }
+
+    File fileFor(File toSign) {
+        new File(toSign.path + ".${getExtension()}")
+    }
+    
+    String combinedExtension(File toSign) {
+        def name = toSign.name
+        def dotIndex = name.lastIndexOf(".")
+        if (dotIndex == -1 || dotIndex + 1 == name.size()) {
+            getExtension()
+        } else {
+            name[++dotIndex..-1] + ".${getExtension()}"
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeProvider.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeProvider.groovy
new file mode 100644
index 0000000..38dfd9e
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeProvider.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.signing.type
+
+import org.gradle.api.InvalidUserDataException
+
+abstract class AbstractSignatureTypeProvider implements SignatureTypeProvider {
+    
+    private String defaultTypeExtension
+    private Map<String, SignatureType> types = [:]
+    
+    SignatureType getDefaultType() {
+        getTypeForExtension(defaultTypeExtension)
+    }
+    
+    void setDefaultType(String defaultTypeExtension) {
+        getTypeForExtension(defaultTypeExtension) // verify we have this extension
+        this.defaultTypeExtension = defaultTypeExtension
+    }
+    
+    SignatureType getTypeForExtension(String extension) {
+        if (!types.containsKey(extension)) {
+            throw new InvalidUserDataException("no signature type is registered for extension '$extension'")
+        }
+        types[extension]
+    }
+    
+    protected void register(SignatureType type) {
+        types[type.extension] = type
+    }
+    
+    boolean hasTypeForExtension(String extension) {
+        types.containsKey(extension)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/BinarySignatureType.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/BinarySignatureType.groovy
new file mode 100644
index 0000000..3cd1323
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/BinarySignatureType.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.signing.type
+
+class BinarySignatureType extends AbstractSignatureType {
+    
+    String getExtension() {
+        "sig"
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/DefaultSignatureTypeProvider.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/DefaultSignatureTypeProvider.groovy
new file mode 100644
index 0000000..bb050e8
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/DefaultSignatureTypeProvider.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2011 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.signing.type
+
+import org.gradle.plugins.signing.type.pgp.ArmoredSignatureType
+
+class DefaultSignatureTypeProvider extends AbstractSignatureTypeProvider {
+    
+    DefaultSignatureTypeProvider() {
+        register(new BinarySignatureType())
+        def armored = new ArmoredSignatureType()
+        register(armored)
+        setDefaultType(armored.extension)
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/SignatureType.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/SignatureType.groovy
new file mode 100644
index 0000000..bc76408
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/SignatureType.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.signing.type
+
+import org.gradle.plugins.signing.signatory.Signatory
+
+interface SignatureType {
+
+    String getExtension()
+
+    File fileFor(File toSign)
+    
+    String combinedExtension(File toSign)
+    
+    File sign(Signatory signatory, File toSign)
+    
+    void sign(Signatory signatory, InputStream toSign, OutputStream destination)
+    
+}
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/SignatureTypeProvider.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/SignatureTypeProvider.groovy
new file mode 100644
index 0000000..5359787
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/SignatureTypeProvider.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.signing.type
+
+interface SignatureTypeProvider {
+    
+    SignatureType getDefaultType()
+    
+    void setDefaultType(String extension)
+    
+    SignatureType getTypeForExtension(String extension)
+    
+    boolean hasTypeForExtension(String extension)
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/pgp/ArmoredSignatureType.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/pgp/ArmoredSignatureType.groovy
new file mode 100644
index 0000000..0d90b25
--- /dev/null
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/type/pgp/ArmoredSignatureType.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.signing.type.pgp
+
+import org.bouncycastle.bcpg.ArmoredOutputStream
+import org.gradle.plugins.signing.signatory.Signatory
+import org.gradle.plugins.signing.type.AbstractSignatureType
+
+class ArmoredSignatureType extends AbstractSignatureType {
+
+    String getExtension() {
+        "asc"
+    }
+    
+    void sign(Signatory signatory, InputStream toSign, OutputStream destination) {
+        def armoredOutputStream = new ArmoredOutputStream(destination)
+        super.sign(signatory, toSign, armoredOutputStream)
+        armoredOutputStream.close()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/main/resources/META-INF/gradle-plugins/signing.properties b/subprojects/signing/src/main/resources/META-INF/gradle-plugins/signing.properties
new file mode 100644
index 0000000..0aa92e7
--- /dev/null
+++ b/subprojects/signing/src/main/resources/META-INF/gradle-plugins/signing.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.signing.SigningPlugin
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/ConventionSmokeSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/ConventionSmokeSpec.groovy
new file mode 100644
index 0000000..fd517f4
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/ConventionSmokeSpec.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2011 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.signing
+
+
+import org.gradle.plugins.signing.signatory.SignatoryProvider
+import spock.lang.Unroll
+
+class ConventionSmokeSpec extends SigningProjectSpec {
+    
+    def setup() {
+        applyPlugin()
+    }
+    
+    def "signing block"() {
+        when:
+        signing {
+            signatories {
+                
+            }
+        }
+        
+        then:
+        notThrown Exception
+    }
+
+    def "signatories"() {
+        expect:
+        signing.signatories != null
+        signing.signatories instanceof SignatoryProvider
+    }
+    
+    def "signing configuration"() {
+        expect:
+        signing != null
+        signing instanceof SigningExtension
+        signing.project == project
+    }
+    
+    def "default signatory with no properties"() {
+        expect:
+        signing.signatory == null
+    }
+    
+    def "default type"() {
+        expect:
+        signing.signatureType.extension == "asc"
+    }
+    
+    @Unroll
+    def "required has flexible input"() {
+        when:
+        signing.required = value
+        
+        then:
+        signing.required == required
+
+        where:
+        value | required
+        true  | true
+        false | false
+        []    | false
+        ""    | false
+    }
+    
+    def "can supply a callable as the required value"() {
+        given:
+        def flag = false
+        signing.required { flag }
+        
+        expect:
+        !signing.required
+        
+        when:
+        flag = true
+        
+        then:
+        signing.required
+    }
+    
+
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SignOperationSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SignOperationSpec.groovy
new file mode 100644
index 0000000..e000eb4
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SignOperationSpec.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+
+class SignOperationSpec extends SigningProjectSpec {
+    
+    def input1
+    def input2
+    def input1Artifact
+    def input2Artifact
+    def output1
+    def output2
+    
+    def setup() {
+        applyPlugin()
+        addSigningProperties()
+        
+        input1 = getResourceFile("1.txt")
+        input1Artifact = new DefaultPublishArtifact(input1.name, "Text File", "txt", null, null, input1)
+        output1 = signing.signatureType.fileFor(input1)
+        
+        input2 = getResourceFile("2.txt")
+        output2 = signing.signatureType.fileFor(input2)
+        input2Artifact = new DefaultPublishArtifact(input2.name, "Text File", "txt", null, null, input2)
+        
+        [output1, output2].each { output ->
+            assert !output.exists() || output.delete()
+        }
+        
+        assert input1.text && input2.text  // don't care what it is, just need some
+    }
+    
+    def "sign single file with defaults"() {
+        when:
+        def operation = sign(input1)
+        
+        then:
+        output1.exists()
+        output1 == operation.singleSignature.file
+    }
+    
+    def "sign single artifact with defaults"() {
+        when:
+        def operation = sign(input1Artifact)
+        
+        then:
+        output1.exists()
+        output1 == operation.singleSignature.file
+    }
+    
+    def "sign multiple files with defaults"() {
+        when:
+        def operation = sign(input1, input2)
+        
+        then:
+        output1.exists() && output2.exists()
+        [input1, input2] == operation.filesToSign.files.toList()[0..1]
+        [output1, output2] == operation.signatureFiles.files.toList()[0..1]
+        [output1, output2] == operation.signatures*.file.toList()
+    }
+
+    def "sign multiple artifacts with defaults"() {
+        when:
+        def operation = sign(input1Artifact, input2Artifact)
+        
+        then:
+        output1.exists() && output2.exists()
+        [input1, input2] == operation.filesToSign.files.toList()[0..1]
+        [output1, output2] == operation.signatureFiles.files.toList()[0..1]
+        [output1, output2] == operation.signatures*.file.toList()
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SignatoriesConfigurationSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SignatoriesConfigurationSpec.groovy
new file mode 100644
index 0000000..0bde32f
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SignatoriesConfigurationSpec.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2011 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.signing
+
+import org.gradle.api.InvalidUserDataException
+
+class SignatoriesConfigurationSpec extends SigningProjectSpec {
+    
+    def setup() {
+        applyPlugin()
+    }
+
+    def "default signatory returns null if no properties set"() {
+        expect:
+        signing.signatory == null
+    }
+    
+    def "default signatory with properties"() {
+        when:
+        addSigningProperties()
+        
+        then:
+        signing.signatory != null
+    }
+    
+    def "defining signatories with properties"() {
+        given:
+        def properties = signingPropertiesSet
+        
+        when:
+        signing {
+            signatories {
+                custom properties.keyId, properties.secretKeyRingFile, properties.password
+            }
+        }
+        
+        then:
+        signing.signatories.custom != null
+        signing.signatories.custom.keyId.asHex == properties.keyId
+    }
+    
+    def "defining signatories with default properties"() {
+        given:
+        def properties = addSigningProperties(prefix: "custom")
+
+        when:
+        signing {
+            signatories {
+                custom()
+            }
+        }
+        
+        then:
+        signing.signatories.custom != null
+        signing.signatories.custom.keyId.asHex == properties.keyId
+    }
+ 
+    def "trying to read non existent file produces reasonable error message"() {
+        when:
+        setProperty("signing.keyId", "aaaaaaaa")
+        setProperty("signing.secretKeyRingFile", "i/dont/exist")
+        setProperty("signing.password", "anything")
+        
+        and:
+        signing.signatory
+        
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message.contains "it does not exist"
+    }
+    
+    def "trying to use an invalid key ring file produces a reasonable error message"() {
+        given:
+        addSigningProperties(set: "invalid-key-ring")
+        
+        when:
+        signing.signatory
+        
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message.contains "Unable to read secret key from"
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningConfigurationsSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningConfigurationsSpec.groovy
new file mode 100644
index 0000000..e9a7396
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningConfigurationsSpec.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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.signing
+
+class SigningConfigurationsSpec extends SigningProjectSpec {
+    
+    def setup() {
+        applyPlugin()
+        useJavadocAndSourceJars()
+        configurations {
+            meta
+            produced.extendsFrom meta, archives
+        }
+        
+        artifacts {
+            meta javadocJar, sourcesJar
+        }
+    }
+        
+    def "sign configuration with defaults"() {
+        when:
+        signing {
+            sign configurations.archives, configurations.meta
+        }
+        
+        then:
+        def signingTasks = [signArchives, signMeta]
+
+        // TODO - find way to test that the appopriate dependencies have been setup
+        //        it would be easy if we could do…
+        // 
+        // configurations.archives.buildArtifacts in signArchives.dependsOn
+        // 
+        //        but we can't because of http://issues.gradle.org/browse/GRADLE-1608
+        
+        and:
+        configurations.signatures.artifacts.size() == 3
+        signingTasks.every { it.signatures.every { it in configurations.signatures.artifacts } }
+    }
+
+    def "sign configuration with inherited artifacts"() {
+        when:
+        signing {
+            sign configurations.produced
+        }
+        
+        then:
+        configurations.signatures.artifacts.size() == 3
+        signProduced.signatures.every { it in configurations.signatures.artifacts }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
new file mode 100644
index 0000000..a2babf2
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningProjectSpec.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 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.signing
+
+import spock.lang.*
+import org.gradle.api.Project
+import org.gradle.api.tasks.bundling.*
+import org.gradle.util.HelperUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+
+class SigningProjectSpec extends Specification {
+    
+    static final DEFAULT_KEY_SET = "gradle"
+    
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+        
+    Project project = HelperUtil.createRootProject()
+    
+    private assertProject() {
+        assert project != null : "You haven't created a project"
+    }
+    
+    def methodMissing(String name, args) {
+        assertProject()
+        project."$name"(*args)
+    }
+    
+    def propertyMissing(String name) {
+        project."$name"
+    }
+    
+    def propertyMissing(String name, value) {
+        project."$name" = value
+    }
+    
+    def applyPlugin() {
+        apply plugin: "signing"
+    }
+    
+    def addProperties(Map props) {
+        props.each { k, v ->
+            project.setProperty(k, v)
+        }
+    }
+    
+    def addSigningProperties(keyId, secretKeyRingFile, password) {
+        addPrefixedSigningProperties(null, keyId, secretKeyRingFile, password)
+    }
+    
+    def addPrefixedSigningProperties(prefix, keyId, secretKeyRingFile, password) {
+        def truePrefix = prefix ? "${prefix}." : "" 
+        def properties = [:]
+        def values = [keyId: keyId, secretKeyRingFile: secretKeyRingFile, password: password]
+        values.each { k, v ->
+            properties["signing.${truePrefix}${k}"] = v
+        }
+        addProperties(properties)
+        values
+    }
+    
+    def getSigningPropertiesSet(setName = DEFAULT_KEY_SET) {
+        def properties = [:]
+        properties.keyId = getKeyResourceFile(setName, "keyId.txt").text.trim()
+        properties.secretKeyRingFile = getKeyResourceFile(setName, "secring.gpg").absolutePath
+        properties.password = getKeyResourceFile(setName, "password.txt").text.trim()
+        properties
+    }
+    
+    def addSigningProperties(Map args = [:]) {
+        def properties = getSigningPropertiesSet(args.set ?: DEFAULT_KEY_SET)
+        addPrefixedSigningProperties(args.prefix, properties.keyId, properties.secretKeyRingFile, properties.password)
+    }
+    
+    def getKeyResourceFile(setName, fileName) {
+        getResourceFile("keys/$setName/$fileName")
+    }
+    
+    def getResourceFile(path) {
+        def copiedFile = tmpDir.file(path)
+        if (!copiedFile.exists()) {
+            
+            def url = getClass().classLoader.getResource(path)
+            def file = new File(url.toURI())
+            if (file.exists()) {
+                copiedFile.copyFrom(file)
+            }
+        }
+        
+        copiedFile
+    }
+    
+    def useJavadocAndSourceJars() {
+        apply plugin: "java"
+        
+        task("sourcesJar", type: Jar, dependsOn: classes) { 
+            classifier = 'sources' 
+            from sourceSets.main.allSource
+        } 
+
+        task("javadocJar", type: Jar, dependsOn: javadoc) { 
+            classifier = 'javadoc' 
+            from javadoc.destinationDir 
+        } 
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningTasksSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningTasksSpec.groovy
new file mode 100644
index 0000000..f896f60
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/SigningTasksSpec.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.signing
+
+class SigningTasksSpec extends SigningProjectSpec {
+    
+    def setup() {
+        applyPlugin()
+    }
+        
+    def "sign jar with defaults"() {
+        given:
+        useJavadocAndSourceJars()
+        
+        when:
+        signing {
+            sign jar
+            sign sourcesJar, javadocJar
+        }
+        
+        then:
+        def signingTasks = [signJar, signSourcesJar, signJavadocJar]
+        
+        and:
+        jar in signJar.dependsOn
+        sourcesJar in signSourcesJar.dependsOn
+        javadocJar in signJavadocJar.dependsOn
+        
+        and:
+        signingTasks.every { it.singleSignature in configurations.signatures.artifacts }
+
+        and:
+        signingTasks.every { it.signatory == signing.signatory }
+    }
+    
+    def "sign method return values"() {
+        given:
+        useJavadocAndSourceJars()
+        
+        when:
+        def signJarTask = signing.sign(jar).first()
+        
+        then:
+        signJarTask.name == "signJar"
+        
+        when:
+        def (signSourcesJarTask, signJavadocJarTask) = signing.sign(sourcesJar, javadocJar)
+        
+        then:
+        [signSourcesJarTask, signJavadocJarTask]*.name == ["signSourcesJar", "signJavadocJar"]
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/signatory/pgp/PgpKeyIdSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/signatory/pgp/PgpKeyIdSpec.groovy
new file mode 100644
index 0000000..a61f128
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/signatory/pgp/PgpKeyIdSpec.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.signing.signatory.pgp
+
+import spock.lang.*
+
+class PgpKeyIdSpec extends Specification  {
+    
+    protected key(arg) {
+        new PgpKeyId(arg)
+    }
+    
+    def "conversion is symmetrical"() {
+        expect:
+        key("ABCDABCD").asHex == "ABCDABCD"
+    }
+    
+    @Unroll
+    def "conversion"() {
+        expect:
+        key(hex).asLong == decimal
+        key(decimal).asHex == hex
+        
+        where:
+        hex        | decimal
+        "AAAAAAAA" | 2863311530
+        "DA124B92" | 3658632082
+    }
+    
+    def "equals impl"() {
+        expect:
+        key("AAAAAAAA") == key(2863311530)
+    }
+    
+    def "comparison"() {
+        expect:
+        key("00000000") < key("00000001")
+        key("00000001") > key("00000000")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeProviderSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeProviderSpec.groovy
new file mode 100644
index 0000000..3a95547
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeProviderSpec.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.signing.type
+
+import org.gradle.api.InvalidUserDataException
+
+import spock.lang.*
+
+class AbstractSignatureTypeProviderSpec extends Specification {
+    
+    static type1 = new AbstractSignatureType() { String getExtension() { "1" } }
+    static type2 = new AbstractSignatureType() { String getExtension() { "2" } }
+    static type3 = new AbstractSignatureType() { String getExtension() { "3" } }
+    
+    def provider = new AbstractSignatureTypeProvider() {{ 
+        register(AbstractSignatureTypeProviderSpec.type1)
+        register(AbstractSignatureTypeProviderSpec.type2)
+        setDefaultType(AbstractSignatureTypeProviderSpec.type1.extension)
+    }}
+    
+    def "has check"() {
+        expect:
+        provider.hasTypeForExtension(type1.extension)
+        provider.hasTypeForExtension(type2.extension)
+        !provider.hasTypeForExtension(type3.extension)
+    }
+    
+    def "default type"() {
+        expect:
+        provider.defaultType == type1
+        
+        when:
+        provider.defaultType = type2.extension
+        
+        then:
+        provider.defaultType == type2
+    }
+    
+    def "can't set default type to unregistered type"() {
+        when:
+        provider.defaultType = type3.extension
+        
+        then:
+        thrown InvalidUserDataException
+    }
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeSpec.groovy b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeSpec.groovy
new file mode 100644
index 0000000..ba8ab37
--- /dev/null
+++ b/subprojects/signing/src/test/groovy/org/gradle/plugins/signing/type/AbstractSignatureTypeSpec.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.signing.type
+
+import spock.lang.*
+
+class AbstractSignatureTypeSpec extends Specification {
+
+    static extension = "abc"
+    def type = new AbstractSignatureType() { String getExtension() { AbstractSignatureTypeSpec.extension } }
+
+    def "fileFor"() {
+        when:
+        def input = new File(path)
+        
+        then:
+        type.fileFor(input) == new File(input.path + ".$extension")
+        
+        where:
+        path << ["some.txt", "/absolute/some.txt", "relative/some.txt"]
+    }
+    
+    def "combined extension"() {
+        expect:
+        type.combinedExtension(new File(name)) == expected
+        
+        where:
+        name          | expected
+        "pom.xml"     | "xml.$extension" 
+        "pom"         | extension
+        "pom.xml.zip" | "zip.$extension"
+        ""            | extension
+    }
+
+    
+}
\ No newline at end of file
diff --git a/subprojects/signing/src/test/resources/1.txt b/subprojects/signing/src/test/resources/1.txt
new file mode 100644
index 0000000..85df507
--- /dev/null
+++ b/subprojects/signing/src/test/resources/1.txt
@@ -0,0 +1 @@
+abcd
\ No newline at end of file
diff --git a/subprojects/signing/src/test/resources/2.txt b/subprojects/signing/src/test/resources/2.txt
new file mode 100644
index 0000000..85df507
--- /dev/null
+++ b/subprojects/signing/src/test/resources/2.txt
@@ -0,0 +1 @@
+abcd
\ No newline at end of file
diff --git a/subprojects/signing/src/test/resources/keys/gradle/keyId.txt b/subprojects/signing/src/test/resources/keys/gradle/keyId.txt
new file mode 100644
index 0000000..ee028cc
--- /dev/null
+++ b/subprojects/signing/src/test/resources/keys/gradle/keyId.txt
@@ -0,0 +1 @@
+24875D73
diff --git a/subprojects/signing/src/test/resources/keys/gradle/password.txt b/subprojects/signing/src/test/resources/keys/gradle/password.txt
new file mode 100644
index 0000000..82be7ae
--- /dev/null
+++ b/subprojects/signing/src/test/resources/keys/gradle/password.txt
@@ -0,0 +1 @@
+gradle
\ No newline at end of file
diff --git a/subprojects/signing/src/test/resources/keys/gradle/secring.gpg b/subprojects/signing/src/test/resources/keys/gradle/secring.gpg
new file mode 100644
index 0000000..47a24fe
Binary files /dev/null and b/subprojects/signing/src/test/resources/keys/gradle/secring.gpg differ
diff --git a/subprojects/signing/src/test/resources/keys/invalid-key-ring/keyId.txt b/subprojects/signing/src/test/resources/keys/invalid-key-ring/keyId.txt
new file mode 100644
index 0000000..ee028cc
--- /dev/null
+++ b/subprojects/signing/src/test/resources/keys/invalid-key-ring/keyId.txt
@@ -0,0 +1 @@
+24875D73
diff --git a/subprojects/signing/src/test/resources/keys/invalid-key-ring/password.txt b/subprojects/signing/src/test/resources/keys/invalid-key-ring/password.txt
new file mode 100644
index 0000000..82be7ae
--- /dev/null
+++ b/subprojects/signing/src/test/resources/keys/invalid-key-ring/password.txt
@@ -0,0 +1 @@
+gradle
\ No newline at end of file
diff --git a/subprojects/signing/src/test/resources/keys/invalid-key-ring/secring.gpg b/subprojects/signing/src/test/resources/keys/invalid-key-ring/secring.gpg
new file mode 100644
index 0000000..c74084e
--- /dev/null
+++ b/subprojects/signing/src/test/resources/keys/invalid-key-ring/secring.gpg
@@ -0,0 +1 @@
+not a valid key ring file
\ No newline at end of file
diff --git a/subprojects/sonar/sonar.gradle b/subprojects/sonar/sonar.gradle
index 90eb3f6..85f953f 100644
--- a/subprojects/sonar/sonar.gradle
+++ b/subprojects/sonar/sonar.gradle
@@ -14,37 +14,22 @@
  * limitations under the License.
  */
 
-configurations {
-    provided
-    provided.extendsFrom(compile)
-}
+apply from: "$rootDir/gradle/providedConfiguration.gradle"
 
 dependencies {
     groovy libraries.groovy
 
-    compile project(':core')
-    compile project(':plugins')
+    compile project(":core")
+    compile project(":plugins")
+    compile libraries.guava
     compile libraries.slf4j_api
+    compile "org.codehaus.sonar:sonar-batch-bootstrapper:2.9 at jar"
 
-    compile "org.codehaus.sonar:sonar-batch-bootstrapper:2.6"
-    provided "org.codehaus.sonar:sonar-batch:2.6"
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-
-    testRuntime project(":coreImpl")
+    // minimal dependencies to make our code compile
+    // we don't ship these dependencies because sonar-batch-bootstrapper will download them (and many more) at runtime
+    provided "org.codehaus.sonar:sonar-batch:2.9 at jar"
+    provided "org.codehaus.sonar:sonar-plugin-api:2.9 at jar"
+    provided "commons-configuration:commons-configuration:1.6 at jar"
 }
 
-sourceSets {
-    main {
-        compileClasspath = configurations.provided
-    }
-}
-
-ideaModule {
-    // TODO: remove everything but then-part after Gradle build has been updated to 1.0-milestone-2
-    if (scopes.PROVIDED) {
-        scopes.PROVIDED.plus += configurations.provided
-    } else {
-        scopes.COMPILE.plus += configurations.provided
-    }
-}
+useTestFixtures()
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/Sonar.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/Sonar.groovy
deleted file mode 100644
index 2194140..0000000
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/Sonar.groovy
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright 2011 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.plugins.sonar
-
-import org.sonar.batch.bootstrapper.Bootstrapper
-import org.gradle.api.internal.ConventionTask
-import org.gradle.api.tasks.TaskAction
-import org.gradle.util.ClasspathUtil
-import org.gradle.api.plugins.sonar.internal.ClassesOnlyClassLoader
-import org.gradle.util.GradleVersion
-import org.slf4j.LoggerFactory
-import ch.qos.logback.classic.Logger
-import ch.qos.logback.classic.Level
-
-/**
- * Analyzes a project and stores the results in Sonar's database.
- */
-class Sonar extends ConventionTask {
-    /**
-     * The Sonar server to connect to.
-     */
-    String serverUrl
-
-    /**
-     * The directory to be used for caching files downloaded from the Sonar server.
-     */
-    File bootstrapDir
-
-    /**
-     * The base directory for the project to be analyzed.
-     */
-    File projectDir
-
-    /**
-     * The build output directory for the project to be analyzed.
-     */
-    File buildDir
-
-    /**
-     * The directories containing the production sources of the project to be analyzed.
-     */
-    Set<File> projectMainSourceDirs = []
-
-    /**
-     * The directories containing the test sources of the project to be analyzed.
-     */
-    Set<File> projectTestSourceDirs = []
-
-    /**
-     * The directories containing the class files of the project to be analyzed.
-     */
-    Set<File> projectClassesDirs = []
-
-    /**
-     * The dependencies of the project to be analyzed. Typically these will be Jar files.
-     */
-    Set<File> projectDependencies = []
-
-    /**
-     * A unique key for identifying the project to be analyzed.
-     */
-    String projectKey
-
-    /**
-     * The name of the project to be analyzed.
-     */
-    String projectName
-
-    /**
-     * The description of the project to be analyzed.
-     */
-    String projectDescription
-
-    /**
-     * The version of the project to be analyzed.
-     */
-    String projectVersion
-
-    /**
-     * Global properties for use by the Sonar code analyzer.
-     */
-    Map globalProperties = [:]
-
-    /**
-     * Project-specific properties for use by the Sonar code analyzer.
-     */
-    Map projectProperties = [:]
-
-    @TaskAction
-    void execute() {
-        withErrorSqlLogging {
-            getBootstrapDir().mkdirs()
-            def bootstrapper = new Bootstrapper("Gradle", getServerUrl(), getBootstrapDir())
-
-            def classLoader = bootstrapper.createClassLoader(
-                    [findGradleSonarJar()] as URL[], new ClassesOnlyClassLoader(Sonar.classLoader),
-                    "groovy", "org.codehaus.groovy", "org.slf4j", "org.apache.log4j", "org.apache.commons.logging")
-
-            def analyzerClass = classLoader.loadClass("org.gradle.api.plugins.sonar.internal.SonarCodeAnalyzer")
-            def analyzer = analyzerClass.newInstance()
-            analyzer.gradleVersion = GradleVersion.current().version
-            analyzer.sonarTask = this
-            analyzer.execute()
-        }
-    }
-
-    /**
-     * Adds the specified directory to the set of project main source directories.
-     *
-     * @param sourceDirs the main source directory to be added
-     */
-    void projectMainSourceDir(File sourceDir) {
-        projectMainSourceDirs << sourceDir
-    }
-
-    /**
-     * Adds the specified directories to the set of project main source directories.
-     *
-     * @param sourceDirs the main source directories to be added
-     */
-    void projectMainSourceDirs(File... sourceDirs) {
-        projectMainSourceDirs.addAll(sourceDirs as List)
-    }
-
-    /**
-     * Adds the specified directory to the set of project test source directories.
-     *
-     * @param sourceDirs the testsource directory to be added
-     */
-    void projectTestSourceDir(File sourceDir) {
-        projectTestSourceDirs << sourceDir
-    }
-
-    /**
-     * Adds the specified directories to the set of project test source directories.
-     *
-     * @param sourceDirs the test source directories to be added
-     */
-    void projectTestSourceDirs(File... sourceDirs) {
-        projectTestSourceDirs.addAll(sourceDirs as List)
-    }
-
-    /**
-     * Adds the specified directory to the set of project classes directories.
-     *
-     * @param classesDir the classes directory to be added
-     */
-    void projectClassesDir(File classesDir) {
-        projectClassesDirs << classesDir
-    }
-
-    /**
-     * Adds the specified directories to the set of project classes directories.
-     *
-     * @param classesDirs the classes directories to be added
-     */
-    void projectClassesDirs(File... classesDirs) {
-        projectClassesDirs.addAll(classesDirs as List)
-    }
-
-    /**
-     * Adds the specified dependency to the set of project dependencies. Typically this will be a Jar file.
-     *
-     * @param dependency the depedency to be added
-     */
-    void projectDependency(File dependency) {
-        projectDependencies << dependency
-    }
-
-    /**
-     * Adds the specified dependencies to the set of project dependencies. Typically these will be Jar files.
-     *
-     * @param dependencies the dependencies to be added
-     */
-    void projectDependencies(File... dependencies) {
-        projectDependencies.addAll(dependencies as List)
-    }
-
-    /**
-     * Adds the specified property to the map of global properties.
-     * If a property with the specified name already exists, it will
-     * be overwritten.
-     *
-     * @param name the name of the global property
-     * @param value the value of the global property
-     */
-    void globalProperty(String name, String value) {
-        globalProperties.put(name, value)
-    }
-
-    /**
-     * Adds the specified properties to the map of global properties.
-     * Existing properties with the same name will be overwritten.
-     *
-     * @param properties the global properties to be added
-     */
-    void globalProperties(Map properties) {
-        globalProperties.putAll(properties)
-    }
-
-    /**
-     * Adds the specified property to the map of project properties.
-     * If a property with the specified name already exists, it will
-     * be overwritten.
-     *
-     * @param name the name of the project property
-     * @param value the value of the project property
-     */
-    void projectProperty(String name, String value) {
-        globalProperties.put(name, value)
-    }
-
-    /**
-     * Adds the specified properties to the map of project properties.
-     * Existing properties with the same name will be overwritten.
-     *
-     * @param properties the project properties to be added
-     */
-    void projectProperties(Map properties) {
-        projectProperties.putAll(properties)
-    }
-
-    protected URL findGradleSonarJar() {
-        def url = ClasspathUtil.getClasspath(Sonar.classLoader).find { it.path.contains("gradle-sonar") }
-        assert url != null, "failed to detect file system location of gradle-sonar Jar"
-        url
-    }
-
-    // limit Hibernate SQL logging to errors, no matter what the Gradle log level is
-    // this is a workaround for org.sonar.jpa.session.AbstractDatabaseConnector, line 158:
-    // props.put("hibernate.show_sql", Boolean.valueOf(LOG_SQL.isInfoEnabled()).toString());
-    // without this workaround, each SQL statement gets logged even if Gradle log level
-    // is set to QUIET
-    private void withErrorSqlLogging(Closure block) {
-        Logger sqlLogger = (Logger) LoggerFactory.getLogger("org.hibernate.SQL")
-        def oldLevel = sqlLogger.level
-
-        try {
-            sqlLogger.level = Level.ERROR
-            block()
-        } finally {
-            sqlLogger.level = oldLevel
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy
new file mode 100644
index 0000000..ba5061f
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarAnalyze.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.plugins.sonar
+
+import org.sonar.batch.bootstrapper.Bootstrapper
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.util.ClasspathUtil
+
+import org.gradle.api.plugins.sonar.model.SonarRootModel
+
+/**
+ * Analyzes a project hierachy and writes the results to the
+ * Sonar database.
+ */
+class SonarAnalyze extends ConventionTask {
+    /**
+     * Entry point to Sonar configuration.
+     */
+    SonarRootModel rootModel
+
+    @TaskAction
+    void analyze() {
+        rootModel.bootstrapDir.mkdirs()
+        def bootstrapper = new Bootstrapper("Gradle", rootModel.server.url, rootModel.bootstrapDir)
+
+        def classLoader = bootstrapper.createClassLoader(
+                [findGradleSonarJar()] as URL[], SonarAnalyze.classLoader,
+                        "groovy", "org.codehaus.groovy", "org.slf4j", "org.apache.log4j", "org.apache.commons.logging",
+                                "org.gradle.api.plugins.sonar.model")
+
+        def analyzerClass = classLoader.loadClass("org.gradle.api.plugins.sonar.internal.SonarCodeAnalyzer")
+        def analyzer = analyzerClass.newInstance()
+        analyzer.rootModel = rootModel
+        analyzer.execute()
+    }
+
+    protected URL findGradleSonarJar() {
+        def url = ClasspathUtil.getClasspath(SonarAnalyze.classLoader).find { it.path.contains("gradle-sonar") }
+        assert url != null, "failed to detect file system location of gradle-sonar Jar"
+        url
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
index fe5f21f..5b283b2 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
@@ -17,54 +17,130 @@ package org.gradle.api.plugins.sonar
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.plugins.JavaPlugin
-import org.gradle.api.tasks.SourceSet
+import org.gradle.api.internal.Instantiator
 import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.cache.CacheRepository
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.util.GradleVersion
+import org.gradle.internal.jvm.Jvm
+import org.gradle.api.plugins.sonar.model.*
 
 /**
- * A {@link Plugin} for integrating with <a href="http://www.sonarsource.org">Sonar</a>, a web-based platform
- * for managing code quality. Adds a task named <tt>sonar</tt> with type {@link Sonar} and configures it to
- * analyze the Java sources in the main source set.
+ * A plugin for integrating with <a href="http://www.sonarsource.org">Sonar</a>,
+ * a web-based platform for managing code quality. Adds a task named <tt>sonarAnalyze</tt>
+ * that analyzes the project to which the plugin is applied and its subprojects.
+ * The results are stored in the Sonar database.
+ *
+ * <p>For more information, see the
+ * <a href="http://gradle.org/current/docs/userguide/sonar_plugin.html">Sonar Plugin</a>
+ * chapter in the Gradle user guide.
+ *
+ * @see SonarAnalyze
+ * @see SonarRootModel
+ * @see SonarProjectModel
  */
-class SonarPlugin implements Plugin<Project> {
-    static final String SONAR_TASK_NAME = "sonar"
+class SonarPlugin implements Plugin<ProjectInternal> {
+    static final String SONAR_ANALYZE_TASK_NAME = "sonarAnalyze"
 
-    void apply(Project project) {
-        project.plugins.withType(JavaPlugin) {
-            def sonarTask = project.tasks.add(SONAR_TASK_NAME, Sonar)
-            configureConventions(sonarTask, project)
-        }
+    private Instantiator instantiator
+
+    void apply(ProjectInternal project) {
+        instantiator = project.services.get(Instantiator)
+        def task = configureSonarTask(project)
+        def model = configureSonarRootModel(project)
+        task.rootModel = model
+
+        configureSubprojects(project, model)
     }
 
-    private void configureConventions(Sonar sonarTask, Project project) {
-        def main = project.sourceSets.main
-        def test = project.sourceSets.test
+    private SonarAnalyze configureSonarTask(Project project) {
+        project.tasks.add(SONAR_ANALYZE_TASK_NAME, SonarAnalyze)
+    }
 
-        sonarTask.conventionMapping.serverUrl = { "http://localhost:9000" }
-        sonarTask.conventionMapping.bootstrapDir = {
-            def cacheRepository = (project as ProjectInternal).services.get(CacheRepository)
-            cacheRepository.cache("sonar-bootstrap").forObject(project.gradle).open().baseDir
+    private SonarRootModel configureSonarRootModel(Project project) {
+        def model = project.extensions.create("sonar", SonarRootModel)
+        model.conventionMapping.with {
+            bootstrapDir = { new File(project.buildDir, "sonar") }
+            gradleVersion = { GradleVersion.current().version }
         }
-        sonarTask.conventionMapping.projectDir = { project.projectDir }
-        sonarTask.conventionMapping.buildDir = { project.buildDir }
-        sonarTask.conventionMapping.projectMainSourceDirs = { getSourceDirs(main) }
-        sonarTask.conventionMapping.projectTestSourceDirs = { getSourceDirs(test) }
-        sonarTask.conventionMapping.projectClassesDirs = { [main.classesDir] as Set }
-        sonarTask.conventionMapping.projectDependencies = { project.configurations.compile.resolve() }
-        sonarTask.conventionMapping.projectKey = { "$project.group:$project.name" as String }
-        sonarTask.conventionMapping.projectName = { project.name }
-        sonarTask.conventionMapping.projectDescription = { project.description }
-        sonarTask.conventionMapping.projectVersion = { project.version as String }
-        sonarTask.conventionMapping.projectProperties = {
-            ["sonar.java.source": project.sourceCompatibility as String,
-             "sonar.java.target": project.targetCompatibility as String,
-             "sonar.dynamicAnalysis": "reuseReports",
-             "sonar.surefire.reportsPath": project.test.testResultsDir as String]
+
+        model.server = configureSonarServer()
+        model.database = configureSonarDatabase()
+        model.project = configureSonarProject(project)
+
+        model
+    }
+
+    private SonarServer configureSonarServer() {
+        def server = instantiator.newInstance(SonarServer)
+        server.url = "http://localhost:9000"
+        server
+    }
+
+    private SonarDatabase configureSonarDatabase() {
+        def database = instantiator.newInstance(SonarDatabase)
+        database.url = "jdbc:derby://localhost:1527/sonar"
+        database.driverClassName = "org.apache.derby.jdbc.ClientDriver"
+        database.username = "sonar"
+        database.password = "sonar"
+        database
+    }
+
+    private void configureSubprojects(Project parentProject, SonarModel parentModel) {
+        for (childProject in parentProject.childProjects.values()) {
+            def childModel = childProject.extensions.create("sonar", SonarProjectModel)
+            parentModel.childModels << childModel
+
+            childModel.project = configureSonarProject(childProject)
+
+            configureSubprojects(childProject, childModel)
         }
     }
 
-    private Set<File> getSourceDirs(SourceSet sourceSet) {
-        sourceSet.allSource.srcDirs as LinkedHashSet
+    private SonarProject configureSonarProject(Project project) {
+        def sonarProject = instantiator.newInstance(SonarProject)
+
+        sonarProject.conventionMapping.with {
+            key = { "$project.group:$project.name" as String }
+            name = { project.name }
+            description = { project.description }
+            version = { project.version.toString() }
+            baseDir = { project.projectDir }
+            workDir = { new File(project.buildDir, "sonar") }
+            dynamicAnalysis = { "reuseReports" }
+        }
+
+        def javaSettings = instantiator.newInstance(SonarJavaSettings)
+        sonarProject.java = javaSettings
+
+        project.plugins.withType(JavaBasePlugin) {
+            javaSettings.conventionMapping.with {
+                sourceCompatibility = { project.sourceCompatibility.toString() }
+                targetCompatibility = { project.targetCompatibility.toString() }
+            }
+        }
+
+        project.plugins.withType(JavaPlugin) {
+            def main = project.sourceSets.main
+            def test = project.sourceSets.test
+
+            sonarProject.conventionMapping.with {
+                sourceDirs = { main.allSource.srcDirs as List }
+                testDirs = { test.allSource.srcDirs as List }
+                binaryDirs = { [main.output.classesDir] }
+                libraries = {
+                    def libraries = main.compileClasspath
+                    def runtimeJar = Jvm.current().runtimeJar
+                    if (runtimeJar != null) {
+                        libraries += project.files(runtimeJar)
+                    }
+                    libraries
+                }
+                testReportPath = { project.test.testResultsDir }
+                language = { "java" }
+            }
+        }
+
+        sonarProject
     }
 }
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/ClassesOnlyClassLoader.java b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/ClassesOnlyClassLoader.java
deleted file mode 100644
index 525495f..0000000
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/ClassesOnlyClassLoader.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2011 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.plugins.sonar.internal;
-
-import java.net.URL;
-import java.util.Enumeration;
-
-/**
- * Class loader that cannot load any resources. Used as parent of Sonar
- * bootstrap class loader to work around http://jira.codehaus.org/browse/SONAR-2276
- */
-public class ClassesOnlyClassLoader extends ClassLoader {
-    public ClassesOnlyClassLoader(ClassLoader parent) {
-        super(parent);
-    }
-
-    @Override
-    public URL getResource(String name) {
-        return null;
-    }
-
-    @Override
-    public Enumeration<URL> getResources(String name) {
-        return null;
-    }
-}
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/SonarCodeAnalyzer.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/SonarCodeAnalyzer.groovy
index 396124f..c4772f9 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/SonarCodeAnalyzer.groovy
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/internal/SonarCodeAnalyzer.groovy
@@ -16,39 +16,102 @@
 package org.gradle.api.plugins.sonar.internal
 
 import org.apache.commons.configuration.MapConfiguration
-import org.sonar.api.CoreProperties
+import org.gradle.api.plugins.sonar.model.ModelToPropertiesConverter
+import org.gradle.api.plugins.sonar.model.SonarRootModel
+import org.gradle.api.plugins.sonar.model.SonarModel
+import org.sonar.api.batch.bootstrap.ProjectDefinition
+import org.sonar.api.batch.bootstrap.ProjectReactor
 import org.sonar.batch.Batch
 import org.sonar.batch.bootstrapper.EnvironmentInformation
-import org.sonar.batch.bootstrapper.ProjectDefinition
-import org.sonar.batch.bootstrapper.Reactor
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 
 /**
- * Runs Sonar code analysis using the configuration of the Sonar task.
+ * Runs Sonar code analysis for a project hierarchy.
+ * This class lives on the Sonar bootstrapper's class loader.
  */
 class SonarCodeAnalyzer {
-    String gradleVersion
-    def sonarTask
+    private static final Logger LOGGER = LoggerFactory.getLogger(SonarCodeAnalyzer)
+
+    SonarRootModel rootModel
 
     void execute() {
-        def globalProperties = [:]
-        globalProperties.putAll(sonarTask.globalProperties)
-        globalProperties["sonar.host.url"] = sonarTask.serverUrl
+        if (skipped(rootModel)) {
+            return
+        }
 
-        def projectProperties = new Properties()
-        projectProperties.putAll(sonarTask.projectProperties)
-        projectProperties[CoreProperties.PROJECT_KEY_PROPERTY] = sonarTask.projectKey
-        projectProperties[CoreProperties.PROJECT_NAME_PROPERTY] = sonarTask.projectName
-        projectProperties[CoreProperties.PROJECT_VERSION_PROPERTY] = sonarTask.projectVersion
-
-        def project = new ProjectDefinition(sonarTask.projectDir, sonarTask.bootstrapDir, projectProperties)
-        sonarTask.projectMainSourceDirs.each { project.addSourceDir(it.path) }
-        sonarTask.projectTestSourceDirs.each { project.addTestDir(it.path) }
-        sonarTask.projectClassesDirs.each { project.addBinaryDir(it.path) }
-        sonarTask.projectDependencies.each { project.addLibrary(it.path) }
-
-        def reactor = new Reactor(project)
-        def environment = new EnvironmentInformation("Gradle", gradleVersion)
-        def batch = new Batch(new MapConfiguration(globalProperties), project, reactor, environment)
+        def projectDef = configureProject(rootModel)
+        def reactor = new ProjectReactor(projectDef)
+        def globalProperties = extractProperties(rootModel)
+        for (prop in globalProperties) {
+            LOGGER.info("adding global property $prop")
+        }
+        def environment = new EnvironmentInformation("Gradle", rootModel.gradleVersion)
+        def batch = Batch.create(reactor, new MapConfiguration(globalProperties), environment)
         batch.execute()
     }
+
+    ProjectDefinition configureProject(SonarModel sonarModel) {
+        def sonarProject = sonarModel.project
+
+        LOGGER.info("configuring project $sonarProject.name")
+
+        def projectProperties = new Properties()
+        projectProperties.putAll(extractProperties(sonarProject))
+        for (prop in projectProperties) {
+            LOGGER.info("adding project property $prop")
+        }
+
+        def projectDef = ProjectDefinition.create(projectProperties)
+        projectDef.key = sonarProject.key
+        projectDef.name = sonarProject.name
+        projectDef.description = sonarProject.description
+        projectDef.version = sonarProject.version
+        projectDef.baseDir = sonarProject.baseDir
+        projectDef.workDir = sonarProject.workDir
+
+        for (dir in sonarProject.sourceDirs) {
+            LOGGER.info("adding source dir $dir")
+        }
+        projectDef.sourceDirs = sonarProject.sourceDirs as File[]
+
+        for (dir in sonarProject.testDirs) {
+            LOGGER.info("adding test dir $dir")
+        }
+        projectDef.testDirs = sonarProject.testDirs as File[]
+
+        for (dir in sonarProject.binaryDirs) {
+            LOGGER.info("adding binary dir $dir")
+            projectDef.addBinaryDir(dir)
+        }
+
+        for (lib in sonarProject.libraries) {
+            LOGGER.info("adding library $lib")
+            projectDef.addLibrary(lib.absolutePath)
+        }
+
+        for (childModel in sonarModel.childModels) {
+            if (skipped(childModel)) {
+                continue
+            }
+            def childProjectDef = configureProject(childModel)
+            projectDef.addSubProject(childProjectDef)
+        }
+
+        projectDef
+    }
+
+    private boolean skipped(SonarModel model) {
+        if (model.project.skip) {
+            LOGGER.info("Skipping Sonar analysis for project '{}' and its subprojects because 'sonar.project.skip' is 'true'", model.project.name)
+            return true
+        }
+        false
+    }
+
+    private Map<String, String> extractProperties(model) {
+        def converter = new ModelToPropertiesConverter(model)
+        converter.propertyProcessors = model.propertyProcessors
+        converter.convert()
+    }
 }
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/IncludeProperties.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/IncludeProperties.groovy
new file mode 100644
index 0000000..e3545ca
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/IncludeProperties.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.plugins.sonar.model
+
+import java.lang.annotation.*
+
+/**
+ * Indicates that the annotated property holds an object with nested
+ * Sonar properties.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.FIELD)
+public @interface IncludeProperties {}
\ No newline at end of file
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/ModelToPropertiesConverter.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/ModelToPropertiesConverter.groovy
new file mode 100644
index 0000000..763c2ee
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/ModelToPropertiesConverter.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.plugins.sonar.model
+
+import java.lang.reflect.Field
+
+import net.jcip.annotations.NotThreadSafe
+
+/**
+ * Converts a model object to a map of Sonar properties, guided by the information
+ * provided with <tt>SonarProperty</tt> and <tt>IncludeProperties</tt> annotations.
+ */
+ at NotThreadSafe
+class ModelToPropertiesConverter {
+    List<Closure> propertyProcessors = []
+
+    private final Object model
+
+    ModelToPropertiesConverter(Object model) {
+        this.model = model
+    }
+
+    Map<String, String> convert() {
+        def properties = collectProperties(model)
+        processProperties(properties)
+        properties
+    }
+
+    private Map<String, String> collectProperties(Object model) {
+        Map<String, String> properties = [:]
+
+        if (model == null) {
+            return properties
+        }
+
+        for (field in getAllFields(model.getClass())) {
+            if (field.isAnnotationPresent(IncludeProperties)) {
+                def propValue = model."$field.name"
+                properties.putAll(collectProperties(propValue))
+                continue
+            }
+
+            def propertyAnnotation = field.getAnnotation(SonarProperty)
+            if (propertyAnnotation == null) {
+                continue
+            }
+
+            def propStringValue = model."$field.name"?.toString()
+            if (propStringValue == null) {
+                continue
+            }
+
+            properties.put(propertyAnnotation.value(), propStringValue)
+        }
+
+        properties
+    }
+
+    private void processProperties(Map<String, String> properties) {
+        for (processor in propertyProcessors) {
+            processor(properties)
+        }
+    }
+
+    private List<Field> getAllFields(Class<?> clazz) {
+        def fields = []
+        while (clazz != null) {
+            fields.addAll(clazz.declaredFields)
+            clazz = clazz.superclass
+        }
+        fields
+    }
+}
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/SonarProperty.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/SonarProperty.groovy
new file mode 100644
index 0000000..f924971
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/SonarProperty.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.plugins.sonar.model
+
+import java.lang.annotation.*
+
+/**
+ * Maps a model property to the corresponding Sonar property.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.FIELD)
+ at Documented
+public @interface SonarProperty {
+    String value()
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/SonarRootModel.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/SonarRootModel.groovy
new file mode 100644
index 0000000..bbdf0f6
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/model/SonarRootModel.groovy
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2011 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.plugins.sonar.model
+
+import org.gradle.util.ConfigureUtil
+import org.gradle.api.file.FileCollection
+
+/**
+ * Base interface for Sonar models on analysis roots and their subprojects.
+ */
+interface SonarModel {
+    /**
+     * Returns per-project configuration options.
+     *
+     * @return per-project configuration options
+     */
+    SonarProject getProject()
+
+    /**
+     * Returns the Sonar model objects for this project's child projects.
+     *
+     * @return the Sonar model objects for this project's child projects
+     */
+    List<SonarModel> getChildModels()
+}
+
+/**
+ * Configuration options for a project that has the <tt>sonar</tt> plugin applied.
+ */
+class SonarRootModel implements SonarModel {
+    /**
+     * Configuration options related to the Sonar server.
+     */
+    @IncludeProperties
+    SonarServer server
+
+    /**
+     * Configuration options related to the Sonar database.
+     */
+    @IncludeProperties
+    SonarDatabase database
+
+    /**
+     * Per-project configuration options.
+     */
+    SonarProject project
+
+    /**
+     * Name of the version control branch that is analyzed. Two branches of the same
+     * project are considered as different projects in Sonar. Defaults to <tt>null</tt>.
+     */
+    @SonarProperty("sonar.branch")
+    String branch
+
+    /**
+     * Selects one of the quality profiles configured via the Sonar web interface,
+     * overriding any selection made there. Defaults to <tt>null</tt>.
+     */
+    @SonarProperty("sonar.profile")
+    String profile
+
+    /**
+     * Directory where the Sonar client library will be stored. The correct version
+     * of the client library is automatically downloaded from the Sonar server
+     * before analysis begins. Defaults to <tt>$project.buildDir/sonar</tt>.
+     */
+    File bootstrapDir
+
+    /**
+     * The Gradle version to be reported to the Sonar client library. Only used for
+     * identification purposes. Defaults to the current Gradle version.
+     */
+    String gradleVersion
+
+    /**
+     * Post-processors for global Sonar properties.
+     *
+     * @see #withGlobalProperties
+     */
+    List<Closure> propertyProcessors = []
+
+    /**
+     * Configuration options for child projects of this project.
+     */
+    List<SonarModel> childModels = []
+
+    /**
+     * Configures server configuration options. The specified closure
+     * delegates to an instance of {@link SonarServer}.
+     *
+     * @param server configuration options
+     */
+    void server(Closure config) {
+        ConfigureUtil.configure(config, server)
+    }
+
+    /**
+     * Configures database configuration options. The specified closure
+     * delegates to an instance of {@link SonarDatabase}.
+     *
+     * @param database configuration options
+     */
+    void database(Closure config) {
+        ConfigureUtil.configure(config, database)
+    }
+
+    /**
+     * Configures per-project configuration options. The specified closure
+     * delegates to an instance of {@link SonarProject}.
+     *
+     * @param per-project configuration options
+     */
+    void project(Closure config) {
+        ConfigureUtil.configure(config, project)
+    }
+
+    /**
+     * Registers a closure for post-processing the global Sonar properties covered by
+     * <tt>SonarRootModel<tt>, and for adding further properties not covered by
+     * that model. The properties are passed to the closure as a map
+     * of String keys and values. Keys correspond to the Sonar properties as described in the
+     * <a href="http://docs.codehaus.org/display/SONAR/Advanced+parameters">Sonar documentation</a>.
+     *
+     * <p>Evaluation of the closure is deferred until build execution time. If this
+     * method is called multiple times, closures will be evaluated in the order
+     * they have been registered.
+     *
+     * <p>Example:
+     *
+     * <pre>
+     * withGlobalProperties { props ->
+     *     // add further properties
+     *     props["some.sonar.property"] = "some value"
+     *     props["another.sonar.property"] = "another value"
+     *
+     *     // modify existing properties (rarely necessary)
+     *     props["sonar.branch"] = "some-branch"
+     *     props.remove("sonar.profile")
+     * }
+     * </pre>
+     *
+     * @param block a closure for post-processing global Sonar properties
+     */
+    void withGlobalProperties(Closure block) {
+        propertyProcessors << block
+    }
+}
+
+/**
+ * Configuration options for subprojects of a project that has the <tt>sonar</tt> plugin applied.
+ */
+class SonarProjectModel implements SonarModel {
+    /**
+     * Per-project configuration options.
+     */
+    SonarProject project
+
+    /**
+     * Configuration options for child projects of this project.
+     */
+    List<SonarModel> childModels = []
+
+    /**
+     * Configures per-project configuration options. The specified closure
+     * delegates to an instance of {@link SonarProject}.
+     *
+     * @param config per-project configuration options
+     */
+    void project(Closure config) {
+        ConfigureUtil.configure(config, project)
+    }
+}
+
+/**
+ * Configuration options for the Sonar web server. Defaults match the defaults
+ * for a locally running server.
+ */
+class SonarServer {
+    /**
+     * The URL for the Sonar web server. Defaults to <tt>http://localhost:9000</tt>.
+     */
+    @SonarProperty("sonar.host.url")
+    String url
+}
+
+/**
+ * Configuration options for the Sonar database. Defaults match the defaults for
+ * a locally running server (with embedded database).
+ */
+class SonarDatabase {
+    /**
+     * The JDBC URL for the Sonar database. Defaults to <tt>jdbc:derby://localhost:1527/sonar</tt>.
+     */
+    @SonarProperty("sonar.jdbc.url")
+    String url
+
+    /**
+     * The name of the JDBC driver class. Defaults to <tt>org.apache.derby.jdbc.ClientDriver</tt>.
+     */
+    @SonarProperty("sonar.jdbc.driverClassName")
+    String driverClassName
+    /**
+     * The JDBC username for the Sonar database. Defaults to <tt>sonar</tt>.
+     */
+    @SonarProperty("sonar.jdbc.username")
+    String username
+    /**
+     * The JDBC password for the Sonar database. Defaults to <tt>sonar</tt>.
+     */
+    @SonarProperty("sonar.jdbc.password")
+    String password
+}
+
+/**
+ * Per-project configuration options.
+ */
+class SonarProject {
+    /**
+     * The identifier for the project. Defaults to <tt>$project.group:$project.name</tt>.
+     */
+    String key
+
+    /**
+     * The name for the project. Defaults to <tt>$project.name</tt>.
+     */
+    String name
+
+    /**
+     * A description for the project. Defaults to <tt>$project.description</tt>.
+     */
+    String description
+
+    /**
+     * The version of the project. Defaults to <tt>$project.version</tt>.
+     */
+    String version
+
+    /**
+     * The date when analysis was performed. Format is <tt>yyyy-MM-dd</tt>
+     * (e.g. <tt>2010-12-25</tt>). Defaults to the current date.
+     */
+    @SonarProperty("sonar.projectDate")
+    String date
+
+    /**
+     * Tells whether to skip analysis of this project. Allows to only analyze a
+     * subset of projects in a Gradle build. Defaults to <tt>false</tt>.
+     */
+    boolean skip = false
+
+    /**
+     * The base directory for the analysis. Defaults to <tt>$project.projectDir</tt>.
+     */
+    File baseDir
+
+    /**
+     * The working directory for the analysis. Defaults to <tt>$project.buildDir/sonar<tt>.
+     */
+    File workDir
+
+    /**
+     * The directories containing the project's production source code to be analyzed.
+     * Defaults to <tt>project.sourceSets.main.allSource.srcDirs</tt>.
+     */
+    List<File> sourceDirs = []
+
+    /**
+     * The directories containing the project's test source code to be analyzed.
+     * Defaults to <tt>project.sourceSets.test.allSource.srcDirs</tt>.
+     */
+    List<File> testDirs = []
+
+    /**
+     * The directories containing the project's compiled code to be analyzed.
+     * Defaults to <tt>main.output.classesDir</tt>.
+     */
+    List<File> binaryDirs = []
+
+    /**
+     * A class path containing the libraries used by this project.
+     * Defaults to <tt>project.sourceSets.main.compileClasspath + Jvm.current().runtimeJar</tt>.
+     */
+    FileCollection libraries// = new SimpleFileCollection()
+
+    /**
+     * Tells whether to the project's source code should be stored and made available
+     * via the Sonar web interface. Defaults to <tt>true</tt>.
+     */
+    @SonarProperty("sonar.importSources")
+    boolean importSources = true
+
+    /**
+     * The character encoding for the project's source files. Accepts the same
+     * values as {@link java.nio.charset.Charset} (e.g. <tt>UTF-8<tt>, <tt>MacRoman</tt>,
+     * <tt>Shift_JIS</tt>). Defaults to the JVM's platform encoding.
+     */
+    @SonarProperty("sonar.sourceEncoding")
+    String sourceEncoding
+
+    /**
+     * Source files to be excluded from analysis. Specified as comma-separated
+     * list of Ant-style patterns which are relative to a source directory.
+     *
+     * <p>Example: <tt>com/mycompany/*/.java, **/*Dummy.java</tt>.
+     *
+     * <p>Defaults to <tt>null</tt>.
+     */
+    @SonarProperty("sonar.exclusions")
+    String sourceExclusions
+
+    /**
+     * Tells whether to skip analysis of the project's design. Defaults to <tt>false</tt>.
+     */
+    @SonarProperty("sonar.skipDesign")
+    boolean skipDesignAnalysis = false
+
+    /**
+     * The directory containing the JUnit XML test report. Defaults to
+     * <tt>project.test.testResultsDir</tt>.
+     */
+    @SonarProperty("sonar.surefire.reportsPath")
+    File testReportPath
+
+    /**
+     * The Cobertura XML report file. Defaults to <tt>null</tt>.
+     */
+    @SonarProperty("sonar.cobertura.reportPath")
+    File coberturaReportPath
+
+    /**
+     * The Clover XML report file. Defaults to <tt>null</tt>.
+     */
+    @SonarProperty("sonar.clover.reportPath")
+    File cloverReportPath
+
+    /**
+     * The programming language to be analyzed. Only one language per project
+     * can be analyzed. Defaults to <tt>java</tt>.
+     */
+    @SonarProperty("sonar.language")
+    String language
+
+    /**
+     * Java-related configuration options.
+     */
+    @IncludeProperties
+    SonarJavaSettings java
+
+    /**
+     * Whether and how to perform dynamic analysis. Dynamic analysis includes
+     * the analysis of test and code coverage reports. The following values are allowed:
+     *
+     * <ul>
+     *     <li>
+     *         <tt>reuseReports</tt>: Test and/or coverage reports will be produced by the
+     *         Gradle build and passed via <tt>testReportPath</tt>, <tt>coberturaReportPath</tt>,
+     *         and <tt>cloverReportPath</tt>.
+     *     </li>
+     *     <li>
+     *         <tt>false</tt>: No dynamic analysis will be performed.
+     *     </li>
+     *     <li>
+     *         <tt>true</tt>: Test and/or coverage reports will be produced by Sonar. This mode
+     *         is not supported by the Gradle Sonar plugin.
+     *     </li>
+     * </ul>
+     *
+     * Defaults to <tt>reuseReports</tt>.
+     */
+    @SonarProperty("sonar.dynamicAnalysis")
+    String dynamicAnalysis
+
+    /**
+     * Post-processors for per-project Sonar properties.
+     *
+     * @see #withProjectProperties
+     */
+    List<Closure> propertyProcessors = []
+
+    /**
+     * Configures Java-related configuration options. The specified closure delegates
+     * to an instance of type {@link SonarJavaSettings}.
+     *
+     * @param config Java-related configuration options
+     */
+    void java(Closure config) {
+        ConfigureUtil.configure(config, java)
+    }
+
+    /**
+     * Registers a closure for post-processing the per-project Sonar properties covered by
+     * <tt>SonarProjectModel<tt>, and for adding further properties not covered by
+     * that model. The properties are passed to the closure as a map
+     * of String keys and values. Keys correspond to the Sonar properties as described in the
+     * <a href="http://docs.codehaus.org/display/SONAR/Advanced+parameters">Sonar documentation</a>.
+     *
+     * <p>Evaluation of the closure is deferred until build execution time. If this
+     * method is called multiple times, closures will be evaluated in the order
+     * they have been registered.
+     *
+     * <p>Example:
+     *
+     * <pre>
+     * withProjectProperties { props ->
+     *     // add further properties
+     *     props["some.sonar.property"] = "some value"
+     *     props["another.sonar.property"] = "another value"
+     *
+     *     // modify existing properties (rarely necessary)
+     *     props["sonar.sourceEncoding"] = "UTF-8"
+     *     props.remove("sonar.projectDate")
+     * }
+     * </pre>
+     *
+     * @param block a closure for post-processing per-project Sonar properties
+     */
+    void withProjectProperties(Closure block) {
+        propertyProcessors << block
+    }
+}
+
+/**
+ * Java-related configuration options for the project to be analyzed.
+ */
+class SonarJavaSettings {
+    /**
+     * The source compatibility of the Java code. Defaults to
+     * <tt>project.sourceCompatibility</tt>.
+     */
+    @SonarProperty("sonar.java.source")
+    String sourceCompatibility
+
+    /**
+     * The target compatibility of the Java code. Defaults to
+     * <tt>project.targetCompatibility</tt>.
+     */
+    @SonarProperty("sonar.java.target")
+    String targetCompatibility
+}
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy
new file mode 100644
index 0000000..9898aab
--- /dev/null
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarAnalyzeTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.plugins.sonar
+
+import spock.lang.Specification
+
+class SonarAnalyzeTest extends Specification {
+//    SonarAnalyze task = HelperUtil.createTask(SonarAnalyze)
+//
+//    @Issue("GRADLE-1499")
+//    def "can configure project properties"() {
+//        when:
+//        task.projectProperties = [one: "1", two: "2"]
+//        task.projectProperties three: "3", four: "4"
+//        task.projectProperty "five", "5"
+//
+//        then:
+//        task.projectProperties == [one: "1", two: "2", three: "3", four: "4", five: "5"]
+//    }
+//
+//    def "can configure global properties"() {
+//        when:
+//        task.globalProperties = [one: "1", two: "2"]
+//        task.globalProperties three: "3", four: "4"
+//        task.globalProperty "five", "5"
+//
+//        then:
+//        task.globalProperties == [one: "1", two: "2", three: "3", four: "4", five: "5"]
+//    }
+}
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy
index e646f39..94d68fc 100644
--- a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/SonarPluginTest.groovy
@@ -15,61 +15,129 @@
  */
 package org.gradle.api.plugins.sonar
 
-import org.gradle.util.HelperUtil
+import org.gradle.api.plugins.sonar.model.SonarRootModel
+import org.gradle.api.plugins.sonar.model.SonarProjectModel
+import org.gradle.api.plugins.sonar.model.SonarProject
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.util.ConfigureUtil
+import org.gradle.util.HelperUtil
+import org.gradle.internal.jvm.Jvm
 
 import spock.lang.Specification
+import spock.lang.Issue
 
 class SonarPluginTest extends Specification {
-    def "only adds sonar task if Java plugin is present"() {
+    def "adds model and task to root project"() {
         def project = HelperUtil.createRootProject()
 
         when:
         project.plugins.apply(SonarPlugin)
 
         then:
-        !project.tasks.findByName("sonar")
+        project.sonar instanceof SonarRootModel
+        project.tasks.findByName("sonarAnalyze")
+    }
+
+    def "adds model to subprojects"() {
+        def project = HelperUtil.createRootProject()
+        def child = HelperUtil.createChildProject(project, "child")
 
         when:
-        project.plugins.apply(JavaPlugin)
+        project.plugins.apply(SonarPlugin)
 
         then:
-        project.tasks.findByName("sonar")
+        child.sonar instanceof SonarProjectModel
+        !child.tasks.findByName("sonarAnalyze")
     }
 
-    def "provides default configuration for sonar task"() {
+    def "provides defaults for global configuration"() {
         def project = HelperUtil.createRootProject()
-        project.plugins.apply(JavaPlugin)
-        project.sourceSets.main.java.srcDir("src/main/other")
-        project.sourceSets.test.java.srcDir("src/test/other")
-        project.group = "testGroup"
-        project.description = "testDescription"
-        project.sourceCompatibility = "1.6"
-        project.targetCompatibility = "1.5"
 
         when:
         project.plugins.apply(SonarPlugin)
 
         then:
-        def task = (Sonar) project.tasks.getByName("sonar")
-        task.serverUrl == "http://localhost:9000"
-        task.bootstrapDir.isDirectory()
-        task.projectDir == project.projectDir
-        task.buildDir == project.buildDir
-        task.projectMainSourceDirs == [project.file("src/main/java"),
-                project.file("src/main/resources"), project.file("src/main/other")] as Set
-        task.projectTestSourceDirs == [project.file("src/test/java"),
-                project.file("src/test/resources"), project.file("src/test/other")] as Set
-        task.projectClassesDirs == [project.file("build/classes/main")] as Set
-        task.projectDependencies.isEmpty() // because our project doesn't have any dependencies defined
-        task.projectKey == "testGroup:test"
-        task.projectName == "test"
-        task.projectDescription == "testDescription"
-        task.globalProperties.isEmpty()
-        task.projectProperties.size() == 4
-        task.projectProperties["sonar.java.source"] == "1.6"
-        task.projectProperties["sonar.java.target"] == "1.5"
-        task.projectProperties["sonar.dynamicAnalysis"] == "reuseReports"
-        task.projectProperties["sonar.surefire.reportsPath"] == project.file("build/test-results") as String
+        SonarRootModel sonar = project.sonar
+
+        sonar.server.url == "http://localhost:9000"
+
+        def db = sonar.database
+        db.url == "jdbc:derby://localhost:1527/sonar"
+        db.driverClassName == "org.apache.derby.jdbc.ClientDriver"
+        db.username == "sonar"
+        db.password == "sonar"
+
+        sonar.bootstrapDir instanceof File
+        sonar.gradleVersion == project.gradle.gradleVersion
+    }
+
+    def "provides defaults for project configuration"(Project project) {
+        SonarProject sonarProject = project.sonar.project
+
+        expect:
+        sonarProject.key == "$project.group:$project.name"
+        sonarProject.name == project.name
+        sonarProject.description == project.description
+        sonarProject.version == project.version
+        !sonarProject.skip
+        sonarProject.baseDir == project.projectDir
+        sonarProject.workDir == new File(project.buildDir, "sonar")
+        sonarProject.dynamicAnalysis == "reuseReports"
+
+        where:
+        project << createMultiProject().allprojects
+    }
+
+    def "provides additional defaults for project configuration if java-base plugin is present"(Project project) {
+        SonarProject sonarProject = project.sonar.project
+
+        expect:
+        sonarProject.java.sourceCompatibility == project.sourceCompatibility as String
+        sonarProject.java.targetCompatibility == project.targetCompatibility as String
+
+        where:
+        project << createMultiProject { plugins.apply(JavaBasePlugin) }.allprojects
+    }
+
+    def "provides additional defaults for project configuration if java plugin is present"(Project project) {
+        SonarProject sonarProject = project.sonar.project
+
+        expect:
+        sonarProject.sourceDirs == project.sourceSets.main.allSource.srcDirs as List
+        sonarProject.testDirs == project.sourceSets.test.allSource.srcDirs as List
+        sonarProject.binaryDirs == [project.sourceSets.main.output.classesDir]
+        sonarProject.libraries.files as List == [Jvm.current().runtimeJar]
+
+        sonarProject.testReportPath == project.test.testResultsDir
+        sonarProject.language == "java"
+
+        where:
+        project << createMultiProject { plugins.apply(JavaPlugin) }.allprojects
+    }
+
+    @Issue("http://forums.gradle.org/gradle/topics/gradle_multi_project_build_with_sonar_and_cobertura")
+    def "'dynamicAnalysis' always defaults to 'reuseReports', even for root project and if Java plugin isn't applied"() {
+        def project = createMultiProject()
+        def childProject = project.subprojects.iterator().next()
+
+        expect:
+        project.sonar.project.dynamicAnalysis == "reuseReports"
+        childProject.sonar.project.dynamicAnalysis == "reuseReports"
+    }
+
+    private Project createMultiProject(Closure commonConfig = {}) {
+        def root = HelperUtil.createRootProject()
+        ConfigureUtil.configure(commonConfig, root)
+        root.group = "group"
+
+        def child = HelperUtil.createChildProject(root, "child")
+        ConfigureUtil.configure(commonConfig, child)
+        child.group = "group"
+
+        root.plugins.apply(SonarPlugin)
+
+        root
     }
 }
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/model/ModelToPropertiesConverterTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/model/ModelToPropertiesConverterTest.groovy
new file mode 100644
index 0000000..4743dd8
--- /dev/null
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/plugins/sonar/model/ModelToPropertiesConverterTest.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.plugins.sonar.model
+
+import spock.lang.Specification
+
+class ModelToPropertiesConverterTest extends Specification {
+    class Model {
+        @SonarProperty("my.prop")
+        String annotated = "annotated"
+
+        String notAnnotated = "notAnnotated"
+    }
+
+    def "converts model without annotations to empty map"() {
+        def converter = new ModelToPropertiesConverter(new File("foo"))
+
+        expect:
+        converter.convert() == [:]
+    }
+
+    def "converts annotated properties"() {
+        def converter = new ModelToPropertiesConverter(new Model())
+
+        expect:
+        converter.convert() == ["my.prop": "annotated"]
+    }
+
+    class Model2 {
+        @SonarProperty("my.flag")
+        boolean flag = false
+    }
+
+    def "converts property values to strings"() {
+        def converter = new ModelToPropertiesConverter(new Model2())
+
+        expect:
+        converter.convert() == ["my.flag": "false"]
+    }
+
+    def "doesn't convert properties with null value"() {
+        def model = new Model()
+        model.annotated = null
+        def converter = new ModelToPropertiesConverter(model)
+
+        expect:
+        converter.convert() == [:]
+    }
+
+    class Model3 {
+        @IncludeProperties
+        Model nested = new Model()
+
+        Model2 nested2 = new Model2()
+    }
+
+    def "converts annotated nested models"() {
+        def converter = new ModelToPropertiesConverter(new Model3())
+
+        expect:
+        converter.convert() == ["my.prop": "annotated"]
+    }
+
+    class Model4 extends Model {
+        @SonarProperty("another.prop")
+        int size = 4
+    }
+
+    def "handles inherited properties"() {
+        def converter = new ModelToPropertiesConverter(new Model4())
+
+        expect:
+        converter.convert() == ["my.prop": "annotated", "another.prop": "4"]
+    }
+
+    class Model5 {
+        @SonarProperty("one")
+        String one = "one"
+
+
+        @SonarProperty("two")
+        String two = "two"
+    }
+
+    def "allows to post-process properties"() {
+        def converter = new ModelToPropertiesConverter(new Model5())
+        converter.propertyProcessors << { props ->
+            props.put("added", "added")
+        }
+        converter.propertyProcessors << { props ->
+            props.remove("one")
+        }
+        converter.propertyProcessors << { props ->
+            props.two = "changed"
+        }
+
+        expect:
+        converter.convert() == [added: "added", two: "changed"]
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
new file mode 100644
index 0000000..e67b10a
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * 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.tooling
+
+import org.gradle.integtests.fixtures.AutoTestedSamplesUtil
+import org.gradle.internal.jvm.Jvm
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.IgnoreIf
+import spock.lang.Specification
+import org.gradle.util.ClasspathUtil
+import org.gradle.tooling.model.Element
+
+/**
+ * by Szczepan Faber, created at: 1/5/12
+ */
+ at IgnoreIf({!Jvm.current().java6Compatible})
+public class AutoTestedSamplesToolingApiTest extends Specification {
+
+    @Rule public final TemporaryFolder temp = new TemporaryFolder()
+
+    void runSamples() {
+        expect:
+
+        def util = new AutoTestedSamplesUtil()
+        util.findSamples("subprojects/tooling-api/src/main") { file, sample ->
+            println "Found sample: ${sample.split("\n")[0]} (...) in $file"
+            def javaSource = """
+//some typical imports
+import org.gradle.tooling.*;
+import org.gradle.tooling.model.*;
+import org.gradle.tooling.model.build.*;
+import java.io.*;
+
+public class Sample {
+  public static void main(String ... args) {
+    $sample
+  }
+}
+"""
+            tryCompile(javaSource)
+        }
+    }
+
+    /**
+     * The implementation should never assume we're running against jdk6.
+     * Hence the impl is quite awkward and does all interaction with java6 compiler api reflectively.
+     *
+     * @param source
+     */
+    void tryCompile(String source) {
+        //TODO SF generalize and move the test out of integ tests, add unit tests
+        source = normalize(source)
+        def sourceFile = temp.dir.file("Sample.java")
+        sourceFile.text = source
+
+        def compiler = ("javax.tools.ToolProvider" as Class).getSystemJavaCompiler()
+        def fileManager = compiler.getStandardFileManager(null, null, null);
+
+        def location = ("javax.tools.StandardLocation" as Class).CLASS_OUTPUT
+        fileManager.setLocation(location, [temp.dir]);
+
+        location = ("javax.tools.StandardLocation" as Class).CLASS_PATH
+        fileManager.setLocation(location, [ClasspathUtil.getClasspathForClass(Element)]);
+
+        def checkDiagnostic = { diagnostic ->
+            if (diagnostic.kind.name() == 'ERROR') {
+                String[] lines = source.split("\n")
+                int lineNo = diagnostic.lineNumber - 1
+
+                def message = "Compilation error in sample in line: \n" + lines[lineNo] + "\n" + diagnostic + "\n"
+                message = message - sourceFile.absolutePath
+                throw new AssertionError(message)
+            }
+        }
+
+        def diagnosticListener = checkDiagnostic.asType("javax.tools.DiagnosticListener" as Class)
+
+        def input = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile))
+        compiler.getTask(null,
+            fileManager,
+            diagnosticListener,
+            null,
+            null,
+            input).call();
+
+        fileManager.close();
+    }
+
+    String normalize(String input) {
+        String out = input.replaceAll(">", ">")
+        out = out.replaceAll("<", "<")
+        out
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
new file mode 100644
index 0000000..60be5d0
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2011 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.tooling
+
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.ReleasedVersions
+import org.gradle.integtests.tooling.fixture.ConfigurableOperation
+import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.internal.consumer.ConnectorServices
+import org.gradle.tooling.internal.consumer.Distribution
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.idea.IdeaProject
+import org.junit.Rule
+import spock.lang.Issue
+import spock.lang.Specification
+
+ at Issue("GRADLE-1933")
+class ConcurrentToolingApiIntegrationSpec extends Specification {
+
+    @Rule final ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
+    @Rule final GradleDistribution dist = new GradleDistribution()
+    final ToolingApi toolingApi = new ToolingApi(dist)
+
+    int threads = 3
+
+    def setup() {
+        //concurrent tooling api at the moment is only supported for forked mode
+        toolingApi.isEmbedded = false
+        concurrent.shortTimeout = 180000
+        new ConnectorServices().reset()
+    }
+
+    def cleanup() {
+        new ConnectorServices().reset()
+    }
+
+    def "handles the same target gradle version concurrently"() {
+        dist.file('build.gradle')  << "apply plugin: 'java'"
+
+        when:
+        threads.times {
+            concurrent.start { useToolingApi(dist) }
+        }
+
+        then:
+        concurrent.finished()
+    }
+
+    def "handles different target gradle versions concurrently"() {
+        given:
+        def current = dist
+        def last = new ReleasedVersions(dist).getLast()
+        assert current != last
+        println "Combination of versions used: current - $current, last - $last"
+
+        dist.file('build.gradle')  << "apply plugin: 'java'"
+
+        when:
+        concurrent.start { useToolingApi(current) }
+        concurrent.start { useToolingApi(last)}
+
+        then:
+        concurrent.finished()
+    }
+
+    def useToolingApi(BasicGradleDistribution target) {
+        new ToolingApi(target, dist.userHomeDir, { dist.testDir }, false).withConnection { ProjectConnection connection ->
+            try {
+                def model = connection.getModel(IdeaProject)
+                assert model != null
+                //a bit more stress:
+                connection.newBuild().forTasks('tasks').run()
+            } catch (Exception e) {
+                throw new RuntimeException("""We might have hit a concurrency problem.
+See the full stacktrace and the list of causes to investigate""", e);
+            }
+        }
+    }
+
+    def "can share connection when running build"() {
+        given:
+        dist.file("build.gradle") << """
+def text = System.in.text
+System.out.println 'out=' + text
+System.err.println 'err=' + text
+project.description = text
+"""
+
+        when:
+        toolingApi.withConnection { connection ->
+            threads.times { idx ->
+                concurrent.start {
+                    def model = connection.model(GradleProject.class)
+                    def operation = new ConfigurableOperation(model)
+                        .setStandardInput("hasta la vista $idx")
+
+                    assert model.get().description == "hasta la vista $idx"
+
+                    assert operation.getStandardOutput().contains("out=hasta la vista $idx")
+                    assert operation.getStandardOutput().count("out=hasta la vista") == 1
+
+                    assert operation.getStandardError().contains("err=hasta la vista $idx")
+                    assert operation.getStandardError().count("err=hasta la vista") == 1
+                }
+            }
+            concurrent.finished()
+        }
+
+        then: noExceptionThrown()
+    }
+
+    def "handles standard input concurrently when getting model"() {
+        when:
+        threads.times { idx ->
+            dist.file("build$idx/build.gradle") << "description = System.in.text"
+        }
+
+        then:
+        threads.times { idx ->
+            concurrent.start {
+                withConnectionInDir("build$idx") { connection ->
+                    def model = connection.model(GradleProject.class)
+                    model.standardInput = new ByteArrayInputStream("project $idx".toString().bytes)
+                    def project = model.get()
+                    assert project.description == "project $idx"
+                }
+            }
+        }
+
+        concurrent.finished()
+    }
+
+    def "handles standard input concurrently when running build"() {
+        when:
+        threads.times { idx ->
+            dist.file("build$idx/build.gradle") << "task show << { println System.in.text}"
+        }
+
+        then:
+        threads.times { idx ->
+            concurrent.start {
+                withConnectionInDir("build$idx") { connection ->
+                    def build = connection.newBuild()
+                    def operation = new ConfigurableOperation(build)
+                        .setStandardInput("hasta la vista $idx")
+                    build.forTasks('show').run()
+                    assert operation.standardOutput.contains("hasta la vista $idx")
+                }
+            }
+        }
+
+        concurrent.finished()
+    }
+
+    def "during task execution receives distribution progress including waiting for the other thread"() {
+        given:
+        dist.file("build1/build.gradle") << "task foo1"
+        dist.file("build2/build.gradle") << "task foo2"
+
+        when:
+        def allProgress = []
+
+        concurrent.start {
+            def connector = toolingApi.connector()
+            distributionOperation(connector, { it.description = "download for 1"; Thread.sleep(500) } )
+            connector.forProjectDirectory(dist.file("build1"))
+
+            toolingApi.withConnection(connector) { connection ->
+                def build = connection.newBuild()
+                def operation = new ConfigurableOperation(build)
+                build.forTasks('foo1').run()
+                assert operation.progressMessages.contains("download for 1")
+                assert !operation.progressMessages.contains("download for 2")
+                allProgress << operation.progressMessages
+            }
+        }
+
+        concurrent.start {
+            def connector = toolingApi.connector()
+            distributionOperation(connector, { it.description = "download for 2"; Thread.sleep(500) } )
+            connector.forProjectDirectory(dist.file("build2"))
+
+            def connection = connector.connect()
+
+            try {
+                def build = connection.newBuild()
+                def operation = new ConfigurableOperation(build)
+                build.forTasks('foo2').run()
+                assert operation.progressMessages.contains("download for 2")
+                assert !operation.progressMessages.contains("download for 1")
+                allProgress << operation.progressMessages
+            } finally {
+                connection.close()
+            }
+        }
+
+        then:
+        concurrent.finished()
+        //only one thread should log that progress message
+        1 == allProgress.count {
+            it.contains("Wait for the other thread to finish acquiring the distribution")
+        }
+    }
+
+    def "during model building receives distribution progress"() {
+        given:
+        threads.times { idx ->
+            dist.file("build$idx/build.gradle") << "apply plugin: 'java'"
+        }
+
+        when:
+        threads.times { idx ->
+            concurrent.start {
+                def connector = toolingApi.connector()
+                distributionProgressMessage(connector, "download for " + idx)
+
+                def connection = connector.connect()
+
+                try {
+                    def model = connection.model(GradleProject)
+                    def operation = new ConfigurableOperation(model)
+
+                    assert model.get()
+                    assert operation.progressMessages.contains("download for " + idx)
+                    assert !operation.progressMessages.contains("download for " + ++idx)
+                } finally {
+                    connection.close()
+                }
+            }
+        }
+
+        then:
+        concurrent.finished()
+    }
+
+    void distributionProgressMessage(GradleConnector connector, String message) {
+        connector.distribution = new ConfigurableDistribution(delegate: connector.distribution, operation: { it.description = message} )
+    }
+
+    void distributionOperation(GradleConnector connector, Closure operation) {
+        connector.distribution = new ConfigurableDistribution(delegate: connector.distribution, operation: operation )
+    }
+
+    static class ConfigurableDistribution implements Distribution {
+        Distribution delegate
+        Closure operation
+
+        String getDisplayName() {
+            return 'mock'
+        }
+
+        Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+            def o = progressLoggerFactory.newOperation("mock")
+            operation(o)
+            o.started()
+            o.completed()
+            return delegate.getToolingImplementationClasspath(progressLoggerFactory)
+        }
+    }
+
+    def "receives progress and logging while the model is building"() {
+        when:
+        //create build folders with slightly different builds
+        threads.times { idx ->
+            dist.file("build$idx/build.gradle") << """
+System.out.println 'this is stdout: $idx'
+System.err.println 'this is stderr: $idx'
+logger.lifecycle 'this is lifecycle: $idx'
+"""
+        }
+
+        then:
+        threads.times { idx ->
+            concurrent.start {
+                withConnectionInDir("build$idx") { connection ->
+                    def model = connection.model(GradleProject.class)
+                    def operation = new ConfigurableOperation(model)
+                    assert model.get()
+
+                    assert operation.standardOutput.contains("this is stdout: $idx")
+                    assert operation.standardOutput.count("this is stdout") == 1
+
+                    assert operation.standardError.contains("this is stderr: $idx")
+                    assert operation.standardError.count("this is stderr") == 1
+
+                    assert operation.standardOutput.contains("this is lifecycle: $idx")
+                    assert operation.standardOutput.count("this is lifecycle") == 1
+                    assert operation.standardError.count("this is lifecycle") == 0
+                }
+            }
+        }
+
+        concurrent.finished()
+    }
+
+    def "receives progress and logging while the build is executing"() {
+        when:
+        //create build folders with slightly different builds
+        threads.times { idx ->
+            dist.file("build$idx/build.gradle") << """
+System.out.println 'this is stdout: $idx'
+System.err.println 'this is stderr: $idx'
+logger.lifecycle 'this is lifecycle: $idx'
+"""
+        }
+
+        then:
+        threads.times { idx ->
+            concurrent.start {
+                withConnectionInDir("build$idx") { connection ->
+                    def build = connection.newBuild()
+                    def operation = new ConfigurableOperation(build)
+                    build.run()
+
+                    assert operation.standardOutput.contains("this is stdout: $idx")
+                    assert operation.standardOutput.count("this is stdout") == 1
+
+                    assert operation.standardError.contains("this is stderr: $idx")
+                    assert operation.standardError.count("this is stderr") == 1
+
+                    assert operation.standardOutput.contains("this is lifecycle: $idx")
+                    assert operation.standardOutput.count("this is lifecycle") == 1
+                    assert operation.standardError.count("this is lifecycle") == 0
+                }
+            }
+        }
+
+        concurrent.finished()
+    }
+
+    def withConnectionInDir(String dir, Closure cl) {
+        GradleConnector connector = toolingApi.connector()
+        connector.forProjectDirectory(dist.file(dir))
+        toolingApi.withConnection(connector, cl)
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy
new file mode 100644
index 0000000..c8b5610
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/GlobalLoggingManipulationIntegrationTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.tooling
+
+import java.util.logging.LogManager
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.tooling.model.GradleProject
+import org.gradle.util.RedirectStdIn
+import org.junit.Rule
+
+class GlobalLoggingManipulationIntegrationTest extends AbstractIntegrationSpec {
+    @Rule RedirectStdIn stdIn
+    final ToolingApi toolingApi = new ToolingApi(distribution)
+
+    def "tooling api does not replace standard streams"() {
+        //(SF) only checking if the instances of out and err were not replaced
+        //it would be nice to have more meaningful test that verifies the effects of replacing the streams
+        given:
+        def outInstance = System.out
+        def errInstance = System.err
+
+        buildFile << "task hey"
+
+        when:
+        GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        model.tasks.find { it.name == 'hey' }
+        System.out.is(outInstance)
+        System.err.is(errInstance)
+    }
+
+    static class FailingInputStream extends InputStream implements GroovyInterceptable {
+
+        int read() {
+            throw new RuntimeException("Input stream should not be consumed");
+        }
+
+        def invokeMethod(String name, args) {
+            throw new RuntimeException("Input stream should not be consumed");
+        }
+    }
+
+    def "tooling api should never consume the std in"() {
+        given:
+        System.in = new FailingInputStream()
+        buildFile << "task hey"
+
+        when:
+        toolingApi.withConnection { connection -> connection.newBuild().run() }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "tooling api does not reset the java logging"() {
+        //(SF) checking if the logger level was not overridden.
+        //this gives some confidence that the LogManager was not reset
+        given:
+        LogManager.getLogManager().getLogger("").setLevel(java.util.logging.Level.OFF);
+        buildFile << "task hey"
+
+        when:
+        assert java.util.logging.Level.OFF == LogManager.getLogManager().getLogger("").level
+        GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        model.tasks.find { it.name == 'hey' }
+        java.util.logging.Level.OFF == LogManager.getLogManager().getLogger("").level
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
new file mode 100644
index 0000000..90419a0
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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.tooling
+
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.integtests.fixtures.*
+
+class SamplesToolingApiIntegrationTest extends Specification {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final Sample sample = new Sample()
+
+    @UsesSample('toolingApi/eclipse')
+    def canUseToolingApiToDetermineProjectClasspath() {
+        def projectDir = sample.dir
+        Properties props = new Properties()
+        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
+        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
+        projectDir.file('gradle.properties').withOutputStream {outstr ->
+            props.store(outstr, 'props')
+        }
+        projectDir.file('settings.gradle').text = '// to stop search upwards'
+
+        when:
+        def result = run(projectDir)
+
+        then:
+        result.output.contains("gradle-tooling-api-")
+        result.output.contains("src/main/java")
+    }
+
+    @UsesSample('toolingApi/build')
+    def canUseToolingApiToRunABuild() {
+        def projectDir = sample.dir
+        Properties props = new Properties()
+        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
+        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
+        projectDir.file('gradle.properties').withOutputStream {outstr ->
+            props.store(outstr, 'props')
+        }
+        projectDir.file('settings.gradle').text = '// to stop search upwards'
+
+        when:
+        def result = run(projectDir)
+
+        then:
+        result.output.contains("Welcome to Gradle")
+    }
+
+    @UsesSample('toolingApi/idea')
+    def buildsIdeaModel() {
+        def projectDir = sample.dir
+        Properties props = new Properties()
+        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
+        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
+        projectDir.file('gradle.properties').withOutputStream {outstr ->
+            props.store(outstr, 'props')
+        }
+        projectDir.file('settings.gradle').text = '// to stop search upwards'
+
+        when:
+        run(projectDir)
+
+        then:
+        noExceptionThrown()
+    }
+
+    private ExecutionResult run(dir) {
+        try {
+            return new GradleDistributionExecuter(distribution).inDirectory(dir)
+                    .withArguments("-PautomationSystemProperty=org.gradle.daemon.idletimeout=60000")
+                    .withTasks('run')
+                    .run()
+        } catch (Exception e) {
+            throw new IntegrationTestHint(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
new file mode 100644
index 0000000..dfc1fe7
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ToolingApiIntegrationTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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.tooling
+
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.ReleasedVersions
+import org.gradle.integtests.tooling.fixture.ToolingApi
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.model.GradleProject
+import org.gradle.util.GradleVersion
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class ToolingApiIntegrationTest extends Specification {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    final ToolingApi toolingApi = new ToolingApi(dist)
+    final BasicGradleDistribution otherVersion = new ReleasedVersions(dist).last
+    TestFile projectDir = dist.testDir
+
+    def "ensure the previous version supports short-lived daemons"() {
+        expect:
+        otherVersion.daemonIdleTimeoutConfigurable
+    }
+
+    def "tooling api uses to the current version of gradle when none has been specified"() {
+        projectDir.file('build.gradle') << "assert gradle.gradleVersion == '${GradleVersion.current().version}'"
+
+        when:
+        GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        model != null
+    }
+
+    def "tooling api uses the wrapper properties to determine which version to use"() {
+        projectDir.file('build.gradle').text = """
+task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
+task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
+"""
+        dist.executer().withTasks('wrapper').run()
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.useDefaultDistribution()
+        }
+        toolingApi.withConnection { connection -> connection.newBuild().forTasks('check').run() }
+
+        then:
+        notThrown(Throwable)
+    }
+
+    def "tooling api searches up from the project directory to find the wrapper properties"() {
+        projectDir.file('settings.gradle') << "include 'child'"
+        projectDir.file('build.gradle') << """
+task wrapper(type: Wrapper) { distributionUrl = '${otherVersion.binDistribution.toURI()}' }
+allprojects {
+    task check << { assert gradle.gradleVersion == '${otherVersion.version}' }
+}
+"""
+        projectDir.file('child').createDir()
+        dist.executer().withTasks('wrapper').run()
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.useDefaultDistribution()
+            connector.searchUpwards(true)
+            connector.forProjectDirectory(projectDir.file('child'))
+        }
+        toolingApi.withConnection { connection -> connection.newBuild().forTasks('check').run() }
+
+        then:
+        notThrown(Throwable)
+    }
+
+    def "can specify a gradle installation to use"() {
+        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.useInstallation(otherVersion.gradleHomeDir)
+        }
+        GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        model != null
+    }
+
+    def "can specify a gradle distribution to use"() {
+        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.useDistribution(otherVersion.binDistribution.toURI())
+        }
+        GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        model != null
+    }
+
+    def "can specify a gradle version to use"() {
+        projectDir.file('build.gradle').text = "assert gradle.gradleVersion == '${otherVersion.version}'"
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.useGradleVersion(otherVersion.version)
+        }
+        GradleProject model = toolingApi.withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        model != null
+    }
+
+    def "tooling api reports an error when the specified gradle version does not support the tooling api"() {
+        def dist = dist.previousVersion('0.9.2').binDistribution
+
+        when:
+        toolingApi.withConnector { connector -> connector.useDistribution(dist.toURI()) }
+        def e = toolingApi.maybeFailWithConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        e.class == UnsupportedVersionException
+        e.message == "The specified Gradle distribution '${dist.toURI()}' is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
new file mode 100644
index 0000000..fc6d9a1
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.tooling.fixture
+
+import org.gradle.tooling.LongRunningOperation
+import org.gradle.tooling.ProgressListener
+
+/**
+ * by Szczepan Faber, created at: 12/19/11
+ */
+class ConfigurableOperation {
+
+    LongRunningOperation operation
+
+    def progressMessages = []
+    def listener = { event -> progressMessages << event.description } as ProgressListener
+    def stdout = new ByteArrayOutputStream()
+    def stderr = new ByteArrayOutputStream()
+
+    public ConfigurableOperation(LongRunningOperation operation) {
+        init(operation)
+    }
+
+    public ConfigurableOperation() {}
+
+    void init(LongRunningOperation operation) {
+        this.operation = operation
+        this.operation.addProgressListener(listener)
+        this.operation.standardOutput = stdout
+        this.operation.standardError = stderr
+    }
+
+    String getStandardOutput() {
+        return stdout.toString()
+    }
+
+    String getStandardError() {
+        return stderr.toString()
+    }
+
+    ConfigurableOperation setStandardInput(String input) {
+        this.operation.standardInput = new ByteArrayInputStream(input.toString().bytes)
+        return this
+    }
+
+    List getProgressMessages() {
+        return progressMessages
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy
new file mode 100644
index 0000000..22537c6
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ExternalToolingApiDistribution.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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.tooling.fixture
+
+import org.gradle.util.DefaultClassLoaderFactory
+import org.gradle.api.file.FileCollection
+
+class ExternalToolingApiDistribution implements ToolingApiDistribution {
+    private final String version
+    private final FileCollection classpath
+
+    ExternalToolingApiDistribution(String version, FileCollection classpath) {
+        this.version = version
+        this.classpath = classpath
+    }
+
+    String getVersion() {
+        version
+    }
+    
+    FileCollection getClasspath() {
+        classpath
+    }
+    
+    ClassLoader getClassLoader() {
+        def classLoaderFactory = new DefaultClassLoaderFactory()
+        classLoaderFactory.createIsolatedClassLoader(classpath.collect { it.toURI() })
+    }
+    
+    String toString() {
+        "Tooling API $version"
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/IncludeAllPermutations.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/IncludeAllPermutations.java
new file mode 100644
index 0000000..3eca69f
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/IncludeAllPermutations.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.tooling.fixture;
+
+import java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface IncludeAllPermutations {}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MaxTargetGradleVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MaxTargetGradleVersion.java
new file mode 100644
index 0000000..f46b9d9
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MaxTargetGradleVersion.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.tooling.fixture;
+
+import java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface MaxTargetGradleVersion {
+    String value();
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinTargetGradleVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinTargetGradleVersion.java
new file mode 100644
index 0000000..9128a76
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinTargetGradleVersion.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.tooling.fixture;
+
+import java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface MinTargetGradleVersion {
+    String value();
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinToolingApiVersion.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinToolingApiVersion.java
new file mode 100644
index 0000000..26f2008
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/MinToolingApiVersion.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.tooling.fixture;
+
+import java.lang.annotation.*;
+
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface MinToolingApiVersion {
+    String value();
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy
new file mode 100644
index 0000000..ab54a5b
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TestClasspathToolingApiDistribution.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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.tooling.fixture
+
+import org.gradle.util.GradleVersion
+import org.gradle.api.file.FileCollection
+
+class TestClasspathToolingApiDistribution implements ToolingApiDistribution {
+    String getVersion() {
+        GradleVersion.current().version
+    }
+
+    FileCollection getClasspath() {
+        null
+    }
+
+    ClassLoader getClassLoader() {
+        return getClass().classLoader
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TextUtil.java b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TextUtil.java
new file mode 100644
index 0000000..54b9113
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/TextUtil.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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.tooling.fixture;
+
+public class TextUtil {
+
+    /**
+     * TODO SF - temporary hack to avoid classloading issues. We should use org.gradle.util.TextUtil
+     */
+    public static String escapeString(Object obj) {
+        return obj.toString().replaceAll("\\\\", "\\\\\\\\");
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy
new file mode 100644
index 0000000..ed08ec6
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 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.tooling.fixture
+
+import java.util.concurrent.TimeUnit
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.IntegrationTestHint
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.UnsupportedVersionException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class ToolingApi {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ToolingApi)
+
+    private File projectDir
+    private BasicGradleDistribution dist
+    private Closure getProjectDir
+    private File userHomeDir
+
+    private final List<Closure> connectorConfigurers = []
+    boolean isEmbedded
+    boolean verboseLogging = true
+
+    ToolingApi(GradleDistribution dist) {
+        this(dist, dist.userHomeDir, { dist.testDir }, GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.embedded)
+    }
+
+    ToolingApi(BasicGradleDistribution dist, File userHomeDir, Closure getProjectDir, boolean isEmbedded) {
+        this.dist = dist
+        this.userHomeDir = userHomeDir
+        this.getProjectDir = getProjectDir
+        this.isEmbedded = isEmbedded
+    }
+
+    void withConnector(Closure cl) {
+        connectorConfigurers << cl
+    }
+
+    public <T> T withConnection(Closure<T> cl) {
+        GradleConnector connector = connector()
+        withConnection(connector, cl)
+    }
+
+    public <T> T withConnection(GradleConnector connector, Closure<T> cl) {
+        try {
+            return withConnectionRaw(connector, cl)
+        } catch (UnsupportedVersionException e) {
+            throw new IntegrationTestHint(e);
+        }
+    }
+
+    public Throwable maybeFailWithConnection(Closure cl) {
+        GradleConnector connector = connector()
+        try {
+            withConnectionRaw(connector, cl)
+            return null
+        } catch (Throwable e) {
+            return e
+        }
+    }
+
+    private <T> T withConnectionRaw(GradleConnector connector, Closure<T> cl) {
+        ProjectConnection connection = connector.connect()
+        try {
+            return cl.call(connection)
+        } finally {
+            connection.close()
+        }
+    }
+
+    GradleConnector connector() {
+        GradleConnector connector = GradleConnector.newConnector()
+        connector.useGradleUserHomeDir(userHomeDir)
+        connector.forProjectDirectory(getProjectDir().absoluteFile)
+        connector.searchUpwards(false)
+        connector.daemonMaxIdleTime(60, TimeUnit.SECONDS)
+        if (connector.metaClass.hasProperty(connector, 'verboseLogging')) {
+            connector.verboseLogging = verboseLogging
+        }
+        if (isEmbedded) {
+            LOGGER.info("Using embedded tooling API provider");
+            connector.useClasspathDistribution()
+            connector.embedded(true)
+        } else {
+            LOGGER.info("Using daemon tooling API provider");
+            connector.useInstallation(dist.gradleHomeDir.absoluteFile)
+            connector.embedded(false)
+        }
+        connectorConfigurers.each {
+            it.call(connector)
+        }
+        return connector
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
new file mode 100755
index 0000000..0d01704
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2011 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.tooling.fixture
+
+import org.gradle.integtests.fixtures.AbstractCompatibilityTestRunner
+import org.gradle.integtests.fixtures.AbstractMultiTestRunner
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.util.*
+
+/**
+ * Executes instances of {@link ToolingApiSpecification} against all compatible versions of tooling API consumer
+ * and provider, including the current Gradle version under test.
+ *
+ * <p>A test can be annotated with {@link MinToolingApiVersion} and {@link MinTargetGradleVersion} to indicate the
+ * minimum tooling API or Gradle versions required for the test.
+ */
+class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner {
+    private static final Map<String, ClassLoader> TEST_CLASS_LOADERS = [:]
+
+    ToolingApiCompatibilitySuiteRunner(Class<? extends ToolingApiSpecification> target) {
+        super(target, includesAllPermutations(target))
+    }
+
+    static String includesAllPermutations(Class target) {
+        if (target.getAnnotation(IncludeAllPermutations)) {
+            return "all";
+        } else {
+            return null; //just use whatever is the default
+        }
+    }
+
+    @Override
+    protected void createExecutions() {
+        ToolingApiDistributionResolver resolver = new ToolingApiDistributionResolver().withDefaultRepository()
+
+        add(new Permutation(resolver.resolve(current.version), current))
+        previous.each {
+            if (it.toolingApiSupported) {
+                add(new Permutation(resolver.resolve(current.version), it))
+                add(new Permutation(resolver.resolve(it.version), current))
+            }
+        }
+    }
+
+    private class Permutation extends AbstractMultiTestRunner.Execution {
+        final ToolingApiDistribution toolingApi
+        final BasicGradleDistribution gradle
+
+        Permutation(ToolingApiDistribution toolingApi, BasicGradleDistribution gradle) {
+            this.toolingApi = toolingApi
+            this.gradle = gradle
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return "${displayName(toolingApi)} -> ${displayName(gradle)}"
+        }
+
+        private String displayName(dist) {
+            if (dist.version == GradleVersion.current().version) {
+                return "current"
+            }
+            return dist.version
+        }
+
+        @Override
+        protected boolean isEnabled() {
+            if (!gradle.daemonSupported) {
+                return false
+            }
+            if (!gradle.daemonIdleTimeoutConfigurable && OperatingSystem.current().isWindows()) {
+                //Older daemon don't have configurable ttl and they hung for 3 hours afterwards.
+                // This is a real problem on windows due to eager file locking and continuous CI failures.
+                // On linux it's a lesser problem - long-lived daemons hung and steal resources but don't lock files.
+                // So, for windows we'll only run tests against target gradle that supports ttl
+                return false
+            }
+            MinToolingApiVersion minToolingApiVersion = target.getAnnotation(MinToolingApiVersion)
+            if (minToolingApiVersion && GradleVersion.version(toolingApi.version) < extractVersion(minToolingApiVersion)) {
+                return false
+            }
+            MinTargetGradleVersion minTargetGradleVersion = target.getAnnotation(MinTargetGradleVersion)
+            if (minTargetGradleVersion && GradleVersion.version(gradle.version) < extractVersion(minTargetGradleVersion)) {
+                return false
+            }
+            MaxTargetGradleVersion maxTargetGradleVersion = target.getAnnotation(MaxTargetGradleVersion)
+            if (maxTargetGradleVersion && GradleVersion.version(gradle.version) > extractVersion(maxTargetGradleVersion)) {
+                return false
+            }
+
+            return true
+        }
+
+        private GradleVersion extractVersion(annotation) {
+            if (GradleVersion.current().isSnapshot() && GradleVersion.current().version.startsWith(annotation.value())) {
+                //so that one can use an unreleased version in the annotation value
+                return GradleVersion.current()
+            }
+            if ("current".equals(annotation.value())) {
+                //so that one can use 'current' literal in the annotatin value
+                //(useful if you don't know if the feature makes its way to the upcoming release)
+                return GradleVersion.current()
+            }
+            return GradleVersion.version(annotation.value())
+        }
+
+        @Override
+        protected List<? extends Class<?>> loadTargetClasses() {
+            def testClassLoader = getTestClassLoader()
+            return [testClassLoader.loadClass(target.name)]
+        }
+
+        private ClassLoader getTestClassLoader() {
+            def classLoader = TEST_CLASS_LOADERS.get(toolingApi.version)
+            if (!classLoader) {
+                classLoader = createTestClassLoader()
+                TEST_CLASS_LOADERS.put(toolingApi.version, classLoader)
+            }
+            return classLoader
+        }
+
+        private ClassLoader createTestClassLoader() {
+            def classLoaderFactory = new DefaultClassLoaderFactory()
+
+            def sharedClassLoader = classLoaderFactory.createFilteringClassLoader(getClass().classLoader)
+            sharedClassLoader.allowPackage('org.junit')
+            sharedClassLoader.allowPackage('org.hamcrest')
+            sharedClassLoader.allowPackage('junit.framework')
+            sharedClassLoader.allowPackage('groovy')
+            sharedClassLoader.allowPackage('org.codehaus.groovy')
+            sharedClassLoader.allowPackage('spock')
+            sharedClassLoader.allowPackage('org.spockframework')
+            sharedClassLoader.allowClass(TestFile)
+            sharedClassLoader.allowClass(SetSystemProperties)
+            sharedClassLoader.allowPackage('org.gradle.integtests.fixtures')
+            sharedClassLoader.allowPackage('org.gradle.tests.fixtures')
+            sharedClassLoader.allowClass(OperatingSystem)
+            sharedClassLoader.allowClass(Requires)
+            sharedClassLoader.allowClass(TestPrecondition)
+
+            def parentClassLoader = new MultiParentClassLoader(toolingApi.classLoader, sharedClassLoader)
+
+            def testClassPath = []
+            testClassPath << ClasspathUtil.getClasspathForClass(target)
+
+            return new MutableURLClassLoader(parentClassLoader, testClassPath.collect { it.toURI().toURL() })
+        }
+
+        @Override
+        protected void before() {
+            testClassLoader.loadClass(ToolingApiSpecification.name).selectTargetDist(gradle)
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistribution.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistribution.groovy
new file mode 100644
index 0000000..0a69936
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistribution.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.tooling.fixture
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.Nullable
+
+interface ToolingApiDistribution {
+    String getVersion()
+    @Nullable FileCollection getClasspath()
+    ClassLoader getClassLoader()
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
new file mode 100644
index 0000000..3d2b9b6
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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.tooling.fixture
+
+import org.gradle.util.HelperUtil
+import org.gradle.api.internal.project.ProjectInternalServiceRegistry
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.project.TopLevelBuildServiceRegistry
+import org.gradle.StartParameter
+import org.gradle.api.internal.project.GlobalServicesRegistry
+import org.gradle.integtests.fixtures.GradleDistribution
+
+class ToolingApiDistributionResolver {
+    private final DependencyResolutionServices resolutionServices
+    private final Map<String, ToolingApiDistribution> distributions = [:]
+    private final GradleDistribution currentGradleDistribution = new GradleDistribution()
+    ToolingApiDistributionResolver() {
+        resolutionServices = createResolutionServices()
+        resolutionServices.resolveRepositoryHandler.maven { url currentGradleDistribution.libsRepo.toURI().toURL() }
+    }
+
+    ToolingApiDistributionResolver withRepository(String repositoryUrl) {
+        resolutionServices.resolveRepositoryHandler.maven { url repositoryUrl }
+        this
+    }
+
+    ToolingApiDistributionResolver withDefaultRepository() {
+        withRepository("http://repo.gradle.org/gradle/repo")
+    }
+
+    ToolingApiDistribution resolve(String toolingApiVersion) {
+        if (!distributions[toolingApiVersion]) {
+            if (useToolingApiFromTestClasspath(toolingApiVersion)) {
+                distributions[toolingApiVersion] = new TestClasspathToolingApiDistribution()
+            } else {
+                Dependency toolingApiDep = resolutionServices.dependencyHandler.create("org.gradle:gradle-tooling-api:$toolingApiVersion")
+                Configuration toolingApiConfig = resolutionServices.configurationContainer.detachedConfiguration(toolingApiDep)
+                distributions[toolingApiVersion] = new ExternalToolingApiDistribution(toolingApiVersion, toolingApiConfig)
+            }
+        }
+        distributions[toolingApiVersion]
+    }
+
+    private boolean useToolingApiFromTestClasspath(String toolingApiVersion) {
+        toolingApiVersion == currentGradleDistribution.version && System.getProperty("org.gradle.integtest.toolingApiFromTestClasspath", "true") == "true"
+    }
+
+    private DependencyResolutionServices createResolutionServices() {
+        GlobalServicesRegistry globalRegistry = new GlobalServicesRegistry()
+        TopLevelBuildServiceRegistry topLevelRegistry = new TopLevelBuildServiceRegistry(globalRegistry, new StartParameter())
+        ProjectInternalServiceRegistry projectRegistry = new ProjectInternalServiceRegistry(topLevelRegistry, HelperUtil.createRootProject())
+        projectRegistry.get(DependencyResolutionServices)
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy
new file mode 100644
index 0000000..27d732a
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 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.tooling.fixture
+
+import org.gradle.integtests.fixtures.BasicGradleDistribution
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.tooling.GradleConnector
+import org.gradle.util.GradleVersion
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import org.junit.internal.AssumptionViolatedException
+import org.junit.runner.RunWith
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+
+ at RunWith(ToolingApiCompatibilitySuiteRunner)
+abstract class ToolingApiSpecification extends Specification {
+    static final Logger LOGGER = LoggerFactory.getLogger(ToolingApiSpecification)
+    @Rule public final SetSystemProperties sysProperties = new SetSystemProperties()
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    final ToolingApi toolingApi = new ToolingApi(dist)
+    private static final ThreadLocal<BasicGradleDistribution> VERSION = new ThreadLocal<BasicGradleDistribution>()
+
+    static void selectTargetDist(BasicGradleDistribution version) {
+        VERSION.set(version)
+    }
+
+    static BasicGradleDistribution getTargetDist() {
+        VERSION.get()
+    }
+
+    void setup() {
+        def consumerGradle = GradleVersion.current()
+        def target = GradleVersion.version(VERSION.get().version)
+        LOGGER.info(" Using Tooling API consumer ${consumerGradle}, provider ${target}")
+        boolean accept = accept(consumerGradle, target)
+        if (!accept) {
+            throw new AssumptionViolatedException("Test class ${getClass().name} does not work with tooling API ${consumerGradle} and Gradle ${target}.")
+        }
+        this.toolingApi.withConnector {
+            if (consumerGradle.version != target.version) {
+                LOGGER.info("Overriding daemon tooling API provider to use installation: " + target);
+                def targetGradle = dist.previousVersion(target.version)
+                it.useInstallation(new File(targetGradle.gradleHomeDir.absolutePath))
+                it.embedded(false)
+            }
+        }
+    }
+
+    /**
+     * Returns true if this test class works with the given combination of tooling API consumer and provider.
+     */
+    protected boolean accept(GradleVersion toolingApi, GradleVersion targetGradle) {
+        return true
+    }
+
+    public <T> T withConnection(Closure<T> cl) {
+        toolingApi.withConnection(cl)
+    }
+
+    public <T> T withConnection(GradleConnector connector, Closure<T> cl) {
+        toolingApi.withConnection(connector, cl)
+    }
+
+    def connector() {
+        toolingApi.connector()
+    }
+
+    Throwable maybeFailWithConnection(Closure cl) {
+        toolingApi.maybeFailWithConnection(cl)
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..e41f620
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m3/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -0,0 +1,306 @@
+/*
+ * 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.tooling.m3
+
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.ExternalDependency
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+
+class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
+
+    def "can build the eclipse model for a java project"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+description = 'this is a project'
+'''
+        projectDir.file('settings.gradle').text = 'rootProject.name = \"test project\"'
+
+        when:
+        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        minimalProject.name == 'test project'
+        minimalProject.description == 'this is a project'
+        minimalProject.projectDirectory == projectDir
+        minimalProject.parent == null
+        minimalProject.children.empty
+
+        when:
+        EclipseProject fullProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        fullProject.name == 'test project'
+        fullProject.description == 'this is a project'
+        fullProject.projectDirectory == projectDir
+        fullProject.parent == null
+        fullProject.children.empty
+    }
+
+    def "can build the eclipse model for an empty project"() {
+        when:
+        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        minimalProject != null
+
+        minimalProject.description == null
+        minimalProject.parent == null
+        minimalProject.children.empty
+        minimalProject.sourceDirectories.empty
+        minimalProject.projectDependencies.empty
+
+        when:
+        EclipseProject fullProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        fullProject != null
+
+        fullProject.description == null
+        fullProject.parent == null
+        fullProject.children.empty
+        fullProject.sourceDirectories.empty
+        fullProject.classpath.empty
+        fullProject.projectDependencies.empty
+    }
+
+    def "can build the eclipse source directories for a java project"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = "apply plugin: 'java'"
+
+        projectDir.create {
+            src {
+                main {
+                    java {}
+                    resources {}
+                }
+                test {
+                    java {}
+                    resources {}
+                }
+            }
+        }
+
+        when:
+        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        minimalProject != null
+
+        minimalProject.sourceDirectories.size() == 4
+        minimalProject.sourceDirectories[0].path == 'src/main/java'
+        minimalProject.sourceDirectories[0].directory == projectDir.file('src/main/java')
+        minimalProject.sourceDirectories[1].path == 'src/main/resources'
+        minimalProject.sourceDirectories[1].directory == projectDir.file('src/main/resources')
+        minimalProject.sourceDirectories[2].path == 'src/test/java'
+        minimalProject.sourceDirectories[2].directory == projectDir.file('src/test/java')
+        minimalProject.sourceDirectories[3].path == 'src/test/resources'
+        minimalProject.sourceDirectories[3].directory == projectDir.file('src/test/resources')
+
+        when:
+        EclipseProject fullProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        fullProject != null
+
+        fullProject.sourceDirectories.size() == 4
+        fullProject.sourceDirectories[0].path == 'src/main/java'
+        fullProject.sourceDirectories[0].directory == projectDir.file('src/main/java')
+        fullProject.sourceDirectories[1].path == 'src/main/resources'
+        fullProject.sourceDirectories[1].directory == projectDir.file('src/main/resources')
+        fullProject.sourceDirectories[2].path == 'src/test/java'
+        fullProject.sourceDirectories[2].directory == projectDir.file('src/test/java')
+        fullProject.sourceDirectories[3].path == 'src/test/resources'
+        fullProject.sourceDirectories[3].directory == projectDir.file('src/test/resources')
+    }
+
+    def "can build the eclipse external dependencies for a java project"() {
+        def projectDir = dist.testDir
+        projectDir.file('settings.gradle').text = '''
+include "a"
+rootProject.name = 'root'
+'''
+        projectDir.file('build.gradle').text = '''
+allprojects { apply plugin: 'java' }
+repositories { mavenCentral() }
+dependencies {
+    compile 'commons-lang:commons-lang:2.5'
+    compile project(':a')
+    runtime 'commons-io:commons-io:1.4'
+}
+'''
+
+        when:
+        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        eclipseProject != null
+
+        eclipseProject.classpath.size() == 2
+        eclipseProject.classpath.every { it instanceof ExternalDependency }
+        eclipseProject.classpath.collect { it.file.name } as Set == ['commons-lang-2.5.jar', 'commons-io-1.4.jar' ] as Set
+        eclipseProject.classpath.collect { it.source?.name } as Set == ['commons-lang-2.5-sources.jar', 'commons-io-1.4-sources.jar'] as Set
+        eclipseProject.classpath.collect { it.javadoc?.name } as Set == [null, null] as Set
+    }
+
+    //TODO SF: write a test that checks if minimal project has necessary project dependencies
+
+    def "can build the minimal Eclipse model for a java project with the idea plugin applied"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+apply plugin: 'idea'
+
+dependencies {
+    compile files { throw new RuntimeException('should not be resolving this') }
+}
+'''
+
+        when:
+        HierarchicalEclipseProject minimalProject = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        minimalProject != null
+    }
+
+    def "can build the eclipse project dependencies for a java project"() {
+        def projectDir = dist.testDir
+        projectDir.file('settings.gradle').text = '''
+include "a", "a:b"
+rootProject.name = 'root'
+'''
+        projectDir.file('build.gradle').text = '''
+allprojects {
+    apply plugin: 'java'
+}
+project(':a') {
+    dependencies {
+        compile project(':')
+        compile project(':a:b')
+    }
+}
+'''
+
+        when:
+        HierarchicalEclipseProject minimalModel = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        HierarchicalEclipseProject minimalProject = minimalModel.children[0]
+
+        minimalProject.projectDependencies.size() == 2
+
+        minimalProject.projectDependencies.any { it.path == 'root' && it.targetProject == minimalModel }
+        minimalProject.projectDependencies.any { it.path == 'b' && it.targetProject == minimalProject.children[0] }
+
+        when:
+        EclipseProject fullModel = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        EclipseProject fullProject = fullModel.children[0]
+
+        fullProject.projectDependencies.size() == 2
+
+        fullProject.projectDependencies.any { it.path == 'root' && it.targetProject == fullModel }
+        fullProject.projectDependencies.any { it.path == 'b' && it.targetProject == fullProject.children[0] }
+    }
+
+    def "can build project dependencies with targetProject references for complex scenarios"() {
+        def projectDir = dist.testDir
+        projectDir.file('settings.gradle').text = '''
+include "c", "a", "a:b"
+rootProject.name = 'root'
+'''
+        projectDir.file('build.gradle').text = '''
+allprojects {
+    apply plugin: 'java'
+}
+project(':a') {
+    dependencies {
+        compile project(':')
+        compile project(':a:b')
+        compile project(':c')
+    }
+}
+project(':c') {
+    dependencies {
+        compile project(':a:b')
+    }
+}
+'''
+
+        when:
+        EclipseProject rootProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        def projectC = rootProject.children.find { it.name == 'c'}
+        def projectA = rootProject.children.find { it.name == 'a'}
+        def projectAB = projectA.children.find { it.name == 'b' }
+
+        projectC.projectDependencies.any {it.targetProject == projectAB}
+
+        projectA.projectDependencies.any {it.targetProject == projectAB}
+        projectA.projectDependencies.any {it.targetProject == projectC}
+        projectA.projectDependencies.any {it.targetProject == rootProject}
+    }
+
+    def "can build the eclipse project hierarchy for a multi-project build"() {
+        def projectDir = dist.testDir
+        projectDir.file('settings.gradle').text = '''
+            include "child1", "child2", "child1:grandChild1"
+            rootProject.name = 'root'
+'''
+        projectDir.file('child1').mkdirs()
+
+        when:
+        EclipseProject rootProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        rootProject != null
+        rootProject.name == 'root'
+        rootProject.parent == null
+
+        rootProject.children.size() == 2
+
+        EclipseProject child1 = rootProject.children[0]
+        child1.name == 'child1'
+        child1.parent == rootProject
+        child1.children.size() == 1
+
+        EclipseProject child1Child1 = child1.children[0]
+        child1Child1.name == 'grandChild1'
+        child1Child1.parent == child1
+        child1Child1.children.size() == 0
+
+        EclipseProject child2 = rootProject.children[1]
+        child2.name == 'child2'
+        child2.parent == rootProject
+        child2.children.size() == 0
+
+        when:
+        toolingApi.withConnector { connector ->
+            connector.searchUpwards(true)
+            connector.forProjectDirectory(projectDir.file('child1'))
+        }
+        EclipseProject child = toolingApi.withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        child.name == 'child1'
+        child.parent != null
+        child.parent.name == 'root'
+        child.children.size() == 1
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
new file mode 100644
index 0000000..3daeaef
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseLinkedResourcesCrossVersionSpec.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.tooling.m4
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+
+/**
+ * @author: Szczepan Faber, created at: 6/11/11
+ */
+ at MinToolingApiVersion('1.0-milestone-4')
+ at MinTargetGradleVersion('1.0-milestone-4')
+class ToolingApiEclipseLinkedResourcesCrossVersionSpec extends ToolingApiSpecification {
+    def "can build linked resources"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+eclipse.project {
+    linkedResource name: 'foo', location: '/path/to/foo', type : '2'
+    linkedResource name: 'bar', locationUri: 'file://..', type : '3'
+}
+'''
+        when:
+        HierarchicalEclipseProject minimalProject = withConnection { it.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        minimalProject.linkedResources.size() == 2
+
+        minimalProject.linkedResources[0].name == 'foo'
+        minimalProject.linkedResources[0].type == '2'
+        minimalProject.linkedResources[0].location == '/path/to/foo'
+        minimalProject.linkedResources[0].locationUri == null
+
+        minimalProject.linkedResources[1].name == 'bar'
+        minimalProject.linkedResources[1].type == '3'
+        minimalProject.linkedResources[1].location == null
+        minimalProject.linkedResources[1].locationUri == 'file://..'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..2649973
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m4/ToolingApiEclipseMinimalModelCrossVersionSpec.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.tooling.m4
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject
+
+ at MinTargetGradleVersion('1.0-milestone-4')
+class ToolingApiEclipseMinimalModelCrossVersionSpec extends ToolingApiSpecification {
+    def "minimal Eclipse model does not attempt to resolve external dependencies"() {
+        def projectDir = dist.testDir
+        projectDir.file('settings.gradle').text = 'include "child"'
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+dependencies {
+    compile project(':child')
+    compile files { throw new RuntimeException() }
+    compile 'this.lib.surely.does.not.exist:indeed:1.0'
+}
+project(':child') {
+    apply plugin: 'java'
+    dependencies {
+        compile files { throw new RuntimeException() }
+        compile 'this.lib.surely.does.not.exist:indeed:2.0'
+    }
+}
+'''
+
+        when:
+        HierarchicalEclipseProject project = withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        project.projectDependencies.size() == 1
+        project.projectDependencies[0].path == 'child'
+    }
+
+    def "minimal Eclipse model does not attempt to call any tasks"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+
+sourceSets.main.output.dir "$buildDir/foo", builtBy: 'generateResources'
+
+task generateResources << {
+  assert false : 'should not be called when building minimal model'
+}
+'''
+
+        when:
+        withConnection { connection -> connection.getModel(HierarchicalEclipseProject.class) }
+
+        then:
+        noExceptionThrown()
+    }
+
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
new file mode 100644
index 0000000..f85d816
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/EclipseModelWithFlatRepoCrossVersionSpec.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.util.TestFile
+import spock.lang.Issue
+
+ at MinTargetGradleVersion('1.0-milestone-5')
+class EclipseModelWithFlatRepoCrossVersionSpec extends ToolingApiSpecification {
+    TestFile projectDir = dist.testDir
+
+    @Issue("GRADLE-1621")
+    def "can get Eclipse model for project with flatDir repo and external dependency without source Jar"() {
+        def repoDir = projectDir.createDir("repo")
+        repoDir.createFile("lib-1.0.jar")
+
+        projectDir.file("build.gradle") << """
+apply plugin: "java"
+
+repositories {
+	flatDir dirs: file("${repoDir.toURI()}")
+}
+
+dependencies {
+	compile "some:lib:1.0"
+}
+        """
+
+        when:
+        EclipseProject project = toolingApi.withConnection { connection ->
+            connection.getModel(EclipseProject.class)
+        }
+
+        then:
+        project.classpath[0].file != null
+        project.classpath[0].source == null
+        project.classpath[0].javadoc == null
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
new file mode 100644
index 0000000..b5f44c1
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.BuildException
+import org.gradle.tooling.ProgressListener
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.Task
+import org.gradle.tooling.model.eclipse.EclipseProject
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-5')
+class ToolingApiBuildExecutionCrossVersionSpec extends ToolingApiSpecification {
+    def "can build the set of tasks for a project"() {
+        dist.testFile('build.gradle') << '''
+task a {
+   description = 'this is task a'
+}
+task b
+task c
+'''
+
+        when:
+        GradleProject project = withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        def taskA = project.tasks.find { it.name == 'a' }
+        taskA != null
+        taskA.path == ':a'
+        taskA.description == 'this is task a'
+        taskA.project == project
+        project.tasks.find { it.name == 'b' }
+        project.tasks.find { it.name == 'c' }
+    }
+
+    def "can execute a build for a project"() {
+        dist.testFile('settings.gradle') << 'rootProject.name="test"'
+        dist.testFile('build.gradle') << '''
+apply plugin: 'java'
+'''
+        when:
+        withConnection { connection ->
+            def build = connection.newBuild()
+            build.forTasks('jar')
+            build.run()
+        }
+
+        then:
+        dist.testFile('build/libs/test.jar').assertIsFile()
+
+        when:
+        withConnection { connection ->
+            GradleProject project = connection.getModel(GradleProject.class)
+            Task clean = project.tasks.find { it.name == 'clean' }
+            def build = connection.newBuild()
+            build.forTasks(clean)
+            build.run()
+        }
+
+        then:
+        dist.testFile('build/libs/test.jar').assertDoesNotExist()
+    }
+
+    def "receives progress while the build is executing"() {
+        dist.testFile('build.gradle') << '''
+System.out.println 'this is stdout'
+System.err.println 'this is stderr'
+'''
+        def progressMessages = []
+
+        when:
+        withConnection { connection ->
+            def build = connection.newBuild()
+            build.addProgressListener({ event -> progressMessages << event.description } as ProgressListener)
+            build.run()
+        }
+
+        then:
+        progressMessages.size() >= 2
+        progressMessages.pop() == ''
+        progressMessages.every { it }
+    }
+
+    def "tooling api reports build failure"() {
+        dist.testFile('build.gradle') << 'broken'
+
+        when:
+        withConnection { connection ->
+            return connection.newBuild().forTasks('jar').run()
+        }
+
+        then:
+        BuildException e = thrown()
+        e.message.startsWith('Could not execute build using Gradle')
+        e.cause.message.contains('A problem occurred evaluating root project')
+    }
+
+    def "can build the set of tasks for an Eclipse project"() {
+        dist.testFile('build.gradle') << '''
+task a {
+   description = 'this is task a'
+}
+task b
+task c
+'''
+
+        when:
+        EclipseProject project = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        def taskA = project.gradleProject.tasks.find { it.name == 'a' }
+        taskA != null
+        taskA.path == ':a'
+        taskA.description == 'this is task a'
+        taskA.project == project.gradleProject
+        project.gradleProject.tasks.find { it.name == 'b' }
+        project.gradleProject.tasks.find { it.name == 'c' }
+    }
+
+    def "does not resolve dependencies when building the set of tasks for a project"() {
+        dist.testFile('build.gradle') << '''
+apply plugin: 'java'
+dependencies {
+    compile files { throw new RuntimeException('broken') }
+}
+'''
+
+        when:
+        GradleProject project = withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        !project.tasks.empty
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy
new file mode 100644
index 0000000..57128ba
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildableEclipseModelFixesCrossVersionSpec.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.eclipse.EclipseProject
+import spock.lang.Issue
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-5')
+class ToolingApiBuildableEclipseModelFixesCrossVersionSpec extends ToolingApiSpecification {
+    @Issue("GRADLE-1529")
+    //this is just one of the ways of fixing the problem. See the issue for details
+    def "should not show not executable tasks"() {
+        dist.testFile('build.gradle') << '''
+task a
+task b
+'''
+        when:
+        def project = withConnection { connection -> connection.getModel(GradleProject.class) }
+
+        then:
+        def tasks = project.tasks.collect { it.name }
+        assert tasks == ['a', 'b']: "temp tasks like 'cleanEclipse', 'eclipse', e.g. should not show on this list: " + tasks
+    }
+
+    @Issue("GRADLE-1529")
+    //this is just one of the ways of fixing the problem. See the issue for details
+    def "should hide not executable tasks when necessary for a multi module build"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+project(':api') {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+}
+'''
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+
+        when:
+        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        def rootTasks = eclipseProject.gradleProject.tasks.collect { it.name }
+
+        EclipseProject api = eclipseProject.children[1]
+        def apiTasks = api.gradleProject.tasks.collect { it.name }
+
+        EclipseProject impl = eclipseProject.children[0]
+        def implTasks = impl.gradleProject.tasks.collect { it.name }
+
+        ['eclipse', 'cleanEclipse', 'eclipseProject', 'cleanEclipseProject'].each {
+            assert !rootTasks.contains(it)
+            assert !implTasks.contains(it)
+
+            assert apiTasks.contains(it)
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..6ab846d
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.eclipse.EclipseProject
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-3')
+class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
+    def "eclipse project has access to gradle project and its tasks"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+subprojects {
+    apply plugin: 'java'
+}
+
+task rootTask {}
+
+project(':impl') {
+    task implTask {}
+}
+"""
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'; rootProject.name = 'root'"
+
+        when:
+        def root = withConnection { it.getModel(EclipseProject.class) }
+
+        then:
+        def impl = root.children.find { it.name == 'impl'}
+
+        root.gradleProject.tasks.find { it.name == 'rootTask' && it.path == ':rootTask' && it.project == root.gradleProject }
+        !root.gradleProject.tasks.find { it.name == 'implTask' }
+
+        impl.gradleProject.tasks.find { it.name == 'implTask' && it.path == ':impl:implTask' && it.project == impl.gradleProject}
+        !impl.gradleProject.tasks.find { it.name == 'rootTask' }
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
new file mode 100644
index 0000000..5ba64c7
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiGradleProjectCrossVersionSpec.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.GradleTask
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-3')
+class ToolingApiGradleProjectCrossVersionSpec extends ToolingApiSpecification {
+
+    def "provides tasks of a project"() {
+        dist.testFile('build.gradle') << '''
+task a {
+   description = 'this is task a'
+}
+task b
+task c
+'''
+
+        when:
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        GradleTask taskA = project.tasks.find { it.name == 'a' }
+        taskA.path == ':a'
+        taskA.description == 'this is task a'
+        taskA.project == project
+
+        project.tasks.find { it.name == 'b' && it.path == ':b' }
+        project.tasks.find { it.name == 'c' && it.path == ':c' }
+    }
+
+    def "provides hierarchy"() {
+        dist.testFile('settings.gradle') << "include 'a', 'a:b', 'a:c', 'a:c:d'"
+        dist.testFile('build.gradle') << '''
+task rootTask
+project (':a') { description = 'A rocks!' }
+'''
+
+        when:
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        project.tasks.find { it.name == 'rootTask' }
+
+        project.children.size() == 1
+        GradleProject a = project.children[0]
+        a.name == 'a'
+        a.path == ':a'
+        a.description == 'A rocks!'
+
+        a.children.size() == 2
+        a.children.find { it.name == 'b' && it.path == ':a:b' }
+        a.children.find { it.name == 'c' && it.path == ':a:c' }
+        
+        a.children.find { it.name == 'c'}.children[0].name == 'd'
+    }
+
+    def "can provide tasks for hierarchical project"() {
+        dist.testFile('settings.gradle') << "include 'a', 'a:b', 'a:c'"
+        dist.testFile('build.gradle') << '''
+task rootTask
+project(':a') { task taskA }
+project(':a:b') { task taskAB }
+project(':a:c') { task taskAC }
+
+'''
+
+        when:
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        project.tasks.find { it.name == 'rootTask' && it.project == project }
+        !project.tasks.find { it.name == 'taskA' }
+
+        GradleProject a = project.children[0]
+        a.tasks.find { it.name == 'taskA' && it.project == a }
+        !a.tasks.find { it.name == 'rootTask' }
+
+        GradleProject ab = a.children.find { it.path == ':a:b' }
+        ab.tasks.find { it.name == 'taskAB'}
+
+        GradleProject ac = a.children.find { it.path == ':a:c' }
+        ac.tasks.find { it.name == 'taskAC'}
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
new file mode 100755
index 0000000..314defe
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiHonorsProjectCustomizationsCrossVersionSpec.groovy
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.model.eclipse.EclipseProject
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-5')
+class ToolingApiHonorsProjectCustomizationsCrossVersionSpec extends ToolingApiSpecification {
+
+    def "should honour reconfigured project names"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+allprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+}
+
+project(':api') {
+    eclipse.project.name = 'gradle-api'
+}
+
+project(':impl') {
+    eclipse.project.name = 'gradle-impl'
+}
+'''
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+
+        when:
+        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        EclipseProject api = eclipseProject.children[1]
+        assert api.name == 'gradle-api'
+        EclipseProject impl = eclipseProject.children[0]
+        assert impl.name == 'gradle-impl'
+    }
+
+    def "should deduplicate project names"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+allprojects {
+    apply plugin: 'java'
+}
+'''
+        projectDir.file('settings.gradle').text = "include 'services:api', 'contrib:api'"
+
+        when:
+        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        String grandChildOne = eclipseProject.children[0].children[0].name
+        String grandChildTwo = eclipseProject.children[1].children[0].name
+        assert grandChildOne != grandChildTwo : "Deduplication logic should make that project names are not the same."
+    }
+
+    def "can have overlapping source and resource directories"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceSets {
+    main {
+        java { srcDir 'src' }
+        resources { srcDir 'src' }
+    }
+
+    test {
+        java { srcDir 'test' }
+        resources { srcDir 'testResources' }
+    }
+}
+'''
+        //if we don't create the folders eclipse plugin will not build the classpath
+        projectDir.create {
+            src {}
+            test {}
+            testResources {}
+        }
+
+        when:
+        EclipseProject eclipseProject = withConnection { connection -> connection.getModel(EclipseProject.class) }
+
+        then:
+        eclipseProject.sourceDirectories.size() == 3
+        eclipseProject.sourceDirectories[0].path == 'src'
+        eclipseProject.sourceDirectories[1].path == 'testResources'
+        eclipseProject.sourceDirectories[2].path == 'test'
+    }
+
+    def "can enable download of Javadoc for external dependencies"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories { mavenCentral() }
+dependencies {
+    compile 'commons-lang:commons-lang:2.5'
+    runtime 'commons-io:commons-io:1.4'
+}
+eclipse { classpath { downloadJavadoc = true } }
+'''
+
+        when:
+        EclipseProject eclipseProject = withConnection { ProjectConnection connection ->
+            def builder = connection.model(EclipseProject.class)
+            return builder.get()
+        }
+
+        then:
+        eclipseProject.classpath.size() == 2
+        eclipseProject.classpath.collect { it.file.name } as Set == ['commons-lang-2.5.jar', 'commons-io-1.4.jar' ] as Set
+        eclipseProject.classpath.collect { it.source?.name } as Set == ['commons-lang-2.5-sources.jar', 'commons-io-1.4-sources.jar'] as Set
+        eclipseProject.classpath.collect { it.javadoc?.name } as Set == ['commons-lang-2.5-javadoc.jar', 'commons-io-1.4-javadoc.jar'] as Set
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..d3ae85f
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiIdeaModelCrossVersionSpec.groovy
@@ -0,0 +1,312 @@
+/*
+ * 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.tooling.m5
+
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.idea.*
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-5')
+class ToolingApiIdeaModelCrossVersionSpec extends ToolingApiSpecification {
+
+    def "builds the model even if idea plugin not applied"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+apply plugin: 'java'
+description = 'this is a project'
+'''
+        projectDir.file('settings.gradle').text = 'rootProject.name = \"test project\"'
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+
+        then:
+        project.parent == null
+        project.name == 'test project'
+        project.description == null
+        project.children.size() == 1
+        project.children[0] instanceof IdeaModule
+        project.children == project.modules
+    }
+
+    def "provides basic project information"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea.project {
+  languageLevel = '1.5'
+  jdkName = '1.6'
+}
+"""
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+
+        then:
+        project.languageLevel.level == "JDK_1_5"
+        project.jdkName == '1.6'
+    }
+
+    def "provides all modules"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = '''
+subprojects {
+    apply plugin: 'java'
+}
+'''
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+
+        then:
+        project.children.size() == 3
+        project.children.any { it.name == 'api' }
+        project.children.any { it.name == 'impl' }
+    }
+
+    def "provides basic module information"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea.module.inheritOutputDirs = false
+idea.module.outputDir = file('someDir')
+idea.module.testOutputDir = file('someTestDir')
+"""
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+        def module = project.children[0]
+
+        then:
+        module.contentRoots.size() == 1
+        module.contentRoots[0].rootDirectory == projectDir
+        module.parent instanceof IdeaProject
+        module.parent == project
+        module.parent == module.project
+        module.children.empty
+        module.description == null
+
+        !module.compilerOutput.inheritOutputDirs
+        module.compilerOutput.outputDir == projectDir.file('someDir')
+        module.compilerOutput.testOutputDir == projectDir.file('someTestDir')
+    }
+
+    def "provides source dir information"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = "apply plugin: 'java'"
+
+        projectDir.create {
+            src {
+                main {
+                    java {}
+                    resources {}
+                }
+                test {
+                    java {}
+                    resources {}
+                }
+            }
+        }
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+        IdeaModule module = project.children[0]
+        IdeaContentRoot root = module.contentRoots[0]
+
+        then:
+        root.sourceDirectories.size() == 2
+        root.sourceDirectories.any { it.directory == projectDir.file('src/main/java') }
+        root.sourceDirectories.any { it.directory == projectDir.file('src/main/resources') }
+
+        root.testDirectories.size() == 2
+        root.testDirectories.any { it.directory == projectDir.file('src/test/java') }
+        root.testDirectories.any { it.directory == projectDir.file('src/test/resources') }
+    }
+
+    def "provides exclude dir information"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+idea.module.excludeDirs += file('foo')
+"""
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+        def module = project.children[0]
+
+        then:
+        module.contentRoots[0].excludeDirectories.any { it.path.endsWith 'foo' }
+    }
+
+    def "provides dependencies"() {
+        def projectDir = dist.testDir
+        def fakeRepo = projectDir.file("repo")
+
+        def dependency = new MavenRepository(fakeRepo).module("foo.bar", "coolLib", 1.0)
+        dependency.artifact(classifier: 'sources')
+        dependency.artifact(classifier: 'javadoc')
+        dependency.publish()
+
+        projectDir.file('build.gradle').text = """
+subprojects {
+    apply plugin: 'java'
+}
+
+project(':impl') {
+    apply plugin: 'idea'
+
+    repositories {
+        maven { url "${fakeRepo.toURI()}" }
+    }
+
+    dependencies {
+        compile project(':api')
+        testCompile 'foo.bar:coolLib:1.0'
+    }
+
+    idea.module.downloadJavadoc = true
+}
+"""
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+        def module = project.children.find { it.name == 'impl' }
+
+        then:
+        def libs = module.dependencies
+        IdeaSingleEntryLibraryDependency lib = libs.find {it instanceof IdeaSingleEntryLibraryDependency}
+
+        lib.file.exists()
+        lib.file.path.endsWith('coolLib-1.0.jar')
+
+        lib.source.exists()
+        lib.source.path.endsWith('coolLib-1.0-sources.jar')
+
+        lib.javadoc.exists()
+        lib.javadoc.path.endsWith('coolLib-1.0-javadoc.jar')
+
+        lib.scope.scope == 'TEST'
+
+        IdeaModuleDependency mod = libs.find {it instanceof IdeaModuleDependency}
+        mod.dependencyModule == project.modules.find { it.name == 'api'}
+        mod.scope.scope == 'COMPILE'
+    }
+
+    def "makes sure module names are unique"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+subprojects {
+    apply plugin: 'java'
+}
+
+project(':impl') {
+    dependencies {
+        compile project(':api')
+    }
+}
+
+project(':contrib:impl') {
+    dependencies {
+        compile project(':contrib:api')
+    }
+}
+"""
+        projectDir.file('settings.gradle').text = "include 'api', 'impl', 'contrib:api', 'contrib:impl'"
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+
+        then:
+        def allNames = project.modules*.name
+        allNames.unique().size() == 6
+
+        IdeaModule impl = project.modules.find { it.name == 'impl' }
+        IdeaModule contribImpl = project.modules.find { it.name == 'contrib-impl' }
+
+        impl.dependencies[0].dependencyModule        == project.modules.find { it.name == 'api' }
+        contribImpl.dependencies[0].dependencyModule == project.modules.find { it.name == 'contrib-api' }
+    }
+
+    def "module has access to gradle project and its tasks"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+subprojects {
+    apply plugin: 'java'
+}
+
+task rootTask {}
+
+project(':impl') {
+    task implTask {}
+}
+"""
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'; rootProject.name = 'root'"
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+
+        then:
+        def impl = project.modules.find { it.name == 'impl'}
+        def root = project.modules.find { it.name == 'root'}
+
+        root.gradleProject.tasks.find { it.name == 'rootTask' && it.path == ':rootTask' && it.project == root.gradleProject }
+        !root.gradleProject.tasks.find { it.name == 'implTask' }
+
+        impl.gradleProject.tasks.find { it.name == 'implTask' && it.path == ':impl:implTask' && it.project == impl.gradleProject}
+        !impl.gradleProject.tasks.find { it.name == 'rootTask' }
+    }
+
+    def "offline model should not resolve external dependencies"() {
+        def projectDir = dist.testDir
+
+        projectDir.file('build.gradle').text = """
+subprojects {
+    apply plugin: 'java'
+}
+
+project(':impl') {
+    apply plugin: 'idea'
+
+    dependencies {
+        compile project(':api')
+        testCompile 'i.dont:Exist:2.4'
+    }
+}
+"""
+        projectDir.file('settings.gradle').text = "include 'api', 'impl'"
+
+        when:
+        BasicIdeaProject project = withConnection { connection -> connection.getModel(BasicIdeaProject.class) }
+        def impl = project.children.find { it.name == 'impl' }
+
+        then:
+        def libs = impl.dependencies
+        libs.size() == 1
+        IdeaModuleDependency d = libs[0]
+        d.dependencyModule == project.modules.find { it.name == 'api' }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..8f16de2
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.BuildException
+import org.gradle.tooling.ProgressListener
+import org.gradle.tooling.model.GradleProject
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-5')
+class ToolingApiModelCrossVersionSpec extends ToolingApiSpecification {
+    def "receives progress while the model is building"() {
+        dist.testFile('build.gradle') << '''
+System.out.println 'this is stdout'
+System.err.println 'this is stderr'
+'''
+
+        def progressMessages = []
+
+        when:
+        withConnection { connection ->
+            def model = connection.model(GradleProject.class)
+            model.addProgressListener({ event -> progressMessages << event.description } as ProgressListener)
+            return model.get()
+        }
+
+        then:
+        progressMessages.size() >= 2
+        progressMessages.pop() == ''
+        progressMessages.every { it }
+    }
+
+    def "tooling api reports failure to build model"() {
+        dist.testFile('build.gradle') << 'broken'
+
+        when:
+        withConnection { connection ->
+            return connection.getModel(GradleProject.class)
+        }
+
+        then:
+        BuildException e = thrown()
+        e.message.startsWith('Could not fetch model of type \'GradleProject\' using Gradle')
+        e.cause.message.contains('A problem occurred evaluating root project')
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
new file mode 100644
index 0000000..3fb806d
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiReceivingStandardStreamsCrossVersionSpec.groovy
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 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.tooling.m5
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.GradleProject
+
+ at MinToolingApiVersion('1.0-milestone-5')
+ at MinTargetGradleVersion('1.0-milestone-5')
+class ToolingApiReceivingStandardStreamsCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //because embedded tooling api should not replace system out / err
+        //we will run below tests only for forked mode
+        toolingApi.isEmbedded = false
+    }
+
+    def "receives standard streams while the build is executing"() {
+        dist.testFile('build.gradle') << '''
+System.out.println 'this is stdout'
+System.err.println 'this is stderr'
+'''
+        def stdout = new ByteArrayOutputStream()
+        def stderr = new ByteArrayOutputStream()
+
+        when:
+        withConnection { connection ->
+            def build = connection.newBuild()
+            build.standardOutput = stdout
+            build.standardError = stderr
+            build.run()
+        }
+
+        then:
+        stdout.toString().contains('this is stdout')
+        stderr.toString().contains('this is stderr')
+    }
+
+    def "receives standard streams while the model is building"() {
+        dist.testFile('build.gradle') << '''
+System.out.println 'this is stdout'
+System.err.println 'this is stderr'
+'''
+
+        def stdout = new ByteArrayOutputStream()
+        def stderr = new ByteArrayOutputStream()
+
+        when:
+        withConnection { connection ->
+            def model = connection.model(GradleProject.class)
+            model.standardOutput = stdout
+            model.standardError = stderr
+            return model.get()
+        }
+
+        then:
+        stdout.toString().contains('this is stdout')
+        stderr.toString().contains('this is stderr')
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..cbfb1ff
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/BuildEnvironmentModelCrossVersionSpec.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class BuildEnvironmentModelCrossVersionSpec extends ToolingApiSpecification {
+
+    def "informs about build environment"() {
+        when:
+        BuildEnvironment model = withConnection { it.getModel(BuildEnvironment.class) }
+
+        then:
+        model.gradle.gradleVersion == targetDist.version
+        model.java.javaHome
+        !model.java.jvmArguments.empty
+    }
+
+    def "informs about java args as in the build script"() {
+        given:
+        toolingApi.isEmbedded = false //cannot be run in embedded mode
+
+        dist.file('build.gradle') <<
+            "project.description = java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.join('##')"
+
+        when:
+        BuildEnvironment env = withConnection { it.getModel(BuildEnvironment.class) }
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        def inputArgsInBuild = project.description.split('##') as List
+        env.java.jvmArguments.each { inputArgsInBuild.contains(it) }
+    }
+
+    def "informs about java home as in the build script"() {
+        given:
+        dist.file('build.gradle') << """
+        description = Jvm.current().javaHome.toString()
+        """
+
+        when:
+        BuildEnvironment env = withConnection { it.getModel(BuildEnvironment.class) }
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        env.java.javaHome.toString() == project.description
+    }
+
+    def "informs about gradle version as in the build script"() {
+        given:
+        dist.file('build.gradle') << "description = GradleVersion.current().getVersion()"
+
+        when:
+        BuildEnvironment env = withConnection { it.getModel(BuildEnvironment.class) }
+        GradleProject project = withConnection { it.getModel(GradleProject.class) }
+
+        then:
+        env.gradle.gradleVersion == project.description
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
new file mode 100644
index 0000000..1591c26
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ConsumingStandardInputCrossVersionSpec.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.model.GradleProject
+import spock.lang.Timeout
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class ConsumingStandardInputCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //since this test treats with standard input I will not run it for embedded daemon for safety.
+        toolingApi.isEmbedded = false
+    }
+
+    @Timeout(90)
+    def "consumes input when building model"() {
+        given:
+        dist.file('build.gradle')  << """
+description = System.in.text
+"""
+        when:
+        GradleProject model = (GradleProject) withConnection { ProjectConnection connection ->
+            def model = connection.model(GradleProject.class)
+            model.standardInput = new ByteArrayInputStream("Cool project".bytes)
+            model.get()
+        }
+
+        then:
+        model.description == 'Cool project'
+    }
+
+    @Timeout(90)
+    def "works well if the standard input configured with null"() {
+        given:
+        dist.file('build.gradle')  << """
+description = System.in.text
+"""
+        when:
+        GradleProject model = (GradleProject) withConnection { ProjectConnection connection ->
+            def model = connection.model(GradleProject.class)
+            model.standardInput = null
+            model.get()
+        }
+
+        then:
+        model.description == ""
+    }
+
+    @Timeout(90)
+    def "does not consume input when not explicitly provided"() {
+        given:
+        dist.file('build.gradle')  << """
+description = "empty" + System.in.text
+"""
+        when:
+        GradleProject model = (GradleProject) withConnection { ProjectConnection connection ->
+            def model = connection.model(GradleProject.class)
+            model.get()
+        }
+
+        then:
+        model.description == 'empty'
+    }
+
+    @Timeout(90)
+    def "consumes input when running tasks"() {
+        given:
+        dist.file('build.gradle') << """
+task createFile << {
+    file('input.txt') << System.in.text
+}
+"""
+        when:
+        withConnection { ProjectConnection connection ->
+            def build = connection.newBuild()
+            build.standardInput = new ByteArrayInputStream("Hello world!".bytes)
+            build.forTasks('createFile')
+            build.run()
+        }
+
+        then:
+        dist.file('input.txt').text == "Hello world!"
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
new file mode 100644
index 0000000..bc13fd3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TextUtil
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.build.BuildEnvironment
+import spock.lang.IgnoreIf
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class GradlePropertiesCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //this test does not make any sense in embedded mode
+        //as we don't own the process
+        toolingApi.isEmbedded = false
+    }
+
+    def "tooling api honours jvm args specified in gradle.properties"() {
+        dist.file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
+assert System.getProperty('some-prop') == 'some-value'
+"""
+        dist.file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
+
+        when:
+        BuildEnvironment env = toolingApi.withConnection { connection ->
+            connection.newBuild().run() //the assert
+            connection.getModel(BuildEnvironment.class)
+        }
+
+        then:
+        env.java.jvmArguments.contains('-Xmx16m')
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "tooling api honours java home specified in gradle.properties"() {
+        File javaHome = AvailableJavaHomes.bestAlternative
+        String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
+
+        dist.file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
+
+        dist.file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
+
+        when:
+        BuildEnvironment env = toolingApi.withConnection { connection ->
+            connection.newBuild().run() //the assert
+            connection.getModel(BuildEnvironment.class)
+        }
+
+        then:
+        env.java.javaHome == javaHome
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
new file mode 100644
index 0000000..8ec471f
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/JavaConfigurabilityCrossVersionSpec.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import spock.lang.Issue
+import spock.lang.Timeout
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //this test does not make any sense in embedded mode
+        //as we don't own the process
+        toolingApi.isEmbedded = false
+    }
+
+    def "configures the java settings"() {
+        when:
+        BuildEnvironment env = withConnection {
+            def model = it.model(BuildEnvironment.class)
+            model
+                .setJvmArguments("-Xmx333m", "-Xms13m")
+                .get()
+        }
+
+        then:
+        env.java.jvmArguments.contains "-Xms13m"
+        env.java.jvmArguments.contains "-Xmx333m"
+    }
+
+    def "uses sensible java defaults if nulls configured"() {
+        when:
+        BuildEnvironment env = withConnection {
+            def model = it.model(BuildEnvironment.class)
+            model
+                .setJvmArguments(null)
+                .get()
+        }
+
+        then:
+        env.java.javaHome
+        !env.java.jvmArguments.empty
+    }
+
+    def "tooling api provided jvm args take precedence over gradle.properties"() {
+        dist.file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx23m')
+assert System.getProperty('some-prop') == 'BBB'
+"""
+        dist.file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=AAA -Xmx16m"
+
+        when:
+        def model = withConnection {
+            it.model(GradleProject.class)
+                .setJvmArguments('-Dsome-prop=BBB', '-Xmx23m')
+                .get()
+        }
+
+        then:
+        model != null
+    }
+
+    def "customized java args are reflected in the inputArguments and the build model"() {
+        given:
+        dist.file('build.gradle') <<
+                "project.description = java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.join('##')"
+
+        when:
+        BuildEnvironment env
+        GradleProject project
+        withConnection {
+            env = it.model(BuildEnvironment.class).setJvmArguments('-Xmx200m', '-Xms100m').get()
+            project = it.model(GradleProject.class).setJvmArguments('-Xmx200m', '-Xms100m').get()
+        }
+
+        then:
+        def inputArgsInBuild = project.description.split('##') as List
+        env.java.jvmArguments.each { inputArgsInBuild.contains(it) }
+    }
+
+    @Issue("GRADLE-1799")
+    @Timeout(25)
+    def "promptly discovers when java does not exist"() {
+        when:
+        withConnection {
+            it.newBuild().setJavaHome(new File("i dont exist"))
+        }
+
+        then:
+        def ex = thrown(IllegalArgumentException)
+        ex.message.contains("i dont exist")
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/StrictLongRunningOperationCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/StrictLongRunningOperationCrossVersionSpec.groovy
new file mode 100644
index 0000000..c3f8ba3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/StrictLongRunningOperationCrossVersionSpec.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.tooling.fixture.MaxTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.tooling.model.internal.Exceptions
+import spock.lang.IgnoreIf
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-3')
+ at MaxTargetGradleVersion('1.0-milestone-7') //the configuration was not supported for old versions
+class StrictLongRunningOperationCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //this test does not make any sense in embedded mode
+        //as we don't own the process and we will attempt to configure system wide options.
+        toolingApi.isEmbedded = false
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "fails eagerly when java home unsupported for model"() {
+        def java = AvailableJavaHomes.bestAlternative
+        when:
+        Exception e = maybeFailWithConnection {
+            def model = it.model(BuildEnvironment.class)
+            model.setJavaHome(java)
+            model.get()
+        }
+
+        then:
+        assertExceptionInformative(e, "setJavaHome()")
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "fails eagerly when java home unsupported for build"() {
+        def java = AvailableJavaHomes.bestAlternative
+        when:
+        Exception e = maybeFailWithConnection {
+            def build = it.newBuild()
+            build.setJavaHome(java)
+            build.forTasks('tasks').run()
+        }
+
+        then:
+        assertExceptionInformative(e, "setJavaHome()")
+    }
+
+    def "fails eagerly when java args unsupported"() {
+        when:
+        Exception e = maybeFailWithConnection {
+            def model = it.model(BuildEnvironment.class)
+            model.setJvmArguments("-Xmx512m")
+            model.get()
+        }
+
+        then:
+        assertExceptionInformative(e, "setJvmArguments()")
+    }
+
+    def "fails eagerly when standard input unsupported"() {
+        when:
+        Exception e = maybeFailWithConnection {
+            def model = it.model(BuildEnvironment.class)
+            model.setStandardInput(new ByteArrayInputStream('yo!'.bytes))
+            model.get()
+        }
+
+        then:
+        assertExceptionInformative(e, "setStandardInput()")
+    }
+
+    void assertExceptionInformative(Exception actual, String expectedMessageSubstring) {
+        assert actual instanceof GradleConnectionException
+        assert actual instanceof UnsupportedOperationConfigurationException
+        assert !actual.message.contains(Exceptions.INCOMPATIBLE_VERSION_HINT) //no need for hint, the message is already good
+        assert actual.message.contains(expectedMessageSubstring)
+
+        //we don't really need that exception as a cause
+        //but one of the versions was released with it as a cause so to keep things compatible
+        assert actual.cause instanceof UnsupportedMethodException
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..50a0318
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiEclipseModelCrossVersionSpec.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.eclipse.EclipseProject
+
+ at MinToolingApiVersion('1.0-milestone-3')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class ToolingApiEclipseModelCrossVersionSpec extends ToolingApiSpecification {
+    def "can customise model late in the configuration phase"() {
+        def projectDir = dist.testDir
+        projectDir.file('build.gradle').text = """
+apply plugin: 'java'
+
+gradle.projectsEvaluated {
+    repositories { mavenCentral() }
+}
+dependencies {
+    compile 'commons-lang:commons-lang:2.5'
+}
+"""
+
+        when:
+        EclipseProject project = withConnection { it.getModel(EclipseProject.class) }
+
+        then:
+        project.classpath[0].file.name == 'commons-lang-2.5.jar'
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
new file mode 100644
index 0000000..03a8bc3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.internal.consumer.ConnectorServices
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class ToolingApiLoggingCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //for embedded tests we don't mess with global logging. Run with forks only.
+        toolingApi.isEmbedded = false
+        new ConnectorServices().reset()
+    }
+
+    def cleanup() {
+        new ConnectorServices().reset()
+    }
+
+    def "logs necessary information when verbose"() {
+        toolingApi.verboseLogging = true
+
+        dist.file("build.gradle") << """
+System.err.println "sys err logging xxx"
+
+println "println logging yyy"
+
+project.logger.error("error logging xxx");
+project.logger.warn("warn logging yyy");
+project.logger.lifecycle("lifecycle logging yyy");
+project.logger.quiet("quiet logging yyy");
+project.logger.info ("info logging yyy");
+project.logger.debug("debug logging yyy");
+"""
+        def output = new ByteArrayOutputStream()
+        def error = new ByteArrayOutputStream()
+        when:
+        withConnection {
+            it.newBuild()
+                .setStandardOutput(output)
+                .setStandardError(error)
+                .run()
+        }
+
+        then:
+        def out = output.toString()
+        out.count("debug logging yyy") == 1
+        out.count("info logging yyy") == 1
+        out.count("quiet logging yyy") == 1
+        out.count("lifecycle logging yyy") == 1
+        out.count("warn logging yyy") == 1
+        out.count("println logging yyy") == 1
+        out.count("error logging xxx") == 0
+
+        shouldNotContainProviderLogging(out)
+
+        def err = error.toString()
+        err.count("error logging") == 1
+        err.toString().count("sys err") == 1
+        err.toString().count("logging yyy") == 0
+
+        shouldNotContainProviderLogging(err)
+    }
+
+    def "logs necessary information"() {
+        toolingApi.verboseLogging = false
+
+        dist.file("build.gradle") << """
+System.err.println "sys err logging xxx"
+
+println "println logging yyy"
+
+project.logger.error("error logging xxx");
+project.logger.warn("warn logging yyy");
+project.logger.lifecycle("lifecycle logging yyy");
+project.logger.quiet("quiet logging yyy");
+project.logger.info ("info logging yyy");
+project.logger.debug("debug logging yyy");
+"""
+        def output = new ByteArrayOutputStream()
+        def error = new ByteArrayOutputStream()
+        when:
+        withConnection {
+            it.newBuild()
+                    .setStandardOutput(output)
+                    .setStandardError(error)
+                    .run()
+        }
+
+        then:
+        def out = output.toString()
+        out.count("debug logging yyy") == 0
+        out.count("info logging yyy") == 0
+        out.count("quiet logging yyy") == 1
+        out.count("lifecycle logging yyy") == 1
+        out.count("warn logging yyy") == 1
+        out.count("println logging yyy") == 1
+        out.count("error logging xxx") == 0
+
+        shouldNotContainProviderLogging(out)
+
+        def err = error.toString()
+        err.count("error logging") == 1
+        err.count("sys err") == 1
+        err.count("logging yyy") == 0
+
+        shouldNotContainProviderLogging(err)
+    }
+
+    void shouldNotContainProviderLogging(String output) {
+        assert !output.contains("Provider implementation created.")
+        assert !output.contains("Tooling API uses target gradle version:")
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/UnknownModelFeedbackCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/UnknownModelFeedbackCrossVersionSpec.groovy
new file mode 100644
index 0000000..5074015
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/UnknownModelFeedbackCrossVersionSpec.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.UnsupportedVersionException
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-3')
+class UnknownModelFeedbackCrossVersionSpec extends ToolingApiSpecification {
+
+    class UnknownModel {}
+
+    def "fails gracefully when building unknown model"() {
+        //this test exposes a daemon issue that appears in M5 and M6
+        //basically when we ask to build a model for a an unknown type for given provider
+        //the daemon breaks and does not return anything to the client making the client waiting forever.
+
+        when:
+        def e = maybeFailWithConnection { it.getModel(UnknownModel.class) }
+
+        then:
+        e instanceof UnsupportedVersionException
+        e instanceof UnknownModelException
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
new file mode 100644
index 0000000..eefd53c
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/VersionOnlyBuildEnvironmentCrossVersionSpec.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.tooling.m8
+
+import org.gradle.integtests.tooling.fixture.MaxTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.tooling.model.build.BuildEnvironment
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-3')
+ at MaxTargetGradleVersion('1.0-milestone-7')
+class VersionOnlyBuildEnvironmentCrossVersionSpec extends ToolingApiSpecification {
+
+    def "informs about version"() {
+        when:
+        BuildEnvironment model = withConnection { it.getModel(BuildEnvironment.class) }
+
+        then:
+        model.gradle.gradleVersion == targetDist.version
+    }
+
+    def "fails gracefully for other info"() {
+        given:
+        BuildEnvironment model = withConnection { it.getModel(BuildEnvironment.class) }
+
+        when:
+        model.java.javaHome
+        then:
+        def ex = thrown(UnsupportedMethodException)
+        ex instanceof UnsupportedOperationException //backwards compatibility
+
+        when:
+        model.java.jvmArguments
+        then:
+        def e = thrown(UnsupportedMethodException)
+        e instanceof UnsupportedOperationException //backwards compatibility
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy
new file mode 100644
index 0000000..6858744
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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.tooling.m9
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.GradleConnectionException
+import spock.lang.Issue
+import spock.lang.Timeout
+
+ at MinToolingApiVersion('1.0-milestone-9')
+ at MinTargetGradleVersion('1.0-milestone-9')
+class DaemonErrorFeedbackCrossVersionSpec extends ToolingApiSpecification {
+
+    @Issue("GRADLE-1799")
+    @Timeout(25)
+    def "promptly discovers rubbish jvm arguments"() {
+        //jvm arguments cannot be set for an existing process
+        //so we must not run in embedded mode
+        toolingApi.isEmbedded = false
+
+        when:
+        def ex = maybeFailWithConnection {
+            it.newBuild()
+                    .setJvmArguments("-Xasdf")
+                    .run()
+        }
+
+        then:
+        ex instanceof GradleConnectionException
+        ex.cause.message.contains "-Xasdf"
+        ex.cause.message.contains "Unable to start the daemon"
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy
new file mode 100644
index 0000000..1e033ff
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m9/M9JavaConfigurabilityCrossVersionSpec.groovy
@@ -0,0 +1,131 @@
+/*
+ * 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.integtests.tooling.m9
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TextUtil
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.GradleConnectionException
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.build.BuildEnvironment
+import org.gradle.util.GradleVersion
+import spock.lang.IgnoreIf
+import spock.lang.Issue
+import spock.lang.Timeout
+import org.gradle.util.Jvm
+
+ at MinToolingApiVersion('1.0-milestone-9')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class M9JavaConfigurabilityCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //this test does not make any sense in embedded mode
+        //as we don't own the process
+        toolingApi.isEmbedded = false
+    }
+
+    def "uses sensible java defaults if nulls configured"() {
+        when:
+        BuildEnvironment env = withConnection {
+            def model = it.model(BuildEnvironment.class)
+            model
+                    .setJvmArguments(null)
+                    .get()
+        }
+
+        then:
+        env.java.javaHome
+    }
+
+    @Issue("GRADLE-1799")
+    @Timeout(25)
+    def "promptly discovers when java is not a valid installation"() {
+        def dummyJdk = dist.file("wrong jdk location").createDir()
+
+        when:
+        def ex = maybeFailWithConnection {
+            it.newBuild().setJavaHome(dummyJdk).run()
+        }
+
+        then:
+        ex instanceof GradleConnectionException
+        ex.cause.message.contains "wrong jdk location"
+    }
+
+    def "uses defaults when a variant of empty jvm args requested"() {
+        when:
+        def env = withConnection {
+            it.model(BuildEnvironment.class).setJvmArguments(new String[0]).get()
+        }
+
+        def env2 = withConnection {
+            it.model(BuildEnvironment.class).setJvmArguments(null).get()
+        }
+
+        def env3 = withConnection {
+            it.model(BuildEnvironment.class).get()
+        }
+
+        then:
+        env.java.jvmArguments
+        env.java.jvmArguments == env2.java.jvmArguments
+        env.java.jvmArguments == env3.java.jvmArguments
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "customized java home is reflected in the java.home and the build model"() {
+        given:
+        dist.file('build.gradle') << "project.description = new File(System.getProperty('java.home')).canonicalPath"
+
+        when:
+        File javaHome = AvailableJavaHomes.bestAlternative
+        BuildEnvironment env
+        GradleProject project
+        withConnection {
+            env = it.model(BuildEnvironment.class).setJavaHome(javaHome).get()
+            project = it.model(GradleProject.class).setJavaHome(javaHome).get()
+        }
+
+        then:
+        project.description.startsWith(env.java.javaHome.canonicalPath)
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "tooling api provided java home takes precedence over gradle.properties"() {
+        File javaHome = AvailableJavaHomes.bestAlternative
+        String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
+        File otherJava = Jvm.current().getJavaHome()
+        String otherJavaPath = TextUtil.escapeString(otherJava.canonicalPath)
+        dist.file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
+        dist.file('gradle.properties') << "org.gradle.java.home=$otherJavaPath"
+
+        when:
+        def env = withConnection {
+            it.newBuild().setJavaHome(javaHome).run() //the assert
+            it.model(BuildEnvironment.class)
+                    .setJavaHome(javaHome)
+                    .get()
+        }
+
+        then:
+        env != null
+        env.java.javaHome == javaHome
+        env.java.javaHome != otherJava
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
new file mode 100644
index 0000000..bfbda07
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
@@ -0,0 +1,187 @@
+/*
+ * 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.integtests.tooling.rc1
+
+import org.gradle.integtests.tooling.fixture.ConfigurableOperation
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
+import org.gradle.tooling.model.GradleProject
+
+ at MinToolingApiVersion("1.0-rc-1")
+ at MinTargetGradleVersion("1.0-rc-2")
+class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecification {
+
+//    We don't want to validate *all* command line options here, just enough to make sure passing through works.
+
+    def "understands project properties for building model"() {
+        given:
+        toolingApi.verboseLogging = false //sanity check, see GRADLE-2226
+        dist.file("build.gradle") << """
+        description = project.getProperty('theDescription')
+"""
+
+        when:
+        GradleProject project = withConnection { ProjectConnection it ->
+            it.model(GradleProject).withArguments('-PtheDescription=heyJoe').get()
+        }
+
+        then:
+        project.description == 'heyJoe'
+    }
+
+    def "understands system properties"() {
+        given:
+        dist.file("build.gradle") << """
+        task printProperty << {
+            file('sysProperty.txt') << System.getProperty('sysProperty')
+        }
+"""
+
+        when:
+        withConnection { ProjectConnection it ->
+            it.newBuild().forTasks('printProperty').withArguments('-DsysProperty=welcomeToTheJungle').run()
+        }
+
+        then:
+        dist.file('sysProperty.txt').text.contains('welcomeToTheJungle')
+    }
+
+    def "can use custom build file"() {
+        given:
+        dist.file("foo.gradle") << """
+        task someCoolTask
+"""
+
+        when:
+        withConnection { ProjectConnection it ->
+            it.newBuild().forTasks('someCoolTask').withArguments('-b', 'foo.gradle').run()
+        }
+
+        then:
+        noExceptionThrown()
+
+    }
+
+    def "can use custom log level"() {
+        //logging infrastructure is not installed when running in-process to avoid issues
+        toolingApi.isEmbedded = false
+
+        given:
+        dist.file("build.gradle") << """
+        logger.debug("debugging stuff")
+        logger.info("infoing stuff")
+"""
+
+        when:
+        def debug = withConnection {
+            def build = it.newBuild().withArguments('-d')
+            def op = new ConfigurableOperation(build)
+            build.run()
+            op.standardOutput
+        }
+
+        and:
+        def info = withConnection {
+            def build = it.newBuild().withArguments('-i')
+            def op = new ConfigurableOperation(build)
+            build.run()
+            op.standardOutput
+        }
+
+        then:
+        debug.count("debugging stuff") == 1
+        debug.count("infoing stuff") == 1
+
+        and:
+        info.count("debugging stuff") == 0
+        info.count("infoing stuff") == 1
+    }
+
+    def "gives decent feedback for invalid option"() {
+        when:
+        def ex = maybeFailWithConnection { ProjectConnection it ->
+            it.newBuild().withArguments('--foreground').run()
+        }
+
+        then:
+        ex instanceof UnsupportedBuildArgumentException
+        ex.message.contains('--foreground')
+    }
+
+    def "can overwrite project dir via build arguments"() {
+        given:
+        dist.file('otherDir').createDir()
+        dist.file('build.gradle') << "assert projectDir.name.endsWith('otherDir')"
+
+        when:
+        withConnection { 
+            it.newBuild().withArguments('-p', 'otherDir').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can overwrite gradle user home via build arguments"() {
+        given:
+        dist.file('.myGradle').createDir()
+        dist.file('build.gradle') << "assert gradle.gradleUserHomeDir.name.endsWith('.myGradle')"
+
+        when:
+        withConnection {
+            it.newBuild().withArguments('-p', '.myGradle').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can overwrite searchUpwards via build arguments"() {
+        given:
+        dist.file('build.gradle') << "assert !gradle.startParameter.searchUpwards"
+
+        when:
+        toolingApi.withConnector { it.searchUpwards(true) }
+        withConnection {
+            it.newBuild().withArguments('-u').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can overwrite task names via build arguments"() {
+        given:
+        dist.file('build.gradle') << """
+task foo << { assert false }
+task bar << { assert true }
+"""
+
+        when:
+        withConnection {
+            it.newBuild().forTasks('foo').withArguments('bar').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
index 8d73f75..8d43b5d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
@@ -15,30 +15,62 @@
  */
 package org.gradle.tooling;
 
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
 import org.gradle.tooling.model.Task;
 
+import java.io.File;
+import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
- * <p>A {@code BuildLauncher} allows you to configure and execute a Gradle build.
- *
- * <p>You use a {@code BuildLauncher} as follows:
+ * A {@code BuildLauncher} allows you to configure and execute a Gradle build.
+ * <p>
+ * Instances of {@code BuildLauncher} are not thread-safe. You use a {@code BuildLauncher} as follows:
  *
  * <ul>
- *
  * <li>Create an instance of {@code BuildLauncher} by calling {@link org.gradle.tooling.ProjectConnection#newBuild()}.
- *
  * <li>Configure the launcher as appropriate.
- *
  * <li>Call either {@link #run()} or {@link #run(ResultHandler)} to execute the build.
- *
  * <li>Optionally, you can reuse the launcher to launcher additional builds.
- *
  * </ul>
  *
- * <p>Instances of {@code BuildLauncher} are not thread-safe.
+ * Example:
+ * <pre autoTested=''>
+ * ProjectConnection connection = GradleConnector.newConnector()
+ *    .forProjectDirectory(new File("someFolder"))
+ *    .connect();
+ *
+ * try {
+ *    BuildLauncher build = connection.newBuild();
+ *
+ *    //select tasks to run:
+ *    build.forTasks("clean", "test");
+ *
+ *    //include some build arguments:
+ *    build.withArguments("--no-search-upward", "-i", "--project-dir", "someProjectDir");
+ *
+ *    //configure the standard input:
+ *    build.setStandardInput(new ByteArrayInputStream("consume this!".getBytes()));
+ *
+ *    //in case you want the build to use java different than default:
+ *    build.setJavaHome(new File("/path/to/java"));
+ *
+ *    //if your build needs crazy amounts of memory:
+ *    build.setJvmArguments("-Xmx2048m", "-XX:MaxPermSize=512m");
+ *
+ *    //if you want to listen to the progress events:
+ *    ProgressListener listener = null; // use your implementation
+ *    build.addProgressListener(listener);
+ *
+ *    //kick the build off:
+ *    build.run();
+ * } finally {
+ *    connection.close();
+ * }
+ * </pre>
+ *
  */
-public interface BuildLauncher {
+public interface BuildLauncher extends LongRunningOperation {
     /**
      * Sets the tasks to be executed.
      *
@@ -64,26 +96,37 @@ public interface BuildLauncher {
     BuildLauncher forTasks(Iterable<? extends Task> tasks);
 
     /**
-     * Sets the {@link OutputStream} that should receive standard output logging from this build. The default is to discard the output.
-     *
-     * @param outputStream The output stream.
-     * @return this
+     * {@inheritDoc}
+     */
+    BuildLauncher withArguments(String ... arguments);
+
+    /**
+     * {@inheritDoc}
      */
     BuildLauncher setStandardOutput(OutputStream outputStream);
 
     /**
-     * Sets the {@link OutputStream} that should receive standard error logging from this build. The default is to discard the output.
-     *
-     * @param outputStream The output stream.
-     * @return this
+     * {@inheritDoc}
      */
     BuildLauncher setStandardError(OutputStream outputStream);
 
     /**
-     * Adds a progress listener which will receive progress events as the build executes.
-     *
-     * @param listener The listener
-     * @return this
+     * {@inheritDoc}
+     */
+    BuildLauncher setStandardInput(InputStream inputStream);
+
+    /**
+     * {@inheritDoc}
+     */
+    BuildLauncher setJavaHome(File javaHome);
+
+    /**
+     * {@inheritDoc}
+     */
+    BuildLauncher setJvmArguments(String... jvmArguments);
+
+    /**
+     * {@inheritDoc}
      */
     BuildLauncher addProgressListener(ProgressListener listener);
 
@@ -91,11 +134,17 @@ public interface BuildLauncher {
      * Execute the build, blocking until it is complete.
      *
      * @throws UnsupportedVersionException When the target Gradle version does not support the features required for this build.
+     * @throws org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
+     *          when you have configured the long running operation with a settings
+     *          like: {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
+     *          {@link #setJvmArguments(String...)} but those settings are not supported on the target Gradle.
      * @throws BuildException On some failure executing the Gradle build.
      * @throws GradleConnectionException On some other failure using the connection.
+     * @throws UnsupportedBuildArgumentException When there is a problem with build arguments provided by {@link #withArguments(String...)}
      * @throws IllegalStateException When the connection has been closed or is closing.
      */
-    void run() throws GradleConnectionException;
+    void run() throws GradleConnectionException, UnsupportedBuildArgumentException, IllegalStateException,
+            BuildException, UnsupportedVersionException;
 
     /**
      * Launchers the build. This method returns immediately, and the result is later passed to the given handler.
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnector.java
index 898b827..316928b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnector.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/GradleConnector.java
@@ -15,11 +15,7 @@
  */
 package org.gradle.tooling;
 
-import org.gradle.api.internal.project.ServiceRegistry;
-import org.gradle.tooling.internal.consumer.ConnectionFactory;
-import org.gradle.tooling.internal.consumer.ConnectorServiceRegistry;
-import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
-import org.gradle.tooling.internal.consumer.DistributionFactory;
+import org.gradle.tooling.internal.consumer.ConnectorServices;
 
 import java.io.File;
 import java.net.URI;
@@ -35,16 +31,27 @@ import java.net.URI;
  *
  * <li>Call {@link #connect()} to create the connection to a project.</li>
  *
- * <li>Optionally reuse the connector to create additional connections.</li>
- *
  * <li>When finished with the connection, call {@link ProjectConnection#close()} to clean up.</li>
  *
  * </ol>
  *
- * <p>{@code GradleConnector} instances are not thread-safe.</p>
+ * Example:
+ * <pre autoTested=''>
+ * ProjectConnection connection = GradleConnector.newConnector()
+ *    .forProjectDirectory(new File("someProjectFolder"))
+ *    .connect();
+ *
+ * try {
+ *    connection.newBuild().forTasks("tasks").run();
+ * } finally {
+ *    connection.close();
+ * }
+ * </pre>
+ *
+ * <p>{@code GradleConnector} instances are not thread-safe. If you want to use a {@code GradleConnector} concurrently you *must* always create a
+ * new instance for each thread using {@link #newConnector()}. Note, however, the {@link ProjectConnection} instances that a connector creates are completely thread-safe.</p>
  */
 public abstract class GradleConnector {
-    private static final ServiceRegistry SERVICES = new ConnectorServiceRegistry();
 
     /**
      * Creates a new connector instance.
@@ -52,7 +59,7 @@ public abstract class GradleConnector {
      * @return The instance. Never returns null.
      */
     public static GradleConnector newConnector() {
-        return new DefaultGradleConnector(SERVICES.get(ConnectionFactory.class), SERVICES.get(DistributionFactory.class));
+        return new ConnectorServices().createConnector();
     }
 
     /**
@@ -91,7 +98,7 @@ public abstract class GradleConnector {
     public abstract GradleConnector forProjectDirectory(File projectDir);
 
     /**
-     * Specifies the user's Gradle home directory to use. Defaults to {@code ~/.gradle}
+     * Specifies the user's Gradle home directory to use. Defaults to {@code ~/.gradle}.
      *
      * @param gradleUserHomeDir The user's Gradle home directory to use.
      * @return this
@@ -105,6 +112,6 @@ public abstract class GradleConnector {
      * @throws UnsupportedVersionException When the target Gradle version does not support this version of the tooling API.
      * @throws GradleConnectionException On failure to establish a connection with the target Gradle version.
      */
-    public abstract ProjectConnection connect() throws GradleConnectionException;
+    public abstract ProjectConnection connect() throws GradleConnectionException, UnsupportedVersionException;
 
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
new file mode 100644
index 0000000..456e217
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2011 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.tooling;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Offers ways to communicate both ways with a gradle operation, be it building a model or running tasks.
+ * <p>
+ * Enables tracking progress via listeners that will receive events from the gradle operation.
+ * <p>
+ * Allows providing standard output streams that will receive output if the gradle operation writes to standard streams.
+ * <p>
+ * Allows providing standard input that can be consumed by the gradle operation (useful for interactive builds).
+ * <p>
+ * Enables configuring the build run / model request with options like the java home or jvm arguments.
+ * Those settings might not be supported by the target Gradle version. Refer to javadoc for those methods
+ * to understand what kind of exception throw and when is it thrown.
+ */
+public interface LongRunningOperation {
+
+    /**
+     * Sets the {@link java.io.OutputStream} which should receive standard output logging generated while running the operation.
+     * The default is to discard the output.
+     *
+     * @param outputStream The output stream.
+     * @return this
+     */
+    LongRunningOperation setStandardOutput(OutputStream outputStream);
+
+    /**
+     * Sets the {@link OutputStream} which should receive standard error logging generated while running the operation.
+     * The default is to discard the output.
+     *
+     * @param outputStream The output stream.
+     * @return this
+     */
+    LongRunningOperation setStandardError(OutputStream outputStream);
+
+    /**
+     * If the target gradle version supports it you can use this setting
+     * to set the standard {@link java.io.InputStream} that will be used by builds.
+     * Useful when the tooling api drives interactive builds.
+     * <p>
+     * If the target gradle version does not support it the long running operation will fail eagerly with
+     * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
+     * <p>
+     * If not configured or null passed the dummy input stream with zero bytes is used to avoid the build hanging problems.
+     *
+     * @param inputStream The input stream
+     * @return this
+     * @since 1.0-milestone-8
+     */
+    LongRunningOperation setStandardInput(InputStream inputStream);
+
+    /**
+     * If the target gradle version supports it you can use this setting
+     * to specify the java home directory to use for the long running operation.
+     * <p>
+     * If the target gradle version does not support it the long running operation will fail eagerly with
+     * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
+     * <p>
+     * {@link org.gradle.tooling.model.build.BuildEnvironment} model contains information such as java or gradle environment.
+     * If you want to get hold of this information you can ask tooling API to build this model.
+     * <p>
+     * If not configured or null passed the sensible default will be used.
+     *
+     * @param javaHome to use for the gradle process
+     * @return this
+     * @since 1.0-milestone-8
+     * @throws IllegalArgumentException when supplied javaHome is not a valid folder.
+     */
+    LongRunningOperation setJavaHome(File javaHome) throws IllegalArgumentException;
+
+    /**
+     * If the target gradle version supports it you can use this setting
+     * to specify the java vm arguments to use for the long running operation.
+     * <p>
+     * If the target gradle version does not support it the long running operation will fail eagerly with
+     * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
+     * <p>
+     * {@link org.gradle.tooling.model.build.BuildEnvironment} model contains information such as java or gradle environment.
+     * If you want to get hold of this information you can ask tooling API to build this model.
+     * <p>
+     * If not configured, null an empty array passed then the reasonable default will be used.
+     *
+     * @param jvmArguments to use for the gradle process
+     * @return this
+     * @since 1.0-milestone-9
+     */
+    LongRunningOperation setJvmArguments(String... jvmArguments);
+
+    /**
+     * Specify the command line build arguments. Useful mostly for running tasks via {@link BuildLauncher}.
+     * <p>
+     * Be aware that not all of the Gradle command line options are supported!
+     * Only the build arguments that configure the build execution are supported.
+     * They are modelled in the Gradle API via {@link org.gradle.StartParameter}.
+     * Examples of supported build arguments: '--info', '-u', '-p'.
+     * The command line instructions that are actually separate commands (like '-?', '-v') are not supported.
+     * Some other instructions like '--daemon' are also not supported - the tooling API always runs with the daemon.
+     * <p>
+     * If you specify unknown or unsupported command line option the {@link org.gradle.tooling.exceptions.UnsupportedBuildArgumentException}
+     * will be thrown but only at the time when you execute the operation, i.e. {@link BuildLauncher#run()} or {@link ModelBuilder#get()}.
+     * <p>
+     * For the list of all Gradle command line options please refer to the user guide
+     * or take a look at the output of the 'gradle -?' command. Supported arguments are those modelled by
+     * {@link org.gradle.StartParameter}.
+     * <p>
+     * The arguments can potentially override some other settings you have configured.
+     * For example, the project directory or Gradle user home directory that are configured
+     * in the {@link GradleConnector}.
+     * Also, the task names configured by {@link BuildLauncher#forTasks(String...)} can be overridden
+     * if you happen to specify other tasks via the build arguments.
+     * <p>
+     * See the example in the docs for {@link BuildLauncher}
+     *
+     * @param arguments gradle command line arguments
+     * @return this
+     * @since 1.0-rc-1
+     */
+    LongRunningOperation withArguments(String ... arguments);
+
+    /**
+     * Adds a progress listener which will receive progress events as the operation runs.
+     *
+     * @param listener The listener
+     * @return this
+     */
+    LongRunningOperation addProgressListener(ProgressListener listener);
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ModelBuilder.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ModelBuilder.java
index 7eb1bdb..db39e83 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ModelBuilder.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ModelBuilder.java
@@ -15,53 +15,90 @@
  */
 package org.gradle.tooling;
 
-import org.gradle.tooling.model.Project;
+import org.gradle.tooling.model.Model;
 
+import java.io.File;
+import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
- * <p>A {@code ModelBuilder} allows you to fetch a snapshot of the model for a project.
- *
- * <p>You use a {@code ModelBuilder} as follows:
+ * A {@code ModelBuilder} allows you to fetch a snapshot of the model for a project.
+ * Instances of {@code ModelBuilder} are not thread-safe.
+ * <p>
+ * You use a {@code ModelBuilder} as follows:
  *
  * <ul>
- *
  * <li>Create an instance of {@code ModelBuilder} by calling {@link org.gradle.tooling.ProjectConnection#model(Class)}.
- *
  * <li>Configure the builder as appropriate.
- *
  * <li>Call either {@link #get()} or {@link #get(ResultHandler)} to build the model.
- *
  * <li>Optionally, you can reuse the builder to build the model multiple times.
- *
  * </ul>
  *
- * <p>Instances of {@code ModelBuilder} are not thread-safe.
+ * Example:
+ * <pre autoTested=''>
+ * ProjectConnection connection = GradleConnector.newConnector()
+ *    .forProjectDirectory(new File("someFolder"))
+ *    .connect();
+ *
+ * try {
+ *    ModelBuilder<GradleProject> builder = connection.model(GradleProject.class);
+ *
+ *    //if you use a different than usual build file name:
+ *    builder.withArguments("--build-file", "theBuild.gradle");
+ *
+ *    //configure the standard input in case your build is interactive:
+ *    builder.setStandardInput(new ByteArrayInputStream("consume this!".getBytes()));
+ *
+ *    //if you want to listen to the progress events:
+ *    ProgressListener listener = null; // use your implementation
+ *    builder.addProgressListener(listener);
+ *
+ *    //get the model:
+ *    GradleProject project = builder.get();
+ *
+ *    //query the model for information:
+ *    System.out.println("Available tasks: " + project.getTasks());
+ * } finally {
+ *    connection.close();
+ * }
+ * </pre>
  *
  * @param <T> The type of model to build
  */
-public interface ModelBuilder<T extends Project> {
+public interface ModelBuilder<T extends Model> extends LongRunningOperation {
+
     /**
-     * Sets the {@link OutputStream} which should receive standard output logging generated while building the model. The default is to discard the output.
-     *
-     * @param outputStream The output stream.
-     * @return this
+     * {@inheritDoc}
+     */
+    ModelBuilder<T> withArguments(String ... arguments);
+
+    /**
+     * {@inheritDoc}
      */
     ModelBuilder<T> setStandardOutput(OutputStream outputStream);
 
     /**
-     * Sets the {@link OutputStream} which should receive standard error logging generated while building the model. The default is to discard the output.
-     *
-     * @param outputStream The output stream.
-     * @return this
+     * {@inheritDoc}
      */
     ModelBuilder<T> setStandardError(OutputStream outputStream);
 
     /**
-     * Adds a progress listener which will receive progress events as the model is being built.
-     *
-     * @param listener The listener
-     * @return this
+     * {@inheritDoc}
+     */
+    ModelBuilder<T> setStandardInput(InputStream inputStream);
+
+    /**
+     * {@inheritDoc}
+     */
+    ModelBuilder<T> setJavaHome(File javaHome);
+
+    /**
+     * {@inheritDoc}
+     */
+    ModelBuilder<T> setJvmArguments(String... jvmArguments);
+
+    /**
+     * {@inheritDoc}
      */
     ModelBuilder<T> addProgressListener(ProgressListener listener);
 
@@ -70,6 +107,10 @@ public interface ModelBuilder<T extends Project> {
      *
      * @return The model.
      * @throws UnsupportedVersionException When the target Gradle version does not support the features required to build this model.
+     * @throws org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
+     *          when you have configured the long running operation with a settings
+     *          like: {@link #setStandardInput(java.io.InputStream)}, {@link #setJavaHome(java.io.File)},
+     *          {@link #setJvmArguments(String...)} but those settings are not supported on the target Gradle.
      * @throws BuildException On some failure executing the Gradle build.
      * @throws GradleConnectionException On some other failure using the connection.
      * @throws IllegalStateException When the connection has been closed or is closing.
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java
index 02cf9e7..7c60dc3 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ProjectConnection.java
@@ -15,16 +15,37 @@
  */
 package org.gradle.tooling;
 
-import org.gradle.tooling.model.Project;
+import org.gradle.tooling.model.Model;
 
 /**
- * Represents a long-lived connection to a Gradle project.
+ * Represents a long-lived connection to a Gradle project. You obtain an instance of a {@code ProjectConnection} by using {@link org.gradle.tooling.GradleConnector#connect()}.
  *
- * <h2>Thread safety</h2>
+ * <pre autoTested=''>
+ * ProjectConnection connection = GradleConnector.newConnector()
+ *    .forProjectDirectory(new File("someFolder"))
+ *    .connect();
  *
- * <p>All implementations of {@code GradleConnection} are thread-safe, and may be shared by any number of threads.</p>
+ * try {
+ *    //obtain some information from the build
+ *    BuildEnvironment environment = connection.model(BuildEnvironment.class)
+ *      .get();
  *
- * <p>All notifications from a given {@code GradleConnection} instance are delivered by a single thread at a time. Note, however, that the delivery thread may change over time.</p>
+ *    //run some tasks
+ *    connection.newBuild()
+ *      .forTasks("tasks")
+ *      .setStandardOutput(System.out)
+ *      .run();
+ *
+ * } finally {
+ *    connection.close();
+ * }
+ * </pre>
+ *
+ * <h3>Thread safety information</h3>
+ *
+ * <p>All implementations of {@code ProjectConnection} are thread-safe, and may be shared by any number of threads.</p>
+ *
+ * <p>All notifications from a given {@code ProjectConnection} instance are delivered by a single thread at a time. Note, however, that the delivery thread may change over time.</p>
  */
 public interface ProjectConnection {
     /**
@@ -36,11 +57,14 @@ public interface ProjectConnection {
      * @param <T> The model type.
      * @return The model.
      * @throws UnsupportedVersionException When the target Gradle version does not support the given model.
+     * @throws UnknownModelException When you are building a model unknown to the Tooling API,
+     *  for example you attempt to build a model of a type does not come from the Tooling API.
      * @throws BuildException On some failure executing the Gradle build, in order to build the model.
      * @throws GradleConnectionException On some other failure using the connection.
      * @throws IllegalStateException When this connection has been closed or is closing.
      */
-    <T extends Project> T getModel(Class<T> viewType) throws GradleConnectionException;
+    <T extends Model> T getModel(Class<T> viewType) throws UnsupportedVersionException,
+            UnknownModelException, BuildException, GradleConnectionException, IllegalStateException;
 
     /**
      * Fetches a snapshot of the model for this project asynchronously. This method return immediately, and the result of the operation is passed to the supplied result handler.
@@ -49,8 +73,10 @@ public interface ProjectConnection {
      * @param handler The handler to pass the result to.
      * @param <T> The model type.
      * @throws IllegalStateException When this connection has been closed or is closing.
+     * @throws UnknownModelException When you are building a model unknown to the Tooling API,
+     *  for example you attempt to build a model of a type does not come from the Tooling API.
      */
-    <T extends Project> void getModel(Class<T> viewType, ResultHandler<? super T> handler) throws IllegalStateException;
+    <T extends Model> void getModel(Class<T> viewType, ResultHandler<? super T> handler) throws IllegalStateException, UnknownModelException;
 
     /**
      * Creates a launcher which can be used to execute a build.
@@ -65,8 +91,10 @@ public interface ProjectConnection {
      * @param modelType The model type
      * @param <T> The model type.
      * @return The builder.
+     * @throws UnknownModelException When you are building a model unknown to the Tooling API,
+     *  for example you attempt to build a model of a type does not come from the Tooling API.
      */
-    <T extends Project> ModelBuilder<T> model(Class<T> modelType);
+    <T extends Model> ModelBuilder<T> model(Class<T> modelType) throws UnknownModelException;
 
     /**
      * Closes this connection. Blocks until any pending operations are complete. Once this method has returned, no more notifications will be delivered by any threads.
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ResultHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ResultHandler.java
index 1e3e660..0fa363b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/ResultHandler.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/ResultHandler.java
@@ -21,7 +21,20 @@ package org.gradle.tooling;
  * @param <T> The result type.
  */
 public interface ResultHandler<T> {
+
+    /**
+     * Handles successful completion of the operation.
+     *
+     * @param result the result
+     */
     void onComplete(T result);
 
+    /**
+     * Handles failures. A failure happens when the target Gradle version does not support
+     * the features required to build this model. For example, when you have configured the long running operation with a settings
+     *  like: {@link LongRunningOperation#setStandardInput(java.io.InputStream)}, {@link LongRunningOperation#setJavaHome(java.io.File)}, {@link LongRunningOperation#setJvmArguments(String...)}
+     *  but those settings are not supported on the target Gradle.
+     * @param failure the failure
+     */
     void onFailure(GradleConnectionException failure);
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java
new file mode 100644
index 0000000..7739fb3
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnknownModelException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.tooling;
+
+/**
+ * Thrown when the client is trying to acquire a model that is unknown to the Tooling API.
+ * <p>
+ * The exception extends {@link UnsupportedVersionException} only for backwards compatibility reasons.
+ *
+ * @since 1.0-milestone-8
+ */
+public class UnknownModelException extends UnsupportedVersionException {
+
+    public UnknownModelException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java
index 346a60a..069fa5c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/UnsupportedVersionException.java
@@ -22,4 +22,8 @@ public class UnsupportedVersionException extends GradleConnectionException {
     public UnsupportedVersionException(String message) {
         super(message);
     }
+
+    public UnsupportedVersionException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java
new file mode 100644
index 0000000..007934d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedBuildArgumentException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.tooling.exceptions;
+
+import org.gradle.tooling.GradleConnectionException;
+
+/**
+ * Thrown when the {@link org.gradle.tooling.BuildLauncher} has been configured
+ * with unsupported build arguments. For more information see docs for
+ * {@link org.gradle.tooling.BuildLauncher#withArguments(String...)} method.
+ */
+public class UnsupportedBuildArgumentException extends GradleConnectionException {
+    public UnsupportedBuildArgumentException(String message) {
+        super(message);
+    }
+
+    public UnsupportedBuildArgumentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java
new file mode 100644
index 0000000..a6a2bd9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.tooling.exceptions;
+
+import org.gradle.tooling.UnsupportedVersionException;
+
+/**
+ * Thrown when the {@link org.gradle.tooling.LongRunningOperation} has been configured
+ * with unsupported settings. For example {@link org.gradle.tooling.LongRunningOperation#setJavaHome(java.io.File)}
+ * might not be supported by the target gradle version.
+ *
+ * @since 1.0-rc-1
+ */
+public class UnsupportedOperationConfigurationException extends UnsupportedVersionException {
+    public UnsupportedOperationConfigurationException(String message) {
+        super(message);
+    }
+
+    public UnsupportedOperationConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/package-info.java
new file mode 100644
index 0000000..f1a5acf
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Exceptions thrown when using the tooling API.
+ */
+package org.gradle.tooling.exceptions;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseProject.java
deleted file mode 100644
index 5c89da4..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseProject.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal;
-
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.List;
-
-public class DefaultEclipseProject implements EclipseProjectVersion3, Serializable {
-    private final String name;
-    private final String path;
-    private EclipseProjectVersion3 parent;
-    private List<ExternalDependencyVersion1> classpath;
-    private final List<EclipseProjectVersion3> children;
-    private List<EclipseSourceDirectoryVersion1> sourceDirectories;
-    private List<EclipseProjectDependencyVersion2> projectDependencies;
-    private final String description;
-    private final File projectDirectory;
-    private Iterable<? extends EclipseTaskVersion1> tasks;
-
-    public DefaultEclipseProject(String name, String path, String description, File projectDirectory, Iterable<? extends EclipseProjectVersion3> children) {
-        this.name = name;
-        this.path = path;
-        this.description = description;
-        this.projectDirectory = projectDirectory;
-        this.tasks = Collections.emptyList();
-        this.children = GUtil.addLists(children);
-        this.classpath = Collections.emptyList();
-        this.sourceDirectories = Collections.emptyList();
-        this.projectDependencies = Collections.emptyList();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("project '%s'", path);
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public EclipseProjectVersion3 getParent() {
-        return parent;
-    }
-
-    public File getProjectDirectory() {
-        return projectDirectory;
-    }
-
-    public void setParent(EclipseProjectVersion3 parent) {
-        this.parent = parent;
-    }
-
-    public List<EclipseProjectVersion3> getChildren() {
-        return children;
-    }
-
-    public Iterable<? extends EclipseSourceDirectoryVersion1> getSourceDirectories() {
-        return sourceDirectories;
-    }
-
-    public void setSourceDirectories(List<EclipseSourceDirectoryVersion1> sourceDirectories) {
-        this.sourceDirectories = sourceDirectories;
-    }
-
-    public Iterable<? extends EclipseProjectDependencyVersion2> getProjectDependencies() {
-        return projectDependencies;
-    }
-
-    public void setProjectDependencies(List<EclipseProjectDependencyVersion2> projectDependencies) {
-        this.projectDependencies = projectDependencies;
-    }
-
-    public List<ExternalDependencyVersion1> getClasspath() {
-        return classpath;
-    }
-    public void setClasspath(List<ExternalDependencyVersion1> classpath) {
-        this.classpath = classpath;
-    }
-
-    public Iterable<? extends EclipseTaskVersion1> getTasks() {
-        return tasks;
-    }
-
-    public void setTasks(Iterable<? extends EclipseTaskVersion1> tasks) {
-        this.tasks = tasks;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseProjectDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseProjectDependency.java
deleted file mode 100644
index 54ff2d3..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseProjectDependency.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
-import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
-
-import java.io.Serializable;
-
-public class DefaultEclipseProjectDependency implements EclipseProjectDependencyVersion2, Serializable {
-    private final String path;
-    private final HierarchicalEclipseProjectVersion1 target;
-
-    public DefaultEclipseProjectDependency(String path, HierarchicalEclipseProjectVersion1 target) {
-        this.target = target;
-        this.path = path;
-    }
-
-    public HierarchicalEclipseProjectVersion1 getTargetProject() {
-        return target;
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("project dependency %s (%s)", path, target);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseSourceDirectory.java
deleted file mode 100644
index daca1c2..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultEclipseSourceDirectory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class DefaultEclipseSourceDirectory implements EclipseSourceDirectoryVersion1, Serializable {
-    private final String path;
-    private final File directory;
-
-    public DefaultEclipseSourceDirectory(String path, File directory) {
-        this.path = path;
-        this.directory = directory;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("source directory '%s'", path);
-    }
-
-    public File getDirectory() {
-        return directory;
-    }
-
-    public String getPath() {
-        return path;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultExternalDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultExternalDependency.java
deleted file mode 100644
index dad304e..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultExternalDependency.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal;
-
-import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
-
-import java.io.File;
-import java.io.Serializable;
-
-public class DefaultExternalDependency implements ExternalDependencyVersion1, Serializable {
-    private final File file;
-    private final File javadoc;
-    private final File source;
-
-    public DefaultExternalDependency(File file, File javadoc, File source) {
-        this.file = file;
-        this.javadoc = javadoc;
-        this.source = source;
-    }
-
-    public File getFile() {
-        return file;
-    }
-
-    public File getJavadoc() {
-        return javadoc;
-    }
-
-    public File getSource() {
-        return source;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultTask.java
deleted file mode 100644
index b17b093..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/DefaultTask.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal;
-
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
-
-import java.io.Serializable;
-
-public class DefaultTask implements EclipseTaskVersion1, Serializable {
-    private final EclipseProjectVersion3 project;
-    private final String path;
-    private final String name;
-    private final String description;
-
-    public DefaultTask(EclipseProjectVersion3 project, String path, String name, String description) {
-        this.project = project;
-        this.path = path;
-        this.name = name;
-        this.description = description;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("task '%s'", path);
-    }
-
-    public EclipseProjectVersion3 getProject() {
-        return project;
-    }
-
-    public String getPath() {
-        return path;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java
new file mode 100644
index 0000000..03f354f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/DefaultBuildEnvironment.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.tooling.internal.build;
+
+import org.gradle.tooling.internal.protocol.InternalBuildEnvironment;
+import org.gradle.tooling.model.build.BuildEnvironment;
+import org.gradle.tooling.model.build.GradleEnvironment;
+import org.gradle.tooling.model.build.JavaEnvironment;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 12/17/11
+ */
+public class DefaultBuildEnvironment implements BuildEnvironment, InternalBuildEnvironment, Serializable {
+
+    private final String gradleVersion;
+    private final File javaHome;
+    private final List<String> jvmArguments;
+
+    public DefaultBuildEnvironment(String gradleVersion, File javaHome, List<String> jvmArguments) {
+        this.gradleVersion = gradleVersion;
+        this.javaHome = javaHome;
+        this.jvmArguments = jvmArguments;
+    }
+
+    public GradleEnvironment getGradle() {
+        return new GradleEnvironment() {
+            public String getGradleVersion() {
+                return gradleVersion;
+            }
+        };
+    }
+
+    public JavaEnvironment getJava() {
+        return new JavaEnvironment() {
+            public File getJavaHome() {
+                return javaHome;
+            }
+
+            public List<String> getJvmArguments() {
+                return jvmArguments;
+            }
+        };
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java
new file mode 100644
index 0000000..4925d81
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/build/VersionOnlyBuildEnvironment.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.tooling.internal.build;
+
+import org.gradle.tooling.model.build.JavaEnvironment;
+import org.gradle.tooling.model.internal.Exceptions;
+
+/**
+ * by Szczepan Faber, created at: 12/22/11
+ */
+public class VersionOnlyBuildEnvironment extends DefaultBuildEnvironment {
+
+    public VersionOnlyBuildEnvironment(String gradleVersion) {
+        super(gradleVersion, null, null);
+    }
+
+    public JavaEnvironment getJava() {
+        throw Exceptions.unsupportedMethod("getJava()");
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AbstractLongRunningOperation.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AbstractLongRunningOperation.java
deleted file mode 100644
index 275cc5f..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AbstractLongRunningOperation.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.ProgressListener;
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
-import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
-
-import java.io.File;
-import java.io.OutputStream;
-import java.util.concurrent.TimeUnit;
-
-public class AbstractLongRunningOperation {
-    private OutputStream stdout;
-    private OutputStream stderr;
-    private final ProgressListenerAdapter progressListener = new ProgressListenerAdapter();
-    private final ConnectionParameters parameters;
-
-    public AbstractLongRunningOperation(ConnectionParameters parameters) {
-        this.parameters = parameters;
-    }
-
-    public AbstractLongRunningOperation setStandardOutput(OutputStream outputStream) {
-        stdout = outputStream;
-        return this;
-    }
-
-    public AbstractLongRunningOperation setStandardError(OutputStream outputStream) {
-        stderr = outputStream;
-        return this;
-    }
-
-    public AbstractLongRunningOperation addProgressListener(ProgressListener listener) {
-        progressListener.add(listener);
-        return this;
-    }
-
-    protected BuildOperationParametersVersion1 operationParameters() {
-        return new OperationParameters();
-    }
-
-    private class OperationParameters implements BuildOperationParametersVersion1 {
-        long startTime = System.currentTimeMillis();
-
-        public long getStartTime() {
-            return startTime;
-        }
-
-        public File getGradleUserHomeDir() {
-            return parameters.getGradleUserHomeDir();
-        }
-
-        public File getProjectDir() {
-            return parameters.getProjectDir();
-        }
-
-        public Boolean isSearchUpwards() {
-            return parameters.isSearchUpwards();
-        }
-
-        public Boolean isEmbedded() {
-            return parameters.isEmbedded();
-        }
-
-        public TimeUnit getDaemonMaxIdleTimeUnits() {
-            return parameters.getDaemonMaxIdleTimeUnits();
-        }
-
-        public Integer getDaemonMaxIdleTimeValue() {
-            return parameters.getDaemonMaxIdleTimeValue();
-        }
-
-        public OutputStream getStandardOutput() {
-            return stdout;
-        }
-
-        public OutputStream getStandardError() {
-            return stderr;
-        }
-
-        public ProgressListenerVersion1 getProgressListener() {
-            return progressListener;
-        }
-    }
-
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AsyncConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AsyncConnection.java
deleted file mode 100644
index 2c73999..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/AsyncConnection.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.internal.protocol.*;
-
-public interface AsyncConnection {
-    void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters, ResultHandlerVersion1<? super Void> handler) throws IllegalStateException;
-
-    void getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters, ResultHandlerVersion1<? super ProjectVersion3> handler) throws UnsupportedOperationException, IllegalStateException;
-
-    void stop();
-
-    String getDisplayName();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java
index 6ec582f..516c3ef 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/BlockingResultHandler.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.tooling.internal.consumer;
 
+import org.gradle.internal.UncheckedException;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.ResultHandler;
-import org.gradle.util.UncheckedException;
 
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
@@ -36,11 +36,11 @@ class BlockingResultHandler<T> implements ResultHandler<T> {
         try {
             result = queue.take();
         } catch (InterruptedException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         if (result instanceof Throwable) {
-            throw UncheckedException.asUncheckedException((Throwable) result);
+            throw UncheckedException.throwAsUncheckedException((Throwable) result);
         }
         if (result == NULL) {
             return null;
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/CachingToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/CachingToolingImplementationLoader.java
deleted file mode 100644
index 676c8ed..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/CachingToolingImplementationLoader.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.internal.protocol.ConnectionVersion4;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-public class CachingToolingImplementationLoader implements ToolingImplementationLoader {
-    private final ToolingImplementationLoader loader;
-    private final Map<Set<File>, ConnectionVersion4> connections = new HashMap<Set<File>, ConnectionVersion4>();
-
-    public CachingToolingImplementationLoader(ToolingImplementationLoader loader) {
-        this.loader = loader;
-    }
-
-    public ConnectionVersion4 create(Distribution distribution) {
-        Set<File> classpath = new LinkedHashSet<File>(distribution.getToolingImplementationClasspath());
-        ConnectionVersion4 connection = connections.get(classpath);
-        if (connection == null) {
-            connection = loader.create(distribution);
-            connections.put(classpath, connection);
-        }
-
-        return connection;
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
index e8f7220..a60807d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
@@ -15,33 +15,36 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.messaging.concurrent.DefaultExecutorFactory;
 import org.gradle.tooling.ProjectConnection;
-import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.internal.consumer.async.AsyncConnection;
+import org.gradle.tooling.internal.consumer.async.DefaultAsyncConnection;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+import org.gradle.tooling.internal.consumer.connection.LazyConnection;
+import org.gradle.tooling.internal.consumer.connection.LoggingInitializerConnection;
+import org.gradle.tooling.internal.consumer.connection.ProgressLoggingConnection;
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
+import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
 
-/**
- * This is the main internal entry point for the tooling API.
- *
- * This implementation is thread-safe.
- */
 public class ConnectionFactory {
     private final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter();
     private final ToolingImplementationLoader toolingImplementationLoader;
     private final DefaultExecutorFactory executorFactory = new DefaultExecutorFactory();
-    private final ListenerManager listenerManager;
-    private final ProgressLoggerFactory progressLoggerFactory;
 
-    public ConnectionFactory(ToolingImplementationLoader toolingImplementationLoader, ListenerManager listenerManager, ProgressLoggerFactory progressLoggerFactory) {
+    public ConnectionFactory(ToolingImplementationLoader toolingImplementationLoader) {
         this.toolingImplementationLoader = toolingImplementationLoader;
-        this.listenerManager = listenerManager;
-        this.progressLoggerFactory = progressLoggerFactory;
     }
 
     public ProjectConnection create(Distribution distribution, ConnectionParameters parameters) {
-        ConnectionVersion4 connection = new ProgressLoggingConnection(new LazyConnection(distribution, toolingImplementationLoader), progressLoggerFactory, listenerManager);
-        AsyncConnection asyncConnection = new DefaultAsyncConnection(connection, executorFactory);
+        SynchronizedLogging synchronizedLogging = new SynchronizedLogging();
+        ConsumerConnection lazyConnection = new LazyConnection(distribution, toolingImplementationLoader, synchronizedLogging, parameters.getVerboseLogging());
+        ConsumerConnection progressLoggingConnection = new ProgressLoggingConnection(lazyConnection, synchronizedLogging);
+        ConsumerConnection initializingConnection = new LoggingInitializerConnection(progressLoggingConnection, synchronizedLogging);
+        AsyncConnection asyncConnection = new DefaultAsyncConnection(initializingConnection, executorFactory);
         return new DefaultProjectConnection(asyncConnection, adapter, parameters);
     }
+
+    ToolingImplementationLoader getToolingImplementationLoader() {
+        return toolingImplementationLoader;
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java
index accf502..a8dea70 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionParameters.java
@@ -36,4 +36,9 @@ public interface ConnectionParameters {
     Integer getDaemonMaxIdleTimeValue();
 
     TimeUnit getDaemonMaxIdleTimeUnits();
+
+    /**
+     * Whether to log debug statements eagerly
+     */
+    boolean getVerboseLogging();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServiceRegistry.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServiceRegistry.java
deleted file mode 100644
index c4ad4b3..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServiceRegistry.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.StartParameter;
-import org.gradle.api.internal.project.DefaultServiceRegistry;
-import org.gradle.listener.DefaultListenerManager;
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.logging.internal.DefaultProgressLoggerFactory;
-import org.gradle.logging.internal.ProgressListener;
-import org.gradle.util.TrueTimeProvider;
-
-public class ConnectorServiceRegistry extends DefaultServiceRegistry {
-    protected ListenerManager createListenerManager() {
-        return new DefaultListenerManager();
-    }
-
-    protected ProgressLoggerFactory createProgressLoggerFactory() {
-        return new DefaultProgressLoggerFactory(get(ListenerManager.class).getBroadcaster(ProgressListener.class), new TrueTimeProvider());
-    }
-
-    protected ToolingImplementationLoader createToolingImplementationLoader() {
-        return new CachingToolingImplementationLoader(new DefaultToolingImplementationLoader());
-    }
-
-    protected ConnectionFactory createConnectionFactory() {
-        return new ConnectionFactory(get(ToolingImplementationLoader.class), get(ListenerManager.class), get(ProgressLoggerFactory.class));
-    }
-
-    protected DistributionFactory createDistributionFactory() {
-        return new DistributionFactory(StartParameter.DEFAULT_GRADLE_USER_HOME, get(ProgressLoggerFactory.class));
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
new file mode 100644
index 0000000..0eefc17
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.concurrent.SynchronizedServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.tooling.internal.consumer.loader.CachingToolingImplementationLoader;
+import org.gradle.tooling.internal.consumer.loader.DefaultToolingImplementationLoader;
+import org.gradle.tooling.internal.consumer.loader.SynchronizedToolingImplementationLoader;
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
+
+/**
+ * by Szczepan Faber, created at: 12/6/11
+ */
+public class ConnectorServices {
+
+    private static ServiceRegistry singletonRegistry = new SynchronizedServiceRegistry(new ConnectorServiceRegistry());
+
+    public DefaultGradleConnector createConnector() {
+        ConnectionFactory connectionFactory = new ConnectionFactory(singletonRegistry.get(ToolingImplementationLoader.class));
+        return new DefaultGradleConnector(connectionFactory, new DistributionFactory(StartParameter.DEFAULT_GRADLE_USER_HOME));
+    }
+
+    /**
+     * Resets the state of connector services. Meant to be used only for testing!
+     */
+    public void reset() {
+        singletonRegistry = new SynchronizedServiceRegistry(new ConnectorServiceRegistry());
+    }
+
+    private static class ConnectorServiceRegistry extends DefaultServiceRegistry {
+        protected ToolingImplementationLoader createToolingImplementationLoader() {
+            return new SynchronizedToolingImplementationLoader(new CachingToolingImplementationLoader(new DefaultToolingImplementationLoader()));
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultAsyncConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultAsyncConnection.java
deleted file mode 100644
index c064fcb..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultAsyncConnection.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.tooling.internal.protocol.*;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Adapts a {@link ConnectionVersion4} to an {@link AsyncConnection}.
- */
-class DefaultAsyncConnection implements AsyncConnection {
-    private final ConnectionVersion4 connection;
-    private final StoppableExecutor executor;
-    private final AtomicBoolean closed = new AtomicBoolean();
-
-    public DefaultAsyncConnection(ConnectionVersion4 connection, ExecutorFactory executorFactory) {
-        this.connection = connection;
-        executor = executorFactory.create("Connection worker");
-    }
-
-    public String getDisplayName() {
-        return connection.getMetaData().getDisplayName();
-    }
-
-    public void executeBuild(final BuildParametersVersion1 buildParameters, final BuildOperationParametersVersion1 operationParameters, ResultHandlerVersion1<? super Void> handler) throws IllegalStateException {
-        runLater(handler, new ConnectionAction<Void>() {
-            public Void run() {
-                connection.executeBuild(buildParameters, operationParameters);
-                return null;
-            }
-        });
-    }
-
-    public void getModel(final Class<? extends ProjectVersion3> type, final BuildOperationParametersVersion1 operationParameters, ResultHandlerVersion1<? super ProjectVersion3> handler) throws UnsupportedOperationException, IllegalStateException {
-        runLater(handler, new ConnectionAction<ProjectVersion3>() {
-            public ProjectVersion3 run() {
-                return connection.getModel(type, operationParameters);
-            }
-        });
-    }
-
-    public void stop() {
-        closed.set(true);
-        executor.stop();
-        connection.stop();
-    }
-
-    private <T> void runLater(final ResultHandlerVersion1<? super T> handler, final ConnectionAction<T> action) {
-        onStartOperation();
-
-        executor.execute(new Runnable() {
-            public void run() {
-                T result;
-                try {
-                    result = action.run();
-                } catch (Throwable t) {
-                    handler.onFailure(t);
-                    return;
-                }
-                handler.onComplete(result);
-            }
-        });
-    }
-
-    private void onStartOperation() {
-        if (closed.get()) {
-            throw new IllegalStateException("This connection has been closed.");
-        }
-    }
-
-    private interface ConnectionAction<T> {
-        T run();
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildLauncher.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildLauncher.java
index be49b9c..a1b5ac9 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildLauncher.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultBuildLauncher.java
@@ -18,20 +18,25 @@ package org.gradle.tooling.internal.consumer;
 import org.gradle.tooling.BuildLauncher;
 import org.gradle.tooling.ProgressListener;
 import org.gradle.tooling.ResultHandler;
+import org.gradle.tooling.internal.consumer.async.AsyncConnection;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
 import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
 import org.gradle.tooling.model.Task;
 
+import java.io.File;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-class DefaultBuildLauncher extends AbstractLongRunningOperation implements BuildLauncher {
+class DefaultBuildLauncher implements BuildLauncher {
     private final List<String> tasks = new ArrayList<String>();
     private final AsyncConnection connection;
+    private ConsumerOperationParameters operationParameters;
 
     public DefaultBuildLauncher(AsyncConnection connection, ConnectionParameters parameters) {
-        super(parameters);
+        operationParameters = new ConsumerOperationParameters(parameters);
         this.connection = connection;
     }
 
@@ -54,21 +59,38 @@ class DefaultBuildLauncher extends AbstractLongRunningOperation implements Build
         return this;
     }
 
-    @Override
+    public BuildLauncher withArguments(String... arguments) {
+        operationParameters.setArguments(arguments);
+        return this;
+    }
+
     public DefaultBuildLauncher setStandardError(OutputStream outputStream) {
-        super.setStandardError(outputStream);
+        operationParameters.setStandardError(outputStream);
         return this;
     }
 
-    @Override
     public DefaultBuildLauncher setStandardOutput(OutputStream outputStream) {
-        super.setStandardOutput(outputStream);
+        operationParameters.setStandardOutput(outputStream);
+        return this;
+    }
+
+    public DefaultBuildLauncher setStandardInput(InputStream inputStream) {
+        operationParameters.setStandardInput(inputStream);
+        return this;
+    }
+
+    public DefaultBuildLauncher setJavaHome(File javaHome) {
+        operationParameters.setJavaHome(javaHome);
+        return this;
+    }
+
+    public DefaultBuildLauncher setJvmArguments(String... jvmArguments) {
+        operationParameters.setJvmArguments(jvmArguments);
         return this;
     }
 
-    @Override
     public DefaultBuildLauncher addProgressListener(ProgressListener listener) {
-        super.addProgressListener(listener);
+        operationParameters.addProgressListener(listener);
         return this;
     }
 
@@ -79,7 +101,7 @@ class DefaultBuildLauncher extends AbstractLongRunningOperation implements Build
     }
 
     public void run(final ResultHandler<? super Void> handler) {
-        connection.executeBuild(new DefaultBuildParameters(), operationParameters(), new ResultHandlerAdapter<Void>(handler){
+        connection.executeBuild(new DefaultBuildParameters(), operationParameters, new ResultHandlerAdapter<Void>(handler){
             @Override
             protected String connectionFailureMessage(Throwable failure) {
                 return String.format("Could not execute build using %s.", connection.getDisplayName());
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java
index 1057844..344ad67 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultConnectionParameters.java
@@ -19,43 +19,67 @@ import java.io.File;
 import java.util.concurrent.TimeUnit;
 
 public class DefaultConnectionParameters implements ConnectionParameters {
-    private final File gradleUserHomeDir;
-    private final File projectDir;
-    private final Boolean searchUpwards;
-    private final Boolean embedded;
-    private final Integer daemonMaxIdleTimeValue;
-    private final TimeUnit daemonMaxIdleTimeUnits;
-
-    public DefaultConnectionParameters(File projectDir, File gradleUserHomeDir, Boolean searchUpwards, Boolean embedded, Integer daemonMaxIdleTimeValue, TimeUnit daemonMaxIdleTimeUnits) {
-        this.projectDir = projectDir;
-        this.gradleUserHomeDir = gradleUserHomeDir;
-        this.searchUpwards = searchUpwards;
-        this.embedded = embedded;
-        this.daemonMaxIdleTimeValue = daemonMaxIdleTimeValue;
-        this.daemonMaxIdleTimeUnits = daemonMaxIdleTimeUnits;
-    }
+    private File gradleUserHomeDir;
+    private File projectDir;
+    private Boolean searchUpwards;
+    private Boolean embedded;
+    private Integer daemonMaxIdleTimeValue;
+    private TimeUnit daemonMaxIdleTimeUnits;
+    private boolean verboseLogging;
 
     public File getGradleUserHomeDir() {
         return gradleUserHomeDir;
     }
 
+    public void setGradleUserHomeDir(File gradleUserHomeDir) {
+        this.gradleUserHomeDir = gradleUserHomeDir;
+    }
+
     public File getProjectDir() {
         return projectDir;
     }
 
+    public void setProjectDir(File projectDir) {
+        this.projectDir = projectDir;
+    }
+
     public Boolean isSearchUpwards() {
         return searchUpwards;
     }
 
+    public void setSearchUpwards(Boolean searchUpwards) {
+        this.searchUpwards = searchUpwards;
+    }
+
     public Boolean isEmbedded() {
         return embedded;
     }
 
+    public void setEmbedded(Boolean embedded) {
+        this.embedded = embedded;
+    }
+
     public Integer getDaemonMaxIdleTimeValue() {
         return daemonMaxIdleTimeValue;
     }
 
+    public void setDaemonMaxIdleTimeValue(Integer daemonMaxIdleTimeValue) {
+        this.daemonMaxIdleTimeValue = daemonMaxIdleTimeValue;
+    }
+
     public TimeUnit getDaemonMaxIdleTimeUnits() {
         return daemonMaxIdleTimeUnits;
     }
+
+    public void setDaemonMaxIdleTimeUnits(TimeUnit daemonMaxIdleTimeUnits) {
+        this.daemonMaxIdleTimeUnits = daemonMaxIdleTimeUnits;
+    }
+
+    public boolean getVerboseLogging() {
+        return verboseLogging;
+    }
+
+    public void setVerboseLogging(boolean verboseLogging) {
+        this.verboseLogging = verboseLogging;
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java
index 4cc8222..f414cac 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultGradleConnector.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.tooling.ProjectConnection;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
 import org.gradle.util.GradleVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -30,13 +30,9 @@ public class DefaultGradleConnector extends GradleConnector {
     private static final Logger LOGGER = LoggerFactory.getLogger(GradleConnector.class);
     private final ConnectionFactory connectionFactory;
     private final DistributionFactory distributionFactory;
-    private File projectDir;
-    private File gradleUserHomeDir;
     private Distribution distribution;
-    private Boolean searchUpwards;
-    private Boolean embedded;
-    private Integer daemonMaxIdleTimeValue;
-    private TimeUnit daemonMaxIdleTimeUnits;
+
+    private final DefaultConnectionParameters connectionParameters = new DefaultConnectionParameters();
 
     public DefaultGradleConnector(ConnectionFactory connectionFactory, DistributionFactory distributionFactory) {
         this.connectionFactory = connectionFactory;
@@ -63,42 +59,65 @@ public class DefaultGradleConnector extends GradleConnector {
         return this;
     }
 
+    public GradleConnector useDefaultDistribution() {
+        distribution = null;
+        return this;
+    }
+
     public GradleConnector forProjectDirectory(File projectDir) {
-        this.projectDir = projectDir;
+        connectionParameters.setProjectDir(projectDir);
         return this;
     }
 
     public GradleConnector useGradleUserHomeDir(File gradleUserHomeDir) {
-        this.gradleUserHomeDir = gradleUserHomeDir;
+        connectionParameters.setGradleUserHomeDir(gradleUserHomeDir);
         return this;
     }
 
     public GradleConnector searchUpwards(boolean searchUpwards) {
-        this.searchUpwards = searchUpwards;
+        connectionParameters.setSearchUpwards(searchUpwards);
         return this;
     }
 
     public GradleConnector embedded(boolean embedded) {
-        this.embedded = embedded;
+        connectionParameters.setEmbedded(embedded);
         return this;
     }
 
     public GradleConnector daemonMaxIdleTime(int timeoutValue, TimeUnit timeoutUnits) {
-        this.daemonMaxIdleTimeValue = timeoutValue;
-        this.daemonMaxIdleTimeUnits = timeoutUnits;
+        connectionParameters.setDaemonMaxIdleTimeValue(timeoutValue);
+        connectionParameters.setDaemonMaxIdleTimeUnits(timeoutUnits);
+        return this;
+    }
+
+    /**
+     * If true then debug log statements will be shown
+     *
+     * @param verboseLogging
+     * @return
+     */
+    public DefaultGradleConnector setVerboseLogging(boolean verboseLogging) {
+        connectionParameters.setVerboseLogging(verboseLogging);
         return this;
     }
 
     public ProjectConnection connect() throws GradleConnectionException {
         LOGGER.debug("Connecting from tooling API consumer version {}", GradleVersion.current().getVersion());
 
-        if (projectDir == null) {
+        if (connectionParameters.getProjectDir() == null) {
             throw new IllegalStateException("A project directory must be specified before creating a connection.");
         }
         if (distribution == null) {
-            distribution = distributionFactory.getDefaultDistribution(projectDir);
+            distribution = distributionFactory.getDefaultDistribution(connectionParameters.getProjectDir(), connectionParameters.isSearchUpwards() != null ? connectionParameters.isSearchUpwards() : true);
         }
-        return connectionFactory.create(distribution, new DefaultConnectionParameters(projectDir, gradleUserHomeDir, searchUpwards, embedded, daemonMaxIdleTimeValue, daemonMaxIdleTimeUnits));
+        return connectionFactory.create(distribution, connectionParameters);
     }
 
+    ConnectionFactory getConnectionFactory() {
+        return connectionFactory;
+    }
+
+    void setDistribution(Distribution distribution) {
+        this.distribution = distribution;
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultModelBuilder.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultModelBuilder.java
index 6a7b984..0de98f1 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultModelBuilder.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultModelBuilder.java
@@ -19,19 +19,27 @@ import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.ModelBuilder;
 import org.gradle.tooling.ProgressListener;
 import org.gradle.tooling.ResultHandler;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.tooling.model.Project;
+import org.gradle.tooling.internal.consumer.async.AsyncConnection;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.protocoladapter.ModelPropertyHandler;
+import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
+import org.gradle.tooling.model.Model;
+import org.gradle.tooling.model.UnsupportedMethodException;
+import org.gradle.tooling.model.internal.Exceptions;
 
+import java.io.File;
+import java.io.InputStream;
 import java.io.OutputStream;
 
-public class DefaultModelBuilder<T extends Project> extends AbstractLongRunningOperation implements ModelBuilder<T> {
+public class DefaultModelBuilder<T extends Model, P> implements ModelBuilder<T> {
     private final Class<T> modelType;
-    private final Class<? extends ProjectVersion3> protocolType;
+    private final Class<P> protocolType;
     private final AsyncConnection connection;
     private final ProtocolToModelAdapter adapter;
+    private ConsumerOperationParameters operationParameters;
 
-    public DefaultModelBuilder(Class<T> modelType, Class<? extends ProjectVersion3> protocolType, AsyncConnection connection, ProtocolToModelAdapter adapter, ConnectionParameters parameters) {
-        super(parameters);
+    public DefaultModelBuilder(Class<T> modelType, Class<P> protocolType, AsyncConnection connection, ProtocolToModelAdapter adapter, ConnectionParameters parameters) {
+        operationParameters = new ConsumerOperationParameters(parameters);
         this.modelType = modelType;
         this.protocolType = protocolType;
         this.connection = connection;
@@ -45,43 +53,65 @@ public class DefaultModelBuilder<T extends Project> extends AbstractLongRunningO
     }
 
     public void get(final ResultHandler<? super T> handler) throws IllegalStateException {
-        ResultHandler<ProjectVersion3> adaptingHandler = new ProtocolToModelAdaptingHandler(handler);
-        connection.getModel(protocolType, operationParameters(), new ResultHandlerAdapter<ProjectVersion3>(adaptingHandler) {
+        ResultHandler<P> adaptingHandler = new ProtocolToModelAdaptingHandler(handler);
+        connection.getModel(protocolType, operationParameters, new ResultHandlerAdapter<P>(adaptingHandler) {
             @Override
             protected String connectionFailureMessage(Throwable failure) {
-                return String.format("Could not fetch model of type '%s' using %s.", modelType.getSimpleName(), connection.getDisplayName());
+                String message = String.format("Could not fetch model of type '%s' using %s.", modelType.getSimpleName(), connection.getDisplayName());
+                if (!(failure instanceof UnsupportedMethodException)
+                        && failure instanceof UnsupportedOperationException) {
+                    message += "\n" + Exceptions.INCOMPATIBLE_VERSION_HINT;
+                }
+                return message;
             }
         });
     }
 
-    @Override
-    public DefaultModelBuilder<T> setStandardOutput(OutputStream outputStream) {
-        super.setStandardOutput(outputStream);
+    public DefaultModelBuilder<T, P> withArguments(String... arguments) {
+        operationParameters.setArguments(arguments);
         return this;
     }
 
-    @Override
-    public DefaultModelBuilder<T> setStandardError(OutputStream outputStream) {
-        super.setStandardError(outputStream);
+    public DefaultModelBuilder<T, P> setStandardOutput(OutputStream outputStream) {
+        operationParameters.setStandardOutput(outputStream);
         return this;
     }
 
-    @Override
-    public DefaultModelBuilder<T> addProgressListener(ProgressListener listener) {
-        super.addProgressListener(listener);
+    public DefaultModelBuilder<T, P> setStandardError(OutputStream outputStream) {
+        operationParameters.setStandardError(outputStream);
         return this;
     }
 
-    private class ProtocolToModelAdaptingHandler implements ResultHandler<ProjectVersion3> {
+    public DefaultModelBuilder<T, P> setStandardInput(InputStream inputStream) {
+        operationParameters.setStandardInput(inputStream);
+        return this;
+    }
+
+    public DefaultModelBuilder<T, P> setJavaHome(File javaHome) {
+        operationParameters.setJavaHome(javaHome);
+        return this;
+    }
+
+    public DefaultModelBuilder<T, P> setJvmArguments(String... jvmArguments) {
+        operationParameters.setJvmArguments(jvmArguments);
+        return this;
+    }
+
+    public DefaultModelBuilder<T, P> addProgressListener(ProgressListener listener) {
+        operationParameters.addProgressListener(listener);
+        return this;
+    }
+
+    private class ProtocolToModelAdaptingHandler implements ResultHandler<P> {
         private final ResultHandler<? super T> handler;
 
         public ProtocolToModelAdaptingHandler(ResultHandler<? super T> handler) {
             this.handler = handler;
         }
 
-        public void onComplete(ProjectVersion3 result) {
-            handler.onComplete(adapter.adapt(modelType, result));
-
+        public void onComplete(P result) {
+            ModelPropertyHandler propertyHandler = new ModelPropertyHandler(connection.getVersionDetails());
+            handler.onComplete(adapter.adapt(modelType, result, propertyHandler));
         }
 
         public void onFailure(GradleConnectionException failure) {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java
index 7ec2731..663ea7d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultProjectConnection.java
@@ -16,23 +16,14 @@
 package org.gradle.tooling.internal.consumer;
 
 import org.gradle.tooling.*;
-import org.gradle.tooling.internal.protocol.BuildableProjectVersion1;
-import org.gradle.tooling.internal.protocol.HierarchicalProjectVersion1;
-import org.gradle.tooling.internal.protocol.ProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
-import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
-import org.gradle.tooling.model.BuildableProject;
-import org.gradle.tooling.model.HierarchicalProject;
-import org.gradle.tooling.model.Project;
-import org.gradle.tooling.model.eclipse.EclipseProject;
-import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
-
-import java.util.HashMap;
-import java.util.Map;
+import org.gradle.tooling.internal.consumer.async.AsyncConnection;
+import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter;
+import org.gradle.tooling.internal.consumer.versioning.ModelMapping;
+import org.gradle.tooling.model.Model;
 
 class DefaultProjectConnection implements ProjectConnection {
     private final AsyncConnection connection;
-    private final Map<Class<? extends Project>, Class<? extends ProjectVersion3>> modelTypeMap = new HashMap<Class<? extends Project>, Class<? extends ProjectVersion3>>();
+    private final ModelMapping modelMapping = new ModelMapping();
     private ProtocolToModelAdapter adapter;
     private final ConnectionParameters parameters;
 
@@ -40,22 +31,17 @@ class DefaultProjectConnection implements ProjectConnection {
         this.connection = connection;
         this.parameters = parameters;
         this.adapter = adapter;
-        modelTypeMap.put(Project.class, ProjectVersion3.class);
-        modelTypeMap.put(BuildableProject.class, BuildableProjectVersion1.class);
-        modelTypeMap.put(HierarchicalProject.class, HierarchicalProjectVersion1.class);
-        modelTypeMap.put(HierarchicalEclipseProject.class, HierarchicalEclipseProjectVersion1.class);
-        modelTypeMap.put(EclipseProject.class, EclipseProjectVersion3.class);
     }
 
     public void close() {
         connection.stop();
     }
 
-    public <T extends Project> T getModel(Class<T> viewType) {
+    public <T extends Model> T getModel(Class<T> viewType) {
         return model(viewType).get();
     }
 
-    public <T extends Project> void getModel(final Class<T> viewType, final ResultHandler<? super T> handler) {
+    public <T extends Model> void getModel(final Class<T> viewType, final ResultHandler<? super T> handler) {
         model(viewType).get(handler);
     }
 
@@ -63,16 +49,19 @@ class DefaultProjectConnection implements ProjectConnection {
         return new DefaultBuildLauncher(connection, parameters);
     }
 
-    public <T extends Project> ModelBuilder<T> model(Class<T> modelType) {
-        return new DefaultModelBuilder<T>(modelType, mapToProtocol(modelType), connection, adapter, parameters);
+    public <T extends Model> ModelBuilder<T> model(Class<T> modelType) {
+        return new DefaultModelBuilder<T, Class>(modelType, mapToProtocol(modelType), connection, adapter, parameters);
     }
 
-    private Class<? extends ProjectVersion3> mapToProtocol(Class<? extends Project> viewType) {
-        Class<? extends ProjectVersion3> protocolViewType = modelTypeMap.get(viewType);
+    private Class mapToProtocol(Class<? extends Model> viewType) {
+        Class protocolViewType = modelMapping.getInternalType(viewType);
         if (protocolViewType == null) {
-            throw new UnsupportedVersionException(String.format("Model of type '%s' is not supported.", viewType.getSimpleName()));
+            throw new UnknownModelException(
+                    "Unknown model: '" + viewType.getSimpleName() + "'.\n"
+                        + "Most likely you are trying to acquire a model for a class that is not a valid Tooling API model class.\n"
+                        + "Review the documentation on the version of Tooling API you use to find out what models can be build."
+            );
         }
         return protocolViewType;
     }
-
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultToolingImplementationLoader.java
deleted file mode 100644
index f5670d5..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DefaultToolingImplementationLoader.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.GradleConnectionException;
-import org.gradle.tooling.UnsupportedVersionException;
-import org.gradle.tooling.internal.protocol.ConnectionVersion4;
-import org.gradle.util.FilteringClassLoader;
-import org.gradle.util.GFileUtils;
-import org.gradle.util.GradleVersion;
-import org.gradle.util.ObservableUrlClassLoader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class DefaultToolingImplementationLoader implements ToolingImplementationLoader {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultToolingImplementationLoader.class);
-    private final ClassLoader classLoader;
-
-    public DefaultToolingImplementationLoader() {
-        this(DefaultToolingImplementationLoader.class.getClassLoader());
-    }
-
-    DefaultToolingImplementationLoader(ClassLoader classLoader) {
-        this.classLoader = classLoader;
-    }
-
-    public ConnectionVersion4 create(Distribution distribution) {
-        LOGGER.debug("Using tooling provider from {}", distribution.getDisplayName());
-        ClassLoader classLoader = createImplementationClassLoader(distribution);
-        String implementationClassName = loadImplementationClassName(classLoader, distribution);
-        try {
-            return (ConnectionVersion4) classLoader.loadClass(implementationClassName).newInstance();
-        } catch (Throwable t) {
-            throw new GradleConnectionException(String.format("Could not create an instance of Tooling API implementation class '%s'.", implementationClassName), t);
-        }
-    }
-
-    private ClassLoader createImplementationClassLoader(Distribution distribution) {
-        Set<File> implementationClasspath = distribution.getToolingImplementationClasspath();
-        LOGGER.debug("Using tooling provider classpath: {}", implementationClasspath);
-        URL[] urls = GFileUtils.toURLArray(implementationClasspath);
-        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(classLoader);
-        filteringClassLoader.allowPackage("org.gradle.tooling.internal.protocol");
-        return new ObservableUrlClassLoader(filteringClassLoader, urls);
-    }
-
-    private String loadImplementationClassName(ClassLoader classLoader, Distribution distribution) {
-        try {
-            String resourceName = "META-INF/services/" + ConnectionVersion4.class.getName();
-            InputStream inputStream = classLoader.getResourceAsStream(resourceName);
-            if (inputStream == null) {
-                Matcher m = Pattern.compile("\\w+Version(\\d+)").matcher(ConnectionVersion4.class.getSimpleName());
-                m.matches();
-                String protocolVersion = m.group(1);
-                throw new UnsupportedVersionException(String.format("The specified %s is not supported by this tooling API version (%s, protocol version %s)", distribution.getDisplayName(), GradleVersion.current().getVersion(), protocolVersion));
-            }
-
-            try {
-                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    line = line.replaceAll("#.*", "").trim();
-                    if (line.length() > 0) {
-                        return line;
-                    }
-                }
-            } finally {
-                inputStream.close();
-            }
-            throw new UnsupportedOperationException(String.format("No implementation class specified in resource '%s'.", resourceName));
-        } catch (UnsupportedVersionException e) {
-            throw e;
-        } catch (Throwable t) {
-            throw new GradleConnectionException(String.format("Could not determine class name of Tooling API implementation."), t);
-        }
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
index 37504d6..2d0d179 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
@@ -15,11 +15,13 @@
  */
 package org.gradle.tooling.internal.consumer;
 
+import org.gradle.logging.ProgressLoggerFactory;
+
 import java.io.File;
 import java.util.Set;
 
 public interface Distribution {
     String getDisplayName();
 
-    Set<File> getToolingImplementationClasspath();
+    Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory);
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java
index d6ffae0..4d2873c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java
@@ -15,38 +15,37 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.initialization.layout.BuildLayout;
+import org.gradle.initialization.layout.BuildLayoutFactory;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.util.DistributionLocator;
 import org.gradle.util.GradleVersion;
-import org.gradle.util.UncheckedException;
 import org.gradle.wrapper.*;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
 public class DistributionFactory {
     private final File userHomeDir;
-    private final ProgressLoggerFactory progressLoggerFactory;
 
-    public DistributionFactory(File userHomeDir, ProgressLoggerFactory progressLoggerFactory) {
+    public DistributionFactory(File userHomeDir) {
         this.userHomeDir = userHomeDir;
-        this.progressLoggerFactory = progressLoggerFactory;
     }
 
     /**
      * Returns the default distribution to use for the specified project.
      */
-    public Distribution getDefaultDistribution(File projectDir) {
-        Wrapper wrapper = new Wrapper(projectDir);
+    public Distribution getDefaultDistribution(File projectDir, boolean searchUpwards) {
+        BuildLayout layout = new BuildLayoutFactory().getLayoutFor(projectDir, searchUpwards);
+        WrapperExecutor wrapper = WrapperExecutor.forProjectDirectory(layout.getRootDirectory(), System.out);
         if (wrapper.getDistribution() != null) {
-            return getDistribution(wrapper.getDistribution());
+            return new ZippedDistribution(wrapper.getConfiguration());
         }
         return getDownloadedDistribution(GradleVersion.current().getVersion());
     }
@@ -55,7 +54,8 @@ public class DistributionFactory {
      * Returns the distribution installed in the specified directory.
      */
     public Distribution getDistribution(File gradleHomeDir) {
-        return new InstalledDistribution(gradleHomeDir, String.format("Gradle installation '%s'", gradleHomeDir), String.format("Gradle installation directory '%s'", gradleHomeDir));
+        return new InstalledDistribution(gradleHomeDir, String.format("Gradle installation '%s'", gradleHomeDir),
+                String.format("Gradle installation directory '%s'", gradleHomeDir));
     }
 
     /**
@@ -69,7 +69,9 @@ public class DistributionFactory {
      * Returns the distribution at the given URI.
      */
     public Distribution getDistribution(URI gradleDistribution) {
-        return new ZippedDistribution(gradleDistribution);
+        WrapperConfiguration configuration = new WrapperConfiguration();
+        configuration.setDistribution(gradleDistribution);
+        return new ZippedDistribution(configuration);
     }
 
     /**
@@ -80,41 +82,36 @@ public class DistributionFactory {
     }
 
     private Distribution getDownloadedDistribution(String gradleVersion) {
-        URI distUri;
-        try {
-            distUri = new URI(new DistributionLocator().getDistributionFor(GradleVersion.version(gradleVersion)));
-        } catch (URISyntaxException e) {
-            throw UncheckedException.asUncheckedException(e);
-        }
+        URI distUri = new DistributionLocator().getDistributionFor(GradleVersion.version(gradleVersion));
         return getDistribution(distUri);
     }
 
     private class ZippedDistribution implements Distribution {
-        private final URI gradleDistribution;
         private InstalledDistribution installedDistribution;
+        private final WrapperConfiguration wrapperConfiguration;
 
-        private ZippedDistribution(URI gradleDistribution) {
-            this.gradleDistribution = gradleDistribution;
+        private ZippedDistribution(WrapperConfiguration wrapperConfiguration) {
+            this.wrapperConfiguration = wrapperConfiguration;
         }
 
         public String getDisplayName() {
-            return String.format("Gradle distribution '%s'", gradleDistribution);
+            return String.format("Gradle distribution '%s'", wrapperConfiguration.getDistribution());
         }
 
-        public Set<File> getToolingImplementationClasspath() {
+        public Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
             if (installedDistribution == null) {
                 File installDir;
                 try {
-                    Install install = new Install(false, false, new ProgressReportingDownload(progressLoggerFactory), new PathAssembler(userHomeDir));
-                    installDir = install.createDist(gradleDistribution, PathAssembler.GRADLE_USER_HOME_STRING, org.gradle.api.tasks.wrapper.Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, PathAssembler.GRADLE_USER_HOME_STRING, org.gradle.api.tasks.wrapper.Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME);
+                    Install install = new Install(new ProgressReportingDownload(progressLoggerFactory), new PathAssembler(userHomeDir));
+                    installDir = install.createDist(wrapperConfiguration);
                 } catch (FileNotFoundException e) {
                     throw new IllegalArgumentException(String.format("The specified %s does not exist.", getDisplayName()), e);
                 } catch (Exception e) {
-                    throw new GradleConnectionException(String.format("Could not install Gradle distribution from '%s'.", gradleDistribution), e);
+                    throw new GradleConnectionException(String.format("Could not install Gradle distribution from '%s'.", wrapperConfiguration.getDistribution()), e);
                 }
                 installedDistribution = new InstalledDistribution(installDir, getDisplayName(), getDisplayName());
             }
-            return installedDistribution.getToolingImplementationClasspath();
+            return installedDistribution.getToolingImplementationClasspath(progressLoggerFactory);
         }
     }
 
@@ -152,7 +149,18 @@ public class DistributionFactory {
             return displayName;
         }
 
-        public Set<File> getToolingImplementationClasspath() {
+        public Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+            ProgressLogger progressLogger = progressLoggerFactory.newOperation(DistributionFactory.class);
+            progressLogger.setDescription("Validate distribution");
+            progressLogger.started();
+            try {
+                return getToolingImpl();
+            } finally {
+                progressLogger.completed();
+            }
+        }
+
+        private Set<File> getToolingImpl() {
             if (!gradleHomeDir.exists()) {
                 throw new IllegalArgumentException(String.format("The specified %s does not exist.", locationDisplayName));
             }
@@ -178,9 +186,8 @@ public class DistributionFactory {
             return "Gradle classpath distribution";
         }
 
-        public Set<File> getToolingImplementationClasspath() {
-            DefaultClassPathProvider provider = new DefaultClassPathProvider();
-            return provider.findClassPath("GRADLE_RUNTIME");
+        public Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+            return new DefaultModuleRegistry().getFullClasspath();
         }
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LazyConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LazyConnection.java
deleted file mode 100644
index ffa0d58..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LazyConnection.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.internal.protocol.*;
-import org.gradle.util.UncheckedException;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A {@link ConnectionVersion4} implementation which creates the actual connection implementation on demand.
- */
-public class LazyConnection implements ConnectionVersion4 {
-    private final Distribution distribution;
-    private final ToolingImplementationLoader implementationLoader;
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private Set<Thread> executing = new HashSet<Thread>();
-    private boolean stopped;
-    private ConnectionVersion4 connection;
-
-    public LazyConnection(Distribution distribution, ToolingImplementationLoader implementationLoader) {
-        this.distribution = distribution;
-        this.implementationLoader = implementationLoader;
-    }
-
-    public void stop() {
-        ConnectionVersion4 connection = null;
-        lock.lock();
-        try {
-            stopped = true;
-            while (!executing.isEmpty()) {
-                try {
-                    condition.await();
-                } catch (InterruptedException e) {
-                    throw UncheckedException.asUncheckedException(e);
-                }
-            }
-            connection = this.connection;
-            this.connection = null;
-        } finally {
-            lock.unlock();
-        }
-        if (connection != null) {
-            connection.stop();
-        }
-    }
-
-    public ConnectionMetaDataVersion1 getMetaData() {
-        return new ConnectionMetaDataVersion1() {
-            public String getVersion() {
-                throw new UnsupportedOperationException();
-            }
-
-            public String getDisplayName() {
-                return distribution.getDisplayName();
-            }
-        };
-    }
-
-    public void executeBuild(final BuildParametersVersion1 buildParameters, final BuildOperationParametersVersion1 operationParameters) {
-        withConnection(new ConnectionAction<Object>() {
-            public Object run(ConnectionVersion4 connection) {
-                connection.executeBuild(buildParameters, operationParameters);
-                return null;
-            }
-        });
-    }
-
-    public ProjectVersion3 getModel(final Class<? extends ProjectVersion3> type, final BuildOperationParametersVersion1 operationParameters) {
-        return withConnection(new ConnectionAction<ProjectVersion3>() {
-            public ProjectVersion3 run(ConnectionVersion4 connection) {
-                return connection.getModel(type, operationParameters);
-            }
-        });
-    }
-
-    private <T> T withConnection(ConnectionAction<T> action) {
-        try {
-            ConnectionVersion4 connection = onStartAction();
-            return action.run(connection);
-        } finally {
-            onEndAction();
-        }
-    }
-
-    private ConnectionVersion4 onStartAction() {
-        lock.lock();
-        try {
-            if (stopped) {
-                throw new IllegalStateException("This connection has been stopped.");
-            }
-            executing.add(Thread.currentThread());
-            if (connection == null) {
-                // Hold the lock while creating the connection. Not generally good form.
-                // In this instance, blocks other threads from creating the connection at the same time
-                connection = implementationLoader.create(distribution);
-            }
-            return connection;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void onEndAction() {
-        lock.lock();
-        try {
-            executing.remove(Thread.currentThread());
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private interface ConnectionAction<T> {
-        T run(ConnectionVersion4 connection);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java
new file mode 100644
index 0000000..9bfb606
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/LoggingProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer;
+
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.ProgressLoggerFactory;
+
+/**
+ * by Szczepan Faber, created at: 12/14/11
+ */
+public interface LoggingProvider {
+    ListenerManager getListenerManager();
+    ProgressLoggerFactory getProgressLoggerFactory();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ModelProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ModelProvider.java
new file mode 100644
index 0000000..433671f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ModelProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer;
+
+import org.gradle.tooling.internal.build.VersionOnlyBuildEnvironment;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+import org.gradle.tooling.internal.consumer.converters.GradleProjectConverter;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.InternalBuildEnvironment;
+import org.gradle.tooling.internal.protocol.InternalGradleProject;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.model.GradleProject;
+
+/**
+ * by Szczepan Faber, created at: 12/21/11
+ */
+public class ModelProvider {
+
+    public <T> T provide(ConsumerConnection connection, Class<T> type, ConsumerOperationParameters operationParameters) {
+        VersionDetails version = connection.getVersionDetails();
+        if (type == InternalBuildEnvironment.class && !version.supportsCompleteBuildEnvironment()) {
+            //early versions of provider do not support BuildEnvironment model
+            //since we know the gradle version at least we can give back some result
+            VersionOnlyBuildEnvironment out = new VersionOnlyBuildEnvironment(version.getVersion());
+            return type.cast(out);
+        }
+        if (version.clientHangsOnEarlyDaemonFailure()) {
+            //those version require special handling because of the client hanging bug
+            //it is due to the exception handing on the daemon side in M5 and M6
+            if (version.isPostM6Model(type)) {
+                String message = String.format("I don't know how to build a model of type '%s'.", type.getSimpleName());
+                throw new UnsupportedOperationException(message);
+            }
+        }
+        if (type == InternalGradleProject.class && !version.supportsGradleProjectModel()) {
+            //we broke compatibility around M9 wrt getting the tasks of a project (issue GRADLE-1875)
+            //this patch enables getting gradle tasks for target gradle version pre M5
+            EclipseProjectVersion3 project = connection.getModel(EclipseProjectVersion3.class, operationParameters);
+            GradleProject gradleProject = new GradleProjectConverter().convert(project);
+            return (T) gradleProject;
+        }
+        return connection.getModel(type, operationParameters);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProgressListenerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProgressListenerAdapter.java
deleted file mode 100644
index 1f0d730..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProgressListenerAdapter.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.listener.ListenerBroadcast;
-import org.gradle.tooling.ProgressEvent;
-import org.gradle.tooling.ProgressListener;
-import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
-
-import java.util.LinkedList;
-
-class ProgressListenerAdapter implements ProgressListenerVersion1 {
-    private final ListenerBroadcast<ProgressListener> listeners = new ListenerBroadcast<ProgressListener>(ProgressListener.class);
-    private final LinkedList<String> stack = new LinkedList<String>();
-
-    public void onOperationStart(final String description) {
-        stack.addFirst(description == null ? "" : description);
-        fireChangeEvent();
-    }
-
-    public void onOperationEnd() {
-        stack.removeFirst();
-        fireChangeEvent();
-    }
-
-    private void fireChangeEvent() {
-        final String description = stack.isEmpty() ? "" : stack.getFirst();
-        listeners.getSource().statusChanged(new ProgressEvent() {
-            public String getDescription() {
-                return description;
-            }
-        });
-    }
-
-    public void add(ProgressListener listener) {
-        listeners.add(listener);
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProgressLoggingConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProgressLoggingConnection.java
deleted file mode 100644
index ba59d9a..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProgressLoggingConnection.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.listener.ListenerManager;
-import org.gradle.logging.ProgressLogger;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.logging.internal.ProgressCompleteEvent;
-import org.gradle.logging.internal.ProgressEvent;
-import org.gradle.logging.internal.ProgressListener;
-import org.gradle.logging.internal.ProgressStartEvent;
-import org.gradle.tooling.internal.protocol.*;
-
-/**
- * A {@link ConnectionVersion4} implementation which provides some high-level progress information.
- */
-public class ProgressLoggingConnection implements ConnectionVersion4 {
-    private final ConnectionVersion4 connection;
-    private final ProgressLoggerFactory progressLoggerFactory;
-    private final ListenerManager listenerManager;
-
-    public ProgressLoggingConnection(ConnectionVersion4 connection, ProgressLoggerFactory progressLoggerFactory, ListenerManager listenerManager) {
-        this.connection = connection;
-        this.progressLoggerFactory = progressLoggerFactory;
-        this.listenerManager = listenerManager;
-    }
-
-    public void stop() {
-        connection.stop();
-    }
-
-    public ConnectionMetaDataVersion1 getMetaData() {
-        return connection.getMetaData();
-    }
-
-    public void executeBuild(final BuildParametersVersion1 buildParameters, final BuildOperationParametersVersion1 operationParameters) {
-        run("Execute build", operationParameters, new BuildAction<Void>() {
-            public Void run(ConnectionVersion4 connection) {
-                connection.executeBuild(buildParameters, operationParameters);
-                return null;
-            }
-        });
-    }
-
-    public ProjectVersion3 getModel(final Class<? extends ProjectVersion3> type, final BuildOperationParametersVersion1 operationParameters) {
-        return run("Load projects", operationParameters, new BuildAction<ProjectVersion3>() {
-            public ProjectVersion3 run(ConnectionVersion4 connection) {
-                return connection.getModel(type, operationParameters);
-            }
-        });
-    }
-
-    private <T> T run(String description, BuildOperationParametersVersion1 parameters, BuildAction<T> action) {
-        ProgressListenerAdapter listener = new ProgressListenerAdapter(parameters.getProgressListener());
-        listenerManager.addListener(listener);
-        try {
-            ProgressLogger progressLogger = progressLoggerFactory.newOperation(ProgressLoggingConnection.class);
-            progressLogger.setDescription(description);
-            progressLogger.started();
-            try {
-                return action.run(connection);
-            } finally {
-                progressLogger.completed();
-            }
-        } finally {
-            listenerManager.removeListener(listener);
-        }
-    }
-
-    private interface BuildAction<T> {
-        T run(ConnectionVersion4 connection);
-    }
-
-    private static class ProgressListenerAdapter implements ProgressListener {
-        private final ProgressListenerVersion1 progressListener;
-
-        public ProgressListenerAdapter(ProgressListenerVersion1 progressListener) {
-            this.progressListener = progressListener;
-        }
-
-        public void started(ProgressStartEvent event) {
-            progressListener.onOperationStart(event.getDescription());
-        }
-
-        public void progress(ProgressEvent event) {
-        }
-
-        public void completed(ProgressCompleteEvent event) {
-            progressListener.onOperationEnd();
-        }
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProtocolToModelAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProtocolToModelAdapter.java
deleted file mode 100644
index 612e274..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ProtocolToModelAdapter.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
-import org.gradle.util.UncheckedException;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-public class ProtocolToModelAdapter {
-    public <T, S> T adapt(Class<T> targetType, S protocolObject) {
-        return targetType.cast(Proxy.newProxyInstance(targetType.getClassLoader(), new Class<?>[]{targetType}, new InvocationHandlerImpl(protocolObject)));
-    }
-
-    private class InvocationHandlerImpl implements InvocationHandler {
-        private final Object delegate;
-        private final Map<Method, Method> methods = new HashMap<Method, Method>();
-        private final Map<String, Object> properties = new HashMap<String, Object>();
-        private final Method equalsMethod;
-        private final Method hashCodeMethod;
-
-        public InvocationHandlerImpl(Object delegate) {
-            this.delegate = delegate;
-            try {
-                equalsMethod = Object.class.getMethod("equals", Object.class);
-                hashCodeMethod = Object.class.getMethod("hashCode");
-            } catch (NoSuchMethodException e) {
-                throw UncheckedException.asUncheckedException(e);
-            }
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (o == null || o.getClass() != getClass()) {
-                return false;
-            }
-
-            InvocationHandlerImpl other = (InvocationHandlerImpl) o;
-            return delegate.equals(other.delegate);
-        }
-
-        @Override
-        public int hashCode() {
-            return delegate.hashCode();
-        }
-
-        public Object invoke(Object target, Method method, Object[] params) throws Throwable {
-            if (method.equals(equalsMethod)) {
-                Object param = params[0];
-                if (param == null || !Proxy.isProxyClass(param.getClass())) {
-                    return false;
-                }
-                InvocationHandler other = Proxy.getInvocationHandler(param);
-                return equals(other);
-            } else if (method.equals(hashCodeMethod)) {
-                return hashCode();
-            }
-
-            if (method.getName().matches("get\\w+")) {
-                if (properties.containsKey(method.getName())) {
-                    return properties.get(method.getName());
-                }
-                Object value = doInvokeMethod(method, params);
-                properties.put(method.getName(), value);
-                return value;
-            }
-
-            return doInvokeMethod(method, params);
-        }
-
-        private Object doInvokeMethod(Method method, Object[] params) throws Throwable {
-            Method targetMethod = methods.get(method);
-            if (targetMethod == null) {
-                targetMethod = findMethod(method);
-                methods.put(method, targetMethod);
-            }
-
-            Object returnValue;
-            try {
-                returnValue = targetMethod.invoke(delegate, params);
-            } catch (InvocationTargetException e) {
-                throw e.getCause();
-            }
-
-            if (returnValue == null || method.getReturnType().isInstance(returnValue)) {
-                return returnValue;
-            }
-
-            return convert(returnValue, method.getGenericReturnType());
-        }
-
-        private Method findMethod(Method method) {
-            Method match;
-            try {
-                match = delegate.getClass().getMethod(method.getName(), method.getParameterTypes());
-            } catch (NoSuchMethodException e) {
-                throw new UnsupportedOperationException(String.format("Cannot map method %s.%s() to target object of type %s.", method.getDeclaringClass().getSimpleName(), method.getName(), delegate.getClass().getSimpleName()), e);
-            }
-
-            LinkedList<Class<?>> queue = new LinkedList<Class<?>>();
-            queue.add(delegate.getClass());
-            while (!queue.isEmpty()) {
-                Class<?> c = queue.removeFirst();
-                try {
-                    match = c.getMethod(method.getName(), method.getParameterTypes());
-                } catch (NoSuchMethodException e) {
-                    // ignore
-                }
-                for (Class<?> interfaceType : c.getInterfaces()) {
-                    queue.addFirst(interfaceType);
-                }
-                if (c.getSuperclass() !=null) {
-                    queue.addFirst(c.getSuperclass());
-                }
-            }
-            match.setAccessible(true);
-            return match;
-        }
-
-        private Object convert(Object value, Type targetType) {
-            if (targetType instanceof ParameterizedType) {
-                ParameterizedType parameterizedTargetType = (ParameterizedType) targetType;
-                if (parameterizedTargetType.getRawType().equals(DomainObjectSet.class)) {
-                    Type targetElementType = getElementType(parameterizedTargetType);
-                    List<Object> convertedElements = new ArrayList<Object>();
-                    for (Object element : (Iterable) value) {
-                        convertedElements.add(convert(element, targetElementType));
-                    }
-                    return new ImmutableDomainObjectSet(convertedElements);
-                }
-            }
-            if (targetType instanceof Class) {
-                return adapt((Class) targetType, value);
-            }
-            throw new UnsupportedOperationException(String.format("Cannot convert object of %s to %s.", value.getClass(), targetType));
-        }
-
-        private Type getElementType(ParameterizedType type) {
-            Type elementType = type.getActualTypeArguments()[0];
-            if (elementType instanceof WildcardType) {
-                WildcardType wildcardType = (WildcardType) elementType;
-                return wildcardType.getUpperBounds()[0];
-            }
-            return elementType;
-        }
-    }
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java
index 413893f..071df79 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ResultHandlerAdapter.java
@@ -18,8 +18,11 @@ package org.gradle.tooling.internal.consumer;
 import org.gradle.tooling.BuildException;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.ResultHandler;
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException;
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
 import org.gradle.tooling.internal.protocol.ResultHandlerVersion1;
+import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
 
 /**
  * Adapts a {@link ResultHandler} to a {@link ResultHandlerVersion1}.
@@ -38,7 +41,13 @@ abstract class ResultHandlerAdapter<T> implements ResultHandlerVersion1<T> {
     }
 
     public void onFailure(Throwable failure) {
-        if (failure instanceof GradleConnectionException) {
+        if (failure instanceof InternalUnsupportedBuildArgumentException) {
+            handler.onFailure(new UnsupportedBuildArgumentException(connectionFailureMessage(failure)
+                    + "\n" + failure.getMessage(), failure));
+        } else if (failure instanceof UnsupportedOperationConfigurationException) {
+            handler.onFailure(new UnsupportedOperationConfigurationException(connectionFailureMessage(failure)
+                    + "\n" + failure.getMessage(), failure.getCause()));
+        } else if (failure instanceof GradleConnectionException) {
             handler.onFailure((GradleConnectionException) failure);
         } else if (failure instanceof BuildExceptionVersion1) {
             handler.onFailure(new BuildException(connectionFailureMessage(failure), failure.getCause()));
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
new file mode 100644
index 0000000..bfa3654
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer;
+
+import org.gradle.api.internal.Operation;
+import org.gradle.api.internal.concurrent.Synchronizer;
+import org.gradle.internal.Factory;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.internal.DefaultProgressLoggerFactory;
+import org.gradle.logging.internal.ProgressListener;
+import org.gradle.util.TrueTimeProvider;
+
+/**
+ * Thread safe logging provider that needs to be initialized before use.
+ * <p>
+ * by Szczepan Faber, created at: 12/14/11
+ */
+public class SynchronizedLogging implements LoggingProvider {
+
+    private final ThreadLocal<ListenerManager> listenerManager = new ThreadLocal<ListenerManager>();
+    private final ThreadLocal<DefaultProgressLoggerFactory> progressLoggerFactory = new ThreadLocal<DefaultProgressLoggerFactory>();
+
+    //even though we use thread locals we need to synchronize a bit
+    //to avoid partial initialization / race conditions like listenerManager initialized but not yet progressLoggerFactory
+    private final Synchronizer synchronizer = new Synchronizer();
+
+    public ListenerManager getListenerManager() {
+        return synchronizer.synchronize(new Factory<ListenerManager>() {
+            public ListenerManager create() {
+                assertInitialized();
+                return listenerManager.get();
+            }
+        });
+    }
+
+    public DefaultProgressLoggerFactory getProgressLoggerFactory() {
+        return synchronizer.synchronize(new Factory<DefaultProgressLoggerFactory>() {
+            public DefaultProgressLoggerFactory create() {
+                assertInitialized();
+                return progressLoggerFactory.get();
+            }
+        });
+    }
+
+    public void init() {
+        synchronizer.synchronize(new Operation() {
+            public void execute() {
+                DefaultListenerManager manager = new DefaultListenerManager();
+                listenerManager.set(manager);
+                progressLoggerFactory.set(new DefaultProgressLoggerFactory(manager.getBroadcaster(ProgressListener.class), new TrueTimeProvider()));
+            }
+        });
+    }
+
+    private void assertInitialized() {
+        if (listenerManager.get() == null) {
+            throw new IllegalStateException("Internal problem. Logging has not yet been initialized for this thread.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ToolingImplementationLoader.java
deleted file mode 100644
index 910cedc..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ToolingImplementationLoader.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.internal.protocol.ConnectionVersion4;
-
-public interface ToolingImplementationLoader {
-    ConnectionVersion4 create(Distribution distribution);
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConnection.java
new file mode 100644
index 0000000..09fb6e7
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/AsyncConnection.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.async;
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1;
+
+public interface AsyncConnection {
+    void executeBuild(BuildParametersVersion1 buildParameters, ConsumerOperationParameters operationParameters, ResultHandlerVersion1<? super Void> handler) throws IllegalStateException;
+
+    <T> void getModel(Class<T> type, ConsumerOperationParameters operationParameters, ResultHandlerVersion1<T> handler) throws UnsupportedOperationException, IllegalStateException;
+
+    void stop();
+
+    String getDisplayName();
+    
+    VersionDetails getVersionDetails();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java
new file mode 100644
index 0000000..4b53a37
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.async;
+
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+import org.gradle.tooling.internal.protocol.ResultHandlerVersion1;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Adapts a {@link ConsumerConnection} to an {@link AsyncConnection}.
+ */
+public class DefaultAsyncConnection implements AsyncConnection {
+    private final ConsumerConnection connection;
+    private final StoppableExecutor executor;
+    private final AtomicBoolean closed = new AtomicBoolean();
+
+    public DefaultAsyncConnection(ConsumerConnection connection, ExecutorFactory executorFactory) {
+        this.connection = connection;
+        executor = executorFactory.create("Connection worker");
+    }
+
+    public String getDisplayName() {
+        return connection.getDisplayName();
+    }
+
+    public VersionDetails getVersionDetails() {
+        return connection.getVersionDetails();
+    }
+
+    public void executeBuild(final BuildParametersVersion1 buildParameters, final ConsumerOperationParameters operationParameters, ResultHandlerVersion1<? super Void> handler) throws IllegalStateException {
+        runLater(handler, new ConnectionAction<Void>() {
+            public Void run() {
+                connection.executeBuild(buildParameters, operationParameters);
+                return null;
+            }
+        });
+    }
+
+    public <T> void getModel(final Class<T> type, final ConsumerOperationParameters operationParameters, ResultHandlerVersion1<T> handler) throws UnsupportedOperationException, IllegalStateException {
+        runLater(handler, new ConnectionAction<T>() {
+            public T run() {
+                return connection.getModel(type, operationParameters);
+            }
+        });
+    }
+
+    public void stop() {
+        closed.set(true);
+        executor.stop();
+        connection.stop();
+    }
+
+    private <T> void runLater(final ResultHandlerVersion1<? super T> handler, final ConnectionAction<T> action) {
+        onStartOperation();
+
+        executor.execute(new Runnable() {
+            public void run() {
+                T result;
+                try {
+                    result = action.run();
+                } catch (Throwable t) {
+                    handler.onFailure(t);
+                    return;
+                }
+                handler.onComplete(result);
+            }
+        });
+    }
+
+    private void onStartOperation() {
+        if (closed.get()) {
+            throw new IllegalStateException("This connection has been closed.");
+        }
+    }
+
+    private interface ConnectionAction<T> {
+        T run();
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AdaptedConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AdaptedConnection.java
new file mode 100644
index 0000000..e221fde
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/AdaptedConnection.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.tooling.internal.protocol.InternalConnection;
+import org.gradle.tooling.internal.reflect.CompatibleIntrospector;
+
+/**
+ * An implementation that wraps a protocol instance that has rigid compatibility policy.
+ * <p>
+ * by Szczepan Faber, created at: 12/22/11
+ */
+public class AdaptedConnection implements ConsumerConnection {
+    private final ConnectionVersion4 delegate;
+
+    public AdaptedConnection(ConnectionVersion4 delegate) {
+        this.delegate = delegate;
+    }
+
+    public void stop() {
+        delegate.stop();
+    }
+
+    public String getDisplayName() {
+        return delegate.getMetaData().getDisplayName();
+    }
+
+    public VersionDetails getVersionDetails() {
+        return new VersionDetails(delegate.getMetaData().getVersion());
+    }
+
+    @SuppressWarnings({"deprecation"})
+    public <T> T getModel(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        if (delegate instanceof InternalConnection) {
+            return ((InternalConnection) delegate).getTheModel(type, operationParameters);
+        } else {
+            return (T) delegate.getModel((Class) type, operationParameters);
+        }
+    }
+
+    public void executeBuild(BuildParametersVersion1 buildParameters, ConsumerOperationParameters operationParameters) throws IllegalStateException {
+        delegate.executeBuild(buildParameters, operationParameters);
+    }
+
+    public ConnectionVersion4 getDelegate() {
+        return delegate;
+    }
+
+    public void configureLogging(boolean verboseLogging) {
+        new CompatibleIntrospector(delegate).callSafely("configureLogging", verboseLogging);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnection.java
new file mode 100644
index 0000000..f4dda8a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+
+/**
+ * by Szczepan Faber, created at: 12/22/11
+ */
+public interface ConsumerConnection {
+
+    void stop();
+    
+    String getDisplayName();
+    
+    VersionDetails getVersionDetails();
+
+    <T> T getModel(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException;
+
+    void executeBuild(BuildParametersVersion1 buildParameters, ConsumerOperationParameters operationParameters) throws IllegalStateException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnectionMetadata.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnectionMetadata.java
new file mode 100644
index 0000000..cf5d37b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ConsumerConnectionMetadata.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+
+/**
+ * by Szczepan Faber, created at: 1/13/12
+ */
+public class ConsumerConnectionMetadata {
+
+    private final String displayName;
+    private final String version;
+
+    public ConsumerConnectionMetadata(String displayName, String version) {
+        this.displayName = displayName;
+        this.version = version;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public VersionDetails getVersionDetails() {
+        if (version == null) {
+            throw new UnsupportedOperationException();
+        }
+        return new VersionDetails(version);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConnection.java
new file mode 100644
index 0000000..b6945e6
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LazyConnection.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.LoggingProvider;
+import org.gradle.tooling.internal.consumer.ModelProvider;
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.FeatureValidator;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Creates the actual connection implementation on demand.
+ */
+public class LazyConnection implements ConsumerConnection {
+    private final Distribution distribution;
+    private final ToolingImplementationLoader implementationLoader;
+    private final LoggingProvider loggingProvider;
+
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private Set<Thread> executing = new HashSet<Thread>();
+    private boolean stopped;
+    private ConsumerConnection connection;
+
+    boolean verboseLogging;
+
+    ModelProvider modelProvider = new ModelProvider();
+    FeatureValidator featureValidator = new FeatureValidator();
+
+    public LazyConnection(Distribution distribution, ToolingImplementationLoader implementationLoader, LoggingProvider loggingProvider, boolean verboseLogging) {
+        this.distribution = distribution;
+        this.implementationLoader = implementationLoader;
+        this.loggingProvider = loggingProvider;
+        this.verboseLogging = verboseLogging;
+    }
+
+    public void stop() {
+        ConsumerConnection connection = null;
+        lock.lock();
+        try {
+            stopped = true;
+            while (!executing.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+            connection = this.connection;
+            this.connection = null;
+        } finally {
+            lock.unlock();
+        }
+        if (connection != null) {
+            connection.stop();
+        }
+    }
+
+    public ConsumerConnectionMetadata getMetaData() {
+        return new ConsumerConnectionMetadata(distribution.getDisplayName(), null);
+    }
+
+    public String getDisplayName() {
+        return distribution.getDisplayName();
+    }
+
+    public VersionDetails getVersionDetails() {
+        if (connection == null) {
+            throw new IllegalStateException("Cannot provide version details just yet. You need to execute build or acquire some model first.");
+        }
+        return connection.getVersionDetails();
+    }
+
+    public void executeBuild(final BuildParametersVersion1 buildParameters, final ConsumerOperationParameters operationParameters) {
+        withConnection(new ConnectionAction<Object>() {
+            public Object run(ConsumerConnection connection) {
+                featureValidator.validate(connection, operationParameters);
+                connection.executeBuild(buildParameters, operationParameters);
+                return null;
+            }
+        });
+    }
+
+    public <T> T getModel(final Class<T> type, final ConsumerOperationParameters operationParameters) {
+        return withConnection(new ConnectionAction<T>() {
+            public T run(ConsumerConnection connection) {
+                featureValidator.validate(connection, operationParameters);
+                return modelProvider.provide(connection, type, operationParameters);
+            }
+        });
+    }
+
+    private <T> T withConnection(ConnectionAction<T> action) {
+        try {
+            ConsumerConnection connection = onStartAction();
+            return action.run(connection);
+        } finally {
+            onEndAction();
+        }
+    }
+
+    private ConsumerConnection onStartAction() {
+        lock.lock();
+        try {
+            if (stopped) {
+                throw new IllegalStateException("This connection has been stopped.");
+            }
+            executing.add(Thread.currentThread());
+            if (connection == null) {
+                // Hold the lock while creating the connection. Not generally good form.
+                // In this instance, blocks other threads from creating the connection at the same time
+                connection = implementationLoader.create(distribution, loggingProvider.getProgressLoggerFactory(), verboseLogging);
+            }
+            return connection;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onEndAction() {
+        lock.lock();
+        try {
+            executing.remove(Thread.currentThread());
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private interface ConnectionAction<T> {
+        T run(ConsumerConnection connection);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConnection.java
new file mode 100644
index 0000000..14aab8d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/LoggingInitializerConnection.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection;
+
+import org.gradle.tooling.internal.consumer.SynchronizedLogging;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+
+/**
+ * The idea is to initialize the logging infrastructure before we actually build the model or run a build.
+ * <p>
+ * by Szczepan Faber, created at: 12/14/11
+ */
+public class LoggingInitializerConnection implements ConsumerConnection {
+
+    private final ConsumerConnection connection;
+    private final SynchronizedLogging synchronizedLogging;
+
+    public LoggingInitializerConnection(ConsumerConnection connection, SynchronizedLogging synchronizedLogging) {
+        this.connection = connection;
+        this.synchronizedLogging = synchronizedLogging;
+    }
+
+    public void stop() {
+        connection.stop();
+    }
+
+    public String getDisplayName() {
+        return connection.getDisplayName();
+    }
+
+    public VersionDetails getVersionDetails() {
+        return connection.getVersionDetails();
+    }
+
+    public <T> T getModel(Class<T> type, ConsumerOperationParameters operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        synchronizedLogging.init();
+        return connection.getModel(type, operationParameters);
+    }
+
+    public void executeBuild(BuildParametersVersion1 buildParameters, ConsumerOperationParameters operationParameters) throws IllegalStateException {
+        synchronizedLogging.init();
+        connection.executeBuild(buildParameters, operationParameters);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnection.java
new file mode 100644
index 0000000..d2d084b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnection.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection;
+
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.internal.ProgressCompleteEvent;
+import org.gradle.logging.internal.ProgressEvent;
+import org.gradle.logging.internal.ProgressListener;
+import org.gradle.logging.internal.ProgressStartEvent;
+import org.gradle.tooling.internal.consumer.LoggingProvider;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+
+/**
+ * Provides some high-level progress information.
+ */
+public class ProgressLoggingConnection implements ConsumerConnection {
+    private final ConsumerConnection connection;
+    private final LoggingProvider loggingProvider;
+
+    public ProgressLoggingConnection(ConsumerConnection connection, LoggingProvider loggingProvider) {
+        this.connection = connection;
+        this.loggingProvider = loggingProvider;
+    }
+
+    public void stop() {
+        connection.stop();
+    }
+
+    public String getDisplayName() {
+        return connection.getDisplayName();
+    }
+
+    public VersionDetails getVersionDetails() {
+        return connection.getVersionDetails();
+    }
+
+    public void executeBuild(final BuildParametersVersion1 buildParameters, final ConsumerOperationParameters operationParameters) {
+        run("Execute build", operationParameters, new BuildAction<Void>() {
+            public Void run(ConsumerConnection connection) {
+                connection.executeBuild(buildParameters, operationParameters);
+                return null;
+            }
+        });
+    }
+
+    public <T> T getModel(final Class<T> type, final ConsumerOperationParameters operationParameters) {
+        return run("Load projects", operationParameters, new BuildAction<T>() {
+            public T run(ConsumerConnection connection) {
+                return connection.getModel(type, operationParameters);
+            }
+        });
+    }
+
+    private <T> T run(String description, BuildOperationParametersVersion1 parameters, BuildAction<T> action) {
+        ProgressListenerAdapter listener = new ProgressListenerAdapter(parameters.getProgressListener());
+        ListenerManager listenerManager = loggingProvider.getListenerManager();
+        listenerManager.addListener(listener);
+        try {
+            ProgressLogger progressLogger = loggingProvider.getProgressLoggerFactory().newOperation(ProgressLoggingConnection.class);
+            progressLogger.setDescription(description);
+            progressLogger.started();
+            try {
+                return action.run(connection);
+            } finally {
+                progressLogger.completed();
+            }
+        } finally {
+            listenerManager.removeListener(listener);
+        }
+    }
+
+    private interface BuildAction<T> {
+        T run(ConsumerConnection connection);
+    }
+
+    private static class ProgressListenerAdapter implements ProgressListener {
+        private final ProgressListenerVersion1 progressListener;
+
+        public ProgressListenerAdapter(ProgressListenerVersion1 progressListener) {
+            this.progressListener = progressListener;
+        }
+
+        public void started(ProgressStartEvent event) {
+            progressListener.onOperationStart(event.getDescription());
+        }
+
+        public void progress(ProgressEvent event) {
+        }
+
+        public void completed(ProgressCompleteEvent event) {
+            progressListener.onOperationEnd();
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java
new file mode 100644
index 0000000..18f50da
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/converters/GradleProjectConverter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 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.tooling.internal.consumer.converters;
+
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.gradle.DefaultGradleTask;
+import org.gradle.tooling.internal.protocol.TaskVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.model.GradleTask;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 3/27/12
+ */
+public class GradleProjectConverter {
+
+    public DefaultGradleProject convert(EclipseProjectVersion3 project) {
+        //build children recursively
+        List<DefaultGradleProject> children = new LinkedList<DefaultGradleProject>();
+        for (EclipseProjectVersion3 p : project.getChildren()) {
+            children.add(convert(p));
+        }
+        //build parent
+        DefaultGradleProject parent = new DefaultGradleProject()
+                .setPath(project.getPath())
+                .setName(project.getName())
+                .setChildren(children)
+                .setDescription(project.getDescription());
+
+        //build tasks
+        List<GradleTask> tasks = new LinkedList<GradleTask>();
+        for (TaskVersion1 t : project.getTasks()) {
+            tasks.add(new DefaultGradleTask()
+                    .setName(t.getName())
+                    .setPath(t.getPath())
+                    .setDescription(t.getDescription())
+                    .setProject(parent));
+        }
+        parent.setTasks(tasks);
+
+        //apply parent to each child
+        for (DefaultGradleProject child : children) {
+            child.setParent(parent);
+        }
+
+        return parent;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java
new file mode 100644
index 0000000..bf3fb1b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader;
+
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class CachingToolingImplementationLoader implements ToolingImplementationLoader {
+    private final ToolingImplementationLoader loader;
+    private final Map<Set<File>, ConsumerConnection> connections = new HashMap<Set<File>, ConsumerConnection>();
+
+    public CachingToolingImplementationLoader(ToolingImplementationLoader loader) {
+        this.loader = loader;
+    }
+
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
+        Set<File> classpath = new LinkedHashSet<File>(distribution.getToolingImplementationClasspath(progressLoggerFactory));
+
+        ConsumerConnection connection = connections.get(classpath);
+        if (connection == null) {
+            connection = loader.create(distribution, progressLoggerFactory, verboseLogging);
+            connections.put(classpath, connection);
+        }
+
+        return connection;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java
new file mode 100644
index 0000000..e559004
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader;
+
+import org.gradle.internal.Factory;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.GradleConnectionException;
+import org.gradle.tooling.UnsupportedVersionException;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.connection.AdaptedConnection;
+import org.gradle.tooling.internal.protocol.ConnectionVersion4;
+import org.gradle.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DefaultToolingImplementationLoader implements ToolingImplementationLoader {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultToolingImplementationLoader.class);
+    private final ClassLoader classLoader;
+
+    public DefaultToolingImplementationLoader() {
+        this(DefaultToolingImplementationLoader.class.getClassLoader());
+    }
+
+    DefaultToolingImplementationLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public AdaptedConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
+        LOGGER.debug("Using tooling provider from {}", distribution.getDisplayName());
+        ClassLoader classLoader = createImplementationClassLoader(distribution, progressLoggerFactory);
+        ServiceLocator serviceLocator = new ServiceLocator(classLoader);
+        try {
+            Factory<ConnectionVersion4> factory = serviceLocator.findFactory(ConnectionVersion4.class);
+            if (factory == null) {
+                Matcher m = Pattern.compile("\\w+Version(\\d+)").matcher(ConnectionVersion4.class.getSimpleName());
+                m.matches();
+                String protocolVersion = m.group(1);
+                throw new UnsupportedVersionException(String.format("The specified %s is not supported by this tooling API version (%s, protocol version %s)", distribution.getDisplayName(), GradleVersion.current().getVersion(), protocolVersion));
+            }
+            // ConnectionVersion4 is a part of the protocol and cannot be easily changed.
+            ConnectionVersion4 connection = factory.create();
+            // Adopting the connection to a refactoring friendly type that the consumer owns
+            AdaptedConnection adaptedConnection = new AdaptedConnection(connection);
+            adaptedConnection.configureLogging(verboseLogging);
+            return adaptedConnection;
+        } catch (UnsupportedVersionException e) {
+            throw e;
+        } catch (Throwable t) {
+            throw new GradleConnectionException(String.format("Could not create an instance of Tooling API implementation using the specified %s.", distribution.getDisplayName()), t);
+        }
+    }
+
+    private ClassLoader createImplementationClassLoader(Distribution distribution, ProgressLoggerFactory progressLoggerFactory) {
+        Set<File> implementationClasspath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
+        LOGGER.debug("Using tooling provider classpath: {}", implementationClasspath);
+        URL[] urls = GFileUtils.toURLArray(implementationClasspath);
+        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(classLoader);
+        filteringClassLoader.allowPackage("org.gradle.tooling.internal.protocol");
+        return new MutableURLClassLoader(filteringClassLoader, urls);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoader.java
new file mode 100644
index 0000000..9deb8e9
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoader.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader;
+
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * by Szczepan Faber, created at: 12/6/11
+ */
+public class SynchronizedToolingImplementationLoader implements ToolingImplementationLoader {
+
+    Lock lock = new ReentrantLock();
+    private final ToolingImplementationLoader delegate;
+
+    public SynchronizedToolingImplementationLoader(ToolingImplementationLoader delegate) {
+        this.delegate = delegate;
+    }
+
+    public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
+        if (lock.tryLock()) {
+            try {
+                return delegate.create(distribution, progressLoggerFactory, verboseLogging);
+            } finally {
+                lock.unlock();
+            }
+        }
+        ProgressLogger logger = progressLoggerFactory.newOperation(SynchronizedToolingImplementationLoader.class);
+        logger.setDescription("Wait for the other thread to finish acquiring the distribution");
+        logger.started();
+        lock.lock();
+        try {
+            return delegate.create(distribution, progressLoggerFactory, verboseLogging);
+        } finally {
+            lock.unlock();
+            logger.completed();
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/ToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/ToolingImplementationLoader.java
new file mode 100644
index 0000000..cf5fa51
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/ToolingImplementationLoader.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader;
+
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.tooling.internal.consumer.Distribution;
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+
+public interface ToolingImplementationLoader {
+    ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParameters.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParameters.java
new file mode 100644
index 0000000..2bfb28f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParameters.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.parameters;
+
+import org.gradle.tooling.ProgressListener;
+import org.gradle.tooling.internal.consumer.ConnectionParameters;
+import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * by Szczepan Faber, created at: 1/9/12
+ */
+public class ConsumerOperationParameters implements BuildOperationParametersVersion1 {
+
+    private final ProgressListenerAdapter progressListener = new ProgressListenerAdapter();
+    private final ConnectionParameters parameters;
+    private final long startTime = System.currentTimeMillis();
+
+    private OutputStream stdout;
+    private OutputStream stderr;
+    private InputStream stdin;
+
+    private File javaHome;
+    private List<String> jvmArguments;
+    private List<String> arguments;
+
+    public ConsumerOperationParameters(ConnectionParameters parameters) {
+        this.parameters = parameters;
+    }
+
+    public void setArguments(String[] arguments) {
+        this.arguments = rationalizeInput(arguments);
+    }
+
+    private List<String> rationalizeInput(String[] arguments) {
+        return arguments != null && arguments.length > 0 ? Arrays.asList(arguments) : null;
+    }
+
+    public void setStandardOutput(OutputStream outputStream) {
+        stdout = outputStream;
+    }
+
+    public void setStandardError(OutputStream outputStream) {
+        stderr = outputStream;
+    }
+
+    public void setStandardInput(InputStream inputStream) {
+        stdin = inputStream;
+    }
+
+    public void addProgressListener(ProgressListener listener) {
+        progressListener.add(listener);
+    }
+
+    public void setJavaHome(File javaHome) {
+        validateJavaHome(javaHome);
+        this.javaHome = javaHome;
+    }
+
+    private void validateJavaHome(File javaHome) {
+        if (javaHome == null) {
+            return;
+        }
+        if (!javaHome.isDirectory()) {
+            throw new IllegalArgumentException("Supplied javaHome is not a valid folder. You supplied: " + javaHome);
+        }
+    }
+
+    public void setJvmArguments(String... jvmArguments) {
+        this.jvmArguments = rationalizeInput(jvmArguments);
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public boolean getVerboseLogging() {
+        return parameters.getVerboseLogging();
+    }
+
+    public File getGradleUserHomeDir() {
+        return parameters.getGradleUserHomeDir();
+    }
+
+    public File getProjectDir() {
+        return parameters.getProjectDir();
+    }
+
+    public Boolean isSearchUpwards() {
+        return parameters.isSearchUpwards();
+    }
+
+    public Boolean isEmbedded() {
+        return parameters.isEmbedded();
+    }
+
+    public TimeUnit getDaemonMaxIdleTimeUnits() {
+        return parameters.getDaemonMaxIdleTimeUnits();
+    }
+
+    public Integer getDaemonMaxIdleTimeValue() {
+        return parameters.getDaemonMaxIdleTimeValue();
+    }
+
+    public OutputStream getStandardOutput() {
+        return stdout;
+    }
+
+    public OutputStream getStandardError() {
+        return stderr;
+    }
+
+    public ProgressListenerVersion1 getProgressListener() {
+        return progressListener;
+    }
+
+    public InputStream getStandardInput() {
+        return stdin;
+    }
+
+    public File getJavaHome() {
+        return javaHome;
+    }
+
+    public List<String> getJvmArguments() {
+        return jvmArguments;
+    }
+
+    public List<String> getArguments() {
+        return arguments;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ProgressListenerAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ProgressListenerAdapter.java
new file mode 100644
index 0000000..900a4bd
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/parameters/ProgressListenerAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.parameters;
+
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.tooling.ProgressEvent;
+import org.gradle.tooling.ProgressListener;
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1;
+
+import java.util.LinkedList;
+
+class ProgressListenerAdapter implements ProgressListenerVersion1 {
+    private final ListenerBroadcast<ProgressListener> listeners = new ListenerBroadcast<ProgressListener>(ProgressListener.class);
+    private final LinkedList<String> stack = new LinkedList<String>();
+
+    public void onOperationStart(final String description) {
+        stack.addFirst(description == null ? "" : description);
+        fireChangeEvent();
+    }
+
+    public void onOperationEnd() {
+        stack.removeFirst();
+        fireChangeEvent();
+    }
+
+    private void fireChangeEvent() {
+        final String description = stack.isEmpty() ? "" : stack.getFirst();
+        listeners.getSource().statusChanged(new ProgressEvent() {
+            public String getDescription() {
+                return description;
+            }
+        });
+    }
+
+    public void add(ProgressListener listener) {
+        listeners.add(listener);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ModelPropertyHandler.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ModelPropertyHandler.java
new file mode 100644
index 0000000..7723529
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ModelPropertyHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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.tooling.internal.consumer.protocoladapter;
+
+import org.gradle.tooling.internal.consumer.converters.GradleProjectConverter;
+import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
+import org.gradle.tooling.internal.gradle.DefaultGradleProject;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+
+import java.lang.reflect.Method;
+
+/**
+ * by Szczepan Faber, created at: 4/2/12
+ */
+public class ModelPropertyHandler {
+
+    private final VersionDetails versionDetails;
+
+    public ModelPropertyHandler(VersionDetails versionDetails) {
+        this.versionDetails = versionDetails;
+    }
+
+    /**
+     * @param method getter for the property
+     * @param delegate object that contain the property
+     * @return whether this handler should provide the return value for given property.
+     */
+    public boolean shouldHandle(Method method, Object delegate) {
+        return method.getName().equals("getGradleProject")
+                && delegate instanceof EclipseProjectVersion3
+                && !versionDetails.supportsGradleProjectModel();
+    }
+
+    public DefaultGradleProject getPropertyValue(Method method, Object delegate) {
+        return new GradleProjectConverter().convert((EclipseProjectVersion3) delegate);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapter.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapter.java
new file mode 100644
index 0000000..c71cbad
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.protocoladapter;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.internal.Exceptions;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class ProtocolToModelAdapter {
+
+    private final TargetTypeProvider targetTypeProvider = new TargetTypeProvider();
+
+    public <T, S> T adapt(Class<T> targetType, S protocolObject, ModelPropertyHandler modelPropertyHandler) {
+        Class<T> target = targetTypeProvider.getTargetType(targetType, protocolObject);
+        if (target.isInstance(protocolObject)) {
+            return target.cast(protocolObject);
+        }
+        Object proxy = Proxy.newProxyInstance(target.getClassLoader(), new Class<?>[]{target}, new InvocationHandlerImpl(protocolObject, modelPropertyHandler));
+        return target.cast(proxy);
+    }
+
+    private class InvocationHandlerImpl implements InvocationHandler {
+        private final Object delegate;
+        private final ModelPropertyHandler modelPropertyHandler;
+        private final Map<Method, Method> methods = new HashMap<Method, Method>();
+        private final Map<String, Object> properties = new HashMap<String, Object>();
+        private final Method equalsMethod;
+        private final Method hashCodeMethod;
+
+        public InvocationHandlerImpl(Object delegate, ModelPropertyHandler modelPropertyHandler) {
+            this.delegate = delegate;
+            this.modelPropertyHandler = modelPropertyHandler;
+            try {
+                equalsMethod = Object.class.getMethod("equals", Object.class);
+                hashCodeMethod = Object.class.getMethod("hashCode");
+            } catch (NoSuchMethodException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+
+            InvocationHandlerImpl other = (InvocationHandlerImpl) o;
+            return delegate.equals(other.delegate);
+        }
+
+        @Override
+        public int hashCode() {
+            return delegate.hashCode();
+        }
+
+        public Object invoke(Object target, Method method, Object[] params) throws Throwable {
+            if (method.equals(equalsMethod)) {
+                Object param = params[0];
+                if (param == null || !Proxy.isProxyClass(param.getClass())) {
+                    return false;
+                }
+                InvocationHandler other = Proxy.getInvocationHandler(param);
+                return equals(other);
+            } else if (method.equals(hashCodeMethod)) {
+                return hashCode();
+            }
+
+            if (method.getName().matches("get\\w+")) {
+                if (properties.containsKey(method.getName())) {
+                    return properties.get(method.getName());
+                }
+
+                Object value;
+                if (modelPropertyHandler.shouldHandle(method, delegate)) {
+                    value = modelPropertyHandler.getPropertyValue(method, delegate);
+                } else {
+                    value = doInvokeMethod(method, params);
+                }
+                properties.put(method.getName(), value);
+                return value;
+            }
+
+            return doInvokeMethod(method, params);
+        }
+
+        private Object doInvokeMethod(Method method, Object[] params) throws Throwable {
+            Method targetMethod = methods.get(method);
+            if (targetMethod == null) {
+                targetMethod = findMethod(method);
+                methods.put(method, targetMethod);
+            }
+
+            Object returnValue;
+            try {
+                returnValue = targetMethod.invoke(delegate, params);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+
+            if (returnValue == null || method.getReturnType().isInstance(returnValue)) {
+                return returnValue;
+            }
+
+            return convert(returnValue, method.getGenericReturnType());
+        }
+
+        private Method findMethod(Method method) {
+            Method match;
+            try {
+                match = delegate.getClass().getMethod(method.getName(), method.getParameterTypes());
+            } catch (NoSuchMethodException e) {
+                String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()";
+                throw Exceptions.unsupportedMethod(methodName, e);
+            }
+
+            LinkedList<Class<?>> queue = new LinkedList<Class<?>>();
+            queue.add(delegate.getClass());
+            while (!queue.isEmpty()) {
+                Class<?> c = queue.removeFirst();
+                try {
+                    match = c.getMethod(method.getName(), method.getParameterTypes());
+                } catch (NoSuchMethodException e) {
+                    // ignore
+                }
+                for (Class<?> interfaceType : c.getInterfaces()) {
+                    queue.addFirst(interfaceType);
+                }
+                if (c.getSuperclass() !=null) {
+                    queue.addFirst(c.getSuperclass());
+                }
+            }
+            match.setAccessible(true);
+            return match;
+        }
+
+        private Object convert(Object value, Type targetType) {
+            if (targetType instanceof ParameterizedType) {
+                ParameterizedType parameterizedTargetType = (ParameterizedType) targetType;
+                if (parameterizedTargetType.getRawType().equals(DomainObjectSet.class)) {
+                    Type targetElementType = getElementType(parameterizedTargetType);
+                    List<Object> convertedElements = new ArrayList<Object>();
+                    for (Object element : (Iterable) value) {
+                        convertedElements.add(convert(element, targetElementType));
+                    }
+                    return new ImmutableDomainObjectSet(convertedElements);
+                }
+            }
+            if (targetType instanceof Class) {
+                if (((Class) targetType).isPrimitive()) {
+                    return value;
+                }
+                return adapt((Class) targetType, value, modelPropertyHandler);
+            }
+            throw new UnsupportedOperationException(String.format("Cannot convert object of %s to %s.", value.getClass(), targetType));
+        }
+
+        private Type getElementType(ParameterizedType type) {
+            Type elementType = type.getActualTypeArguments()[0];
+            if (elementType instanceof WildcardType) {
+                WildcardType wildcardType = (WildcardType) elementType;
+                return wildcardType.getUpperBounds()[0];
+            }
+            return elementType;
+        }
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/TargetTypeProvider.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/TargetTypeProvider.java
new file mode 100644
index 0000000..cd8899d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/protocoladapter/TargetTypeProvider.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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.tooling.internal.consumer.protocoladapter;
+
+import org.gradle.tooling.model.idea.IdeaModuleDependency;
+import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 4/2/12
+ */
+public class TargetTypeProvider {
+
+    Map<String, Class<?>> configuredTargetTypes = new HashMap<String, Class<?>>();
+
+    public TargetTypeProvider() {
+        configuredTargetTypes.put(IdeaSingleEntryLibraryDependency.class.getCanonicalName(), IdeaSingleEntryLibraryDependency.class);
+        configuredTargetTypes.put(IdeaModuleDependency.class.getCanonicalName(), IdeaModuleDependency.class);
+    }
+
+    /**
+     * Occasionally we want to use preconfigured target type instead of passed target type.
+     *
+     * @param initialTargetType
+     * @param protocolObject
+     */
+    public <T, S> Class<T> getTargetType(Class<T> initialTargetType, S protocolObject) {
+        Class<?>[] interfaces = protocolObject.getClass().getInterfaces();
+        for (Class<?> i : interfaces) {
+            if (configuredTargetTypes.containsKey(i.getName())) {
+                return (Class<T>) configuredTargetTypes.get(i.getName());
+            }
+        }
+
+        return initialTargetType;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/FeatureValidator.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/FeatureValidator.java
new file mode 100644
index 0000000..c5183d1
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/FeatureValidator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.versioning;
+
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
+import org.gradle.tooling.model.internal.Exceptions;
+
+/**
+ * by Szczepan Faber, created at: 1/9/12
+ */
+public class FeatureValidator {
+
+    public void validate(ConsumerConnection connection, ConsumerOperationParameters operationParameters) {
+        VersionDetails version = connection.getVersionDetails();
+        if (operationParameters.getJavaHome() != null) {
+            if(!version.supportsConfiguringJavaHome()) {
+                throw Exceptions.unsupportedOperationConfiguration("modelBuilder.setJavaHome() and buildLauncher.setJavaHome()");
+            }
+        }
+        if (operationParameters.getJvmArguments() != null) {
+            if (!version.supportsConfiguringJvmArguments()) {
+                throw Exceptions.unsupportedOperationConfiguration("modelBuilder.setJvmArguments() and buildLauncher.setJvmArguments()");
+            }
+        }
+        if (operationParameters.getStandardInput() != null) {
+            if (!version.supportsConfiguringStandardInput()) {
+                throw Exceptions.unsupportedOperationConfiguration("modelBuilder.setStandardInput() and buildLauncher.setStandardInput()");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java
new file mode 100644
index 0000000..e69fc7c
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.versioning;
+
+import org.gradle.tooling.internal.protocol.*;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
+import org.gradle.tooling.model.*;
+import org.gradle.tooling.model.build.BuildEnvironment;
+import org.gradle.tooling.model.eclipse.EclipseProject;
+import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
+import org.gradle.tooling.model.idea.BasicIdeaProject;
+import org.gradle.tooling.model.idea.IdeaProject;
+import org.gradle.tooling.model.internal.TestModel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 1/13/12
+ */
+public class ModelMapping {
+
+    private static final Map<Class<? extends Model>, Class> MODEL_TYPE_MAP = new HashMap<Class<? extends Model>, Class>();
+
+    static {
+        MODEL_TYPE_MAP.putAll(getModelsUpToM6());
+        MODEL_TYPE_MAP.putAll(getModelsPostM6());
+    }
+
+    static Map<Class<? extends Model>, Class> getModelsUpToM6() {
+        Map<Class<? extends Model>, Class> map = new HashMap<Class<? extends Model>, Class>();
+        map.put(HierarchicalEclipseProject.class, HierarchicalEclipseProjectVersion1.class);
+        map.put(EclipseProject.class, EclipseProjectVersion3.class);
+        map.put(IdeaProject.class, InternalIdeaProject.class);
+        map.put(GradleProject.class, InternalGradleProject.class);
+        map.put(BasicIdeaProject.class, InternalBasicIdeaProject.class);
+        return map;
+    }
+
+    private static Map<Class<? extends Model>, Class> getModelsPostM6() {
+        Map<Class<? extends Model>, Class> map = new HashMap<Class<? extends Model>, Class>();
+        map.put(BuildEnvironment.class, InternalBuildEnvironment.class);
+        map.put(TestModel.class, InternalTestModel.class);
+        return map;
+    }
+
+    public Class getInternalType(Class<? extends Model> viewType) {
+        return MODEL_TYPE_MAP.get(viewType);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/VersionDetails.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/VersionDetails.java
new file mode 100644
index 0000000..7124627
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/VersionDetails.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.versioning;
+
+import org.gradle.util.GradleVersion;
+
+/**
+ * by Szczepan Faber, created at: 1/13/12
+ */
+public class VersionDetails {
+
+    private final GradleVersion gradleVersion;
+    private static final GradleVersion M5 = GradleVersion.version("1.0-milestone-5");
+    private static final GradleVersion M6 = GradleVersion.version("1.0-milestone-6");
+    private static final GradleVersion M7 = GradleVersion.version("1.0-milestone-7");
+
+    public VersionDetails(String version) {
+        gradleVersion = GradleVersion.version(version);
+    }
+
+    public boolean supportsCompleteBuildEnvironment() {
+        return gradleVersion.compareTo(M7) > 0;
+    }
+
+    public boolean clientHangsOnEarlyDaemonFailure() {
+        return gradleVersion.equals(M5) || gradleVersion.equals(M6);
+    }
+
+    public <T> boolean isPostM6Model(Class<T> internalModelType) {
+        return !ModelMapping.getModelsUpToM6().containsValue(internalModelType);
+    }
+
+    public boolean supportsConfiguringJavaHome() {
+        return gradleVersion.compareTo(M7) > 0;
+    }
+
+    public boolean supportsConfiguringJvmArguments() {
+        return gradleVersion.compareTo(M7) > 0;
+    }
+
+    public boolean supportsConfiguringStandardInput() {
+        return gradleVersion.compareTo(M7) > 0;
+    }
+
+    public String getVersion() {
+        return gradleVersion.getVersion();
+    }
+
+    public boolean supportsGradleProjectModel() {
+        return gradleVersion.compareTo(M5) >= 0;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
new file mode 100644
index 0000000..e8be323
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse;
+
+import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultEclipseExternalDependency implements ExternalDependencyVersion1, Serializable {
+    private final File file;
+    private final File javadoc;
+    private final File source;
+
+    public DefaultEclipseExternalDependency(File file, File javadoc, File source) {
+        this.file = file;
+        this.javadoc = javadoc;
+        this.source = source;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public File getJavadoc() {
+        return javadoc;
+    }
+
+    public File getSource() {
+        return source;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseLinkedResource.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseLinkedResource.java
new file mode 100644
index 0000000..d50f281
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseLinkedResource.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseLinkedResourceVersion1;
+
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 6/11/11
+ */
+public class DefaultEclipseLinkedResource implements Serializable, EclipseLinkedResourceVersion1 {
+
+    private String name;
+    private String type;
+    private String location;
+    private String locationUri;
+
+    public DefaultEclipseLinkedResource(String name, String type, String location, String locationUri) {
+        this.name = name;
+        this.type = type;
+        this.location = location;
+        this.locationUri = locationUri;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public String getLocationUri() {
+        return locationUri;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProject.java
new file mode 100644
index 0000000..9a0e141
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProject.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse;
+
+import com.google.common.collect.Lists;
+import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+import org.gradle.tooling.internal.protocol.eclipse.*;
+import org.gradle.tooling.model.GradleProject;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+public class DefaultEclipseProject implements EclipseProjectVersion3, Serializable {
+    private final String name;
+    private final String path;
+    private EclipseProjectVersion3 parent;
+    private List<ExternalDependencyVersion1> classpath;
+    private final List<EclipseProjectVersion3> children;
+    private List<EclipseSourceDirectoryVersion1> sourceDirectories;
+    private List<EclipseProjectDependencyVersion2> projectDependencies;
+    private final String description;
+    private final File projectDirectory;
+    private Iterable<? extends EclipseTaskVersion1> tasks;
+    private Iterable<? extends EclipseLinkedResourceVersion1> linkedResources;
+    private GradleProject gradleProject;
+
+    public DefaultEclipseProject(String name, String path, String description, File projectDirectory, Iterable<? extends EclipseProjectVersion3> children) {
+        this.name = name;
+        this.path = path;
+        this.description = description;
+        this.projectDirectory = projectDirectory;
+        this.tasks = Collections.emptyList();
+        this.children = Lists.newArrayList(children);
+        this.classpath = Collections.emptyList();
+        this.sourceDirectories = Collections.emptyList();
+        this.projectDependencies = Collections.emptyList();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("project '%s'", path);
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public EclipseProjectVersion3 getParent() {
+        return parent;
+    }
+
+    public File getProjectDirectory() {
+        return projectDirectory;
+    }
+
+    public void setParent(EclipseProjectVersion3 parent) {
+        this.parent = parent;
+    }
+
+    public List<EclipseProjectVersion3> getChildren() {
+        return children;
+    }
+
+    public Iterable<? extends EclipseSourceDirectoryVersion1> getSourceDirectories() {
+        return sourceDirectories;
+    }
+
+    public void setSourceDirectories(List<EclipseSourceDirectoryVersion1> sourceDirectories) {
+        this.sourceDirectories = sourceDirectories;
+    }
+
+    public Iterable<? extends EclipseProjectDependencyVersion2> getProjectDependencies() {
+        return projectDependencies;
+    }
+
+    public void setProjectDependencies(List<EclipseProjectDependencyVersion2> projectDependencies) {
+        this.projectDependencies = projectDependencies;
+    }
+
+    public List<ExternalDependencyVersion1> getClasspath() {
+        return classpath;
+    }
+    public void setClasspath(List<ExternalDependencyVersion1> classpath) {
+        this.classpath = classpath;
+    }
+
+    public Iterable<? extends EclipseTaskVersion1> getTasks() {
+        return tasks;
+    }
+
+    public void setTasks(Iterable<? extends EclipseTaskVersion1> tasks) {
+        this.tasks = tasks;
+    }
+
+    public Iterable<? extends EclipseLinkedResourceVersion1> getLinkedResources() {
+        return linkedResources;
+    }
+
+    public void setLinkedResources(Iterable<? extends EclipseLinkedResourceVersion1> linkedResources) {
+        this.linkedResources = linkedResources;
+    }
+
+    public GradleProject getGradleProject() {
+        return gradleProject;
+    }
+
+    public DefaultEclipseProject setGradleProject(GradleProject gradleProject) {
+        this.gradleProject = gradleProject;
+        return this;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectDependency.java
new file mode 100644
index 0000000..6a97814
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectDependency.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectDependencyVersion2;
+import org.gradle.tooling.internal.protocol.eclipse.HierarchicalEclipseProjectVersion1;
+
+import java.io.Serializable;
+
+public class DefaultEclipseProjectDependency implements EclipseProjectDependencyVersion2, Serializable {
+    private final String path;
+    private final HierarchicalEclipseProjectVersion1 target;
+
+    public DefaultEclipseProjectDependency(String path, HierarchicalEclipseProjectVersion1 target) {
+        this.target = target;
+        this.path = path;
+    }
+
+    public HierarchicalEclipseProjectVersion1 getTargetProject() {
+        return target;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("project dependency %s (%s)", path, target);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseSourceDirectory.java
new file mode 100644
index 0000000..4d5939e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseSourceDirectory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseSourceDirectoryVersion1;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultEclipseSourceDirectory implements EclipseSourceDirectoryVersion1, Serializable {
+    private final String path;
+    private final File directory;
+
+    public DefaultEclipseSourceDirectory(String path, File directory) {
+        this.path = path;
+        this.directory = directory;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("source directory '%s'", path);
+    }
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public String getPath() {
+        return path;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseTask.java
new file mode 100644
index 0000000..d00db49
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseTask.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse;
+
+import org.gradle.tooling.internal.protocol.eclipse.EclipseProjectVersion3;
+import org.gradle.tooling.internal.protocol.eclipse.EclipseTaskVersion1;
+
+import java.io.Serializable;
+
+public class DefaultEclipseTask implements EclipseTaskVersion1, Serializable {
+    private final EclipseProjectVersion3 project;
+    private final String path;
+    private final String name;
+    private final String description;
+
+    public DefaultEclipseTask(EclipseProjectVersion3 project, String path, String name, String description) {
+        this.project = project;
+        this.path = path;
+        this.name = name;
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("task '%s'", path);
+    }
+
+    public EclipseProjectVersion3 getProject() {
+        return project;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java
new file mode 100644
index 0000000..34db911
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleProject.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2011 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.tooling.internal.gradle;
+
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.GradleTask;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author: Szczepan Faber, created at: 7/27/11
+ */
+public class DefaultGradleProject implements ProjectVersion3, GradleProject, Serializable {
+
+    private String name;
+    private String description;
+    private String path;
+    private GradleProject parent;
+    private List<? extends GradleProject> children = new LinkedList<GradleProject>();
+    private List<GradleTask> tasks = new LinkedList<GradleTask>();
+
+    public DefaultGradleProject() {}
+
+    public DefaultGradleProject(String path) {
+        this.path = path;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultGradleProject setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public DefaultGradleProject setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public GradleProject getParent() {
+        return parent;
+    }
+
+    public DefaultGradleProject setParent(GradleProject parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    public DomainObjectSet<? extends GradleProject> getChildren() {
+        return new ImmutableDomainObjectSet<GradleProject>(children);
+    }
+
+    public DefaultGradleProject setChildren(List<? extends GradleProject> children) {
+        this.children = children;
+        return this;
+    }
+
+    public DomainObjectSet<GradleTask> getTasks() {
+        return new ImmutableDomainObjectSet<GradleTask>(tasks);
+    }
+
+    public DefaultGradleProject setTasks(List<GradleTask> tasks) {
+        this.tasks = tasks;
+        return this;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public DefaultGradleProject setPath(String path) {
+        this.path = path;
+        return this;
+    }
+
+    public File getProjectDirectory() {
+        throw new RuntimeException("ProjectVersion3 methods are deprecated.");
+    }
+
+    public GradleProject findByPath(String path) {
+        if (path.equals(this.path)) {
+            return this;
+        }
+        for (GradleProject child : children) {
+            GradleProject found = child.findByPath(path);
+            if (found != null) {
+                return found;
+            }
+        }
+
+        return null;
+    }
+
+    public String toString() {
+        return "GradleProject{"
+                + "path='" + path + '\''
+                + "tasks='" + tasks + '\''
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java
new file mode 100644
index 0000000..bf0c39e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleTask.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.tooling.internal.gradle;
+
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.GradleTask;
+
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 7/27/11
+ */
+public class DefaultGradleTask implements GradleTask, Serializable {
+
+    String path;
+    String name;
+    String description;
+    GradleProject project;
+
+    public String getPath() {
+        return path;
+    }
+
+    public DefaultGradleTask setPath(String path) {
+        this.path = path;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultGradleTask setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public DefaultGradleTask setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public GradleProject getProject() {
+        return project;
+    }
+
+    public DefaultGradleTask setProject(GradleProject project) {
+        this.project = project;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "GradleTask{"
+                + "name='" + name + '\''
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaCompilerOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaCompilerOutput.java
new file mode 100644
index 0000000..e7d9856
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaCompilerOutput.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.idea.IdeaCompilerOutput;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 8/5/11
+ */
+public class DefaultIdeaCompilerOutput implements IdeaCompilerOutput, Serializable {
+
+    private boolean inheritOutputDirs;
+    private File outputDir;
+    private File testOutputDir;
+
+    public boolean getInheritOutputDirs() {
+        return inheritOutputDirs;
+    }
+
+    public DefaultIdeaCompilerOutput setInheritOutputDirs(boolean inheritOutputDirs) {
+        this.inheritOutputDirs = inheritOutputDirs;
+        return this;
+    }
+
+    public File getOutputDir() {
+        return outputDir;
+    }
+
+    public DefaultIdeaCompilerOutput setOutputDir(File outputDir) {
+        this.outputDir = outputDir;
+        return this;
+    }
+
+    public File getTestOutputDir() {
+        return testOutputDir;
+    }
+
+    public DefaultIdeaCompilerOutput setTestOutputDir(File testOutputDir) {
+        this.testOutputDir = testOutputDir;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaCompilerOutput{"
+                + "inheritOutputDirs=" + inheritOutputDirs
+                + ", outputDir=" + outputDir
+                + ", testOutputDir=" + testOutputDir
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaContentRoot.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaContentRoot.java
new file mode 100644
index 0000000..6620ee5
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaContentRoot.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.idea.IdeaContentRoot;
+import org.gradle.tooling.model.idea.IdeaSourceDirectory;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author: Szczepan Faber, created at: 8/3/11
+ */
+public class DefaultIdeaContentRoot implements IdeaContentRoot, Serializable {
+
+    File rootDirectory;
+    Set<IdeaSourceDirectory> sourceDirectories = new LinkedHashSet<IdeaSourceDirectory>();
+    Set<IdeaSourceDirectory> testDirectories = new LinkedHashSet<IdeaSourceDirectory>();
+    Set<File> excludeDirectories = new LinkedHashSet<File>();
+
+    public File getRootDirectory() {
+        return rootDirectory;
+    }
+
+    public DefaultIdeaContentRoot setRootDirectory(File rootDirectory) {
+        this.rootDirectory = rootDirectory;
+        return this;
+    }
+
+    public DomainObjectSet<IdeaSourceDirectory> getSourceDirectories() {
+        return new ImmutableDomainObjectSet<IdeaSourceDirectory>(sourceDirectories);
+    }
+
+    public DefaultIdeaContentRoot setSourceDirectories(Set<IdeaSourceDirectory> sourceDirectories) {
+        this.sourceDirectories = sourceDirectories;
+        return this;
+    }
+
+    public DomainObjectSet<IdeaSourceDirectory> getTestDirectories() {
+        return new ImmutableDomainObjectSet<IdeaSourceDirectory>(testDirectories);
+    }
+
+    public DefaultIdeaContentRoot setTestDirectories(Set<IdeaSourceDirectory> testDirectories) {
+        this.testDirectories = testDirectories;
+        return this;
+    }
+
+    public Set<File> getExcludeDirectories() {
+        return excludeDirectories;
+    }
+
+    public DefaultIdeaContentRoot setExcludeDirectories(Set<File> excludeDirectories) {
+        this.excludeDirectories = excludeDirectories;
+        return this;
+    }
+
+    public String toString() {
+        return "IdeaContentRoot{"
+                + "rootDirectory=" + rootDirectory
+                + ", sourceDirectories count=" + sourceDirectories.size()
+                + ", testDirectories count=" + testDirectories.size()
+                + ", excludeDirectories count=" + excludeDirectories.size()
+                + '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaDependencyScope.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaDependencyScope.java
new file mode 100644
index 0000000..815e4d2
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaDependencyScope.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.idea.IdeaDependencyScope;
+
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 8/3/11
+ */
+public class DefaultIdeaDependencyScope implements IdeaDependencyScope, Serializable {
+
+    String scope;
+
+    public DefaultIdeaDependencyScope(String scope) {
+        this.scope = scope;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaDependencyScope{"
+                + "scope='" + scope + '\''
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultIdeaDependencyScope)) {
+            return false;
+        }
+
+        DefaultIdeaDependencyScope that = (DefaultIdeaDependencyScope) o;
+
+        if (scope != null ? !scope.equals(that.scope) : that.scope != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return scope != null ? scope.hashCode() : 0;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaLanguageLevel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaLanguageLevel.java
new file mode 100644
index 0000000..fa0313a
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaLanguageLevel.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.idea.IdeaLanguageLevel;
+
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 7/30/11
+ */
+public class DefaultIdeaLanguageLevel implements IdeaLanguageLevel, Serializable {
+
+    private final String level;
+
+    public DefaultIdeaLanguageLevel(String level) {
+        this.level = level;
+    }
+
+    public boolean isJDK_1_4() {
+        return "JDK_1_4".equals(level);
+    }
+
+    public boolean isJDK_1_5() {
+        return "JDK_1_5".equals(level);
+    }
+
+    public boolean isJDK_1_6() {
+        return "JDK_1_6".equals(level);
+    }
+
+    public boolean isJDK_1_7() {
+        return "JDK_1_7".equals(level);
+    }
+
+    public boolean isJDK_1_8() {
+        return "JDK_1_8".equals(level);
+    }
+
+    public String getLevel() {
+        return level;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaLanguageLevel{level='" + level + "'}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultIdeaLanguageLevel)) {
+            return false;
+        }
+
+        DefaultIdeaLanguageLevel that = (DefaultIdeaLanguageLevel) o;
+
+        if (level != null ? !level.equals(that.level) : that.level != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return level != null ? level.hashCode() : 0;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
new file mode 100644
index 0000000..7f95b4c
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.HierarchicalElement;
+import org.gradle.tooling.model.Task;
+import org.gradle.tooling.model.idea.*;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author: Szczepan Faber, created at: 7/25/11
+ */
+public class DefaultIdeaModule implements Serializable, IdeaModule {
+
+    private String name;
+    private List<? extends IdeaContentRoot> contentRoots = new LinkedList<IdeaContentRoot>();
+    private IdeaProject parent;
+
+    private File moduleFileDir;
+    private List<IdeaDependency> dependencies = new LinkedList<IdeaDependency>();
+    private GradleProject gradleProject;
+
+    private IdeaCompilerOutput compilerOutput;
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultIdeaModule setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public DomainObjectSet<? extends IdeaContentRoot> getContentRoots() {
+        return new ImmutableDomainObjectSet<IdeaContentRoot>(contentRoots);
+    }
+
+    public DefaultIdeaModule setContentRoots(List<? extends IdeaContentRoot> contentRoots) {
+        this.contentRoots = contentRoots;
+        return this;
+    }
+
+    public IdeaProject getParent() {
+        return parent;
+    }
+
+    public IdeaProject getProject() {
+        return parent;
+    }
+
+    public DefaultIdeaModule setParent(IdeaProject parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    public File getModuleFileDir() {
+        return moduleFileDir;
+    }
+
+    public DefaultIdeaModule setModuleFileDir(File moduleFileDir) {
+        this.moduleFileDir = moduleFileDir;
+        return this;
+    }
+
+    public DomainObjectSet<IdeaDependency> getDependencies() {
+        return new ImmutableDomainObjectSet<IdeaDependency>(dependencies);
+    }
+
+    public DefaultIdeaModule setDependencies(List<IdeaDependency> dependencies) {
+        this.dependencies = dependencies;
+        return this;
+    }
+
+    public DomainObjectSet<? extends Task> getTasks() {
+        throw new RuntimeException("not yet implemented");
+    }
+
+    public DomainObjectSet<? extends HierarchicalElement> getChildren() {
+        return new ImmutableDomainObjectSet<HierarchicalElement>(Collections.<HierarchicalElement>emptySet());
+    }
+
+    public String getDescription() {
+        return null;
+    }
+
+    public GradleProject getGradleProject() {
+        return gradleProject;
+    }
+
+    public DefaultIdeaModule setGradleProject(GradleProject gradleProject) {
+        this.gradleProject = gradleProject;
+        return this;
+    }
+
+    public IdeaCompilerOutput getCompilerOutput() {
+        return compilerOutput;
+    }
+
+    public DefaultIdeaModule setCompilerOutput(IdeaCompilerOutput compilerOutput) {
+        this.compilerOutput = compilerOutput;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaModule{"
+                + "name='" + name + '\''
+                + ", gradleProject='" + gradleProject + '\''
+                + ", contentRoots=" + contentRoots
+                + ", compilerOutput=" + compilerOutput
+                + ", moduleFileDir=" + moduleFileDir
+                + ", dependencies count=" + dependencies.size()
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java
new file mode 100644
index 0000000..f77ed76
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModuleDependency.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.idea.IdeaDependencyScope;
+import org.gradle.tooling.model.idea.IdeaModule;
+import org.gradle.tooling.model.idea.IdeaModuleDependency;
+
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 7/26/11
+ */
+public class DefaultIdeaModuleDependency implements IdeaModuleDependency, Serializable {
+
+    private IdeaDependencyScope scope;
+    private IdeaModule dependencyModule;
+    private boolean exported;
+
+    public IdeaDependencyScope getScope() {
+        return scope;
+    }
+
+    public DefaultIdeaModuleDependency setScope(IdeaDependencyScope scope) {
+        this.scope = scope;
+        return this;
+    }
+
+    public IdeaModule getDependencyModule() {
+        return dependencyModule;
+    }
+
+    public DefaultIdeaModuleDependency setDependencyModule(IdeaModule dependencyModule) {
+        this.dependencyModule = dependencyModule;
+        return this;
+    }
+
+    public boolean getExported() {
+        return exported;
+    }
+
+    public DefaultIdeaModuleDependency setExported(boolean exported) {
+        this.exported = exported;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultIdeaModuleDependency{"
+                 + "scope='" + scope + '\''
+                 + ", dependencyModule name='" + dependencyModule.getName() + '\''
+                 + ", exported=" + exported
+                 + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
new file mode 100644
index 0000000..c97f967
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.api.GradleException;
+import org.gradle.tooling.internal.protocol.InternalIdeaProject;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.HierarchicalElement;
+import org.gradle.tooling.model.idea.IdeaLanguageLevel;
+import org.gradle.tooling.model.idea.IdeaModule;
+import org.gradle.tooling.model.idea.IdeaProject;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * @author: Szczepan Faber, created at: 7/25/11
+ */
+public class DefaultIdeaProject implements InternalIdeaProject, IdeaProject, Serializable {
+
+//    public static final long serialVersionUID = 1L;
+
+    private String id;
+    private String name;
+    private String description;
+    private Collection<? extends IdeaModule> children = new LinkedList<IdeaModule>();
+    private IdeaLanguageLevel languageLevel;
+    private String jdkName;
+
+    public IdeaLanguageLevel getLanguageLevel() {
+        return languageLevel;
+    }
+
+    public DefaultIdeaProject setLanguageLevel(IdeaLanguageLevel languageLevel) {
+        this.languageLevel = languageLevel;
+        return this;
+    }
+
+    public String getJdkName() {
+        return jdkName;
+    }
+
+    public DefaultIdeaProject setJdkName(String jdkName) {
+        this.jdkName = jdkName;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public DefaultIdeaProject setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public DefaultIdeaProject setDescription(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public HierarchicalElement getParent() {
+        return null;
+    }
+
+    public File getProjectDirectory() {
+        throw new GradleException("This method should not be used.");
+    }
+
+    public String getPath() {
+        throw new GradleException("This method should not be used.");
+    }
+
+    public DefaultIdeaProject setChildren(Collection<? extends IdeaModule> children) {
+        this.children = children;
+        return this;
+    }
+
+    public DomainObjectSet<? extends IdeaModule> getChildren() {
+        return new ImmutableDomainObjectSet<IdeaModule>(children);
+    }
+
+    public DomainObjectSet<? extends IdeaModule> getModules() {
+        return new ImmutableDomainObjectSet<IdeaModule>(children);
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultIdeaProject{"
+                + " name='" + name + '\''
+                + ", description='" + description + '\''
+                + ", children count=" + children.size()
+                + ", languageLevel='" + languageLevel + '\''
+                + ", jdkName='" + jdkName + '\''
+                + '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
new file mode 100644
index 0000000..d58d1ee
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.idea.IdeaDependencyScope;
+import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 7/26/11
+ */
+public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryLibraryDependency, Serializable {
+
+    private File file;
+    private File source;
+    private File javadoc;
+    private Boolean exported;
+    private IdeaDependencyScope scope;
+
+    public File getFile() {
+        return file;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setFile(File file) {
+        this.file = file;
+        return this;
+    }
+
+    public File getSource() {
+        return source;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setSource(File source) {
+        this.source = source;
+        return this;
+    }
+
+    public File getJavadoc() {
+        return javadoc;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setJavadoc(File javadoc) {
+        this.javadoc = javadoc;
+        return this;
+    }
+
+    public boolean getExported() {
+        return exported;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setExported(Boolean exported) {
+        this.exported = exported;
+        return this;
+    }
+
+    public IdeaDependencyScope getScope() {
+        return scope;
+    }
+
+    public DefaultIdeaSingleEntryLibraryDependency setScope(IdeaDependencyScope scope) {
+        this.scope = scope;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "IdeaLibraryDependency{"
+                + "file=" + file
+                + ", source=" + source
+                + ", javadoc=" + javadoc
+                + ", exported=" + exported
+                + ", scope='" + scope + '\''
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSourceDirectory.java
new file mode 100644
index 0000000..72021d1
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSourceDirectory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.tooling.internal.idea;
+
+import org.gradle.tooling.model.idea.IdeaSourceDirectory;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * @author: Szczepan Faber, created at: 7/27/11
+ */
+public class DefaultIdeaSourceDirectory implements IdeaSourceDirectory, Serializable {
+
+    private File directory;
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public DefaultIdeaSourceDirectory setDirectory(File directory) {
+        this.directory = directory;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultIdeaSourceDirectory{"
+                + "directory=" + directory
+                + '}';
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java
index 4955b20..853f6b2 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/BuildOperationParametersVersion1.java
@@ -50,4 +50,6 @@ public interface BuildOperationParametersVersion1 extends LongRunningOperationPa
     TimeUnit getDaemonMaxIdleTimeUnits();
 
     long getStartTime();
+
+    boolean getVerboseLogging();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionVersion4.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionVersion4.java
index 2f52a96..67d89bb 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionVersion4.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/ConnectionVersion4.java
@@ -20,7 +20,9 @@ package org.gradle.tooling.internal.protocol;
  *
  * <p>Implementations must be thread-safe.
  *
- * <p>DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.</p>
+ * <p>
+ * Changes to this interface may break the cross-version protocol.
+ * If you change it, make sure you run the all tooling api tests to flush out compatibility issues.
  */
 public interface ConnectionVersion4 {
     /**
@@ -36,10 +38,13 @@ public interface ConnectionVersion4 {
 
     /**
      * Fetches a snapshot of the model for the project.
+     * <p>
+     * Deprecated, please use {@link InternalConnection#getTheModel(Class, BuildOperationParametersVersion1)}
      *
      * @throws UnsupportedOperationException When the given model type is not supported.
      * @throws IllegalStateException When this connection has been stopped.
      */
+    @Deprecated
     ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) throws UnsupportedOperationException, IllegalStateException;
 
     /**
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBasicIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBasicIdeaProject.java
new file mode 100644
index 0000000..6b4a3f4
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBasicIdeaProject.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * See {@link org.gradle.tooling.internal.protocol.InternalProtocolInterface}
+ */
+public interface InternalBasicIdeaProject extends ProjectVersion3, InternalProtocolInterface {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java
new file mode 100644
index 0000000..332d79b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalBuildEnvironment.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * Marker interface for the internal protocol purposes.
+ * Corresponding client facing model is {@link org.gradle.tooling.model.build.BuildEnvironment}
+ * <p>
+ * by Szczepan Faber, created at: 12/17/11
+ */
+public interface InternalBuildEnvironment extends InternalProtocolInterface {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalConnection.java
new file mode 100644
index 0000000..1040363
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalConnection.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * by Szczepan Faber, created at: 1/1/12
+ */
+public interface InternalConnection extends ConnectionVersion4, InternalProtocolInterface {
+
+    /**
+     * Fetches a snapshot of the model for the project. This method is generic so that we're not locked
+     * to building particular model type.
+     * <p>
+     * The other method on the interface, e.g. {@link #getModel(Class, BuildOperationParametersVersion1)} should be considered deprecated
+     *
+     * @throws UnsupportedOperationException When the given model type is not supported.
+     * @throws IllegalStateException When this connection has been stopped.
+     */
+    <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 operationParameters) throws UnsupportedOperationException, IllegalStateException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalGradleProject.java
new file mode 100644
index 0000000..3c03a83
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalGradleProject.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * See {@link InternalProtocolInterface}
+ */
+public interface InternalGradleProject extends ProjectVersion3, InternalProtocolInterface {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java
new file mode 100644
index 0000000..4186fac
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalIdeaProject.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * See {@link InternalProtocolInterface}
+ */
+public interface InternalIdeaProject extends ProjectVersion3, InternalProtocolInterface {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java
new file mode 100644
index 0000000..f97648d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProtocolInterface.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * I needed this interface so that it is possible to develop new features incrementally.
+ * In general I'd like to avoid growing VersionX interfaces
+ * because we have an excellent test suite that tells the story of what has changed and when
+ * <p>
+ * A marker interface to document the problem consistently.
+ * Might live only until we gradually remove old VersionX types.
+ * <p>
+ * If you make changes to inheritor of this interfaces make sure you run all compatibility tests.
+ *
+ * @author: Szczepan Faber, created at: 8/5/11
+ */
+public interface InternalProtocolInterface {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalTestModel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalTestModel.java
new file mode 100644
index 0000000..53fecb3
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalTestModel.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol;
+
+/**
+ * For testing purposes only
+ * <p>
+ * by Szczepan Faber, created at: 12/21/11
+ */
+public interface InternalTestModel extends ProjectVersion3 {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java
index 5bf5da8..5eec6bd 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/LongRunningOperationParametersVersion1.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.tooling.internal.protocol;
 
+import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
@@ -41,4 +42,11 @@ public interface LongRunningOperationParametersVersion1 {
      * @return The listener. Must not be null.
      */
     ProgressListenerVersion1 getProgressListener();
+
+    /**
+     * Returns the input stream to that can be consumed.
+     *
+     * @return The input stream. May be null.
+     */
+    InputStream getStandardInput();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java
new file mode 100644
index 0000000..1e6eebe
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/eclipse/EclipseLinkedResourceVersion1.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.tooling.internal.protocol.eclipse;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ *
+ * @author: Szczepan Faber, created at: 6/11/11
+ */
+public interface EclipseLinkedResourceVersion1 {
+
+    String getName();
+
+    String getType();
+
+    String getLocation();
+
+    String getLocationUri();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/exceptions/InternalUnsupportedBuildArgumentException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/exceptions/InternalUnsupportedBuildArgumentException.java
new file mode 100644
index 0000000..0b7767b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/exceptions/InternalUnsupportedBuildArgumentException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.tooling.internal.protocol.exceptions;
+
+/**
+ * DO NOT CHANGE THIS INTERFACE. It is part of the cross-version protocol.
+ */
+public class InternalUnsupportedBuildArgumentException extends RuntimeException {
+
+    public InternalUnsupportedBuildArgumentException(String message) {
+        super(message);
+    }
+
+    public InternalUnsupportedBuildArgumentException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/reflect/CompatibleIntrospector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/reflect/CompatibleIntrospector.java
new file mode 100644
index 0000000..bb2a863
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/reflect/CompatibleIntrospector.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.tooling.internal.reflect;
+
+import java.lang.reflect.Method;
+
+/**
+ * Uses reflection to find out / call methods.
+ *
+ * by Szczepan Faber, created at: 12/9/11
+ */
+public class CompatibleIntrospector {
+
+    private final Object target;
+
+    public CompatibleIntrospector(Object target) {
+        this.target = target;
+    }
+
+    private Method getMethod(String methodName) throws NoSuchMethodException {
+        Method[] methods = target.getClass().getDeclaredMethods();
+        for (Method m : methods) {
+            if (m.getName().equals(methodName)) {
+                return m;
+            }
+        }
+        throw new NoSuchMethodException("No such method: '" + methodName + "' on type: '" + target.getClass().getSimpleName() + "'.");
+    }
+
+    public <T> T getSafely(T defaultValue, String methodName) {
+        try {
+            Method method = getMethod(methodName);
+            method.setAccessible(true);
+            return (T) method.invoke(target);
+        } catch (NoSuchMethodException e) {
+            return defaultValue;
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to get value reflectively", e);
+        }
+    }
+
+    public void callSafely(String methodName, Object ... params) {
+        Method method;
+        try {
+            method = getMethod(methodName);
+        } catch (NoSuchMethodException e) {
+            return; // ignore
+        }
+
+        method.setAccessible(true);
+        try {
+            method.invoke(target, params);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to call method reflectively", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
new file mode 100644
index 0000000..1390b91
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Represents an element which has Gradle tasks associated with it.
+ *
+ * @since 1.0-milestone-5
+ */
+public interface BuildableElement extends Element {
+
+    /**
+     * Returns the tasks of this project.
+     *
+     * @return The tasks.
+     */
+    DomainObjectSet<? extends Task> getTasks();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableProject.java
deleted file mode 100644
index b59f90c..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableProject.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2011 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.tooling.model;
-
-/**
- * Represents a project which has Gradle tasks associated with it.
- */
-public interface BuildableProject extends Project {
-    /**
-     * Returns the tasks of this project.
-     *
-     * @return The tasks.
-     */
-    DomainObjectSet<? extends Task> getTasks();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
new file mode 100644
index 0000000..40bc19c
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Described model element
+ *
+ * @since 1.0-milestone-5
+ */
+public interface Element extends Model {
+
+    /**
+     * Returns the name. Note that the name is not a unique identifier.
+     *
+     * @return The name.
+     */
+    String getName();
+
+    /**
+     * Returns the description.
+     *
+     * @return The description. May be null.
+     */
+    String getDescription();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
new file mode 100644
index 0000000..def0938
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Gradle project.
+ *
+ * @since 1.0-milestone-5
+ */
+public interface GradleProject extends HierarchicalElement, BuildableElement {
+
+    /**
+     * Returns the tasks of this project.
+     *
+     * @return The tasks.
+     */
+    DomainObjectSet<? extends GradleTask> getTasks();
+
+    /**
+     * {@inheritDoc}
+     */
+    GradleProject getParent();
+
+    /**
+     * {@inheritDoc}
+     */
+    DomainObjectSet<? extends GradleProject> getChildren();
+
+    /**
+     * Returns gradle path
+     *
+     * @return The path.
+     */
+    String getPath();
+
+    /**
+     * searches all descendants (children, grand children, etc.), including self, by given path.
+     *
+     * @return gradle project with matching path or null if not found
+     */
+    GradleProject findByPath(String path);
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
new file mode 100644
index 0000000..d003832
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Represents a task which is executable by Gradle.
+ *
+ * @since 1.0-milestone-5
+ */
+public interface GradleTask extends Task {
+
+    /**
+     * Returns the gradle project this task is defined in.
+     *
+     * @return The element.
+     */
+    GradleProject getProject();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java
new file mode 100644
index 0000000..f271e9b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Is associated with a Gradle project.
+ * <p>
+ * Via the gradle project you can access (list, run, etc.) gradle tasks
+ */
+public interface HasGradleProject {
+
+    /**
+     * The associated gradle project.
+     * <p>
+     * Via the gradle project you can access (list, run, etc.) gradle tasks
+     */
+    GradleProject getGradleProject();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
new file mode 100644
index 0000000..5f75423
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Represents an element which belongs to some hierarchy.
+ *
+ * @since 1.0-milestone-5
+ */
+public interface HierarchicalElement extends Element {
+
+    /**
+     * Returns the parent of this element, if any.
+     *
+     * @return The parent, or null if it has no parent.
+     */
+    HierarchicalElement getParent();
+
+    /**
+     * Returns the child elements.
+     *
+     * @return The child elements. Returns an empty set if it has no children.
+     */
+    DomainObjectSet<? extends HierarchicalElement> getChildren();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalProject.java
deleted file mode 100644
index 08ee969..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalProject.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2011 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.tooling.model;
-
-/**
- * Represents a project which belongs to some hierarchy.
- */
-public interface HierarchicalProject extends Project {
-    /**
-     * Returns the parent project of this project, if any.
-     *
-     * @return The parent, or null if this project has no parent.
-     */
-    HierarchicalProject getParent();
-
-    /**
-     * Returns the child projects of this project.
-     *
-     * @return The child projects. Returns an empty set if this project has no children.
-     */
-    DomainObjectSet<? extends HierarchicalProject> getChildren();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
new file mode 100644
index 0000000..58644df
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * A Model that is buildable by the Tooling API.
+ * Models contain various information regarding the build.
+ * Models are typically tailored to specific domain (for example build environment or IDE, etc.)
+ *
+ * @since 1.0-milestone-8
+ */
+public interface Model {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Project.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Project.java
deleted file mode 100644
index b87d22a..0000000
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Project.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2011 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.tooling.model;
-
-import java.io.File;
-
-/**
- * Represents a project of some kind.
- */
-public interface Project {
-    /**
-     * Returns the fully-qualified path of this project. This is a unique identifier for the project.
-     *
-     * @return The path.
-     */
-    String getPath();
-
-    /**
-     * Returns the name of this project. Note that the name is not a unique identifier for the project.
-     *
-     * @return The name.
-     */
-    String getName();
-
-    /**
-     * Returns the description of this project.
-     *
-     * @return The description. May be null.
-     */
-    String getDescription();
-
-    /**
-     * Returns the project directory for this project.
-     *
-     * @return The project directory.
-     */
-    File getProjectDirectory();
-}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ProjectDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ProjectDependency.java
index def051b..e3dc17a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ProjectDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ProjectDependency.java
@@ -19,10 +19,5 @@ package org.gradle.tooling.model;
  * Represents a dependency on another project.
  */
 public interface ProjectDependency extends Dependency {
-    /**
-     * Returns the target of this dependency.
-     *
-     * @return The target project. Does not return null.
-     */
-    Project getTargetProject();
+
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java
index 043e77e..256b3ca 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java
@@ -39,9 +39,10 @@ public interface Task {
     String getDescription();
 
     /**
-     * Returns the project which this task belongs to.
+     * Returns the element which this task belongs to.
      *
-     * @return The project.
+     * @return The element.
      */
-    Project getProject();
+    Element getProject();
+    //TODO SF rename to 'owner'? I'd deprecate the Task interface and leave only GradleTask (or push this method down to the GradleTask)
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java
new file mode 100644
index 0000000..84fd2cc
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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.tooling.model;
+
+/**
+ * Thrown when the tooling api client client attempts to use a method that does not exist
+ * in the version of gradle the tooling api is connected to.
+ * <p>
+ * Typically, to resolve such problem you change/upgrade the target version of Gradle the tooling api is connected to.
+ * Alternatively, you can handle and ignore this exception.
+ *
+ * @since 1.0-milestone-8
+ */
+public class UnsupportedMethodException extends UnsupportedOperationException {
+
+    public UnsupportedMethodException(String s) {
+        super(s);
+    }
+
+    public UnsupportedMethodException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
new file mode 100644
index 0000000..f61f2bd
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2011 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.tooling.model.build;
+
+import org.gradle.tooling.model.Model;
+import org.gradle.tooling.model.UnsupportedMethodException;
+
+/**
+ * Informs about the build environment, like Gradle version or the java home in use.
+ * <p>
+ * Example:
+ * <pre autoTested=''>
+ * ProjectConnection connection = GradleConnector.newConnector()
+ *    .forProjectDirectory(new File("someProjectFolder"))
+ *    .connect();
+ *
+ * try {
+ *    BuildEnvironment env = connection.getModel(BuildEnvironment.class);
+ *    System.out.println("Gradle version: " + env.getGradle().getGradleVersion());
+ *    System.out.println("Java home: " + env.getJava().getJavaHome());
+ * } finally {
+ *    connection.close();
+ * }
+ * </pre>
+ *
+ * @since 1.0-milestone-8
+ */
+public interface BuildEnvironment extends Model {
+
+    /**
+     * Informs about the gradle environment, for example the gradle version.
+     */
+    GradleEnvironment getGradle();
+
+    /**
+     * Informs about the java environment, for example the java home or the jvm args used.
+     *
+     * @throws org.gradle.tooling.model.UnsupportedMethodException
+     * when the gradle version the tooling api is connected to does not support the java environment information.
+     */
+    JavaEnvironment getJava() throws UnsupportedMethodException;
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
new file mode 100644
index 0000000..71763b8
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.tooling.model.build;
+
+/**
+ * Informs about the gradle environment, for example the gradle version.
+ * <p>
+ * See example in {@link BuildEnvironment}
+ *
+ * @since 1.0-milestone-8
+ */
+public interface GradleEnvironment {
+
+    /**
+     * Informs about the gradle version.
+     */
+    String getGradleVersion();
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
new file mode 100644
index 0000000..b8d6bb7
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.tooling.model.build;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Informs about the java environment, for example the java home or the jvm args used.
+ * <p>
+ * See example in {@link BuildEnvironment}
+ *
+ * @since 1.0-milestone-8
+ */
+public interface JavaEnvironment {
+
+    /**
+     * The java home used for gradle operations (e.g. running tasks or acquiring model information, etc).
+     */
+    File getJavaHome();
+
+    /**
+     * The jvm arguments used to start the java process that handles gradle operations
+     * (e.g. running tasks or acquiring model information, etc).
+     * <p>
+     * The returned jvm arguments that were used to start the java process.
+     * They do not include system properties passed as -Dfoo=bar.
+     * They may include the implicitly immutable system properties like "file.encoding".
+     */
+    List<String> getJvmArguments();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
new file mode 100644
index 0000000..431bfe2
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Models the build environment information like gradle or java versions
+ */
+package org.gradle.tooling.model.build;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java
new file mode 100644
index 0000000..f007053
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseLinkedResource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.tooling.model.eclipse;
+
+/**
+ * Linked resources are files and folders that are stored in locations in the file system outside of the project's location.
+ *
+ * @since 1.0-milestone-4
+ */
+public interface EclipseLinkedResource {
+
+    /**
+     * The project-relative path of the linked resource as it appears in the workspace.
+     * <p>
+     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     * <p>
+     * For example, a linked resource to a file system folder /some/path/to/someFolder can have a name 'someFolder'
+     *
+     * @return name
+     */
+    String getName();
+
+    /**
+     * The resource type.
+     * <p>
+     * If 'location' property is used the values are: "1" for a file, or "2" for a folder.
+     * <p>
+     * If 'locationUri' property is used then the values are:
+     * "1" for file or folder when 'locationUri' first segment is a workspace path variable (or path variable navigation element),
+     * "2" for an eclipse virtual folder.
+     * <p>
+     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     *
+     * @return eclipse link type
+     */
+    String getType();
+
+    /**
+     * The local file system absolute path of the target of the linked resource. For example: '/path/to/somewhere'.
+     * Mutually exclusive with 'locationUri'
+     * <p>
+     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     *
+     * @return location
+     */
+    String getLocation();
+
+    /**
+     * If the file is not in the local file system, this attribute contains the absolute URI of the resource in some backing file system.
+     * Mutually exclusive with 'location'.
+     * <p>
+     * When workspace path variable is used as part of path then this property must be used instead of 'location'
+     * <p>
+     * Used for virtual folders. In that case the value is: 'virtual:/virtual'
+     * <p>
+     * See the official eclipse documentation for most up-to-date information on properties of a linked resource
+     *
+     * @return location uri
+     */
+    String getLocationUri();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java
index ec08acd..3d662c9 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProject.java
@@ -15,16 +15,19 @@
  */
 package org.gradle.tooling.model.eclipse;
 
-import org.gradle.tooling.model.BuildableProject;
 import org.gradle.tooling.model.DomainObjectSet;
 import org.gradle.tooling.model.ExternalDependency;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.HasGradleProject;
+
+import java.io.File;
 
 /**
  * The complete model of an Eclipse project.
  *
  * <p>Note that the names of Eclipse projects are unique, and can be used as an identifier for the project.
  */
-public interface EclipseProject extends HierarchicalEclipseProject, BuildableProject {
+public interface EclipseProject extends HierarchicalEclipseProject, HasGradleProject {
     /**
      * {@inheritDoc}
      */
@@ -36,9 +39,15 @@ public interface EclipseProject extends HierarchicalEclipseProject, BuildablePro
     DomainObjectSet<? extends EclipseProject> getChildren();
 
     /**
-     * {@inheritDoc}
+     * The gradle project that is associated with this project.
+     * Typically, a single eclipse project corresponds to a single gradle project.
+     * <p>
+     * See {@link HasGradleProject}
+     *
+     * @return associated gradle project
+     * @since 1.0-milestone-5
      */
-    DomainObjectSet<? extends EclipseTask> getTasks();
+    GradleProject getGradleProject();
 
     /**
      * Returns the external dependencies which make up the classpath of this project.
@@ -46,4 +55,12 @@ public interface EclipseProject extends HierarchicalEclipseProject, BuildablePro
      * @return The dependencies. Returns an empty set if the project has no external dependencies.
      */
     DomainObjectSet<? extends ExternalDependency> getClasspath();
+
+    /**
+     * Returns the project directory for this project.
+     *
+     * @return The project directory.
+     */
+    File getProjectDirectory();
+
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java
index da69715..94c1cf0 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseProjectDependency.java
@@ -22,8 +22,10 @@ import org.gradle.tooling.model.ProjectDependency;
  */
 public interface EclipseProjectDependency extends ProjectDependency {
     /**
-     * {@inheritDoc}
-     */
+    * Returns the target of this dependency.
+    *
+    * @return The target project. Does not return null.
+    */
     HierarchicalEclipseProject getTargetProject();
 
     /**
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java
index 93d15d5..8c3d7b2 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/EclipseTask.java
@@ -18,8 +18,9 @@ package org.gradle.tooling.model.eclipse;
 import org.gradle.tooling.model.Task;
 
 /**
- * An Eclipse centric view of a Gradle task.
+ * Deprecated because gradle tasks are not associated with eclipse projects.
  */
+ at Deprecated
 public interface EclipseTask extends Task {
     /**
      * {@inheritDoc}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java
index 62e5747..1c5e460 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/HierarchicalEclipseProject.java
@@ -16,12 +16,15 @@
 package org.gradle.tooling.model.eclipse;
 
 import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.tooling.model.HierarchicalProject;
+import org.gradle.tooling.model.HierarchicalElement;
+import org.gradle.tooling.model.UnsupportedMethodException;
+
+import java.io.File;
 
 /**
  * Represents the basic information about an Eclipse project.
  */
-public interface HierarchicalEclipseProject extends HierarchicalProject {
+public interface HierarchicalEclipseProject extends HierarchicalElement {
     /**
      * {@inheritDoc}
      */
@@ -45,4 +48,23 @@ public interface HierarchicalEclipseProject extends HierarchicalProject {
      * @return The source directories. Returns an empty set if the project has no source directories.
      */
     DomainObjectSet<? extends EclipseSourceDirectory> getSourceDirectories();
+
+    /**
+     * Returns the linked resources for this project.
+     *
+     * @return The linked resources.
+     * @since 1.0-milestone-4
+     * @throws org.gradle.tooling.model.UnsupportedMethodException
+     *  When the target Gradle version does not support this information.
+     *  You can safely catch ignore this exception and query the model for other information.
+     */
+    DomainObjectSet<? extends EclipseLinkedResource> getLinkedResources() throws UnsupportedMethodException;
+
+    /**
+     * Returns the project directory for this project.
+     *
+     * @return The project directory.
+     */
+    File getProjectDirectory();
+
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
new file mode 100644
index 0000000..5d9e308
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+/**
+ * IdeaProject that does not provide/resolve any external dependencies.
+ * Only project dependencies and local file dependencies are included on the modules' classpath.
+ * <p>
+ * Useful for 'previewing' the output model of IdeaProject because it supposed to be fast (e.g. does not download dependencies from the web).
+ */
+public interface BasicIdeaProject extends IdeaProject {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java
new file mode 100644
index 0000000..44cc9b2
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import java.io.File;
+
+/**
+ * Idea compiler ouput settings
+ */
+public interface IdeaCompilerOutput {
+
+    /**
+     * whether current module should inherit project's output directory.
+     *
+     * @return inherit output dirs flag
+     * @see #getOutputDir()
+     * @see #getTestOutputDir()
+     */
+    boolean getInheritOutputDirs();
+
+    /**
+     * directory to store module's production classes and resources.
+     *
+     * @return directory to store production output. non-<code>null</code> if
+     *            {@link #getInheritOutputDirs()} returns <code>'false'</code>
+     */
+    File getOutputDir();
+
+    /**
+     * directory to store module's test classes and resources.
+     *
+     * @return directory to store test output. non-<code>null</code> if
+     *            {@link #getInheritOutputDirs()} returns <code>'false'</code>
+     */
+    File getTestOutputDir();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java
new file mode 100644
index 0000000..5246288
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import org.gradle.tooling.model.DomainObjectSet;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Contains content root information
+ */
+public interface IdeaContentRoot {
+
+    /**
+     * root directory
+     */
+    File getRootDirectory();
+
+    /**
+     * source dirs.
+     */
+    DomainObjectSet<? extends IdeaSourceDirectory> getSourceDirectories();
+
+    /**
+     * test dirs.
+     */
+    DomainObjectSet<? extends IdeaSourceDirectory> getTestDirectories();
+
+    /**
+     * exclude dirs
+     */
+    Set<File> getExcludeDirectories();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
new file mode 100644
index 0000000..0fba0d7
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import org.gradle.tooling.model.Dependency;
+
+/**
+ * Idea dependency
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaDependency extends Dependency {
+
+    /**
+     * scope of the current dependency. Not-<code>null</code> all the time
+     *
+     * @return scope
+     */
+    IdeaDependencyScope getScope();
+
+    /**
+     * Allows to check if current dependency is transitive, i.e. is visible to the module which depends on module that has current dependency.
+     *
+     * @return <code>true</code> if current dependency is transitive; <code>false</code> otherwise
+     */
+    boolean getExported();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java
new file mode 100644
index 0000000..8f68fc4
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+/**
+ * The scope of the Idea dependency
+ */
+public interface IdeaDependencyScope {
+
+    String getScope();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java
new file mode 100644
index 0000000..5cf1987
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+/**
+ * Language level setting for IDEA
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaLanguageLevel {
+
+    /**
+     * Gets the level value
+     */
+    String getLevel();
+
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
new file mode 100644
index 0000000..18a54a3
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.GradleProject;
+import org.gradle.tooling.model.HasGradleProject;
+import org.gradle.tooling.model.HierarchicalElement;
+
+/**
+ * Represents information about the IntelliJ IDEA module
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaModule extends HierarchicalElement, HasGradleProject {
+
+    /**
+     * All content roots. Most idea modules have a single content root.
+     *
+     * @return content roots
+     */
+    DomainObjectSet<? extends IdeaContentRoot> getContentRoots();
+
+    /**
+     * The gradle project that is associated with this module.
+     * Typically, a single module corresponds to a single gradle project.
+     * <p>
+     * See {@link HasGradleProject}
+     *
+     * @return associated gradle project
+     */
+    GradleProject getGradleProject();
+
+    /**
+     * Returns the project of this module.
+     * Alias to {@link #getProject()}
+     *
+     * @return idea project
+     */
+    IdeaProject getParent();
+
+    /**
+     * Returns the project of this module.
+     * Alias to {@link #getParent()}
+     *
+     * @return idea project
+     */
+    IdeaProject getProject();
+
+    /**
+     * information about idea compiler output (output dirs, inheritance of output dir, etc.)
+     */
+    IdeaCompilerOutput getCompilerOutput();
+
+    /**
+     * dependencies of this module (i.e. module dependencies, library dependencies, etc.)
+     *
+     * @return dependencies
+     */
+    DomainObjectSet<? extends IdeaDependency> getDependencies();
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java
new file mode 100644
index 0000000..f4bd57f
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+/**
+ * dependency to a module in a project
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaModuleDependency extends IdeaDependency {
+
+    /**
+     * returns dependency module
+     *
+     * @return dependency module
+     */
+    IdeaModule getDependencyModule();
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
new file mode 100644
index 0000000..658dd12
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.Element;
+import org.gradle.tooling.model.HierarchicalElement;
+
+/**
+ * Represents the information about the IntelliJ IDEA project
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaProject extends HierarchicalElement, Element {
+
+    /**
+     * The name of the jdk
+     *
+     * @return jdk name
+     */
+    String getJdkName();
+
+    /**
+     * Language level to use within the current project.
+     *
+     * @return language level
+     */
+    IdeaLanguageLevel getLanguageLevel();
+
+    /**
+     * Returns modules of this idea project. Most projects have at least one module.
+     * Alias to {@link #getModules()}
+     *
+     * @return modules
+     */
+    DomainObjectSet<? extends IdeaModule> getChildren();
+
+    /**
+     * Returns modules of this idea project. Most projects have at least one module.
+     * Alias to {@link #getChildren()}
+     *
+     * @return modules
+     */
+    DomainObjectSet<? extends IdeaModule> getModules();
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java
new file mode 100644
index 0000000..61c0791
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import org.gradle.tooling.model.ExternalDependency;
+
+import java.io.File;
+
+/**
+ * "Single-Entry Module Library" as IDEA calls it. For example a single jar file with sources jar.
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaSingleEntryLibraryDependency extends IdeaDependency, ExternalDependency {
+    /**
+     * Returns the file for this dependency.
+     *
+     * @return The file. Never null.
+     */
+    File getFile();
+
+    /**
+     * Returns the source directory/archive for this dependency.
+     *
+     * @return The source file. Returns null when the source is not available for this dependency.
+     */
+    File getSource();
+
+    /**
+     * Returns the Javadoc directory/archive for this dependency.
+     *
+     * @return The Javadoc file. Returns null when the Javadoc is not available for this dependency.
+     */
+    File getJavadoc();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java
new file mode 100644
index 0000000..e5ef911
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.tooling.model.idea;
+
+import org.gradle.tooling.model.SourceDirectory;
+
+/**
+ * Idea source directory
+ *
+ * @since 1.0-milestone-5
+ */
+public interface IdeaSourceDirectory extends SourceDirectory {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
new file mode 100644
index 0000000..01dc985
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * IntelliJ IDEA related API of the tooling API
+ */
+package org.gradle.tooling.model.idea;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/Exceptions.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/Exceptions.java
new file mode 100644
index 0000000..8c64bae
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/Exceptions.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.tooling.model.internal;
+
+import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException;
+import org.gradle.tooling.model.UnsupportedMethodException;
+
+/**
+ * by Szczepan Faber, created at: 12/22/11
+ */
+public class Exceptions {
+
+    public final static String INCOMPATIBLE_VERSION_HINT =
+            "Most likely the model of that type is not supported in the target Gradle version."
+            + "\nTo resolve the problem you can change/upgrade the Gradle version the tooling api connects to.";
+
+    public static UnsupportedMethodException unsupportedMethod(String method, Throwable cause) {
+        return new UnsupportedMethodException(formatUnsupportedModelMethod(method), cause);
+    }
+
+    public static UnsupportedMethodException unsupportedMethod(String method) {
+        return new UnsupportedMethodException(formatUnsupportedModelMethod(method));
+    }
+
+    private static String formatUnsupportedModelMethod(String method) {
+        return String.format("Unsupported method: %s."
+                + "\nThe version of Gradle you connect to does not support that method."
+                + "\nTo resolve the problem you can change/upgrade the target version of Gradle you connect to."
+                + "\nAlternatively, you can ignore this exception and read other information from the model."
+                , method);
+    }
+
+    public static UnsupportedOperationConfigurationException unsupportedOperationConfiguration(String operation) {
+        //we only need that cause for backwards-compatibility.
+        UnsupportedMethodException cause = new UnsupportedMethodException(operation);
+        return new UnsupportedOperationConfigurationException(String.format("Unsupported configuration: %s."
+                + "\nYou configured the LongRunningOperation (ModelBuilder or BuildLauncher) with an unsupported option."
+                + "\nThe version of Gradle you connect to does not support this configuration option."
+                + "\nTo resolve the problem you can change/upgrade the target version of Gradle you connect to."
+                + "\nAlternatively, you may stop using this configuration option."
+                , operation), cause);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/TestModel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/TestModel.java
new file mode 100644
index 0000000..f93382e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/TestModel.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.tooling.model.internal;
+
+import org.gradle.tooling.model.Element;
+
+/**
+ * For testing purposes only
+ * <p>
+ * by Szczepan Faber, created at: 12/21/11
+ */
+public interface TestModel extends Element {}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/DefaultEclipseProjectTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/DefaultEclipseProjectTest.groovy
deleted file mode 100644
index 76fb812..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/DefaultEclipseProjectTest.groovy
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal
-
-import spock.lang.Specification
-
-class DefaultEclipseProjectTest extends Specification {
-    def usesPathForToStringValue() {
-        def project = new DefaultEclipseProject("name", ":path", null, null, [])
-
-        expect:
-        project.toString() == "project ':path'"
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/CachingToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/CachingToolingImplementationLoaderTest.groovy
deleted file mode 100644
index b4e1bc6..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/CachingToolingImplementationLoaderTest.groovy
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer
-
-import org.gradle.tooling.internal.protocol.ConnectionVersion4
-import spock.lang.Specification
-
-class CachingToolingImplementationLoaderTest extends Specification {
-    final ToolingImplementationLoader target = Mock()
-    final CachingToolingImplementationLoader loader = new CachingToolingImplementationLoader(target)
-
-    def delegatesToTargetLoaderToCreateImplementation() {
-        ConnectionVersion4 connectionImpl = Mock()
-        final Distribution distribution = Mock()
-
-        when:
-        def impl = loader.create(distribution)
-
-        then:
-        impl == connectionImpl
-        1 * target.create(distribution) >> connectionImpl
-        _ * distribution.toolingImplementationClasspath >> ([new File('a.jar')] as Set)
-        0 * _._
-    }
-
-    def reusesImplementationWithSameClasspath() {
-        ConnectionVersion4 connectionImpl = Mock()
-        final Distribution distribution = Mock()
-
-        when:
-        def impl = loader.create(distribution)
-        def impl2 = loader.create(distribution)
-
-        then:
-        impl == connectionImpl
-        impl2 == connectionImpl
-        1 * target.create(distribution) >> connectionImpl
-        _ * distribution.toolingImplementationClasspath >> ([new File('a.jar')] as Set)
-        0 * _._
-    }
-
-    def createsNewImplementationWhenClasspathNotSeenBefore() {
-        ConnectionVersion4 connectionImpl1 = Mock()
-        ConnectionVersion4 connectionImpl2 = Mock()
-        Distribution distribution1 = Mock()
-        Distribution distribution2 = Mock()
-
-        when:
-        def impl = loader.create(distribution1)
-        def impl2 = loader.create(distribution2)
-
-        then:
-        impl == connectionImpl1
-        impl2 == connectionImpl2
-        1 * target.create(distribution1) >> connectionImpl1
-        1 * target.create(distribution2) >> connectionImpl2
-        _ * distribution1.toolingImplementationClasspath >> ([new File('a.jar')] as Set)
-        _ * distribution2.toolingImplementationClasspath >> ([new File('b.jar')] as Set)
-        0 * _._
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy
index 181e59a..5095c2d 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectionFactoryTest.groovy
@@ -15,19 +15,22 @@
  */
 package org.gradle.tooling.internal.consumer
 
-import org.gradle.tooling.internal.protocol.ConnectionVersion4
-import spock.lang.Specification
 import org.gradle.listener.ListenerManager
 import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.async.DefaultAsyncConnection
+import org.gradle.tooling.internal.consumer.connection.LazyConnection
+import org.gradle.tooling.internal.consumer.connection.LoggingInitializerConnection
+import org.gradle.tooling.internal.consumer.connection.ProgressLoggingConnection
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader
+import spock.lang.Specification
 
 class ConnectionFactoryTest extends Specification {
     final ToolingImplementationLoader implementationLoader = Mock()
     final ListenerManager listenerManager = Mock()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
     final Distribution distribution = Mock()
-    final ConnectionVersion4 connectionImpl = Mock()
-    final ConnectionParameters parameters = Mock()
-    final ConnectionFactory factory = new ConnectionFactory(implementationLoader, listenerManager, progressLoggerFactory)
+    final ConnectionParameters parameters = new DefaultConnectionParameters()
+    final ConnectionFactory factory = new ConnectionFactory(implementationLoader)
 
     def usesImplementationLoaderToLoadConnectionFactory() {
         when:
@@ -36,8 +39,9 @@ class ConnectionFactoryTest extends Specification {
         then:
         result instanceof DefaultProjectConnection
         result.connection instanceof DefaultAsyncConnection
-        result.connection.connection instanceof ProgressLoggingConnection
-        result.connection.connection.connection instanceof LazyConnection
+        result.connection.connection instanceof LoggingInitializerConnection
+        result.connection.connection.connection instanceof ProgressLoggingConnection
+        result.connection.connection.connection.connection instanceof LazyConnection
         0 * _._
     }
 }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy
new file mode 100644
index 0000000..8d15931
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ConnectorServicesTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer;
+
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/6/11
+ */
+public class ConnectorServicesTest extends Specification {
+
+    def "services sharing configuration"() {
+        when:
+        def connectorOne = new ConnectorServices().createConnector()
+        def connectorTwo = new ConnectorServices().createConnector()
+
+        then:
+        connectorOne != connectorTwo
+
+        //below is necessary for some of the thread safety measures we took in the internal implementation
+        //it is covered in integrations tests as well, but is not immediately obvious why the concurrent integ test fails hence below assertions
+
+        //tooling impl loader must be shared across connectors, so that we have single DefaultConnection per distro/classpath
+        connectorOne.connectionFactory.toolingImplementationLoader == connectorTwo.connectionFactory.toolingImplementationLoader
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildLauncherTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildLauncherTest.groovy
index 48d33b6..07c901d 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildLauncherTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultBuildLauncherTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.tooling.internal.consumer
 
 import org.gradle.tooling.GradleConnectionException
 import org.gradle.tooling.ResultHandler
+import org.gradle.tooling.internal.consumer.async.AsyncConnection
 import org.gradle.tooling.model.Task
 import org.gradle.util.ConcurrentSpecification
 
@@ -92,47 +93,42 @@ class DefaultBuildLauncherTest extends ConcurrentSpecification {
     }
 
     def buildBlocksUntilResultReceived() {
-        def supplyResult = later()
+        def supplyResult = waitsForAsyncCallback()
         Task task = Mock()
 
         when:
-        def action = start {
+        supplyResult.start {
             launcher.forTasks(task).run()
         }
 
         then:
-        action.waitsFor(supplyResult)
         1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
             def handler = args[2]
-            supplyResult.finishLater {
+            supplyResult.callbackLater {
                 handler.onComplete(null)
             }
         }
     }
 
     def buildBlocksUntilFailureReceived() {
-        def supplyResult = later()
+        def supplyResult = waitsForAsyncCallback()
+        def failure = new RuntimeException()
         Task task = Mock()
 
         when:
-        def action = start {
+        supplyResult.start {
             launcher.forTasks(task).run()
         }
 
         then:
-        action.waitsFor(supplyResult)
+        GradleConnectionException e = thrown()
+        e.cause == failure
         1 * protocolConnection.executeBuild(!null, !null, !null) >> { args ->
             def handler = args[2]
-            supplyResult.finishLater {
-                handler.onFailure(new RuntimeException())
+            supplyResult.callbackLater {
+                handler.onFailure(failure)
             }
         }
-
-        when:
-        finished()
-
-        then:
-        GradleConnectionException e = thrown()
     }
 
     def task(String path) {
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy
index feefc28..1389e54 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultGradleConnectorTest.groovy
@@ -34,7 +34,9 @@ class DefaultGradleConnectorTest extends Specification {
 
         then:
         result == connection
-        1 * distributionFactory.getDefaultDistribution(projectDir) >> distribution
+
+        and:
+        1 * distributionFactory.getDefaultDistribution(projectDir, true) >> distribution
         1 * connectionFactory.create(distribution, { it.projectDir == projectDir }) >> connection
     }
 
@@ -47,7 +49,9 @@ class DefaultGradleConnectorTest extends Specification {
 
         then:
         result == connection
-        1 * distributionFactory.getDefaultDistribution(projectDir) >> distribution
+
+        and:
+        1 * distributionFactory.getDefaultDistribution(projectDir, true) >> distribution
         1 * connectionFactory.create(distribution, { it.gradleUserHomeDir == userDir }) >> connection
     }
 
@@ -60,6 +64,8 @@ class DefaultGradleConnectorTest extends Specification {
 
         then:
         result == connection
+
+        and:
         1 * distributionFactory.getDistribution(gradleHome) >> distribution
         1 * connectionFactory.create(distribution, !null) >> connection
     }
@@ -73,6 +79,8 @@ class DefaultGradleConnectorTest extends Specification {
 
         then:
         result == connection
+
+        and:
         1 * distributionFactory.getDistribution(gradleDist) >> distribution
         1 * connectionFactory.create(distribution, !null) >> connection
     }
@@ -85,6 +93,8 @@ class DefaultGradleConnectorTest extends Specification {
 
         then:
         result == connection
+
+        and:
         1 * distributionFactory.getDistribution('1.0') >> distribution
         1 * connectionFactory.create(distribution, !null) >> connection
     }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultModelBuilderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultModelBuilderTest.groovy
index 5e64b44..f08b101 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultModelBuilderTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultModelBuilderTest.groovy
@@ -17,22 +17,27 @@ package org.gradle.tooling.internal.consumer
 
 import org.gradle.tooling.GradleConnectionException
 import org.gradle.tooling.ResultHandler
+import org.gradle.tooling.internal.consumer.async.AsyncConnection
+import org.gradle.tooling.internal.consumer.protocoladapter.ModelPropertyHandler
+import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter
 import org.gradle.tooling.internal.protocol.ProjectVersion3
 import org.gradle.tooling.internal.protocol.ResultHandlerVersion1
-import org.gradle.tooling.model.Project
+import org.gradle.tooling.model.GradleProject
+import org.gradle.tooling.model.internal.Exceptions
 import org.gradle.util.ConcurrentSpecification
 
 class DefaultModelBuilderTest extends ConcurrentSpecification {
     final AsyncConnection protocolConnection = Mock()
     final ProtocolToModelAdapter adapter = Mock()
     final ConnectionParameters parameters = Mock()
-    final DefaultModelBuilder<Project> builder = new DefaultModelBuilder<Project>(Project, ProjectVersion3, protocolConnection, adapter, parameters)
+    final DefaultModelBuilder<GradleProject, ProjectVersion3> builder = new DefaultModelBuilder<GradleProject, ProjectVersion3>(GradleProject, ProjectVersion3, protocolConnection, adapter, parameters)
+    final ModelPropertyHandler modelPropertyHandler = Mock()
 
     def getModelDelegatesToProtocolConnectionToFetchModel() {
-        ResultHandler<Project> handler = Mock()
+        ResultHandler<GradleProject> handler = Mock()
         ResultHandlerVersion1<ProjectVersion3> adaptedHandler
         ProjectVersion3 result = Mock()
-        Project adaptedResult = Mock()
+        GradleProject adaptedResult = Mock()
 
         when:
         builder.get(handler)
@@ -50,13 +55,14 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         adaptedHandler.onComplete(result)
 
         then:
-        1 * adapter.adapt(Project.class, result) >> adaptedResult
+        1 * protocolConnection.versionDetails
+        1 * adapter.adapt(GradleProject.class, result, _ as ModelPropertyHandler) >> adaptedResult
         1 * handler.onComplete(adaptedResult)
         0 * _._
     }
 
     def getModelWrapsFailureToFetchModel() {
-        ResultHandler<Project> handler = Mock()
+        ResultHandler<GradleProject> handler = Mock()
         ResultHandlerVersion1<ProjectVersion3> adaptedHandler
         RuntimeException failure = new RuntimeException()
         GradleConnectionException wrappedFailure
@@ -73,64 +79,73 @@ class DefaultModelBuilderTest extends ConcurrentSpecification {
         then:
         1 * handler.onFailure(!null) >> {args -> wrappedFailure = args[0] }
         _ * protocolConnection.displayName >> '[connection]'
-        wrappedFailure.message == 'Could not fetch model of type \'Project\' using [connection].'
+        wrappedFailure.message == 'Could not fetch model of type \'GradleProject\' using [connection].'
         wrappedFailure.cause.is(failure)
         0 * _._
     }
 
+    def "provides compatibility hint on failure"() {
+        ResultHandler<GradleProject> handler = Mock()
+        ResultHandlerVersion1<ProjectVersion3> adaptedHandler
+        RuntimeException failure = new UnsupportedOperationException()
+        GradleConnectionException wrappedFailure
+
+        when:
+        builder.get(handler)
+
+        then:
+        1 * protocolConnection.getModel(!null, !null, !null) >> {args -> adaptedHandler = args[2]}
+
+        when:
+        adaptedHandler.onFailure(failure)
+
+        then:
+        1 * handler.onFailure(!null) >> {args -> wrappedFailure = args[0] }
+        wrappedFailure.message.contains(Exceptions.INCOMPATIBLE_VERSION_HINT)
+        wrappedFailure.cause.is(failure)
+    }
+
     def getModelBlocksUntilResultReceivedFromProtocolConnection() {
-        def supplyResult = later()
+        def supplyResult = waitsForAsyncCallback()
         ProjectVersion3 result = Mock()
-        Project adaptedResult = Mock()
-        _ * adapter.adapt(Project.class, result) >> adaptedResult
+        GradleProject adaptedResult = Mock()
+        _ * adapter.adapt(GradleProject.class, result, _) >> adaptedResult
 
         when:
         def model
-        def action = start {
+        supplyResult.start {
             model = builder.get()
         }
 
         then:
-        action.waitsFor(supplyResult)
+        model == adaptedResult
         1 * protocolConnection.getModel(!null, !null, !null) >> { args ->
             def handler = args[2]
-            supplyResult.finishLater {
+            supplyResult.callbackLater {
                 handler.onComplete(result)
             }
         }
-
-        when:
-        finished()
-
-        then:
-        model == adaptedResult
     }
 
     def getModelBlocksUntilFailureReceivedFromProtocolConnectionAndRethrowsFailure() {
-        def supplyResult = later()
+        def supplyResult = waitsForAsyncCallback()
         RuntimeException failure = new RuntimeException()
 
         when:
         def model
-        def action = start {
+        supplyResult.start {
             model = builder.get()
         }
 
         then:
-        action.waitsFor(supplyResult)
+        GradleConnectionException e = thrown()
+        e.cause.is(failure)
         1 * protocolConnection.getModel(!null, !null, !null) >> { args ->
             def handler = args[2]
-            supplyResult.finishLater {
+            supplyResult.callbackLater {
                 handler.onFailure(failure)
             }
         }
-
-        when:
-        finished()
-
-        then:
-        GradleConnectionException e = thrown()
-        e.cause.is(failure)
     }
 }
 
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy
index 9c0f67c..d18f2a0 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultProjectConnectionTest.groovy
@@ -15,11 +15,13 @@
  */
 package org.gradle.tooling.internal.consumer
 
-import org.gradle.tooling.UnsupportedVersionException
-import org.gradle.tooling.model.Project
-import org.gradle.util.ConcurrentSpecification
+import org.gradle.tooling.UnknownModelException
+import org.gradle.tooling.internal.consumer.async.AsyncConnection
+import org.gradle.tooling.internal.consumer.protocoladapter.ProtocolToModelAdapter
+import org.gradle.tooling.model.GradleProject
+import spock.lang.Specification
 
-class DefaultProjectConnectionTest extends ConcurrentSpecification {
+class DefaultProjectConnectionTest extends Specification {
     final AsyncConnection protocolConnection = Mock()
     final ProtocolToModelAdapter adapter = Mock()
     final ConnectionParameters parameters = Mock()
@@ -27,7 +29,7 @@ class DefaultProjectConnectionTest extends ConcurrentSpecification {
 
     def canCreateAModelBuilder() {
         expect:
-        connection.model(Project.class) instanceof DefaultModelBuilder
+        connection.model(GradleProject.class) instanceof DefaultModelBuilder
     }
 
     def canCreateABuildLauncher() {
@@ -40,8 +42,7 @@ class DefaultProjectConnectionTest extends ConcurrentSpecification {
         connection.model(TestBuild.class)
 
         then:
-        UnsupportedVersionException e = thrown()
-        e.message == 'Model of type \'TestBuild\' is not supported.'
+        thrown(UnknownModelException)
     }
 
     def closeStopsBackingConnection() {
@@ -53,6 +54,6 @@ class DefaultProjectConnectionTest extends ConcurrentSpecification {
     }
 }
 
-interface TestBuild extends Project {
+interface TestBuild extends GradleProject {
     
 }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultToolingImplementationLoaderTest.groovy
deleted file mode 100644
index f693057..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DefaultToolingImplementationLoaderTest.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer
-
-import org.gradle.api.internal.AbstractClassPathProvider
-import org.gradle.messaging.actor.ActorFactory
-import org.gradle.tooling.UnsupportedVersionException
-import org.gradle.util.GradleVersion
-import org.slf4j.Logger
-import spock.lang.Specification
-import java.util.regex.Pattern
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-
-class DefaultToolingImplementationLoaderTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
-    final Distribution distribution = Mock()
-
-    def usesMetaInfServiceToDetermineFactoryImplementation() {
-        given:
-        def loader = new DefaultToolingImplementationLoader()
-        distribution.toolingImplementationClasspath >> ([
-                getToolingApiResourcesDir(),
-                AbstractClassPathProvider.getClasspathForClass(TestConnection.class),
-                AbstractClassPathProvider.getClasspathForClass(ActorFactory.class),
-                AbstractClassPathProvider.getClasspathForClass(Logger.class),
-                getVersionResourcesDir(),
-                AbstractClassPathProvider.getClasspathForClass(GradleVersion.class)
-        ] as Set)
-
-        when:
-        def factory = loader.create(distribution)
-
-        then:
-        factory.class != TestConnection.class
-        factory.class.name == TestConnection.class.name
-    }
-
-    private getToolingApiResourcesDir() {
-        tmpDir.file("META-INF/services/org.gradle.tooling.internal.protocol.ConnectionVersion4") << TestConnection.name
-        return tmpDir.dir;
-    }
-
-    private getVersionResourcesDir() {
-        return getResourcesDir("org/gradle/version.properties")
-    }
-
-    private getResourcesDir(String name) {
-        def resource = getClass().classLoader.getResource(name)
-        assert resource
-        assert resource.protocol == 'file'
-        def dir = resource.path.replaceFirst(Pattern.quote(name), '')
-        return new File(dir)
-    }
-
-    def failsWhenNoImplementationDeclared() {
-        ClassLoader cl = new ClassLoader() {}
-        def loader = new DefaultToolingImplementationLoader(cl)
-
-        when:
-        loader.create(distribution)
-
-        then:
-        UnsupportedVersionException e = thrown()
-        e.message == "The specified <dist-display-name> is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
-        _ * distribution.toolingImplementationClasspath >> ([] as Set)
-        _ * distribution.displayName >> '<dist-display-name>'
-    }
-}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
index 34b7cf3..4e7fa7a 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
@@ -17,18 +17,20 @@ package org.gradle.tooling.internal.consumer
 
 import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.testing.internal.util.Network
+import org.gradle.util.DistributionLocator
+import org.gradle.util.GradleVersion
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.junit.Rule
+import spock.lang.IgnoreIf
 import spock.lang.Specification
-import org.gradle.util.DistributionLocator
-import org.gradle.util.GradleVersion
 
 class DistributionFactoryTest extends Specification {
     @Rule final TemporaryFolder tmpDir = new TemporaryFolder()
     final ProgressLoggerFactory progressLoggerFactory = Mock()
     final ProgressLogger progressLogger = Mock()
-    final DistributionFactory factory = new DistributionFactory(tmpDir.file('userHome'), progressLoggerFactory)
+    final DistributionFactory factory = new DistributionFactory(tmpDir.file('userHome'))
 
     def setup() {
         _ * progressLoggerFactory.newOperation(!null) >> progressLogger
@@ -39,14 +41,23 @@ class DistributionFactoryTest extends Specification {
         tmpDir.file('gradle/wrapper/gradle-wrapper.properties') << "distributionUrl=${zipFile.toURI()}"
 
         expect:
-        factory.getDefaultDistribution(tmpDir.dir).displayName == "Gradle distribution '${zipFile.toURI()}'"
+        factory.getDefaultDistribution(tmpDir.dir, false).displayName == "Gradle distribution '${zipFile.toURI()}'"
+    }
+
+    def usesTheWrapperPropertiesToDetermineTheDefaultDistributionForASubprojectInAMultiProjectBuild() {
+        def zipFile = createZip { }
+        tmpDir.file('settings.gradle') << 'include "child"'
+        tmpDir.file('gradle/wrapper/gradle-wrapper.properties') << "distributionUrl=${zipFile.toURI()}"
+
+        expect:
+        factory.getDefaultDistribution(tmpDir.dir.createDir("child"), true).displayName == "Gradle distribution '${zipFile.toURI()}'"
     }
 
     def usesTheCurrentVersionAsTheDefaultDistributionWhenNoWrapperPropertiesFilePresent() {
         def uri = new DistributionLocator().getDistributionFor(GradleVersion.current())
 
         expect:
-        factory.getDefaultDistribution(tmpDir.dir).displayName == "Gradle distribution '${uri}'"
+        factory.getDefaultDistribution(tmpDir.dir, false).displayName == "Gradle distribution '${uri}'"
     }
 
     def createsADisplayNameForAnInstallation() {
@@ -60,7 +71,7 @@ class DistributionFactoryTest extends Specification {
 
         expect:
         def dist = factory.getDistribution(tmpDir.dir)
-        dist.toolingImplementationClasspath == [libA, libB] as Set
+        dist.getToolingImplementationClasspath(progressLoggerFactory) == [libA, libB] as Set
     }
 
     def failsWhenInstallationDirectoryDoesNotExist() {
@@ -68,7 +79,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(distDir)
 
         when:
-        dist.toolingImplementationClasspath
+        dist.getToolingImplementationClasspath(progressLoggerFactory)
 
         then:
         IllegalArgumentException e = thrown()
@@ -80,7 +91,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(distDir)
 
         when:
-        dist.toolingImplementationClasspath
+        dist.getToolingImplementationClasspath(progressLoggerFactory)
 
         then:
         IllegalArgumentException e = thrown()
@@ -92,7 +103,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(distDir)
 
         when:
-        dist.toolingImplementationClasspath
+        dist.getToolingImplementationClasspath(progressLoggerFactory)
 
         then:
         IllegalArgumentException e = thrown()
@@ -116,7 +127,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(zipFile.toURI())
 
         expect:
-        dist.toolingImplementationClasspath.collect { it.name } as Set == ['a.jar', 'b.jar'] as Set
+        dist.getToolingImplementationClasspath(progressLoggerFactory).collect { it.name } as Set == ['a.jar', 'b.jar'] as Set
     }
 
     def reportsZipDownload() {
@@ -126,24 +137,33 @@ class DistributionFactoryTest extends Specification {
             }
         }
         def dist = factory.getDistribution(zipFile.toURI())
+        ProgressLogger loggerOne = Mock()
+        ProgressLogger loggerTwo = Mock()
 
         when:
-        dist.toolingImplementationClasspath
+        dist.getToolingImplementationClasspath(progressLoggerFactory)
 
         then:
-        1 * progressLoggerFactory.newOperation(DistributionFactory.class) >> progressLogger
-        1 * progressLogger.setDescription("Download ${zipFile.toURI()}")
-        1 * progressLogger.started()
-        1 * progressLogger.completed()
+        2 * progressLoggerFactory.newOperation(DistributionFactory.class) >>> [loggerOne, loggerTwo]
+
+        1 * loggerOne.setDescription("Download ${zipFile.toURI()}")
+        1 * loggerOne.started()
+        1 * loggerOne.completed()
+
+        1 * loggerTwo.setDescription("Validate distribution")
+        1 * loggerTwo.started()
+        1 * loggerTwo.completed()
+
         0 * _._
     }
 
+    @IgnoreIf({ Network.offline })
     def failsWhenDistributionZipDoesNotExist() {
-        URI zipFile = new URI("http://gradle.org/does-not-exist/gradle-1.0.zip")
+        URI zipFile = new URI("http://google.com/does-not-exist/gradle-1.0.zip")
         def dist = factory.getDistribution(zipFile)
 
         when:
-        dist.toolingImplementationClasspath
+        dist.getToolingImplementationClasspath(progressLoggerFactory)
 
         then:
         IllegalArgumentException e = thrown()
@@ -155,7 +175,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(zipFile.toURI())
 
         when:
-        dist.toolingImplementationClasspath
+        dist.getToolingImplementationClasspath(progressLoggerFactory)
 
         then:
         IllegalArgumentException e = thrown()
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/LazyConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/LazyConnectionTest.groovy
deleted file mode 100644
index 84b1a64..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/LazyConnectionTest.groovy
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer
-
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1
-import org.gradle.tooling.internal.protocol.BuildParametersVersion1
-import org.gradle.tooling.internal.protocol.ConnectionVersion4
-import org.gradle.tooling.internal.protocol.ProjectVersion3
-import spock.lang.Specification
-
-class LazyConnectionTest extends Specification {
-    final Distribution distribution = Mock()
-    final ToolingImplementationLoader implementationLoader = Mock()
-    final BuildParametersVersion1 buildParams = Mock()
-    final BuildOperationParametersVersion1 params = Mock()
-    final ConnectionVersion4 connectionImpl = Mock()
-    final LazyConnection connection = new LazyConnection(distribution, implementationLoader)
-
-    def createsConnectionOnDemandToExecuteBuild() {
-        when:
-        connection.executeBuild(buildParams, params)
-
-        then:
-        1 * implementationLoader.create(distribution) >> connectionImpl
-        1 * connectionImpl.executeBuild(buildParams, params)
-        0 * _._
-    }
-
-    def createsConnectionOnDemandToBuildModel() {
-        when:
-        connection.getModel(ProjectVersion3, params)
-
-        then:
-        1 * implementationLoader.create(distribution) >> connectionImpl
-        1 * connectionImpl.getModel(ProjectVersion3, params)
-        0 * _._
-    }
-
-    def reusesConnection() {
-        when:
-        connection.getModel(ProjectVersion3, params)
-        connection.executeBuild(buildParams, params)
-
-        then:
-        1 * implementationLoader.create(distribution) >> connectionImpl
-        1 * connectionImpl.getModel(ProjectVersion3, params)
-        1 * connectionImpl.executeBuild(buildParams, params)
-        0 * _._
-    }
-
-    def stopsConnectionOnStop() {
-        when:
-        connection.getModel(ProjectVersion3, params)
-        connection.stop()
-
-        then:
-        1 * implementationLoader.create(distribution) >> connectionImpl
-        1 * connectionImpl.getModel(ProjectVersion3, params)
-        1 * connectionImpl.stop()
-        0 * _._
-    }
-
-    def doesNotStopConnectionOnStopIfNotCreated() {
-        when:
-        connection.stop()
-
-        then:
-        0 * _._
-    }
-
-    def doesNotStopConnectionOnStopIfConnectionCouldNotBeCreated() {
-        def failure = new RuntimeException()
-
-        when:
-        connection.getModel(ProjectVersion3, params)
-
-        then:
-        RuntimeException e = thrown()
-        e == failure
-        1 * implementationLoader.create(distribution) >> { throw failure }
-
-        when:
-        connection.stop()
-
-        then:
-        0 * _._
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProgressListenerAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProgressListenerAdapterTest.groovy
deleted file mode 100644
index 2936cb8..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProgressListenerAdapterTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer
-
-import spock.lang.Specification
-import org.gradle.tooling.ProgressListener
-
-class ProgressListenerAdapterTest extends Specification {
-    final ProgressListener listener = Mock()
-    final ProgressListenerAdapter adapter = new ProgressListenerAdapter()
-
-    def setup() {
-        adapter.add(listener)
-    }
-
-    def notifiesListenerOnOperationStartAndEnd() {
-        when:
-        adapter.onOperationStart('main')
-
-        then:
-        1 * listener.statusChanged({it.description == 'main'})
-
-        when:
-        adapter.onOperationEnd()
-
-        then:
-        1 * listener.statusChanged({it.description == ''})
-        0 * _._
-    }
-
-    def notifiesListenerOnNestedOperationStartAndEnd() {
-        when:
-        adapter.onOperationStart('main')
-        adapter.onOperationStart('nested')
-
-        then:
-        1 * listener.statusChanged({it.description == 'main'})
-        1 * listener.statusChanged({it.description == 'nested'})
-
-        when:
-        adapter.onOperationEnd()
-        adapter.onOperationEnd()
-
-        then:
-        1 * listener.statusChanged({it.description == 'main'})
-        1 * listener.statusChanged({it.description == ''})
-        0 * _._
-
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProgressLoggingConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProgressLoggingConnectionTest.groovy
deleted file mode 100644
index 4de41c7..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProgressLoggingConnectionTest.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer
-
-import org.gradle.listener.ListenerManager
-import org.gradle.logging.ProgressLogger
-import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.tooling.internal.protocol.BuildOperationParametersVersion1
-import org.gradle.tooling.internal.protocol.BuildParametersVersion1
-import org.gradle.tooling.internal.protocol.ConnectionVersion4
-import org.gradle.tooling.internal.protocol.ProjectVersion3
-import spock.lang.Specification
-import org.gradle.tooling.internal.protocol.ProgressListenerVersion1
-
-class ProgressLoggingConnectionTest extends Specification {
-    final ConnectionVersion4 target = Mock()
-    final BuildOperationParametersVersion1 params = Mock()
-    final ProgressListenerVersion1 listener = Mock()
-    final ProgressLogger progressLogger = Mock()
-    final ProgressLoggerFactory progressLoggerFactory = Mock()
-    final ListenerManager listenerManager = Mock()
-    final ProgressLoggingConnection connection = new ProgressLoggingConnection(target, progressLoggerFactory, listenerManager)
-
-    def notifiesProgressListenerOfStartAndEndOfFetchingModel() {
-        when:
-        connection.getModel(ProjectVersion3, params)
-
-        then:
-        1 * listenerManager.addListener(!null)
-        1 * progressLoggerFactory.newOperation(ProgressLoggingConnection.class) >> progressLogger
-        1 * progressLogger.setDescription('Load projects')
-        1 * progressLogger.started()
-        1 * target.getModel(ProjectVersion3, params)
-        1 * progressLogger.completed()
-        1 * listenerManager.removeListener(!null)
-        _ * params.progressListener >> listener
-        0 * _._
-    }
-
-    def notifiesProgressListenerOfStartAndEndOfExecutingBuild() {
-        BuildParametersVersion1 buildParams = Mock()
-
-        when:
-        connection.executeBuild(buildParams, params)
-
-        then:
-        1 * listenerManager.addListener(!null)
-        1 * progressLoggerFactory.newOperation(ProgressLoggingConnection.class) >> progressLogger
-        1 * progressLogger.setDescription('Execute build')
-        1 * progressLogger.started()
-        1 * target.executeBuild(buildParams, params)
-        1 * progressLogger.completed()
-        1 * listenerManager.removeListener(!null)
-        _ * params.progressListener >> listener
-        0 * _._
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProtocolToModelAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProtocolToModelAdapterTest.groovy
index 871fcf2..fe8c9c5 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProtocolToModelAdapterTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/ProtocolToModelAdapterTest.groovy
@@ -16,114 +16,6 @@
 package org.gradle.tooling.internal.consumer
 
 import org.gradle.tooling.model.DomainObjectSet
-import spock.lang.Specification
-import org.gradle.util.Matchers
-
-class ProtocolToModelAdapterTest extends Specification {
-    final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter()
-
-    def createsProxyAdapterForProtocolModel() {
-        TestProtocolModel protocolModel = Mock()
-
-        expect:
-        adapter.adapt(TestModel.class, protocolModel) instanceof TestModel
-    }
-
-    def proxiesAreEqualWhenTargetProtocolObjectsAreEqual() {
-        TestProtocolModel protocolModel1 = Mock()
-        TestProtocolModel protocolModel2 = Mock()
-
-        def model = adapter.adapt(TestModel.class, protocolModel1)
-        def equal = adapter.adapt(TestModel.class, protocolModel1)
-        def different = adapter.adapt(TestModel.class, protocolModel2)
-
-        expect:
-        Matchers.strictlyEquals(model, equal)
-        model != different
-    }
-
-    def methodInvocationOnModelDelegatesToTheProtocolModelObject() {
-        TestProtocolModel protocolModel = Mock()
-        _ * protocolModel.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.name == 'name'
-    }
-
-    def createsProxyAdapterForMethodReturnValue() {
-        TestProtocolModel protocolModel = Mock()
-        TestProtocolProject protocolProject = Mock()
-        _ * protocolModel.getProject() >> protocolProject
-        _ * protocolProject.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project instanceof TestProject
-        model.project.name == 'name'
-    }
-
-    def doesNotAdaptNullReturnValue() {
-        TestProtocolModel protocolModel = Mock()
-        _ * protocolModel.getProject() >> null
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project == null
-    }
-
-    def adaptsIterableToDomainObjectSet() {
-        TestProtocolModel protocolModel = Mock()
-        TestProtocolProject protocolProject = Mock()
-        _ * protocolModel.getChildren() >> [protocolProject]
-        _ * protocolProject.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.children.size() == 1
-        model.children[0] instanceof TestProject
-        model.children[0].name == 'name'
-    }
-
-    def cachesPropertyValues() {
-        TestProtocolModel protocolModel = Mock()
-        TestProtocolProject protocolProject = Mock()
-        _ * protocolModel.getProject() >> protocolProject
-        _ * protocolModel.getChildren() >> [protocolProject]
-        _ * protocolProject.getName() >> 'name'
-
-        expect:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project.is(model.project)
-        model.children.is(model.children)
-    }
-
-    def reportsMethodWhichDoesNotExistOnProtocolObject() {
-        PartialTestProtocolModel protocolModel = Mock()
-
-        when:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.project
-
-        then:
-        UnsupportedOperationException e = thrown()
-        e.message == "Cannot map method TestModel.getProject() to target object of type ${protocolModel.class.simpleName}."
-    }
-
-    def propagatesExceptionThrownByProtocolObject() {
-        TestProtocolModel protocolModel = Mock()
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        def model = adapter.adapt(TestModel.class, protocolModel)
-        model.name
-
-        then:
-        protocolModel.name >> { throw failure }
-        RuntimeException e = thrown()
-        e == failure
-    }
-}
 
 interface TestModel {
     String getName()
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
new file mode 100644
index 0000000..b9b1cd0
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer;
+
+
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/16/11
+ */
+public class SynchronizedLoggingTest extends Specification {
+
+    def logging = new SynchronizedLogging()
+    def concurrent = new ConcurrentTestUtil()
+
+    def "must be initialized"() {
+        when:
+        logging.listenerManager
+        then:
+        thrown(IllegalStateException)
+
+        when:
+        logging.progressLoggerFactory
+        then:
+        thrown(IllegalStateException)
+
+        when:
+        logging.init()
+        then:
+        logging.listenerManager
+        logging.progressLoggerFactory
+    }
+
+    def "keeps state per thread"() {
+        given:
+
+        Set loggingTools = []
+
+        when:
+        2.times {
+            concurrent.start {
+                logging.init()
+                loggingTools << logging.listenerManager
+                loggingTools << logging.progressLoggerFactory
+            }
+        }
+        concurrent.finished()
+
+        then: "each thread has separate instances of logging tools"
+        loggingTools.size() == 4
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/TestConnection.java b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/TestConnection.java
deleted file mode 100644
index fa3e48a..0000000
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/TestConnection.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2011 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.tooling.internal.consumer;
-
-import org.gradle.tooling.internal.protocol.*;
-
-public class TestConnection implements ConnectionVersion4 {
-    public void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) throws IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void stop() {
-        throw new UnsupportedOperationException();
-    }
-
-    public ConnectionMetaDataVersion1 getMetaData() {
-        throw new UnsupportedOperationException();
-    }
-
-    public ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) throws UnsupportedOperationException, IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConnectionTest.groovy
new file mode 100644
index 0000000..d01594e
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/LazyConnectionTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection
+
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.Distribution
+import org.gradle.tooling.internal.consumer.LoggingProvider
+import org.gradle.tooling.internal.consumer.ModelProvider
+import org.gradle.tooling.internal.consumer.loader.ToolingImplementationLoader
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.consumer.versioning.FeatureValidator
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1
+import spock.lang.Specification
+
+class LazyConnectionTest extends Specification {
+    final Distribution distribution = Mock()
+    final ToolingImplementationLoader implementationLoader = Mock()
+    final BuildParametersVersion1 buildParams = Mock()
+    final ConsumerOperationParameters params = Mock()
+    final ConsumerConnection consumerConnection = Mock()
+    final LoggingProvider loggingProvider = Mock()
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final LazyConnection connection = new LazyConnection(distribution, implementationLoader, loggingProvider, false)
+
+    static class SomeModel {}
+
+    def setup() {
+        connection.modelProvider = Mock(ModelProvider)
+        connection.featureValidator = Mock(FeatureValidator)
+    }
+
+    def createsConnectionOnDemandToExecuteBuild() {
+        when:
+        connection.executeBuild(buildParams, params)
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * consumerConnection.executeBuild(buildParams, params)
+        1 * connection.featureValidator.validate(consumerConnection, params)
+        0 * _._
+    }
+
+    def createsConnectionOnDemandToBuildModel() {
+        when:
+        connection.getModel(SomeModel, params)
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * connection.modelProvider.provide(!null, SomeModel, params)
+        1 * connection.featureValidator.validate(consumerConnection, params)
+        0 * _._
+    }
+
+    def "informs the loader about the verbose logging"() {
+        given:
+        connection.verboseLogging = true
+
+        when:
+        connection.getModel(SomeModel, params)
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, _ as ProgressLoggerFactory, true)
+    }
+
+    def reusesConnection() {
+        when:
+        connection.getModel(SomeModel, params)
+        connection.executeBuild(buildParams, params)
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * connection.modelProvider.provide(consumerConnection, SomeModel, params)
+        1 * consumerConnection.executeBuild(buildParams, params)
+        2 * connection.featureValidator.validate(consumerConnection, params)
+        0 * _._
+    }
+
+    def stopsConnectionOnStop() {
+        when:
+        connection.getModel(SomeModel, params)
+        connection.stop()
+
+        then:
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> consumerConnection
+        1 * connection.modelProvider.provide(consumerConnection, SomeModel, params)
+        1 * connection.featureValidator.validate(consumerConnection, params)
+        1 * consumerConnection.stop()
+        0 * _._
+    }
+
+    def doesNotStopConnectionOnStopIfNotCreated() {
+        when:
+        connection.stop()
+
+        then:
+        0 * _._
+    }
+
+    def doesNotStopConnectionOnStopIfConnectionCouldNotBeCreated() {
+        def failure = new RuntimeException()
+
+        when:
+        connection.getModel(SomeModel, params)
+
+        then:
+        RuntimeException e = thrown()
+        e == failure
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * implementationLoader.create(distribution, progressLoggerFactory, false) >> { throw failure }
+
+        when:
+        connection.stop()
+
+        then:
+        0 * _._
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnectionTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnectionTest.groovy
new file mode 100644
index 0000000..c32a709
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/connection/ProgressLoggingConnectionTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.connection
+
+import org.gradle.listener.ListenerManager
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.LoggingProvider
+import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters
+import org.gradle.tooling.internal.protocol.BuildParametersVersion1
+import org.gradle.tooling.internal.protocol.ProgressListenerVersion1
+import spock.lang.Specification
+
+class ProgressLoggingConnectionTest extends Specification {
+    final ConsumerConnection target = Mock()
+    final ConsumerOperationParameters params = Mock()
+    final ProgressListenerVersion1 listener = Mock()
+    final ProgressLogger progressLogger = Mock()
+    final ProgressLoggerFactory progressLoggerFactory = Mock()
+    final ListenerManager listenerManager = Mock()
+    final LoggingProvider loggingProvider = Mock()
+    final ProgressLoggingConnection connection = new ProgressLoggingConnection(target, loggingProvider)
+
+    static class SomeModel {}
+
+    def notifiesProgressListenerOfStartAndEndOfFetchingModel() {
+        when:
+        connection.getModel(SomeModel, params)
+
+        then:
+        1 * loggingProvider.getListenerManager() >> listenerManager
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * listenerManager.addListener(!null)
+        1 * progressLoggerFactory.newOperation(ProgressLoggingConnection.class) >> progressLogger
+        1 * progressLogger.setDescription('Load projects')
+        1 * progressLogger.started()
+        1 * target.getModel(SomeModel, params)
+        1 * progressLogger.completed()
+        1 * listenerManager.removeListener(!null)
+        _ * params.progressListener >> listener
+        0 * _._
+    }
+
+    def notifiesProgressListenerOfStartAndEndOfExecutingBuild() {
+        BuildParametersVersion1 buildParams = Mock()
+
+        when:
+        connection.executeBuild(buildParams, params)
+
+        then:
+        1 * loggingProvider.getListenerManager() >> listenerManager
+        1 * loggingProvider.getProgressLoggerFactory() >> progressLoggerFactory
+        1 * listenerManager.addListener(!null)
+        1 * progressLoggerFactory.newOperation(ProgressLoggingConnection.class) >> progressLogger
+        1 * progressLogger.setDescription('Execute build')
+        1 * progressLogger.started()
+        1 * target.executeBuild(buildParams, params)
+        1 * progressLogger.completed()
+        1 * listenerManager.removeListener(!null)
+        _ * params.progressListener >> listener
+        0 * _._
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy
new file mode 100644
index 0000000..6f777ff
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader
+
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tooling.internal.consumer.Distribution
+import org.gradle.tooling.internal.consumer.connection.ConsumerConnection
+import spock.lang.Specification
+
+class CachingToolingImplementationLoaderTest extends Specification {
+    final ToolingImplementationLoader target = Mock()
+    final ProgressLoggerFactory loggerFactory = Mock()
+    final CachingToolingImplementationLoader loader = new CachingToolingImplementationLoader(target)
+
+    def delegatesToTargetLoaderToCreateImplementation() {
+        final Distribution distribution = Mock()
+        final ConsumerConnection connection = Mock()
+
+        when:
+        def impl = loader.create(distribution, loggerFactory, true)
+
+        then:
+        impl == connection
+        1 * target.create(distribution, loggerFactory, true) >> connection
+        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> ([new File('a.jar')] as Set)
+        0 * _._
+    }
+
+    def reusesImplementationWithSameClasspath() {
+        final Distribution distribution = Mock()
+        final ConsumerConnection connection = Mock()
+
+        when:
+        def impl = loader.create(distribution, loggerFactory, true)
+        def impl2 = loader.create(distribution, loggerFactory, true)
+
+        then:
+        impl == connection
+        impl2 == connection
+        1 * target.create(distribution, loggerFactory, true) >> connection
+        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> ([new File('a.jar')] as Set)
+        0 * _._
+    }
+
+    def createsNewImplementationWhenClasspathNotSeenBefore() {
+        ConsumerConnection connection1 = Mock()
+        ConsumerConnection connection2 = Mock()
+        Distribution distribution1 = Mock()
+        Distribution distribution2 = Mock()
+
+        when:
+        def impl = loader.create(distribution1, loggerFactory, true)
+        def impl2 = loader.create(distribution2, loggerFactory, false)
+
+        then:
+        impl == connection1
+        impl2 == connection2
+        1 * target.create(distribution1, loggerFactory, true) >> connection1
+        1 * target.create(distribution2, loggerFactory, false) >> connection2
+        _ * distribution1.getToolingImplementationClasspath(loggerFactory) >> ([new File('a.jar')] as Set)
+        _ * distribution2.getToolingImplementationClasspath(loggerFactory) >> ([new File('b.jar')] as Set)
+        0 * _._
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
new file mode 100644
index 0000000..3a0216a
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader
+
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.messaging.actor.ActorFactory
+import org.gradle.tooling.UnsupportedVersionException
+import org.gradle.tooling.internal.consumer.Distribution
+import org.gradle.util.ClasspathUtil
+import org.gradle.util.GradleVersion
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.slf4j.Logger
+import spock.lang.Specification
+
+class DefaultToolingImplementationLoaderTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    Distribution distribution = Mock()
+    ProgressLoggerFactory loggerFactory = Mock()
+
+    def usesMetaInfServiceToDetermineFactoryImplementation() {
+        given:
+        def loader = new DefaultToolingImplementationLoader()
+        distribution.getToolingImplementationClasspath(loggerFactory) >> ([
+                getToolingApiResourcesDir(),
+                ClasspathUtil.getClasspathForClass(TestConnection.class),
+                ClasspathUtil.getClasspathForClass(ActorFactory.class),
+                ClasspathUtil.getClasspathForClass(Logger.class),
+                getVersionResourcesDir(),
+                ClasspathUtil.getClasspathForClass(GradleVersion.class)
+        ] as Set)
+
+        when:
+        def adaptedConnection = loader.create(distribution, loggerFactory, true)
+
+        then:
+        adaptedConnection.delegate.class != TestConnection.class //different classloaders
+        adaptedConnection.delegate.class.name == TestConnection.class.name
+    }
+
+    private getToolingApiResourcesDir() {
+        tmpDir.file("META-INF/services/org.gradle.tooling.internal.protocol.ConnectionVersion4") << TestConnection.name
+        return tmpDir.dir;
+    }
+
+    private getVersionResourcesDir() {
+        return ClasspathUtil.getClasspathForResource(getClass().classLoader, "org/gradle/releases.xml")
+    }
+
+    def failsWhenNoImplementationDeclared() {
+        ClassLoader cl = new ClassLoader() {}
+        def loader = new DefaultToolingImplementationLoader(cl)
+
+        when:
+        loader.create(distribution, loggerFactory, true)
+
+        then:
+        UnsupportedVersionException e = thrown()
+        e.message == "The specified <dist-display-name> is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
+        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> ([] as Set)
+        _ * distribution.displayName >> '<dist-display-name>'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy
new file mode 100644
index 0000000..247bb9c
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/SynchronizedToolingImplementationLoaderTest.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader;
+
+
+import java.util.concurrent.locks.Lock
+import java.util.concurrent.locks.ReentrantLock
+import org.gradle.logging.ProgressLogger
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.tests.fixtures.ConcurrentTestUtil
+import org.gradle.tooling.internal.consumer.Distribution
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 12/15/11
+ */
+public class SynchronizedToolingImplementationLoaderTest extends Specification {
+
+    def factory = Mock(ProgressLoggerFactory)
+    def distro = Mock(Distribution)
+    def logger = Mock(ProgressLogger)
+
+    def loader = new SynchronizedToolingImplementationLoader(Mock(ToolingImplementationLoader))
+
+    def setup() {
+        loader.lock = Mock(Lock)
+    }
+
+    def "reports progress when busy"() {
+        when:
+        loader.create(distro, factory, true)
+
+        then: "stubs"
+        1 * loader.lock.tryLock() >> false
+        1 * factory.newOperation(_ as Class) >> logger
+
+        then:
+        1 * logger.setDescription(_ as String)
+        then:
+        1 * logger.started()
+        then:
+        1 * loader.lock.lock()
+        then:
+        1 * loader.delegate.create(distro, factory, true)
+        then:
+        1 * logger.completed()
+        1 * loader.lock.unlock()
+        0 * _
+    }
+
+    def "does not report progress when appropriate"() {
+        when:
+        loader.create(distro, factory, true)
+
+        then:
+        1 * loader.lock.tryLock() >> true
+        then:
+        1 * loader.delegate.create(distro, factory, true)
+        then:
+        1 * loader.lock.unlock()
+        0 * _
+    }
+
+    def concurrent = new ConcurrentTestUtil()
+
+    def "is thread safe"() {
+        given:
+        loader.lock = new ReentrantLock()
+        factory.newOperation(_ as Class) >> logger
+
+        when:
+        5.times {
+            concurrent.start { loader.create(distro, factory, true) }
+        }
+
+        then:
+        concurrent.finished()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/TestConnection.java b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/TestConnection.java
new file mode 100644
index 0000000..8a449c0
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/TestConnection.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.loader;
+
+import org.gradle.tooling.internal.protocol.*;
+
+public class TestConnection implements ConnectionVersion4 {
+    public void executeBuild(BuildParametersVersion1 buildParameters, BuildOperationParametersVersion1 operationParameters) throws IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+
+    public void stop() {
+        throw new UnsupportedOperationException();
+    }
+
+    public ConnectionMetaDataVersion1 getMetaData() {
+        throw new UnsupportedOperationException();
+    }
+
+    public ProjectVersion3 getModel(Class<? extends ProjectVersion3> type, BuildOperationParametersVersion1 operationParameters) throws UnsupportedOperationException, IllegalStateException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy
new file mode 100644
index 0000000..fd7f727
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ConsumerOperationParametersTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * 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.tooling.internal.consumer.parameters
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/12/12
+ */
+class ConsumerOperationParametersTest extends Specification {
+    
+    def params = new ConsumerOperationParameters(null)
+    
+    def "null or empty arguments have the same meaning"() {
+        when:
+        params.arguments = null
+
+        then:
+        params.arguments == null
+
+        when:
+        params.arguments = []
+
+        then:
+        params.arguments == null
+
+        when:
+        params.arguments = ['-Dfoo']
+
+        then:
+        params.arguments == ['-Dfoo']
+    }
+
+    def "null or empty jvm arguments have the same meaning"() {
+        when:
+        params.jvmArguments = null
+
+        then:
+        params.jvmArguments == null
+
+        when:
+        params.jvmArguments = []
+
+        then:
+        params.jvmArguments == null
+
+        when:
+        params.jvmArguments = ['-Xmx']
+
+        then:
+        params.jvmArguments == ['-Xmx']
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ProgressListenerAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ProgressListenerAdapterTest.groovy
new file mode 100644
index 0000000..c5e57ab
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/parameters/ProgressListenerAdapterTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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.tooling.internal.consumer.parameters
+
+import org.gradle.tooling.ProgressListener
+import spock.lang.Specification
+
+class ProgressListenerAdapterTest extends Specification {
+    final ProgressListener listener = Mock()
+    final ProgressListenerAdapter adapter = new ProgressListenerAdapter()
+
+    def setup() {
+        adapter.add(listener)
+    }
+
+    def notifiesListenerOnOperationStartAndEnd() {
+        when:
+        adapter.onOperationStart('main')
+
+        then:
+        1 * listener.statusChanged({it.description == 'main'})
+
+        when:
+        adapter.onOperationEnd()
+
+        then:
+        1 * listener.statusChanged({it.description == ''})
+        0 * _._
+    }
+
+    def notifiesListenerOnNestedOperationStartAndEnd() {
+        when:
+        adapter.onOperationStart('main')
+        adapter.onOperationStart('nested')
+
+        then:
+        1 * listener.statusChanged({it.description == 'main'})
+        1 * listener.statusChanged({it.description == 'nested'})
+
+        when:
+        adapter.onOperationEnd()
+        adapter.onOperationEnd()
+
+        then:
+        1 * listener.statusChanged({it.description == 'main'})
+        1 * listener.statusChanged({it.description == ''})
+        0 * _._
+
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapterTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapterTest.groovy
new file mode 100644
index 0000000..f85c173
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/protocoladapter/ProtocolToModelAdapterTest.groovy
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2012 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.tooling.internal.consumer.protocoladapter
+
+import org.gradle.tooling.internal.idea.DefaultIdeaModuleDependency
+import org.gradle.tooling.internal.idea.DefaultIdeaSingleEntryLibraryDependency
+import org.gradle.tooling.model.UnsupportedMethodException
+import org.gradle.tooling.model.idea.IdeaDependency
+import org.gradle.tooling.model.idea.IdeaModuleDependency
+import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency
+import org.gradle.util.Matchers
+import spock.lang.Specification
+import org.gradle.tooling.internal.consumer.*
+
+/**
+ * by Szczepan Faber, created at: 4/2/12
+ */
+class ProtocolToModelAdapterTest extends Specification {
+    final ProtocolToModelAdapter adapter = new ProtocolToModelAdapter()
+    final ModelPropertyHandler propertyHandler = Mock()
+
+    def createsProxyAdapterForProtocolModel() {
+        TestProtocolModel protocolModel = Mock()
+
+        expect:
+        adapter.adapt(TestModel.class, protocolModel, propertyHandler) instanceof TestModel
+    }
+
+    def proxiesAreEqualWhenTargetProtocolObjectsAreEqual() {
+        TestProtocolModel protocolModel1 = Mock()
+        TestProtocolModel protocolModel2 = Mock()
+
+        def model = adapter.adapt(TestModel.class, protocolModel1, propertyHandler)
+        def equal = adapter.adapt(TestModel.class, protocolModel1, propertyHandler)
+        def different = adapter.adapt(TestModel.class, protocolModel2, propertyHandler)
+
+        expect:
+        Matchers.strictlyEquals(model, equal)
+        model != different
+    }
+
+    def methodInvocationOnModelDelegatesToTheProtocolModelObject() {
+        TestProtocolModel protocolModel = Mock()
+        _ * protocolModel.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.name == 'name'
+    }
+
+    def createsProxyAdapterForMethodReturnValue() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getProject() >> protocolProject
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.project instanceof TestProject
+        model.project.name == 'name'
+    }
+
+    def doesNotAdaptNullReturnValue() {
+        TestProtocolModel protocolModel = Mock()
+        _ * protocolModel.getProject() >> null
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.project == null
+    }
+
+    def adaptsIterableToDomainObjectSet() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getChildren() >> [protocolProject]
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.children.size() == 1
+        model.children[0] instanceof TestProject
+        model.children[0].name == 'name'
+    }
+
+    def cachesPropertyValues() {
+        TestProtocolModel protocolModel = Mock()
+        TestProtocolProject protocolProject = Mock()
+        _ * protocolModel.getProject() >> protocolProject
+        _ * protocolModel.getChildren() >> [protocolProject]
+        _ * protocolProject.getName() >> 'name'
+
+        expect:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.project.is(model.project)
+        model.children.is(model.children)
+    }
+
+    def reportsMethodWhichDoesNotExistOnProtocolObject() {
+        PartialTestProtocolModel protocolModel = Mock()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.project
+
+        then:
+        UnsupportedMethodException e = thrown()
+        e.message.contains "TestModel.getProject()"
+    }
+
+    def propagatesExceptionThrownByProtocolObject() {
+        TestProtocolModel protocolModel = Mock()
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        def model = adapter.adapt(TestModel.class, protocolModel, propertyHandler)
+        model.name
+
+        then:
+        protocolModel.name >> { throw failure }
+        RuntimeException e = thrown()
+        e == failure
+    }
+
+    def "adapts idea dependencies"() {
+        def libraryDep = new GroovyClassLoader().loadClass(DefaultIdeaSingleEntryLibraryDependency.class.getCanonicalName()).newInstance()
+        def moduleDep = new GroovyClassLoader().loadClass(DefaultIdeaModuleDependency.class.getCanonicalName()).newInstance()
+
+        when:
+        def library = adapter.adapt(IdeaDependency.class, libraryDep, propertyHandler)
+        def module  = adapter.adapt(IdeaDependency.class, moduleDep, propertyHandler)
+
+        then:
+        library instanceof IdeaSingleEntryLibraryDependency
+        module instanceof IdeaModuleDependency
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectTest.groovy
new file mode 100644
index 0000000..5fdbc7f
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/eclipse/DefaultEclipseProjectTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 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.tooling.internal.eclipse
+
+import spock.lang.Specification
+
+class DefaultEclipseProjectTest extends Specification {
+    def usesPathForToStringValue() {
+        def project = new DefaultEclipseProject("name", ":path", null, null, [])
+
+        expect:
+        project.toString() == "project ':path'"
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy
new file mode 100644
index 0000000..3048ead
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/gradle/DefaultGradleProjectTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 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.tooling.internal.gradle
+
+import spock.lang.Specification
+
+class DefaultGradleProjectTest extends Specification {
+
+    def "allows finding descendant by path"() {
+        def root = new DefaultGradleProject(":")
+
+        def child1 = new DefaultGradleProject(":child1")
+        def child11 = new DefaultGradleProject(":child1:child11")
+        def child12 = new DefaultGradleProject(":child1:child12")
+
+        def child2 = new DefaultGradleProject(":child2")
+        def child21 = new DefaultGradleProject(":child2:child21")
+
+        root.children = [child1, child2]
+        child1.children = [child11, child12]
+        child2.children = [child21]
+
+        expect:
+        root.findByPath(':') == root
+        root.findByPath('') == null
+        root.findByPath('blah blah') == null
+
+        root.findByPath(':child1:child12') == child12
+        root.findByPath(':child2') == child2
+
+        child1.findByPath(':') == null
+        child1.findByPath(':child1:child11') == child11
+    }
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/reflect/CompatibleIntrospectorTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/reflect/CompatibleIntrospectorTest.groovy
new file mode 100644
index 0000000..bd12f78
--- /dev/null
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/reflect/CompatibleIntrospectorTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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.tooling.internal.reflect
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 3/16/12
+ */
+class CompatibleIntrospectorTest extends Specification {
+    
+    class Foo {
+        
+        int number
+        
+        String getMessage() {
+            "Hello!"
+        }
+        
+        void setNumber(int number) {
+            this.number = number
+        }
+    }
+
+    def foo = new Foo()
+    def intro = new CompatibleIntrospector(foo)
+    
+    def "gets stuff safely"() {
+        expect:
+        'Hello!' == intro.getSafely('blah', 'getMessage')
+        'blah' == intro.getSafely('blah', 'doesNotExist')
+    }
+
+    def "calls methods safely"() {
+        when:
+        intro.callSafely('doesNotExist', 10)
+        then:
+        foo.number == 0
+
+        when:
+        intro.callSafely('setNumber', 10)
+        then:
+        foo.number == 10
+    }
+}
diff --git a/subprojects/tooling-api/tooling-api.gradle b/subprojects/tooling-api/tooling-api.gradle
index 03aaeb0..c30aef5 100644
--- a/subprojects/tooling-api/tooling-api.gradle
+++ b/subprojects/tooling-api/tooling-api.gradle
@@ -1,10 +1,31 @@
-apply plugin: 'groovy'
+import org.gradle.internal.os.OperatingSystem
 
 dependencies {
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
-    publishCompile project(':core'), project(':wrapper')
+    publishCompile project(':core')
+    publishCompile project(':baseServices')
+    publishCompile project(':wrapper')
 
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    // lots of integTest errors otherwise
+    integTestRuntime project(':ide')
 }
+
+useTestFixtures()
+
+// is some of this unnecessary, or should it be moved into gradle/integTest ?
+integTest {
+    dependsOn ':publishLocalArchives', ':binZip'
+
+    doFirst {
+        systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
+        systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
+        systemProperties['org.gradle.integtest.toolingApiFromTestClasspath'] = 'true'
+    }
+}
+
+daemonIntegTest {
+    enabled = false //tooling integ tests use daemon anyway, don't rerun
+}
+
+
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/ExtraTestCommandLineOptionsListener.java b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/ExtraTestCommandLineOptionsListener.java
new file mode 100644
index 0000000..2cb562a
--- /dev/null
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/ExtraTestCommandLineOptionsListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.gradleplugin.foundation.CommandLineArgumentAlteringListener;
+
+import java.io.File;
+
+public class ExtraTestCommandLineOptionsListener implements CommandLineArgumentAlteringListener {
+    private final File gradleUserHomeDir;
+
+    public ExtraTestCommandLineOptionsListener(File gradleUserHomeDir) {
+        this.gradleUserHomeDir = gradleUserHomeDir;
+    }
+
+    public String getAdditionalCommandLineArguments(String commandLineArguments) {
+        return String.format("--no-search-upward --gradle-user-home \'%s\'", gradleUserHomeDir);
+    }
+}
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
index ba7af26..582d0ae 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
@@ -34,6 +34,7 @@ import org.junit.Test;
 import javax.swing.filechooser.FileFilter;
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -126,7 +127,7 @@ public class FavoritesIntegrationTest {
 
         Assert.assertTrue(originalEditor.getFavoriteTasks().isEmpty());
 
-        //add two tasks
+        //add some tasks
         FavoriteTask favoriteTask1 = originalEditor.addFavorite(mySubProject1Comple, true);
         FavoriteTask favoriteTask2 = originalEditor.addFavorite(mySubSubProjectLib, false);
 
@@ -164,7 +165,7 @@ public class FavoritesIntegrationTest {
     }
 
     /**
-     * This verifies that the serialization mechananism corrects the extension so that it is correct. We'll save a file with the wrong extension. The save mechanism should save it with the correct
+     * This verifies that the serialization mechanism corrects the extension so that it is correct. We'll save a file with the wrong extension. The save mechanism should save it with the correct
      * extension appended to the end (leaving the wrong extension in tact, just not at the end).
      */
     @Test
@@ -245,7 +246,7 @@ public class FavoritesIntegrationTest {
     }
 
     /**
-     * This exists soley so we can track if confirmOverwritingExisingFile was called.
+     * This exists solely so we can track if confirmOverwritingExistingFile was called.
      */
     private class TestOverwriteConfirmExportInteraction extends TestUtility.TestExportInteraction {
         public boolean wasConfirmed;
@@ -270,7 +271,7 @@ public class FavoritesIntegrationTest {
          * @return true to overwrite it, false not to.
          */
         @Override
-        public boolean confirmOverwritingExisingFile(File file) {
+        public boolean confirmOverwritingExistingFile(File file) {
             wasConfirmed = true;
             return false;
         }
@@ -283,7 +284,7 @@ public class FavoritesIntegrationTest {
     public void testDuplicateSingleFavorite() {
         FavoritesEditor editor = new FavoritesEditor();
 
-        //add two tasks
+        //add some tasks
         FavoriteTask favoriteTask1 = editor.addFavorite(mySubProject1Comple, true);
         FavoriteTask favoriteTask2 = editor.addFavorite(mySubSubProjectLib, false);
         FavoriteTask favoriteTask3 = editor.addFavorite(mySubSubProjectDoc, false);
@@ -294,20 +295,20 @@ public class FavoritesIntegrationTest {
         editFavorite(editor, favoriteTask3, "name3", false);
 
         //duplicate a single task
-        FavoriteTask favoriteTask4 = editor.duplicateFavorite(favoriteTask1);
+        FavoriteTask favoriteTask4 = editor.duplicateFavorite(favoriteTask1, new TestEditFavoriteInteraction("name4", "command4"));
         Assert.assertNotNull(favoriteTask4);
-        Assert.assertEquals(favoriteTask1.getFullCommandLine(), favoriteTask4.getFullCommandLine());
-        Assert.assertEquals(favoriteTask1.getDisplayName(), favoriteTask4.getDisplayName());
+        Assert.assertEquals("command4", favoriteTask4.getFullCommandLine());
+        Assert.assertEquals("name4", favoriteTask4.getDisplayName());
         Assert.assertEquals(favoriteTask1.alwaysShowOutput(), favoriteTask4.alwaysShowOutput());
 
         //there should be 4 tasks now
         Assert.assertEquals(4, editor.getFavoriteTasks().size());
 
         //now duplicate another one
-        FavoriteTask favoriteTask5 = editor.duplicateFavorite(favoriteTask2);
+        FavoriteTask favoriteTask5 = editor.duplicateFavorite(favoriteTask2, new TestEditFavoriteInteraction("name5", "command5"));
         Assert.assertNotNull(favoriteTask5);
-        Assert.assertEquals(favoriteTask2.getFullCommandLine(), favoriteTask5.getFullCommandLine());
-        Assert.assertEquals(favoriteTask2.getDisplayName(), favoriteTask5.getDisplayName());
+        Assert.assertEquals("command5", favoriteTask5.getFullCommandLine());
+        Assert.assertEquals("name5", favoriteTask5.getDisplayName());
         Assert.assertEquals(favoriteTask2.alwaysShowOutput(), favoriteTask5.alwaysShowOutput());
 
         //there should be 5 tasks now
@@ -321,7 +322,7 @@ public class FavoritesIntegrationTest {
     public void testDuplicatingMultipleFavorites() {
         FavoritesEditor editor = new FavoritesEditor();
 
-        //add two tasks
+        //add some tasks
         FavoriteTask favoriteTask1 = editor.addFavorite(mySubProject1Comple, true);
         FavoriteTask favoriteTask2 = editor.addFavorite(mySubSubProjectLib, false);
         FavoriteTask favoriteTask3 = editor.addFavorite(mySubSubProjectDoc, false);
@@ -336,8 +337,9 @@ public class FavoritesIntegrationTest {
         tasksToCopy.add(favoriteTask1);
         tasksToCopy.add(favoriteTask2);
 
-        //now peform the duplication
-        editor.duplicateFavorites(tasksToCopy);
+        //now perform the duplication
+        editor.duplicateFavorites(tasksToCopy, new TestEditFavoriteInteraction(new NameAndCommand("newname1", "newcommand1"),
+                new NameAndCommand("newname2", "newcommand2")));
 
         //there should be 5 tasks now
         Assert.assertEquals(5, editor.getFavoriteTasks().size());
@@ -346,19 +348,103 @@ public class FavoritesIntegrationTest {
         FavoriteTask favoriteTask4 = editor.getFavoriteTasks().get(3);
 
         Assert.assertNotNull(favoriteTask4);
-        Assert.assertEquals(favoriteTask1.getFullCommandLine(), favoriteTask4.getFullCommandLine());
-        Assert.assertEquals(favoriteTask1.getDisplayName(), favoriteTask4.getDisplayName());
+        Assert.assertEquals("newcommand1", favoriteTask4.getFullCommandLine());
+        Assert.assertEquals("newname1", favoriteTask4.getDisplayName());
         Assert.assertEquals(favoriteTask1.alwaysShowOutput(), favoriteTask4.alwaysShowOutput());
 
         //the 5th one (4 from index 0) should be the same as the second one
         FavoriteTask favoriteTask5 = editor.getFavoriteTasks().get(4);
         Assert.assertNotNull(favoriteTask5);
-        Assert.assertEquals(favoriteTask2.getFullCommandLine(), favoriteTask5.getFullCommandLine());
-        Assert.assertEquals(favoriteTask2.getDisplayName(), favoriteTask5.getDisplayName());
+        Assert.assertEquals("newcommand2", favoriteTask5.getFullCommandLine());
+        Assert.assertEquals("newname2", favoriteTask5.getDisplayName());
         Assert.assertEquals(favoriteTask2.alwaysShowOutput(), favoriteTask5.alwaysShowOutput());
     }
 
     /**
+     * This tests duplicating multiple favorites at once, but we cancel out after duplicating one. We want to make sure that it doesn't continue to create the others. First, we'll create some, then
+     * duplicate them.
+     */
+    @Test
+    public void testDuplicatingMultipleFavoritesAndCanceling() {
+
+        FavoritesEditor editor = new FavoritesEditor();
+
+        //add some tasks
+        FavoriteTask favoriteTask1 = editor.addFavorite(mySubProject1Comple, true);
+        FavoriteTask favoriteTask2 = editor.addFavorite(mySubSubProjectLib, false);
+        FavoriteTask favoriteTask3 = editor.addFavorite(mySubSubProjectDoc, false);
+
+        //now change the display names and the alwaysShowOutput field, just so we can verify that all fields are copied.
+        editFavorite(editor, favoriteTask1, "name1", false);
+        editFavorite(editor, favoriteTask2, "name2", true);
+        editFavorite(editor, favoriteTask3, "name3", false);
+
+        //get the ones to duplicate in a list
+        List<FavoriteTask> tasksToCopy = new ArrayList<FavoriteTask>();
+        tasksToCopy.add(favoriteTask1);
+        tasksToCopy.add(favoriteTask2);
+
+        //now perform the duplication, we only pass in one NameAndCommand but we're editing 2. This makes it cancel the second one.
+        editor.duplicateFavorites(tasksToCopy, new TestEditFavoriteInteraction(new NameAndCommand("newname1", "newcommand1")));
+
+        //there should be 4 tasks now
+        Assert.assertNotSame("Failed to cancel", 5, editor.getFavoriteTasks().size()); //this just provides a better error if this fails to cancel
+        Assert.assertEquals(4, editor.getFavoriteTasks().size());
+
+        //the 4th one (3 from index 0) should be the same as the first one
+        FavoriteTask favoriteTask4 = editor.getFavoriteTasks().get(3);
+
+        Assert.assertNotNull(favoriteTask4);
+        Assert.assertEquals("newcommand1", favoriteTask4.getFullCommandLine());
+        Assert.assertEquals("newname1", favoriteTask4.getDisplayName());
+        Assert.assertEquals(favoriteTask1.alwaysShowOutput(), favoriteTask4.alwaysShowOutput());
+    }
+
+    private class TestEditFavoriteInteraction implements FavoritesEditor.EditFavoriteInteraction {
+        private List<NameAndCommand> values = new ArrayList<NameAndCommand>();
+
+        private TestEditFavoriteInteraction(NameAndCommand... values) {
+            if (values != null) {
+                this.values = new ArrayList<NameAndCommand>(Arrays.asList(values));   //making a new ArrayList because Arrays.asList makes it unmodifiable.
+            }
+        }
+
+        private TestEditFavoriteInteraction(String displayName, String fullCommandLine) {
+            values.add(new NameAndCommand(displayName, fullCommandLine));
+        }
+
+        public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+            if (values.isEmpty()) {
+                return false;   //if we have no more choices, that simulates the user canceling
+            }
+
+            NameAndCommand nameAndCommand = values.remove(0);
+
+            favoriteTask.displayName = nameAndCommand.displayName;
+            favoriteTask.fullCommandLine = nameAndCommand.fullCommandLine;
+
+            return true;
+        }
+
+        public void reportError(String error) {
+            throw new AssertionFailedError("Unexpected error; " + error);
+        }
+    }
+
+    /**
+     * wrapper class to hold a display name and a full command line
+     */
+    private class NameAndCommand {
+        String displayName;
+        String fullCommandLine;
+
+        private NameAndCommand(String displayName, String fullCommandLine) {
+            this.displayName = displayName;
+            this.fullCommandLine = fullCommandLine;
+        }
+    }
+
+    /**
      * This sets the display name of the favorite task to the specified new name.
      */
     private void editFavorite(FavoritesEditor editor, FavoriteTask favoriteTask, final String newDisplayName, final boolean newAlwaysShowOutput) {
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
index 2fa00f4..0c6009b 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
@@ -30,6 +30,10 @@ import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.gradle.foundation.output.LiveOutputParser
+import org.gradle.foundation.output.FileLinkDefinitionLord
+import org.gradle.foundation.output.FileLink
+import org.gradle.logging.ShowStacktrace
 
 /**
 This tests the that live output is gathered while executing a task.
@@ -71,6 +75,7 @@ that's likely to change over time. This version executes the command via GradleP
         GradlePluginLord gradlePluginLord = new GradlePluginLord();
         gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
         gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+        gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(dist.userHomeDir))
 
         gradlePluginLord.startExecutionQueue(); //for tests, we'll need to explicitly start the execution queue (unless we do a refresh via the TestUtility).
 
@@ -99,8 +104,9 @@ that's likely to change over time. This version executes the command via GradleR
         TestExecutionInteraction executionInteraction = new TestExecutionInteraction();
 
         //execute a command. We don't really care what the command is, just something that generates output
-        gradleRunner.executeCommand("tasks", org.gradle.api.logging.LogLevel.LIFECYCLE,
-                                            org.gradle.StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS,
+        def cl = new ExtraTestCommandLineOptionsListener(dist.userHomeDir).getAdditionalCommandLineArguments('') + ' tasks'
+        gradleRunner.executeCommand(cl, org.gradle.api.logging.LogLevel.LIFECYCLE,
+                                            ShowStacktrace.INTERNAL_EXCEPTIONS,
                                             executionInteraction);
 
         executionInteraction.waitForCompletion(100, TimeUnit.SECONDS)
@@ -132,6 +138,101 @@ that's likely to change over time. This version executes the command via GradleR
       //live output. As such, we won't verify they're equal.
       Assert.assertTrue( "Verifying the final output message was received", executionInteraction.finalMessage.length() > 30 )
    }
+
+
+
+     /**
+      Tests that navigating to the next file link works correctly. It should circle back around if we
+      search passed the end.
+      */
+     @Test
+     public void testNextFileLinks()
+     {
+       LiveOutputParser parser = new LiveOutputParser( new FileLinkDefinitionLord(), false );  //we don't really need/want real live output for this test
+
+       //should not find a match. There's absolutely no text present.
+       Assert.assertNull( parser.getNextFileLink( 0 ) );
+
+       parser.appendText( "Beginning\n");
+
+       //should not find a match. There's no links present.
+       Assert.assertNull( parser.getNextFileLink( 0 ) );
+
+       parser.appendText( "first.java:1\n");    //file link
+       parser.appendText( "middle 1\n");
+       parser.appendText( "second.java:2\n");   //file link
+       parser.appendText( "middle 2\n");
+       parser.appendText( "next.java:3\n");    //file link
+       parser.appendText( "last\n");
+
+       List<FileLink> links = parser.getFileLinks();
+       Assert.assertEquals( 3, links.size() );
+
+       Assert.assertEquals( new File( "first.java" ), links.get( 0 ).getFile() );
+       Assert.assertEquals( new File( "second.java" ), links.get( 1 ).getFile() );
+       Assert.assertEquals( new File( "next.java" ), links.get( 2 ).getFile() );
+
+       //now do a series of searching next.
+       FileLink link1 = parser.getNextFileLink( 0 );
+       Assert.assertEquals( links.get( 0 ), link1 );
+
+       FileLink link2 = parser.getNextFileLink( link1.getEndingIndex() );
+       Assert.assertEquals( links.get( 1 ), link2 );
+
+       FileLink link3 = parser.getNextFileLink( link2.getEndingIndex() );
+       Assert.assertEquals( links.get( 2 ), link3 );
+
+       //search once more. This should go back to the beginning
+       FileLink link4 = parser.getNextFileLink( link3.getEndingIndex() );
+       Assert.assertEquals( link1, link4 );
+}
+
+     /**
+      Tests that navigating to the previous file link works correctly. It should circle back around if we
+      search passed the beginning.
+      */
+     @Test
+     public void testPreviousFileLinks()
+     {
+       LiveOutputParser parser = new LiveOutputParser( new FileLinkDefinitionLord(), false );  //we don't really need/want real live output for this test
+
+       //should not find a match. There's absolutely no text present.
+       Assert.assertNull( parser.getPreviousFileLink( 0 ) );
+
+       parser.appendText( "Beginning\n");
+
+       //should not find a match. There's no links present.
+       Assert.assertNull( parser.getPreviousFileLink( 0 ) );
+
+       parser.appendText( "first.java:1\n");    //file link
+       parser.appendText( "middle 1\n");
+       parser.appendText( "second.java:2\n");   //file link
+       parser.appendText( "middle 2\n");
+       parser.appendText( "next.java:3\n");    //file link
+       parser.appendText( "last\n");
+
+       List<FileLink> links = parser.getFileLinks();
+       Assert.assertEquals( 3, links.size() );
+
+       Assert.assertEquals( new File( "first.java" ), links.get( 0 ).getFile() );
+       Assert.assertEquals( new File( "second.java" ), links.get( 1 ).getFile() );
+       Assert.assertEquals( new File( "next.java" ), links.get( 2 ).getFile() );
+
+       //now do a series of searching previous starting at the end.
+       FileLink link3 = parser.getPreviousFileLink( parser.getText().length() - 1 );
+       Assert.assertEquals( links.get( 2 ), link3  );
+
+       FileLink link2 = parser.getPreviousFileLink( link3.getStartingIndex() );
+       Assert.assertEquals( links.get( 1 ), link2 );
+
+       FileLink link1 = parser.getPreviousFileLink( link2.getStartingIndex() );
+       Assert.assertEquals( links.get( 0 ), link1 );
+
+       //search once more. This should go back to the ending
+       FileLink link4 = parser.getPreviousFileLink( link1.getStartingIndex() );
+       Assert.assertEquals( link3, link4 );
+     }
+
 }
 
 //this class just holds onto our liveOutput and also tracks whether or not we've finished.
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
index a10efe8..141e7ec 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
@@ -15,8 +15,10 @@
  */
 package org.gradle.integtests
 
+import java.util.concurrent.TimeUnit
 import org.gradle.foundation.ProjectView
 import org.gradle.foundation.TaskView
+import org.gradle.foundation.TestUtility
 import org.gradle.gradleplugin.foundation.GradlePluginLord
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
@@ -27,9 +29,6 @@ import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import java.util.concurrent.TimeUnit
-import org.gradle.foundation.TestUtility
-import org.gradle.util.TestFile
 
 /**
  This tests the multiproject sample with the GradleView mechanism.
@@ -44,15 +43,16 @@ class MultiprojectProjectAndTaskListIntegrationTest {
     static final String SERVICES_NAME = 'services'
     static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
 
-    private TestFile javaprojectDir
-
     @Rule public final GradleDistribution dist = new GradleDistribution()
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final Sample sample = new Sample('java/multiproject')
+    GradlePluginLord gradlePluginLord = new GradlePluginLord()
 
     @Before
     void setUp() {
-        javaprojectDir = sample.getDir()
+        gradlePluginLord.setCurrentDirectory(sample.dir);
+        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+        gradlePluginLord.addCommandLineArgumentAlteringListener(new ExtraTestCommandLineOptionsListener(dist.userHomeDir))
     }
 
     /*
@@ -67,10 +67,6 @@ class MultiprojectProjectAndTaskListIntegrationTest {
 
     @Test
     public void multiProjectjavaProjectSample() {
-        GradlePluginLord gradlePluginLord = new GradlePluginLord();
-        gradlePluginLord.setCurrentDirectory(javaprojectDir);
-        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
-
         //refresh the projects and wait. This will throw an exception if it fails.
         TestUtility.refreshProjectsBlocking(gradlePluginLord, 80, TimeUnit.SECONDS);
 
@@ -112,10 +108,6 @@ class MultiprojectProjectAndTaskListIntegrationTest {
    @Test
    public void testOpenAPIWrapperProjectAndTaskList()
    {
-        GradlePluginLord gradlePluginLord = new GradlePluginLord();
-        gradlePluginLord.setCurrentDirectory(javaprojectDir);
-        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
-
         GradleInterfaceWrapperVersion1 wrapper = new GradleInterfaceWrapperVersion1( gradlePluginLord );
 
         //the rest of this uses the open API mechanism to access the projects and tasks
@@ -167,10 +159,6 @@ class MultiprojectProjectAndTaskListIntegrationTest {
    @Test
    public void testSubProjectFromFullPath()
    {
-      GradlePluginLord gradlePluginLord = new GradlePluginLord();
-      gradlePluginLord.setCurrentDirectory(javaprojectDir);
-      gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
-
       //refresh the projects and wait. This will throw an exception if it fails.
       TestUtility.refreshProjectsBlocking(gradlePluginLord, 80, TimeUnit.SECONDS);
 
@@ -200,10 +188,6 @@ class MultiprojectProjectAndTaskListIntegrationTest {
    @Test
    public void testGetTaskFromFullPath()
    {
-      GradlePluginLord gradlePluginLord = new GradlePluginLord();
-      gradlePluginLord.setCurrentDirectory(javaprojectDir);
-      gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
-
       //refresh the projects and wait. This will throw an exception if it fails.
       TestUtility.refreshProjectsBlocking(gradlePluginLord, 100, TimeUnit.SECONDS);
 
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java b/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
index edd547b..d2ce564 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
@@ -15,10 +15,9 @@
  */
 package org.gradle.foundation;
 
-import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
+import org.gradle.util.internal.ArgumentsSplitter;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
@@ -28,17 +27,8 @@ import java.util.List;
  * @author mhunsicker
  */
 public class CommandLineAssistant {
-    private final DefaultCommandLineConverter commandLineConverter;
     private final LoggingCommandLineConverter loggingCommandLineConverter = new LoggingCommandLineConverter();
 
-    public CommandLineAssistant() {
-        commandLineConverter = new DefaultCommandLineConverter();
-    }
-
-    public DefaultCommandLineConverter getCommandLineConverter() {
-        return commandLineConverter;
-    }
-
     public LoggingCommandLineConverter getLoggingCommandLineConverter() {
         return loggingCommandLineConverter;
     }
@@ -50,35 +40,7 @@ public class CommandLineAssistant {
      * @return a string array of the separate command line arguments.
      */
     public static String[] breakUpCommandLine(String fullCommandLine) {
-        List<String> commandLineArguments = new ArrayList<String>();
-
-        Character currentQuote = null;
-        StringBuilder currentOption = new StringBuilder();
-        boolean hasOption = false;
-
-        for (int index = 0; index < fullCommandLine.length(); index++) {
-            char c = fullCommandLine.charAt(index);
-            if (currentQuote == null && Character.isWhitespace(c)) {
-                if (hasOption) {
-                    commandLineArguments.add(currentOption.toString());
-                    hasOption = false;
-                    currentOption.setLength(0);
-                }
-            } else if (currentQuote == null && (c == '"' || c == '\'')) {
-                currentQuote = c;
-                hasOption = true;
-            } else if (currentQuote != null && c == currentQuote) {
-                currentQuote = null;
-            } else {
-                currentOption.append(c);
-                hasOption = true;
-            }
-        }
-
-        if (hasOption) {
-            commandLineArguments.add(currentOption.toString());
-        }
-
+        List<String> commandLineArguments = ArgumentsSplitter.split(fullCommandLine);
         return commandLineArguments.toArray(new String[commandLineArguments.size()]);
     }
 
@@ -94,7 +56,7 @@ public class CommandLineAssistant {
     public boolean hasShowStacktraceDefined(String[] commandLineArguments) {
         return hasCommandLineOptionsDefined(commandLineArguments, new CommandLineSearch() {
             public boolean contains(String commandLine) {
-                return commandLineConverter.getShowStacktrace(commandLine) != null;
+                return loggingCommandLineConverter.getShowStacktrace(commandLine) != null;
             }
         });
     }
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java b/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
index c2c86cd..14198a4 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
@@ -1,344 +1,344 @@
-/*
- * 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.foundation.common;
-
-import java.util.*;
-
-/**
- * Utility class that allows lists to be reordered.
- *
- * @author Chris Hampton
- */
-public class ListReorderer {
-    /**
-     * Moves the object down one position in the group list.
-     *
-     * @param sourceList The list whose elements we want to reorder.
-     * @param object The object to move.
-     */
-    public static boolean moveBefore(List sourceList, Object object) {
-        // Get the old index of the object
-        int previousIndex = sourceList.indexOf(object);
-        // If the index of the object is 0 it can't go any lower. If it's
-        // -1 it's not even in the list. In these cases we do nothing.
-        if (previousIndex < 1) {
-            return false;
-        }
-        // Remove the object from it's old position in the list, this shifts all
-        // elements above it down by 1
-        sourceList.remove(object);
-        // Add the object back at 1 less than it's original index, the old
-        // element at this position is shifted to right in the lise
-        sourceList.add(previousIndex - 1, object);
-        // If we get here we assume we moved it.
-        return true;
-    }
-
-    /**
-     * Moves a list of elements in this list while keeping their relative positions. When the first element reaches the beginning it goes no further and the other elements in the list will continue to
-     * be shifted on subsequent calls as long as they don't overwrite previously moved elements. This means that elements with other elements between them will continue move with the same distance
-     * between them but will 'bunch up' toward the beginning of the list.
-     *
-     * NOTE: The order of the list of moved elements is important. They have to be added in order from lowest index to highest.
-     *
-     * @param sourceList The list whose elements we want to reorder.
-     * @param objectsToMove List of elements to move.
-     */
-    public static void moveBefore(List sourceList, List objectsToMove) {
-        sortMoveListByRelativeOrder(sourceList, objectsToMove);
-        // Create a new list to put elements in we try to move
-        List triedToMove = new ArrayList();
-        // Now iterate through the elements to move and attempt to move them
-        Iterator iterator = objectsToMove.iterator();
-        while (iterator.hasNext()) {
-            // Get the next object to move
-            Object objectToMove = iterator.next();
-            // Get the index of the object to move
-            int currentPosition = sourceList.indexOf(objectToMove);
-            // Only move the element if it's not already at the front of the list
-            if (currentPosition > 0) {
-                // Get the element at the position we want to move to and make sure it's not
-                // an element in the list that we'vd already moved
-                Object occupyingObject = sourceList.get(currentPosition - 1);
-                if (currentPosition < sourceList.size() && !triedToMove.contains(occupyingObject)) {
-                    moveBefore(sourceList, objectToMove);
-                }
-            }
-            // If we get here we have at least tried to move the object,
-            // so stick it in the tried list
-            triedToMove.add(objectToMove);
-        }
-    }
-
-    /**
-     * Moves a list of objects to a new index location.
-     *
-     * @param sourceList The list where the move will occur.
-     * @param moveList The objects to move.
-     * @param index The object's new location in the list.
-     */
-    public static void moveTo(List sourceList, List moveList, int index) {
-        // First make sure the move is valid
-        if (index < 0 || index >= sourceList.size()) {
-            return;
-        }
-        // Store the object at the index to move to
-        Object moveBeforeObject = sourceList.get(index);
-
-        //This fixes a bug. This happens if the user selects things and moves them to an index where something is already selected. I select 1, 2, and 4 and I say move to 2. 2 is already selected. This makes no sense, but its happened in the field.
-        if (moveList.contains(moveBeforeObject)) //just remove the item from the move list.
-        {
-            List newMoveList = new ArrayList(
-                    moveList);   //we don't want to actually affect the move list. Callers use it for visually selecting items after the move. So we'll make a duplicate and just recursively call ourselves again.
-            newMoveList.remove(moveBeforeObject);
-            moveTo(sourceList, newMoveList, index + 1);   //skip over the one we took out
-            return;
-        }
-
-        // Remove the object from it's old position
-        sourceList.removeAll(moveList);
-        // Get the new index value after shifts
-        index = sourceList.indexOf(moveBeforeObject);
-
-        //make sure the index is within bounds.
-        if (index < 0) {
-            index = 0;
-        }
-        if (index > sourceList.size() - 1) {
-            index = sourceList.size() - 1;
-        }
-
-        // Add the element to the new location
-        sourceList.addAll(index, moveList);
-    }
-
-    /**
-     * Moves an object to the front of the list.
-     *
-     * @param sourceList The list the object is in.
-     * @param object The object to move.
-     * @return True if the object was in the list and it was moved.
-     */
-    public static boolean moveToFront(List sourceList, Object object) {
-        boolean moved = false;
-        // If we can remove it, then it was in the list
-        if (sourceList.remove(object)) {
-            sourceList.add(0, object); // This is a void method, so it doesn't set our flag
-            moved = true;
-        }
-
-        return moved;
-    }
-
-    /**
-     * Moves a list of objects to the front of the list.
-     *
-     * @param sourceList The list the object is in.
-     * @param objectsToMove The object to move.
-     */
-    public static void moveToFront(List sourceList, List objectsToMove) {
-        sortMoveListByRelativeOrder(sourceList, objectsToMove);
-        for (int i = objectsToMove.size() - 1; i >= 0; i--) {
-            Object object = objectsToMove.get(i);
-            if (sourceList.remove(object)) {
-                sourceList.add(0, object);
-            }
-        }
-    }
-
-    /**
-     * Moves the object up one index position in the list.
-     *
-     * @param sourceList The list whose elements we want to reorder.
-     * @param object The object to move.
-     */
-    public static boolean moveAfter(List sourceList, Object object) {
-        // Get the old index of the object
-        int previousIndex = sourceList.indexOf(object);
-        // If the index of the object is 0 it can't go any higher. If it's
-        // -1 it's not even in the list. In these cases we do nothing.
-        if (previousIndex >= sourceList.size() - 1 || previousIndex == -1) {
-            return false;
-        }
-        // Remove the object from it's old position in the list, this shifts all
-        // elements above it down by 1
-        sourceList.remove(object);
-        // Add the object back at 2 higher than it's original index, if we only
-        // add one than we just place it back where it was since everything shifted
-        // down when we removed it
-        sourceList.add(previousIndex + 1, object);
-        // If we get here we assume we moved it.
-        return true;
-    }
-
-    /**
-     * Moves the objects in the list up one index position in this list while maintaining their relative position. When an element reaches the end of the list it can go no farther, but the other
-     * elements continue to move each call without overwriting previously moved elements. This causes moved elements to 'bunch up' at the end of the list.
-     *
-     * NOTE: The order of the list of moved elements is important. They have to be added in order from lowest index to highest.
-     *
-     * @param sourceList The list whose elements we want to reorder.
-     * @param objectsToMove List of elements to move.
-     */
-    public static void moveAfter(List sourceList, List objectsToMove) {
-        sortMoveListByRelativeOrder(sourceList, objectsToMove);
-        List triedToMove = new ArrayList();
-        // Since we are moving elements to a greater index in the list,
-        // we iterate through the list backwards to move the highest indexed
-        // element first
-        for (int i = objectsToMove.size() - 1; i >= 0; i--) {
-            Object objectToMove = objectsToMove.get(i);
-            // Get the index of the object to move
-            int currentPosition = sourceList.indexOf(objectToMove);
-            // Make sure the element we want to move isn't already at the end of
-            // the list
-            if (currentPosition < sourceList.size() - 1) {
-                // Now get the index of the elment occupying the spot we want
-                // to move to and only move the current objectToMove if it
-                // does not overwite a previously moved element
-                Object occupyingObject = sourceList.get(currentPosition + 1);
-                if (!triedToMove.contains(occupyingObject)) {
-                    moveAfter(sourceList, objectToMove);
-                }
-            }
-            // If we get here we have at least tried to move the object,
-            // so stick it in the tried list
-            triedToMove.add(objectToMove);
-        }
-    }
-
-    /**
-     * Moves an object to the back of the list.
-     *
-     * @param sourceList The list the object is in.
-     * @param object The object to move.
-     * @return True if the object was in the list and it was moved.
-     */
-    public static boolean moveToBack(List sourceList, Object object) {
-        boolean moved = false;
-
-        // If we can remove it, then it was in the list
-        if (sourceList.remove(object)) {
-            moved = sourceList.add(object);
-        }
-
-        return moved;
-    }
-
-    /**
-     * Moves a list of objects to the front of the list.
-     *
-     * @param sourceList The list the object is in.
-     * @param objectsToMove The object to move.
-     */
-    public static void moveToBack(List sourceList, List objectsToMove) {
-        sortMoveListByRelativeOrder(sourceList, objectsToMove);
-        for (int i = 0; i < objectsToMove.size(); i++) {
-            Object object = objectsToMove.get(i);
-            if (sourceList.remove(object)) {
-                sourceList.add(object);
-            }
-        }
-    }
-
-    /**
-     * Sorts a child list by position in a parent list to preserve relative ordering of the elements.
-     *
-     * @param parentList .
-     * @param childList .
-     */
-    public static void sortMoveListByRelativeOrder(final List parentList, List childList) {
-        Collections.sort(childList, new Comparator() {
-            public int compare(Object o, Object o1) {
-                int index = parentList.indexOf(o);
-                int index1 = parentList.indexOf(o1);
-                return (index < index1) ? -1 : (index > index1) ? 1 : 0;
-            }
-        });
-    }
-
-    /**
-     * Returns true if all the elements of the check list are at the end of the source list.
-     *
-     * @param sourceList .
-     * @param checkList .
-     * @return .
-     */
-    public static boolean allElementsInFront(List sourceList, List checkList) {
-        // Quick check, if the source list doesn't contain all elements of the checklist,
-        // abort and return false
-        if (!sourceList.containsAll(checkList)) {
-            return false;
-        }
-        // Get the last index of the source list
-        int sourceIndex = checkList.size();
-        // Iterate thru the check list. Find the index of the element
-        // in the source list; and check it's index against the index
-        // we should be on to match the source.
-        for (int index = 0; index < checkList.size(); index++) {
-            Object element = checkList.get(index);
-            int checkIndex = sourceList.indexOf(element);
-            if (checkIndex >= sourceIndex) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    public static boolean allElementsInBack(List sourceList, List checkList) {
-        // Quick check, if the source list doesn't contain all elements of the checklist,
-        // abort and return false
-        if (!sourceList.containsAll(checkList)) {
-            return false;
-        }
-        // Get the last index of the source list
-        int sourceIndex = sourceList.size() - checkList.size();
-        // Iterate thru the check list. Find the index of the element
-        // in the source list; and check it's index against the index
-        // we should be on to match the source.
-        for (int index = checkList.size() - 1; index >= 0; index--) {
-            Object element = checkList.get(index);
-            int checkIndex = sourceList.indexOf(element);
-            if (checkIndex < sourceIndex) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * This is mainly used for after doing a move. It gives you the current index of all the moved elements. This is useful for UIs that need to reselect the new items.
-     *
-     * @param sourceList the source list
-     * @param objectsToMove the elements to move
-     * @return an integer array of the items to select.
-     */
-    public static int[] getIndices(List sourceList, List objectsToMove) {
-        int[] newIndices = new int[objectsToMove.size()];
-
-        for (int index = 0; index < objectsToMove.size(); index++) {
-            Object elementToMove = objectsToMove.get(index);
-            int sourceIndexOfElement = sourceList.indexOf(elementToMove);
-
-            newIndices[index] = sourceIndexOfElement;
-        }
-
-        return newIndices;
-    }
-}
-
+/*
+ * 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.foundation.common;
+
+import java.util.*;
+
+/**
+ * Utility class that allows lists to be reordered.
+ *
+ * @author Chris Hampton
+ */
+public class ListReorderer {
+    /**
+     * Moves the object down one position in the group list.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param object The object to move.
+     */
+    public static <T> boolean moveBefore(List<T> sourceList, T object) {
+        // Get the old index of the object
+        int previousIndex = sourceList.indexOf(object);
+        // If the index of the object is 0 it can't go any lower. If it's
+        // -1 it's not even in the list. In these cases we do nothing.
+        if (previousIndex < 1) {
+            return false;
+        }
+        // Remove the object from it's old position in the list, this shifts all
+        // elements above it down by 1
+        sourceList.remove(object);
+        // Add the object back at 1 less than it's original index, the old
+        // element at this position is shifted to right in the lise
+        sourceList.add(previousIndex - 1, object);
+        // If we get here we assume we moved it.
+        return true;
+    }
+
+    /**
+     * Moves a list of elements in this list while keeping their relative positions. When the first element reaches the beginning it goes no further and the other elements in the list will continue to
+     * be shifted on subsequent calls as long as they don't overwrite previously moved elements. This means that elements with other elements between them will continue move with the same distance
+     * between them but will 'bunch up' toward the beginning of the list.
+     *
+     * NOTE: The order of the list of moved elements is important. They have to be added in order from lowest index to highest.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param objectsToMove List of elements to move.
+     */
+    public static <T> void moveBefore(List<T> sourceList, List<T> objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        // Create a new list to put elements in we try to move
+        List<T> triedToMove = new ArrayList<T>();
+        // Now iterate through the elements to move and attempt to move them
+        Iterator<T> iterator = objectsToMove.iterator();
+        while (iterator.hasNext()) {
+            // Get the next object to move
+            T objectToMove = iterator.next();
+            // Get the index of the object to move
+            int currentPosition = sourceList.indexOf(objectToMove);
+            // Only move the element if it's not already at the front of the list
+            if (currentPosition > 0) {
+                // Get the element at the position we want to move to and make sure it's not
+                // an element in the list that we'vd already moved
+                T occupyingObject = sourceList.get(currentPosition - 1);
+                if (currentPosition < sourceList.size() && !triedToMove.contains(occupyingObject)) {
+                    moveBefore(sourceList, objectToMove);
+                }
+            }
+            // If we get here we have at least tried to move the object,
+            // so stick it in the tried list
+            triedToMove.add(objectToMove);
+        }
+    }
+
+    /**
+     * Moves a list of objects to a new index location.
+     *
+     * @param sourceList The list where the move will occur.
+     * @param moveList The objects to move.
+     * @param index The object's new location in the list.
+     */
+    public static <T> void moveTo(List<T> sourceList, List<T> moveList, int index) {
+        // First make sure the move is valid
+        if (index < 0 || index >= sourceList.size()) {
+            return;
+        }
+        // Store the object at the index to move to
+        T moveBeforeObject = sourceList.get(index);
+
+        //This fixes a bug. This happens if the user selects things and moves them to an index where something is already selected. I select 1, 2, and 4 and I say move to 2. 2 is already selected. This makes no sense, but its happened in the field.
+        if (moveList.contains(moveBeforeObject)) //just remove the item from the move list.
+        {
+            List<T> newMoveList = new ArrayList<T>(
+                    moveList);   //we don't want to actually affect the move list. Callers use it for visually selecting items after the move. So we'll make a duplicate and just recursively call ourselves again.
+            newMoveList.remove(moveBeforeObject);
+            moveTo(sourceList, newMoveList, index + 1);   //skip over the one we took out
+            return;
+        }
+
+        // Remove the object from it's old position
+        sourceList.removeAll(moveList);
+        // Get the new index value after shifts
+        index = sourceList.indexOf(moveBeforeObject);
+
+        //make sure the index is within bounds.
+        if (index < 0) {
+            index = 0;
+        }
+        if (index > sourceList.size() - 1) {
+            index = sourceList.size() - 1;
+        }
+
+        // Add the element to the new location
+        sourceList.addAll(index, moveList);
+    }
+
+    /**
+     * Moves an object to the front of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param object The object to move.
+     * @return True if the object was in the list and it was moved.
+     */
+    public static <T> boolean moveToFront(List<T> sourceList, T object) {
+        boolean moved = false;
+        // If we can remove it, then it was in the list
+        if (sourceList.remove(object)) {
+            sourceList.add(0, object); // This is a void method, so it doesn't set our flag
+            moved = true;
+        }
+
+        return moved;
+    }
+
+    /**
+     * Moves a list of objects to the front of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param objectsToMove The object to move.
+     */
+    public static <T> void moveToFront(List<T> sourceList, List<T> objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        for (int i = objectsToMove.size() - 1; i >= 0; i--) {
+            T object = objectsToMove.get(i);
+            if (sourceList.remove(object)) {
+                sourceList.add(0, object);
+            }
+        }
+    }
+
+    /**
+     * Moves the object up one index position in the list.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param object The object to move.
+     */
+    public static <T> boolean moveAfter(List<T> sourceList, T object) {
+        // Get the old index of the object
+        int previousIndex = sourceList.indexOf(object);
+        // If the index of the object is 0 it can't go any higher. If it's
+        // -1 it's not even in the list. In these cases we do nothing.
+        if (previousIndex >= sourceList.size() - 1 || previousIndex == -1) {
+            return false;
+        }
+        // Remove the object from it's old position in the list, this shifts all
+        // elements above it down by 1
+        sourceList.remove(object);
+        // Add the object back at 2 higher than it's original index, if we only
+        // add one than we just place it back where it was since everything shifted
+        // down when we removed it
+        sourceList.add(previousIndex + 1, object);
+        // If we get here we assume we moved it.
+        return true;
+    }
+
+    /**
+     * Moves the objects in the list up one index position in this list while maintaining their relative position. When an element reaches the end of the list it can go no farther, but the other
+     * elements continue to move each call without overwriting previously moved elements. This causes moved elements to 'bunch up' at the end of the list.
+     *
+     * NOTE: The order of the list of moved elements is important. They have to be added in order from lowest index to highest.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param objectsToMove List of elements to move.
+     */
+    public static <T> void moveAfter(List<T> sourceList, List<T> objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        List<T> triedToMove = new ArrayList<T>();
+        // Since we are moving elements to a greater index in the list,
+        // we iterate through the list backwards to move the highest indexed
+        // element first
+        for (int i = objectsToMove.size() - 1; i >= 0; i--) {
+            T objectToMove = objectsToMove.get(i);
+            // Get the index of the object to move
+            int currentPosition = sourceList.indexOf(objectToMove);
+            // Make sure the element we want to move isn't already at the end of
+            // the list
+            if (currentPosition < sourceList.size() - 1) {
+                // Now get the index of the elment occupying the spot we want
+                // to move to and only move the current objectToMove if it
+                // does not overwite a previously moved element
+                T occupyingObject = sourceList.get(currentPosition + 1);
+                if (!triedToMove.contains(occupyingObject)) {
+                    moveAfter(sourceList, objectToMove);
+                }
+            }
+            // If we get here we have at least tried to move the object,
+            // so stick it in the tried list
+            triedToMove.add(objectToMove);
+        }
+    }
+
+    /**
+     * Moves an object to the back of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param object The object to move.
+     * @return True if the object was in the list and it was moved.
+     */
+    public static <T> boolean moveToBack(List<T> sourceList, T object) {
+        boolean moved = false;
+
+        // If we can remove it, then it was in the list
+        if (sourceList.remove(object)) {
+            moved = sourceList.add(object);
+        }
+
+        return moved;
+    }
+
+    /**
+     * Moves a list of objects to the front of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param objectsToMove The object to move.
+     */
+    public static <T> void moveToBack(List<T> sourceList, List<T> objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        for (int i = 0; i < objectsToMove.size(); i++) {
+            T object = objectsToMove.get(i);
+            if (sourceList.remove(object)) {
+                sourceList.add(object);
+            }
+        }
+    }
+
+    /**
+     * Sorts a child list by position in a parent list to preserve relative ordering of the elements.
+     *
+     * @param parentList .
+     * @param childList .
+     */
+    public static <T> void sortMoveListByRelativeOrder(final List<T> parentList, List<T> childList) {
+        Collections.sort(childList, new Comparator<T>() {
+            public int compare(T o, T o1) {
+                int index = parentList.indexOf(o);
+                int index1 = parentList.indexOf(o1);
+                return (index < index1) ? -1 : (index > index1) ? 1 : 0;
+            }
+        });
+    }
+
+    /**
+     * Returns true if all the elements of the check list are at the end of the source list.
+     *
+     * @param sourceList .
+     * @param checkList .
+     * @return .
+     */
+    public static <T> boolean allElementsInFront(List<T> sourceList, List<T> checkList) {
+        // Quick check, if the source list doesn't contain all elements of the checklist,
+        // abort and return false
+        if (!sourceList.containsAll(checkList)) {
+            return false;
+        }
+        // Get the last index of the source list
+        int sourceIndex = checkList.size();
+        // Iterate thru the check list. Find the index of the element
+        // in the source list; and check it's index against the index
+        // we should be on to match the source.
+        for (int index = 0; index < checkList.size(); index++) {
+            T element = checkList.get(index);
+            int checkIndex = sourceList.indexOf(element);
+            if (checkIndex >= sourceIndex) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static <T> boolean allElementsInBack(List<T> sourceList, List<T> checkList) {
+        // Quick check, if the source list doesn't contain all elements of the checklist,
+        // abort and return false
+        if (!sourceList.containsAll(checkList)) {
+            return false;
+        }
+        // Get the last index of the source list
+        int sourceIndex = sourceList.size() - checkList.size();
+        // Iterate thru the check list. Find the index of the element
+        // in the source list; and check it's index against the index
+        // we should be on to match the source.
+        for (int index = checkList.size() - 1; index >= 0; index--) {
+            T element = checkList.get(index);
+            int checkIndex = sourceList.indexOf(element);
+            if (checkIndex < sourceIndex) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * This is mainly used for after doing a move. It gives you the current index of all the moved elements. This is useful for UIs that need to reselect the new items.
+     *
+     * @param sourceList the source list
+     * @param objectsToMove the elements to move
+     * @return an integer array of the items to select.
+     */
+    public static <T> int[] getIndices(List<T> sourceList, List<T> objectsToMove) {
+        int[] newIndices = new int[objectsToMove.size()];
+
+        for (int index = 0; index < objectsToMove.size(); index++) {
+            T elementToMove = objectsToMove.get(index);
+            int sourceIndexOfElement = sourceList.indexOf(elementToMove);
+
+            newIndices[index] = sourceIndexOfElement;
+        }
+
+        return newIndices;
+    }
+}
+
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java b/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
index 4b114cf..58d5ae3 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
@@ -1,168 +1,168 @@
-/*
- * 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.foundation.common;
-
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-
-import javax.swing.*;
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * This is a Swing-friendly observer manager class. Swing-friendly, but can be used by non-Swing classes. Its meant to abstract the fact that you probably need to be in the Event Dispatch Thread when
- * receiving notifications inside Swing-related classes.
- *
- * To use this class, add it as a member variable (don't derive from this!) of a class that you want to be observered. You can have multiple instances of this if you want to allow for a finer
- * granularity of observing (similar to components having mouse move listeners and mouse (click) listeners). Next, create an interface for the observers. Now implement add and remove observer
- * functions that call the add and remove functions here. Lastly, implement ObserverNotification and have it call the aforementioned observer interface appropriately. Note: you should actually
- * implement ObserverNotification for each "message" you want to send. Example: One that would tell a view a node was added. One that would tell a view a node was deleted, etc. While you have multiple
- * notification classes, you only need 1 (or few) actual observer interfaces, containing all the possible functions called by all notifications.
- *
- * @author mhunsicker
- */
-
-public class ObserverLord<E> {
-    private List<E> regularObservers = new ArrayList<E>();
-    private List<E> eventQueueObservers = new ArrayList<E>();
-
-    private final Logger logger = Logging.getLogger(ObserverLord.class);
-
-    /**
-     * Implement this for each call to ObserverLord.notifyObservers. The notify function usually just has a single call to a function on the observer.
-     *
-     * Example:
-     * <pre>
-     * public void notify( MyObserver observer )
-     * {
-     * observer.myfunction();
-     * }
-     * </pre>
-     */
-    public interface ObserverNotification<E> {
-        public void notify(E observer);
-    }
-
-    /**
-     * Adds an observer to our messaging system.
-     *
-     * <!       Name        Description  >
-     *
-     * @param observer observer to add.
-     * @param inEventQueue true to notify this observer only in the event queue, false to notify it immediately.
-     */
-
-    public void addObserver(E observer, boolean inEventQueue) {
-        if (!inEventQueue) {
-            addIfNew(observer, regularObservers);
-        } else {
-            addIfNew(observer, eventQueueObservers);
-        }
-    }
-
-    private void addIfNew(E observer, List<E> destinationList) {
-        if (!destinationList.contains(observer)) {
-            destinationList.add(observer);
-        }
-    }
-
-    /**
-     * Deletes an observer in our messaging system.
-     *
-     * <!       Name     Dir   Description  >
-     *
-     * @param observer in,
-     */
-    public void removeObserver(E observer) {
-        regularObservers.remove(observer);
-        eventQueueObservers.remove(observer);
-    }
-
-    public void removeAllObservers() {
-        regularObservers.clear();
-        eventQueueObservers.clear();
-    }
-
-    /**
-     * Messaging method that handles telling each observer that something happen to the observable.
-     *
-     * <!       Name        Dir   Description  >
-     *
-     * @param notification in,  notification sent to the observer
-     */
-    public void notifyObservers(ObserverNotification<E> notification) {
-        //notify all the non-event queue observers now.
-        notifyObserversInternal(regularObservers, notification);
-        notifyObserversInEventQueueThread(notification);
-    }
-
-    /**
-     * Here is where we notify all the event queue observers. To notify the event queue observers we have to make sure it occurs in the event queue thread. If we're not in the event queue, we'll wrap
-     * it in an invoke and wait.
-     *
-     * <!       Name        Dir   Description  > <!       Name        Dir   Description  >
-     *
-     * @param notification in,  notification sent to the observer
-     */
-    private void notifyObserversInEventQueueThread(final ObserverNotification notification) {
-        if (eventQueueObservers.size() == 0) //if we have no event queue observsers, we're done
-        {
-            return;
-        }
-
-        if (EventQueue.isDispatchThread()) {
-            notifyObserversInternal(eventQueueObservers, notification);
-        } else {
-            try {
-                SwingUtilities.invokeAndWait(new Runnable() {
-                    public void run() {
-                        notifyObserversInternal(eventQueueObservers, notification);
-                    }
-                });
-            } catch (Exception e) {
-                logger.error("notifyObservers exception", e);
-            }
-        }
-    }
-
-    /**
-     * The internal mechanism that actually notifies the observers. We just iterate though each observer and pass it to the notification mechanism.
-     *
-     *
-     * <!       Name         Dir  Description  >
-     *
-     * @param observers in,  objects that changed (observable)
-     * @param notification in,  notification sent to the observer
-     */
-    private void notifyObserversInternal(List<E> observers, ObserverNotification notification) {
-        Iterator<E> iterator = observers.iterator();
-        while (iterator.hasNext()) {
-            E observer = iterator.next();
-            try {
-                notification.notify(observer);
-            } catch (Exception e) //this is so an error in the notification doesn't stop the entire process.
-            {
-                logger.error("error notifying observers", e);
-            }
-        }
-    }
-
-    public String toString() {
-        return regularObservers.size() + " regular observers, " + eventQueueObservers.size() + " event queue observers";
-    }
+/*
+ * 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.foundation.common;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This is a Swing-friendly observer manager class. Swing-friendly, but can be used by non-Swing classes. Its meant to abstract the fact that you probably need to be in the Event Dispatch Thread when
+ * receiving notifications inside Swing-related classes.
+ *
+ * To use this class, add it as a member variable (don't derive from this!) of a class that you want to be observered. You can have multiple instances of this if you want to allow for a finer
+ * granularity of observing (similar to components having mouse move listeners and mouse (click) listeners). Next, create an interface for the observers. Now implement add and remove observer
+ * functions that call the add and remove functions here. Lastly, implement ObserverNotification and have it call the aforementioned observer interface appropriately. Note: you should actually
+ * implement ObserverNotification for each "message" you want to send. Example: One that would tell a view a node was added. One that would tell a view a node was deleted, etc. While you have multiple
+ * notification classes, you only need 1 (or few) actual observer interfaces, containing all the possible functions called by all notifications.
+ *
+ * @author mhunsicker
+ */
+
+public class ObserverLord<E> {
+    private List<E> regularObservers = new ArrayList<E>();
+    private List<E> eventQueueObservers = new ArrayList<E>();
+
+    private final Logger logger = Logging.getLogger(ObserverLord.class);
+
+    /**
+     * Implement this for each call to ObserverLord.notifyObservers. The notify function usually just has a single call to a function on the observer.
+     *
+     * Example:
+     * <pre>
+     * public void notify( MyObserver observer )
+     * {
+     * observer.myfunction();
+     * }
+     * </pre>
+     */
+    public interface ObserverNotification<E> {
+        public void notify(E observer);
+    }
+
+    /**
+     * Adds an observer to our messaging system.
+     *
+     * <!       Name        Description  >
+     *
+     * @param observer observer to add.
+     * @param inEventQueue true to notify this observer only in the event queue, false to notify it immediately.
+     */
+
+    public void addObserver(E observer, boolean inEventQueue) {
+        if (!inEventQueue) {
+            addIfNew(observer, regularObservers);
+        } else {
+            addIfNew(observer, eventQueueObservers);
+        }
+    }
+
+    private void addIfNew(E observer, List<E> destinationList) {
+        if (!destinationList.contains(observer)) {
+            destinationList.add(observer);
+        }
+    }
+
+    /**
+     * Deletes an observer in our messaging system.
+     *
+     * <!       Name     Dir   Description  >
+     *
+     * @param observer in,
+     */
+    public void removeObserver(E observer) {
+        regularObservers.remove(observer);
+        eventQueueObservers.remove(observer);
+    }
+
+    public void removeAllObservers() {
+        regularObservers.clear();
+        eventQueueObservers.clear();
+    }
+
+    /**
+     * Messaging method that handles telling each observer that something happen to the observable.
+     *
+     * <!       Name        Dir   Description  >
+     *
+     * @param notification in,  notification sent to the observer
+     */
+    public void notifyObservers(ObserverNotification<E> notification) {
+        //notify all the non-event queue observers now.
+        notifyObserversInternal(regularObservers, notification);
+        notifyObserversInEventQueueThread(notification);
+    }
+
+    /**
+     * Here is where we notify all the event queue observers. To notify the event queue observers we have to make sure it occurs in the event queue thread. If we're not in the event queue, we'll wrap
+     * it in an invoke and wait.
+     *
+     * <!       Name        Dir   Description  > <!       Name        Dir   Description  >
+     *
+     * @param notification in,  notification sent to the observer
+     */
+    private void notifyObserversInEventQueueThread(final ObserverNotification<E> notification) {
+        if (eventQueueObservers.size() == 0) //if we have no event queue observsers, we're done
+        {
+            return;
+        }
+
+        if (EventQueue.isDispatchThread()) {
+            notifyObserversInternal(eventQueueObservers, notification);
+        } else {
+            try {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                    public void run() {
+                        notifyObserversInternal(eventQueueObservers, notification);
+                    }
+                });
+            } catch (Exception e) {
+                logger.error("notifyObservers exception", e);
+            }
+        }
+    }
+
+    /**
+     * The internal mechanism that actually notifies the observers. We just iterate though each observer and pass it to the notification mechanism.
+     *
+     *
+     * <!       Name         Dir  Description  >
+     *
+     * @param observers in,  objects that changed (observable)
+     * @param notification in,  notification sent to the observer
+     */
+    private void notifyObserversInternal(List<E> observers, ObserverNotification<E> notification) {
+        Iterator<E> iterator = observers.iterator();
+        while (iterator.hasNext()) {
+            E observer = iterator.next();
+            try {
+                notification.notify(observer);
+            } catch (Exception e) //this is so an error in the notification doesn't stop the entire process.
+            {
+                logger.error("error notifying observers", e);
+            }
+        }
+    }
+
+    public String toString() {
+        return regularObservers.size() + " regular observers, " + eventQueueObservers.size() + " event queue observers";
+    }
 }
\ No newline at end of file
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java b/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
index 6b4521a..f98fba0 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
@@ -148,7 +148,7 @@ public class ReorderableList<E> implements List<E> {
        @return Returns true if the object was in the list and was moved.
     */
 
-    public boolean moveBefore(Object objectToMove) {
+    public boolean moveBefore(E objectToMove) {
         return ListReorderer.moveBefore(elements, objectToMove);
     }
 
@@ -167,7 +167,7 @@ public class ReorderableList<E> implements List<E> {
        @param  elementsToMove       List of elements to move.
     */
 
-    public void moveBefore(List elementsToMove) {
+    public void moveBefore(List<E> elementsToMove) {
         ListReorderer.moveBefore(elements, elementsToMove);
     }
 
@@ -177,7 +177,7 @@ public class ReorderableList<E> implements List<E> {
        @param  newIndex     The new location of the object.
     */
 
-    public void moveTo(List objectsToMove, int newIndex) {
+    public void moveTo(List<E> objectsToMove, int newIndex) {
         ListReorderer.moveTo(elements, objectsToMove, newIndex);
     }
 
@@ -187,7 +187,7 @@ public class ReorderableList<E> implements List<E> {
        @return True if the object was moved, false otherwise.
     */
 
-    public boolean moveToFront(Object objectToMove) {
+    public boolean moveToFront(E objectToMove) {
         return ListReorderer.moveToFront(elements, objectToMove);
     }
 
@@ -196,7 +196,7 @@ public class ReorderableList<E> implements List<E> {
        @param  elementsToMove  The list of objects to move in the list.
     */
 
-    public void moveToFront(List elementsToMove) {
+    public void moveToFront(List<E> elementsToMove) {
         ListReorderer.moveToFront(elements, elementsToMove);
     }
 
@@ -208,7 +208,7 @@ public class ReorderableList<E> implements List<E> {
        @return Returns true if the object was in the list and was moved.
     */
 
-    public boolean moveAfter(Object objectToMove) {
+    public boolean moveAfter(E objectToMove) {
         return ListReorderer.moveAfter(elements, objectToMove);
     }
 
@@ -225,7 +225,7 @@ public class ReorderableList<E> implements List<E> {
        @param  elementsToMove     List of elements to move.
     */
 
-    public void moveAfter(List elementsToMove) {
+    public void moveAfter(List<E> elementsToMove) {
         ListReorderer.moveAfter(elements, elementsToMove);
     }
 
@@ -235,7 +235,7 @@ public class ReorderableList<E> implements List<E> {
        @return Returns true if the object was in the list and was moved.
     */
 
-    public boolean moveToBack(Object objectToMove) {
+    public boolean moveToBack(E objectToMove) {
         return ListReorderer.moveToBack(elements, objectToMove);
     }
 
@@ -244,7 +244,7 @@ public class ReorderableList<E> implements List<E> {
        @param  elementsToMove The list of objects to move.
     */
 
-    public void moveToBack(List elementsToMove) {
+    public void moveToBack(List<E> elementsToMove) {
         ListReorderer.moveToBack(elements, elementsToMove);
     }
 
@@ -254,7 +254,7 @@ public class ReorderableList<E> implements List<E> {
                list, false otherwise.
     */
 
-    public boolean allElementsInFront(List checkList) {
+    public boolean allElementsInFront(List<E> checkList) {
         return ListReorderer.allElementsInFront(elements, checkList);
     }
 
@@ -264,7 +264,7 @@ public class ReorderableList<E> implements List<E> {
                list, false otherwise.
     */
 
-    public boolean allElementsInBack(List checkList) {
+    public boolean allElementsInBack(List<E> checkList) {
         return ListReorderer.allElementsInBack(elements, checkList);
     }
 
@@ -519,7 +519,7 @@ public class ReorderableList<E> implements List<E> {
     @author mhunsicker
     */
 
-    public int[] getIndices(List elementsToMove) {
+    public int[] getIndices(List<E> elementsToMove) {
         return ListReorderer.getIndices(elements, elementsToMove);
     }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
index ad22320..f1c80dd 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
@@ -16,7 +16,9 @@
 package org.gradle.foundation.ipc.gradle;
 
 import org.apache.commons.io.IOUtils;
-import org.gradle.StartParameter;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -26,8 +28,9 @@ import org.gradle.foundation.ipc.basic.ExecutionInfo;
 import org.gradle.foundation.ipc.basic.MessageObject;
 import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
 import org.gradle.initialization.DefaultCommandLineConverter;
-import org.gradle.util.Jvm;
-import org.gradle.util.OperatingSystem;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -49,7 +52,8 @@ public abstract class AbstractGradleServerProtocol implements ProcessLauncherSer
     private static final String INIT_SCRIPT_EXTENSION = ".gradle";
 
     private final Logger logger = Logging.getLogger(AbstractGradleServerProtocol.class);
-
+    private final TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
+    
     protected ProcessLauncherServer server;
     private boolean continueConnection;
     private boolean waitingOnHandshakeCompletion;
@@ -78,10 +82,10 @@ public abstract class AbstractGradleServerProtocol implements ProcessLauncherSer
         return continueConnection;
     }
 
-    private StartParameter.ShowStacktrace stackTraceLevel;
+    private ShowStacktrace stackTraceLevel;
 
     public AbstractGradleServerProtocol(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel,
-                                        StartParameter.ShowStacktrace stackTraceLevel) {
+                                        ShowStacktrace stackTraceLevel) {
         this.currentDirectory = currentDirectory;
         this.gradleHomeDirectory = gradleHomeDirectory;
         this.customGradleExecutor = customGradleExecutor;
@@ -251,7 +255,7 @@ public abstract class AbstractGradleServerProtocol implements ProcessLauncherSer
         CommandLineAssistant commandLineAssistant = new CommandLineAssistant();
 
         //add whatever the user ran
-        String[] individualCommandLineArguments = commandLineAssistant.breakUpCommandLine(commandLine);
+        String[] individualCommandLineArguments = CommandLineAssistant.breakUpCommandLine(commandLine);
         executionCommandLine.addAll(Arrays.asList(individualCommandLineArguments));
 
         File initStriptPath = getInitScriptFile();
@@ -271,7 +275,7 @@ public abstract class AbstractGradleServerProtocol implements ProcessLauncherSer
 
         //add the stack trace level if its not present
         if (!commandLineAssistant.hasShowStacktraceDefined(individualCommandLineArguments)) {
-            String stackTraceLevelText = commandLineAssistant.getCommandLineConverter().getShowStacktraceCommandLine(stackTraceLevel);
+            String stackTraceLevelText = commandLineAssistant.getLoggingCommandLineConverter().getShowStacktraceCommandLine(stackTraceLevel);
             if (stackTraceLevelText != null) {
                 executionCommandLine.add('-' + stackTraceLevelText);
             }
@@ -360,8 +364,8 @@ public abstract class AbstractGradleServerProtocol implements ProcessLauncherSer
     protected File extractInitScriptFile(Class resourceClass, String resourceName) {
         File file = null;
         try {
-            file = File.createTempFile(resourceName, INIT_SCRIPT_EXTENSION);
-        } catch (IOException e) {
+            file = temporaryFileProvider.createTemporaryFile(resourceName, INIT_SCRIPT_EXTENSION);
+        } catch (UncheckedIOException e) {
             logger.error("Creating init script file temp file", e);
             return null;
         }
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
index 2a830ff..3ab3ca5 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
@@ -217,7 +217,7 @@ public class ExecuteGradleCommandClientProtocol implements ClientProcess.Protoco
             String details = GradlePluginLord.getGradleExceptionMessage(buildResult.getFailure(), gradle.getStartParameter().getShowStacktrace());
             output += details;
 
-            client.sendMessage(ProtocolConstants.EXECUTION_COMPLETED_TYPE, output, new Boolean(wasSuccessful));
+            client.sendMessage(ProtocolConstants.EXECUTION_COMPLETED_TYPE, output, wasSuccessful);
 
             client.sendMessage(ProtocolConstants.EXITING, null, null);
             client.stop();
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
index 947b576..1a6172a 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.foundation.ipc.gradle;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.foundation.ipc.basic.MessageObject;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 
@@ -62,7 +62,7 @@ public class ExecuteGradleCommandServerProtocol extends AbstractGradleServerProt
     }
 
     public ExecuteGradleCommandServerProtocol(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel,
-                                              StartParameter.ShowStacktrace stackTraceLevel, ExecutionInteraction executionInteraction) {
+                                              ShowStacktrace stackTraceLevel, ExecutionInteraction executionInteraction) {
         super(currentDirectory, gradleHomeDirectory, customGradleExecutor, fullCommandLine, logLevel, stackTraceLevel);
         this.executionInteraction = executionInteraction;
     }
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
index 173b54f..4963768 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
@@ -15,12 +15,10 @@
  */
 package org.gradle.foundation.ipc.gradle;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
 import org.gradle.foundation.ProjectView;
 import org.gradle.foundation.ipc.basic.MessageObject;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 import java.util.List;
@@ -32,8 +30,6 @@ import java.util.List;
  * @author mhunsicker
  */
 public class TaskListServerProtocol extends AbstractGradleServerProtocol {
-    private final Logger logger = Logging.getLogger(TaskListServerProtocol.class);
-
     private static final String INIT_SCRIPT_NAME = "refresh-tasks-init-script";
 
     private ExecutionInteraction executionInteraction;
@@ -58,7 +54,7 @@ public class TaskListServerProtocol extends AbstractGradleServerProtocol {
         void reportLiveOutput(String message);
     }
 
-    public TaskListServerProtocol(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel,
+    public TaskListServerProtocol(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel, ShowStacktrace stackTraceLevel,
                                   ExecutionInteraction executionInteraction) {
         super(currentDirectory, gradleHomeDirectory, customGradleExecutor, fullCommandLine, logLevel, stackTraceLevel);
         this.executionInteraction = executionInteraction;
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java b/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
index 6b90b16..0fd04a0 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
@@ -112,4 +112,42 @@ public class LiveOutputParser {
     public String getText() {
         return totalTextToParse.toString();
     }
+
+    /**
+     * Returns the previous file link relative to the caret location. This will cycle around and get the last one if you're before the first link.
+     */
+    public FileLink getPreviousFileLink(int caretLocation) {
+        if (fileLinks.isEmpty()) {
+            return null;
+        }
+
+        //walk them in reverse order
+        for (int index = fileLinks.size() - 1; index >= 0; index--) {
+            FileLink fileLink = fileLinks.get(index);
+            if (fileLink.getEndingIndex() < caretLocation) {
+                return fileLink;
+            }
+        }
+
+        return fileLinks.get(fileLinks.size() - 1);
+    }
+
+    /**
+     * Returns the next file link relative to the caret location. This will cycle around and get the first one if you're after the last link.
+     */
+    public FileLink getNextFileLink(int caretLocation) {
+        if (fileLinks.isEmpty()) {
+            return null;
+        }
+
+        Iterator<FileLink> iterator = fileLinks.iterator();
+        while (iterator.hasNext()) {
+            FileLink fileLink = iterator.next();
+            if (fileLink.getStartingIndex() > caretLocation) {
+                return fileLink;
+            }
+        }
+
+        return fileLinks.get(0);
+    }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
index 5e62626..59869dc 100644
--- a/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
+++ b/subprojects/ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
@@ -1,160 +1,185 @@
-/*
- * 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.foundation.visitors;
-
-import org.gradle.foundation.ProjectView;
-import org.gradle.foundation.TaskView;
-import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
-import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * This visits each project and task in a hierarchical manner. This visitor is specifically meant to walk the projects first and tasks second for the purpose of populating a tree where the
- * projects/subprojects are first and the tasks are second.
- *
- * @author mhunsicker
- */
-public class TaskTreePopulationVisitor {
-    public interface Visitor<P, T> {
-        /*
-           This is called for each project.
-         @param  project    the project
-         @param indexOfProject
-         @param  parentProjectObject whatever you handed back from a prior call to
-                 visitProject if this is a sub project. Otherwise, it'll be whatever
-                 was passed into the visitPojectsAndTasks function.
-           @return an object that will be handed back to you for each of this
-                   project's tasks.
-           @author mhunsicker
-        */
-
-        public P visitProject(ProjectView project, int indexOfProject, P parentProjectObject);
-
-        /*
-           This is called for each task.
-
-         @param  task               the task
-         @param indexOfTask
-         @param  tasksProject       the project for this task
-         @param  userProjectObject  whatever you returned from the parent project's visitProject
-           @author mhunsicker
-        */
-
-        public T visitTask(TaskView task, int indexOfTask, ProjectView tasksProject, P userProjectObject);
-
-        /*
-           This is called when a project has been visited completely and is just a
-           notification giving you an opportunity to do whatever you like.
-           This is possibly where you want to delete any nodes that we didn't
-           visit.
-
-           @param  parentProjectObject the object that represents the parent of
-                                       the project and task objects below
-           @param  projectObjects      a list of whatever you returned from visitProject
-           @param  taskObjects         a list of whatever you returned from visitTask
-           @author mhunsicker
-        */
-
-        public void completedVisitingProject(P parentProjectObject, List<P> projectObjects, List<T> taskObjects);
-    }
-
-    /*
-       This visitor will visit each project, sub-project and task that was discovered
-       by the GradleHelper. This is useful for building a list or tree of
-       projects and tasks.
-
-       This is the same as the other version of visitProjectsAndTasks except this
-       one visits everything.
-
-       @author mhunsicker
-    */
-
-    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor, P rootProjectObject) {
-        visitProjectAndTasks(projects, visitor, new AllowAllProjectAndTaskFilter(), rootProjectObject);
-    }
-
-    /*
-       This visitor will visit each project, sub-project and task that was discovered
-       by the GradleHelper. This is useful for building a list or tree of
-       projects and tasks.
-
-       @param  visitor    this notified you of each project and task.
-       @param  filter     allows you to skip projects and tasks as specified by the filter.
-       @param  rootProjectObject whatever you pass here will be passed to the
-                root-level projects as parentProjectObject.
-       @author mhunsicker
-    */
-
-    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor, ProjectAndTaskFilter filter, P rootProjectObject) {
-        List<P> userProjectObjects = visitProjects(visitor, filter, projects, rootProjectObject);
-
-        //notify the visitation of the root projects. There are no tasks for this one, but there are projects.
-        visitor.completedVisitingProject(rootProjectObject, userProjectObjects, Collections.EMPTY_LIST);
-    }
-
-    private static <P, T> List<P> visitProjects(Visitor<P, T> visitor, ProjectAndTaskFilter filter, List<ProjectView> projects, P parentProjectObject) {
-        List<P> projectObjects = new ArrayList<P>();
-
-        Iterator<ProjectView> iterator = projects.iterator();
-        int index = 0;
-        while (iterator.hasNext()) {
-            ProjectView project = iterator.next();
-
-            if (filter.doesAllowProject(project)) {
-                P userProjectObject = visitor.visitProject(project, index, parentProjectObject);
-                projectObjects.add(userProjectObject);
-
-                //visit sub projects
-                List<P> subProjectObjects = visitProjects(visitor, filter, project.getSubProjects(), userProjectObject);
-
-                //visit tasks. Notice that we pass in the number of subprojects as a starting index. This is so they'll come afterwards.
-                List<T> taskObjects = visitTasks(visitor, filter, project, subProjectObjects.size(), userProjectObject);
-
-                visitor.completedVisitingProject(userProjectObject, subProjectObjects, taskObjects);
-            }
-            index++;
-        }
-
-        return projectObjects;
-    }
-
-    /*
-       Add the list of tasks to the parent tree node.
-
-       @author mhunsicker
-    */
-
-    private static <P, T> List<T> visitTasks(Visitor<P, T> visitor, ProjectAndTaskFilter filter, ProjectView project, int startingIndex, P userProjectObject) {
-        List<T> taskObjects = new ArrayList<T>();
-        Iterator<TaskView> iterator = project.getTasks().iterator();
-        int index = startingIndex;
-        while (iterator.hasNext()) {
-            TaskView task = iterator.next();
-
-            if (filter.doesAllowTask(task)) {
-                T taskObject = visitor.visitTask(task, index, project, userProjectObject);
-                taskObjects.add(taskObject);
-            }
-            index++;
-        }
-
-        return taskObjects;
-    }
-}
+/*
+ * 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.foundation.visitors;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
+import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
+import org.gradle.util.GUtil;
+
+import java.util.*;
+
+/**
+ * This visits each project and task in a hierarchical manner. This visitor is specifically meant to walk the projects first and tasks second for the purpose of populating a tree where the
+ * projects/subprojects are first and the tasks are second.
+ *
+ * @author mhunsicker
+ */
+public class TaskTreePopulationVisitor {
+    public interface Visitor<P, T> {
+        /*
+           This is called for each project.
+         @param  project    the project
+         @param indexOfProject
+         @param  parentProjectObject whatever you handed back from a prior call to
+                 visitProject if this is a sub project. Otherwise, it'll be whatever
+                 was passed into the visitPojectsAndTasks function.
+           @return an object that will be handed back to you for each of this
+                   project's tasks.
+           @author mhunsicker
+        */
+
+        public P visitProject(ProjectView project, int indexOfProject, P parentProjectObject);
+
+        /*
+           This is called for each task.
+
+         @param  task               the task
+         @param indexOfTask
+         @param  tasksProject       the project for this task
+         @param  userProjectObject  whatever you returned from the parent project's visitProject
+           @author mhunsicker
+        */
+
+        public T visitTask(TaskView task, int indexOfTask, ProjectView tasksProject, P userProjectObject);
+
+        /*
+           This is called when a project has been visited completely and is just a
+           notification giving you an opportunity to do whatever you like.
+           This is possibly where you want to delete any nodes that we didn't
+           visit.
+
+           @param  parentProjectObject the object that represents the parent of
+                                       the project and task objects below
+           @param  projectObjects      a list of whatever you returned from visitProject
+           @param  taskObjects         a list of whatever you returned from visitTask
+           @author mhunsicker
+        */
+
+        public void completedVisitingProject(P parentProjectObject, List<P> projectObjects, List<T> taskObjects);
+    }
+
+    /*
+       This visitor will visit each project, sub-project and task that was discovered
+       by the GradleHelper. This is useful for building a list or tree of
+       projects and tasks.
+
+       This is the same as the other version of visitProjectsAndTasks except this
+       one visits everything.
+
+       @author mhunsicker
+    */
+
+    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
+                                                   P rootProjectObject) {
+        visitProjectAndTasks(projects, visitor, new AllowAllProjectAndTaskFilter(), rootProjectObject);
+    }
+
+    /*
+       This visitor will visit each project, sub-project and task that was discovered
+       by the GradleHelper. This is useful for building a list or tree of
+       projects and tasks.
+
+       @param  visitor    this notified you of each project and task.
+       @param  filter     allows you to skip projects and tasks as specified by the filter.
+       @param  rootProjectObject whatever you pass here will be passed to the
+                root-level projects as parentProjectObject.
+       @author mhunsicker
+    */
+
+    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
+                                                   ProjectAndTaskFilter filter, P rootProjectObject) {
+        List<P> userProjectObjects = visitProjects(visitor, filter, projects, rootProjectObject, new AlphabeticalProjectNameComparator(), new AlphabeticalTaskNameComparator());
+
+        //notify the visitation of the root projects. There are no tasks for this one, but there are projects.
+        visitor.completedVisitingProject(rootProjectObject, userProjectObjects, Collections.<T>emptyList());
+    }
+
+    private static <P, T> List<P> visitProjects(Visitor<P, T> visitor, ProjectAndTaskFilter filter,
+                                                List<ProjectView> sourceProjects, P parentProjectObject, Comparator<ProjectView> projectSorter, Comparator<TaskView> taskSorter) {
+        List<P> projectObjects = new ArrayList<P>();
+
+        sourceProjects = new ArrayList<ProjectView>(sourceProjects);  //make a copy because we're going to sort them.
+        Collections.sort(sourceProjects, projectSorter);
+
+        Iterator<ProjectView> iterator = sourceProjects.iterator();
+        int index = 0;
+        while (iterator.hasNext()) {
+            ProjectView project = iterator.next();
+
+            if (filter.doesAllowProject(project)) {
+                P userProjectObject = visitor.visitProject(project, index, parentProjectObject);
+                projectObjects.add(userProjectObject);
+
+                //visit sub projects
+                List<P> subProjectObjects = visitProjects(visitor, filter, project.getSubProjects(), userProjectObject, projectSorter, taskSorter);
+
+                //visit tasks. Notice that we pass in the number of subprojects as a starting index. This is so they'll come afterwards.
+                List<T> taskObjects = visitTasks(visitor, filter, project, subProjectObjects.size(), userProjectObject, taskSorter);
+
+                visitor.completedVisitingProject(userProjectObject, subProjectObjects, taskObjects);
+            }
+            index++;
+        }
+
+        return projectObjects;
+    }
+
+    /*
+       Add the list of tasks to the parent tree node.
+
+       @author mhunsicker
+    */
+
+    private static <P, T> List<T> visitTasks(Visitor<P, T> visitor, ProjectAndTaskFilter filter, ProjectView project,
+                                             int startingIndex, P userProjectObject, Comparator<TaskView> taskSorter) {
+        List<T> taskObjects = new ArrayList<T>();
+        List<TaskView> tasks = new ArrayList<TaskView>(project.getTasks()); //make a copy because we're going to sort them
+        Collections.sort(tasks, taskSorter);
+
+        Iterator<TaskView> iterator = tasks.iterator();
+        int index = startingIndex;
+        while (iterator.hasNext()) {
+            TaskView task = iterator.next();
+            if (filter.doesAllowTask(task)) {
+                T taskObject = visitor.visitTask(task, index, project, userProjectObject);
+                taskObjects.add(taskObject);
+            }
+            index++;
+        }
+
+        return taskObjects;
+    }
+
+    /**
+     * This comparator sorts project names alphabetically ignoring case.
+     */
+    public static class AlphabeticalProjectNameComparator implements Comparator<ProjectView> {
+        public int compare(ProjectView o1, ProjectView o2) {
+            return GUtil.caseInsensitive().compare(o1.getName(), o2.getName());
+        }
+    }
+
+    /**
+     * This comparator sorts task names alphabetically ignoring case.
+     */
+    public static class AlphabeticalTaskNameComparator implements Comparator<TaskView> {
+        public int compare(TaskView o1, TaskView o2) {
+            return GUtil.caseInsensitive().compare(o1.getName(), o2.getName());
+        }
+    }
+}
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
index 1e9ff86..1a72eb6 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
@@ -63,7 +63,7 @@ public class DOM4JSerializer {
          * @param file the file in question
          * @return true to overwrite it, false not to.
          */
-        boolean confirmOverwritingExisingFile(File file);
+        boolean confirmOverwritingExistingFile(File file);
     }
 
     /**
@@ -155,7 +155,7 @@ public class DOM4JSerializer {
                 file = ensureFileHasCorrectExtensionAndCase(file, fileFilter.getExtension());
 
                 if (file.exists()) {
-                    promptAgain = !exportInteraction.confirmOverwritingExisingFile(file);
+                    promptAgain = !exportInteraction.confirmOverwritingExistingFile(file);
                 }
             }
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
index 43ebddd..565fa28 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
@@ -16,9 +16,8 @@
 package org.gradle.gradleplugin.foundation;
 
 import org.codehaus.groovy.runtime.StackTraceUtils;
-import org.gradle.StartParameter;
-import org.gradle.api.LocationAwareException;
-import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.api.internal.LocationAwareException;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -34,6 +33,7 @@ import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
 import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
 import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
 import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.logging.ShowStacktrace;
 import org.gradle.util.GUtil;
 
 import java.io.File;
@@ -63,7 +63,7 @@ public class GradlePluginLord {
 
     private boolean isStarted;  //this flag is mostly to prevent initialization from firing off repeated refresh requests.
 
-    private StartParameter.ShowStacktrace stackTraceLevel = StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+    private ShowStacktrace stackTraceLevel = ShowStacktrace.INTERNAL_EXCEPTIONS;
     private LogLevel logLevel = LogLevel.LIFECYCLE;
 
     private ObserverLord<GeneralPluginObserver> generalObserverLord = new ObserverLord<GeneralPluginObserver>();
@@ -149,7 +149,7 @@ public class GradlePluginLord {
         if (gradleHomeProperty != null) {
             gradleHomeDirectory = new File(gradleHomeProperty);
         } else {
-            gradleHomeDirectory = new DefaultClassPathProvider().getGradleHome();
+            gradleHomeDirectory = new DefaultModuleRegistry().getGradleHome();
         }
     }
 
@@ -214,7 +214,7 @@ public class GradlePluginLord {
     /**
      * this allows you to change how much information is given when an error occurs.
      */
-    public void setStackTraceLevel(StartParameter.ShowStacktrace stackTraceLevel) {
+    public void setStackTraceLevel(ShowStacktrace stackTraceLevel) {
         if (areEqual(this.stackTraceLevel, stackTraceLevel))    //already set to this. This prevents recursive notifications.
         {
             return;
@@ -223,7 +223,7 @@ public class GradlePluginLord {
         notifySettingsChanged();
     }
 
-    public StartParameter.ShowStacktrace getStackTraceLevel() {
+    public ShowStacktrace getStackTraceLevel() {
         return stackTraceLevel;
     }
 
@@ -585,7 +585,7 @@ public class GradlePluginLord {
      * This code was copied from BuildExceptionReporter.reportBuildFailure in gradle's source, then modified slightly to compensate for the fact that we're not driven by options or logging things to a
      * logger object.
      */
-    public static String getGradleExceptionMessage(Throwable failure, StartParameter.ShowStacktrace stackTraceLevel) {
+    public static String getGradleExceptionMessage(Throwable failure, ShowStacktrace stackTraceLevel) {
         if (failure == null) {
             return "";
         }
@@ -594,7 +594,7 @@ public class GradlePluginLord {
 
         formatter.format("%nBuild failed.%n");
 
-        if (stackTraceLevel == StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
+        if (stackTraceLevel == ShowStacktrace.INTERNAL_EXCEPTIONS) {
             formatter.format("Use the stack trace options to get more details.");
         }
 
@@ -613,9 +613,9 @@ public class GradlePluginLord {
                 formatter.format("%s", getMessage(failure));
             }
 
-            if (stackTraceLevel != StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
+            if (stackTraceLevel != ShowStacktrace.INTERNAL_EXCEPTIONS) {
                 formatter.format("%n%nException is:\n");
-                if (stackTraceLevel == StartParameter.ShowStacktrace.ALWAYS_FULL) {
+                if (stackTraceLevel == ShowStacktrace.ALWAYS_FULL) {
                     return formatter.toString() + getStackTraceAsText(failure);
                 }
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
index 5cea542..d4b2f21 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
@@ -254,6 +254,24 @@ public class FavoritesEditor implements SettingsSerializable {
             this.displayName = displayName;
             this.alwaysShowOutput = alwaysShowOutput;
         }
+
+        /**
+         * Determines if the display name and full command are equal. This is useful when editing a favorite to know if they should be kept synchronized.
+         *
+         * @return true if they are, false if not.
+         */
+        public boolean isDisplayNameAndFullCommandSynchronized() {
+            if (displayName == null) {
+                return fullCommandLine == null;
+            }
+
+            return displayName.equals(fullCommandLine);
+        }
+
+        @Override
+        public String toString() {
+            return displayName + " " + fullCommandLine;
+        }
     }
 
     public interface EditFavoriteInteraction extends ValidationInteraction {
@@ -428,7 +446,7 @@ public class FavoritesEditor implements SettingsSerializable {
      *
      * @param tasksToCopy the tasks to copy
      */
-    public void duplicateFavorites(List<FavoriteTask> tasksToCopy) {
+    public void duplicateFavorites(List<FavoriteTask> tasksToCopy, EditFavoriteInteraction editFavoriteInteraction) {
         if (tasksToCopy == null || tasksToCopy.isEmpty()) {
             return;
         }
@@ -437,6 +455,10 @@ public class FavoritesEditor implements SettingsSerializable {
         while (iterator.hasNext()) {
             FavoriteTask taskToCopy = iterator.next();
             FavoriteTask newFavoriteTask = new FavoriteTask(taskToCopy.getFullCommandLine(), taskToCopy.getDisplayName(), taskToCopy.alwaysShowOutput());
+
+            if (!editInternal(newFavoriteTask, editFavoriteInteraction)) {
+                return;
+            }
             favoriteTasks.add(newFavoriteTask);
         }
 
@@ -448,13 +470,16 @@ public class FavoritesEditor implements SettingsSerializable {
      *
      * @param taskToCopy the task to copy
      */
-    public FavoriteTask duplicateFavorite(FavoriteTask taskToCopy) {
+    public FavoriteTask duplicateFavorite(FavoriteTask taskToCopy, EditFavoriteInteraction editFavoriteInteraction) {
 
         if (taskToCopy == null) {
             return null;
         }
 
         FavoriteTask newFavoriteTask = new FavoriteTask(taskToCopy.getFullCommandLine(), taskToCopy.getDisplayName(), taskToCopy.alwaysShowOutput());
+        if (!editInternal(newFavoriteTask, editFavoriteInteraction)) {
+            return null;
+        }
         favoriteTasks.add(newFavoriteTask);
         notifyFavoritesChanged();
         return newFavoriteTask;
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
index 63fc015..3c19970 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
@@ -15,12 +15,12 @@
  */
 package org.gradle.gradleplugin.foundation.request;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
 import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
 import org.gradle.foundation.queue.ExecutionQueue;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 
@@ -50,7 +50,7 @@ public class ExecutionRequest extends AbstractRequest {
      * @param customGradleExecutor the path to a custom gradle executable. May be null.
      * @return a protocol that our server will use to communicate with the launched gradle process.
      */
-    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory,
+    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory,
                                                                File customGradleExecutor) {
         executionInteraction.reportExecutionStarted();  //go ahead and fire off that the execution has started. It has from the user's standpoint.
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
index 89945ad..08bb430 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.gradleplugin.foundation.request;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.foundation.ProjectView;
 import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
@@ -23,6 +22,7 @@ import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
 import org.gradle.foundation.ipc.gradle.TaskListServerProtocol;
 import org.gradle.foundation.queue.ExecutionQueue;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 import java.util.List;
@@ -55,7 +55,7 @@ public class RefreshTaskListRequest extends AbstractRequest {
      * @param customGradleExecutor the path to a custom gradle executable. May be null.
      * @return a protocol that our server will use to communicate with the launched gradle process.
      */
-    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory,
+    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory,
                                                                File customGradleExecutor) {
         executionInteraction.reportExecutionStarted();  //go ahead and fire off that the execution has started. It has from the user's standpoint.
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
index 9cfdf82..ea7c4ba 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
@@ -15,12 +15,12 @@
  */
 package org.gradle.gradleplugin.foundation.request;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
 import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
 import org.gradle.foundation.queue.ExecutionQueue;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 
@@ -66,7 +66,7 @@ public interface Request extends ExecutionQueue.Request {
      * @param customGradleExecutor the path to a custom gradle executable. May be null.
      * @return a protocol that our server will use to communicate with the launched gradle process.
      */
-    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory,
+    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory,
                                                                File customGradleExecutor);
 
     public void executeAgain(GradlePluginLord gradlePluginLord);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
index 89baef7..c448d1d 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
@@ -15,10 +15,10 @@
  */
 package org.gradle.gradleplugin.foundation.runner;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
 import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.logging.ShowStacktrace;
 
 import java.io.File;
 
@@ -39,7 +39,7 @@ public class GradleRunner {
         this.customGradleExecutor = customGradleExecutor;
     }
 
-    public synchronized void executeCommand(String commandLine, LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel,
+    public synchronized void executeCommand(String commandLine, LogLevel logLevel, ShowStacktrace stackTraceLevel,
                                             ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction) {
         //the protocol manages the command line and messaging observers
         ExecuteGradleCommandServerProtocol serverProtocol = new ExecuteGradleCommandServerProtocol(currentDirectory, gradleHomeDirectory, customGradleExecutor, commandLine, logLevel, stackTraceLevel,
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java
new file mode 100644
index 0000000..5fea746
--- /dev/null
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/BasicTextSearchCriteria.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2011 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.gradleplugin.foundation.search;
+
+import java.util.regex.Pattern;
+
+/**
+ * This is basic search criteria for searching text. This could be extended for more advanced searches.
+ *
+ * @author mhunsicker
+ */
+public class BasicTextSearchCriteria {
+    private String textToMatch = "";
+    private boolean isCaseSensitive;
+    private boolean useRegularExpressions;
+    private int startingFrom = -1;
+
+    private boolean hasChanged;
+    private Pattern regularExpressionPattern; //we'll hold onto this as a tiny optimization and only create it when we search or things change
+
+    public BasicTextSearchCriteria() {
+    }
+
+    public String getTextToMatch() {
+        return textToMatch;
+    }
+
+    public void setTextToMatch(String textToMatch) {
+        if (this.textToMatch.equals(textToMatch)) {
+            return;
+        }
+
+        this.hasChanged = true;
+        this.textToMatch = textToMatch;
+    }
+
+    public boolean isCaseSensitive() {
+        return isCaseSensitive;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        if (isCaseSensitive == caseSensitive) {
+            return;
+        }
+
+        this.hasChanged = true;
+        isCaseSensitive = caseSensitive;
+    }
+
+    public boolean useRegularExpressions() {
+        return useRegularExpressions;
+    }
+
+    public void setUseRegularExpressions(boolean useRegularExpressions) {
+        if (this.useRegularExpressions == useRegularExpressions) {
+            return;
+        }
+
+        this.hasChanged = true;
+        this.useRegularExpressions = useRegularExpressions;
+    }
+
+    public Pattern getRegularExpressionPattern() {
+        if (textToMatch == null || "".equals(textToMatch)) {
+            return null;
+        }
+
+        String actualTextToMatch = textToMatch;
+        if (!useRegularExpressions) {
+            actualTextToMatch = Pattern.quote(textToMatch);
+        }
+
+        if (this.hasChanged || regularExpressionPattern == null) {
+            if (isCaseSensitive) {
+                regularExpressionPattern = Pattern.compile(actualTextToMatch);
+            } else {
+                regularExpressionPattern = Pattern.compile("(?i)" + actualTextToMatch);  //this makes it case insensitive
+            }
+        }
+
+        return regularExpressionPattern;
+    }
+
+    public int getStartingFrom() {
+        return startingFrom;
+    }
+
+    public void setStartingFrom(int startingFrom) {
+        if (this.startingFrom == startingFrom) {
+            return;
+        }
+
+        this.hasChanged = true;
+        this.startingFrom = startingFrom;
+    }
+
+    public boolean hasChanged() {
+        return hasChanged;
+    }
+
+    /*package*/ void resetHasChanged() {
+        this.hasChanged = false;
+    }   //this should only be called by the search editor.
+
+    @Override
+    public String toString() {
+        return '\'' + textToMatch + "' Case sensitive: " + isCaseSensitive + " Regular Expressions: " + useRegularExpressions;
+    }
+}
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java
new file mode 100644
index 0000000..c081b18
--- /dev/null
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/search/TextBlockSearchEditor.java
@@ -0,0 +1,192 @@
+/*
+ * 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.gradleplugin.foundation.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This searches within a block of text. The bulk of the work is handled by RegEx. This builds a list of results.
+ *
+ * @author mhunsicker
+ */
+public class TextBlockSearchEditor {
+    private List<SearchResult> matchedResults = new ArrayList<SearchResult>();
+
+    public int searchAllText(String textToSearch, BasicTextSearchCriteria searchCriteria) {
+        matchedResults.clear();
+
+        if (textToSearch == null) {
+            return 0;
+        }
+
+        Pattern pattern = searchCriteria.getRegularExpressionPattern();
+        if (pattern == null)   //this happens if the user clears the 'search for' text. We have no pattern.
+        {
+            return 0;
+        }
+
+        Matcher matcher = pattern.matcher(textToSearch);
+
+        searchCriteria.resetHasChanged();
+
+        boolean wasMatchFound = false;
+        int matcherStart = 0;
+
+        do {
+            wasMatchFound = matcher.find(matcherStart);   //this will reset our search so we'll start at our new location
+            if (wasMatchFound) {
+                // Retrieve matching string
+                String matchedText = matcher.group();
+
+                // Retrieve indices of matching string
+                int start = matcher.start();
+                int end = matcher.end();
+
+                matchedResults.add(new SearchResult(matchedText, start, end));
+
+                matcherStart = end + 1;
+            }
+        }
+        while (wasMatchFound);
+
+        return matchedResults.size();
+    }
+
+    public List<SearchResult> getMatchedResults() {
+        return Collections.unmodifiableList(matchedResults);
+    }
+
+    public boolean hasMatches() {
+        return !matchedResults.isEmpty();
+    }
+
+    //
+
+    /**
+     * Information about a search's results.
+     *
+     * @author mhunsicker
+     */
+    public static class SearchResult {
+        private String matchedText;
+        private int beginningIndexOfMatch;
+        private int endingIndexOfMatch;
+
+        public SearchResult(String matchedText, int beginningIndexOfMatch, int endingIndexOfMatch) {
+            this.beginningIndexOfMatch = beginningIndexOfMatch;
+            this.endingIndexOfMatch = endingIndexOfMatch;
+            this.matchedText = matchedText;
+        }
+
+        public String getMatchedText() {
+            return matchedText;
+        }
+
+        public int getBeginningIndexOfMatch() {
+            return beginningIndexOfMatch;
+        }
+
+        public int getEndingIndexOfMatch() {
+            return endingIndexOfMatch;
+        }
+
+        public boolean foundAMatch() {
+            return beginningIndexOfMatch != -1;
+        }
+
+        public String toString() {
+            if (!foundAMatch()) {
+                return "No match found";
+            }
+            return "Matched '" + matchedText + "' at " + beginningIndexOfMatch + " - " + endingIndexOfMatch;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            SearchResult that = (SearchResult) o;
+
+            if (beginningIndexOfMatch != that.beginningIndexOfMatch) {
+                return false;
+            }
+            if (endingIndexOfMatch != that.endingIndexOfMatch) {
+                return false;
+            }
+            if (matchedText != null ? !matchedText.equals(that.matchedText) : that.matchedText != null) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = matchedText != null ? matchedText.hashCode() : 0;
+            result = 31 * result + beginningIndexOfMatch;
+            result = 31 * result + endingIndexOfMatch;
+            return result;
+        }
+    }
+
+    /**
+     * Returns the SearchResult after the specified location. Useful for doing a 'show next match'. This will cycle around and get the first one if you're after the last result.
+     */
+    public SearchResult getNextSearchResult(int fromLocation) {
+        if (matchedResults.isEmpty()) {
+            return null;
+        }
+
+        Iterator<SearchResult> iterator = matchedResults.iterator();
+        while (iterator.hasNext()) {
+            SearchResult searchResult = iterator.next();
+            if (searchResult.getBeginningIndexOfMatch() > fromLocation) {
+                return searchResult;
+            }
+        }
+
+        return matchedResults.get(0);
+    }
+
+    /**
+     * Returns the SearchResult after the specified location. Useful for doing a 'show previous match'. This will cycle around and get the last one if you're before the first result.
+     */
+    public SearchResult getPreviousSearchResult(int fromLocation) {
+        if (matchedResults.isEmpty()) {
+            return null;
+        }
+
+        //walk them in reverse order
+        for (int index = matchedResults.size() - 1; index >= 0; index--) {
+            SearchResult searchResult = matchedResults.get(index);
+            if (searchResult.getEndingIndexOfMatch() < fromLocation) {
+                return searchResult;
+            }
+        }
+
+        return matchedResults.get(matchedResults.size() - 1);
+    }
+}
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java
new file mode 100644
index 0000000..02f92b3
--- /dev/null
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/SearchPanel.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright 2011 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.gradleplugin.userinterface.swing.common;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.gradleplugin.foundation.search.BasicTextSearchCriteria;
+import org.gradle.gradleplugin.foundation.search.TextBlockSearchEditor;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+
+import javax.swing.AbstractAction;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A simple search panel (opposed to a dialog box). It is meant to be added to the bottom or top of a panel to search and hidden. It is then displayed when needed. However, you could keep it visible
+ * if you choose.
+ *
+ * This performs a threaded search. Its not that the search will take a particularly long time (although it certainly could). Its that we're doing work and we can't do that in the Swing EDT. We'll be
+ * searching every time a user types a letter, As such, we queue up our requests and then perform a search on the latest request.
+ *
+ * @author mhunsicker
+ */
+public class SearchPanel {
+    private final Logger logger = Logging.getLogger(SearchPanel.class);
+
+    private JPanel mainPanel;
+
+    private JTextField textToMatchField;
+    private JCheckBox isCaseSensitiveCheckBox;
+    private JCheckBox useRegularExpressionsCheckBox;
+    private JButton findNextButton;
+    private JButton findPreviousButton;
+
+    private SearchInteraction searchInteraction;
+
+    private TextBlockSearchEditor editor = new TextBlockSearchEditor();
+
+    private Color notFoundColor = Color.red.brighter();
+
+    private volatile LinkedBlockingQueue<SearchRequest> searchRequests = new LinkedBlockingQueue<SearchRequest>();
+    private ExecutorService executorService;
+
+    //
+    public interface SearchInteraction {
+        /**
+         * @return the block of text that we're going to search
+         */
+        public String getTextToSearch();
+
+        /**
+         * @return the current location of hte caret within the text we're going to search.
+         */
+        public int getCaretLocation();
+
+        /**
+         * Highlight and then ensure this result is visible.
+         *
+         * @param editor the editor that was used to search
+         * @param searchResult the specific result (within the editor's search results) to highlight.
+         */
+        public void highlightAndScrollToResult(TextBlockSearchEditor editor, TextBlockSearchEditor.SearchResult searchResult);
+
+        /**
+         * Notification that the search was complete and we have results to show to the user.
+         */
+        public void searchComplete(TextBlockSearchEditor editor);
+
+        /**
+         * Notification to hide your results. This panel is going away or the user cleared the results.
+         */
+        public void removeResultHighlights();
+    }
+
+    public SearchPanel(SearchInteraction searchInteraction) {
+        this.searchInteraction = searchInteraction;
+
+        executorService = Executors.newSingleThreadExecutor();
+        executorService.submit(new SearchTask());
+
+        setupUI();
+        hide();
+    }
+
+    public JComponent getComponent() {
+        return mainPanel;
+    }
+
+    private void setupUI() {
+        mainPanel = new JPanel();
+        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
+
+        isCaseSensitiveCheckBox = new JCheckBox("Case Sensitive");
+        isCaseSensitiveCheckBox.setMnemonic('c');
+
+        isCaseSensitiveCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                requestSearch();
+            }
+        });
+
+        useRegularExpressionsCheckBox = new JCheckBox("Regular Expression");
+        useRegularExpressionsCheckBox.setMnemonic('r');
+
+        useRegularExpressionsCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                requestSearch();
+            }
+        });
+
+        findNextButton = Utility.createButton(getClass(), "/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-down.png", "Find Next Match", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                goToNextMatch();
+            }
+        });
+        findPreviousButton = Utility.createButton(getClass(), "/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-up.png", "Find Previous Match", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                goToPreviousMatch();
+            }
+        });
+
+        JButton closeButton = Utility.createButton(getClass(), "/org/gradle/gradleplugin/userinterface/swing/generic/close.png", "Close Search Panel", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                hide();
+            }
+        });
+
+        mainPanel.add(createTextToMatchField());
+        mainPanel.add(Box.createHorizontalStrut(5));
+        mainPanel.add(findPreviousButton);
+        mainPanel.add(Box.createHorizontalStrut(5));
+        mainPanel.add(findNextButton);
+        mainPanel.add(Box.createHorizontalStrut(5));
+        mainPanel.add(isCaseSensitiveCheckBox);
+        mainPanel.add(Box.createHorizontalStrut(5));
+        mainPanel.add(useRegularExpressionsCheckBox);
+        addAdditionalFields(mainPanel);
+        mainPanel.add(Box.createHorizontalGlue());
+        mainPanel.add(closeButton);
+    }
+
+    private Component createTextToMatchField() {
+        textToMatchField = new JTextField();
+        textToMatchField.setMinimumSize(new Dimension(50, 10));
+
+        //escape closes this dialog
+        textToMatchField.registerKeyboardAction(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                hide();
+            }
+        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        //hook up the key strokes that perform the search
+        ActionListener performSearchNextAction = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                goToNextMatch();
+            }
+        };
+
+        //hook up the key strokes that perform the search
+        ActionListener performSearchPreviousAction = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                goToPreviousMatch();
+            }
+        };
+
+        textToMatchField.registerKeyboardAction(performSearchNextAction, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        //F3 and Shift F3
+        textToMatchField.registerKeyboardAction(performSearchNextAction, KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+        textToMatchField.registerKeyboardAction(performSearchPreviousAction, KeyStroke.getKeyStroke(KeyEvent.VK_F3, KeyEvent.SHIFT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        //as the user types, perform a 'continue' search.
+        textToMatchField.getDocument().addDocumentListener(new DocumentListener() {
+            public void insertUpdate(DocumentEvent e) {
+                requestSearch();
+            }
+
+            public void removeUpdate(DocumentEvent e) {
+                requestSearch();
+            }
+
+            public void changedUpdate(DocumentEvent e) {
+                requestSearch();
+            }
+        });
+
+        return textToMatchField;
+    }
+
+    /**
+     * This adds a request to search on the queue.
+     */
+    private void requestSearch() {
+        String textToMatch = textToMatchField.getText();
+        boolean isCaseSensitive = isCaseSensitiveCheckBox.isSelected();
+        boolean useRegularExpressions = useRegularExpressionsCheckBox.isSelected();
+        String textToSearch = searchInteraction.getTextToSearch();
+
+        searchRequests.offer(new SearchRequest(textToMatch, isCaseSensitive, useRegularExpressions, textToSearch));
+    }
+
+
+    /**
+     * This waits until the next request is available.
+     *
+     * @return the last request.
+     */
+    private SearchRequest getNextAvailableRequest() {
+        try {
+            SearchRequest searchRequest = searchRequests.take();  //this will block until at least one request is on our queue.
+
+            if (searchRequests.size() > 1)  //if we've got multiple requests, go get the latest.
+            {
+                List<SearchRequest> tasks = new ArrayList<SearchRequest>();
+                searchRequests.drainTo(tasks);
+                if (!tasks.isEmpty()) {
+                    searchRequest = tasks.get(tasks.size() - 1); //we'll just use the latest
+                }
+            }
+
+            return searchRequest;
+        } catch (Exception e) {
+            logger.error("Getting next available request", e);
+            return null;
+        }
+    }
+
+    private class SearchRequest {
+        private String textToMatch;
+        private boolean isCaseSensitive;
+        private boolean useRegularExpressions;
+        private String textToSearch;
+
+        private SearchRequest(String textToMatch, boolean caseSensitive, boolean useRegularExpressions, String textToSearch) {
+            this.textToMatch = textToMatch;
+            isCaseSensitive = caseSensitive;
+            this.useRegularExpressions = useRegularExpressions;
+            this.textToSearch = textToSearch;
+        }
+
+        @Override
+        public String toString() {
+            return "textToMatch='" + textToMatch + '\''
+                    + ", isCaseSensitive=" + isCaseSensitive
+                    + ", useRegularExpressions=" + useRegularExpressions;
+            //testToSearch is probably too long to show here
+        }
+    }
+
+    private class SearchTask implements Runnable {
+        private BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+
+        /**
+         * When an object implementing interface <code>Runnable</code> is used to create a thread, starting the thread causes the object's <code>run</code> method to be called in that separately
+         * executing thread. <p> The general contract of the method <code>run</code> is that it may take any action whatsoever.
+         *
+         * @see Thread#run()
+         */
+        public void run() {
+            while (true) {
+                SearchRequest request = getNextAvailableRequest();
+                if (request != null) {
+                    criteria.setTextToMatch(request.textToMatch);
+                    criteria.setCaseSensitive(request.isCaseSensitive);
+                    criteria.setUseRegularExpressions(request.useRegularExpressions);
+
+                    editor.searchAllText(request.textToSearch, criteria);
+
+                    SwingUtilities.invokeLater(new Runnable() {
+                        public void run() {
+                            searchInteraction.searchComplete(editor);
+                            enableButtonsAndFieldsAppropriately(editor.hasMatches());
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    /**
+     * Call this to perform the last search again. This is useful if the search text has been changed behind our backs. This has no affect if we're not currently shown.
+     */
+    public void performSearchAgain() {
+        if (mainPanel.isVisible()) {
+            requestSearch();
+        }
+    }
+
+    private void goToNextMatch() {
+        TextBlockSearchEditor.SearchResult searchResult = editor.getNextSearchResult(searchInteraction.getCaretLocation());
+        if (searchResult != null) {
+            searchInteraction.highlightAndScrollToResult(editor, searchResult);
+        }
+    }
+
+    private void goToPreviousMatch() {
+        TextBlockSearchEditor.SearchResult searchResult = editor.getPreviousSearchResult(searchInteraction.getCaretLocation());
+        if (searchResult != null) {
+            searchInteraction.highlightAndScrollToResult(editor, searchResult);
+        }
+    }
+
+    /**
+     * You can override this to add additional fields to the given panel.
+     *
+     * <!      Name       Description>
+     *
+     * @param panel where to add your additional fields.
+     * @author mhunsicker
+     */
+    protected void addAdditionalFields(JPanel panel) {
+
+    }
+
+    /**
+     * Call this to hide this panel.
+     *
+     * @author mhunsicker
+     */
+    public void hide() {
+        if (this.searchInteraction != null) {
+            this.searchInteraction.removeResultHighlights();
+        }
+
+        mainPanel.setVisible(false);
+    }
+
+    /**
+     * Call this to show this panel so that a search can begin. <!      Name              Description>
+     *
+     * @author mhunsicker
+     */
+    public void show() {
+        mainPanel.setVisible(true);
+        showNormalColor();
+
+        textToMatchField.selectAll();
+
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                textToMatchField.requestFocus();
+            }
+        });
+
+        requestSearch();  //go ahead an perform a search based on what is currently present
+    }
+
+
+    private void showNormalColor() {
+        textToMatchField.setForeground(UIManager.getColor("TextArea.foreground"));
+    }
+
+    private void showNoMatchColor() {
+        textToMatchField.setForeground(notFoundColor);
+    }
+
+    public void enableButtonsAndFieldsAppropriately(boolean foundAMatch) {
+        if (foundAMatch) {
+            showNormalColor();
+        } else {
+            showNoMatchColor();
+        }
+
+        findNextButton.setEnabled(foundAMatch);
+        findPreviousButton.setEnabled(foundAMatch);
+    }
+}
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java
new file mode 100644
index 0000000..382cc09
--- /dev/null
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/TextPaneSearchInteraction.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2011 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.gradleplugin.userinterface.swing.common;
+
+import org.gradle.gradleplugin.foundation.search.TextBlockSearchEditor;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+
+import javax.swing.JTextPane;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.DefaultStyledDocument;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A search interaction that searches a JTextPane and highlights matches.
+ *
+ * Note: there's something kind of goofy here. This draws and 'undraws' highlights on text (using AttributeSets). If you use this and you draw your own highlights in the JTextPane, you need to
+ * override removeResultHighlights() and reset your AttributeSets. Otherwise, this assumes there is a default style that all non-highlighted text uses.
+ *
+ * @author mhunsicker
+ */
+public class TextPaneSearchInteraction implements SearchPanel.SearchInteraction {
+    private JTextPane textComponentToSearch;
+    private AttributeSet defaultStyle;              //the style of the non-highlighted text. When we remove our highlight, this is what we'll use.
+    private AttributeSet highlightStyle;            //the style of highlighted text.
+    private AttributeSet emphasizedHighlightStyle;  //an style of emphasized highlighted text. This is used to show the 'current' highlight when multiple matches exist.
+
+    private List<TextBlockSearchEditor.SearchResult> currentHighlights = new ArrayList<TextBlockSearchEditor.SearchResult>();
+
+    public TextPaneSearchInteraction(JTextPane textComponentToSearch, AttributeSet defaultStyle, AttributeSet highlightStyle, AttributeSet emphasizedHighlightStyle) {
+        this.textComponentToSearch = textComponentToSearch;
+        this.defaultStyle = defaultStyle;
+        this.highlightStyle = highlightStyle;
+        this.emphasizedHighlightStyle = emphasizedHighlightStyle;
+    }
+
+    /**
+     * Notification that the search was complete and we have results to show to the user.
+     */
+    public void searchComplete(TextBlockSearchEditor editor) {
+        removeResultHighlights();  //hide any previous results that may have been highlighted.
+
+        if (editor.hasMatches()) {
+            currentHighlights.addAll(editor.getMatchedResults());
+            highlightResults(editor, true);
+        }
+    }
+
+    /**
+     * this sets the style for the current results.
+     *
+     * @param editor where we get the existing highlights.
+     * @param highlightFirst true to emphasize and scroll to the first highlight
+     */
+    private void highlightResults(TextBlockSearchEditor editor, boolean highlightFirst) {
+        currentHighlights.clear();   //since this can be called after a search has completed, clear the previous results (we're going to add it back below).
+
+        boolean isFirst = highlightFirst;
+        Iterator<TextBlockSearchEditor.SearchResult> iterator = editor.getMatchedResults().iterator();
+        while (iterator.hasNext()) {
+            TextBlockSearchEditor.SearchResult searchResult = iterator.next();
+            highlightText(searchResult.getBeginningIndexOfMatch(), searchResult.getEndingIndexOfMatch(), isFirst, isFirst);
+
+            isFirst = false;  //we'll only scroll to the first match
+
+            currentHighlights.add(searchResult);
+        }
+    }
+
+    /**
+     * Call this to remove the highlights of the search results. Override this if you draw your own highlights and you'll probably just want to reset the AttributeSets to the entire text (and not call
+     * this (super)).
+     */
+    public void removeResultHighlights() {
+        removeExistingHighlights();
+    }
+
+    //this walks our current highlights and sets the text to its default style.
+    private void removeExistingHighlights() {
+        Iterator<TextBlockSearchEditor.SearchResult> iterator = currentHighlights.iterator();
+
+        while (iterator.hasNext()) {
+            TextBlockSearchEditor.SearchResult searchResult = iterator.next();
+            removeTextHighlightText(searchResult.getBeginningIndexOfMatch(), searchResult.getEndingIndexOfMatch());
+        }
+    }
+
+    /**
+     * This removes the highlight from the specified existing text. Unfortunately, it always sets it to the default style. So it removes any previous styling.
+     */
+    public void removeTextHighlightText(int startingIndex, int endingIndex) {
+        int length = endingIndex - startingIndex;
+        ((DefaultStyledDocument) textComponentToSearch.getDocument()).setCharacterAttributes(startingIndex, length, defaultStyle, true);
+    }
+
+    public String getTextToSearch() {
+        return textComponentToSearch.getText();
+    }
+
+    /**
+     * @return the current location of hte caret within the text we're going to search.
+     */
+    public int getCaretLocation() {
+        return textComponentToSearch.getCaretPosition();
+    }
+
+    /**
+     * Highlight and then ensure this result is visible.
+     *
+     * @param editor the editor that was used to search
+     * @param searchResult the specific result (within the editor's search results) to highlight.
+     */
+    public void highlightAndScrollToResult(TextBlockSearchEditor editor, TextBlockSearchEditor.SearchResult searchResult) {
+        //first, reset the existing highlights so there's no highlights
+        highlightResults(editor, false);
+
+        //
+        highlightText(searchResult.getBeginningIndexOfMatch(), searchResult.getEndingIndexOfMatch(), true, true);
+    }
+
+    /**
+     * This highlights the text within the specified range
+     *
+     * @param startingIndex where to start the highlight
+     * @param endingIndex where to end the highlight
+     * @param ensureVisible true to scroll to the text
+     * @param isEmphasized true to use an emphasized highlight (versus a regular highlight). Useful for showing the 'current' highlighted result.
+     */
+    public void highlightText(int startingIndex, int endingIndex, boolean ensureVisible, boolean isEmphasized) {
+        int length = endingIndex - startingIndex;
+
+        AttributeSet style;
+        if (isEmphasized) {
+            style = emphasizedHighlightStyle;
+        } else {
+            style = highlightStyle;
+        }
+
+        ((DefaultStyledDocument) textComponentToSearch.getDocument()).setCharacterAttributes(startingIndex, length, style, true);
+
+        if (ensureVisible) {
+            Utility.scrollToText(textComponentToSearch, startingIndex, endingIndex);
+            textComponentToSearch.setCaretPosition(endingIndex);
+        }
+
+        textComponentToSearch.repaint();
+    }
+}
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
index a5682b7..2af8fc7 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
@@ -16,21 +16,43 @@
 package org.gradle.gradleplugin.userinterface.swing.generic;
 
 import org.gradle.BuildResult;
-import org.gradle.StartParameter;
 import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.foundation.output.FileLink;
 import org.gradle.foundation.output.FileLinkDefinitionLord;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
 import org.gradle.gradleplugin.foundation.request.Request;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.ActionEvent;
+import org.gradle.gradleplugin.userinterface.swing.common.SearchPanel;
+import org.gradle.gradleplugin.userinterface.swing.common.TextPaneSearchInteraction;
+import org.gradle.logging.ShowStacktrace;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JTextPane;
+import javax.swing.JToggleButton;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.JButton;
+import javax.swing.AbstractAction;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.io.File;
+import java.awt.event.ActionEvent;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
+import java.io.File;
 
 /**
  * This is a panel that displays the results of executing a gradle command. It shows gradle's output as well as progress.
@@ -39,6 +61,7 @@ import java.util.Calendar;
  */
 public class OutputPanel extends JPanel implements ExecuteGradleCommandServerProtocol.ExecutionInteraction {
 
+    private GradlePluginLord gradlePluginLord;
     private OutputPanelParent parent;
     private AlternateUIInteraction alternateUIInteraction;
 
@@ -53,16 +76,27 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
     private JLabel statusLabel;
 
     private JButton executeAgainButton;
+    private JButton stopButton;
+    private JButton findButton;
+    private JToggleButton pinButton;
+    private JButton addToFavoritesButton;
+
+    private JPanel linkNavigationPanel;
 
     private JLabel forceShowOutputButtonLabel;   //a label that acts like a button
 
+    private SearchPanel searchPanel;
+
     private boolean isBusy;     //is this actively showing output?
-    private boolean isPending;  //is this waitin got show output?
+    private boolean isPending;  //is this waiting to show output?
     private boolean isPinned;   //keeps this panel open and disallows it from being re-used.
     private boolean showProgress = true;
     private boolean onlyShowOutputOnErrors;
+    private boolean wasStopped;   //whether or not execution has been stopped by the user
 
     private Request request;
+    private JButton nextLinkButton;
+    private JButton previousLinkButton;
 
     public interface OutputPanelParent {
 
@@ -75,7 +109,8 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
         public FileLinkDefinitionLord getFileLinkDefinitionLord();
     }
 
-    public OutputPanel(OutputPanelParent parent, AlternateUIInteraction alternateUIInteraction) {
+    public OutputPanel(GradlePluginLord gradlePluginLord, OutputPanelParent parent, AlternateUIInteraction alternateUIInteraction) {
+        this.gradlePluginLord = gradlePluginLord;
         this.parent = parent;
         this.alternateUIInteraction = alternateUIInteraction;
     }
@@ -99,6 +134,8 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
             setOnlyShowOutputOnErrors(onlyShowOutputOnErrors);
         }
 
+        enableAddToFavoritesAppropriately();
+
         //set this to indeterminate until we figure out how many tasks to execute.
         progressBar.setIndeterminate(true);
         progressBar.setStringPainted(false); //And don't show '0%' in the mean time.
@@ -123,6 +160,7 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
 
     public void setPinned(boolean pinned) {
         isPinned = pinned;
+        pinButton.setSelected(isPinned);
     }
 
     public boolean isBusy() {
@@ -153,8 +191,17 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
     private void setupUI() {
         setLayout(new BorderLayout());
 
-        add(createInfoPanel(), BorderLayout.NORTH);
-        add(createGradleOutputPanel(), BorderLayout.CENTER);
+        add(createSideOptionsPanel(), BorderLayout.WEST);
+
+        //why am I adding this center panel? Its so I can make the WEST side always stay in place. NORTH takes presedent over WEST normally.
+        //and the NORTH panel here changes it height (when the progress bar comes and goes). This made the buttons along the WEST layout move
+        //and was very jarring if you were about to click one. Now the buttons stay in place.
+        JPanel centerPanel = new JPanel(new BorderLayout());
+        add(centerPanel, BorderLayout.CENTER);
+
+        centerPanel.add(createGradleOutputPanel(), BorderLayout.CENTER);
+        centerPanel.add(createInfoPanel(), BorderLayout.NORTH);
+        centerPanel.add(createSearchPanel(), BorderLayout.SOUTH);
     }
 
     private Component createGradleOutputPanel() {
@@ -200,12 +247,6 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
         statusPanel = new JPanel();
         statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.X_AXIS));
         statusLabel = new JLabel();
-        executeAgainButton = Utility.createButton(OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png", "Execute Again", new AbstractAction() {
-            public void actionPerformed(ActionEvent e) {
-                parent.executeAgain(request, OutputPanel.this);
-            }
-        });
-        executeAgainButton.setVisible(false);
 
         //this button is only shown when the output is hidden
         forceShowOutputButtonLabel = new JLabel("Show Output");
@@ -223,9 +264,6 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
                 forceShowOutputButtonLabel.setForeground(UIManager.getColor("Label.foreground"));
             }
         });
-
-        statusPanel.add(executeAgainButton);
-        statusPanel.add(Box.createHorizontalStrut(2));
         statusPanel.add(statusLabel);
         statusPanel.add(Box.createHorizontalGlue());
         statusPanel.add(forceShowOutputButtonLabel);
@@ -235,14 +273,159 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
     }
 
     /**
-     * Call this if you're going to reuse this. it resets its output.
+     * This creates a panel that has several options such as execute again, stop, cancel, go to next link, etc..
+     */
+    private Component createSideOptionsPanel() {
+        executeAgainButton = Utility.createButton(OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png", "Execute again", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                parent.executeAgain(request, OutputPanel.this);
+            }
+        });
+
+        executeAgainButton.setEnabled(false);
+
+        stopButton = Utility.createButton(OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/stop.png", "Stop executing", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                stop();
+            }
+        });
+        stopButton.setEnabled(true);
+
+        findButton = Utility.createButton(OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/find.png", "Find in output", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                searchPanel.show();
+            }
+        });
+
+        pinButton = Utility.createToggleButton(OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/pin.png", "Pin this output tab", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                setPinned(!isPinned);
+            }
+        });
+
+        addToFavoritesButton = Utility.createButton(OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/add-favorite.png", "Add to favorites", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                addToFavorites();
+            }
+        });
+
+
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        panel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); //not only does this make it look better, it's really need so the pin toggle shows up clearly.
+
+        panel.add(executeAgainButton);
+        panel.add(Box.createVerticalStrut(5));
+        panel.add(stopButton);
+        panel.add(Box.createVerticalStrut(10));
+        panel.add(pinButton);
+        panel.add(Box.createVerticalStrut(10));
+        panel.add(findButton);
+        panel.add(Box.createVerticalStrut(10));
+        panel.add(createLinkNavigationOptions());
+        //the navigation options create a vertical strut and is only shown if the options are present, so we don't need to add one here.
+        panel.add(addToFavoritesButton);
+
+        panel.add(Box.createVerticalGlue());
+
+        return panel;
+    }
+
+    /**
+     * This creates a panel that has options for going to the next and previous link. This may be entirely hidden if the user doesn't have the ability to open file links.
+     */
+    private Component createLinkNavigationOptions() {
+
+        linkNavigationPanel = new JPanel();
+        linkNavigationPanel.setLayout(new BoxLayout(linkNavigationPanel, BoxLayout.Y_AXIS));
+
+        nextLinkButton = Utility.createButton(getClass(), "/org/gradle/gradleplugin/userinterface/swing/generic/next-link.png", "Go to the next link", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                goToNextLink();
+            }
+        });
+
+        previousLinkButton = Utility.createButton(getClass(), "/org/gradle/gradleplugin/userinterface/swing/generic/previous-link.png", "Go to the previous link", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                goToPreviousLink();
+            }
+        });
+
+        linkNavigationPanel.add(previousLinkButton);
+        linkNavigationPanel.add(Box.createVerticalStrut(5));
+        linkNavigationPanel.add(nextLinkButton);
+        linkNavigationPanel.add(Box.createVerticalStrut(10));
+
+        if (!alternateUIInteraction.doesSupportEditingOpeningFiles()) {
+            linkNavigationPanel.setVisible(false); //If we don't support it, hide this panel. Its just easier to create the above controls and know they're always present and hide them than constantly check for nulls.
+        }
+
+        return linkNavigationPanel;
+    }
+
+    private Component createSearchPanel() {
+        StyleContext styleContent = StyleContext.getDefaultStyleContext();
+
+        AttributeSet highlightStyle = gradleOutputTextPane.getDefaultStyle().copyAttributes();
+        highlightStyle = styleContent.addAttribute(highlightStyle, StyleConstants.Foreground, Color.white);
+        highlightStyle = styleContent.addAttribute(highlightStyle, StyleConstants.Background, Color.orange);
+        highlightStyle = styleContent.addAttribute(highlightStyle, StyleConstants.Underline, true);
+
+        AttributeSet emphasizedHighlightStyle = highlightStyle.copyAttributes();
+        emphasizedHighlightStyle = styleContent.addAttribute(emphasizedHighlightStyle, StyleConstants.Foreground, Color.black);
+        emphasizedHighlightStyle = styleContent.addAttribute(emphasizedHighlightStyle, StyleConstants.Background, Color.yellow);
+
+        searchPanel = new SearchPanel(new OutputPanelSearchInteraction(gradleOutputTextPane.getTextComponent(), gradleOutputTextPane.getDefaultStyle(), highlightStyle, emphasizedHighlightStyle));
+        searchPanel.hide();
+
+        return searchPanel.getComponent();
+    }
+
+    /**
+     * Special implementation just so can control how results are repainted. Specifically, so we can erase search highlights.
+     */
+    private class OutputPanelSearchInteraction extends TextPaneSearchInteraction {
+        private OutputPanelSearchInteraction(JTextPane textComponentToSearch, AttributeSet defaultStyle, AttributeSet highlightStyle, AttributeSet emphasizedHighlightStyle) {
+            super(textComponentToSearch, defaultStyle, highlightStyle, emphasizedHighlightStyle);
+        }
+
+        /**
+         * We override this so we can handle our more-complicated case. The base class will remove the highlighting, and in doing so, will remove ALL highlighting. We want to keep the highlighting we've
+         * specially added.
+         */
+        @Override
+        public void removeResultHighlights() {
+            gradleOutputTextPane.resetHighlights();
+        }
+    }
+
+    private void goToNextLink() {
+        FileLink fileLink = gradleOutputTextPane.getNextFileLink();
+        gradleOutputTextPane.selectFileLink(fileLink);
+    }
+
+    private void goToPreviousLink() {
+        FileLink fileLink = gradleOutputTextPane.getPreviousFileLink();
+        gradleOutputTextPane.selectFileLink(fileLink);
+    }
+
+    /**
+     * Call this before you use this. It resets its output as well as enabling buttons appropriately.
      */
     public void reset() {
-        executeAgainButton.setVisible(false);
+        executeAgainButton.setEnabled(false);
+        stopButton.setEnabled(true);
         statusLabel.setText("");
         statusLabel.setForeground(UIManager.getColor("Label.foreground"));
         gradleOutputTextPane.setText("");
         progressLabel.setText("");
+        wasStopped = false;
+
+        searchPanel.hide();
+
+        previousLinkButton.setEnabled(false);
+        nextLinkButton.setEnabled(false);
     }
 
     /**
@@ -254,6 +437,7 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
         SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                 gradleOutputTextPane.appendText(text);
+                updateLinkNavigationOptions();
             }
         });
     }
@@ -340,28 +524,38 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
                     statusLabel.setText("Completed successfully at " + formattedTime);
                     appendGradleOutput("\nCompleted Successfully");
                 } else {
-                    statusLabel.setText("Completed with errors at " + formattedTime);
+                    if (wasStopped) {
+                        statusLabel.setText("User stopped execution at " + formattedTime);
+                    } else {
+                        statusLabel.setText("Completed with errors at " + formattedTime);
+                    }
+
                     statusLabel.setForeground(Color.red.darker());
 
                     //since errors occurred, show the output. If onlyShowOutputOnErrors is false, this textPanel will already be visible.
                     gradleOutputTextPanel.setVisible(true);
                 }
 
-                executeAgainButton.setVisible(true);
+                executeAgainButton.setEnabled(true);
+                stopButton.setEnabled(false);
 
                 appendThrowable(throwable);
 
                 //lastly, if the text output is not visible, make the 'show output' button visible
                 forceShowOutputButtonLabel.setVisible(!gradleOutputTextPanel.isVisible());
+                updateLinkNavigationOptions();
+
+                searchPanel.performSearchAgain(); //this will update our results if the user was searching during the execution
 
                 parent.reportExecuteFinished(request, wasSuccessful);
+
             }
         });
     }
 
     private void appendThrowable(Throwable throwable) {
         if (throwable != null) {
-            String output = GradlePluginLord.getGradleExceptionMessage(throwable, StartParameter.ShowStacktrace.ALWAYS_FULL);
+            String output = GradlePluginLord.getGradleExceptionMessage(throwable, ShowStacktrace.ALWAYS_FULL);
             appendGradleOutput(output);
         }
     }
@@ -431,6 +625,23 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
     }
 
     public boolean close() {
+
+        if (!stop()) {
+            return false;
+        }
+
+        parent.removeOutputPanel(this);
+
+        setPinned(false);  //unpin it when it is removed
+        return true;
+    }
+
+    /**
+     * This stops the currently executing task if any exists.
+     *
+     * @return true if the request stopped, false if not.
+     */
+    public boolean stop() {
         if (request != null)   //if we have a request, we can only close if it allows us to.
         {
             if (!request.cancel()) {
@@ -438,12 +649,12 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
             }
         }
 
-        parent.removeOutputPanel(this);
+        wasStopped = true;
 
-        setPinned(false);  //unpin it when it is removed
         return true;
     }
 
+
     /**
      * Sets the font for this component.
      *
@@ -459,4 +670,56 @@ public class OutputPanel extends JPanel implements ExecuteGradleCommandServerPro
             gradleOutputTextPane.setFont(font);
         }
     }
+
+    /**
+     * Shows or hides the link navigation options appropriately
+     */
+    private void updateLinkNavigationOptions() {
+        if (gradleOutputTextPane.hasClickableLinks()) {
+
+            nextLinkButton.setEnabled(true);
+            previousLinkButton.setEnabled(true);
+        } else {
+            nextLinkButton.setEnabled(false);
+            previousLinkButton.setEnabled(false);
+        }
+    }
+
+    /**
+     * Adds the current request to the favorites and allows the user to edit it.
+     */
+    private void addToFavorites() {
+        if (request == null) {
+            return;
+        }
+
+        String fullCommandLine = request.getFullCommandLine();
+        String displayName = request.getDisplayName();
+
+        FavoriteTask favoriteTask = gradlePluginLord.getFavoritesEditor().addFavorite(fullCommandLine, displayName, false);
+        if (favoriteTask != null) {
+            gradlePluginLord.getFavoritesEditor().editFavorite(favoriteTask, new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(this), "Edit Favorite", SwingEditFavoriteInteraction.SynchronizeType.OnlyIfAlreadySynchronized));
+            enableAddToFavoritesAppropriately();
+        }
+    }
+
+    /**
+     * This shows or hides the 'add to favorites' button based on the current request.
+     */
+    private void enableAddToFavoritesAppropriately() {
+        boolean isVisible = true;
+        if (request == null) {
+            isVisible = false;
+        } else {
+            //adding 'refresh' to favorites no sense. Hide this button in that case.
+            if (request.getType() == RefreshTaskListRequest.TYPE) {
+                isVisible = false;
+            } else if (gradlePluginLord.getFavoritesEditor().getFavorite(request.getFullCommandLine()) != null) //is it a command that's already a favorite?
+            {
+                isVisible = false;
+            }
+        }
+
+        addToFavoritesButton.setVisible(isVisible);
+    }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
index 4c3a7e1..c3288b1 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.gradleplugin.userinterface.swing.generic;
 
-import org.gradle.foundation.common.ObserverLord;
 import org.gradle.foundation.output.FileLinkDefinitionLord;
 import org.gradle.foundation.queue.ExecutionQueue;
+import org.gradle.foundation.common.ObserverLord;
 import org.gradle.gradleplugin.foundation.GradlePluginLord;
 import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
 import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
@@ -25,7 +25,9 @@ import org.gradle.gradleplugin.foundation.request.Request;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
 
 import javax.swing.*;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Point;
+import java.awt.Font;
 import java.awt.event.ActionEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
@@ -173,9 +175,10 @@ public class OutputPanelLord implements OutputUILord, GradlePluginLord.RequestOb
             outputPanel.setTabHeaderText(description);
             outputPanel.reset();
         } else {  //we don't have an existing tab. Create a new one.
-            outputPanel = new OutputTab(this, description, alternateUIInteraction);
+            outputPanel = new OutputTab(gradlePluginLord, this, description, alternateUIInteraction);
             outputPanel.setFont(font);
             outputPanel.initialize();
+            outputPanel.reset();
             tabbedPane.addTab(description, outputPanel);
             if (selectOutputPanel) {
                 tabbedPane.setSelectedComponent(outputPanel);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
index 4030a6e..b3c65a8 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
@@ -17,16 +17,21 @@ package org.gradle.gradleplugin.userinterface.swing.generic;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
 
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.ImageIcon;
 import javax.imageio.ImageIO;
-import javax.swing.*;
-import java.awt.*;
+import java.awt.Component;
+import java.awt.image.BufferedImage;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
 import java.io.InputStream;
+import java.io.IOException;
 
 /**
  * This just wraps up an OutputPanel so it has a tab header that can be dynamic. The current (rather awkward) JTabbedPane implementation is to separate the tab contents from its component. This only
@@ -45,15 +50,21 @@ public class OutputTab extends OutputPanel {
 
     private static ImageIcon closeIcon;
     private static ImageIcon closeHighlightIcon;
+    private static ImageIcon pinnedIcon;
 
-    public OutputTab(OutputPanelParent parent, String header, AlternateUIInteraction alternateUIInteraction) {
-        super(parent, alternateUIInteraction);
+    public OutputTab(GradlePluginLord gradlePluginLord, OutputPanelParent parent, String header, AlternateUIInteraction alternateUIInteraction) {
+        super(gradlePluginLord, parent, alternateUIInteraction);
         mainPanel = new JPanel();
         mainPanel.setOpaque(false);
         mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
 
         mainTextLabel = new JLabel(header);
-        pinnedLabel = new JLabel("(Pinned) ");
+
+        if (pinnedIcon == null) {
+            pinnedIcon = getImageIconResource("/org/gradle/gradleplugin/userinterface/swing/generic/pin.png");
+        }
+
+        pinnedLabel = new JLabel(pinnedIcon);
         pinnedLabel.setVisible(isPinned());
 
         setupCloseLabel();
@@ -66,16 +77,8 @@ public class OutputTab extends OutputPanel {
 
     private void setupCloseLabel() {
         if (closeIcon == null) {
-            BufferedImage closeImage = getImageResource("close.png");
-            BufferedImage closeHighlightImage = getImageResource("close-highlight.png");
-
-            if (closeImage != null) {
-                closeIcon = new ImageIcon(closeImage);
-            }
-
-            if (closeHighlightImage != null) {
-                closeHighlightIcon = new ImageIcon(closeHighlightImage);
-            }
+            closeIcon = getImageIconResource("close.png");
+            closeHighlightIcon = getImageIconResource("close-highlight.png");
         }
 
         closeLabel = new JLabel(closeIcon);
@@ -110,8 +113,16 @@ public class OutputTab extends OutputPanel {
         return null;
     }
 
+    private ImageIcon getImageIconResource(String imageIconResourceName) {
+        BufferedImage image = getImageResource(imageIconResourceName);
+        if (image != null) {
+            return new ImageIcon(image);
+        }
+        return null;
+    }
+
     /**
-     * Call this if you're going to reuse this. it resets its output.
+     * Call this before you use this tab. It resets its output as well as enabling buttons appropriately.
      *
      * @author mhunsicker
      */
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java
index 09ca6c0..dc5c015 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java
@@ -15,20 +15,23 @@
  */
 package org.gradle.gradleplugin.userinterface.swing.generic;
 
-import org.gradle.foundation.output.FileLink;
 import org.gradle.foundation.output.FileLinkDefinitionLord;
 import org.gradle.foundation.output.LiveOutputParser;
+import org.gradle.foundation.output.FileLink;
 
 import javax.swing.*;
 import javax.swing.text.*;
+
 import java.awt.*;
 import java.awt.datatransfer.StringSelection;
-import java.awt.event.ActionEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
-import java.io.File;
-import java.util.Iterator;
+import java.awt.event.ActionEvent;
 import java.util.List;
+import java.util.Iterator;
+import java.io.File;
 
 /**
  * Rich text pane meant to simplify adding text, scrolling, prevent line wrapping, and highlighting FileLinks.
@@ -46,7 +49,8 @@ public class OutputTextPane {
     private LiveOutputParser liveOutputParser;
 
     private Interaction interaction;
-    private boolean hasClickableFiles;  //determines whether or not we allow the user to click on files. We'll highlight them if we allow this.
+    private boolean allowsClickingFiles;  //determines whether or not we allow the user to click on file links. We'll highlight them if we allow this.
+    private boolean hasClickableLinks;     //whether or not any clickable links actually exist
 
     private JPopupMenu popupMenu;
 
@@ -63,9 +67,9 @@ public class OutputTextPane {
         public void fileClicked(File file, int line);
     }
 
-    public OutputTextPane(Interaction interaction, boolean hasClickableFiles, Font font, FileLinkDefinitionLord fileLinkDefinitionLord) {
+    public OutputTextPane(Interaction interaction, boolean allowsClickingFiles, Font font, FileLinkDefinitionLord fileLinkDefinitionLord) {
         this.interaction = interaction;
-        this.hasClickableFiles = hasClickableFiles;
+        this.allowsClickingFiles = allowsClickingFiles;
         this.font = font;
 
         document = new DefaultStyledDocument();
@@ -87,27 +91,42 @@ public class OutputTextPane {
         textPane.setCaret(caret);
 
         Color background = Color.white;
-        textPane.setBackground(
-                background);  //its not editable, but it looks better with a white background. (I tried using UI.Manager.getColor( "TextArea.background" ) (and others) but it was showing up as gray when using inside Idea. I think the L&F remapped some things and we want it white.
+        textPane.setBackground(background);  //its not editable, but it looks better with a white background. (I tried using UI.Manager.getColor( "TextArea.background" ) (and others) but it was showing up as gray when using inside Idea. I think the L&F remapped some things and we want it white.
         scroll.setBackground(background);    //the scroll pane was showing up as grey in the Idea plugin. Not sure why. This should fix it.
         scroll.getViewport().setBackground(background);    //this makes the non-text area of the scroll pane appear white on Windows (if you have short text).
         resetFontStyles();
 
         textPane.addMouseListener(new MouseAdapter() {
+            /**
+             {@inheritDoc}
+             */
             @Override
-            public void mouseReleased(MouseEvent e) {
+            public void mouseClicked(MouseEvent e) {
                 handleClick(e.getButton() == MouseEvent.BUTTON3, e.getPoint());
             }
+
+            @Override
+            public void mouseReleased(MouseEvent e) {
+                if (e.getButton() == MouseEvent.BUTTON3) {
+                    showPopup(e.getPoint());
+                }
+            }
+        });
+        textPane.addKeyListener(new KeyAdapter() {
+            @Override
+            public void keyPressed(KeyEvent e) {
+                handleKeyPress(e.getKeyCode());
+            }
         });
         liveOutputParser = new LiveOutputParser(fileLinkDefinitionLord, true);
     }
 
     private void resetFontStyles() {
+        defaultStyle = createDefaultAttributeSet();
+
         //setup the fileStyle
         StyleContext styleContent = StyleContext.getDefaultStyleContext();
 
-        defaultStyle = createDefaultAttributeSet();
-
         //modify the default to have a blue color and an underline
         fileStyle = createDefaultAttributeSet();
         fileStyle = styleContent.addAttribute(fileStyle, StyleConstants.Foreground, Color.blue);
@@ -126,10 +145,21 @@ public class OutputTextPane {
         return attributeSet;
     }
 
+    public AttributeSet getDefaultStyle() {
+        return defaultStyle;
+    }
+
+    /**
+     * @return the component you would use to insert this control into a container. Its actually the scroll pane.
+     */
     public JComponent asComponent() {
         return scroll;
     }
 
+    public JTextPane getTextComponent() {
+        return textPane;
+    }
+
     public String getText() {
         return textPane.getText();
     }
@@ -138,13 +168,26 @@ public class OutputTextPane {
      * When a user clicks, we determine if a FileLink was clicked on and if so, notify our interaction.
      */
     private void handleClick(boolean isRightButton, Point point) {
-        if (isRightButton) {
-            showPopup(point);
-        } else {
-            if (hasClickableFiles) {
-                FileLink fileLink = getFileLinkAt(point);
-                if (fileLink != null) {
-                    interaction.fileClicked(fileLink.getFile(), fileLink.getLineNumber());
+        if (!isRightButton && allowsClickingFiles) {
+            FileLink fileLink = getFileLinkAt(point);
+            if (fileLink != null) {
+                interaction.fileClicked(fileLink.getFile(), fileLink.getLineNumber());
+            }
+        }
+    }
+
+    /**
+     * When a user hits enter we'll open any file links that they're on. This is useful if the user is going to the next/previous link and want to open the current link.
+     */
+    private void handleKeyPress(int keyCode) {
+        if (allowsClickingFiles) {
+            if (keyCode == KeyEvent.VK_ENTER) {
+                int caretLocation = textPane.getCaretPosition();
+                if (caretLocation != -1) {
+                    FileLink fileLink = liveOutputParser.getFileLink(caretLocation);
+                    if (fileLink != null) {
+                        interaction.fileClicked(fileLink.getFile(), fileLink.getLineNumber());
+                    }
                 }
             }
         }
@@ -186,6 +229,7 @@ public class OutputTextPane {
         appendText(text, false);
     }
 
+
     /**
      * This sets the full text of this control, removing existing text.
      *
@@ -219,6 +263,7 @@ public class OutputTextPane {
         try {
             if (replaceExisting)   //if we're supposed to be replacing, then do so.
             {
+                hasClickableLinks = false;
                 document.remove(0, document.getLength());
             }
 
@@ -229,7 +274,7 @@ public class OutputTextPane {
 
         //parse this text and apply the styles accordingly. Note: the LiveOutputParser only returns FileLinks for full lines. The text
         //we add may contain a FileLink, but it won't parse it until it reaches a new line.
-        if (hasClickableFiles) {
+        if (allowsClickingFiles) {
             List<FileLink> fileLinks = liveOutputParser.appendText(text);
             highlightFileLinks(fileLinks);
         }
@@ -253,7 +298,8 @@ public class OutputTextPane {
         while (iterator.hasNext()) {
             FileLink fileLink = iterator.next();
 
-            document.setCharacterAttributes(fileLink.getStartingIndex(), fileLink.getLength(), fileStyle, true);
+            document.setCharacterAttributes(fileLink.getStartingIndex(), fileLink.getLength(), fileStyle, false);
+            hasClickableLinks = true;
         }
     }
 
@@ -313,4 +359,63 @@ public class OutputTextPane {
         liveOutputParser.reset();
         appendText(text, true);
     }
+
+    public boolean hasClickableLinks() {
+        return hasClickableLinks;
+    }
+
+    public boolean allowsClickingFiles() {
+        return allowsClickingFiles;
+    }
+
+    /**
+     * Selects and scrolls to the specified file link
+     */
+    public void selectFileLink(FileLink fileLink) {
+
+        if (fileLink == null) {
+            return;
+        }
+
+        textPane.setCaretPosition(fileLink.getStartingIndex());
+        textPane.select(fileLink.getStartingIndex(), fileLink.getEndingIndex());
+
+        try {
+            Rectangle startingRectangle = textPane.modelToView(fileLink.getStartingIndex());
+            Rectangle endDingRectangle = textPane.modelToView(fileLink.getEndingIndex());
+
+            Rectangle totalBounds = startingRectangle.union(endDingRectangle);
+
+            textPane.scrollRectToVisible(totalBounds);
+            textPane.requestFocus();   //this seems to help the selection being painted
+        } catch (BadLocationException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Returns the previous file link relative to the specified file link. If you pass in null, we'll return the last one.
+     */
+    public FileLink getPreviousFileLink() {
+        return liveOutputParser.getPreviousFileLink(textPane.getCaretPosition());
+    }
+
+    /**
+     * Returns the next file link relative to the specified file link. If you pass in null, we'll return the first one.
+     */
+    public FileLink getNextFileLink() {
+        return liveOutputParser.getNextFileLink(textPane.getCaretPosition());
+    }
+
+    /**
+     * Call this if you've changed any styles of our text and want to revert it back to the defaults.
+     */
+    public void resetHighlights() {
+        document.setCharacterAttributes(0, document.getLength(), defaultStyle, true);
+
+        if (allowsClickingFiles) {
+            List<FileLink> fileLinks = liveOutputParser.getFileLinks();
+            highlightFileLinks(fileLinks);
+        }
+    }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
index 7469b6f..e6c0600 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
@@ -18,10 +18,12 @@ package org.gradle.gradleplugin.userinterface.swing.generic;
 import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
 
 import javax.swing.*;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
 import javax.swing.text.BadLocationException;
-import java.awt.*;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Window;
 import java.awt.event.*;
 
 /**
@@ -30,17 +32,27 @@ import java.awt.event.*;
  * @author mhunsicker
  */
 public class SwingEditFavoriteInteraction implements FavoritesEditor.EditFavoriteInteraction {
+
+    public enum SynchronizeType {
+        OnlyIfAlreadySynchronized,   //the the display name in synch with the command only if they are already synchronized (and it can be overridden by the user if they change the display name manually)
+        Never                        //Do not attempt to keep them in synch
+    };
+
     private JDialog dialog;
     private JTextField fullCommandLineTextField;
     private JTextField displayNameTextField;
     private JCheckBox alwaysShowOutputCheckBox;
     private boolean saveResults;
-    private boolean synchronizeDisplayNameWithCommand;
+
+    private SynchronizeType synchronizeType;
+
+    private DocumentListener synchronizationDocumentListener;
+    private KeyAdapter synchronizationKeyAdapter;
 
     //pass in true to synchronizeDisplayNameWithCommand for new favorites.
 
-    public SwingEditFavoriteInteraction(Window parent, String title, boolean synchronizeDisplayNameWithCommand) {
-        this.synchronizeDisplayNameWithCommand = synchronizeDisplayNameWithCommand;
+    public SwingEditFavoriteInteraction(Window parent, String title, SynchronizeType synchronizeType) {
+        this.synchronizeType = synchronizeType;
         setupUI(parent, title);
     }
 
@@ -82,21 +94,9 @@ public class SwingEditFavoriteInteraction implements FavoritesEditor.EditFavorit
         panel.add(Utility.addLeftJustifiedComponent(alwaysShowOutputCheckBox));
         panel.add(Box.createVerticalGlue());
 
-        synchronizeDisplayNameWithCommand();
-
-        return panel;
-    }
 
-    /**
-     * This synchronizes the display name with the command line. This is so when you're adding a new favorite, the display name is automatic. If you type anything in the display name, we'll cancel
-     * synchronization.
-     */
-    private void synchronizeDisplayNameWithCommand() {
-        if (!synchronizeDisplayNameWithCommand) {
-            return;
-        }
-
-        final DocumentListener documentListener = new DocumentListener() {
+        //create some listeners that we can use for synchronization purposes.
+        synchronizationDocumentListener = new DocumentListener() {
             public void insertUpdate(DocumentEvent documentEvent) {
                 setDisplayNameTextToCommandLineText();
             }
@@ -110,18 +110,38 @@ public class SwingEditFavoriteInteraction implements FavoritesEditor.EditFavorit
             }
         };
 
-        fullCommandLineTextField.getDocument().addDocumentListener(documentListener);
-        displayNameTextField.addKeyListener(new KeyAdapter() {
+        synchronizationKeyAdapter = new KeyAdapter() {
             @Override
-            public void keyPressed(KeyEvent keyEvent) {  //the user typed someting. Remove the document listener
-                fullCommandLineTextField.getDocument().removeDocumentListener(documentListener);
+            public void keyPressed(KeyEvent keyEvent) {  //the user typed something. Remove the document listener
+                fullCommandLineTextField.getDocument().removeDocumentListener(synchronizationDocumentListener);
+                displayNameTextField.removeKeyListener(synchronizationKeyAdapter); //and we don't need this anymore either
             }
-        });
+        };
+
+        return panel;
+    }
+
+    /**
+     * This synchronizes the display name with the command line (based on whether or not we should synchronize). This is so when you're adding a new favorite, the display name is automatic. If you
+     * type anything in the display name, we'll cancel synchronization. This can be called repeatedly for this dialog so it resets it rather than just sets it up once.
+     *
+     * @param favoriteTask the task currently being edited.
+     */
+    private void synchronizeDisplayNameWithCommand(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+
+        if (synchronizeType == SynchronizeType.Never || !favoriteTask.isDisplayNameAndFullCommandSynchronized()) {
+            fullCommandLineTextField.getDocument().removeDocumentListener(synchronizationDocumentListener);
+            displayNameTextField.removeKeyListener(synchronizationKeyAdapter);
+        } else {
+            fullCommandLineTextField.getDocument().addDocumentListener(synchronizationDocumentListener);
+            displayNameTextField.addKeyListener(synchronizationKeyAdapter);
+        }
     }
 
     private void setDisplayNameTextToCommandLineText() {
         try {
-            String text = fullCommandLineTextField.getDocument().getText(0, fullCommandLineTextField.getDocument().getLength());
+            String text = fullCommandLineTextField.getDocument().getText(0,
+                    fullCommandLineTextField.getDocument().getLength());
             displayNameTextField.setText(text);
         } catch (BadLocationException e) {
             e.printStackTrace();
@@ -177,6 +197,8 @@ public class SwingEditFavoriteInteraction implements FavoritesEditor.EditFavorit
         displayNameTextField.setText(favoriteTask.displayName);
         alwaysShowOutputCheckBox.setSelected(favoriteTask.alwaysShowOutput);
 
+        synchronizeDisplayNameWithCommand(favoriteTask);
+
         dialog.pack();
         dialog.setLocationRelativeTo(dialog.getParent());
         dialog.setVisible(true);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
index cf0a838..4b1d135 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
@@ -17,9 +17,11 @@ package org.gradle.gradleplugin.userinterface.swing.generic;
 
 import org.gradle.gradleplugin.foundation.DOM4JSerializer;
 
-import javax.swing.*;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
 import javax.swing.filechooser.FileFilter;
-import java.awt.*;
+import java.awt.Window;
 import java.io.File;
 
 /**
@@ -65,8 +67,9 @@ public class SwingExportInteraction implements DOM4JSerializer.ExportInteraction
      * @param file the file in question
      * @return true to overwrite it, false not to.
      */
-    public boolean confirmOverwritingExisingFile(File file) {
-        int result = JOptionPane.showConfirmDialog(SwingUtilities.getWindowAncestor(parent), "The file '" + file.getAbsolutePath() + "' already exists. Overwrite?", "Confirm Overwriting File",
+    public boolean confirmOverwritingExistingFile(File file) {
+        int result = JOptionPane.showConfirmDialog(SwingUtilities.getWindowAncestor(parent),
+                "The file '" + file.getAbsolutePath() + "' already exists. Overwrite?", "Confirm Overwriting File",
                 JOptionPane.YES_NO_OPTION);
         return result == JOptionPane.YES_OPTION;
     }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
index 626c17a..e2b765a 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
@@ -594,7 +594,7 @@ public class TaskTreeComponent {
     }
 
     /**
-     * This returns a list of selected tasks. This ignores selected projects.
+     * This returns a list of selected tasks. This ignores selected projects. Note: the tasks are ordered according to the order in which they were selected.
      *
      * @return the selected tasks. Will return an empty list if no tasks are selected.
      */
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
index e9d7178..d3a0ee3 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
@@ -21,8 +21,22 @@ import org.gradle.gradleplugin.userinterface.swing.common.BorderlessImageButton;
 import org.gradle.gradleplugin.userinterface.swing.common.BorderlessImageToggleButton;
 
 import javax.imageio.ImageIO;
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.Action;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.JToggleButton;
+import javax.swing.JMenuItem;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.JTextComponent;
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.awt.Window;
 import java.awt.event.InputEvent;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
@@ -97,6 +111,7 @@ public class Utility {
      * This creates a button with the specified action, image, and tooltip text. The main issue here is that it doesn't crash if the image is missing (which is just something that happens in real life
      * from time to time). You probably should specify a name on the action just in case.
      *
+     * @param resourceClass the calling class. Useful when multiple classloaders are used.
      * @param imageResourceName the image resource
      * @param tooltip the tooltip to display
      * @param action the action to perform
@@ -183,4 +198,21 @@ public class Utility {
 
         return null;
     }
+
+    /**
+     * Scrolls the specified text component so the text between the starting and ending index are visible.
+     */
+    public static void scrollToText(JTextComponent textComponent, int startingIndex, int endingIndex) {
+        try {
+            Rectangle startingRectangle = textComponent.modelToView(startingIndex);
+            Rectangle endDingRectangle = textComponent.modelToView(endingIndex);
+
+            Rectangle totalBounds = startingRectangle.union(endDingRectangle);
+
+            textComponent.scrollRectToVisible(totalBounds);
+            textComponent.repaint();
+        } catch (BadLocationException e) {
+            e.printStackTrace();
+        }
+    }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
index da6bbaf..8d32a2e 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
@@ -394,20 +394,19 @@ public class FavoriteTasksTab implements GradleTab, GradlePluginLord.GeneralPlug
      * Call this to prompt the user for a task to add.
      */
     private void addTask() {
-        favoritesEditor.addFavorite(new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Add Favorite", true));
+        favoritesEditor.addFavorite(new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Add Favorite", SwingEditFavoriteInteraction.SynchronizeType.OnlyIfAlreadySynchronized));
     }
 
     private void editTask() {
         FavoriteTask selectedFavoriteTask = getFirstSelectedFavoriteTask();
         //if the user has kept these two in synch, we'll continue to keep them in synch.
-        boolean synchronizeDisplayNameWithCommand = selectedFavoriteTask.getDisplayName().equals(selectedFavoriteTask.getFullCommandLine());
-        favoritesEditor.editFavorite(selectedFavoriteTask, new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Edit Favorite", synchronizeDisplayNameWithCommand));
+        favoritesEditor.editFavorite(selectedFavoriteTask, new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Edit Favorite", SwingEditFavoriteInteraction.SynchronizeType.OnlyIfAlreadySynchronized));
     }
 
     /**
      * This duplicates all the selected tasks
      */
     private void duplicateTasks() {
-        favoritesEditor.duplicateFavorites(getSelectedFavoriteTasks());
+        favoritesEditor.duplicateFavorites(getSelectedFavoriteTasks(), new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Duplicate Favorite", SwingEditFavoriteInteraction.SynchronizeType.OnlyIfAlreadySynchronized));
     }
 }
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
index aa153ad..c52e0d6 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.gradleplugin.userinterface.swing.generic.tabs;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -23,7 +22,8 @@ import org.gradle.gradleplugin.foundation.GradlePluginLord;
 import org.gradle.gradleplugin.foundation.settings.SettingsNode;
 import org.gradle.gradleplugin.userinterface.swing.generic.OutputUILord;
 import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
-import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.internal.SystemProperties;
+import org.gradle.logging.ShowStacktrace;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
 
 import javax.swing.*;
@@ -189,8 +189,6 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
 
     /**
      * Browses for a file using the text value from the text field as the current value.
-     *
-     * @param fileTextField where we get the current value
      */
     private File browseForDirectory(File initialFile) {
 
@@ -326,13 +324,13 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
         panel.setBorder(BorderFactory.createTitledBorder("Stack Trace Output"));
 
         showNoStackTraceRadioButton = new JRadioButton("Exceptions Only");
-        showStackTrackRadioButton = new JRadioButton("Standard Stack Trace (-" + DefaultCommandLineConverter.STACKTRACE
+        showStackTrackRadioButton = new JRadioButton("Standard Stack Trace (-" + LoggingCommandLineConverter.STACKTRACE
                 + ")");  //add the command line character to the end (so if an error message says use a stack trace level, you can easily translate)
-        showFullStackTrackRadioButton = new JRadioButton("Full Stack Trace (-" + DefaultCommandLineConverter.FULL_STACKTRACE + ")");
+        showFullStackTrackRadioButton = new JRadioButton("Full Stack Trace (-" + LoggingCommandLineConverter.FULL_STACKTRACE + ")");
 
-        showNoStackTraceRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS);
-        showStackTrackRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, StartParameter.ShowStacktrace.ALWAYS);
-        showFullStackTrackRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, StartParameter.ShowStacktrace.ALWAYS_FULL);
+        showNoStackTraceRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, ShowStacktrace.INTERNAL_EXCEPTIONS);
+        showStackTrackRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, ShowStacktrace.ALWAYS);
+        showFullStackTrackRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, ShowStacktrace.ALWAYS_FULL);
 
         stackTraceButtonGroup = new ButtonGroup();
         stackTraceButtonGroup.add(showNoStackTraceRadioButton);
@@ -358,7 +356,7 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
         String stackTraceLevel = settingsNode.getValueOfChild(STACK_TRACE_LEVEL, getSelectedStackTraceLevel().name());
         if (stackTraceLevel != null) {
             try {
-                setSelectedStackTraceLevel(StartParameter.ShowStacktrace.valueOf(stackTraceLevel));
+                setSelectedStackTraceLevel(ShowStacktrace.valueOf(stackTraceLevel));
                 updateStackTraceSetting(false);   //false because we're serializing this in
             } catch (Exception e) {  //this can happen if the stack trace levels change because you're moving between versions.
                 logger.error("Converting stack trace level text to stack trace level enum '" + stackTraceLevel + "'", e);
@@ -372,7 +370,7 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
      * This stores the current stack trace setting (based on the UI controls) in the plugin.
      */
     private void updateStackTraceSetting(boolean saveSetting) {
-        StartParameter.ShowStacktrace stackTraceLevel = getSelectedStackTraceLevel();
+        ShowStacktrace stackTraceLevel = getSelectedStackTraceLevel();
         gradlePluginLord.setStackTraceLevel(stackTraceLevel);
 
         if (saveSetting) {
@@ -386,11 +384,11 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
      *
      * @param newStackTraceLevel the new stack trace level.
      */
-    private void setSelectedStackTraceLevel(StartParameter.ShowStacktrace newStackTraceLevel) {
+    private void setSelectedStackTraceLevel(ShowStacktrace newStackTraceLevel) {
         Enumeration<AbstractButton> buttonEnumeration = stackTraceButtonGroup.getElements();
         while (buttonEnumeration.hasMoreElements()) {
             JRadioButton radioButton = (JRadioButton) buttonEnumeration.nextElement();
-            StartParameter.ShowStacktrace level = (StartParameter.ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
+            ShowStacktrace level = (ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
             if (newStackTraceLevel == level) {
                 radioButton.setSelected(true);
                 return;
@@ -404,20 +402,20 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
      *
      * @return the selected stack trace level
      */
-    private StartParameter.ShowStacktrace getSelectedStackTraceLevel() {
+    private ShowStacktrace getSelectedStackTraceLevel() {
         ButtonModel selectedButtonModel = stackTraceButtonGroup.getSelection();
         if (selectedButtonModel != null) {
             Enumeration<AbstractButton> buttonEnumeration = stackTraceButtonGroup.getElements();
             while (buttonEnumeration.hasMoreElements()) {
                 JRadioButton radioButton = (JRadioButton) buttonEnumeration.nextElement();
                 if (radioButton.getModel() == selectedButtonModel) {
-                    StartParameter.ShowStacktrace level = (StartParameter.ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
+                    ShowStacktrace level = (ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
                     return level;
                 }
             }
         }
 
-        return StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+        return ShowStacktrace.INTERNAL_EXCEPTIONS;
     }
 
     private Component createOptionsPanel() {
@@ -494,7 +492,7 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
      * Call this to browse for a custom gradle executor.
      */
     private void browseForCustomGradleExecutor() {
-        File startingDirectory = new File(System.getProperty("user.home"));
+        File startingDirectory = new File(SystemProperties.getUserHome());
         File currentFile = gradlePluginLord.getCustomGradleExecutor();
         if (currentFile != null) {
             startingDirectory = currentFile.getAbsoluteFile();
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
index ad719b8..dcfbf3b 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
@@ -21,7 +21,7 @@ import org.gradle.gradleplugin.foundation.settings.DOM4JSettingsNode;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
 import org.gradle.gradleplugin.userinterface.swing.common.PreferencesAssistant;
 import org.gradle.gradleplugin.userinterface.swing.generic.SinglePaneUIInstance;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import javax.swing.*;
 import javax.swing.filechooser.FileFilter;
@@ -86,7 +86,7 @@ public class Application implements AlternateUIInteraction {
         try {   //try and make it look like a native app
             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
         } catch (Exception e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         this.doesSupportEditingFiles = determineIfSupportsEditingFiles();
@@ -259,13 +259,9 @@ public class Application implements AlternateUIInteraction {
     }
 
     /**
-     * <!===== getFileNameExtension ===========================================> Returns the file extension preserving its case.
-     *
-     * <!      Name       Description>
-     *
+     * Returns the file extension preserving its case.
      * @param fileName the file name
-     * @return its extension.
-     * @author mhunsicker <!=======================================================================>
+     * @return the extension.
      */
     public static String getFileNameExtension(String fileName) {
         String result = fileName;
@@ -358,7 +354,7 @@ public class Application implements AlternateUIInteraction {
          * @param file the file in question
          * @return true to overwrite it, false not to.
          */
-        public boolean confirmOverwritingExisingFile(File file) {
+        public boolean confirmOverwritingExistingFile(File file) {
             return true;   //It's most likely going to exist. Always overwrite it.
         }
 
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
index f31fdc6..42d6433 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.gradleplugin.userinterface.swing.standalone;
 
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 
 import javax.swing.*;
 import java.lang.reflect.InvocationTargetException;
@@ -62,9 +62,9 @@ public class BlockingApplication {
                 }
             });
         } catch (InterruptedException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         } catch (InvocationTargetException e) {
-            throw UncheckedException.asUncheckedException(e.getCause());
+            throw UncheckedException.unwrapAndRethrow(e);
         }
 
         //the calling thread will now block until the caller is complete.
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
index a50cfc6..8d3c486 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
@@ -98,13 +98,13 @@ public class FavoritesEditorWrapper implements FavoritesEditorVersion1 {
     }
 
     public FavoriteTaskVersion1 promptUserToAddFavorite(Window parent) {
-        FavoriteTask favoriteTask = favoritesEditor.addFavorite(new SwingEditFavoriteInteraction(parent, "Add Favorite", true));
+        FavoriteTask favoriteTask = favoritesEditor.addFavorite(new SwingEditFavoriteInteraction(parent, "Add Favorite", SwingEditFavoriteInteraction.SynchronizeType.OnlyIfAlreadySynchronized));
         return convertFavoriteTask(favoriteTask);
     }
 
     public boolean promptUserToEditFavorite(Window parent, FavoriteTaskVersion1 favorite) {
         FavoriteTask favoriteTask = getFavoriteTask(favorite);
-        return favoritesEditor.editFavorite(favoriteTask, new SwingEditFavoriteInteraction(parent, "Edit Favorite", true));
+        return favoritesEditor.editFavorite(favoriteTask, new SwingEditFavoriteInteraction(parent, "Edit Favorite", SwingEditFavoriteInteraction.SynchronizeType.OnlyIfAlreadySynchronized));
     }
 
     public void removeFavorites(List<FavoriteTaskVersion1> favoritesToRemove) {
diff --git a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
index 2c61d76..0e27749 100644
--- a/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
+++ b/subprojects/ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.openapi.wrappers.runner;
 
-import org.gradle.StartParameter;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.logging.ShowStacktrace;
 import org.gradle.openapi.external.runner.GradleRunnerInteractionVersion1;
 
 import java.io.File;
@@ -54,18 +54,18 @@ public class GradleRunnerInteractionWrapper implements ExecuteGradleCommandServe
     /**
      * @return the stack trace level. This determines the detail level of any stack traces should an exception occur.
      */
-    public StartParameter.ShowStacktrace getStackTraceLevel() {
+    public ShowStacktrace getStackTraceLevel() {
         GradleRunnerInteractionVersion1.StackTraceLevel stackTraceLevel = interactionVersion1.getStackTraceLevel();
         switch (stackTraceLevel) {
             case InternalExceptions:
-                return StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+                return ShowStacktrace.INTERNAL_EXCEPTIONS;
             case Always:
-                return StartParameter.ShowStacktrace.ALWAYS;
+                return ShowStacktrace.ALWAYS;
             case AlwaysFull:
-                return StartParameter.ShowStacktrace.ALWAYS_FULL;
+                return ShowStacktrace.ALWAYS_FULL;
         }
 
-        return StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+        return ShowStacktrace.INTERNAL_EXCEPTIONS;
     }
 
     /**
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/add-favorite.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/add-favorite.png
new file mode 100644
index 0000000..3acb57d
Binary files /dev/null and b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/add-favorite.png differ
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close.png
old mode 100644
new mode 100755
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/find.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/find.png
new file mode 100644
index 0000000..1f5a903
Binary files /dev/null and b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/find.png differ
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/next-link.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/next-link.png
new file mode 100644
index 0000000..295264a
Binary files /dev/null and b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/next-link.png differ
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/pin.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/pin.png
new file mode 100644
index 0000000..9c37976
Binary files /dev/null and b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/pin.png differ
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/previous-link.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/previous-link.png
new file mode 100644
index 0000000..0587d91
Binary files /dev/null and b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/previous-link.png differ
diff --git a/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/stop.png b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/stop.png
new file mode 100644
index 0000000..1689f86
Binary files /dev/null and b/subprojects/ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/stop.png differ
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineAssistantTest.groovy b/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineAssistantTest.groovy
deleted file mode 100644
index 8b2b178..0000000
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineAssistantTest.groovy
+++ /dev/null
@@ -1,79 +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.foundation
-
-import spock.lang.Specification
-
-class CommandLineAssistantTest extends Specification {
-    def breaksUpEmptyCommandLineIntoEmptyList() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('') == []
-    }
-
-    def breaksUpWhitespaceOnlyCommandLineIntoEmptyList() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine(' \t ') == []
-    }
-
-    def breaksUpCommandLineIntoSpaceSeparatedArgument() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('a') == ['a']
-        CommandLineAssistant.breakUpCommandLine('a b\tc') == ['a', 'b', 'c']
-    }
-
-    def ignoresExtraWhiteSpaceBetweenArguments() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('  a \t') == ['a']
-        CommandLineAssistant.breakUpCommandLine('a  \t\t b ') == ['a', 'b']
-    }
-
-    def breaksUpCommandLineIntoDoubleQuotedArguments() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('"a b c"') == ['a b c']
-        CommandLineAssistant.breakUpCommandLine('a "b c d" e') == ['a', 'b c d', 'e']
-        CommandLineAssistant.breakUpCommandLine('a "  b c d  "') == ['a', '  b c d  ']
-    }
-
-    def breaksUpCommandLineIntoSingleQuotedArguments() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine("'a b c'") == ['a b c']
-        CommandLineAssistant.breakUpCommandLine("a 'b c d' e") == ['a', 'b c d', 'e']
-        CommandLineAssistant.breakUpCommandLine("a '  b c d  '") == ['a', '  b c d  ']
-    }
-
-    def canHaveEmptyQuotedArgument() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('""') == ['']
-        CommandLineAssistant.breakUpCommandLine("''") == ['']
-    }
-    
-    def canHaveQuoteInsideQuotedArgument() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('"\'quoted\'"') == ['\'quoted\'']
-        CommandLineAssistant.breakUpCommandLine("'\"quoted\"'") == ['"quoted"']
-    }
-
-    def argumentCanHaveQuotedAndUnquotedParts() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('a"b "c') == ['ab c']
-        CommandLineAssistant.breakUpCommandLine("a'b 'c") == ['ab c']
-    }
-
-    def canHaveMissingEndQuote() {
-        expect:
-        CommandLineAssistant.breakUpCommandLine('"a b c') == ['a b c']
-    }
-}
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
index 5f4e06f..ec32f8b 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
@@ -16,7 +16,9 @@
 package org.gradle.foundation;
 
 import junit.framework.TestCase;
-import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.logging.internal.LoggingCommandLineConverter;
+
+import static org.gradle.foundation.CommandLineAssistant.breakUpCommandLine;
 
 /**
  * This tests aspects of command line parsing that the UI does.
@@ -36,27 +38,27 @@ public class CommandLineParsingTest extends TestCase {
         String commandLine = ":build:something -d";
 
         CommandLineAssistant commandLineAssistant = new CommandLineAssistant();
-        String[] arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        String[] arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
 
         //now try it with the log level in the middle
         commandLine = ":build:something -d :clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
 
         //now try it with the log level at the beginning
         commandLine = "-d :clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
 
         //now try it with 'info' instead of debug
         commandLine = "-i :clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
 
         //lastly verify it doesn't inadvertantly detect a log level
         commandLine = ":clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        arguments = breakUpCommandLine(commandLine);
         assertFalse(commandLineAssistant.hasLogLevelDefined(arguments));
     }
 
@@ -70,30 +72,30 @@ public class CommandLineParsingTest extends TestCase {
      */
     public void testOverridingStackTraceLevel() {
         //first try it with the stack trace level at the end
-        String commandLine = ":build:something -" + DefaultCommandLineConverter.FULL_STACKTRACE;
+        String commandLine = ":build:something -" + LoggingCommandLineConverter.FULL_STACKTRACE;
 
         CommandLineAssistant commandLineAssistant = new CommandLineAssistant();
-        String[] arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        String[] arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
 
         //now try it with the stack trace level in the middle
-        commandLine = ":build:something -" + DefaultCommandLineConverter.FULL_STACKTRACE + " :clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        commandLine = ":build:something -" + LoggingCommandLineConverter.FULL_STACKTRACE + " :clean";
+        arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
 
         //now try it with the stack trace level at the beginning
-        commandLine = "-" + DefaultCommandLineConverter.FULL_STACKTRACE + " :clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        commandLine = "-" + LoggingCommandLineConverter.FULL_STACKTRACE + " :clean";
+        arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
 
         //now try it with a different stack trace level
-        commandLine = "-" + DefaultCommandLineConverter.STACKTRACE + " :clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        commandLine = "-" + LoggingCommandLineConverter.STACKTRACE + " :clean";
+        arguments = breakUpCommandLine(commandLine);
         assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
 
         //lastly verify it doesn't inadvertantly detect a stack trace
         commandLine = ":clean";
-        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        arguments = breakUpCommandLine(commandLine);
         assertFalse(commandLineAssistant.hasShowStacktraceDefined(arguments));
     }
 }
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
index 6c8a1b8..cebd902 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
@@ -26,7 +26,7 @@ import org.gradle.gradleplugin.foundation.GradlePluginLord;
 import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
 import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
 import org.gradle.gradleplugin.foundation.request.Request;
-import org.gradle.util.UncheckedException;
+import org.gradle.internal.UncheckedException;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JUnit4Mockery;
 
@@ -268,7 +268,7 @@ public class TestUtility {
          * @param file the file in question
          * @return true to overwrite it, false not to.
          */
-        public boolean confirmOverwritingExisingFile(File file) {
+        public boolean confirmOverwritingExistingFile(File file) {
             return confirmOverwrite;
         }
 
@@ -368,7 +368,7 @@ public class TestUtility {
         try {
             completed = complete.await(maximumWaitValue, maximumWaitUnits);
         } catch (InterruptedException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         gradlePluginLord.removeRequestObserver(observer);
@@ -426,7 +426,7 @@ public class TestUtility {
         try {
             timeout = !complete.await(maximumWaitSeconds, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
-            throw UncheckedException.asUncheckedException(e);
+            throw UncheckedException.throwAsUncheckedException(e);
         }
 
         gradlePluginLord.removeRequestObserver(observer);
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java
new file mode 100644
index 0000000..500de05
--- /dev/null
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/TextBlockSearchEditorTests.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2011 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.foundation;
+
+import junit.framework.TestCase;
+import org.gradle.gradleplugin.foundation.search.BasicTextSearchCriteria;
+import org.gradle.gradleplugin.foundation.search.TextBlockSearchEditor;
+
+/**
+ * Tests for TextBlockSearchEditor
+ *
+ * @author mhunsicker
+ */
+public class TextBlockSearchEditorTests extends TestCase {
+    private final static String TEXT_1 = "blah blah\n" +                       //ends on index 9
+            "error 1: missing matching quote\n" + //ends on index 41
+            "ErRor 2: undefined symbol\n" +       //ends on index 67
+            "\n\nBuild Failed";                   //ends on index 81
+
+    /**
+     * This does a basic search. Something simple expecting matches
+     */
+    public void testBasic() {
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("error");
+        criteria.setCaseSensitive(false);
+
+        int matches = editor.searchAllText(TEXT_1, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 2, matches);   //this tests the matches return result is correct
+
+        TestUtility.assertListContents(editor.getMatchedResults(), new TextBlockSearchEditor.SearchResult("error", 10, 15),
+                new TextBlockSearchEditor.SearchResult("ErRor", 42, 47));
+    }
+
+    /**
+     * This searches with case sensitivity on.
+     */
+    public void testCaseSensitivity() {
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("ErRor");
+        criteria.setCaseSensitive(true);
+
+        int matches = editor.searchAllText(TEXT_1, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 1, matches);   //this tests the matches return result is correct
+
+        TestUtility.assertListContents(editor.getMatchedResults(), new TextBlockSearchEditor.SearchResult("ErRor", 42, 47));
+    }
+
+    /**
+     * Tests making sure the indices are correct. Note: the ending index will always be at least 1 more than the starting index. It's actually the start of the character after the match.
+     */
+    public void testIndices() {
+        String textToSearch = "01234567890123456789";
+
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("2");
+        criteria.setCaseSensitive(false);
+
+        int matches = editor.searchAllText(textToSearch, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 2, matches);   //this tests the matches return result is correct
+
+        TestUtility.assertListContents(editor.getMatchedResults(), new TextBlockSearchEditor.SearchResult("2", 2, 3),
+                new TextBlockSearchEditor.SearchResult("2", 12, 13));
+    }
+
+    /**
+     * Tests with 'null' for search for text. This is just to make sure nothing blows up.
+     */
+    public void testWithNoCriteria() {
+        String textToSearch = "01234567890123456789";
+
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch(null);
+        criteria.setCaseSensitive(false);
+
+        int matches = editor.searchAllText(textToSearch, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 0, matches);   //this tests the matches return result is correct
+
+        assertEquals(0, editor.getMatchedResults().size());
+    }
+
+    /**
+     * Tests with 'null' for the text to search. This is just to make sure nothing blows up.
+     */
+    public void testWithSearchText() {
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("a");
+        criteria.setCaseSensitive(false);
+
+        int matches = editor.searchAllText(null, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 0, matches);   //this tests the matches return result is correct
+
+        assertEquals(0, editor.getMatchedResults().size());
+    }
+
+    /**
+     * Tests doing a search for a blank string after another search. This should change the results. This is specifically testing a bug I discovered where the search results weren't cleared first and
+     * this resulted in getting the previous search results.
+     */
+    public void testSecondSearchBlank() {
+        String textToSearch = "01234567890123456789";
+
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("2");
+        criteria.setCaseSensitive(false);
+
+        int matches = editor.searchAllText(textToSearch, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 2, matches);   //this tests the matches return result is correct
+
+        TestUtility.assertListContents(editor.getMatchedResults(), new TextBlockSearchEditor.SearchResult("2", 2, 3),
+                new TextBlockSearchEditor.SearchResult("2", 12, 13));
+
+        criteria.setTextToMatch("");
+        criteria.setCaseSensitive(false);
+
+        matches = editor.searchAllText(textToSearch, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 0, matches);   //this tests the matches return result is correct
+
+        assertEquals(0, editor.getMatchedResults().size());
+    }
+
+    /**
+     * This does a simple test of searching via a regular expression. This one is case insensitive.
+     */
+    public void testBasicRegularExpressions() {
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("error [1-9]:");
+        criteria.setCaseSensitive(false);
+        criteria.setUseRegularExpressions(true);
+
+        int matches = editor.searchAllText(TEXT_1, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 2, matches);   //this tests the matches return result is correct
+
+        TestUtility.assertListContents(editor.getMatchedResults(), new TextBlockSearchEditor.SearchResult("error 1:", 10, 18),
+                new TextBlockSearchEditor.SearchResult("ErRor 2:", 42, 50));
+    }
+
+    /**
+     * This does a simple test of searching via a regular expression but is case sensitive.
+     */
+    public void testCaseSensitiveRegularExpressions() {
+        TextBlockSearchEditor editor = new TextBlockSearchEditor();
+        BasicTextSearchCriteria criteria = new BasicTextSearchCriteria();
+        criteria.setTextToMatch("error [1-9]:");
+        criteria.setCaseSensitive(true);
+        criteria.setUseRegularExpressions(true);
+
+        int matches = editor.searchAllText(TEXT_1, criteria);
+
+        assertEquals("Failed to find the correct number of matches", 1, matches);   //this tests the matches return result is correct
+
+        TestUtility.assertListContents(editor.getMatchedResults(), new TextBlockSearchEditor.SearchResult("error 1:", 10, 18));
+    }
+}
diff --git a/subprojects/ui/ui.gradle b/subprojects/ui/ui.gradle
index babeb74..32aa5af 100644
--- a/subprojects/ui/ui.gradle
+++ b/subprojects/ui/ui.gradle
@@ -14,26 +14,21 @@
  * limitations under the License.
  */
 
-apply from: "$rootDir/gradle/integTest.gradle"
-
 dependencies {
     compile project(':core')
     compile project(':openApi')
 
-    groovy libraries.groovy_depends
+    groovy libraries.groovy
 
-    compile libraries.dom4j,
-            libraries.commons_io,
-            libraries.slf4j_api
+    compile libraries.dom4j
+    compile libraries.commons_io
+    compile libraries.slf4j_api
 
     runtime libraries.jaxen
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
-    integTestCompile project(path: ':core', configuration: 'integTestFixtures')
-    integTestRuntime project(path: ':core', configuration: 'integTestFixturesRuntime')
 }
 
 test {
     jvmArgs '-Xms128m', '-Xmx256m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
 }
+
+useTestFixtures()
diff --git a/subprojects/website/website.gradle b/subprojects/website/website.gradle
new file mode 100644
index 0000000..1ae1be9
--- /dev/null
+++ b/subprojects/website/website.gradle
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2011 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.
+ */
+import java.util.regex.Pattern
+
+buildscript {
+    dependencies.classpath 'net.java.dev.jets3t:jets3t:0.8.1'
+    repositories.mavenCentral()
+}
+
+apply plugin: "base"
+evaluationDependsOn ":docs"
+
+// fail early if we don't have credentials to connect to the host
+gradle.taskGraph.whenReady { graph ->
+    tasks.withType(S3PutTask).matching { graph.hasTask(it) }.all {
+        accessKey project.gradleS3AccessKey
+        secretKey project.gradleS3SecretKey
+    }
+}
+
+task uploadDistributions(type: S3DistributionFileUpload) {
+    bucketName "downloads.gradle.org"
+    source rootProject.testedDists
+    directory "distributions${-> project.version.release ? "" : "-snapshots"}"
+}
+
+task generateReleasesXml() {
+    ext.outputFile = file("$buildDir/releases.xml")
+    outputs.file outputFile
+    outputs.upToDateWhen { false }
+
+    doLast {
+        project.releases.modifyTo(outputFile) {
+            def releases = release
+
+            current[0] + {
+                def readVersion = { label ->
+                    try {
+                        def text = new URL("http://gradle.org/versions/$label").text
+                        new groovy.json.JsonSlurper().parseText(text)
+                    } catch (FileNotFoundException e) {
+                        // service returns 404 if there is no version with that label,
+                        // so we get a FileNotFoundException from URL.text
+                        null
+                    }
+                }
+
+                // nightly
+                def nightlyVersion
+                def nightlyBuildTime
+                if (this.project.isNightlyBuild()) {
+                    nightlyVersion = this.project.version
+                    nightlyBuildTime = this.project.version.timestamp
+                } else {
+                    def nightlyRemote = readVersion("nightly")
+                    if (nightlyRemote) {
+                        nightlyVersion = nightlyRemote.version
+                        nightlyBuildTime = nightlyRemote.buildTime
+                    }
+                }
+
+                if (nightlyVersion && nightlyBuildTime) {
+                    release(version: nightlyVersion, "build-time": nightlyBuildTime, nightly: true, snapshot: true)
+                }
+
+                // rc
+                if (!this.project.isFinalReleaseBuild()) { // wipe out the rc if we are in a final release
+                    if (this.project.isRcBuild()) {
+                        def nextNode = next[0]
+                        assert nextNode
+                        release(version: this.project.version, "build-time": this.project.version.timestamp, "rc-for": nextNode. at version, snapshot: true)
+                    } else {
+                        def rcRemote = readVersion("release-candidate")
+                        if (rcRemote) {
+                            release(version: rcRemote.version, "build-time": rcRemote.buildTime, "rc-for": rcRemote.rcFor, snapshot: true)
+                        }
+                    }
+                }
+
+                // current
+                def currentVersion
+                def currentBuildTime
+                if (this.project.version.release) {
+                    
+                    /*
+                        We are doing a release build.
+                        We are relying on the project.releases.incrementNextVersion() NOT being called yet.
+                    */
+                    currentVersion = this.project.version
+                    currentBuildTime = this.project.version.timestamp
+                    release(version: currentVersion, "build-time": currentBuildTime, current: true)
+                } else {
+                    def currentRemote = readVersion("current")
+                    currentVersion = currentRemote.version
+
+                    def currentRelease = releases.find { it. at version == currentVersion }
+                    assert currentRelease : "didn't find $currentVersion in source releases.xml"
+                    currentRelease. at current = true
+                }
+            }
+
+            [next, current]*.each { remove(it) }
+        }
+    }
+}
+
+task checkoutRepo(type: Exec) {
+    ext.checkoutDir = file("$buildDir/repo-master")
+    onlyIf { !checkoutDir.exists() }
+    executable "git"
+    args "clone", "git at github.com:gradleware/web.git", checkoutDir
+}
+
+task pushReleasesXml {
+    dependsOn checkoutRepo, generateReleasesXml
+    outputs.upToDateWhen { false }
+    ext.repo = project.file("$project.buildDir/repo-$name")
+
+    doLast {
+        def masterCheckout = project.checkoutRepo.checkoutDir
+        def gitOnMasterCheckout = { Object[] cliArgs ->
+            project.exec {
+                workingDir masterCheckout
+                executable "git"
+                args cliArgs
+            }
+        }
+
+        gitOnMasterCheckout "reset", "--hard", "HEAD"
+        gitOnMasterCheckout "clean", "-f", "-d"
+        gitOnMasterCheckout "pull"
+
+        project.delete repo
+
+        // Gradle copy chokes on the symlinks
+        ant.copy(todir: repo) {
+            fileset(dir: masterCheckout, defaultexcludes: false)
+        }
+
+        def releasesXml = new File(repo, "data/releases.xml")
+        def checkedInReleasesXmlText = releasesXml.text
+        def newReleasesXmlText = generateReleasesXml.outputFile.text
+        if (checkedInReleasesXmlText != newReleasesXmlText) {
+            releasesXml.text = newReleasesXmlText
+
+            def gitOnTaskRepo = { Object[] cliArgs ->
+                project.exec {
+                    workingDir repo
+                    executable "git"
+                    args cliArgs
+                }
+            }
+
+            def message = "updating releases.xml from "
+            if (isFinalReleaseBuild()) {
+                message += "final release build"
+            } else if (isNightlyBuild()) {
+                message += "nightly build"
+            } else if (isRcBuild()) {
+                message += "release-candidate build"
+            } else {
+                message += "adhoc build"
+            }
+
+            gitOnTaskRepo "add", releasesXml.absolutePath
+            gitOnTaskRepo "commit", "-m", "[gradle-build] $message"
+
+            if (!project.hasProperty("noPushReleasesXml")) {
+                gitOnTaskRepo "push"
+            }
+        } else {
+            println "Not pushing new releases.xml to site as there were no changes after generation"
+        }
+    }
+
+}
+
+
+task docsWithAnalytics(type: PatternTransform) {
+    inputs.files project(":docs").docsZip
+    from { zipTree(project(":docs").docsZip.outputs.files.singleFile) }
+    into "$buildDir/$name"
+    transform ".*(?<!(javadoc|groovydoc)/index)\\.html", Pattern.compile("<\\s*/\\s*head\\s*>", Pattern.CASE_INSENSITIVE), """
+            <script type="text/javascript">
+              var _gaq = _gaq || [];
+              _gaq.push(['_setAccount', 'UA-4207603-1']);
+              _gaq.push(['_trackPageview']);
+
+              (function() {
+                var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+                ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+                var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+              })();
+            </script>
+            <script type="text/javascript" language="javascript">llactid=20600</script>
+            <script type="text/javascript" language="javascript" src="http://t3.trackalyzer.com/trackalyze.js"></script>
+        </head>
+    """.trim()
+}
+
+task docsWithAnalyticsZip(type: Zip) {
+    from docsWithAnalytics
+    baseName "online-docs"
+}
+
+task uploadDocs(type: S3DistributionFileUpload) {
+    bucketName "downloads.gradle.org"
+    source docsWithAnalyticsZip
+    directory "online-docs${-> project.version.release ? "" : "-snapshots"}"
+}
+
+task pullDocs {
+    doLast {
+        new URL("http://gradle.org/update-docs/").text
+    }
+}
+
+class PatternTransform extends Sync {
+
+    private transformCounter = 0
+
+    void transform(relativePathPattern, toReplacePattern, replaceWith) {
+        relativePathPattern = compilePattern(relativePathPattern)
+        toReplacePattern = compilePattern(toReplacePattern)
+
+        inputs.property "transform:${transformCounter++}", [
+            relativePathPattern: asMap(relativePathPattern),
+            toReplacePattern: asMap(toReplacePattern),
+            replaceWith: replaceWith
+        ]
+
+        eachFile {
+            if (relativePathPattern.matcher(it.relativePath.toString()).matches()) {
+                it.filter {
+                    toReplacePattern.matcher(it).replaceAll(replaceWith)
+                }
+            }
+        }
+    }
+
+    private asMap(Pattern pattern) {
+        [pattern: pattern.toString(), flags: pattern.flags]
+    }
+
+    protected Pattern compilePattern(pattern) {
+        pattern instanceof Pattern ? pattern : Pattern.compile(pattern)
+    }
+}
+
+import org.jets3t.service.security.AWSCredentials
+import org.jets3t.service.security.ProviderCredentials
+import org.jets3t.service.model.S3Object
+import org.jets3t.service.S3Service
+import org.jets3t.service.impl.rest.httpclient.RestS3Service
+import org.gradle.api.Action
+import org.gradle.listener.ActionBroadcast
+// only needed for doLast block
+import org.jets3t.service.model.StorageObject
+
+class S3DistributionFileUpload extends S3PutTask {
+    @Input directory
+    
+    S3DistributionFileUpload() {
+        bucketName "downloads.gradle.org"
+        eachObject {
+            if (it.key.endsWith(".zip")) {
+                it.contentType = "application/zip"
+            } else {
+                throw new InvalidUserDataException("Don't know content type for file: $it.key")
+            }
+            it.key = "$directory/$name"
+        }
+
+        // S3 incorrectly treats “+” incorrectly as a “ ”
+        // https://forums.aws.amazon.com/thread.jspa?threadID=55746
+        // We compensate by also providing files with spaces (for unencoded “+”) and files with “+” (for encoded “+”)
+        doLast {
+            def service = createService()
+            source.each {
+                def key = "$directory/$it.name"
+                if (key.contains("+")) {
+                    def copyDestination = key.replace("+", " ")
+                    logger.lifecycle "making copy of '$key' to '$copyDestination'"
+                    service.copyObject(bucketName, key, bucketName, new StorageObject(copyDestination), false)
+                }
+            }
+        }
+    }
+}
+
+class S3PutTask extends SourceTask {
+    @Input accessKey
+    @Input secretKey
+    @Input bucketName
+    @Input @Optional friendlyName
+
+    protected ActionBroadcast<S3Object> eachObjects = new ActionBroadcast<S3Object>()
+
+    void eachObject(Action<S3Object> action) {
+        eachObjects.add(action)
+    }
+
+    @TaskAction
+    void put() {
+        def service = createService()
+        source.each { File file ->
+            def s3Object = new S3Object(file)
+            s3Object.addMetadata("gradle-release-date", project.version.timestamp)
+            eachObjects.execute(s3Object)
+            logger.lifecycle "uploading '$file.name' to '$s3Object.key'"
+            service.putObject(bucketName, s3Object)
+        }
+    }
+
+    ProviderCredentials createCredentials() {
+        new AWSCredentials(accessKey, secretKey, friendlyName)
+    }
+
+    S3Service createService() {
+        new RestS3Service(createCredentials())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java b/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
deleted file mode 100644
index 23d5292..0000000
--- a/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
+++ /dev/null
@@ -1,392 +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.api.tasks.wrapper;
-
-import org.gradle.api.DefaultTask;
-import org.gradle.api.GradleException;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.tasks.Input;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.util.DistributionLocator;
-import org.gradle.api.tasks.wrapper.internal.WrapperScriptGenerator;
-import org.gradle.util.*;
-
-import java.io.File;
-import java.net.URL;
-import java.util.Properties;
-
-/**
- * <p>Generates scripts (for *nix and windows) which allow you to build your project with Gradle, without having to
- * install Gradle.
- *
- * <p>When a user executes a wrapper script the first time, the script downloads and installs the appropriate Gradle
- * distribution and runs the build against this downloaded distribution. Any installed Gradle distribution is ignored
- * when using the wrapper scripts.
- *
- * <p>The scripts generated by this task are intended to be committed to your version control system. This task also
- * generates a small {@code gradle-wrapper.jar} bootstrap JAR file and properties file which should also be committed to
- * your VCS. The scripts delegates to this JAR.
- *
- * @author Hans Dockter
- */
-public class Wrapper extends DefaultTask {
-    // Properties used by the gradle-wrapper
-    static final String DISTRIBUTION_URL_PROPERTY = "distributionUrl";
-    static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase";
-    static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase";
-    static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath";
-    static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath";
-
-    public static final String DEFAULT_DISTRIBUTION_PARENT_NAME = "wrapper/dists";
-    public static final String DEFAULT_ARCHIVE_NAME = "gradle";
-    public static final String DEFAULT_ARCHIVE_CLASSIFIER = "bin";
-
-    private String distributionUrl;
-
-    /**
-     * Specifies how the wrapper path should be interpreted.
-     */
-    public enum PathBase {
-        PROJECT, GRADLE_USER_HOME
-    }
-
-    private Object scriptFile;
-    private Object jarFile;
-
-    @Input
-    private String distributionPath;
-
-    @Input
-    private PathBase distributionBase = PathBase.GRADLE_USER_HOME;
-
-    private String archiveName;
-
-    private String archiveClassifier;
-
-    private GradleVersion gradleVersion;
-
-    private String urlRoot;
-
-    @Input
-    private String archivePath;
-
-    @Input
-    private PathBase archiveBase = PathBase.GRADLE_USER_HOME;
-
-    private WrapperScriptGenerator wrapperScriptGenerator = new WrapperScriptGenerator();
-    private final DistributionLocator locator = new DistributionLocator();
-
-    public Wrapper() {
-        scriptFile = "gradlew";
-        jarFile = "gradle/wrapper/gradle-wrapper.jar";
-        distributionPath = DEFAULT_DISTRIBUTION_PARENT_NAME;
-        archiveName = DEFAULT_ARCHIVE_NAME;
-        archiveClassifier = DEFAULT_ARCHIVE_CLASSIFIER;
-        archivePath = DEFAULT_DISTRIBUTION_PARENT_NAME;
-        gradleVersion = GradleVersion.current();
-    }
-
-    @TaskAction
-    void generate() {
-        File jarFileDestination = getJarFile();
-        File propertiesFileDestination = getPropertiesFile();
-        File scriptFileDestination = getScriptFile();
-        FileResolver resolver = getServices().get(FileResolver.class).withBaseDir(
-                scriptFileDestination.getParentFile());
-        String jarFileRelativePath = resolver.resolveAsRelativePath(jarFileDestination);
-        String propertiesFileRelativePath = resolver.resolveAsRelativePath(propertiesFileDestination);
-
-        propertiesFileDestination.delete();
-        jarFileDestination.delete();
-        scriptFileDestination.delete();
-
-        writeProperties(propertiesFileDestination);
-
-        URL jarFileSource = getClass().getResource("/gradle-wrapper.jar");
-        if (jarFileSource == null) {
-            throw new GradleException("Cannot locate wrapper JAR resource.");
-        }
-        GFileUtils.copyURLToFile(jarFileSource, jarFileDestination);
-
-        wrapperScriptGenerator.generate(jarFileRelativePath, propertiesFileRelativePath, scriptFileDestination);
-    }
-
-    private void writeProperties(File propertiesFileDestination) {
-        Properties wrapperProperties = new Properties();
-        wrapperProperties.put(DISTRIBUTION_URL_PROPERTY, getDistributionUrl());
-        wrapperProperties.put(DISTRIBUTION_BASE_PROPERTY, distributionBase.toString());
-        wrapperProperties.put(DISTRIBUTION_PATH_PROPERTY, distributionPath);
-        wrapperProperties.put(ZIP_STORE_BASE_PROPERTY, archiveBase.toString());
-        wrapperProperties.put(ZIP_STORE_PATH_PROPERTY, archivePath);
-        GUtil.saveProperties(wrapperProperties, propertiesFileDestination);
-    }
-
-    /**
-     * Returns the file to write the wrapper script to.
-     */
-    @OutputFile
-    public File getScriptFile() {
-        return getProject().file(scriptFile);
-    }
-
-    public void setScriptFile(Object scriptFile) {
-        this.scriptFile = scriptFile;
-    }
-
-    /**
-     * Returns the script destination path, relative to the project directory.
-     *
-     * @see #setScriptDestinationPath(String)
-     */
-    @Deprecated
-    public String getScriptDestinationPath() {
-        DeprecationLogger.nagUser("Wrapper.getScriptDestinationPath()", "getScriptFile()");
-        return getProject().relativePath(getScriptFile().getParentFile());
-    }
-
-    /**
-     * Specifies a path as the parent dir of the scripts which are generated when executing the wrapper task. This path
-     * specifies a directory <i>relative</i> to the project dir.  Defaults to empty string, i.e. the scripts are placed
-     * into the project root dir.
-     *
-     * @param scriptDestinationPath Any object which <code>toString</code> method specifies the path. Most likely a
-     * String or File object.
-     */
-    @Deprecated
-    public void setScriptDestinationPath(String scriptDestinationPath) {
-        DeprecationLogger.nagUser("Wrapper.setScriptDestinationPath()", "setScriptFile()");
-        setScriptFile(scriptDestinationPath + "/gradlew");
-    }
-
-    /**
-     * Returns the file to write the wrapper jar file to.
-     */
-    @OutputFile
-    public File getJarFile() {
-        return getProject().file(jarFile);
-    }
-
-    public void setJarFile(Object jarFile) {
-        this.jarFile = jarFile;
-    }
-
-    /**
-     * Returns the file to write the wrapper properties to.
-     */
-    @OutputFile
-    public File getPropertiesFile() {
-        File jarFileDestination = getJarFile();
-        return new File(jarFileDestination.getParentFile(), jarFileDestination.getName().replaceAll("\\.jar$",
-                ".properties"));
-    }
-
-    /**
-     * Returns the jar path, relative to the project directory.
-     *
-     * @see #setJarPath(String)
-     */
-    @Deprecated
-    public String getJarPath() {
-        DeprecationLogger.nagUser("Wrapper.getJarPath()", "getJarFile()");
-        return getProject().relativePath(getJarFile().getParentFile());
-    }
-
-    /**
-     * When executing the wrapper task, the jar path specifies the path where the gradle-wrapper.jar is copied to. The
-     * jar path must be a path relative to the project dir. The gradle-wrapper.jar must be submitted to your version
-     * control system. Defaults to empty string, i.e. the jar is placed into the project root dir.
-     */
-    @Deprecated
-    public void setJarPath(String jarPath) {
-        DeprecationLogger.nagUser("Wrapper.setJarPath()", "setJarFile()");
-        setJarFile(jarPath + "/gradle-wrapper.jar");
-    }
-
-    /**
-     * Returns the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
-     * distribution base directory
-     *
-     * @see #setDistributionPath(String)
-     */
-    public String getDistributionPath() {
-        return distributionPath;
-    }
-
-    /**
-     * Sets the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
-     * distribution base directory
-     *
-     * @see #setDistributionPath(String)
-     */
-    public void setDistributionPath(String distributionPath) {
-        this.distributionPath = distributionPath;
-    }
-
-    /**
-     * Returns the gradle version for the wrapper.
-     *
-     * @see #setGradleVersion(String)
-     */
-    public String getGradleVersion() {
-        return gradleVersion.getVersion();
-    }
-
-    /**
-     * The version of the gradle distribution required by the wrapper. This is usually the same version of Gradle you
-     * use for building your project.
-     */
-    public void setGradleVersion(String gradleVersion) {
-        this.gradleVersion = GradleVersion.version(gradleVersion);
-    }
-
-    /**
-     * The URL to download the gradle distribution from.
-     *
-     * <p>If not set, the download URL is assembled by the pattern: <code>[urlRoot]/[archiveName]-[gradleVersion]-[archiveClassifier].zip</code>
-     *
-     * <p>The wrapper downloads a certain distribution only once and caches it. If your distribution base is the
-     * project, you might submit the distribution to your version control system. That way no download is necessary at
-     * all. This might be in particular interesting, if you provide a custom gradle snapshot to the wrapper, because you
-     * don't need to provide a download server then.
-     */
-    @Input
-    public String getDistributionUrl() {
-        if (distributionUrl != null) {
-            return distributionUrl;
-        }
-        return locator.getDistribution(getUrlRoot(), gradleVersion, archiveName, archiveClassifier);
-    }
-
-    public void setDistributionUrl(String url) {
-        this.distributionUrl = url;
-    }
-
-    /**
-     * The base URL to download the gradle distribution from.
-     *
-     * <p>The download URL is assembled by the pattern: <code>[urlRoot]/[archiveName]-[gradleVersion]-[archiveClassifier].zip</code>
-     */
-    @Deprecated
-    public String getUrlRoot() {
-        if (urlRoot != null) {
-            return urlRoot;
-        }
-        return locator.getDistributionRepository(gradleVersion);
-    }
-
-    /**
-     * Sets the base URL to download the gradle distribution from.
-     */
-    @Deprecated
-    public void setUrlRoot(String urlRoot) {
-        DeprecationLogger.nagUser("Wrapper.setUrlRoot()", "setDistributionUrl()");
-        this.urlRoot = urlRoot;
-    }
-
-    /**
-     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
-     * the gradle user home dir.
-     */
-    public PathBase getDistributionBase() {
-        return distributionBase;
-    }
-
-    /**
-     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
-     * the gradle user home dir.
-     */
-    public void setDistributionBase(PathBase distributionBase) {
-        this.distributionBase = distributionBase;
-    }
-
-    /**
-     * Returns the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is
-     * relative to the archive base directory.
-     */
-    public String getArchivePath() {
-        return archivePath;
-    }
-
-    /**
-     * Set's the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is relative
-     * to the parent dir specified with {@link #getArchiveBase()}.
-     */
-    public void setArchivePath(String archivePath) {
-        this.archivePath = archivePath;
-    }
-
-    /**
-     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
-     * gradle user home dir.
-     */
-    public PathBase getArchiveBase() {
-        return archiveBase;
-    }
-
-    /**
-     * The archive base specifies whether the unpacked wrapper distribution should be stored in the project or in the
-     * gradle user home dir.
-     */
-    public void setArchiveBase(PathBase archiveBase) {
-        this.archiveBase = archiveBase;
-    }
-
-    /**
-     * The name of the archive as part of the download URL.
-     *
-     * <p>The download URL is assembled by the pattern: <code>[urlRoot]/[archiveName]-[gradleVersion]-[archiveClassifier].zip</code>
-     *
-     * <p>The default for the archive name is {@value #DEFAULT_ARCHIVE_NAME}.
-     */
-    @Deprecated
-    public String getArchiveName() {
-        return archiveName;
-    }
-
-    @Deprecated
-    public void setArchiveName(String archiveName) {
-        DeprecationLogger.nagUser("Wrapper.setArchiveName()", "setDistributionUrl()");
-        this.archiveName = archiveName;
-    }
-
-    /**
-     * The classifier of the archive as part of the download URL.
-     *
-     * <p>The download URL is assembled by the pattern: <code>[urlRoot]/[archiveName]-[gradleVersion]-[archiveClassifier].zip</code>
-     *
-     * <p>The default for the archive classifier is {@value #DEFAULT_ARCHIVE_CLASSIFIER}.
-     */
-    @Deprecated
-    public String getArchiveClassifier() {
-        return archiveClassifier;
-    }
-
-    @Deprecated
-    public void setArchiveClassifier(String archiveClassifier) {
-        DeprecationLogger.nagUser("Wrapper.setArchiveClassifier()", "setDistributionUrl()");
-        this.archiveClassifier = archiveClassifier;
-    }
-
-    public WrapperScriptGenerator getUnixWrapperScriptGenerator() {
-        return wrapperScriptGenerator;
-    }
-
-    public void setUnixWrapperScriptGenerator(WrapperScriptGenerator wrapperScriptGenerator) {
-        this.wrapperScriptGenerator = wrapperScriptGenerator;
-    }
-}
diff --git a/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/internal/WrapperScriptGenerator.java b/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/internal/WrapperScriptGenerator.java
deleted file mode 100644
index 78504f8..0000000
--- a/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/internal/WrapperScriptGenerator.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.api.tasks.wrapper.internal;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.tools.ant.taskdefs.Chmod;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.util.AntUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringWriter;
-
-/**
- * @author Hans Dockter
- */
-public class WrapperScriptGenerator {
-    public static final String UNIX_NL = "\n";
-    public static final String CURRENT_DIR_UNIX = "`dirname \"$0\"`";
-    public static final String WINDOWS_NL = "\n";
-    public static final String CURRENT_DIR_WINDOWS = "%DIRNAME%";
-    private static final String FULLY_QUALIFIED_WRAPPER_NAME = "org.gradle.wrapper.GradleWrapperMain";
-
-    public void generate(String jarPath, String wrapperPropertiesPath, File scriptFile) {
-        try {
-            createUnixScript(jarPath, scriptFile, wrapperPropertiesPath);
-            createWindowsScript(jarPath, scriptFile, wrapperPropertiesPath);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    private void createUnixScript(String jarPath, File scriptFile, String wrapperPropertiesPath) throws IOException {
-        String unixWrapperScriptHead = IOUtils.toString(getClass().getResourceAsStream("unixWrapperScriptHead.txt"));
-        String unixWrapperScriptTail = IOUtils.toString(getClass().getResourceAsStream("unixWrapperScriptTail.txt"));
-
-        String fillingUnix = "" + UNIX_NL
-                + "STARTER_MAIN_CLASS=" + FULLY_QUALIFIED_WRAPPER_NAME + UNIX_NL
-                + "CLASSPATH=" + CURRENT_DIR_UNIX + "/" + FilenameUtils.separatorsToUnix(jarPath) + UNIX_NL
-                + "WRAPPER_PROPERTIES=" + CURRENT_DIR_UNIX + "/" + FilenameUtils.separatorsToUnix(wrapperPropertiesPath) + UNIX_NL;
-
-        String unixScript = unixWrapperScriptHead + fillingUnix + unixWrapperScriptTail;
-        File unixScriptFile = scriptFile;
-        FileUtils.writeStringToFile(unixScriptFile, unixScript);
-        createExecutablePermission(unixScriptFile);
-    }
-
-    private void createExecutablePermission(File unixScriptFile) {
-        Chmod chmod = new Chmod();
-        chmod.setProject(AntUtil.createProject());
-        chmod.setFile(unixScriptFile);
-        chmod.setPerm("ugo+rx");
-        chmod.execute();
-    }
-
-    private void createWindowsScript(String jarPath, File scriptFile, String wrapperPropertiesPath) throws IOException {
-        String windowsWrapperScriptHead = IOUtils.toString(getClass().getResourceAsStream("windowsWrapperScriptHead.txt"));
-        String windowsWrapperScriptTail = IOUtils.toString(getClass().getResourceAsStream("windowsWrapperScriptTail.txt"));
-        String fillingWindows = "" + WINDOWS_NL
-                + "set STARTER_MAIN_CLASS=" + FULLY_QUALIFIED_WRAPPER_NAME + WINDOWS_NL
-                + "set CLASSPATH=" + CURRENT_DIR_WINDOWS + "\\" + FilenameUtils.separatorsToWindows(jarPath) + WINDOWS_NL
-                + "set WRAPPER_PROPERTIES=" + CURRENT_DIR_WINDOWS + "\\" + FilenameUtils.separatorsToWindows(wrapperPropertiesPath) + WINDOWS_NL;
-        String windowsScript = windowsWrapperScriptHead + fillingWindows + windowsWrapperScriptTail;
-        File windowsScriptFile = new File(scriptFile.getParentFile(), scriptFile.getName() + ".bat");
-        FileUtils.writeStringToFile(windowsScriptFile, transformIntoWindowsNewLines(windowsScript));
-    }
-
-    private String transformIntoWindowsNewLines(String s) {
-        StringWriter writer = new StringWriter();
-        for (char c : s.toCharArray()) {
-            if (c == '\n') {
-                writer.write('\r');
-                writer.write('\n');
-            } else if (c != '\r') {
-                writer.write(c);
-            }
-        }        
-        return writer.toString();
-    }
-}
diff --git a/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/package-info.java b/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/package-info.java
deleted file mode 100644
index 1ddac17..0000000
--- a/subprojects/wrapper/src/main/java/org/gradle/api/tasks/wrapper/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 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.
- */
-
-/**
- * The Gradle wrapper {@link org.gradle.api.Task}.
- */
-package org.gradle.api.tasks.wrapper;
\ No newline at end of file
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
index dae4d04..05a3085 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
@@ -26,7 +26,7 @@ import java.net.URLClassLoader;
 public class BootstrapMainStarter {
     public void start(String[] args, File gradleHome) throws Exception {
         File gradleJar = findLauncherJar(gradleHome);
-        URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()});
+        URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
         Thread.currentThread().setContextClassLoader(contextClassLoader);
         Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
         Method mainMethod = mainClass.getMethod("main", String[].class);
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
index d5e441b..8e661b7 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Download.java
@@ -17,6 +17,8 @@
 package org.gradle.wrapper;
 
 import java.io.*;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
 import java.net.URI;
 import java.net.URL;
 import java.net.URLConnection;
@@ -28,6 +30,16 @@ public class Download implements IDownload {
     private static final int PROGRESS_CHUNK = 20000;
     private static final int BUFFER_SIZE = 10000;
 
+    public Download() {
+        configureProxyAuthentication();
+    }
+
+    private void configureProxyAuthentication() {
+        if (System.getProperty("http.proxyUser") != null) {
+            Authenticator.setDefault(new SystemPropertiesProxyAuthenticator());
+        }
+    }
+
     public void download(URI address, File destination) throws Exception {
         if (destination.exists()) {
             return;
@@ -69,5 +81,13 @@ public class Download implements IDownload {
         }
     }
 
+    private static class SystemPropertiesProxyAuthenticator extends Authenticator {
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            return new PasswordAuthentication(
+                    System.getProperty("http.proxyUser"),
+                    System.getProperty("http.proxyPassword", "").toCharArray());
+        }
+    }
 
 }
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
index bf64543..7f40a52 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
@@ -16,34 +16,72 @@
 
 package org.gradle.wrapper;
 
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.SystemPropertiesCommandLineConverter;
+
 import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.Properties;
 
 /**
  * @author Hans Dockter
  */
 public class GradleWrapperMain {
-    public static final String ALWAYS_UNPACK_ENV = "GRADLE_WRAPPER_ALWAYS_UNPACK";
-    public static final String ALWAYS_DOWNLOAD_ENV = "GRADLE_WRAPPER_ALWAYS_DOWNLOAD";
     public static final String DEFAULT_GRADLE_USER_HOME = System.getProperty("user.home") + "/.gradle";
     public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home";
     public static final String GRADLE_USER_HOME_ENV_KEY = "GRADLE_USER_HOME";
 
     public static void main(String[] args) throws Exception {
-        addSystemProperties(args);
+        File wrapperJar = wrapperJar();
+        File propertiesFile = wrapperProperties(wrapperJar);
+        File rootDir = rootDir(wrapperJar);
+
+        Properties systemProperties = System.getProperties();
+        systemProperties.putAll(parseSystemPropertiesFromArgs(args));
         
-        boolean alwaysDownload = Boolean.parseBoolean(System.getenv(ALWAYS_DOWNLOAD_ENV));
-        boolean alwaysUnpack = Boolean.parseBoolean(System.getenv(ALWAYS_UNPACK_ENV));
+        addSystemProperties(rootDir);
 
-        new Wrapper().execute(
+        WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out);
+        wrapperExecutor.execute(
                 args,
-                new Install(alwaysDownload, alwaysUnpack, new Download(), new PathAssembler(gradleUserHome())),
+                new Install(new Download(), new PathAssembler(gradleUserHome())),
                 new BootstrapMainStarter());
     }
 
-    private static void addSystemProperties(String[] args) {
-        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(args));
+    private static Map<String, String> parseSystemPropertiesFromArgs(String[] args) {
+        SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter();
+        CommandLineParser commandLineParser = new CommandLineParser();
+        converter.configure(commandLineParser);
+        commandLineParser.allowUnknownOptions();
+        return converter.convert(commandLineParser.parse(args));
+    }
+    
+    private static void addSystemProperties(File rootDir) {
         System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(gradleUserHome(), "gradle.properties")));
-        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File("gradle.properties")));
+        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(rootDir, "gradle.properties")));
+    }
+
+    private static File rootDir(File wrapperJar) {
+        return wrapperJar.getParentFile().getParentFile().getParentFile();
+    }
+
+    private static File wrapperProperties(File wrapperJar) {
+        return new File(wrapperJar.getParent(), wrapperJar.getName().replaceFirst("\\.jar$", ".properties"));
+    }
+
+    private static File wrapperJar() {
+        URI location;
+        try {
+            location = GradleWrapperMain.class.getProtectionDomain().getCodeSource().getLocation().toURI();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+        if (!location.getScheme().equals("file")) {
+            throw new RuntimeException(String.format("Cannot determine classpath for wrapper Jar from codebase '%s'.", location));
+        }
+        return new File(location.getPath());
     }
 
     private static File gradleUserHome() {
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java
index b489695..04938ac 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Install.java
@@ -16,12 +16,9 @@
 
 package org.gradle.wrapper;
 
-import org.gradle.util.SystemProperties;
-
 import java.io.*;
 import java.net.URI;
-import java.util.Enumeration;
-import java.util.Locale;
+import java.util.*;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -29,45 +26,65 @@ import java.util.zip.ZipFile;
  * @author Hans Dockter
  */
 public class Install {
+    public static final String DEFAULT_DISTRIBUTION_PATH = "wrapper/dists";
     private final IDownload download;
-    private final boolean alwaysDownload;
-    private final boolean alwaysUnpack;
     private final PathAssembler pathAssembler;
 
-    public Install(boolean alwaysDownload, boolean alwaysUnpack, IDownload download, PathAssembler pathAssembler) {
-        this.alwaysDownload = alwaysDownload;
-        this.alwaysUnpack = alwaysUnpack;
+    public Install(IDownload download, PathAssembler pathAssembler) {
         this.download = download;
         this.pathAssembler = pathAssembler;
     }
 
-    public File createDist(URI distributionUrl, String distBase, String distPath, String zipBase, String zipPath) throws Exception {
-        File gradleHome = pathAssembler.gradleHome(distBase, distPath, distributionUrl);
-        if (!alwaysDownload && !alwaysUnpack && gradleHome.isDirectory()) {
-            return gradleHome;
-        }
-        File localZipFile = pathAssembler.distZip(zipBase, zipPath, distributionUrl);
+    public File createDist(WrapperConfiguration configuration) throws Exception {
+        URI distributionUrl = configuration.getDistribution();
+        boolean alwaysDownload = configuration.isAlwaysDownload();
+        boolean alwaysUnpack = configuration.isAlwaysUnpack();
+
+        PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration);
+
+        File localZipFile = localDistribution.getZipFile();
+        boolean downloaded = false;
         if (alwaysDownload || !localZipFile.exists()) {
             File tmpZipFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".part");
             tmpZipFile.delete();
             System.out.println("Downloading " + distributionUrl);
             download.download(distributionUrl, tmpZipFile);
             tmpZipFile.renameTo(localZipFile);
+            downloaded = true;
         }
-        if (gradleHome.isDirectory()) {
-            System.out.println("Deleting directory " + gradleHome.getAbsolutePath());
-            deleteDir(gradleHome);
+
+        File distDir = localDistribution.getDistributionDir();
+        List<File> dirs = listDirs(distDir);
+
+        if (downloaded || alwaysUnpack || dirs.isEmpty()) {
+            for (File dir : dirs) {
+                System.out.println("Deleting directory " + dir.getAbsolutePath());
+                deleteDir(dir);
+            }
+            System.out.println("Unzipping " + localZipFile.getAbsolutePath() + " to " + distDir.getAbsolutePath());
+            unzip(localZipFile, distDir);
+            dirs = listDirs(distDir);
+            if (dirs.isEmpty()) {
+                throw new RuntimeException(String.format("Gradle distribution '%s' does not contain any directories. Expected to find exactly 1 directory.", distributionUrl));
+            }
+            setExecutablePermissions(dirs.get(0));
+        }
+        if (dirs.size() != 1) {
+            throw new RuntimeException(String.format("Gradle distribution '%s' contains too many directories. Expected to find exactly 1 directory.", distributionUrl));
         }
-        File distDest = gradleHome.getParentFile();
-        System.out.println("Unzipping " + localZipFile.getAbsolutePath() + " to " + distDest.getAbsolutePath());
-        unzip(localZipFile, distDest);
-        if (!gradleHome.isDirectory()) {
-            throw new RuntimeException(String.format(
-                    "Gradle distribution '%s' does not contain expected root directory '%s'.", distributionUrl,
-                    gradleHome.getName()));
+        return dirs.get(0);
+    }
+
+    private List<File> listDirs(File distDir) {
+        List<File> dirs = new ArrayList<File>();
+        if (distDir.exists()) {
+            for (File file : distDir.listFiles()) {
+                if (file.isDirectory()) {
+                    dirs.add(file);
+                }
+            }
         }
-        setExecutablePermissions(gradleHome);
-        return gradleHome;
+        return dirs;
     }
 
     private void setExecutablePermissions(File gradleHome) {
@@ -83,11 +100,12 @@ public class Install {
                 System.out.println("Set executable permissions for: " + gradleCommand.getAbsolutePath());
             } else {
                 BufferedReader is = new BufferedReader(new InputStreamReader(p.getInputStream()));
-                errorMessage = "";
+                Formatter stdout = new Formatter();
                 String line;
                 while ((line = is.readLine()) != null) {
-                    errorMessage += line + SystemProperties.getLineSeparator();
+                    stdout.format("%s%n", line);
                 }
+                errorMessage = stdout.toString();
             }
         } catch (IOException e) {
             errorMessage = e.getMessage();
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
index 2eca50b..eb7b6c9 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
@@ -16,9 +16,9 @@
 package org.gradle.wrapper;
 
 import java.io.File;
+import java.math.BigInteger;
 import java.net.URI;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.security.MessageDigest;
 
 /**
  * @author Hans Dockter
@@ -36,22 +36,40 @@ public class PathAssembler {
         this.gradleUserHome = gradleUserHome;
     }
 
-    public File gradleHome(String distBase, String distPath, URI distUrl) {
-        return new File(getBaseDir(distBase), distPath + "/" + getDistHome(distUrl));
+    /**
+     * Determines the local locations for the distribution to use given the supplied configuration. 
+     */
+    public LocalDistribution getDistribution(WrapperConfiguration configuration) {
+        String baseName = getDistName(configuration.getDistribution());
+        String distName = removeExtension(baseName);
+        String rootDirName = rootDirName(distName, configuration);
+        File distDir = new File(getBaseDir(configuration.getDistributionBase()), configuration.getDistributionPath() + "/" + rootDirName);
+        File distZip = new File(getBaseDir(configuration.getZipBase()), configuration.getZipPath() + "/" + rootDirName + "/" + baseName);
+        return new LocalDistribution(distDir, distZip);
     }
 
-    public File distZip(String zipBase, String zipPath, URI distUrl) {
-        return new File(getBaseDir(zipBase), zipPath + "/" + getDistName(distUrl));
+    private String rootDirName(String distName, WrapperConfiguration configuration) {
+        String urlHash = getMd5Hash(configuration.getDistribution().toString());
+        return String.format("%s/%s", distName, urlHash);
     }
 
-    private String getDistHome(URI distUrl) {
-        String name = getDistName(distUrl);
-        Matcher matcher = Pattern.compile("(\\p{Alpha}+-\\d+\\.\\d+.*?)(-\\p{Alpha}+)?\\.zip").matcher(name);
-        if (!matcher.matches()) {
-            throw new RuntimeException(String.format("Cannot determine Gradle version from distribution URL '%s'.",
-                    distUrl));
+    private String getMd5Hash(String string) {
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+            byte[] bytes = string.getBytes();
+            messageDigest.update(bytes);
+            return new BigInteger(1, messageDigest.digest()).toString(32);
+        } catch (Exception e) {
+            throw new RuntimeException("Could not hash input string.", e);
         }
-        return matcher.group(1);
+    }
+
+    private String removeExtension(String name) {
+        int p = name.lastIndexOf(".");
+        if (p < 0) {
+            return name;
+        }
+        return name.substring(0, p);
     }
 
     private String getDistName(URI distUrl) {
@@ -72,4 +90,28 @@ public class PathAssembler {
             throw new RuntimeException("Base: " + base + " is unknown");
         }
     }
+    
+    public class LocalDistribution {
+        private final File distZip;
+        private final File distDir;
+
+        public LocalDistribution(File distDir, File distZip) {
+            this.distDir = distDir;
+            this.distZip = distZip;
+        }
+
+        /**
+         * Returns the location to install the distribution into.
+         */
+        public File getDistributionDir() {
+            return distDir;
+        }
+
+        /**
+         * Returns the location to install the distribution ZIP file to.
+         */
+        public File getZipFile() {
+            return distZip;
+        }
+    }
 }
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
index 81a5c08..e2d0ed1 100644
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
@@ -28,21 +28,6 @@ import java.util.regex.Pattern;
  * @author Hans Dockter
  */
 public class SystemPropertiesHandler {
-    public static Map<String, String> getSystemProperties(String[] arguments) {
-        Map<String, String> propertyMap = new HashMap<String, String>();
-        Pattern pattern = Pattern.compile("-D([^=]*)=?(.*)");
-        for (String argument : arguments) {
-            Matcher matcher = pattern.matcher(argument);
-            if (matcher.find()) {
-                String key = matcher.group(1);
-                String value = matcher.group(2);
-                if (key.length() > 0) {
-                    propertyMap.put(key, value);
-                }
-            }
-        }
-        return propertyMap;
-    }
 
     public static Map<String, String> getSystemProperties(File propertiesFile) {
         Map<String, String> propertyMap = new HashMap<String, String>();
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Wrapper.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/Wrapper.java
deleted file mode 100644
index 1cd74f1..0000000
--- a/subprojects/wrapper/src/main/java/org/gradle/wrapper/Wrapper.java
+++ /dev/null
@@ -1,98 +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.wrapper;
-
-import java.io.*;
-import java.net.URI;
-import java.util.Properties;
-
-/**
- * @author Hans Dockter
- */
-public class Wrapper {
-    public static final String WRAPPER_PROPERTIES_PROPERTY = "org.gradle.wrapper.properties";
-
-    public static final String DISTRIBUTION_URL_PROPERTY = "distributionUrl";
-    public static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase";
-    public static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase";
-    public static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath";
-    public static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath";
-    private final Properties properties;
-    private final URI distribution;
-    private final File propertiesFile;
-
-    public Wrapper() {
-        this(new File(System.getProperty(WRAPPER_PROPERTIES_PROPERTY)), new Properties());
-    }
-
-    public Wrapper(File projectDir) {
-        this(new File(projectDir, "gradle/wrapper/gradle-wrapper.properties"), new Properties());
-    }
-
-    private Wrapper(File propertiesFile, Properties properties) {
-        this.properties = properties;
-        this.propertiesFile = propertiesFile;
-        if (propertiesFile.exists()) {
-            try {
-                loadProperties(propertiesFile, properties);
-                distribution = new URI(getProperty(DISTRIBUTION_URL_PROPERTY));
-            } catch (Exception e) {
-                throw new RuntimeException(String.format("Could not load wrapper properties from '%s'.", propertiesFile), e);
-            }
-        } else {
-            distribution = null;
-        }
-    }
-
-    private static void loadProperties(File propertiesFile, Properties properties) throws IOException {
-        InputStream inStream = new FileInputStream(propertiesFile);
-        try {
-            properties.load(inStream);
-        } finally {
-            inStream.close();
-        }
-    }
-
-    /**
-     * Returns the distribution which this wrapper will use. Returns null if no wrapper meta-data was found in the specified project directory.
-     */
-    public URI getDistribution() {
-        return distribution;
-    }
-
-    public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
-        if (distribution == null) {
-            throw new FileNotFoundException(String.format("Wrapper properties file '%s' does not exist.", propertiesFile));
-        }
-        File gradleHome = install.createDist(
-                getDistribution(),
-                getProperty(DISTRIBUTION_BASE_PROPERTY),
-                getProperty(DISTRIBUTION_PATH_PROPERTY),
-                getProperty(ZIP_STORE_BASE_PROPERTY),
-                getProperty(ZIP_STORE_PATH_PROPERTY)
-        );
-        bootstrapMainStarter.start(args, gradleHome);
-    }
-
-    private String getProperty(String propertyName) {
-        String value = properties.getProperty(propertyName);
-        if (value == null) {
-            throw new RuntimeException(String.format(
-                    "No value with key '%s' specified in wrapper properties file '%s'.", propertyName, propertiesFile));
-        }
-        return value;
-    }
-}
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java
new file mode 100644
index 0000000..b7b1002
--- /dev/null
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperConfiguration.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 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.wrapper;
+
+import java.net.URI;
+
+public class WrapperConfiguration {
+    public static final String ALWAYS_UNPACK_ENV = "GRADLE_WRAPPER_ALWAYS_UNPACK";
+    public static final String ALWAYS_DOWNLOAD_ENV = "GRADLE_WRAPPER_ALWAYS_DOWNLOAD";
+
+    private boolean alwaysUnpack = Boolean.parseBoolean(System.getenv(ALWAYS_UNPACK_ENV));
+    private boolean alwaysDownload = Boolean.parseBoolean(System.getenv(ALWAYS_DOWNLOAD_ENV));
+    private URI distribution;
+    private String distributionBase = PathAssembler.GRADLE_USER_HOME_STRING;
+    private String distributionPath = Install.DEFAULT_DISTRIBUTION_PATH;
+    private String zipBase = PathAssembler.GRADLE_USER_HOME_STRING;
+    private String zipPath = Install.DEFAULT_DISTRIBUTION_PATH;
+
+    public boolean isAlwaysDownload() {
+        return alwaysDownload;
+    }
+
+    public void setAlwaysDownload(boolean alwaysDownload) {
+        this.alwaysDownload = alwaysDownload;
+    }
+
+    public boolean isAlwaysUnpack() {
+        return alwaysUnpack;
+    }
+
+    public void setAlwaysUnpack(boolean alwaysUnpack) {
+        this.alwaysUnpack = alwaysUnpack;
+    }
+
+    public URI getDistribution() {
+        return distribution;
+    }
+
+    public void setDistribution(URI distribution) {
+        this.distribution = distribution;
+    }
+
+    public String getDistributionBase() {
+        return distributionBase;
+    }
+
+    public void setDistributionBase(String distributionBase) {
+        this.distributionBase = distributionBase;
+    }
+
+    public String getDistributionPath() {
+        return distributionPath;
+    }
+
+    public void setDistributionPath(String distributionPath) {
+        this.distributionPath = distributionPath;
+    }
+
+    public String getZipBase() {
+        return zipBase;
+    }
+
+    public void setZipBase(String zipBase) {
+        this.zipBase = zipBase;
+    }
+
+    public String getZipPath() {
+        return zipPath;
+    }
+
+    public void setZipPath(String zipPath) {
+        this.zipPath = zipPath;
+    }
+}
diff --git a/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java
new file mode 100644
index 0000000..d7e58e7
--- /dev/null
+++ b/subprojects/wrapper/src/main/java/org/gradle/wrapper/WrapperExecutor.java
@@ -0,0 +1,152 @@
+/*
+ * 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.wrapper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Formatter;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+public class WrapperExecutor {
+    public static final String DISTRIBUTION_URL_PROPERTY = "distributionUrl";
+    public static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase";
+    public static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase";
+    public static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath";
+    public static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath";
+    private final Properties properties;
+    private final File propertiesFile;
+    private final Appendable warningOutput;
+    private final WrapperConfiguration config = new WrapperConfiguration();
+
+    public static WrapperExecutor forProjectDirectory(File projectDir, Appendable warningOutput) {
+        return new WrapperExecutor(new File(projectDir, "gradle/wrapper/gradle-wrapper.properties"), new Properties(), warningOutput);
+    }
+
+    public static WrapperExecutor forWrapperPropertiesFile(File propertiesFile, Appendable warningOutput) {
+        if (!propertiesFile.exists()) {
+            throw new RuntimeException(String.format("Wrapper properties file '%s' does not exist.", propertiesFile));
+        }
+        return new WrapperExecutor(propertiesFile, new Properties(), warningOutput);
+    }
+
+    WrapperExecutor(File propertiesFile, Properties properties, Appendable warningOutput) {
+        this.properties = properties;
+        this.propertiesFile = propertiesFile;
+        this.warningOutput = warningOutput;
+        if (propertiesFile.exists()) {
+            try {
+                loadProperties(propertiesFile, properties);
+                config.setDistribution(prepareDistributionUri());
+                config.setDistributionBase(getProperty(DISTRIBUTION_BASE_PROPERTY, config.getDistributionBase()));
+                config.setDistributionPath(getProperty(DISTRIBUTION_PATH_PROPERTY, config.getDistributionPath()));
+                config.setZipBase(getProperty(ZIP_STORE_BASE_PROPERTY, config.getZipBase()));
+                config.setZipPath(getProperty(ZIP_STORE_PATH_PROPERTY, config.getZipPath()));
+            } catch (Exception e) {
+                throw new RuntimeException(String.format("Could not load wrapper properties from '%s'.", propertiesFile), e);
+            }
+        }
+    }
+
+    private URI prepareDistributionUri() throws URISyntaxException {
+        URI source = readDistroUrl();
+        if (source.getScheme() == null) {
+            //no scheme means someone passed a relative url. In our context only file relative urls make sense.
+            return new File(propertiesFile.getParentFile(), source.getSchemeSpecificPart()).toURI();
+        } else {
+            return source;
+        }
+    }
+
+    private URI readDistroUrl() throws URISyntaxException {
+        if (properties.getProperty(DISTRIBUTION_URL_PROPERTY) != null) {
+            return new URI(getProperty(DISTRIBUTION_URL_PROPERTY));
+        }
+        //try the deprecated way:
+        return readDistroUrlDeprecatedWay();
+    }
+
+    private URI readDistroUrlDeprecatedWay() throws URISyntaxException {
+        String distroUrl = null;
+        try {
+            distroUrl = getProperty("urlRoot") + "/"
+                    + getProperty("distributionName") + "-"
+                    + getProperty("distributionVersion") + "-"
+                    + getProperty("distributionClassifier") + ".zip";
+            Formatter formatter = new Formatter();
+            formatter.format("Wrapper properties file '%s' contains deprecated entries 'urlRoot', 'distributionName', 'distributionVersion' and 'distributionClassifier'. These will be removed soon. Please use '%s' instead.%n", propertiesFile, DISTRIBUTION_URL_PROPERTY);
+            warningOutput.append(formatter.toString());
+        } catch (Exception e) {
+            //even the deprecated properties are not provided, report error:
+            reportMissingProperty(DISTRIBUTION_URL_PROPERTY);
+        }
+        return new URI(distroUrl);
+    }
+
+    private static void loadProperties(File propertiesFile, Properties properties) throws IOException {
+        InputStream inStream = new FileInputStream(propertiesFile);
+        try {
+            properties.load(inStream);
+        } finally {
+            inStream.close();
+        }
+    }
+
+    /**
+     * Returns the distribution which this wrapper will use. Returns null if no wrapper meta-data was found in the specified project directory.
+     */
+    public URI getDistribution() {
+        return config.getDistribution();
+    }
+
+    /**
+     * Returns the configuration for this wrapper.
+     */
+    public WrapperConfiguration getConfiguration() {
+        return config;
+    }
+
+    public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
+        File gradleHome = install.createDist(config);
+        bootstrapMainStarter.start(args, gradleHome);
+    }
+
+    private String getProperty(String propertyName) {
+        return getProperty(propertyName, null);
+    }
+
+    private String getProperty(String propertyName, String defaultValue) {
+        String value = properties.getProperty(propertyName);
+        if (value != null) {
+            return value;
+        }
+        if (defaultValue != null) {
+            return defaultValue;
+        }
+        return reportMissingProperty(propertyName);
+    }
+
+    private String reportMissingProperty(String propertyName) {
+        throw new RuntimeException(String.format(
+                "No value with key '%s' specified in wrapper properties file '%s'.", propertyName, propertiesFile));
+    }
+}
diff --git a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/unixWrapperScriptHead.txt b/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/unixWrapperScriptHead.txt
deleted file mode 100644
index 116fae7..0000000
--- a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/unixWrapperScriptHead.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/bin/bash
-
-##############################################################################
-##                                                                          ##
-##  Gradle wrapper script for UN*X                                         ##
-##                                                                          ##
-##############################################################################
-
-# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
-# GRADLE_OPTS="$GRADLE_OPTS -Xmx512m"
-# JAVA_OPTS="$JAVA_OPTS -Xmx512m"
-
-GRADLE_APP_NAME=Gradle
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# Attempt to set JAVA_HOME if it's not already set.
-if [ -z "$JAVA_HOME" ] ; then
-    if $darwin ; then
-        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
-        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
-    else
-        javaExecutable="`which javac`"
-        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
-        # readlink(1) is not available as standard on Solaris 10.
-        readLink=`which readlink`
-        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
-        javaExecutable="`readlink -f \"$javaExecutable\"`"
-        javaHome="`dirname \"$javaExecutable\"`"
-        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
-        export JAVA_HOME="$javaHome"
-    fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
diff --git a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/unixWrapperScriptTail.txt b/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/unixWrapperScriptTail.txt
deleted file mode 100644
index d86da84..0000000
--- a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/unixWrapperScriptTail.txt
+++ /dev/null
@@ -1,97 +0,0 @@
-# Determine the Java command to use to start the JVM.
-if [ -z "$JAVACMD" ] ; then
-    if [ -n "$JAVA_HOME" ] ; then
-        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-            # IBM's JDK on AIX uses strange locations for the executables
-            JAVACMD="$JAVA_HOME/jre/sh/java"
-        else
-            JAVACMD="$JAVA_HOME/bin/java"
-        fi
-    else
-        JAVACMD="java"
-    fi
-fi
-if [ ! -x "$JAVACMD" ] ; then
-    die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-if [ -z "$JAVA_HOME" ] ; then
-    warn "JAVA_HOME environment variable is not set"
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
-if $darwin; then
-    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
-# we may also want to set -Xdock:image
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done 
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-GRADLE_APP_BASE_NAME=`basename "$0"`
-
-exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
-        -classpath "$CLASSPATH" \
-        -Dorg.gradle.appname="$GRADLE_APP_BASE_NAME" \
-        -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \
-        $STARTER_MAIN_CLASS \
-        "$@"
diff --git a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/windowsWrapperScriptHead.txt b/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/windowsWrapperScriptHead.txt
deleted file mode 100644
index 5e53a46..0000000
--- a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/windowsWrapperScriptHead.txt
+++ /dev/null
@@ -1,57 +0,0 @@
- at if "%DEBUG%" == "" @echo off
- at rem ##########################################################################
- at rem                                                                         ##
- at rem  Gradle startup script for Windows                                      ##
- at rem                                                                         ##
- at rem ##########################################################################
-
- at rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
- at rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
- at rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512m
- at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512m
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.\
-
- at rem Find java.exe
-set JAVA_EXE=java.exe
-if not defined JAVA_HOME goto init
-
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-echo.
-goto end
-
-:init
- at rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
- at rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
- at rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
- at rem Setup the command line
diff --git a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/windowsWrapperScriptTail.txt b/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/windowsWrapperScriptTail.txt
deleted file mode 100644
index 45deb2d..0000000
--- a/subprojects/wrapper/src/main/resources/org/gradle/api/tasks/wrapper/internal/windowsWrapperScriptTail.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-
-set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%"
-
- at rem Execute Gradle
-"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
-
-:end
- at rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
-
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
-exit /b "%ERRORLEVEL%"
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
\ No newline at end of file
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java b/subprojects/wrapper/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
deleted file mode 100644
index d868c32..0000000
--- a/subprojects/wrapper/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
+++ /dev/null
@@ -1,197 +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.api.tasks.wrapper;
-
-import org.gradle.api.internal.AbstractTask;
-import org.gradle.api.tasks.AbstractTaskTest;
-import org.gradle.api.tasks.wrapper.internal.WrapperScriptGenerator;
-import org.gradle.util.*;
-import org.gradle.wrapper.GradleWrapperMain;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(org.jmock.integration.junit4.JMock.class)
-public class WrapperTest extends AbstractTaskTest {
-
-    private Wrapper wrapper;
-    private WrapperScriptGenerator wrapperScriptGeneratorMock;
-    private String targetWrapperJarPath;
-    private Mockery context = new Mockery();
-    private TestFile expectedTargetWrapperJar;
-    private File expectedTargetWrapperProperties;
-    @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-
-    @Before
-    public void setUp() {
-        super.setUp();
-        context.setImposteriser(ClassImposteriser.INSTANCE);
-        wrapper = createTask(Wrapper.class);
-        wrapperScriptGeneratorMock = context.mock(WrapperScriptGenerator.class);
-        wrapper.setScriptDestinationPath("scriptDestination");
-        wrapper.setGradleVersion("1.0");
-        targetWrapperJarPath = "jarPath";
-        expectedTargetWrapperJar = new TestFile(getProject().getProjectDir(),
-                targetWrapperJarPath + "/gradle-wrapper.jar");
-        expectedTargetWrapperProperties = new File(getProject().getProjectDir(),
-                targetWrapperJarPath + "/gradle-wrapper.properties");
-        new File(getProject().getProjectDir(), targetWrapperJarPath).mkdirs();
-        wrapper.setJarPath(targetWrapperJarPath);
-        wrapper.setDistributionPath("somepath");
-        wrapper.setUnixWrapperScriptGenerator(wrapperScriptGeneratorMock);
-    }
-
-    public AbstractTask getTask() {
-        return wrapper;
-    }
-
-    @Test
-    public void testWrapperDefaults() {
-        wrapper = createTask(Wrapper.class);
-        assertEquals(new File(getProject().getProjectDir(), "gradle/wrapper/gradle-wrapper.jar"), wrapper.getJarFile());
-        assertEquals(toNative("gradle/wrapper"), wrapper.getJarPath());
-        assertEquals(new File(getProject().getProjectDir(), "gradlew"), wrapper.getScriptFile());
-        assertEquals(".", wrapper.getScriptDestinationPath());
-        assertEquals(GradleVersion.current().getVersion(), wrapper.getGradleVersion());
-        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getDistributionPath());
-        assertEquals(Wrapper.DEFAULT_ARCHIVE_NAME, wrapper.getArchiveName());
-        assertEquals(Wrapper.DEFAULT_ARCHIVE_CLASSIFIER, wrapper.getArchiveClassifier());
-        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getArchivePath());
-        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getDistributionBase());
-        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getArchiveBase());
-        assertNotNull(wrapper.getUrlRoot());
-        assertNotNull(wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromReleaseRepositoryForReleaseVersions() {
-        wrapper.setGradleVersion("0.9.1");
-        assertEquals("http://repo.gradle.org/gradle/distributions", wrapper.getUrlRoot());
-        assertEquals("http://repo.gradle.org/gradle/distributions/gradle-0.9.1-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromReleaseRepositoryForPreviewReleaseVersions() {
-        wrapper.setGradleVersion("1.0-milestone-1");
-        assertEquals("http://repo.gradle.org/gradle/distributions", wrapper.getUrlRoot());
-        assertEquals("http://repo.gradle.org/gradle/distributions/gradle-1.0-milestone-1-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromOldReleaseRepositoryForPre09ReleaseVersions() {
-        wrapper.setGradleVersion("0.9-rc-3");
-        assertEquals("http://dist.codehaus.org/gradle", wrapper.getUrlRoot());
-        assertEquals("http://dist.codehaus.org/gradle/gradle-0.9-rc-3-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromSnapshotRepositoryForSnapshotVersions() {
-        wrapper.setGradleVersion("0.9.1-20101224110000+1100");
-        assertEquals("http://repo.gradle.org/gradle/distributions/gradle-snapshots", wrapper.getUrlRoot());
-        assertEquals("http://repo.gradle.org/gradle/distributions/gradle-snapshots/gradle-0.9.1-20101224110000+1100-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testDownloadsFromOldSnapshotRepositoryForPre09SnapshotVersions() {
-        wrapper.setGradleVersion("0.9-20101224110000+1100");
-        assertEquals("http://snapshots.dist.codehaus.org/gradle", wrapper.getUrlRoot());
-        assertEquals("http://snapshots.dist.codehaus.org/gradle/gradle-0.9-20101224110000+1100-bin.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testUsesExplicitlyDefinedDistributionUrl() {
-        wrapper.setGradleVersion("0.9");
-        wrapper.setDistributionUrl("http://some-url");
-        assertEquals("http://repo.gradle.org/gradle/distributions", wrapper.getUrlRoot());
-        assertEquals("http://some-url", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testAssemblesDistributionUrlFromPartsIfNotSpecified() {
-        wrapper.setGradleVersion("0.9");
-        wrapper.setUrlRoot("http://some-url");
-        wrapper.setArchiveName("archive");
-        wrapper.setArchiveClassifier("all");
-        assertEquals("http://some-url/archive-0.9-all.zip", wrapper.getDistributionUrl());
-    }
-
-    @Test
-    public void testExecuteWithNonExistingWrapperJarParentDir() throws IOException {
-        checkExecute();
-    }
-
-    @Test
-    public void testCheckInputs() throws IOException {
-        assertThat(wrapper.getInputs().getProperties().keySet(),
-                equalTo(WrapUtil.toSet("distributionBase", "distributionPath", "distributionUrl", "archiveBase", "archivePath")));
-    }
-
-    @Test
-    public void testExecuteWithExistingWrapperJarParentDirAndExistingWrapperJar() throws IOException {
-        File jarDir = new File(getProject().getProjectDir(), "lib");
-        jarDir.mkdirs();
-        File wrapperJar = new File(getProject().getProjectDir(), targetWrapperJarPath);
-        File parentFile = expectedTargetWrapperJar.getParentFile();
-        assertTrue(parentFile.isDirectory() || parentFile.mkdirs());
-        try {
-            assertTrue(expectedTargetWrapperJar.createNewFile());
-        } catch (IOException e) {
-            throw new RuntimeException(String.format("Could not create %s.", wrapperJar), e);
-        }
-        checkExecute();
-    }
-
-    private void checkExecute() throws IOException {
-        context.checking(new Expectations() {
-            {
-                one(wrapperScriptGeneratorMock).generate(
-                        toNative("../" + targetWrapperJarPath + "/gradle-wrapper.jar"),
-                        toNative("../" + targetWrapperJarPath + "/gradle-wrapper.properties"),
-                        wrapper.getScriptFile());
-            }
-        });
-        wrapper.execute();
-        TestFile unjarDir = tmpDir.createDir("unjar");
-        expectedTargetWrapperJar.unzipTo(unjarDir);
-        unjarDir.file(GradleWrapperMain.class.getName().replace(".", "/") + ".class").assertIsFile();
-        Properties properties = GUtil.loadProperties(expectedTargetWrapperProperties);
-        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_URL_PROPERTY), wrapper.getDistributionUrl());
-        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_BASE_PROPERTY), wrapper.getDistributionBase().toString());
-        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_PATH_PROPERTY), wrapper.getDistributionPath());
-        assertEquals(properties.getProperty(Wrapper.ZIP_STORE_BASE_PROPERTY), wrapper.getArchiveBase().toString());
-        assertEquals(properties.getProperty(Wrapper.ZIP_STORE_PATH_PROPERTY), wrapper.getArchivePath());
-    }
-
-    private String toNative(String s) {
-        return s.replace("/", File.separator);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
index 6f91633..e437c0c 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
@@ -16,54 +16,55 @@
 
 package org.gradle.wrapper
 
-import org.gradle.api.tasks.wrapper.Wrapper.PathBase
 import org.gradle.util.TemporaryFolder
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import static org.junit.Assert.assertEquals
+import spock.lang.Specification
+import org.gradle.util.TestFile
 
 /**
  * @author Hans Dockter
  */
-class InstallTest {
+class InstallTest extends Specification {
     File testDir
     Install install
-    String testZipBase
-    String testZipPath
-    String testDistBase
-    String testDistPath
-    URI testDistUrl
     IDownload downloadMock
     PathAssembler pathAssemblerMock;
     boolean downloadCalled
     File zip
-    File distributionDir
+    TestFile distributionDir
     File zipStore
-    File gradleScript
-    File gradleHomeDir
-    File zipDestination
+    TestFile gradleHomeDir
+    TestFile zipDestination
+    WrapperConfiguration configuration = new WrapperConfiguration()
+    IDownload download = Mock()
+    PathAssembler pathAssembler = Mock()
+    PathAssembler.LocalDistribution localDistribution = Mock()
     @Rule
     public TemporaryFolder tmpDir = new TemporaryFolder();
 
-    @Before public void setUp() {
+    @Before public void setup() {
         downloadCalled = false
         testDir = tmpDir.dir
-        testZipBase = PathBase.PROJECT.toString()
-        testZipPath = 'someZipPath'
-        testDistBase = PathBase.GRADLE_USER_HOME.toString()
-        testDistPath = 'someDistPath'
-        testDistUrl = new URI('http://server/gradle-0.9.zip')
-        distributionDir = new File(testDir, testDistPath)
-        gradleHomeDir = new File(distributionDir, 'gradle-0.9')
+        configuration.zipBase = PathAssembler.PROJECT_STRING
+        configuration.zipPath = 'someZipPath'
+        configuration.distributionBase = PathAssembler.GRADLE_USER_HOME_STRING
+        configuration.distributionPath = 'someDistPath'
+        configuration.distribution = new URI('http://server/gradle-0.9.zip')
+        configuration.alwaysDownload = false
+        configuration.alwaysUnpack = false
+        distributionDir = new TestFile(testDir, 'someDistPath')
+        gradleHomeDir = new TestFile(distributionDir, 'gradle-0.9')
         zipStore = new File(testDir, 'zips');
-        zipDestination = new File(zipStore, 'gradle-0.9.zip')
-        install = new Install(false, false, createDownloadMock(), createPathAssemblerMock())
+        zipDestination = new TestFile(zipStore, 'gradle-0.9.zip')
+        install = new Install(download, pathAssembler)
     }
 
     IDownload createDownloadMock() {
         [download: {URI url, File destination ->
-            assertEquals(testDistUrl, url)
+            assertEquals(configuration.distribution, url)
             assertEquals(zipDestination.getAbsolutePath() + '.part', destination.getAbsolutePath())
             zip = createTestZip()
             downloadCalled = true
@@ -72,83 +73,109 @@ class InstallTest {
 
     PathAssembler createPathAssemblerMock() {
         [gradleHome: {String distBase, String distPath, URI distUrl ->
-            assertEquals(testDistBase, distBase)
-            assertEquals(testDistPath, distPath)
-            assertEquals(testDistUrl, distUrl)
+            assertEquals(configuration.distributionBase, distBase)
+            assertEquals(configuration.distributionPath, distPath)
+            assertEquals(configuration.distribution, distUrl)
             gradleHomeDir},
          distZip: { String zipBase, String zipPath, URI distUrl ->
-            assertEquals(testZipBase, zipBase)
-            assertEquals(testZipPath, zipPath)
-             assertEquals(testDistUrl, distUrl)
+            assertEquals(configuration.zipBase, zipBase)
+            assertEquals(configuration.zipPath, zipPath)
+             assertEquals(configuration.distribution, distUrl)
             zipDestination
         }] as PathAssembler
     }
 
-    @Test public void testInit() {
-        assert !install.alwaysDownload
-        assert !install.alwaysUnpack
-    }
-
-    File createTestZip() {
-        File explodedZipDir = new File(testDir, 'explodedZip')
-        File binDir = new File(explodedZipDir, 'bin')
-        binDir.mkdirs()
-        gradleScript = new File(binDir, 'gradle')
+    void createTestZip(File zipDestination) {
+        TestFile explodedZipDir = tmpDir.createDir('explodedZip')
+        TestFile gradleScript = explodedZipDir.file('gradle-0.9/bin/gradle')
+        gradleScript.parentFile.createDir()
         gradleScript.write('something')
-        zipStore.mkdirs()
-        AntBuilder antBuilder = new AntBuilder()
-        antBuilder.zip(destfile: zipDestination.absolutePath + '.part') {
-            zipfileset(dir: explodedZipDir, prefix: 'gradle-0.9')
-        }
-        (zipDestination.absolutePath + '.part') as File
+        explodedZipDir.zipTo(new TestFile(zipDestination))
     }
 
-    @Test public void testCreateDist() {
-        assertEquals(gradleHomeDir, install.createDist(testDistUrl, testDistBase, testDistPath, testZipBase, testZipPath))
-        assert downloadCalled
-        assert distributionDir.isDirectory()
-        assert zipDestination.exists()
-        assert gradleScript.exists()
-//        assert new File(gradleHomeDir, "bin/gradle").canExecute()
-    }
+    public void testCreateDist() {
+        when:
+        def homeDir = install.createDist(configuration)
+
+        then:
+        homeDir == gradleHomeDir
+        gradleHomeDir.assertIsDir()
+        gradleHomeDir.file("bin/gradle").assertIsFile()
+        zipDestination.assertIsFile()
 
-    @Test public void testCreateDistWithExistingRoot() {
-        distributionDir.mkdirs()
-        install.createDist(testDistUrl, testDistBase, testDistPath, testZipBase, testZipPath)
-        assert downloadCalled
-        assert gradleHomeDir.isDirectory()
-        assert gradleScript.exists()
+        and:
+        1 * pathAssembler.getDistribution(configuration) >> localDistribution
+        _ * localDistribution.distributionDir >> distributionDir
+        _ * localDistribution.zipFile >> zipDestination
+        1 * download.download(configuration.distribution, _) >> { createTestZip(it[1]) }
+        0 * download._
     }
 
-    @Test public void testCreateDistWithExistingDist() {
-        gradleHomeDir.mkdirs()
-        long lastModified = gradleHomeDir.lastModified()
-        install.createDist(testDistUrl, testDistBase, testDistPath, testZipBase, testZipPath)
-        assert !downloadCalled
-        assert lastModified == gradleHomeDir.lastModified()
+    @Test public void testCreateDistWithExistingDistribution() {
+        given:
+        zipDestination.createFile()
+        gradleHomeDir.file('some-file').createFile()
+        gradleHomeDir.createDir()
+
+        when:
+        def homeDir = install.createDist(configuration)
+
+        then:
+        homeDir == gradleHomeDir
+        gradleHomeDir.assertIsDir()
+        gradleHomeDir.file('some-file').assertIsFile()
+        zipDestination.assertIsFile()
+
+        and:
+        1 * pathAssembler.getDistribution(configuration) >> localDistribution
+        _ * localDistribution.distributionDir >> distributionDir
+        _ * localDistribution.zipFile >> zipDestination
+        0 * download._
     }
 
     @Test public void testCreateDistWithExistingDistAndZipAndAlwaysUnpackTrue() {
-        install = new Install(false, true, createDownloadMock(), createPathAssemblerMock())
-        createTestZip().renameTo(zipDestination)
-        gradleHomeDir.mkdirs()
-        File testFile = new File(gradleHomeDir, 'testfile')
-        install.createDist(testDistUrl, testDistBase, testDistPath, testZipBase, testZipPath)
-        assert distributionDir.isDirectory()
-        assert gradleScript.exists()
-        assert !testFile.exists()
-        assert !downloadCalled
+        given:
+        createTestZip(zipDestination)
+        gradleHomeDir.file('garbage').createFile()
+        configuration.alwaysUnpack = true
+
+        when:
+        def homeDir = install.createDist(configuration)
+
+        then:
+        homeDir == gradleHomeDir
+        gradleHomeDir.assertIsDir()
+        gradleHomeDir.file('garbage').assertDoesNotExist()
+        zipDestination.assertIsFile()
+
+        and:
+        1 * pathAssembler.getDistribution(configuration) >> localDistribution
+        _ * localDistribution.distributionDir >> distributionDir
+        _ * localDistribution.zipFile >> zipDestination
+        0 * download._
     }
 
     @Test public void testCreateDistWithExistingZipAndDistAndAlwaysDownloadTrue() {
-        install = new Install(true, false, createDownloadMock(), createPathAssemblerMock())
-        createTestZip().renameTo(zipDestination)
-        distributionDir.mkdirs()
-        File testFile = new File(gradleHomeDir, 'testfile')
-        install.createDist(testDistUrl, testDistBase, testDistPath, testZipBase, testZipPath)
-        assert gradleHomeDir.isDirectory()
-        assert gradleScript.exists()
-        assert !testFile.exists()
-        assert downloadCalled
+        given:
+        createTestZip(zipDestination)
+        gradleHomeDir.file('garbage').createFile()
+        configuration.alwaysDownload = true
+
+        when:
+        def homeDir = install.createDist(configuration)
+
+        then:
+        homeDir == gradleHomeDir
+        gradleHomeDir.assertIsDir()
+        gradleHomeDir.file("bin/gradle").assertIsFile()
+        gradleHomeDir.file('garbage').assertDoesNotExist()
+        zipDestination.assertIsFile()
+
+        and:
+        1 * pathAssembler.getDistribution(configuration) >> localDistribution
+        _ * localDistribution.distributionDir >> distributionDir
+        _ * localDistribution.zipFile >> zipDestination
+        1 * download.download(configuration.distribution, _) >> { createTestZip(it[1]) }
+        0 * download._
     }
 }
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
index 12a3a63..1e8adbf 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
@@ -15,11 +15,14 @@
  */
 package org.gradle.wrapper;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.io.File;
 import java.net.URI;
 
+import static org.gradle.util.Matchers.matchesRegexp;
+import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.*;
 
 /**
@@ -28,67 +31,67 @@ import static org.junit.Assert.*;
 public class PathAssemblerTest {
     public static final String TEST_GRADLE_USER_HOME = "someUserHome";
     private PathAssembler pathAssembler = new PathAssembler(new File(TEST_GRADLE_USER_HOME));
+    final WrapperConfiguration configuration = new WrapperConfiguration();
 
-    @Test
-    public void gradleHomeWithGradleUserHomeBase() throws Exception {
-        File gradleHome = pathAssembler.gradleHome(PathAssembler.GRADLE_USER_HOME_STRING, "somePath", new URI("http://server/dist/gradle-0.9-bin.zip"));
-        assertEquals(file(TEST_GRADLE_USER_HOME + "/somePath/gradle-0.9"), gradleHome);
+    @Before
+    public void setup() {
+        configuration.setDistributionBase(PathAssembler.GRADLE_USER_HOME_STRING);
+        configuration.setDistributionPath("somePath");
+        configuration.setZipBase(PathAssembler.GRADLE_USER_HOME_STRING);
+        configuration.setZipPath("somePath");
     }
-
+    
     @Test
-    public void gradleHomeWithProjectBase() throws Exception {
-        File gradleHome = pathAssembler.gradleHome(PathAssembler.PROJECT_STRING, "somePath", new URI("http://server/dist/gradle-0.9-bin.zip"));
-        assertEquals(file(currentDirPath() + "/somePath/gradle-0.9"), gradleHome);
+    public void distributionDirWithGradleUserHomeBase() throws Exception {
+        configuration.setDistribution(new URI("http://server/dist/gradle-0.9-bin.zip"));
+        
+        File distributionDir = pathAssembler.getDistribution(configuration).getDistributionDir();
+        assertThat(distributionDir.getName(), matchesRegexp("[a-z0-9]+"));
+        assertThat(distributionDir.getParentFile(), equalTo(file(TEST_GRADLE_USER_HOME + "/somePath/gradle-0.9-bin")));
     }
 
     @Test
-    public void gradleHomeForUriWithNoPath() throws Exception {
-        File gradleHome = pathAssembler.gradleHome(PathAssembler.GRADLE_USER_HOME_STRING, "somePath", new URI("http://server/gradle-0.9-bin.zip"));
-        assertEquals(file(TEST_GRADLE_USER_HOME + "/somePath/gradle-0.9"), gradleHome);
-    }
+    public void distributionDirWithProjectBase() throws Exception {
+        configuration.setDistributionBase(PathAssembler.PROJECT_STRING);
+        configuration.setDistribution(new URI("http://server/dist/gradle-0.9-bin.zip"));
 
-    @Test
-    public void gradleHomeForSnapshotVersion() throws Exception {
-        File gradleHome = pathAssembler.gradleHome(PathAssembler.GRADLE_USER_HOME_STRING, "somePath", new URI("http://server/gradle-0.9-some-branch-2010+1100-bin.zip"));
-        assertEquals(file(TEST_GRADLE_USER_HOME + "/somePath/gradle-0.9-some-branch-2010+1100"), gradleHome);
+        File distributionDir = pathAssembler.getDistribution(configuration).getDistributionDir();
+        assertThat(distributionDir.getName(), matchesRegexp("[a-z0-9]+"));
+        assertThat(distributionDir.getParentFile(), equalTo(file(currentDirPath() + "/somePath/gradle-0.9-bin")));
     }
 
     @Test
-    public void gradleHomeForUrlWithNoClassifier() throws Exception {
-        File gradleHome = pathAssembler.gradleHome(PathAssembler.GRADLE_USER_HOME_STRING, "somePath", new URI("http://server/gradle-0.9.zip"));
-        assertEquals(file(TEST_GRADLE_USER_HOME + "/somePath/gradle-0.9"), gradleHome);
-    }
+    public void distributionDirWithUnknownBase() throws Exception {
+        configuration.setDistribution(new URI("http://server/dist/gradle-1.0.zip"));
+        configuration.setDistributionBase("unknownBase");
 
-    @Test
-    public void gradleHomeForUrlWithNoVersion() throws Exception {
         try {
-            pathAssembler.gradleHome(PathAssembler.GRADLE_USER_HOME_STRING, "somePath", new URI("http://server/gradle-bin.zip"));
+            pathAssembler.getDistribution(configuration);
             fail();
         } catch (RuntimeException e) {
-            assertEquals("Cannot determine Gradle version from distribution URL 'http://server/gradle-bin.zip'.", e.getMessage());
+            assertEquals("Base: unknownBase is unknown", e.getMessage());
         }
     }
 
-    @Test(expected = RuntimeException.class)
-    public void gradleHomeWithUnknownBase() throws Exception {
-        pathAssembler.gradleHome("unknownBase", "somePath", new URI("http://server/gradle.zip"));
-    }
-
     @Test
     public void distZipWithGradleUserHomeBase() throws Exception {
-        File dist = pathAssembler.distZip(PathAssembler.GRADLE_USER_HOME_STRING, "somePath", new URI("http://server/dist/gradle.zip"));
-        assertEquals(file(TEST_GRADLE_USER_HOME + "/somePath/gradle.zip"), dist);
+        configuration.setDistribution(new URI("http://server/dist/gradle-1.0.zip"));
+
+        File dist = pathAssembler.getDistribution(configuration).getZipFile();
+        assertThat(dist.getName(), equalTo("gradle-1.0.zip"));
+        assertThat(dist.getParentFile().getName(), matchesRegexp("[a-z0-9]+"));
+        assertThat(dist.getParentFile().getParentFile(), equalTo(file(TEST_GRADLE_USER_HOME + "/somePath/gradle-1.0")));
     }
 
     @Test
     public void distZipWithProjectBase() throws Exception {
-        File dist = pathAssembler.distZip(PathAssembler.PROJECT_STRING, "somePath", new URI("http://server/dist/gradle.zip"));
-        assertEquals(file(currentDirPath() + "/somePath/gradle.zip"), dist);
-    }
+        configuration.setZipBase(PathAssembler.PROJECT_STRING);
+        configuration.setDistribution(new URI("http://server/dist/gradle-1.0.zip"));
 
-    @Test(expected = RuntimeException.class)
-    public void distZipWithUnknownBase() throws Exception {
-        pathAssembler.distZip("unknownBase", "somePath", new URI("http://server/dist/gradle.zip"));
+        File dist = pathAssembler.getDistribution(configuration).getZipFile();
+        assertThat(dist.getName(), equalTo("gradle-1.0.zip"));
+        assertThat(dist.getParentFile().getName(), matchesRegexp("[a-z0-9]+"));
+        assertThat(dist.getParentFile().getParentFile(), equalTo(file(currentDirPath() + "/somePath/gradle-1.0")));
     }
 
     private File file(String path) {
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
index fc53d84..93e7a8e 100644
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
@@ -26,11 +26,6 @@ class SystemPropertiesHandlerTest extends Specification {
     @Rule
     TemporaryFolder tmpDir = new TemporaryFolder()
 
-    def parsesCommandLineProperties() {
-        expect:
-        ['a.b': 'c', d: '', e: '', f: 'g'] == SystemPropertiesHandler.getSystemProperties(['-Da.b=c', 'arg', '-Pa=v', '-D', '-Dd', '-De=', '-Df=g'] as String[])
-    }
-
     def parsesPropertiesFile() {
         File propFile = tmpDir.file('props')
         Properties props = new Properties()
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy
new file mode 100644
index 0000000..ce088b5
--- /dev/null
+++ b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperExecutorTest.groovy
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2011 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.wrapper
+
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+class WrapperExecutorTest extends Specification {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    final Install install = Mock()
+    final BootstrapMainStarter start = Mock()
+    TestFile projectDir;
+    TestFile propertiesFile;
+    Properties properties = new Properties()
+
+    def setup() {
+        projectDir = tmpDir.dir
+        propertiesFile = tmpDir.file('gradle/wrapper/gradle-wrapper.properties')
+
+        properties.distributionUrl = 'http://server/test/gradle.zip'
+        properties.distributionBase = 'testDistBase'
+        properties.distributionPath = 'testDistPath'
+        properties.zipStoreBase = 'testZipBase'
+        properties.zipStorePath = 'testZipPath'
+        propertiesFile.parentFile.mkdirs()
+        propertiesFile.withOutputStream { properties.store(it, 'header') }
+    }
+
+    def "loads wrapper meta data from specified properties file"() {
+        def wrapper = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+
+        expect:
+        wrapper.distribution == new URI('http://server/test/gradle.zip')
+        wrapper.configuration.distribution == new URI('http://server/test/gradle.zip')
+        wrapper.configuration.distributionBase == 'testDistBase'
+        wrapper.configuration.distributionPath == 'testDistPath'
+        wrapper.configuration.zipBase == 'testZipBase'
+        wrapper.configuration.zipPath == 'testZipPath'
+    }
+
+    def "loads wrapper meta data from specified project directory"() {
+        def wrapper = WrapperExecutor.forProjectDirectory(projectDir, System.out)
+
+        expect:
+        wrapper.distribution == new URI('http://server/test/gradle.zip')
+        wrapper.configuration.distribution == new URI('http://server/test/gradle.zip')
+        wrapper.configuration.distributionBase == 'testDistBase'
+        wrapper.configuration.distributionPath == 'testDistPath'
+        wrapper.configuration.zipBase == 'testZipBase'
+        wrapper.configuration.zipPath == 'testZipPath'
+    }
+
+    def "uses default meta data when properties file does not exist in project directory"() {
+        def wrapper = WrapperExecutor.forProjectDirectory(tmpDir.file('unknown'), System.out)
+
+        expect:
+        wrapper.distribution == null
+        wrapper.configuration.distribution == null
+        wrapper.configuration.distributionBase == PathAssembler.GRADLE_USER_HOME_STRING
+        wrapper.configuration.distributionPath == Install.DEFAULT_DISTRIBUTION_PATH
+        wrapper.configuration.zipBase == PathAssembler.GRADLE_USER_HOME_STRING
+        wrapper.configuration.zipPath == Install.DEFAULT_DISTRIBUTION_PATH
+    }
+
+    def "properties file need contain only the distribution URL"() {
+        given:
+        def properties = new Properties()
+        properties.distributionUrl = 'http://server/test/gradle.zip'
+        propertiesFile.withOutputStream { properties.store(it, 'header') }
+        
+        def wrapper = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+
+        expect:
+        wrapper.distribution == new URI("http://server/test/gradle.zip")
+        wrapper.configuration.distribution == new URI("http://server/test/gradle.zip")
+        wrapper.configuration.distributionBase == PathAssembler.GRADLE_USER_HOME_STRING
+        wrapper.configuration.distributionPath == Install.DEFAULT_DISTRIBUTION_PATH
+        wrapper.configuration.zipBase == PathAssembler.GRADLE_USER_HOME_STRING
+        wrapper.configuration.zipPath == Install.DEFAULT_DISTRIBUTION_PATH
+    }
+
+    def "execute installs distribution and launches application"() {
+        def wrapper = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+        def installDir = tmpDir.file('install')
+
+        when:
+        wrapper.execute(['arg'] as String[], install, start)
+
+        then:
+        1 * install.createDist(wrapper.configuration) >> installDir
+        1 * start.start(['arg'] as String[], installDir)
+        0 * _._
+    }
+
+    def "fails when distribution not specified in properties file"() {
+        def properties = new Properties()
+        propertiesFile.withOutputStream { properties.store(it, 'header') }
+
+        when:
+        WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not load wrapper properties from '$propertiesFile'."
+        e.cause.message == "No value with key 'distributionUrl' specified in wrapper properties file '$propertiesFile'."
+    }
+
+    def "forWrapperPropertiesFile() fails when properties file does not exist"() {
+        def propertiesFile = tmpDir.file("unknown.properties")
+
+        when:
+        WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Wrapper properties file '$propertiesFile' does not exist."
+    }
+
+    def "allows old format of the wrapper properties"() {
+        given:
+        def properties = new Properties()
+
+        properties.distributionBase = 'oldDistBase'
+        properties.distributionPath = 'oldDistPath'
+        properties.zipStoreBase = 'oldZipBase'
+        properties.zipStorePath = 'oldZipPath'
+
+        properties.urlRoot="http://gradle.artifactoryonline.com/gradle/distributions"
+        properties.distributionVersion="1.0-milestone-3"
+        properties.distributionName="gradle"
+        properties.distributionClassifier="bin"
+
+        propertiesFile.withOutputStream { properties.store(it, 'header') }
+
+        and:
+        def out = new StringWriter()
+
+        when:
+        def wrapper = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, out)
+
+        then:
+        wrapper.distribution == new URI("http://gradle.artifactoryonline.com/gradle/distributions/gradle-1.0-milestone-3-bin.zip")
+        wrapper.configuration.distribution == new URI("http://gradle.artifactoryonline.com/gradle/distributions/gradle-1.0-milestone-3-bin.zip")
+        wrapper.configuration.distributionBase == 'oldDistBase'
+        wrapper.configuration.distributionPath == 'oldDistPath'
+        wrapper.configuration.zipBase == 'oldZipBase'
+        wrapper.configuration.zipPath == 'oldZipPath'
+
+        and:
+        out.toString().trim() == "Wrapper properties file '$propertiesFile' contains deprecated entries 'urlRoot', 'distributionName', 'distributionVersion' and 'distributionClassifier'. These will be removed soon. Please use 'distributionUrl' instead."
+    }
+
+    def "reports error when none of the valid formats are met"() {
+        given:
+        def properties = new Properties()
+
+        properties.distributionBase = 'testDistBase'
+        properties.distributionPath = 'testDistPath'
+        properties.zipStoreBase = 'testZipBase'
+        properties.zipStorePath = 'testZipPath'
+
+        propertiesFile.withOutputStream { properties.store(it, 'header') }
+
+        when:
+        WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+
+        then:
+        Exception e = thrown()
+        e.cause.message == "No value with key 'distributionUrl' specified in wrapper properties file '$propertiesFile'."
+    }
+
+    def "supports relative distribution url"() {
+        given:
+        properties.distributionUrl = 'some/relative/url/to/bin.zip'
+        propertiesFile.withOutputStream { properties.store(it, 'header') }
+
+        when:
+        WrapperExecutor wrapper = WrapperExecutor.forWrapperPropertiesFile(propertiesFile, System.out)
+
+        then:
+        //distribution uri should resolve into absolute path
+        wrapper.distribution.schemeSpecificPart != 'some/relative/url/to/bin.zip'
+        wrapper.distribution.schemeSpecificPart.endsWith 'some/relative/url/to/bin.zip'
+    }
+}
diff --git a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperTest.groovy b/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperTest.groovy
deleted file mode 100644
index e8627ca..0000000
--- a/subprojects/wrapper/src/test/groovy/org/gradle/wrapper/WrapperTest.groovy
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2011 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.wrapper
-
-import spock.lang.Specification
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-import org.gradle.util.TestFile
-import org.gradle.util.SetSystemProperties
-
-class WrapperTest extends Specification {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-    @Rule
-    public final SetSystemProperties systemProperties = new SetSystemProperties();
-    final Install install = Mock()
-    final BootstrapMainStarter start = Mock()
-    TestFile projectDir;
-    TestFile propertiesFile;
-
-    def setup() {
-        projectDir = tmpDir.dir
-        propertiesFile = tmpDir.file('gradle/wrapper/gradle-wrapper.properties')
-        def properties = new Properties()
-        properties.distributionUrl = 'http://server/test/gradle.zip'
-        properties.distributionBase = 'testDistBase'
-        properties.distributionPath = 'testDistPath'
-        properties.zipStoreBase = 'testZipBase'
-        properties.zipStorePath = 'testZipPath'
-        propertiesFile.parentFile.mkdirs()
-        propertiesFile.withOutputStream { properties.store(it, 'header') }
-        System.setProperty(Wrapper.WRAPPER_PROPERTIES_PROPERTY, propertiesFile.absolutePath)
-    }
-
-    def "uses system property to locate properties file"() {
-        def wrapper = new Wrapper()
-
-        expect:
-        wrapper.distribution == new URI('http://server/test/gradle.zip')
-    }
-
-    def "loads wrapper meta data from project directory"() {
-        def wrapper = new Wrapper(projectDir)
-
-        expect:
-        wrapper.distribution == new URI('http://server/test/gradle.zip')
-    }
-
-    def "can query for distribution when properties file does not exist"() {
-        def wrapper = new Wrapper(tmpDir.file('unknown'))
-
-        expect:
-        wrapper.distribution == null
-    }
-
-    def "execute installs distribution and launches application"() {
-        def wrapper = new Wrapper()
-        def installDir = tmpDir.file('install')
-
-        when:
-        wrapper.execute(['arg'] as String[], install, start)
-
-        then:
-        1 * install.createDist(new URI('http://server/test/gradle.zip'), 'testDistBase', 'testDistPath', 'testZipBase', 'testZipPath') >> installDir
-        1 * start.start(['arg'] as String[], installDir)
-        0 * _._
-    }
-
-    def "fails when distribution not specified"() {
-        def properties = new Properties()
-        propertiesFile.withOutputStream { properties.store(it, 'header') }
-
-        when:
-        new Wrapper()
-
-        then:
-        RuntimeException e = thrown()
-        e.message == "Could not load wrapper properties from '$propertiesFile'."
-        e.cause.message == "No value with key 'distributionUrl' specified in wrapper properties file '$propertiesFile'."
-    }
-
-    def "execute fails when properties file does not exist"() {
-        propertiesFile.delete()
-        def wrapper = new Wrapper()
-
-        when:
-        wrapper.execute(['arg'] as String[], install, start)
-
-        then:
-        FileNotFoundException e = thrown()
-        e.message == "Wrapper properties file '$propertiesFile' does not exist."
-    }
-}
diff --git a/subprojects/wrapper/wrapper.gradle b/subprojects/wrapper/wrapper.gradle
index 86b4358..44a2582 100644
--- a/subprojects/wrapper/wrapper.gradle
+++ b/subprojects/wrapper/wrapper.gradle
@@ -13,27 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-apply plugin: 'groovy'
+apply from: "$rootDir/gradle/classycle.gradle"
 
 dependencies {
-    groovy libraries.groovy_depends
-    publishCompile project(':core')
-
-    compile libraries.commons_io, libraries.ant
-
-    testCompile project(path: ':core', configuration: 'testFixtures')
-    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    groovy libraries.groovy
+    compile project(":cli")
+    testCompile libraries.ant
 }
 
-task wrapperJar(type: Jar) {
-    dependsOn compileJava, processResources
-    from sourceSets.main.classesDir
-    include 'org/gradle/wrapper/**'
+task executableJar(type: Jar) {
     archiveName = 'gradle-wrapper.jar'
-    destinationDir = sourceSets.main.classesDir
-    manifest.mainAttributes("Main-Class": 'org.gradle.wrapper.GradleWrapperMain')
+    from sourceSets.main.output
+    from configurations.runtime.allDependencies.withType(ProjectDependency).collect { it.dependencyProject.sourceSets.main.output }
 }
 
-classes {
-    dependsOn wrapperJar
-}
+useTestFixtures()
+

-- 
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